co-mono/packages/coding-agent/examples/hooks/plan-mode.ts
Helmut Januschka 059292ead1 WIP: Add hook API for dynamic tool control with plan-mode hook example
- Add pi.getTools() and pi.setTools(toolNames) to HookAPI
- Hooks can now enable/disable tools dynamically
- Changes take effect on next agent turn

New example hook: plan-mode.ts
- Claude Code-style read-only exploration mode
- /plan command toggles plan mode on/off
- Plan mode tools: read, bash, grep, find, ls
- Edit/write tools disabled in plan mode
- Injects context telling agent about restrictions
- After each response, prompts to execute/stay/refine
- State persists across sessions
2026-01-04 18:13:28 +01:00

119 lines
3.8 KiB
TypeScript

/**
* Plan Mode Hook
*
* Provides a Claude Code-style "plan mode" for safe code exploration.
* When enabled, the agent can only use read-only tools and cannot modify files.
*
* Features:
* - /plan command to toggle plan mode
* - In plan mode: only read, bash (read-only), grep, find, ls are available
* - Injects system context telling the agent about the restrictions
* - After each agent response, prompts to execute the plan or continue planning
*
* Usage:
* 1. Copy this file to ~/.pi/agent/hooks/ or your project's .pi/hooks/
* 2. Use /plan to toggle plan mode on/off
*/
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
// Read-only tools for plan mode
const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls"];
// Full set of tools for normal mode
const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
export default function planModeHook(pi: HookAPI) {
// Track plan mode state
let planModeEnabled = false;
// Register /plan command
pi.registerCommand("plan", {
description: "Toggle plan mode (read-only exploration)",
handler: async (_args, ctx) => {
planModeEnabled = !planModeEnabled;
if (planModeEnabled) {
// Switch to read-only tools
pi.setTools(PLAN_MODE_TOOLS);
ctx.ui.notify(`Plan mode enabled. Tools: ${PLAN_MODE_TOOLS.join(", ")}`);
} else {
// Switch back to normal tools
pi.setTools(NORMAL_MODE_TOOLS);
ctx.ui.notify("Plan mode disabled. Full access restored.");
}
},
});
// Inject plan mode context at the start of each turn via before_agent_start
pi.on("before_agent_start", async () => {
if (!planModeEnabled) return;
// Return a message to inject into context
return {
message: {
customType: "plan-mode-context",
content: `[PLAN MODE ACTIVE]
You are in plan mode - a read-only exploration mode for safe code analysis.
Restrictions:
- You can only use: read, bash (read-only commands), grep, find, ls
- You CANNOT use: edit, write (file modifications are disabled)
- Focus on analysis, planning, and understanding the codebase
Your task is to explore, analyze, and create a detailed plan.
Do NOT attempt to make changes - just describe what you would do.
When you have a complete plan, I will switch to normal mode to execute it.`,
display: false, // Don't show in TUI, just inject into context
},
};
});
// After agent finishes, offer to execute the plan
pi.on("agent_end", async (_event, ctx) => {
if (!planModeEnabled) return;
if (!ctx.hasUI) return;
const choice = await ctx.ui.select("Plan mode - what next?", [
"Execute the plan",
"Stay in plan mode",
"Refine the plan",
]);
if (choice === "Execute the plan") {
// Switch to normal mode
planModeEnabled = false;
pi.setTools(NORMAL_MODE_TOOLS);
ctx.ui.notify("Switched to normal mode. Full access restored.");
// Set editor text to prompt execution
ctx.ui.setEditorText("Execute the plan you just created. Proceed step by step.");
} else if (choice === "Refine the plan") {
const refinement = await ctx.ui.input("What should be refined?");
if (refinement) {
ctx.ui.setEditorText(refinement);
}
}
// "Stay in plan mode" - do nothing, just continue
});
// Persist plan mode state across sessions
pi.on("session_start", async (_event, ctx) => {
// Check if there's persisted plan mode state
const entries = ctx.sessionManager.getEntries();
const planModeEntry = entries
.filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "plan-mode")
.pop() as { data?: { enabled: boolean } } | undefined;
if (planModeEntry?.data?.enabled) {
planModeEnabled = true;
pi.setTools(PLAN_MODE_TOOLS);
}
});
// Save state when plan mode changes (via tool_call or other events)
pi.on("turn_start", async () => {
// Persist current state
pi.appendEntry("plan-mode", { enabled: planModeEnabled });
});
}