Repository navigation
Description
Description
An ACP client that provides its own browser has no way to turn off Nanocoder's built-in browser tool for its sessions. The only switch is the top-level disabledTools array in the user's config, which a client should not edit.
I'd like a way for an ACP client to say, when it creates or resumes a session, "don't offer these built-in tools in this session", and for Nanocoder to enforce that and tell the client what it applied.
Tracked from Nano-Collective/nanocoder-desktop#12 (item 4). Related: #1889 (per-session tool registry for client mcpServers) and #1857 (mode changes mid-turn).
Use Case
Nanocoder Desktop has its own in-app browser with its own rules: localhost by default, public sites only after the user opts in, and agent control of public sites only in normal mode. In a desktop session the agent could use Nanocoder's built-in browser tool (added in #1626) instead and skip those rules. The tool can reach public sites (it blocks private and metadata addresses). It asks for approval, except when it is in alwaysAllow, the session is in yolo mode, or the run is headless.
The same applies to any ACP client with its own browser, such as an editor extension that embeds a preview.
Today the only way to turn the tool off is disabledTools in the user's agents.config.json. That is the user's own configuration, and a client rewriting it would leave the tool disabled outside that client.
The desktop needs this before its Milestone 3 (agent control of its browser).
Proposed Solution
Let a client pass a per-session list of disabled tools. My suggestion is _meta on session/new, session/load and session/resume, for example _meta: {"nanocoder/disabledTools": ["browser"]}. ACP reserves _meta for extensions, so no protocol change is needed.
Behavior:
- The session's list is merged with the user's global
disabledTools. It never replaces it. - The tools are removed from the session's tool list and system prompt, as
disabledToolsdoes today. - They are also rejected at execution. If the model calls a disabled tool anyway, the call returns an error and nothing runs.
- Subagents started from that session respect the same list.
- Other sessions in the same process are unaffected.
- The user's config files are never read or modified by this.
- Unknown tool names in the list are ignored with a debug log.
- The client can verify it. Nanocoder reports the effective disabled list for the session in the
_metaof thesession/new,session/loadandsession/resumeresponse. Older versions ignore unknown_metasilently, so without this a client cannot tell whether the request was honored. - The list is not persisted in the session file. The client sends it on every new, load and resume.
Alternatives Considered
- A process-level switch (a CLI flag such as
--disable-tool browser, or an environment variable) fornanocoder --acp. It is simpler, and it would be enough for the desktop, which starts one process per project. But it applies to every session in the process and can't change onsession/loadorsession/resume. If the maintainers prefer it, the same enforcement and reporting should apply. - A
session/set_config_optionentry. More plumbing and not needed for this use case. - Route the built-in
browsertool to the client's browser. A bigger change, and it ties Nanocoder's tool to a client capability. - Accept two browsers and document the difference. This leaves the client's browsing rules bypassable, which is the problem.
- Edit the user's
disabledTools. Ruled out: it conflicts with Nanocoder owning its configuration and leaves the tool disabled outside the client.
Additional Context
- I have searched existing issues to ensure this is not a duplicate
- This feature aligns with the project's goals (local-first AI assistance)
Current behavior, checked against main at fc01e23:
source/tools/tool-manager.ts:273:getAvailableToolNames(tuneConfig, developmentMode, disabledTools, model)already accepts adisabledToolsoverride, but it replaces the global list:disabledTools ?? getAppConfig().disabledTools.source/acp/acp-agent.ts(~L1058) andsource/acp/acp-conversation.ts(~L305) both call it withundefined, so ACP always uses the global list.source/subagents/subagent-executor.ts(~L425) reads onlygetAppConfig().disabledTools.source/hooks/chat-handler/utils/tool-filters.ts(~L52):partitionToolCallsonly checkstoolManager.hasTool(name), i.e. that the tool exists in the registry. A tool hidden bydisabledToolsstill runs if the model emits the call.source/tools/browser.tsx(~L86):browserasks for approval unless it is inalwaysAllowor the run is headless.resolveToolApproval(source/tools/approval-policy.ts) also skips approval inyolomode.browseris already excluded in plan mode.docs/configuration/index.mddescribesdisabledToolsas global only.
Implementation Notes (optional)
Sketch: store the session's list on AcpSession when it is registered or resumed. Pass the merged list (global plus session) wherever the ACP code calls getAvailableToolNames, make the execution-time check reject any tool that is not in the session's available list, and pass the same merged list into the subagent executor. Add the effective list to the response _meta.
This overlaps with #1889 (a per-session tool registry). If that lands first, the disabled list can live in the same per-session structure. The two can also be developed independently.
Acceptance criteria:
- A client can pass
disabledToolsin_metaonsession/new,session/loadandsession/resume. - Those tools are absent from the model's tool list and the system prompt in that session only.
- A disabled tool the model calls anyway is rejected, not executed.
- The user's global
disabledToolsstill applies. The lists are merged. - Subagents from that session respect the list.
- Another session in the same process still has the tool.
- The effective list is reported in the response
_metaso a client can verify it. - The list is not persisted, and the user's config files are not read or written for this.
- Docs describe the
_metakey.
Test plan:
- Unit: merging the global and session lists, and unknown names.
- ACP spec: with
browserdisabled it is not offered, and a forcedbrowsercall returns an error without running. - ACP spec: two sessions where only one disables the tool.
- ACP spec: the response
_metareports the effective list. - Subagent spec: a subagent from a disabling session does not get the tool.
Out of scope: routing the built-in browser to a client's browser, changes to the browser tool's own approval or address-blocking rules, and fetch_url and web_search, which also bring web content in and are a separate question.
Activity