refactor(tools): let the terminal marker ride the nested result
The staged-promotion machinery (concludingParents keyed by parent token plus a pendingParentConclusions staging map) spread one fact — this call concluded the turn — across three registry-side collections with manual cleanup. Align it with how additionalContexts already crosses the same boundary: concludeTurn() marks only its own execution, the marker rides that execution's successful result (ToolExecutionFailure types concludesTurn as never, so a policy-converted failure sheds it with the type), and the composite that owns the nested dispatch forwards it — Code Mode's binding does so beside its existing context forwarding. The registry loses both parent-keyed collections and the promotion block; the propagation decision moves to the owning boundary; the structured-output consumer's own two-phase commit is untouched.
This commit is contained in:
@@ -417,6 +417,46 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect(result.content).toEqual([{ type: 'text', text: 'done' }])
|
||||
})
|
||||
|
||||
it('forwards a nested terminal conclusion onto the successful run_code result', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'finalize',
|
||||
description: 'Terminal tool.',
|
||||
parameters: {},
|
||||
output: {
|
||||
schema: { type: 'string' },
|
||||
render: (_args, value) => [{ type: 'text', text: value }],
|
||||
},
|
||||
execute(_args, exec) {
|
||||
exec.concludeTurn()
|
||||
return Promise.resolve('done')
|
||||
},
|
||||
}))
|
||||
runtime.behavior = async (request) => {
|
||||
await request.bindings[0]!.functions.finalize!({})
|
||||
return { logs: [], value: 'program complete' }
|
||||
}
|
||||
|
||||
const concluded = await runCode(ctx, 'await tools.finalize({})')
|
||||
expect(concluded.isError).toBe(false)
|
||||
expect(concluded.concludesTurn).toBe(true)
|
||||
|
||||
// A policy that converts the nested success into an error strips the
|
||||
// marker with the result type: the recovering program cannot conclude.
|
||||
const veto = ctx.on('tools/post-execute', async (exec, _result, next): Promise<PostToolDecision> => {
|
||||
if (exec.name !== 'finalize') return next()
|
||||
return { kind: 'block', feedback: [{ type: 'text', text: 'terminal rejected' }] }
|
||||
})
|
||||
runtime.behavior = async (request) => {
|
||||
await request.bindings[0]!.functions.finalize!({}).catch(() => undefined)
|
||||
return { logs: [], value: 'recovered' }
|
||||
}
|
||||
const recovered = await runCode(ctx, 'await tools.finalize({}).catch(() => {})')
|
||||
veto()
|
||||
expect(recovered.isError).toBe(false)
|
||||
expect(recovered.concludesTurn).toBeUndefined()
|
||||
})
|
||||
|
||||
it('serializes Promise.all dispatches: tool executions never overlap, in submission order', async () => {
|
||||
const { ctx, runtime } = await setup({ mode: 'code' })
|
||||
const intervals: [string, string][] = []
|
||||
|
||||
Reference in New Issue
Block a user