refactor(agent): unify sourced message delivery

This commit is contained in:
_Kerman
2026-07-24 22:38:50 +08:00
parent 009d113e0e
commit 992cf894af
197 changed files with 1890 additions and 2132 deletions

View File

@@ -36,7 +36,7 @@ Depth enforcement is internal to `startInProcessRun`: it reads the parent depth
- An order-190 system-prompt section tells the child that the tool call is the terminal answer.
- Both contributions are ordinary child-scoped registrations. An expert `system-prompt/assemble` listener may replace them and therefore owns preserving the structured-output protocol for that child.
- A `tools/result` observer commits a staged value only after that execution's authoritative final tool result succeeds, including the enclosing `run_code` result for Code Mode sub-dispatch.
- A monotonic tool guard blocks later calls after capture, and `agent/turn-stop` ends the turn after the structured result commits.
- A monotonic tool guard blocks later calls after capture, and the structured-output execution's `concludeTurn()` marker ends the turn after the result commits.
A clean turn that never commits the required structured value reports `error`; the driver does not re-prompt. All registrations ride the child fiber and disappear with it.

View File

@@ -141,7 +141,7 @@ export async function startInProcessRun(
const result: Promise<SubagentResult> = (async () => {
try {
child.followup(request.prompt)
child.followup({ content: request.prompt, source: { kind: 'user' } })
await child.whenIdle()
return readResult(
child,

View File

@@ -463,7 +463,7 @@ describe('in-process structured output', () => {
textResponse('parent answer'),
toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { answer: 1 }),
])
parent.followup([{ type: 'text', text: 'hello' }])
parent.followup({ content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' } })
await parent.whenIdle()
expect(adapter.requests[0]!.system ?? '').not.toContain(STRUCTURED_OUTPUT_INSTRUCTION)
const run = await ctx.subagents.start('spawn', structuredRequest(parent))
@@ -479,7 +479,7 @@ describe('in-process structured output', () => {
describe('scoped registration (each child owns its capture tool)', () => {
it('a plain agent never sees the tool: nothing is registered globally at all', async () => {
const { ctx, parent, adapter } = await setup([textResponse('parent answer')])
parent.followup([{ type: 'text', text: 'hello' }])
parent.followup({ content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' } })
await parent.whenIdle()
// Scoped registration: the global view has no capture tool, ever.
expect(ctx.tools.get(STRUCTURED_OUTPUT_TOOL)).toBeUndefined()
@@ -493,7 +493,7 @@ describe('in-process structured output', () => {
// Child turn: must see it, with the run's schema.
toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { answer: 42 }),
])
parent.followup([{ type: 'text', text: 'hello' }])
parent.followup({ content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' } })
await parent.whenIdle()
expect(toolNames(adapter.requests[0]!)).not.toContain(STRUCTURED_OUTPUT_TOOL)
@@ -571,7 +571,7 @@ describe('in-process structured output', () => {
it('a non-structured agent request keeps tools ABSENT when it had none (no tools: [] materialized)', async () => {
const { parent, adapter } = await setup([textResponse('plain')])
parent.followup([{ type: 'text', text: 'q' }])
parent.followup({ content: [{ type: 'text', text: 'q' }], source: { kind: 'user' } })
await parent.whenIdle()
const request = adapter.requests[0]!
expect(request.tools).toBeUndefined()

View File

@@ -87,7 +87,7 @@ describe('startInProcessRun', () => {
it('seeds a forked child but reads only the child-owned output', async () => {
const { ctx, parent } = await setup([textResponse('parent answer'), textResponse('child answer')])
parent.followup([{ type: 'text', text: 'parent question' }])
parent.followup({ content: [{ type: 'text', text: 'parent question' }], source: { kind: 'user' } })
await parent.whenIdle()
const seed = parent.session.events.slice()
const run = await startInProcessRun(request(parent), { seed })