diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-22 21:28:48 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-22 21:28:48 +0200 |
| commit | 5e817371a43ba468e5f216230d504b6124c85e95 (patch) | |
| tree | 1062a05bb733ef5c04405a619deb52b72c6084a6 | |
| parent | fc8a5aefff7c8421c579df1ba8391fdde411d731 (diff) | |
fix
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 |
