/** * Plan mode's read-only policy. * * Wires the shared {@link createReadOnlyPolicy} to plan mode's specifics: the * structured readers and trackers that stay available, the tools that are * always blocked, and the guidance shown when a tool is refused. It lives apart * from `index.ts` so tests and future read-only modes can build the policy * without loading the extension wiring. * * Plan mode keeps raw shell on a short leash: `bash` stays active only so the * model can run read-only git commands (the `tool_call` guard in `index.ts` * refuses anything that is not one, see `git-bash.ts`), and `powershell` is * blocked outright so plan mode has one shell to reason about. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { createReadOnlyPolicy, WORKSPACE_READERS, type ReadOnlyPolicy } from "../../lib/policy.ts"; import { ASK_USER_QUESTION_TOOL, BOARD_TOOL, GOAL_TOOL, SUBAGENT_TOOL, TODO_TOOL, WEB_FETCH_TOOL, WEB_SEARCH_TOOL, } from "../../lib/tool-names.ts"; import { ENTER_TOOL } from "./runtime.ts"; /** * Tools that stay available while planning: structured readers (documents * included), the trackers, the git-only `bash`, and the known web/question * readers. They are also the tools the path backstop does not second-guess. */ export const PLAN_SAFE_TOOLS = [ ...WORKSPACE_READERS, TODO_TOOL, GOAL_TOOL, "bash", ASK_USER_QUESTION_TOOL, WEB_SEARCH_TOOL, WEB_FETCH_TOOL, ]; /** * Tools that may stay active while planning. Two of them are here so they are not * filtered out, without being in {@link PLAN_SAFE_TOOLS}: the `tool_call` guard * narrows each before the path backstop would see it. `subagent` is forced * read-only per call, and `board` admits only its `list` action — a model planning * work should be able to read the board it is about to add cards to, and the hint * cannot say that because the same tool writes. `powershell` is deliberately * absent — it is default-denied like any other shell. */ export const PLAN_ALLOWED_TOOLS = [...PLAN_SAFE_TOOLS, SUBAGENT_TOOL, BOARD_TOOL]; /** * Tools always blocked while planning, even when annotated read-only. * `enter_plan_mode` is denied so it stays hidden while planning; the guard exempts * plan's own registration so a call already in flight can still answer "already". */ const ALWAYS_BLOCKED_TOOLS = ["write", "edit", ENTER_TOOL]; /** Shown when a tool call is refused while planning. */ export const BLOCKED_GUIDANCE = "Call exit_plan_mode and get approval before using it."; /** Appended to a refused `bash` call; names the readers and the exit hatch. */ export const BASH_BLOCKED_GUIDANCE = "Use the read, grep, find, and ls tools for files, or call exit_plan_mode and get approval before using other shell commands."; /** * Build plan mode's read-only policy: known readers and the plan/goal trackers * are allowed, write/edit/shell/`enter_plan_mode` are always blocked, and every * other tool must carry the MCP `readOnlyHint` to pass. */ export function createPlanPolicy(pi: ExtensionAPI): ReadOnlyPolicy { return createReadOnlyPolicy({ allow: PLAN_ALLOWED_TOOLS, deny: ALWAYS_BLOCKED_TOOLS, annotations: (name) => pi.getAllTools().find((tool) => tool.name === name)?.annotations, guidance: BLOCKED_GUIDANCE, }); }