/** * Ask Mode Extension * * Read-only mode for safe code analysis. * When enabled, only read-only tools are available. * * Features: * - /ask command to toggle * - Bash restricted to allowlisted read-only commands */ import type { AgentMessage } from "@earendil-works/pi-agent-core"; import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { isSafeCommand } from "./utils.js"; // Tools to block in ask mode (file modification tools) const BLOCKED_TOOLS = ["edit", "write"]; export default function askModeExtension(pi: ExtensionAPI): void { let askModeEnabled = false; let originalTools: string[] = []; // Dynamically get all tools except blocked ones (edit, write) function getAskModeTools(): string[] { const allTools = pi.getAllTools(); return allTools .filter(t => !BLOCKED_TOOLS.includes(t.name)) .map(t => t.name); } // Get default normal mode tools (all available minus blocked) function getNormalModeTools(): string[] { const allTools = pi.getAllTools(); return allTools.map(t => t.name); } function updateStatus(ctx: ExtensionContext): void { // Footer status if (askModeEnabled) { ctx.ui.setStatus("ask-mode", ctx.ui.theme.fg("warning", "❓ ask")); } else { ctx.ui.setStatus("ask-mode", undefined); } // Clear widget ctx.ui.setWidget("ask-mode", undefined); } function toggleAskMode(ctx: ExtensionContext): void { askModeEnabled = !askModeEnabled; if (askModeEnabled) { // Save original tools before switching to ask mode originalTools = pi.getActiveTools(); const askModeTools = getAskModeTools(); pi.setActiveTools(askModeTools); ctx.ui.notify(`Ask mode enabled. Tools: ${askModeTools.join(", ")}`); } else { // Restore original tools (or default to all tools if not set) const normalModeTools = originalTools.length > 0 ? originalTools : getNormalModeTools(); pi.setActiveTools(normalModeTools); ctx.ui.notify("Ask mode disabled. Full access restored. All tools available."); } updateStatus(ctx); } pi.registerCommand("ask", { description: "Toggle ask mode or ask a question in ask mode", handler: async (args, ctx) => { const question = args.trim(); if (question) { // /ask with a question - enable ask mode and ask the question if (!askModeEnabled) { // Enable ask mode first askModeEnabled = true; // Save original tools before switching to ask mode originalTools = pi.getActiveTools(); const askModeTools = getAskModeTools(); pi.setActiveTools(askModeTools); updateStatus(ctx); ctx.ui.notify(`Ask mode enabled. Tools: ${askModeTools.join(", ")}`); } // Send the question to the agent pi.sendUserMessage(question); } else { // /ask without a message - just toggle toggleAskMode(ctx); } }, }); // Block destructive bash commands in ask mode pi.on("tool_call", async (event) => { if (!askModeEnabled || event.toolName !== "bash") return; const command = event.input.command as string; if (!isSafeCommand(command)) { return { block: true, reason: `Ask mode: command blocked (not allowlisted). Use /ask to disable ask mode first.\nCommand: ${command}`, }; } }); // Filter out stale ask mode context when not in ask mode pi.on("context", async (event) => { if (askModeEnabled) return; return { messages: event.messages.filter((m) => { const msg = m as AgentMessage & { customType?: string }; if (msg.customType === "ask-mode-context") return false; if (msg.role !== "user") return true; const content = msg.content; if (typeof content === "string") { return !content.includes("[ASK MODE ACTIVE]"); } if (Array.isArray(content)) { return !content.some( (c) => c.type === "text" && "text" in c && c.text?.includes("[ASK MODE ACTIVE]"), ); } return true; }), }; }); // Inject ask mode context before agent starts // v0.65.0: before_agent_start returns systemPromptAppend instead of message pi.on("before_agent_start", async (_event) => { if (!askModeEnabled) return; const askModeTools = getAskModeTools(); const blockedTools = BLOCKED_TOOLS.join(", "); const askModeContext = ` [ASK MODE ACTIVE] You are in ask mode - a read-only Q&A mode for safe code analysis. Restrictions: - You can only use: ${askModeTools.join(", ")} - You CANNOT use: ${blockedTools} (file modifications are disabled) - Bash is restricted to an allowlist of read-only commands Answer the user's question. Do NOT attempt to make any changes.`; return { systemPromptAppend: askModeContext }; }); }