feat: command.execute returns the lifecycle pairing id ({matched, commandId?})
CommandService.execute now returns a CommandExecution — the normalized result plus the commandId minted for its command/run/command/done records — and the wire admission value carries commandId exactly when matched, so the issuing client can correlate its RPC acknowledgment with the flow node the lifecycle events produce. apiproxy api/schema/handler, the connection fixture, and the TUI/plan/goal consumers follow the new shape.
This commit is contained in:
@@ -115,14 +115,15 @@ describe('command.execute', () => {
|
||||
const api = createApiProxy(ctx, DEFAULTS)
|
||||
const agent = stubAgent(ctx)
|
||||
const value = expectOk(await api.commands.execute(request({ sessionId: agent.id, line: '/goal ship it' }), new AbortController().signal))
|
||||
expect(value).toEqual({ matched: true })
|
||||
expect(value).toMatchObject({ matched: true })
|
||||
expect(value.commandId).toBeTruthy()
|
||||
expect(received).toBe(' ship it')
|
||||
// Pure admission on the wire: the outcome rides the durably logged
|
||||
// lifecycle pair instead of the response.
|
||||
const lifecycle = agent.session.events.filter(e => e.type === 'command/run' || e.type === 'command/done')
|
||||
expect(lifecycle).toMatchObject([
|
||||
{ type: 'command/run', data: { name: 'goal', args: ' ship it' } },
|
||||
{ type: 'command/done', data: { kind: 'success', text: `goal:${agent.id}` } },
|
||||
{ type: 'command/run', data: { commandId: value.commandId, name: 'goal', args: ' ship it' } },
|
||||
{ type: 'command/done', data: { commandId: value.commandId, kind: 'success', text: `goal:${agent.id}` } },
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ function fakeApi(overrides: Partial<{ muxFrames: MuxFrame[]; hostFrames: HostFra
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'cancelled', message: 'aborted', details: {} } } }
|
||||
}
|
||||
if (request.payload.line.startsWith('/plan')) {
|
||||
return { rpcId: request.rpcId, result: { ok: true, value: { matched: true } } }
|
||||
return { rpcId: request.rpcId, result: { ok: true, value: { matched: true, commandId: 'cmd-x' } } }
|
||||
}
|
||||
return { rpcId: request.rpcId, result: { ok: true, value: { matched: false } } }
|
||||
},
|
||||
@@ -163,7 +163,7 @@ describe('unary round trip (handler ⇄ client, no network)', () => {
|
||||
const list = await c.commands.list({ sessionId: 's' as never })
|
||||
expect(list.result).toEqual({ ok: true, value: { commands: [{ name: 'plan', description: 'Toggle plan mode', input: { hint: 'on|off' } }] } })
|
||||
const hit = await c.commands.execute({ sessionId: 's' as never, line: '/plan off' })
|
||||
expect(hit.result).toEqual({ ok: true, value: { matched: true } })
|
||||
expect(hit.result).toEqual({ ok: true, value: { matched: true, commandId: 'cmd-x' } })
|
||||
const miss = await c.commands.execute({ sessionId: 's' as never, line: '/nope' })
|
||||
expect(miss.result).toEqual({ ok: true, value: { matched: false } })
|
||||
const skills = await c.skills.list({ sessionId: 's' as never })
|
||||
|
||||
@@ -215,9 +215,12 @@ describe('commands domain schemas', () => {
|
||||
expect(() => commandExecuteRequestSchema.parse({ line: '/compact' })).toThrow()
|
||||
expect(() => commandExecuteRequestSchema.parse({ sessionId: 's1' })).toThrow()
|
||||
expect(commandExecuteValueSchema.parse({ matched: false })).toEqual({ matched: false })
|
||||
// Pure admission: the value carries only the matched bit (outcomes ride
|
||||
// the logged lifecycle events, never this response).
|
||||
// Pure admission: matched plus the optional lifecycle pairing id
|
||||
// (outcomes ride the logged lifecycle events, never this response).
|
||||
expect(commandExecuteValueSchema.parse({ matched: true, commandId: 'cmd-1' }))
|
||||
.toEqual({ matched: true, commandId: 'cmd-1' })
|
||||
expect(commandExecuteValueSchema.parse({ matched: true })).toEqual({ matched: true })
|
||||
expect(() => commandExecuteValueSchema.parse({ matched: true, commandId: '' })).toThrow()
|
||||
expect(() => commandExecuteValueSchema.parse({})).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user