@maxiaochao/pi-toolkit 0.10.4 → 0.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 2026-10-08 - v0.10.5
2
+
3
+ - bundle herdr-subagents v0.4.2: add Alt+P and /parent controls to return from child Agents
1
4
  ## 2026-10-08 - v0.10.4
2
5
 
3
6
  - bundle herdr-subagents v0.4.1: targeted cancellation, live token polling, and idempotent tab cleanup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-toolkit",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "Personal pi coding agent toolkit: execution plans, /btw, /cache_export and apply_patch extensions, browser automation, and bundled theme/skills.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -1,3 +1,8 @@
1
+ ## 2026-10-08 - v0.4.2
2
+
3
+ - add Alt+P and /parent controls to return from child Agents
4
+ - Add `/parent` and `Alt+P` inside child Agents to return focus to the exact parent Agent pane, with a visible child footer hint.
5
+
1
6
  ## 2026-10-08 - v0.4.1
2
7
 
3
8
  - targeted cancellation, live token polling, and idempotent tab cleanup
@@ -76,7 +76,7 @@ Cancel one active or queued run with `herdr_subagents_control({ action: "cancel"
76
76
 
77
77
  While a batch is active, a compact Fleet monitor stays below the editor. Its divider/title row summarizes the run, and each task row shows a hollow or filled status circle, the real persona and task name, status, live active duration, and cumulative child Pi token usage in one compact shared metrics column after the longest row rather than at the terminal edge. Running work uses Pi's warning/yellow semantic color and completed work uses success/green. Token totals use the child session's persisted assistant, tool-result, standalone usage, compaction, and branch-summary usage; they are never estimated. Queued tasks show neither fabricated time nor tokens. The monitor follows Pi theme semantics, truncates by visible terminal columns, and refreshes session usage from both file events and the running one-second timer.
78
78
 
79
- With an empty editor, press `↓` to enter the list, `↑`/`↓` to choose a task, `Enter` to focus its exact pane, and `Esc` to return. `/herdr-subagents focus <task-number>` remains available as a fallback.
79
+ With an empty editor, press `↓` to enter the list, `↑`/`↓` to choose a task, `Enter` to focus its exact pane, and `Esc` to return. `/herdr-subagents focus <task-number>` remains available as a fallback. Inside every child Agent, press `Alt+P` or run `/parent` to return to the exact parent Agent pane; the shortcut is also shown in the child footer.
80
80
 
81
81
  ## History
82
82
 
@@ -16,7 +16,7 @@
16
16
  - Force write-capable batches to serial execution while allowing bounded read-only parallelism.
17
17
  - Preserve compact reports, full results, Pi sessions, blocked questions, and historical follow-ups.
18
18
  - Recover queued/running work after interruption without retrying post-prompt side effects, including truncated reports and recovery errors.
19
- - Verify the split public contracts (`herdr_subagents` with required `tasks`, plus `herdr_subagents_control` with required `action`) and injectable extension wiring, including conditional registration only inside Herdr, Fleet rendering, editor focus ownership, terminal-input passthrough, exact-pane navigation, and stable display while another background run queues.
19
+ - Verify the split public contracts (`herdr_subagents` with required `tasks`, plus `herdr_subagents_control` with required `action`) and injectable extension wiring, including conditional registration only inside Herdr, Fleet rendering, editor focus ownership, terminal-input passthrough, exact-pane navigation, child `/parent` and `Alt+P` return controls, and stable display while another background run queues.
20
20
  - Verify Fleet divider/row formatting, warning/yellow running indicators, success/green completed indicators, persona and task projections, cumulative active duration across blocking/resume, exact Pi usage aggregation from assistant messages, tool results, standalone usage, compaction, and branch summaries, malformed/truncated JSONL tolerance, queued metric omission, and visible-column bounds at narrow widths.
21
21
  - Verify live token updates are file-event driven and polled on each running one-second tick, elapsed repainting exists only while work runs, and widget disposal closes watchers and timers.
22
22
  - Verify selected active/queued run cancellation, blocked-response live Fleet updates, authoritative stale-field replacement, parent-shutdown cancellation, idempotent missing-tab cleanup, cleanup notifications, atomic state replacement, non-retried uncertain startup operations, and stalled warnings.
@@ -24,4 +24,4 @@
24
24
 
25
25
  ## Latest baseline
26
26
 
27
- All 168 deterministic scheduler, recovery, process-boundary, schema-validation, navigation, metrics, responsive-formatting, and persona cases must pass. Release validation also requires extension loading and a real Herdr read-only batch smoke with distinct task panes and final tab cleanup. The latest smoke completed two concurrent tasks, retained its focused run tab through settlement, and closed it automatically after focus moved away.
27
+ All 169 deterministic scheduler, recovery, process-boundary, schema-validation, navigation, metrics, responsive-formatting, and persona cases must pass. Release validation also requires extension loading and a real Herdr read-only batch smoke with distinct task panes and final tab cleanup. The latest smoke completed two concurrent tasks, retained its focused run tab through settlement, and closed it automatically after focus moved away.
@@ -4,6 +4,21 @@ import { StringEnum } from "@earendil-works/pi-ai";
4
4
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
5
  import { Type } from "typebox";
6
6
  import { resultPathForAttempt } from "../src/result-path.ts";
7
+ import { PARENT_PANE_ENV } from "../src/live-agent.ts";
8
+
9
+ const RETURN_SHORTCUT = "alt+p";
10
+
11
+ async function returnToParent(pi: ExtensionAPI, ctx: { ui: { notify(message: string, type?: "info" | "warning" | "error"): void } }): Promise<void> {
12
+ const parentPaneId = process.env[PARENT_PANE_ENV]?.trim();
13
+ if (!parentPaneId) {
14
+ ctx.ui.notify("Parent Agent pane is unavailable for this Subagent session.", "error");
15
+ return;
16
+ }
17
+ const result = await pi.exec("herdr", ["agent", "focus", parentPaneId]);
18
+ if (result.code !== 0) {
19
+ ctx.ui.notify(`Could not return to the parent Agent: ${result.stderr.trim() || result.stdout.trim() || `Herdr exited ${result.code}`}`, "error");
20
+ }
21
+ }
7
22
 
8
23
  const ReportParams = Type.Object({
9
24
  status: StringEnum(["completed", "needs-input", "failed"] as const, { description: "Current outcome for this task." }),
@@ -24,6 +39,17 @@ function writeAtomic(path: string, content: string): void {
24
39
  }
25
40
 
26
41
  export default function (pi: ExtensionAPI) {
42
+ pi.registerCommand("parent", {
43
+ description: "Return focus to the parent Agent pane",
44
+ async handler(_args, ctx) { await returnToParent(pi, ctx); },
45
+ });
46
+ pi.registerShortcut(RETURN_SHORTCUT, {
47
+ description: "Return to parent Agent",
48
+ async handler(ctx) { await returnToParent(pi, ctx); },
49
+ });
50
+ pi.on("session_start", async (_event, ctx) => {
51
+ if (process.env[PARENT_PANE_ENV]?.trim()) ctx.ui.setStatus("herdr-subagents-parent", `${RETURN_SHORTCUT} parent`);
52
+ });
27
53
  pi.registerTool({
28
54
  name: "subagent_report",
29
55
  label: "Subagent Report",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-herdr-subagents",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Run inspectable interactive Pi subagent batches through Herdr.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { appendFile, mkdir, readFile, rm, rmdir } from "node:fs/promises";
3
3
  import { isAbsolute, join } from "node:path";
4
4
  import { CliHerdrAutomation, type HerdrAutomation } from "./herdr.ts";
5
- import { liveAgentName, promptLiveAgent } from "./live-agent.ts";
5
+ import { childEnvironment, liveAgentName, promptLiveAgent } from "./live-agent.ts";
6
6
  import { writeJsonAtomic, writeTextAtomic } from "./state.ts";
7
7
  import { cleanupRunTab } from "./tab-cleanup.ts";
8
8
  import type { OwnerIdentity } from "./ownership.ts";
@@ -144,7 +144,7 @@ async function resumeHistoricalTaskLocked(cwd: string, runId: string, taskNumber
144
144
  let report: PersistedReport;
145
145
  let status: "blocked" | "completed" | "failed";
146
146
  try {
147
- const env = { PI_HERDR_SUBAGENTS_CHILD: "1", PI_HERDR_SUBAGENTS_TASK_DIR: historical.recordDirectory };
147
+ const env = childEnvironment(historical.recordDirectory);
148
148
  tab = await herdr.createTab({ workspaceId: process.env.HERDR_WORKSPACE_ID ?? "", cwd, label: `SA · ${run.label}`, env, focus: false, signal: options.signal });
149
149
  const paneLabel = `${String(historical.order).padStart(2, "0")} · ${historical.name}`;
150
150
  await herdr.renamePane(tab.paneId, paneLabel, options.signal);
@@ -17,6 +17,17 @@ export interface LiveTaskRecord {
17
17
  skills?: string[];
18
18
  }
19
19
 
20
+ export const PARENT_PANE_ENV = "PI_HERDR_SUBAGENTS_PARENT_PANE_ID";
21
+
22
+ export function childEnvironment(taskDirectory: string, environment: NodeJS.ProcessEnv = process.env): Record<string, string> {
23
+ const parentPaneId = environment.HERDR_PANE_ID?.trim();
24
+ return {
25
+ PI_HERDR_SUBAGENTS_CHILD: "1",
26
+ PI_HERDR_SUBAGENTS_TASK_DIR: taskDirectory,
27
+ ...(parentPaneId ? { [PARENT_PANE_ENV]: parentPaneId } : {}),
28
+ };
29
+ }
30
+
20
31
  export function childPiArgs(task: LiveTaskRecord, systemPromptFile: string): string[] {
21
32
  const args = [
22
33
  "--approve",
@@ -3,6 +3,7 @@ import { writeJsonAtomic } from "./state.ts";
3
3
  import type { RunRecord } from "./records.ts";
4
4
  import { applyRunStatus } from "./run-status.ts";
5
5
  import { cleanupRunTab } from "./tab-cleanup.ts";
6
+ import { childEnvironment } from "./live-agent.ts";
6
7
 
7
8
  interface RunPaneAllocatorOptions {
8
9
  herdr: HerdrAutomation;
@@ -31,7 +32,7 @@ export class RunPaneAllocator {
31
32
  let paneId = "";
32
33
  this.allocation = this.allocation.then(async () => {
33
34
  const { herdr, run, runFile, signal } = this.options;
34
- const env = { PI_HERDR_SUBAGENTS_CHILD: "1", PI_HERDR_SUBAGENTS_TASK_DIR: taskDirectory };
35
+ const env = childEnvironment(taskDirectory);
35
36
  let anchorExists = Boolean(run.tabId && this.lastPaneId);
36
37
  if (anchorExists && this.options.validateExistingPane) {
37
38
  const survivingPaneIds: string[] = [];
@@ -3,7 +3,7 @@ import { mkdir, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { CliHerdrAutomation, type HerdrAutomation } from "./herdr.ts";
5
5
  import { ensureRuntimeIgnored } from "./history.ts";
6
- import { liveAgentName, promptLiveAgent } from "./live-agent.ts";
6
+ import { childEnvironment, liveAgentName, promptLiveAgent } from "./live-agent.ts";
7
7
  import { writeJsonAtomic } from "./state.ts";
8
8
  import { cleanupRunTab } from "./tab-cleanup.ts";
9
9
  import type { OwnerIdentity } from "./ownership.ts";
@@ -13,9 +13,6 @@ import { reportProtocolPrompt } from "./protocol.ts";
13
13
  import { applyRunStatus } from "./run-status.ts";
14
14
  import { isAbortError } from "./errors.ts";
15
15
 
16
- const CHILD_ENV = "PI_HERDR_SUBAGENTS_CHILD";
17
- const TASK_DIRECTORY_ENV = "PI_HERDR_SUBAGENTS_TASK_DIR";
18
-
19
16
  export type HerdrSubagentsDocument = PersistedDocument;
20
17
  export type HerdrSubagentsReport = PersistedReport;
21
18
 
@@ -168,10 +165,7 @@ export async function runHerdrSubagents(prompt: string, options: HerdrSubagentsO
168
165
  workspaceId: process.env.HERDR_WORKSPACE_ID,
169
166
  cwd,
170
167
  label: `SA · ${label}`,
171
- env: {
172
- [CHILD_ENV]: "1",
173
- [TASK_DIRECTORY_ENV]: taskDirectory,
174
- },
168
+ env: childEnvironment(taskDirectory),
175
169
  focus: false,
176
170
  signal: options.signal,
177
171
  });
@@ -23,6 +23,7 @@ import { applyTaskStatus, isSettledRunStatus, projectTaskActivity, readTaskRecor
23
23
  import { RunPaneAllocator } from "../src/run-pane-allocator.ts";
24
24
  import { aggregateTaskStatus } from "../src/run-status.ts";
25
25
  import { isAbortError } from "../src/errors.ts";
26
+ import registerSubagentReport from "../extensions/subagent-report.ts";
26
27
 
27
28
  let passed = 0;
28
29
  let failed = 0;
@@ -313,7 +314,25 @@ try {
313
314
  const outside = await runHerdrSubagents("hello", { cwd: root, herdr: unavailableHerdr });
314
315
  check("Herdr is required", !outside.ok && outside.errorMessage?.includes("inside Herdr") && unavailableHerdr.calls.length === 0);
315
316
 
316
- setEnv({ HERDR_ENV: "1", HERDR_WORKSPACE_ID: "w1", PI_HERDR_SUBAGENTS_PI_BINARY: "/definitely/missing/pi" });
317
+ setEnv({ HERDR_ENV: "1", HERDR_WORKSPACE_ID: "w1", HERDR_PANE_ID: "w1:p-parent", PI_HERDR_SUBAGENTS_PI_BINARY: "/definitely/missing/pi" });
318
+ const childCommands = new Map();
319
+ const childShortcuts = new Map();
320
+ const childEvents = new Map();
321
+ const childExecCalls = [];
322
+ const childStatuses = [];
323
+ registerSubagentReport({
324
+ registerTool() {},
325
+ registerCommand(name, options) { childCommands.set(name, options); },
326
+ registerShortcut(key, options) { childShortcuts.set(key, options); },
327
+ on(name, handler) { childEvents.set(name, handler); },
328
+ async exec(command, args) { childExecCalls.push([command, args]); return { stdout: "", stderr: "", code: 0, killed: false }; },
329
+ });
330
+ process.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID = "w1:p-parent";
331
+ const childContext = { ui: { notify() {}, setStatus(key, value) { childStatuses.push([key, value]); } } };
332
+ await childEvents.get("session_start")?.({}, childContext);
333
+ await childCommands.get("parent")?.handler("", childContext);
334
+ await childShortcuts.get("alt+p")?.handler(childContext);
335
+ check("child command and shortcut return to the exact parent Agent pane", childExecCalls.length === 2 && childExecCalls.every(([command, args]) => command === "herdr" && args.join(",") === "agent,focus,w1:p-parent") && childStatuses.some(([, value]) => value.includes("alt+p")));
317
336
  const herdr = fakeHerdr();
318
337
  const success = await runHerdrSubagents("Review authentication", {
319
338
  cwd: root,
@@ -330,7 +349,7 @@ try {
330
349
  const startRequest = herdr.calls.find(([name]) => name === "startAgent")[1];
331
350
  check("named unfocused tab in parent workspace", createRequest.workspaceId === "w1" && createRequest.cwd === root && createRequest.label === "SA · auth-review" && createRequest.focus === false);
332
351
  check("ordinary execution never steals pane focus", !herdr.calls.some(([name]) => name === "focusPane"));
333
- check("child environment points at durable task", createRequest.env.PI_HERDR_SUBAGENTS_CHILD === "1" && createRequest.env.PI_HERDR_SUBAGENTS_TASK_DIR.includes(join(".pi", "herdr-subagents", "runs")));
352
+ check("child environment points at durable task and parent pane", createRequest.env.PI_HERDR_SUBAGENTS_CHILD === "1" && createRequest.env.PI_HERDR_SUBAGENTS_TASK_DIR.includes(join(".pi", "herdr-subagents", "runs")) && createRequest.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID === "w1:p-parent");
334
353
  check("persistent isolated child Pi", startRequest.kind === "pi" && startRequest.paneId === "w1:p2" && startRequest.args.includes("--session") && startRequest.args.includes("--no-extensions") && startRequest.args.includes("--extension") && startRequest.args.includes("--no-skills") && startRequest.args.includes("--model") && startRequest.args.includes("fake/model") && startRequest.args.includes("--tools") && startRequest.args.includes("read,grep,subagent_report"));
335
354
  const promptRequest = herdr.calls.find(([name]) => name === "promptAgent")[1];
336
355
  check("prompt submitted through Herdr", promptRequest.prompt === "Review authentication" && promptRequest.target === startRequest.name);