fix(web): stop leaking ask_user_question into every preset
`ui-question`'s node half called `ctx.tools.register` on the host context. `ScopedLayers.merge()` combines the global layer with the agent's exact-scope layer, and an unscoped registration lands in the global one — so the tool reached every agent no matter which preset composed it. `core-web`, sold as a two-tool benchmark surface, really presented three. Rendering a question is a host UI capability; having the tool is an agent capability, and only a preset decides that. The node half is now empty and the `tool-ask-user` row moved into the preset that wants it. The TUI keeps its own row, having no presets. The composition tests now assert the global tool layer is EMPTY, which is the invariant that would have caught this: any tool outside a preset reaches every agent. The browser lane's composition, seeded-history, and hermetic-skill assertions address their registries through a composed agent for the same reason — those services are per session now, and the host cannot resolve an `isolate` realm by name.
This commit is contained in:
@@ -222,6 +222,9 @@
|
||||
|
||||
# ── remaining model-facing rows ─────────────────────────────────────────────
|
||||
|
||||
- id: tool-ask-user
|
||||
name: '@deepseek-ai/dsh-tool-ask-user'
|
||||
|
||||
- id: tool-todo
|
||||
name: '@deepseek-ai/dsh-tool-todo'
|
||||
|
||||
|
||||
@@ -54,11 +54,13 @@ beforeAll(async () => {
|
||||
}, 120_000)
|
||||
|
||||
describe('the shipped Web composition', () => {
|
||||
it('leaves only the host UI tool in the global layer', () => {
|
||||
// `ask_user_question` is the host's own interaction surface, not an agent
|
||||
// capability, so it stays global. Every other tool now belongs to a
|
||||
// preset; a regression here means an agent-plane row came back to base.
|
||||
expect(toolNames(ctx)).toEqual(['ask_user_question'])
|
||||
it('leaves the global tool layer empty', () => {
|
||||
// Every model-facing tool belongs to a preset, `ask_user_question`
|
||||
// included: a tool in the global layer reaches EVERY agent regardless of
|
||||
// which preset composed it, so a two-tool benchmark surface would really
|
||||
// present three. A regression here means an agent-plane row came back to
|
||||
// the host composition.
|
||||
expect(toolNames(ctx)).toEqual([])
|
||||
})
|
||||
|
||||
it('supplies both shipped presets, and only those, from the system root', async () => {
|
||||
@@ -98,7 +100,8 @@ describe('the shipped Web composition', () => {
|
||||
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'core-web').then(() => undefined),
|
||||
})
|
||||
try {
|
||||
expect(toolNames(ctx, handle.agent)).toEqual(['ask_user_question', 'bash', 'str_replace_editor'])
|
||||
// Exactly what the preset lists — nothing arrives from the host.
|
||||
expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'str_replace_editor'])
|
||||
} finally {
|
||||
await handle.dispose()
|
||||
}
|
||||
@@ -114,14 +117,14 @@ describe('the shipped Web composition', () => {
|
||||
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'core-web').then(() => undefined),
|
||||
})
|
||||
try {
|
||||
expect(toolNames(ctx, minimal.agent)).toEqual(['ask_user_question', 'bash', 'str_replace_editor'])
|
||||
expect(toolNames(ctx, minimal.agent)).toEqual(['bash', 'str_replace_editor'])
|
||||
expect(toolNames(ctx, full.agent).length).toBeGreaterThan(10)
|
||||
|
||||
await minimal.dispose()
|
||||
|
||||
// Tearing the minimal session down leaves the full one whole.
|
||||
expect(toolNames(ctx, full.agent).length).toBeGreaterThan(10)
|
||||
expect(toolNames(ctx)).toEqual(['ask_user_question'])
|
||||
expect(toolNames(ctx)).toEqual([])
|
||||
} finally {
|
||||
await full.dispose()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user