fix: close three seams the message-machine refactor left open

agent-loop lifecycle: dispose drains machine.done to true quiescence.
cancel()'s own running-to-idle transition can legitimately re-enter
through an automation listener (goal-session's idle drive runs
synchronously to its first await) and replace done with a fresh
admission after the single capture; teardown now re-cancels and
re-awaits until the slot stabilizes, so the scope never unwinds under a
live run.

tools: a nested concludeTurn() stages on its own execution and promotes
to the enclosing composite only on the call's authoritative successful
verdict. A post-execute policy that converts the nested success into an
error no longer lets a recovering composite stop the turn on a failed
terminal operation (the Code Mode structured-output shape).

goal-session: the driver owns its round durability barrier again. The
loop's persistence is eager write-behind with no turn-end flush, so the
old post-turn agent/error signal for flush failures never fires; a
settled round now sets needsCheckpoint and re-enters drive, flushing
before the next reservation and disarming on failure instead of queueing
an autonomous round on state that was never persisted.
This commit is contained in:
_Kerman
2026-07-26 20:50:02 +08:00
parent 338da9f2e0
commit 2a51ef85fb
6 changed files with 161 additions and 3 deletions

View File

@@ -553,6 +553,54 @@ describe('ToolRegistry', () => {
expect(nested.isError ? undefined : nested.value).toBe('')
})
it('propagates a nested concludeTurn only when the nested verdict stays successful', async () => {
const ctx = await setup()
ctx.tools.register({
...echoTool,
name: 'terminal-nested',
async execute(_args, exec) {
exec.concludeTurn()
return 'staged'
},
})
// A composite that swallows its nested failure and returns success — the
// Code Mode shape the staged propagation exists for.
let call = 0
ctx.tools.register({
...echoTool,
name: 'composite',
async execute(_args, exec) {
call += 1
const nested = await ctx.tools.execute({
signal: exec.signal, callId: CallId(`nested-${call}`), name: 'terminal-nested', arguments: {}, parent: exec.token,
})
return nested.isError ? 'nested failed, composite recovered' : 'nested succeeded'
},
})
// A policy converts the nested success into an error: the staged
// conclusion must NOT reach the composite, or the loop would stop the
// turn on a failed terminal operation.
const veto = ctx.on('tools/post-execute', async (exec, _result, next): Promise<PostToolDecision> => {
if (exec.name !== 'terminal-nested') return next()
return { kind: 'block', feedback: [{ type: 'text', text: 'nested success rejected' }] }
})
const recovered = await ctx.tools.execute({
signal: testToolSignal, callId: CallId('composite-vetoed'), name: 'composite', arguments: {},
})
expect(recovered.isError).toBe(false)
expect(recovered.concludesTurn).toBeUndefined()
veto()
// The same nested call succeeding promotes the marker: the composite's
// own successful result now carries concludesTurn.
const concluded = await ctx.tools.execute({
signal: testToolSignal, callId: CallId('composite-ok'), name: 'composite', arguments: {},
})
expect(concluded.isError).toBe(false)
expect(concluded.concludesTurn).toBe(true)
})
it('returns isError results for unknown tools and throwing tools', async () => {
const ctx = await setup()
ctx.tools.register({