fix(tools,client): chain-mode resolution and a live preset label

modeFor read only the exact scope's layer, so a `code`-preset session
advertised the native catalog: the mode is declared on the preset's STANDING
scope, and the agent only parents to it. Nearest scope wins along the chain —
the mode decides what the model SEES, which is the class of fact the chain
inherits. Caught live (the model politely computed with bash and said
run_code was not in its list); the chain test pins it.

The client half of the label fix: the create echo and the session-added
frame's agentPreset now reach the session list (newest wins in the upsert —
every producer of the field reports the CURRENT composition), and a confirmed
blank-session switch publishes through the new ISessions.noteAgentPreset, so
the header label moves with the composition instead of waiting for a reload.
This commit is contained in:
Yichen Jiang
2026-08-08 20:19:00 +08:00
parent 3926e95c61
commit 66e9b01fbd
8 changed files with 81 additions and 2 deletions

View File

@@ -1587,6 +1587,27 @@ describe('per-agent presentation', () => {
expect(native.sections.some(section => section.name === 'tools:sdk')).toBe(false)
})
it('inherits a STANDING preset scope\'s mode down the chain, agents beside it unaffected', async () => {
const { setScopeParent } = await import('@deepseek-ai/dsh-scope')
const { ctx, systemPrompt } = await setup({ mode: 'native' })
registerEcho(ctx)
// The preset's standing scope declares once; the agent only PARENTS to it
// (the per-preset standing-mount shape — no per-agent declaration at all).
const standing = await mintAgentScope(ctx, 'preset:code-like')
standing.scope.ctx.tools.presentAs('code')
const joined = await mintAgentScope(ctx, 'joined-agent')
setScopeParent(joined.agent, standing.agent)
const loner = await mintAgentScope(ctx, 'loner-agent')
expect(ctx.tools.get(RUN_CODE_NAME, joined.agent)).toBeDefined()
const coded = await systemPrompt.assemble({ scope: joined.agent })
expect(coded.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME])
// A sibling that never parented stays native, as does the global view.
expect(ctx.tools.get(RUN_CODE_NAME, loner.agent)).toBeUndefined()
const native = await systemPrompt.assemble({ scope: loner.agent })
expect(native.tools.map(tool => tool.name)).toEqual(['echo'])
})
it('keeps run_code out of a native agent\'s dispatch table', async () => {
const { ctx } = await setup({ mode: 'native' })
registerEcho(ctx)