summaryrefslogtreecommitdiff
path: root/pi/agent/extensions/ask-mode
diff options
context:
space:
mode:
Diffstat (limited to 'pi/agent/extensions/ask-mode')
-rw-r--r--pi/agent/extensions/ask-mode/README.md84
-rw-r--r--pi/agent/extensions/ask-mode/index.ts183
-rw-r--r--pi/agent/extensions/ask-mode/utils.ts94
3 files changed, 0 insertions, 361 deletions
diff --git a/pi/agent/extensions/ask-mode/README.md b/pi/agent/extensions/ask-mode/README.md
deleted file mode 100644
index 2c0d17c..0000000
--- a/pi/agent/extensions/ask-mode/README.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Ask Mode
-
-Exploration-only mode for Pi.
-
-This extension adds a session-scoped `/ask` mode that turns Pi into a read-only
-investigation assistant. It is meant for understanding a codebase, debugging,
-reading logs, or answering questions without making changes.
-
-## What It Does
-
-- `/ask` enters ask mode
-- `/ask <prompt>` enters ask mode and immediately sends the prompt
-- `/ask-exit` leaves ask mode
-- `/ask-status` shows whether ask mode is active
-- limits tools to `read`, `bash`, `grep`, `find`, and `ls`
-- blocks unsafe bash commands even though `bash` stays enabled
-- injects per-turn instructions telling the model to inspect and explain, not implement
-
-## Usage Flows
-
-### Flow 1: Enter ask mode first, then explore
-
-```text
-/ask
-```
-
-Then ask questions naturally:
-
-```text
-Why does VM2 fail to reach readiness on the first create attempt?
-```
-
-### Flow 2: Enter ask mode and ask immediately
-
-```text
-/ask Compare the fresh-subagent extension behavior with what the README claims.
-```
-
-### Flow 3: Leave ask mode
-
-```text
-/ask-exit
-```
-
-That restores the previously active tool set.
-
-### Flow 4: Check whether you are still in ask mode
-
-```text
-/ask-status
-```
-
-## Safety Model
-
-Ask mode is meant for exploration only.
-
-- `edit` and `write` are removed from the active tool set
-- custom tools outside the ask-mode allowlist are blocked
-- `bash` remains available, but only for safe read-only commands
-
-Examples of the kind of bash commands ask mode allows:
-
-- `rg foo src`
-- `git diff`
-- `ls -la`
-- `sed -n '1,120p' file`
-- `curl http://host/...`
-
-Examples it blocks:
-
-- `rm`
-- `touch`
-- `mkdir`
-- `git commit`
-- `npm install`
-- `sudo ...`
-- shell redirection that writes files
-
-## Notes And Limits
-
-- This is session-scoped and restores on resume if the session was left in ask mode.
-- It is intended for investigation, not planning or implementation.
-- If you ask for a change while ask mode is active, Pi should explain what would
- need to change instead of making the change.
diff --git a/pi/agent/extensions/ask-mode/index.ts b/pi/agent/extensions/ask-mode/index.ts
deleted file mode 100644
index 4f19815..0000000
--- a/pi/agent/extensions/ask-mode/index.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import type { AgentMessage } from "@mariozechner/pi-agent-core";
-import type { TextContent } from "@mariozechner/pi-ai";
-import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
-import { isSafeAskModeCommand } from "./utils.js";
-
-const ASK_MODE_TOOLS = ["read", "bash", "grep", "find", "ls"];
-const STATE_TYPE = "ask-mode";
-const CONTEXT_TYPE = "ask-mode-context";
-
-interface AskModeState {
- enabled: boolean;
- normalTools: string[];
-}
-
-function hasAskModeMarker(message: AgentMessage): boolean {
- const customMessage = message as AgentMessage & { customType?: string };
- if (customMessage.customType === CONTEXT_TYPE) return true;
-
- if (message.role !== "user") return false;
- if (typeof message.content === "string") return message.content.includes("[ASK MODE ACTIVE]");
- if (!Array.isArray(message.content)) return false;
-
- return message.content.some(
- (block) => block.type === "text" && (block as TextContent).text?.includes("[ASK MODE ACTIVE]"),
- );
-}
-
-export default function askModeExtension(pi: ExtensionAPI): void {
- let askModeEnabled = false;
- let normalTools: string[] = [];
-
- function persistState(): void {
- pi.appendEntry<AskModeState>(STATE_TYPE, {
- enabled: askModeEnabled,
- normalTools,
- });
- }
-
- function updateStatus(ctx: ExtensionContext): void {
- if (!askModeEnabled) {
- ctx.ui.setStatus("ask-mode", undefined);
- ctx.ui.setWidget("ask-mode", undefined);
- return;
- }
-
- ctx.ui.setStatus("ask-mode", ctx.ui.theme.fg("warning", "⏸ ask"));
- ctx.ui.setWidget("ask-mode", [
- ctx.ui.theme.fg("warning", "Ask mode"),
- "Exploration only",
- "Files are read-only",
- "Bash is restricted to safe read-only commands",
- ]);
- }
-
- function enterAskMode(ctx: ExtensionContext): void {
- if (askModeEnabled) {
- updateStatus(ctx);
- return;
- }
-
- normalTools = pi.getActiveTools();
- askModeEnabled = true;
- pi.setActiveTools(ASK_MODE_TOOLS);
- ctx.ui.notify(`Ask mode enabled. Tools: ${ASK_MODE_TOOLS.join(", ")}`, "info");
- updateStatus(ctx);
- persistState();
- }
-
- function exitAskMode(ctx: ExtensionContext): void {
- if (!askModeEnabled) {
- ctx.ui.notify("Ask mode is not active.", "info");
- updateStatus(ctx);
- return;
- }
-
- askModeEnabled = false;
- pi.setActiveTools(normalTools.length > 0 ? normalTools : ["read", "bash", "edit", "write"]);
- ctx.ui.notify("Ask mode disabled. Previous tools restored.", "info");
- updateStatus(ctx);
- persistState();
- }
-
- pi.registerCommand("ask", {
- description: "Enter ask mode for exploration-only work. Optional prompt sends a question immediately.",
- handler: async (args, ctx) => {
- const prompt = args.trim();
- enterAskMode(ctx);
- if (prompt) {
- pi.sendUserMessage(prompt);
- if (!ctx.hasUI) {
- await ctx.waitForIdle();
- }
- }
- },
- });
-
- pi.registerCommand("ask-exit", {
- description: "Leave ask mode and restore the previous tool set",
- handler: async (_args, ctx) => exitAskMode(ctx),
- });
-
- pi.registerCommand("ask-status", {
- description: "Show whether ask mode is active",
- handler: async (_args, ctx) => {
- const message = askModeEnabled
- ? `Ask mode active. Tools: ${ASK_MODE_TOOLS.join(", ")}`
- : "Ask mode is not active.";
- if (!ctx.hasUI) {
- process.stdout.write(`${message}\n`);
- return;
- }
- ctx.ui.notify(message, "info");
- },
- });
-
- pi.on("tool_call", async (event) => {
- if (!askModeEnabled) return;
-
- if (!ASK_MODE_TOOLS.includes(event.toolName)) {
- return {
- block: true,
- reason: `Ask mode: tool "${event.toolName}" is disabled. Use /ask-exit before modifying files or using other tools.`,
- };
- }
-
- if (event.toolName === "bash") {
- const command = String(event.input.command ?? "");
- if (!isSafeAskModeCommand(command)) {
- return {
- block: true,
- reason: `Ask mode: bash command blocked (not recognized as safe read-only exploration).\nCommand: ${command}`,
- };
- }
- }
- });
-
- pi.on("context", async (event) => {
- if (askModeEnabled) return;
- return {
- messages: event.messages.filter((message) => !hasAskModeMarker(message as AgentMessage)),
- };
- });
-
- pi.on("before_agent_start", async () => {
- if (!askModeEnabled) return;
-
- return {
- message: {
- customType: CONTEXT_TYPE,
- content: `[ASK MODE ACTIVE]
-You are in ask mode: exploration only.
-
-Rules:
-- Do not modify files.
-- Do not use edit or write tools.
-- Use read, grep, find, ls, and only safe read-only bash commands.
-- Inspect, explain, compare, summarize, and answer questions.
-- If a requested action would require a file change, say so explicitly instead of doing it.
-
-Focus on observation and analysis, not implementation.`,
- display: false,
- },
- };
- });
-
- pi.on("session_start", async (_event, ctx) => {
- const entries = ctx.sessionManager.getEntries();
- const latestState = entries
- .filter((entry: { type: string; customType?: string }) => entry.type === "custom" && entry.customType === STATE_TYPE)
- .pop() as { data?: AskModeState } | undefined;
-
- if (latestState?.data) {
- askModeEnabled = latestState.data.enabled ?? askModeEnabled;
- normalTools = latestState.data.normalTools ?? normalTools;
- }
-
- if (askModeEnabled) {
- pi.setActiveTools(ASK_MODE_TOOLS);
- }
-
- updateStatus(ctx);
- });
-}
diff --git a/pi/agent/extensions/ask-mode/utils.ts b/pi/agent/extensions/ask-mode/utils.ts
deleted file mode 100644
index db8c889..0000000
--- a/pi/agent/extensions/ask-mode/utils.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-const DESTRUCTIVE_PATTERNS = [
- /\brm\b/i,
- /\brmdir\b/i,
- /\bmv\b/i,
- /\bcp\b/i,
- /\bmkdir\b/i,
- /\btouch\b/i,
- /\bchmod\b/i,
- /\bchown\b/i,
- /\bchgrp\b/i,
- /\bln\b/i,
- /\btee\b/i,
- /\btruncate\b/i,
- /\bdd\b/i,
- /\bshred\b/i,
- /(^|[^<])>(?!>)/,
- />>/,
- /\bnpm\s+(install|uninstall|update|ci|link|publish)/i,
- /\byarn\s+(add|remove|install|publish)/i,
- /\bpnpm\s+(add|remove|install|publish)/i,
- /\bpip\s+(install|uninstall)/i,
- /\bapt(-get)?\s+(install|remove|purge|update|upgrade)/i,
- /\bbrew\s+(install|uninstall|upgrade)/i,
- /\bgit\s+(add|commit|push|pull|merge|rebase|reset|checkout|branch\s+-[dD]|stash|cherry-pick|revert|tag|init|clone)/i,
- /\bsudo\b/i,
- /\bsu\b/i,
- /\bkill\b/i,
- /\bpkill\b/i,
- /\bkillall\b/i,
- /\breboot\b/i,
- /\bshutdown\b/i,
- /\bsystemctl\s+(start|stop|restart|enable|disable)/i,
- /\bservice\s+\S+\s+(start|stop|restart)/i,
- /\b(vim?|nano|emacs|code|subl)\b/i,
-];
-
-const SAFE_PATTERNS = [
- /^\s*cat\b/,
- /^\s*head\b/,
- /^\s*tail\b/,
- /^\s*less\b/,
- /^\s*more\b/,
- /^\s*grep\b/,
- /^\s*find\b/,
- /^\s*ls\b/,
- /^\s*pwd\b/,
- /^\s*echo\b/,
- /^\s*printf\b/,
- /^\s*wc\b/,
- /^\s*sort\b/,
- /^\s*uniq\b/,
- /^\s*diff\b/,
- /^\s*file\b/,
- /^\s*stat\b/,
- /^\s*du\b/,
- /^\s*df\b/,
- /^\s*tree\b/,
- /^\s*which\b/,
- /^\s*whereis\b/,
- /^\s*type\b/,
- /^\s*env\b/,
- /^\s*printenv\b/,
- /^\s*uname\b/,
- /^\s*whoami\b/,
- /^\s*id\b/,
- /^\s*date\b/,
- /^\s*cal\b/,
- /^\s*uptime\b/,
- /^\s*ps\b/,
- /^\s*top\b/,
- /^\s*htop\b/,
- /^\s*free\b/,
- /^\s*git\s+(status|log|diff|show|branch|remote|config\s+--get)/i,
- /^\s*git\s+ls-/i,
- /^\s*npm\s+(list|ls|view|info|search|outdated|audit)/i,
- /^\s*yarn\s+(list|info|why|audit)/i,
- /^\s*node\s+--version/i,
- /^\s*python\s+--version/i,
- /^\s*curl\s/i,
- /^\s*wget\s+-O\s*-/i,
- /^\s*jq\b/,
- /^\s*sed\s+-n/i,
- /^\s*awk\b/,
- /^\s*rg\b/,
- /^\s*fd\b/,
- /^\s*bat\b/,
- /^\s*exa\b/,
-];
-
-export function isSafeAskModeCommand(command: string): boolean {
- const isDestructive = DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command));
- const isSafe = SAFE_PATTERNS.some((pattern) => pattern.test(command));
- return !isDestructive && isSafe;
-}