summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-22 21:28:48 +0200
committerPaul Buetow <paul@buetow.org>2026-03-22 21:28:48 +0200
commit5e817371a43ba468e5f216230d504b6124c85e95 (patch)
tree1062a05bb733ef5c04405a619deb52b72c6084a6
parentfc8a5aefff7c8421c579df1ba8391fdde411d731 (diff)
fix
-rw-r--r--pi/agent/.gitignore2
-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
-rw-r--r--pi/agent/extensions/btw/README.md48
-rw-r--r--pi/agent/extensions/btw/index.ts230
-rw-r--r--pi/agent/extensions/fresh-subagent/README.md246
-rw-r--r--pi/agent/extensions/fresh-subagent/index.ts1144
-rw-r--r--pi/agent/extensions/handoff/README.md45
-rw-r--r--pi/agent/extensions/handoff/index.ts130
-rw-r--r--pi/agent/extensions/inline-bash/README.md44
-rw-r--r--pi/agent/extensions/inline-bash/index.ts72
-rw-r--r--pi/agent/extensions/loop-scheduler/README.md124
-rw-r--r--pi/agent/extensions/loop-scheduler/index.ts380
-rw-r--r--pi/agent/extensions/modal-editor/README.md98
-rw-r--r--pi/agent/extensions/modal-editor/index.ts512
-rw-r--r--pi/agent/extensions/nemotron-tool-repair/README.md87
-rw-r--r--pi/agent/extensions/nemotron-tool-repair/index.ts480
-rw-r--r--pi/agent/extensions/reload-runtime/README.md46
-rw-r--r--pi/agent/extensions/reload-runtime/index.ts26
-rw-r--r--pi/agent/extensions/session-name/README.md41
-rw-r--r--pi/agent/extensions/session-name/index.ts18
-rw-r--r--pi/agent/extensions/taskwarrior-plan-mode/README.md173
-rw-r--r--pi/agent/extensions/taskwarrior-plan-mode/index.ts822
-rw-r--r--pi/agent/extensions/taskwarrior-plan-mode/utils.ts252
-rw-r--r--pi/agent/models.json196
-rw-r--r--pi/agent/settings.json5
-rw-r--r--prompts/skills/auditing-code-quality/SKILL.md7
28 files changed, 3 insertions, 5586 deletions
diff --git a/pi/agent/.gitignore b/pi/agent/.gitignore
deleted file mode 100644
index d106817..0000000
--- a/pi/agent/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/sessions
-/auth.json
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;
-}
diff --git a/pi/agent/extensions/btw/README.md b/pi/agent/extensions/btw/README.md
deleted file mode 100644
index cf39e1c..0000000
--- a/pi/agent/extensions/btw/README.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# BTW
-
-Ephemeral side questions for Pi.
-
-This extension adds `/btw`, modeled after Claude Code's side-question flow:
-
-- it uses the current branch conversation as context
-- it asks a separate one-shot question with the current model
-- it does not add the side question or answer to session history
-- it does not expose tools to that side question
-
-## Command
-
-- `/btw <question>`
- Ask a quick side question without changing the main thread history.
-
-## Usage Flow
-
-### Flow 1: Ask a quick side question
-
-```text
-/btw Why did the current taskwarrior loop happen?
-```
-
-Pi will answer in a temporary overlay. Close it with `Esc`, `Enter`, or `Space`.
-
-### Flow 2: Use it while you are in the middle of another task
-
-```text
-/btw Remind me which file currently owns the SSH host key bootstrap logic.
-```
-
-This is meant for detours and clarifications. The main conversation stays clean.
-
-### Flow 3: Use it in non-interactive mode
-
-```bash
-pi --model openai/gpt-4.1 --no-session -p '/btw Reply with exactly BTW_OK'
-```
-
-In non-interactive mode, the answer is printed directly to stdout.
-
-## Notes And Limits
-
-- `/btw` uses the currently selected model.
-- The side question gets current branch context, not a fresh context.
-- It has no tools. If the answer is not derivable from the supplied context, it should say so.
-- It is best for short clarifications, not long implementation work.
diff --git a/pi/agent/extensions/btw/index.ts b/pi/agent/extensions/btw/index.ts
deleted file mode 100644
index 286c52e..0000000
--- a/pi/agent/extensions/btw/index.ts
+++ /dev/null
@@ -1,230 +0,0 @@
-import { complete, type Message, type TextContent, type UserMessage } from "@mariozechner/pi-ai";
-import {
- BorderedLoader,
- convertToLlm,
- type ExtensionAPI,
- type ExtensionCommandContext,
- type SessionEntry,
- type Theme,
-} from "@mariozechner/pi-coding-agent";
-import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
-
-const SYSTEM_PROMPT = `You are answering a side question for the user.
-
-Rules:
-- Use the supplied conversation context if it is relevant.
-- Answer the side question directly and concisely.
-- Do not use tools.
-- Do not invent facts that are not supported by the supplied context.
-- If the answer is not available from the supplied conversation context, say so plainly.
-- Keep the answer short by default unless the user explicitly asks for depth.`;
-
-function extractResponseText(message: Message): string {
- return message.content
- .filter((block): block is TextContent => block.type === "text")
- .map((block) => block.text)
- .join("\n")
- .trim();
-}
-
-function getConversationMessages(ctx: ExtensionCommandContext): Message[] {
- const branch = ctx.sessionManager.getBranch();
- return branch
- .filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message")
- .map((entry) => entry.message);
-}
-
-function wrapParagraph(text: string, width: number): string[] {
- if (width <= 1) return [text];
- if (!text.trim()) return [""];
-
- const words = text.split(/\s+/).filter(Boolean);
- if (words.length === 0) return [""];
-
- const lines: string[] = [];
- let current = "";
-
- for (const word of words) {
- const next = current ? `${current} ${word}` : word;
- if (visibleWidth(next) <= width) {
- current = next;
- continue;
- }
-
- if (current) lines.push(current);
-
- if (visibleWidth(word) <= width) {
- current = word;
- continue;
- }
-
- let remainder = word;
- while (visibleWidth(remainder) > width) {
- lines.push(truncateToWidth(remainder, width, ""));
- remainder = remainder.slice(lines[lines.length - 1]!.length);
- }
- current = remainder;
- }
-
- if (current) lines.push(current);
- return lines.length > 0 ? lines : [""];
-}
-
-function wrapText(text: string, width: number): string[] {
- return text.split(/\r?\n/).flatMap((line) => wrapParagraph(line, width));
-}
-
-class BtwOverlay {
- constructor(
- private readonly theme: Theme,
- private readonly question: string,
- private readonly answer: string,
- private readonly done: () => void,
- ) {}
-
- handleInput(data: string): void {
- if (matchesKey(data, "escape") || matchesKey(data, "return") || data === " " || data === "\r") {
- this.done();
- }
- }
-
- render(width: number): string[] {
- const innerWidth = Math.max(20, width - 2);
- const contentWidth = Math.max(10, innerWidth - 2);
- const lines: string[] = [];
-
- const pad = (text: string) => {
- const visible = visibleWidth(text);
- return text + " ".repeat(Math.max(0, innerWidth - visible));
- };
-
- const row = (text = "") => `${this.theme.fg("border", "│")}${pad(text)}${this.theme.fg("border", "│")}`;
- const addWrappedSection = (label: string, value: string) => {
- lines.push(row(` ${this.theme.fg("accent", label)}`));
- for (const wrapped of wrapText(value || "(no answer)", contentWidth)) {
- lines.push(row(` ${wrapped}`));
- }
- lines.push(row());
- };
-
- lines.push(this.theme.fg("border", `╭${"─".repeat(innerWidth)}╮`));
- lines.push(row(` ${this.theme.fg("accent", "BTW")}${this.theme.fg("muted", " Side question")}`));
- lines.push(row());
- addWrappedSection("Question", this.question);
- addWrappedSection("Answer", this.answer || "(no answer)");
- lines.push(row(this.theme.fg("dim", " Esc, Enter, or Space to close")));
- lines.push(this.theme.fg("border", `╰${"─".repeat(innerWidth)}╯`));
-
- return lines;
- }
-
- invalidate(): void {}
-}
-
-async function runBtw(question: string, ctx: ExtensionCommandContext): Promise<string> {
- if (!ctx.model) {
- throw new Error("No model selected.");
- }
-
- const branchMessages = getConversationMessages(ctx);
- const llmMessages = convertToLlm(branchMessages);
- const apiKey = await ctx.modelRegistry.getApiKey(ctx.model);
- const userMessage: UserMessage = {
- role: "user",
- content: [{ type: "text", text: question }],
- timestamp: Date.now(),
- };
-
- const response = await complete(
- ctx.model,
- {
- systemPrompt: SYSTEM_PROMPT,
- messages: [...llmMessages, userMessage],
- },
- { apiKey },
- );
-
- if (response.stopReason === "aborted") {
- throw new Error("Cancelled.");
- }
-
- return extractResponseText(response) || "(no answer)";
-}
-
-export default function btwExtension(pi: ExtensionAPI): void {
- pi.registerCommand("btw", {
- description: "Ask a quick side question without adding it to the conversation",
- handler: async (args, ctx) => {
- const question = args.trim();
- if (!question) {
- const usage = "Usage: /btw <side question>";
- if (!ctx.hasUI) process.stdout.write(`${usage}\n`);
- else ctx.ui.notify(usage, "warning");
- return;
- }
-
- if (!ctx.model) {
- const error = "No model selected.";
- if (!ctx.hasUI) process.stdout.write(`${error}\n`);
- else ctx.ui.notify(error, "error");
- return;
- }
-
- if (!ctx.hasUI) {
- try {
- const answer = await runBtw(question, ctx);
- process.stdout.write(`${answer}\n`);
- } catch (error) {
- const text = error instanceof Error ? error.message : String(error);
- process.stdout.write(`${text}\n`);
- }
- return;
- }
-
- const answer = await ctx.ui.custom<string | null>(
- (tui, theme, _kb, done) => {
- const loader = new BorderedLoader(tui, theme, `Asking BTW using ${ctx.model!.id}...`);
- loader.onAbort = () => done(null);
-
- runBtw(question, ctx)
- .then(done)
- .catch((error) => {
- const text = error instanceof Error ? error.message : String(error);
- done(`BTW failed: ${text}`);
- });
-
- return loader;
- },
- {
- overlay: true,
- overlayOptions: {
- width: "50%",
- minWidth: 50,
- maxHeight: "80%",
- anchor: "right-center",
- offsetX: -1,
- },
- },
- );
-
- if (answer === null) {
- ctx.ui.notify("BTW cancelled.", "info");
- return;
- }
-
- await ctx.ui.custom<void>(
- (_tui, theme, _kb, done) => new BtwOverlay(theme, question, answer, done),
- {
- overlay: true,
- overlayOptions: {
- width: "55%",
- minWidth: 56,
- maxHeight: "85%",
- anchor: "right-center",
- offsetX: -1,
- },
- },
- );
- },
- });
-}
diff --git a/pi/agent/extensions/fresh-subagent/README.md b/pi/agent/extensions/fresh-subagent/README.md
deleted file mode 100644
index 701fdda..0000000
--- a/pi/agent/extensions/fresh-subagent/README.md
+++ /dev/null
@@ -1,246 +0,0 @@
-# Fresh Subagent
-
-Generic fresh-context delegation for Pi with live status, per-run log files, and
-history browsing.
-
-This extension gives Pi a simple subagent primitive:
-
-- the main agent can call the `subagent` tool
-- you can call `/subagent <prompt>` directly
-- delegated work runs in a new `pi --mode json -p --no-session` process
-- the child starts with a fresh context
-- each run gets its own log file plus JSON sidecar metadata
-- you can list past runs and open any run's full transcript in `$VISUAL` or `$EDITOR`
-
-This is still intentionally small. It does not manage agent pools, agent
-catalogs, or planner chains. It is meant for focused delegation with a clean
-context and auditable output.
-
-## What It Is For
-
-Subagents are generic. The main agent can hand them any focused prompt that
-benefits from a clean context, for example:
-
-- code review
-- debugging
-- focused research
-- second-opinion architecture checks
-- summarizing noisy output
-- validating whether a task is really complete
-- any other self-contained side task
-
-One common use is the `taskwarrior-task-management` review loop:
-
-1. The main agent implements the change
-2. The main agent self-reviews the change
-3. The main agent uses `subagent` for an independent fresh-context review
-4. The main agent fixes findings
-5. Only then does the task move toward completion
-
-## Usage Flow
-
-### Step 1: Run a subagent
-
-Direct delegation:
-
-```text
-/subagent Compare the current plan-mode extension behavior against the requested workflow and list only the mismatches.
-```
-
-Focused investigation:
-
-```text
-/subagent Find all code paths that write to the SSH known_hosts file and summarize the risk.
-```
-
-Independent review:
-
-```text
-/subagent Independently review the recent changes for bugs, regressions, and missing tests. Only report concrete findings.
-```
-
-The watched slash command is the normal interactive path. It updates status in
-the footer, keeps a widget with recent activity, and writes the full run to a
-durable log file.
-
-### Step 2: Inspect history
-
-List recent runs:
-
-```text
-/subagent-history
-```
-
-List more:
-
-```text
-/subagent-history 20
-```
-
-Each entry includes:
-
-- run ID
-- status
-- started timestamp
-- prompt summary
-- log path
-- output preview when available
-
-You can select later runs either by:
-
-- `latest`
-- numeric index from `/subagent-history`
-- run ID prefix
-
-### Step 3: Inspect a specific run
-
-Show the paths and metadata for the latest run:
-
-```text
-/subagent-log
-```
-
-Show the paths and metadata for a specific run:
-
-```text
-/subagent-log 3
-/subagent-log 20260320T194522-review-ssh
-```
-
-This prints:
-
-- run ID
-- status
-- prompt
-- log path
-- metadata path
-- `tail -f` command
-
-### Step 4: Open the full transcript in Helix or another editor
-
-Open the latest run in `$VISUAL` or `$EDITOR`:
-
-```text
-/subagent-open
-```
-
-Open a specific run:
-
-```text
-/subagent-open 2
-/subagent-open 20260320T194522-review-ssh
-```
-
-In TUI mode the extension temporarily releases the terminal, launches your
-configured editor, then restores Pi when you exit the editor.
-
-In one-shot or print mode it runs the editor command directly.
-
-## Other Commands
-
-Alias with the same watched behavior:
-
-```text
-/subagent-watch <prompt>
-```
-
-Launch a visible fresh Pi session instead of a headless child:
-
-```text
-/subagent-session <prompt>
-```
-
-This is useful when you want to watch the subagent itself, not just the logged
-transcript.
-
-## Tool Usage From The Main Agent
-
-Because this extension registers a `subagent` tool, the main agent can call it
-itself.
-
-Generic handoff pattern:
-
-```text
-Use the subagent tool for a fresh-context pass on this side task, then return only the useful result.
-```
-
-Review handoff pattern:
-
-```text
-First review your own changes. Afterwards, use the subagent tool to perform an independent fresh-context review and then address any findings.
-```
-
-Research handoff pattern:
-
-```text
-Use the subagent tool to inspect only the WireGuard setup path in a fresh context and summarize the concrete risks.
-```
-
-## One-Shot CLI Mode
-
-This works outside the full TUI as well:
-
-```bash
-pi --model openai/gpt-4.1 --no-session -p '/subagent Say only SUBAGENT_COMMAND_OK'
-pi --no-session -p '/subagent-history'
-pi --no-session -p '/subagent-log latest'
-```
-
-If you want to open a run from a shell:
-
-```bash
-pi --no-session -p '/subagent-open latest'
-```
-
-## What To Put In The Prompt
-
-Subagents start fresh, so include enough context in the prompt:
-
-- what to inspect or do
-- the scope or files to focus on
-- the expected output shape
-- any constraints such as “report only concrete findings”
-
-Good:
-
-```text
-/subagent Review the recent SSH bootstrap changes in hyperstack.rb. Report only concrete bugs, regressions, or missing tests.
-```
-
-Weak:
-
-```text
-/subagent Review this
-```
-
-## Log Storage
-
-Fresh-subagent history lives under:
-
-```text
-${XDG_STATE_HOME:-~/.local/state}/pi/subagents
-```
-
-Each run creates:
-
-- one `*.log` transcript file
-- one `*.json` metadata file
-- a rolling `latest.log` symlink pointing at the newest run
-
-That means you can also inspect logs outside Pi with tools like:
-
-```bash
-tail -f ~/.local/state/pi/subagents/latest.log
-ls ~/.local/state/pi/subagents
-```
-
-## Notes And Limits
-
-- The headless subagent uses a fresh session via `--no-session`.
-- The subprocess still runs in the same working directory unless you override
- `cwd`.
-- The extension disables itself inside child subagent processes to avoid
- accidental recursive registration.
-- `subagent-session` is visible because it uses a real Pi session instead of a
- headless child. Its transcript is the session itself, not one of the
- `fresh-subagent` log files.
diff --git a/pi/agent/extensions/fresh-subagent/index.ts b/pi/agent/extensions/fresh-subagent/index.ts
deleted file mode 100644
index 366f94a..0000000
--- a/pi/agent/extensions/fresh-subagent/index.ts
+++ /dev/null
@@ -1,1144 +0,0 @@
-import { spawn, spawnSync } from "node:child_process";
-import { createWriteStream } from "node:fs";
-import { mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
-import { homedir } from "node:os";
-import path from "node:path";
-import type { AgentToolResult, AgentToolResultContent } from "@mariozechner/pi-agent-core";
-import type { Message, TextContent } from "@mariozechner/pi-ai";
-import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
-import { Text } from "@mariozechner/pi-tui";
-import { Type } from "@sinclair/typebox";
-
-const CHILD_ENV_FLAG = "PI_FRESH_SUBAGENT_CHILD";
-const LOG_BASENAME = "latest.log";
-const HISTORY_SUFFIX = ".json";
-const DEFAULT_HISTORY_LIMIT = 10;
-const MAX_HISTORY_LIMIT = 50;
-const MAX_RECENT_ACTIVITY = 12;
-const MAX_WIDGET_LINES = 10;
-const MAX_RENDER_PREVIEW_LINES = 8;
-const MAX_ACTIVITY_LINE_LENGTH = 160;
-const MAX_UPDATE_INTERVAL_MS = 150;
-const HISTORY_PERSIST_INTERVAL_MS = 1000;
-
-interface UsageStats {
- input: number;
- output: number;
- cacheRead: number;
- cacheWrite: number;
- cost: number;
- turns: number;
-}
-
-interface FreshSubagent