@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 +3 -0
- package/package.json +1 -1
- package/packages/herdr-subagents/CHANGELOG.md +5 -0
- package/packages/herdr-subagents/README.md +1 -1
- package/packages/herdr-subagents/TESTING.md +2 -2
- package/packages/herdr-subagents/extensions/subagent-report.ts +26 -0
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/history.ts +2 -2
- package/packages/herdr-subagents/src/live-agent.ts +11 -0
- package/packages/herdr-subagents/src/run-pane-allocator.ts +2 -1
- package/packages/herdr-subagents/src/runner.ts +2 -8
- package/packages/herdr-subagents/tests/run-tests.mjs +21 -2
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maxiaochao/pi-toolkit",
|
|
3
|
-
"version": "0.10.
|
|
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
|
|
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",
|
|
@@ -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 =
|
|
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 =
|
|
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);
|