From af4e032ed4e6ccc3f9da5cb2237ab4ee81f5f582 Mon Sep 17 00:00:00 2001 From: Amos Date: Sat, 27 Jun 2026 02:49:01 +0300 Subject: [PATCH] fixed freezing on session resume --- README.md | 29 ------------------ agents/researcher.md | 2 +- agents/scout.md | 4 +-- agents/worker.md | 2 +- pi-extension/subagents/session.ts | 51 ++++++++++++++++++++----------- 5 files changed, 38 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 9c9557c..9218d93 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Async subagents for [pi](https://github.com/badlogic/pi-mono) — spawn, orchestrate, and manage sub-agent sessions in multiplexer panes. **Fully non-blocking** — the main agent keeps working while subagents run in the background. -https://github.com/user-attachments/assets/30adb156-cfb4-4c47-84ca-dd4aa80cba9f - ## How It Works Call `subagent()` and it **returns immediately**. The sub-agent runs in its own terminal pane. A live widget above the input shows all running agents with their current state — `starting`, `active`, `waiting`, `stalled`, or `running`. When a sub-agent finishes, its result is **steered back** into the main session as an async notification — triggering a new turn so the agent can process it. @@ -23,33 +21,6 @@ subagent({ name: "Scout: DB", agent: "scout", task: "Map database schema" }); // Both return immediately, results steer back independently ``` -## Install - -```bash -pi install git:github.com/HazAT/pi-interactive-subagents -``` - -Supported multiplexers: - -- [cmux](https://github.com/manaflow-ai/cmux) -- [tmux](https://github.com/tmux/tmux) -- [zellij](https://zellij.dev) -- [WezTerm](https://wezfurlong.org/wezterm/) (terminal emulator with built-in multiplexing) - -Start pi inside one of them: - -```bash -cmux pi -# or -tmux new -A -s pi 'pi' -# or -zellij --session pi # then run: pi -# or -# just run pi inside WezTerm — no wrapper needed -``` - -Optional: set `PI_SUBAGENT_MUX=cmux|tmux|zellij|wezterm` to force a specific backend. - If your shell startup is slow and subagent commands sometimes get dropped before the prompt is ready, set `PI_SUBAGENT_SHELL_READY_DELAY_MS` to a higher value (defaults to `500`): ```bash diff --git a/agents/researcher.md b/agents/researcher.md index a2eea37..e40d4a0 100644 --- a/agents/researcher.md +++ b/agents/researcher.md @@ -2,7 +2,7 @@ name: researcher description: Web researcher — searches the web and synthesizes findings tools: web_search, web_fetch -model: anthropic/claude-sonnet-4-6 +model: openrouter/z-ai/glm-5.2 thinking: medium system-prompt: append auto-exit: true diff --git a/agents/scout.md b/agents/scout.md index b5bbec0..766441c 100644 --- a/agents/scout.md +++ b/agents/scout.md @@ -2,8 +2,8 @@ name: scout description: Fast codebase recon — explores files, finds patterns, maps architecture tools: read, grep, find, ls -model: anthropic/claude-haiku-4-5 -thinking: medium +model: openrouter/z-ai/glm-5.2 +thinking: low system-prompt: append auto-exit: true --- diff --git a/agents/worker.md b/agents/worker.md index c59b636..de8e321 100644 --- a/agents/worker.md +++ b/agents/worker.md @@ -3,7 +3,7 @@ name: worker description: General-purpose worker — reads, writes, and edits code tools: read, write, edit, safe_bash, web_search, web_fetch subagent_agents: scout, researcher -model: anthropic/claude-sonnet-4-6 +model: openrouter/z-ai/glm-5.2 thinking: medium system-prompt: append --- diff --git a/pi-extension/subagents/session.ts b/pi-extension/subagents/session.ts index 81c8614..d3937f4 100644 --- a/pi-extension/subagents/session.ts +++ b/pi-extension/subagents/session.ts @@ -1,9 +1,12 @@ import { appendFileSync, + closeSync, copyFileSync, existsSync, mkdirSync, + openSync, readFileSync, + readSync, readdirSync, statSync, writeFileSync, @@ -99,30 +102,44 @@ export function getLeafId(sessionFile: string): string | null { * then prefix), NOT the filename — so this is the value to hand back to the * orchestrator for follow-ups. */ -export function getSessionId(sessionFile: string): string | null { +/** + * Read only the first line of a file without loading the whole thing into + * memory. Session files grow to many MB, but the header we need is always the + * first JSON line, so reading a small prefix keeps header lookups cheap — this + * is what makes scanning a large session tree fast enough to avoid blocking the + * event loop. Returns the first line (sans trailing newline), or null. + */ +function readFirstLine(path: string, maxBytes = 65536): string | null { + let fd: number | undefined; try { - const raw = readFileSync(sessionFile, "utf8"); - for (const line of raw.split("\n")) { - const trimmed = line.trim(); - if (!trimmed) continue; - const entry = JSON.parse(trimmed) as { type?: string; id?: string }; - if (entry.type === "session" && typeof entry.id === "string") { - return entry.id; - } - // Header is always the first entry; bail after the first parse. - break; - } + fd = openSync(path, "r"); + const buf = Buffer.allocUnsafe(maxBytes); + const bytes = readSync(fd, buf, 0, maxBytes, 0); + if (bytes <= 0) return null; + const nl = buf.indexOf(0x0a); // '\n' + const end = nl === -1 || nl >= bytes ? bytes : nl; + return buf.toString("utf8", 0, end); } catch { - // fall through + return null; + } finally { + if (fd !== undefined) { + try { + closeSync(fd); + } catch { + /* ignore */ + } + } } - return null; +} + +export function getSessionId(sessionFile: string): string | null { + return readHeaderId(sessionFile); } function readHeaderId(sessionFile: string): string | null { + const firstLine = readFirstLine(sessionFile)?.trim(); + if (!firstLine) return null; try { - const raw = readFileSync(sessionFile, "utf8"); - const firstLine = raw.split("\n", 1)[0]?.trim(); - if (!firstLine) return null; const entry = JSON.parse(firstLine) as { type?: string; id?: string }; return entry.type === "session" && typeof entry.id === "string" ? entry.id : null; } catch {