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, and a locally authored `bash-only` preset presented two. 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 presets that want it. The TUI keeps its own row, having no presets. The web composition test now asserts the global tool layer is EMPTY, which is the invariant that would have caught this: any tool outside a preset reaches every agent.
This commit is contained in:
@@ -211,6 +211,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'
|
||||
|
||||
|
||||
@@ -226,6 +226,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'
|
||||
|
||||
|
||||
@@ -70,11 +70,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 () => {
|
||||
@@ -114,7 +116,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()
|
||||
}
|
||||
@@ -130,14 +133,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()
|
||||
}
|
||||
@@ -351,7 +354,7 @@ describe('authoring a preset on the shipped composition', () => {
|
||||
try {
|
||||
// The same tools the shipped `core-web` composes, from a file written
|
||||
// through the service into a root outside the installed harness.
|
||||
expect(toolNames(authorCtx, handle.agent)).toEqual(['ask_user_question', 'bash', 'str_replace_editor'])
|
||||
expect(toolNames(authorCtx, handle.agent)).toEqual(['bash', 'str_replace_editor'])
|
||||
} finally {
|
||||
await handle.dispose()
|
||||
}
|
||||
@@ -388,7 +391,7 @@ describe('the default preset as a user setting', () => {
|
||||
try {
|
||||
// `mount()` with no id resolves the effective default. Two tools, not
|
||||
// `standard`'s catalog: the setting decided the composition.
|
||||
expect(toolNames(ctx, handle.agent)).toEqual(['ask_user_question', 'bash', 'str_replace_editor'])
|
||||
expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'str_replace_editor'])
|
||||
} finally {
|
||||
await handle.dispose()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user