fix(cli): give the shipped preset's todo tool its required config

master made `allowParallelInProgress` a required field on dsh-tool-todo, so
every composition must choose it. The base patch was updated with the field,
but the Web surface takes `tool-todo` from the mounted preset instead, and
that row carried no config — so the `standard` preset failed to mount and
every session on the Web surface died at setup.

Also make the preset tree's write test reach the override. Tearing the agent
down stops in the loader's own "tree is being disposed" case before any write;
a live row reconfiguring itself is the trigger that actually gets there.
This commit is contained in:
Yichen Jiang
2026-08-07 14:25:35 +08:00
parent f3f2129154
commit bdf5e39986
3 changed files with 17 additions and 7 deletions

View File

@@ -227,6 +227,8 @@
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
config:
allowParallelInProgress: true
# The `web` service and its search provider stay in the host composition; only
# the model-facing tool is per-session.

View File

@@ -17,4 +17,9 @@ export function apply(ctx, config) {
order: 10,
text: `section for ${config.tool}`,
}))
// Reconfiguring a live row runs the Loader's `internal/update` waterfall,
// which persists the owning tree. That is the trigger reaching the preset
// tree's `write` while the subtree is still mounted; tearing the agent down
// instead stops earlier, in the loader's own "tree is being disposed" case.
globalThis.__RECONFIGURE__ = tool => ctx.fiber.update({ ...config, tool })
}

View File

@@ -219,22 +219,25 @@ describe('a roster with nothing in it', () => {
describe('attributing a service to a subtree', () => {
it('never writes the preset file back, however the subtree changes', async () => {
delete (globalThis as { __RECONFIGURE__?: unknown }).__RECONFIGURE__
const handle = await ctx.agents.create({
sessionId: SessionId('sess-write'),
setup: async (agentCtx: Context) => void await ctx.agentPresets.mount(agentCtx, 'standard'),
})
const [mount] = livePresetMounts().filter(entry => entry.presetId === 'standard')
expect(mount).toBeDefined()
const file = join(FIXTURES, 'system', 'standard', 'agent.cordis.yml')
const before = await readFile(file, 'utf8')
// The inherited `write()` persists the tree whenever the Loader thinks the
// config moved, and disposing an agent disposes its whole subtree — which
// is enough to trigger it. Inheriting that truncates the shipped preset to
// `[]` the first time a session ends.
await handle.dispose()
// The inherited `write()` persists the whole tree whenever the Loader
// decides a row's config moved, so one row reconfiguring itself would
// rewrite the shipped composition — here, with the row's new tool name.
const reconfigure = (globalThis as { __RECONFIGURE__?: (tool: string) => Promise<void> }).__RECONFIGURE__
expect(reconfigure).toBeTypeOf('function')
await reconfigure!('rewritten')
expect(toolNames(ctx, handle.agent)).toContain('rewritten')
expect(await readFile(file, 'utf8')).toBe(before)
await handle.dispose()
})
it('attributes nothing to a subtree that is already torn down', async () => {