Merge latest master into worktree-llm-web-config

This commit is contained in:
Yichen Jiang
2026-07-31 01:09:50 +08:00
12 changed files with 188 additions and 22 deletions

View File

@@ -1388,14 +1388,14 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
const spec = PERMISSION_PRESETS[preset]
if (preset === '') {
const current = permissionSelectOf(logOf(id)).currentValue
append(id, { type: 'command/done', data: { commandId, kind: 'success', text: `Current permission preset: ${current}. Available: ${Object.keys(PERMISSION_PRESETS).join(', ')}.` } })
append(id, { type: 'command/done', data: { commandId, kind: 'success', text: `current preset ${current} (available: ${Object.keys(PERMISSION_PRESETS).join(', ')})` } })
} else if (spec === undefined) {
append(id, { type: 'command/done', data: { commandId, kind: 'error', text: `unknown permission preset ${JSON.stringify(preset)} (available: ${Object.keys(PERMISSION_PRESETS).join(', ')})` } })
append(id, { type: 'command/done', data: { commandId, kind: 'error', text: `unknown preset "${preset}" (available: ${Object.keys(PERMISSION_PRESETS).join(', ')})` } })
} else {
if (permissionSelectOf(logOf(id)).currentValue !== preset) append(id, { type: 'permission/preset', data: { preset } })
append(id, { type: 'sandbox/mode', data: { mode: spec.sandbox } })
append(id, { type: 'approval/policy', data: { policy: spec.approval } })
append(id, { type: 'command/done', data: { commandId, kind: 'success', text: `Permission preset: ${preset}.` } })
append(id, { type: 'command/done', data: { commandId, kind: 'success', text: `preset ${preset}` } })
}
return ok(request, { matched: true as const, commandId })
}

View File

@@ -135,7 +135,7 @@ describe('live event path', () => {
expect(session.getSnapshot().composerPhase).toBe('blank')
const feed = (event: SessionEvent) => { session.handleMuxEnvelope('r' as never, { type: 'session/event', sessionId: SID, event }) }
feed(ev.commandRun(0, 'cmd-perm', 'permission', ' danger-full-access'))
feed(ev.commandDone(1, 'cmd-perm', 'success', 'Permission preset: danger-full-access.'))
feed(ev.commandDone(1, 'cmd-perm', 'success', 'preset danger-full-access'))
const snapshot = session.getSnapshot()
expect(snapshot.nodes.at(-1)).toMatchObject({ kind: 'command', name: 'permission' })
expect(snapshot.composerPhase).toBe('blank')

View File

@@ -1,6 +1,6 @@
// GenericCommandCard: the default command row — a stripped-down
// GenericToolCard rendering the dispatched command line and the settlement
// text. Supplied by the chat view as the keyed commandview slot's render-site
// GenericToolCard rendering the command name and its settlement text.
// Supplied by the chat view as the keyed commandview slot's render-site
// fallback (an unregistered command name lands here); registrants may compose
// it as a base, feeding the same owner payload through.
@@ -20,10 +20,11 @@ export function GenericCommandCard({ node }: CommandRowOwnerProps) {
const summary = node.outcome === null
? '执行中…'
: text ?? (node.outcome.kind === 'error' ? '命令失败' : '已完成')
// Display line rebuilt from the structured payload (args carries its own
// separator whitespace verbatim); a cross-window node whose run page fell
// out of the window has neither.
const title = node.name === null ? '命令' : `/${node.name}${node.args ?? ''}`
// Title is the bare command name: the row already reads `name · outcome`,
// and the dispatched line's own `/` and arguments only restate what the
// settlement text says (`permission · preset workspace-write`). A
// cross-window node whose run page fell out of the window has no name.
const title = node.name ?? '命令'
return (
<ToolRow
variant="others"

View File

@@ -460,10 +460,14 @@ describe('ChatView', () => {
name: 'plan', args: '', outcome: { kind: 'success', text: '已进入 plan mode' },
...over,
})
// Settled success: the command line is the title, the outcome text the summary.
const settled = makeHarness({ nodes: [user(1, 'hi'), command({})] })
// Settled success: the bare command name is the title, the outcome text
// the summary — neither the dispatched `/` nor its arguments reach the row
// (the settlement text already says what the command did).
const settled = makeHarness({ nodes: [user(1, 'hi'), command({ args: ' now' })] })
const view = render(<settled.ChatView {...settled.props} />)
expect(view.getByText('/plan')).toBeTruthy()
expect(view.getByText('plan')).toBeTruthy()
expect(view.queryByText('/plan')).toBeNull()
expect(view.queryByText('/plan now')).toBeNull()
expect(view.getByText('已进入 plan mode')).toBeTruthy()
// Error outcome flips the row state; a text-less error gets the default copy.