test: close the per-file coverage gaps for the scoping surface

Every subject-extractor row of the invariants carrier table is exercised
with a matching and a foreign-keyed carrier; the HMR re-apply seed path
(sessions of agents that predate the plugin are marked started) is pinned;
the scoped tool-provider disposal, plural restrict() validation, singular
scopeHost absentee, tool-subagent passthrough, stale-stage drop, and
disposing-parent spawn (INACTIVE_EFFECT, no orphan) each gain their test.
Two genuinely defensive branches carry justified v8-ignore markers.
This commit is contained in:
Tianyi Cui
2026-07-09 03:41:37 +08:00
parent cc24e79cd2
commit e7b712453a
10 changed files with 169 additions and 3 deletions

View File

@@ -203,6 +203,8 @@ export function startInProcessRun(
try {
unlink = request.parent.ctx.effect(() => () => handle.dispose())
} catch (error: unknown) {
// Fire-and-forget: start() must rethrow synchronously; the child's
// teardown (stop → unregister → detach) reaches quiescence on its own.
void handle.dispose()
throw error
}

View File

@@ -184,6 +184,9 @@ export function attachStructuredRuntime(childCtx: Context, schema: StructuredOut
if (decision.kind === 'accept') captured = { value: staged.value }
return decision
} finally {
/* v8 ignore next -- defensive false branch: a concurrent re-stage
* would need a second capture call INSIDE the first's post-execute
* chain */
if (pending === staged) pending = undefined
}
}, { prepend: true })

View File

@@ -489,4 +489,45 @@ describe('in-process structured output', () => {
expect(result.isError).toBe(true)
expect(result.error?.code).toBe('UNKNOWN_TOOL')
})
it('drops a stale stage from a short-circuited chain: a later call never promotes it (call-keyed commit)', async () => {
const { ctx, parent } = await setup([
toolCallResponse('c1', STRUCTURED_OUTPUT_TOOL, { answer: 1 }),
])
const run = ctx.subagents.start('spawn', structuredRequest(parent))
const child = ctx.agents.get(run.id)!
// An OUTER post-execute listener (registered after attach, prepend ⇒
// outermost) that BLOCKS the first capture WITHOUT delegating: the commit
// listener never runs for c1, so its staged value would linger.
let blocks = 1
ctx.on('tools/post-execute', (exec, _result, next) => {
if (exec.name === STRUCTURED_OUTPUT_TOOL && blocks > 0) {
blocks -= 1
return Promise.resolve({ kind: 'block' as const, feedback: [{ type: 'text' as const, text: 'rejected' }] })
}
return next()
}, { prepend: true })
const result = await run.result
// The blocked capture must NOT surface as structured success…
expect(result.stopReason).toBe('error')
expect(result.structured).toBeUndefined()
// …and a LATER invalid call (its own body staged nothing) must not
// resurrect c1's orphaned value: drive the pipeline directly.
const invalid = await ctx.tools.execute({
callId: 'c2' as never,
name: STRUCTURED_OUTPUT_TOOL,
arguments: { answer: 'not-a-number' },
agent: child,
})
expect(invalid.isError).toBe(true)
// A fresh valid call still captures ITS OWN value.
const valid = await ctx.tools.execute({
callId: 'c3' as never,
name: STRUCTURED_OUTPUT_TOOL,
arguments: { answer: 9 },
agent: child,
})
expect(valid.isError).toBeFalsy()
await run.dispose()
})
})