fix(feedback): keep payload in feedback event
This commit is contained in:
@@ -304,9 +304,9 @@ function viewFor(event: SessionEvent, log: readonly SessionEvent[]): ToolEventVi
|
||||
|
||||
/**
|
||||
* Fixture parallel of the plan unit's double-event fold: `command/run`
|
||||
* records named `plan` set the wanted target (`off` → false, else true);
|
||||
* `plan/mode` commits and clears it. `wanted` is exposed for the prompt
|
||||
* boundary (the fixture's agent/step parallel).
|
||||
* records named `plan` with recorded input set the wanted target (`off` →
|
||||
* false, else true); `plan/mode` commits and clears it. `wanted` is exposed
|
||||
* for the prompt boundary (the fixture's agent/step parallel).
|
||||
*/
|
||||
function foldPlan(log: readonly SessionEvent[]): { active: boolean; pending: boolean; wanted: boolean | null } {
|
||||
let active = false
|
||||
@@ -315,7 +315,8 @@ function foldPlan(log: readonly SessionEvent[]): { active: boolean; pending: boo
|
||||
const item = event as unknown as { type: string; data?: Record<string, unknown> }
|
||||
if (item.type === 'command/run' && item.data?.['name'] === 'plan') {
|
||||
const args = item.data['args']
|
||||
wanted = (typeof args === 'string' ? args : '').trim() !== 'off'
|
||||
if (typeof args !== 'string') continue
|
||||
wanted = args.trim() !== 'off'
|
||||
} else if (item.type === 'plan/mode') {
|
||||
active = item.data?.['active'] === true
|
||||
wanted = null
|
||||
@@ -374,8 +375,9 @@ function projectionFramesOf(id: SessionId, log: readonly SessionEvent[], event:
|
||||
}]
|
||||
}
|
||||
// The plan unit advances on its two folded event kinds.
|
||||
const commandData = event as unknown as { data: { name?: string; args?: unknown } }
|
||||
if (type === 'plan/mode' || (type === 'command/run'
|
||||
&& (event as unknown as { data: { name?: string } }).data.name === 'plan')) {
|
||||
&& commandData.data.name === 'plan' && typeof commandData.data.args === 'string')) {
|
||||
return [{
|
||||
type: 'session/projection',
|
||||
sessionId: id,
|
||||
|
||||
@@ -140,7 +140,10 @@ export interface CommandNode {
|
||||
commandId: CommandId
|
||||
/** Command name (run payload's structured field); null when the run fell outside the window. */
|
||||
name: string | null
|
||||
/** Verbatim rawInput after the name, separator whitespace included (run payload); null when the run fell outside the window. */
|
||||
/**
|
||||
* Verbatim rawInput after the name, including separator whitespace; null
|
||||
* when omitted by the command or when the run fell outside the window.
|
||||
*/
|
||||
args: string | null
|
||||
/** Settlement outcome (done payload); null while the command is still executing. */
|
||||
outcome: { kind: 'success' | 'error'; text?: string } | null
|
||||
|
||||
@@ -234,10 +234,10 @@ export class FoldAdapter {
|
||||
// enter the client program, so this wire consumer narrows structurally
|
||||
// (the same posture as tool/code-dispatch in session.ts).
|
||||
if ((event.type as string) === 'command/run') {
|
||||
const data = event.data as unknown as { commandId: CommandId; name: string; args: string }
|
||||
const data = event.data as unknown as { commandId: CommandId; name: string; args?: string }
|
||||
this.commandIdx.set(data.commandId, {
|
||||
kind: 'command', seq: event.seq, time: event.time,
|
||||
commandId: data.commandId, name: data.name, args: data.args, outcome: null,
|
||||
commandId: data.commandId, name: data.name, args: data.args ?? null, outcome: null,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ export const ev = {
|
||||
at(seq, { type: 'turn/end', data: { turn, reason: { kind: reason } } }),
|
||||
commandRun: (seq: number, commandId: string, name: string, args = ''): SessionEvent =>
|
||||
at(seq, { type: 'command/run', data: { commandId, name, args, source: { kind: 'user' } } }),
|
||||
commandRunWithoutInput: (seq: number, commandId: string, name: string): SessionEvent =>
|
||||
at(seq, { type: 'command/run', data: { commandId, name, source: { kind: 'user' } } }),
|
||||
commandDone: (seq: number, commandId: string, kind: 'success' | 'error' = 'success', text?: string): SessionEvent =>
|
||||
at(seq, { type: 'command/done', data: { commandId, kind, ...text === undefined ? {} : { text } } }),
|
||||
}
|
||||
|
||||
@@ -195,6 +195,14 @@ describe('FoldAdapter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('represents command input omitted by the host as null', () => {
|
||||
const adapter = new FoldAdapter()
|
||||
adapter.reset([ev.commandRunWithoutInput(0, 'cmd-private', 'feedback')], 0)
|
||||
expect(adapter.nodes().nodes[0]).toMatchObject({
|
||||
kind: 'command', name: 'feedback', args: null, outcome: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('soft-falls a done-only window into a node built from the done (cross-window cut)', () => {
|
||||
const adapter = new FoldAdapter()
|
||||
adapter.reset([ev.commandDone(80, 'cmd-3', 'error', '失败了')], 80)
|
||||
|
||||
@@ -21,8 +21,8 @@ export function GenericCommandCard({ node }: CommandRowOwnerProps) {
|
||||
? '执行中…'
|
||||
: 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.
|
||||
// separator whitespace verbatim); omitted input and a cross-window node
|
||||
// whose run page fell out both render without it.
|
||||
const title = node.name === null ? '命令' : `/${node.name}${node.args ?? ''}`
|
||||
return (
|
||||
<ToolRow
|
||||
|
||||
Reference in New Issue
Block a user