fix(tool-subagent): enforce depth only at runtime

This commit is contained in:
Tianyi Cui
2026-07-20 17:58:57 +08:00
parent c40e63d04b
commit 46d60d6d90
26 changed files with 192 additions and 302 deletions
@@ -16,7 +16,6 @@ import {
parseToolSchemasSnapshot,
refreshFixtureReplacements,
sessionFixtureNames,
applyChildToolOmissions,
restorePinnedToolSchemas,
stabilizeRefreshLog,
unknownToolCallIds,
@@ -48,10 +47,6 @@ const RECORD_SRC = fileURLToPath(new URL('./fixtures/record-suite', import.meta.
const REPLAY_SCENARIOS: Scenario[] = [
{ name: 'pin-turn', hasModelTurn: true, recorded: true, pinsHeader: true, expectedHeaderChanges: 1, headerClass: 'main' },
{ name: 'plain-turn', hasModelTurn: true, recorded: true, headerClass: 'main', configPath: AGENT.configPath },
// Two scripted children under a declared omission: one omits t1 (header pin
// minus the declared tool, prompt pin skipped), one keeps the full set (pin
// and prompt compared verbatim) — the childToolOmissions branches.
{ name: 'child-omission', hasModelTurn: true, recorded: false, headerClass: 'main', childToolOmissions: ['t1'] },
{ name: 'no-model', hasModelTurn: false, recorded: false, headerClass: 'main' },
{ name: 'blocked-log', hasModelTurn: false, comparesLog: true, recorded: false, headerClass: 'main' },
{ name: 'authored-error', hasModelTurn: true, recorded: false, overridden: true, headerClass: 'main' },
@@ -371,37 +366,6 @@ describe('tool-schema snapshots', () => {
})
})
describe('applyChildToolOmissions', () => {
const pinned = { system: 's', tools: [{ name: 'bash' }, { name: 'subagent' }, { name: 'subagent_fork' }] }
it('removes exactly the declared tools the child actually omitted', () => {
const actual = { system: 's', tools: [{ name: 'bash' }, { name: 'subagent_fork' }] }
expect(applyChildToolOmissions(pinned, actual, ['subagent', 'subagent_fork']))
.toEqual({ system: 's', tools: [{ name: 'bash' }, { name: 'subagent_fork' }] })
})
it('keeps a declared tool the child still carries and an undeclared omission', () => {
// The child omitted `bash` (undeclared) — the expectation keeps it, so the
// equality assertion downstream still fails loudly on the real divergence.
const actual = { system: 's', tools: [{ name: 'subagent' }, { name: 'subagent_fork' }] }
expect(applyChildToolOmissions(pinned, actual, ['subagent']))
.toEqual(pinned)
})
it('tolerates a headerless tool list and unnamed tool entries', () => {
expect(applyChildToolOmissions({ system: 's' }, { tools: 'not-an-array' }, ['subagent']))
.toEqual({ system: 's', tools: [] })
const unnamed = { system: 's', tools: [{ name: 42 }] }
expect(applyChildToolOmissions(unnamed, { tools: [] }, ['subagent'])).toEqual(unnamed)
})
it('rejects a non-object pinned header', () => {
expect(() => applyChildToolOmissions(null, {}, [])).toThrow(/must be an object/)
expect(() => applyChildToolOmissions([], {}, [])).toThrow(/must be an object/)
expect(() => applyChildToolOmissions('x', {}, [])).toThrow(/must be an object/)
})
})
describe('unknownToolCallIds', () => {
it('returns structured UNKNOWN_TOOL call ids and ignores other results', () => {
const log = [