feat(tool-subagent): default maxDepth 1 with at-cap schema hiding
An omitted maxDepth meant unbounded recursion, and the shipped examples shipped that default. maxDepth now defaults to 1; a numeric cap requires the provider's depthLimit capability (the mount fails loud and points to the explicit 'provider-managed' opt-out for out-of-process providers), and a child AT the cap loses the delegating tool from its own schema via the child toolFilter — prompt-face hiding on top of the execution-face depth check. Examples pin maxDepth explicitly. The ACP snapshot harness gains Scenario.childToolOmissions so a child session may legitimately omit declared delegation tools from its pinned header and prompt; affected subagent/workflow goldens are re-recorded.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
parseToolSchemasSnapshot,
|
||||
refreshFixtureReplacements,
|
||||
sessionFixtureNames,
|
||||
applyChildToolOmissions,
|
||||
restorePinnedToolSchemas,
|
||||
stabilizeRefreshLog,
|
||||
unknownToolCallIds,
|
||||
@@ -366,6 +367,37 @@ 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 = [
|
||||
|
||||
Reference in New Issue
Block a user