Repository navigation
Conversation
Development mode only decided which tools the model was shown. A model that named a hidden tool anyway (write_file or execute_bash in plan mode) went straight to approval, where an alwaysAllow entry skipped the prompt and an ACP client's Allow ran it. The interactive loop and the plain runtime now refuse a registered tool that was not in the turn's offered list, through the shared tool-call partition, and send the model a "not available in the current mode" result. ACP's per-call guard, which only caught a tool that was offered and then hidden by a mode switch, now refuses any registered tool the session's current mode does not offer. Unknown names still take the existing unknown-tool path, and subagents already re-check availability. Tests cover the partition, and ACP plan-mode turns with a client that allows the call and with alwaysAllow. The ACP mock tool managers now offer the tools their tests run, as the real manager does.
nc-review: comments — 1 important@addyCooks — a few things worth a look, none blocking. The PR closes a real gap: a registered tool call that the active mode never offered could still run, because availability was only enforced by the offered tool list, not at the execution boundary. Using the shared partitionUnknownToolCalls/filterValidToolCalls path is the right shape - it keeps the assistant message and its tool results paired 1:1, so the model gets the refusal and can self-correct, and routing availability through ToolManager.getAvailableToolNames keeps one authority for mode, tune-profile and disabled-tool filtering. The one thing worth fixing before merge is the interactive path, which validates against a turn-start snapshot of the offered list rather than the live mode; the ACP and subagent paths this PR also touches both read the mode live. 🟠 important · The offered list this PR validates against is computed once, at the top of processAssistantResponse, from
That gap is reachable, because switching modes mid-turn is allowed:
This is the same posture the documentation states for MCP tools: " The two other paths the PR touches do not have this problem, because they check availability against the mode as it is now: ACP compares against Suggested fix: derive the validation list from the live mode rather than the turn-start snapshot - i.e. compute the availability check against 🔴 blocking · 🟠 a reviewer would ask for a change · ⚪ optional Automated code review — correctness, security, design, tests, plus duplicates and scope. A human still decides; this is not a substitute for review and is not exhaustive. The required status checks separately cover lint, formatting, types, unused dependencies, the test suite and the build. This bot never merges. Maintainers can rerun with |
The interactive loop validated tool calls against the list offered when the turn's request was built, but Shift+Tab can switch the mode while a turn is running. A call made in normal or yolo could then pass that check after a switch to plan, and an alwaysAllow entry would run it without a prompt. The loop now asks the tool manager for what the live mode offers (the same developmentModeRef it already reads for approvals) at three points: when the model's calls are validated, just before each auto-approved tool runs, and before a tool is put to the user for confirmation. A refused call gets a "not available in the current mode" result, and a refusal before an auto tool cancels the rest of that batch, as a denying permission plugin does. Tests flip the mode while the model answers, between two tools of a batch, and just before a confirmation prompt; they fail on the previous version of the loop.
|
/re-review |
nc-review: nothing to raise@addyCooks — nothing to raise from the automated review. The PR closes a real gap: before it, availability was only enforced by the offered tool list, so a model naming a hidden tool (e.g. Addressed since the last review
🔴 blocking · 🟠 a reviewer would ask for a change · ⚪ optional Automated code review — correctness, security, design, tests, plus duplicates and scope. A human still decides; this is not a substitute for review and is not exhaustive. The required status checks separately cover lint, formatting, types, unused dependencies, the test suite and the build. This bot never merges. Maintainers can rerun with |
Description
Development mode only decided which tools the model was shown. A model that named a hidden tool anyway (
write_fileorexecute_bashin plan mode) went straight to approval, where analwaysAllowentry skipped the prompt and an ACP client's Allow ran it. WithalwaysAllowset, plan mode wrote files and ran commands with no prompt, in bothnanocoder runand ACP.The interactive loop and the plain runtime now refuse a registered tool that was not in the turn's offered list, through the shared tool-call partition, and send the model a "not available in the current mode" result. ACP's per-call guard, which only caught a tool that was offered and then hidden by a mode switch, now refuses any registered tool the session's current mode does not offer. Unknown names still take the existing unknown-tool path, and subagents already re-check availability. This also covers MCP tools that plan mode hides because they are not read-only.
Tests cover the partition, and ACP plan-mode turns with a client that allows the call and with
alwaysAllow. They fail without the fix. The ACP mock tool managers now offer the tools their tests run, as the real manager does.Found during a smoke test of
mainat90ffb693. Related to #989, which fixed the same class of problem for MCP only.Type of Change
Changeset
pnpm changeset) describing this change for the changelogTesting
Automated Tests
.spec.ts/tsxfilespnpm test:allcompletes successfully)Manual Testing
Checklist