@maxiaochao/pi-toolkit 0.9.6 → 0.10.1

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -2
  3. package/package.json +5 -5
  4. package/packages/herdr-subagents/CHANGELOG.md +50 -0
  5. package/packages/herdr-subagents/README.md +283 -0
  6. package/packages/herdr-subagents/TESTING.md +25 -0
  7. package/packages/herdr-subagents/agents/explorer.md +8 -0
  8. package/packages/herdr-subagents/agents/reviewer.md +8 -0
  9. package/packages/{tiny-subagent → herdr-subagents}/agents/worker.md +1 -0
  10. package/packages/herdr-subagents/docs/demo.webm +0 -0
  11. package/packages/herdr-subagents/docs/screenshot.png +0 -0
  12. package/packages/herdr-subagents/extensions/herdr-subagents.ts +258 -0
  13. package/packages/herdr-subagents/extensions/subagent-report.ts +65 -0
  14. package/packages/{tiny-subagent → herdr-subagents}/package.json +7 -6
  15. package/packages/herdr-subagents/src/batch-runner.ts +219 -0
  16. package/packages/herdr-subagents/src/blocking.ts +175 -0
  17. package/packages/herdr-subagents/src/config.ts +129 -0
  18. package/packages/herdr-subagents/src/continue-run.ts +87 -0
  19. package/packages/herdr-subagents/src/dispatcher.ts +73 -0
  20. package/packages/herdr-subagents/src/errors.ts +3 -0
  21. package/packages/herdr-subagents/src/fleet-controller.ts +82 -0
  22. package/packages/herdr-subagents/src/fleet.ts +56 -0
  23. package/packages/herdr-subagents/src/herdr.ts +224 -0
  24. package/packages/herdr-subagents/src/history.ts +210 -0
  25. package/packages/herdr-subagents/src/live-agent.ts +87 -0
  26. package/packages/herdr-subagents/src/monitor.ts +18 -0
  27. package/packages/herdr-subagents/src/ownership.ts +17 -0
  28. package/packages/herdr-subagents/src/parameters.ts +21 -0
  29. package/packages/{tiny-subagent → herdr-subagents}/src/personas.ts +13 -2
  30. package/packages/herdr-subagents/src/protocol.ts +23 -0
  31. package/packages/herdr-subagents/src/queued-runs.ts +70 -0
  32. package/packages/herdr-subagents/src/reconcile.ts +167 -0
  33. package/packages/herdr-subagents/src/records.ts +213 -0
  34. package/packages/herdr-subagents/src/result-path.ts +7 -0
  35. package/packages/herdr-subagents/src/run-locator.ts +19 -0
  36. package/packages/herdr-subagents/src/run-pane-allocator.ts +78 -0
  37. package/packages/herdr-subagents/src/run-status.ts +23 -0
  38. package/packages/herdr-subagents/src/runner.ts +250 -0
  39. package/packages/herdr-subagents/src/state.ts +11 -0
  40. package/packages/herdr-subagents/src/tab-cleanup.ts +68 -0
  41. package/packages/herdr-subagents/src/tool.ts +217 -0
  42. package/packages/herdr-subagents/src/validation.ts +38 -0
  43. package/packages/herdr-subagents/tests/extension-contract.ts +225 -0
  44. package/packages/herdr-subagents/tests/run-tests.mjs +895 -0
  45. package/packages/todo/extensions/todo.ts +1 -1
  46. package/packages/tiny-subagent/CHANGELOG.md +0 -12
  47. package/packages/tiny-subagent/README.md +0 -128
  48. package/packages/tiny-subagent/TESTING.md +0 -20
  49. package/packages/tiny-subagent/docs/demo.gif +0 -0
  50. package/packages/tiny-subagent/docs/screenshot.png +0 -0
  51. package/packages/tiny-subagent/extensions/tiny-subagent.ts +0 -228
  52. package/packages/tiny-subagent/src/runner.ts +0 -319
  53. package/packages/tiny-subagent/tests/run-tests.mjs +0 -111
  54. package/skills/calldiff/LICENSE +0 -21
  55. package/skills/calldiff/NOTICE +0 -19
  56. package/skills/calldiff/SKILL.md +0 -121
@@ -0,0 +1,895 @@
1
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { getEventListeners } from "node:events";
5
+ import { discoverPackageAgents, parseAgentMarkdown } from "../src/personas.ts";
6
+ import { loadSubagentConfiguration } from "../src/config.ts";
7
+ import { runHerdrSubagents } from "../src/runner.ts";
8
+ import { executeHerdrSubagents } from "../src/tool.ts";
9
+ import { activityCounts, focusActiveTask } from "../src/monitor.ts";
10
+ import { RunDispatcher } from "../src/dispatcher.ts";
11
+ import { ensureRuntimeIgnored, historyText, listSubagentHistory } from "../src/history.ts";
12
+ import { writeJsonAtomic } from "../src/state.ts";
13
+ import { cancelQueuedRuns, loadQueuedRuns, persistQueuedRun } from "../src/queued-runs.ts";
14
+ import { cancelActiveSubagentRuns, reconcileSubagentRuns } from "../src/reconcile.ts";
15
+ import { continueQueuedRun } from "../src/continue-run.ts";
16
+ import { CliHerdrAutomation } from "../src/herdr.ts";
17
+ import { fleetEditorHasFocus, FleetSelection, fleetLines } from "../src/fleet.ts";
18
+ import { HERDR_ACTIONS, validateToolParams } from "../src/validation.ts";
19
+ import { resultPathForAttempt } from "../src/result-path.ts";
20
+ import { isSettledRunStatus, readTaskRecord } from "../src/records.ts";
21
+ import { RunPaneAllocator } from "../src/run-pane-allocator.ts";
22
+ import { aggregateTaskStatus } from "../src/run-status.ts";
23
+ import { isAbortError } from "../src/errors.ts";
24
+
25
+ let passed = 0;
26
+ let failed = 0;
27
+ function check(name, condition, detail = "") {
28
+ if (condition) {
29
+ passed += 1;
30
+ console.log(`PASS ${name}`);
31
+ } else {
32
+ failed += 1;
33
+ console.log(`FAIL ${name} ${detail}`);
34
+ }
35
+ }
36
+
37
+ const root = mkdtempSync(join(tmpdir(), "pi-herdr-subagents-test-"));
38
+ const oldEnv = { ...process.env };
39
+ function setEnv(values) {
40
+ for (const key of Object.keys(process.env)) delete process.env[key];
41
+ Object.assign(process.env, oldEnv, values);
42
+ }
43
+ function restoreEnv() {
44
+ for (const key of Object.keys(process.env)) delete process.env[key];
45
+ Object.assign(process.env, oldEnv);
46
+ }
47
+
48
+ function fakeHerdr(mode = "completed") {
49
+ const calls = [];
50
+ let taskDirectory;
51
+ let paneSequence = 2;
52
+ let active = 0;
53
+ let maxActive = 0;
54
+ let taskRuns = 0;
55
+ const agents = new Map();
56
+ return {
57
+ calls,
58
+ get maxActive() { return maxActive; },
59
+ async createTab(request) {
60
+ calls.push(["createTab", request]);
61
+ taskDirectory = request.env.PI_HERDR_SUBAGENTS_TASK_DIR;
62
+ return { tabId: "w1:t2", paneId: "w1:p2" };
63
+ },
64
+ async startAgent(request) {
65
+ calls.push(["startAgent", request]);
66
+ const sessionIndex = request.args.indexOf("--session");
67
+ agents.set(request.name, { directory: sessionIndex >= 0 ? join(request.args[sessionIndex + 1], "..") : taskDirectory, request });
68
+ },
69
+ async promptAgent(request) {
70
+ calls.push(["promptAgent", request]);
71
+ const current = agents.get(request.target) ?? { directory: taskDirectory };
72
+ const directory = current.directory;
73
+ taskRuns += 1;
74
+ active += 1;
75
+ maxActive = Math.max(maxActive, active);
76
+ await new Promise((resolve) => setTimeout(resolve, request.prompt.includes("slow") ? 15 : 2));
77
+ if (mode === "report-then-post-fail") {
78
+ writeFileSync(join(directory, "report.json"), JSON.stringify({ status: "completed", summary: "Authoritative report completed before transport failed.", documents: [] }));
79
+ active -= 1;
80
+ throw new Error("connection lost after authoritative report");
81
+ }
82
+ if (mode === "post-fail" || (mode === "blocked-then-post-fail" && taskRuns > 1)) { active -= 1; throw new Error("connection lost after prompt submission"); }
83
+ writeFileSync(join(directory, "session.jsonl"), `${JSON.stringify({ type: "session", version: 3, id: request.target, cwd: root })}\n`);
84
+ if (mode === "completed" || mode === "close-failed") {
85
+ const failed = request.prompt.includes("FAIL");
86
+ writeFileSync(join(directory, "result.md"), request.prompt.startsWith("Review") ? "FULL RESULT THAT STAYS OUT OF PARENT CONTEXT" : `FULL ${request.prompt}`);
87
+ writeFileSync(join(directory, "report.json"), JSON.stringify({
88
+ status: failed ? "failed" : "completed",
89
+ summary: request.prompt.startsWith("Review") ? "Authentication review completed." : failed ? `${request.prompt} failed.` : `${request.prompt} completed.`,
90
+ ...(failed ? { error: "expected failure" } : {}),
91
+ documents: [{ path: join(directory, "result.md"), description: "Full result" }],
92
+ }));
93
+ } else if (mode === "failed") {
94
+ writeFileSync(join(directory, "result.md"), "Provider stopped before completing the review.");
95
+ writeFileSync(join(directory, "report.json"), JSON.stringify({
96
+ status: "failed",
97
+ summary: "Authentication review failed.",
98
+ error: "provider unavailable",
99
+ documents: [],
100
+ }));
101
+ } else if (mode === "malformed") {
102
+ writeFileSync(join(directory, "report.json"), "{truncated");
103
+ } else if (mode === "blocked" || mode === "blocked-twice" || mode === "blocked-then-post-fail") {
104
+ const needsInput = ((mode === "blocked" || mode === "blocked-then-post-fail") && taskRuns === 1) || (mode === "blocked-twice" && (taskRuns === 1 || taskRuns === 3));
105
+ const failed = request.prompt.includes("FAIL");
106
+ const question = taskRuns === 3 ? "Which environment should I inspect?" : "Which branch should I inspect?";
107
+ writeFileSync(join(directory, "result.md"), `FULL ${request.prompt}`);
108
+ writeFileSync(join(directory, "report.json"), JSON.stringify({ status: needsInput ? "needs-input" : failed ? "failed" : "completed", summary: needsInput ? "Need more input." : failed ? `${request.prompt} failed.` : `${request.prompt} completed.`, ...(needsInput ? { question } : {}), ...(failed ? { error: "expected failure" } : {}), documents: [] }));
109
+ }
110
+ active -= 1;
111
+ return { status: mode === "failed" ? "done" : "idle" };
112
+ },
113
+ async splitPane(request) {
114
+ calls.push(["splitPane", request]);
115
+ paneSequence += 1;
116
+ return { paneId: `w1:p${paneSequence}` };
117
+ },
118
+ async renamePane(paneId, label) { calls.push(["renamePane", { paneId, label }]); },
119
+ async renameTab(tabId, label) { calls.push(["renameTab", { tabId, label }]); },
120
+ async focusPane(paneId) { calls.push(["focusPane", { paneId }]); },
121
+ async isTabFocused(tabId) { calls.push(["isTabFocused", { tabId }]); return mode === "focused"; },
122
+ async waitForTabUnfocused(tabId) { calls.push(["waitForTabUnfocused", { tabId }]); },
123
+ async paneExists(paneId) { calls.push(["paneExists", { paneId }]); return mode !== "missing-pane"; },
124
+ async isTaskRunning(paneId) { calls.push(["isTaskRunning", { paneId }]); return mode !== "missing-pane" && mode !== "shell-only"; },
125
+ async closeTab(tabId) {
126
+ calls.push(["closeTab", { tabId }]);
127
+ if (mode === "close-failed") throw new Error("tab still busy");
128
+ },
129
+ };
130
+ }
131
+
132
+ function latestRunDirectory() {
133
+ const runs = join(root, ".pi", "herdr-subagents", "runs");
134
+ const entries = readdirSync(runs).sort();
135
+ return join(runs, entries[entries.length - 1]);
136
+ }
137
+
138
+ try {
139
+ check("flat mode validation accepts supported calls", [
140
+ { action: "list" }, { action: "history" }, { action: "cleanup", runId: "run" },
141
+ { action: "respond", runId: "run", answer: "yes", task: 1 },
142
+ { action: "resume", runId: "run", task: 1, prompt: "continue" },
143
+ { prompt: "one task", agent: "worker", label: "one", model: "fake/model" },
144
+ { tasks: [{ name: "one", prompt: "do it" }], concurrency: 2, background: true },
145
+ ].every((params) => validateToolParams(params) === undefined));
146
+ check("all supported actions have shared validation and rendering metadata", ["list", "history", "cleanup", "respond", "resume"].every((action) => action in HERDR_ACTIONS));
147
+ check("flat mode validation rejects ambiguous and extraneous fields", [
148
+ validateToolParams({ prompt: "one", tasks: [{ name: "two", prompt: "two" }] }),
149
+ validateToolParams({ action: "list", prompt: "not allowed" }),
150
+ validateToolParams({ action: "respond", runId: "run" }),
151
+ validateToolParams({ tasks: [{ name: "one", prompt: "one" }], agent: "worker" }),
152
+ validateToolParams({ prompt: "one", runId: "run" }),
153
+ validateToolParams({ prompt: "one", task: 1 }),
154
+ validateToolParams({ tasks: [{ name: "one", prompt: "one" }], answer: "extra" }),
155
+ ].every((message) => typeof message === "string" && message.length > 0));
156
+ const fleet = new FleetSelection();
157
+ const fleetBatch = { label: "reviews", activity: [
158
+ { index: 0, name: "one", status: "completed", paneId: "p1" },
159
+ { index: 1, name: "two", status: "running", paneId: "p2" },
160
+ ] };
161
+ check("Fleet selection activates only from an empty editor", !fleet.handle("\u001b[B", "draft", 2).consume && fleet.handle("\u001b[B", "", 2).consume && fleet.isSelecting());
162
+ check("Fleet arrows move and Enter selects the exact row", fleet.handle("\u001b[B", "", 2).consume && fleet.handle("\r", "", 2).focusTask === 2 && fleetLines(fleetBatch, fleet)[2].startsWith("›"));
163
+ check("Fleet unrelated input exits without consuming it", fleet.handle("x", "", 2).consume !== true && !fleet.isSelecting());
164
+ check("Fleet Escape exits selection", fleet.handle("\u001b[B", "", 2).consume && fleet.handle("\u001b", "", 2).consume && !fleet.isSelecting());
165
+ const enhancedFleet = new FleetSelection((data, key) => data === `enhanced-${key}`);
166
+ check("Fleet delegates enhanced keyboard decoding to Pi key matching", enhancedFleet.handle("enhanced-down", "", 2).consume && enhancedFleet.handle("enhanced-up", "", 2).consume && enhancedFleet.handle("enhanced-enter", "", 2).focusTask === 2);
167
+ check("Fleet ignores unknown focus, dialogs, and overlays", fleetEditorHasFocus(undefined, () => false) === false && fleetEditorHasFocus({ kind: "dialog" }, () => false) === false && fleetEditorHasFocus({ kind: "editor" }, () => true) === true);
168
+ let unknownStatusRejected = false;
169
+ try { aggregateTaskStatus(["unknown"]); } catch { unknownStatusRejected = true; }
170
+ check("task status aggregation rejects unknown states", unknownStatusRejected);
171
+ check("all execution paths share abort classification", isAbortError(Object.assign(new Error("stopped"), { name: "AbortError" })) && isAbortError(new Error("operation aborted")) && !isAbortError(new Error("provider unavailable")));
172
+ check("respond releases ownership only for explicit terminal statuses", ["completed", "partial", "failed", "cancelled"].every(isSettledRunStatus) && !isSettledRunStatus("blocked") && !isSettledRunStatus(undefined));
173
+
174
+ setEnv({ HERDR_ENV: "0", PI_HERDR_SUBAGENTS_PI_BINARY: "/definitely/missing/pi" });
175
+ const unavailableHerdr = fakeHerdr();
176
+ const outside = await runHerdrSubagents("hello", { cwd: root, herdr: unavailableHerdr });
177
+ check("Herdr is required", !outside.ok && outside.errorMessage?.includes("inside Herdr") && unavailableHerdr.calls.length === 0);
178
+
179
+ setEnv({ HERDR_ENV: "1", HERDR_WORKSPACE_ID: "w1", PI_HERDR_SUBAGENTS_PI_BINARY: "/definitely/missing/pi" });
180
+ const herdr = fakeHerdr();
181
+ const success = await runHerdrSubagents("Review authentication", {
182
+ cwd: root,
183
+ label: "auth-review",
184
+ agent: "worker",
185
+ model: "fake/model",
186
+ tools: ["read", "grep"],
187
+ systemPrompt: "You are a focused worker.",
188
+ herdr,
189
+ });
190
+ check("compact successful report", success.ok && success.status === "completed" && success.summary === "Authentication review completed." && success.documents.length === 1 && !success.output.includes("FULL RESULT"));
191
+ check("Herdr lifecycle", herdr.calls.map(([name]) => name).join(",") === "createTab,renamePane,startAgent,promptAgent,isTabFocused,closeTab");
192
+ const createRequest = herdr.calls.find(([name]) => name === "createTab")[1];
193
+ const startRequest = herdr.calls.find(([name]) => name === "startAgent")[1];
194
+ check("named unfocused tab in parent workspace", createRequest.workspaceId === "w1" && createRequest.cwd === root && createRequest.label === "SA · auth-review" && createRequest.focus === false);
195
+ 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")));
196
+ 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"));
197
+ const promptRequest = herdr.calls.find(([name]) => name === "promptAgent")[1];
198
+ check("prompt submitted through Herdr", promptRequest.prompt === "Review authentication" && promptRequest.target === startRequest.name);
199
+
200
+ const runDirectory = latestRunDirectory();
201
+ const runRecord = JSON.parse(readFileSync(join(runDirectory, "run.json"), "utf8"));
202
+ const taskDirectory = join(runDirectory, "tasks", readdirSync(join(runDirectory, "tasks"))[0]);
203
+ const taskRecord = JSON.parse(readFileSync(join(taskDirectory, "task.json"), "utf8"));
204
+ check("run and task records settle with pane and Agent identity", runRecord.status === "completed" && taskRecord.status === "completed" && taskRecord.prompt === "Review authentication" && taskRecord.model === "fake/model" && taskRecord.paneLabel === "01 · auth-review" && taskRecord.agentName === startRequest.name);
205
+ check("full result and session stay on disk", readFileSync(join(taskDirectory, "result.md"), "utf8").startsWith("FULL RESULT") && existsSync(join(taskDirectory, "session.jsonl")));
206
+
207
+ const extensionHerdr = fakeHerdr();
208
+ const publicResult = await executeHerdrSubagents({ agent: "worker", prompt: "Review through the public tool" }, undefined, undefined, {
209
+ cwd: root,
210
+ model: { provider: "fake", id: "model" },
211
+ thinkingLevel: "medium",
212
+ }, { agentsDirectory: join(process.cwd(), "agents"), herdr: extensionHerdr });
213
+ check("public extension behavior uses injectable Herdr", !publicResult.isError && publicResult.content[0].text.includes("Authentication review completed.") && !publicResult.content[0].text.includes("FULL RESULT") && extensionHerdr.calls.map(([name]) => name).join(",") === "createTab,renamePane,startAgent,promptAgent,isTabFocused,closeTab", JSON.stringify({ publicResult, calls: extensionHerdr.calls.map(([name]) => name) }));
214
+ let singleStalledWarning = "";
215
+ await runHerdrSubagents("single slow task", { cwd: root, herdr: fakeHerdr(), stalledWarningSeconds: 0.001, onStalled: (message) => { singleStalledWarning = message; } });
216
+ check("single-task execution emits configured stalled warning", singleStalledWarning.includes("appears stalled"));
217
+
218
+ const failedHerdr = fakeHerdr("failed");
219
+ const failedRun = await runHerdrSubagents("Fail cleanly", { cwd: root, herdr: failedHerdr });
220
+ check("structured child failure", !failedRun.ok && failedRun.status === "failed" && failedRun.summary === "Authentication review failed." && failedRun.errorMessage === "provider unavailable" && failedHerdr.calls.at(-1)[0] === "closeTab");
221
+
222
+ const silentHerdr = fakeHerdr("silent");
223
+ const silent = await runHerdrSubagents("Forget report", { cwd: root, herdr: silentHerdr });
224
+ check("missing report is visible failure", !silent.ok && silent.errorMessage?.includes("did not submit") && silentHerdr.calls.at(-1)[0] === "closeTab");
225
+ const malformedExecutionHerdr = fakeHerdr("malformed");
226
+ const malformedExecution = await runHerdrSubagents("Write malformed report", { cwd: root, herdr: malformedExecutionHerdr });
227
+ const malformedExecutionTaskDirectory = malformedExecution.recordDirectory;
228
+ const repairedExecutionReport = JSON.parse(readFileSync(join(malformedExecutionTaskDirectory, "report.json"), "utf8"));
229
+ check("malformed child report becomes a durable usable failure", !malformedExecution.ok && repairedExecutionReport.status === "failed" && readdirSync(malformedExecutionTaskDirectory).some((entry) => entry.startsWith("report.json.invalid-")) && (await listSubagentHistory(root)).some((run) => run.id === malformedExecution.runId));
230
+ const firstResultPath = resultPathForAttempt(join(root, "report-tool"), 1);
231
+ const secondResultPath = resultPathForAttempt(join(root, "report-tool"), 2);
232
+ check("historical attempts use distinct full-result documents", firstResultPath.endsWith("/result.md") && secondResultPath.endsWith("/turns/02-result.md") && firstResultPath !== secondResultPath);
233
+
234
+ const cleanupHerdr = fakeHerdr("close-failed");
235
+ const cleanupFailure = await runHerdrSubagents("Complete then fail cleanup", { cwd: root, herdr: cleanupHerdr });
236
+ const cleanupRunDirectory = latestRunDirectory();
237
+ const cleanupRunRecord = JSON.parse(readFileSync(join(cleanupRunDirectory, "run.json"), "utf8"));
238
+ const cleanupTaskDirectory = join(cleanupRunDirectory, "tasks", readdirSync(join(cleanupRunDirectory, "tasks"))[0]);
239
+ const cleanupTaskRecord = JSON.parse(readFileSync(join(cleanupTaskDirectory, "task.json"), "utf8"));
240
+ check("tab cleanup failure settles records as failed", !cleanupFailure.ok && cleanupFailure.errorMessage?.includes("cleanup failed") && cleanupRunRecord.status === "failed" && cleanupTaskRecord.status === "failed");
241
+
242
+ const definition = parseAgentMarkdown(`---\nname: reviewer\ndescription: Review code\nmodel: fake/model\ntools: read, grep\n---\nReview carefully.`, "reviewer.md");
243
+ check("persona frontmatter parsing", definition.name === "reviewer" && definition.model === "fake/model" && definition.tools?.join(",") === "read,grep" && definition.systemPrompt === "Review carefully.");
244
+ const agentsDir = join(root, "agents");
245
+ await import("node:fs/promises").then(({ mkdir }) => mkdir(agentsDir));
246
+ writeFileSync(join(agentsDir, "worker.md"), `---\nname: worker\ndescription: Worker\n---\nDo work.`);
247
+ writeFileSync(join(agentsDir, "broken.md"), "not frontmatter");
248
+ const discovery = discoverPackageAgents(agentsDir);
249
+ check("persona discovery and diagnostics", discovery.agents.length === 1 && discovery.agents[0].name === "worker" && discovery.diagnostics.length === 1);
250
+
251
+ const configRoot = join(root, "configuration");
252
+ const packageAgents = join(configRoot, "package-agents");
253
+ const globalAgents = join(configRoot, "global-agents");
254
+ const projectAgents = join(configRoot, "project-agents");
255
+ mkdirSync(packageAgents, { recursive: true });
256
+ mkdirSync(globalAgents, { recursive: true });
257
+ mkdirSync(projectAgents, { recursive: true });
258
+ writeFileSync(join(packageAgents, "worker.md"), `---\nname: worker\ndescription: Package worker\naccess: write\nmodel: package/model\nskills: package-skill\n---\nPackage prompt.`);
259
+ writeFileSync(join(packageAgents, "explorer.md"), `---\nname: explorer\ndescription: Package explorer\naccess: read\n---\nExplore.`);
260
+ writeFileSync(join(packageAgents, "thinker.md"), `---\nname: thinker\ndescription: Package thinker\nthinking: minimal\n---\nThink.`);
261
+ writeFileSync(join(globalAgents, "worker.md"), `---\nname: worker\ndescription: Global worker\naccess: read\n---\nGlobal prompt.`);
262
+ writeFileSync(join(projectAgents, "worker.md"), `---\nname: worker\ndescription: Project worker\n---\nProject prompt.`);
263
+ const globalConfig = join(configRoot, "global.json");
264
+ const projectConfig = join(configRoot, "project.json");
265
+ writeFileSync(globalConfig, JSON.stringify({ maxConcurrency: 8, defaultModel: "global/default", defaultThinking: "medium", personas: { worker: { model: "global/worker", thinking: "low", skills: ["global-skill"] } } }));
266
+ writeFileSync(projectConfig, JSON.stringify({ maxConcurrency: 3, defaultThinking: "high", personas: { worker: { model: "project/worker", thinking: "high", skills: ["project-skill"] } } }));
267
+ const configurationPaths = { globalConfig, projectConfig, globalAgents, projectAgents };
268
+ const configuration = loadSubagentConfiguration(configRoot, packageAgents, "parent/model", configurationPaths);
269
+ const configuredWorker = configuration.personas.find((persona) => persona.name === "worker");
270
+ const configuredExplorer = configuration.personas.find((persona) => persona.name === "explorer");
271
+ const configuredThinker = configuration.personas.find((persona) => persona.name === "thinker");
272
+ check("built-in settings recursively merge global then project", configuration.settings.defaultConcurrency === 1 && configuration.settings.maxConcurrency === 3 && configuration.settings.defaultModel === "global/default" && configuration.settings.defaultThinking === "high" && configuration.settingSources.maxConcurrency === "project" && configuration.settingSources.defaultThinking === "project");
273
+ check("persona definitions use project global package precedence", configuredWorker?.source === "project" && configuredWorker.description === "Project worker" && configuredWorker.systemPrompt === "Project prompt." && configuredWorker.access === "write" && configuredExplorer?.source === "package" && configuredExplorer.access === "read");
274
+ check("persona configuration resolves model thinking and skills", configuredWorker?.model === "project/worker" && configuredWorker.modelSource === "project persona" && configuredWorker.thinking === "high" && configuredWorker.skills.join(",") === "project-skill");
275
+ check("project default thinking applies below persona settings", configuredExplorer?.thinking === "high" && configuredExplorer.thinkingSource === "project");
276
+ check("persona Markdown thinking overrides default thinking", configuredThinker?.thinking === "minimal" && configuredThinker.thinkingSource === "package persona");
277
+ writeFileSync(projectConfig, JSON.stringify({ defaultThinking: [], personas: { worker: { model: 42, thinking: [], skills: "invalid" } } }));
278
+ const invalidConfiguration = loadSubagentConfiguration(configRoot, packageAgents, "parent/model", configurationPaths);
279
+ const invalidWorker = invalidConfiguration.personas.find((persona) => persona.name === "worker");
280
+ check("invalid persona fields are diagnosed and ignored", invalidWorker?.model === "global/worker" && invalidWorker.thinking === "low" && invalidWorker.skills.join(",") === "global-skill" && invalidConfiguration.diagnostics.filter((message) => message.includes("persona 'worker'")).length === 3);
281
+ const invalidExplorer = invalidConfiguration.personas.find((persona) => persona.name === "explorer");
282
+ check("invalid project default thinking is diagnosed and global default remains effective", invalidExplorer?.thinking === "medium" && invalidExplorer.thinkingSource === "global" && invalidConfiguration.diagnostics.some((message) => message === "project defaultThinking must be a non-empty string"));
283
+ writeFileSync(projectConfig, JSON.stringify({ maxConcurrency: 3, defaultThinking: "high", personas: { worker: { model: "project/worker", thinking: "high", skills: ["project-skill"] } } }));
284
+ const configuredHerdr = fakeHerdr();
285
+ const configuredRun = await executeHerdrSubagents({ agent: "worker", model: "task/model", prompt: "Use configured values" }, undefined, undefined, { cwd: configRoot, model: { provider: "parent", id: "model" }, thinkingLevel: "medium" }, { agentsDirectory: packageAgents, configurationPaths, herdr: configuredHerdr });
286
+ const configuredStart = configuredHerdr.calls.find(([name]) => name === "startAgent")[1];
287
+ check("configured persona uses displayed effective values", !configuredRun.isError && configuredStart.args.includes("task/model") && configuredStart.args.includes("high") && configuredStart.args.includes("--skill") && configuredStart.args.includes("project-skill"));
288
+
289
+ const batchHerdr = fakeHerdr();
290
+ const batchUpdates = [];
291
+ const batch = await executeHerdrSubagents({ label: "reviews", concurrency: 2, tasks: [
292
+ { name: "one", prompt: "one slow", agent: "explorer" },
293
+ { name: "two", prompt: "two", agent: "reviewer" },
294
+ { name: "three", prompt: "three FAIL", agent: "explorer" },
295
+ { name: "four", prompt: "four", agent: "reviewer" },
296
+ ] }, undefined, (update) => batchUpdates.push(update.details), { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: batchHerdr });
297
+ const batchDetails = batch.details;
298
+ const batchPrompts = batchHerdr.calls.filter(([name]) => name === "promptAgent").map(([, request]) => request.prompt);
299
+ const batchStarts = batchHerdr.calls.filter(([name]) => name === "startAgent").map(([, request]) => request);
300
+ check("read-only batch uses bounded concurrency", batchDetails.effectiveConcurrency === 2 && batchHerdr.maxActive === 2 && batchHerdr.calls.filter(([name]) => name === "splitPane").length === 3);
301
+ check("batch is FIFO with a dedicated pane per task", batchPrompts.join(",") === "one slow,two,three FAIL,four" && new Set(batchStarts.map((request) => request.paneId)).size === 4);
302
+ check("failed sibling does not cancel batch", batch.isError && batchDetails.status === "partial" && batchDetails.tasks.map((task) => task.name).join(",") === "one,two,three,four" && batchDetails.tasks[2].status === "failed" && batchDetails.tasks[3].status === "completed");
303
+ const batchCleanupHerdr = fakeHerdr("close-failed");
304
+ const batchCleanup = await executeHerdrSubagents({ tasks: [{ name: "cleanup", prompt: "cleanup", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: batchCleanupHerdr });
305
+ const batchCleanupRecord = JSON.parse(readFileSync(join(batchCleanup.details.recordDirectory, "run.json"), "utf8"));
306
+ check("batch cleanup failure is persisted and surfaced", batchCleanup.isError && batchCleanup.content[0].text.includes("cleanup failed") && batchCleanupRecord.status === "failed" && batchCleanupRecord.cleanupError.includes("tab still busy"));
307
+ const cleanupProbeHerdr = fakeHerdr();
308
+ cleanupProbeHerdr.isTabFocused = async () => { throw new Error("tab lookup failed"); };
309
+ const cleanupProbe = await executeHerdrSubagents({ tasks: [{ name: "cleanup-probe", prompt: "cleanup probe", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: cleanupProbeHerdr });
310
+ const cleanupProbeRecord = JSON.parse(readFileSync(join(cleanupProbe.details.recordDirectory, "run.json"), "utf8"));
311
+ check("cleanup focus-probe failure is persisted and surfaced", cleanupProbe.isError && cleanupProbe.content[0].text.includes("tab lookup failed") && cleanupProbeRecord.status === "failed" && cleanupProbeRecord.cleanupError === "tab lookup failed");
312
+ const activeUpdate = batchUpdates.find((update) => update.activity?.some((task) => task.status === "running"));
313
+ const counts = activityCounts(activeUpdate);
314
+ const focusHerdr = fakeHerdr();
315
+ const runningTask = activeUpdate.activity.find((task) => task.status === "running");
316
+ const focused = await focusActiveTask(activeUpdate, runningTask.index + 1, focusHerdr);
317
+ check("active progress exposes widget counts and exact pane navigation", counts.running > 0 && counts.queued > 0 && focused && focusHerdr.calls.at(-1)[0] === "focusPane" && focusHerdr.calls.at(-1)[1].paneId === runningTask.paneId);
318
+ const settledFocused = await focusActiveTask({ activity: [{ index: 0, name: "settled", status: "completed", paneId: "w1:p-settled" }] }, 1, focusHerdr);
319
+ check("settled Fleet rows remain selectable while the batch is open", settledFocused && focusHerdr.calls.at(-1)[1].paneId === "w1:p-settled");
320
+ const paneLabels = batchHerdr.calls.filter(([name]) => name === "renamePane").map(([, request]) => request.label);
321
+ check("tab and dedicated panes use short stable labels", batchHerdr.calls[0][1].label === "SA · reviews" && paneLabels.includes("01 · one") && paneLabels.includes("04 · four"));
322
+ const writerHerdr = fakeHerdr();
323
+ const writerBatch = await executeHerdrSubagents({ concurrency: 3, tasks: [{ name: "write-one", prompt: "write one" }, { name: "write-two", prompt: "write two" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: writerHerdr });
324
+ check("write-capable batch is forced serial", writerBatch.details.effectiveConcurrency === 1 && writerHerdr.maxActive === 1 && writerHerdr.calls.filter(([name]) => name === "splitPane").length === 1);
325
+ const focusedHerdr = fakeHerdr("focused");
326
+ await executeHerdrSubagents({ tasks: [{ name: "inspect", prompt: "inspect", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: focusedHerdr });
327
+ await new Promise((resolve) => setTimeout(resolve, 0));
328
+ const focusedCalls = focusedHerdr.calls.map(([name]) => name);
329
+ check("focused completed tab defers cleanup until focus leaves", focusedCalls.indexOf("waitForTabUnfocused") >= 0 && focusedCalls.indexOf("closeTab") > focusedCalls.indexOf("waitForTabUnfocused"));
330
+ const pendingCleanupHerdr = fakeHerdr();
331
+ let releasePendingCleanup;
332
+ pendingCleanupHerdr.isTabFocused = async () => true;
333
+ pendingCleanupHerdr.waitForTabUnfocused = async () => new Promise((resolve) => { releasePendingCleanup = resolve; });
334
+ const pendingCleanupRun = await executeHerdrSubagents({ tasks: [{ name: "pending-cleanup", prompt: "inspect", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: pendingCleanupHerdr });
335
+ const pendingCleanupRecord = JSON.parse(readFileSync(join(pendingCleanupRun.details.recordDirectory, "run.json"), "utf8"));
336
+ const cleanupWhilePending = await executeHerdrSubagents({ action: "cleanup", runId: pendingCleanupRun.details.runId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: pendingCleanupHerdr });
337
+ const resumeWhilePending = await executeHerdrSubagents({ action: "resume", runId: pendingCleanupRun.details.runId, task: 1, prompt: "too soon" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: pendingCleanupHerdr });
338
+ check("pending tab cleanup blocks history resume and deletion", pendingCleanupRecord.cleanupPendingTabIds?.includes(pendingCleanupRun.details.tabId) && cleanupWhilePending.isError && resumeWhilePending.isError);
339
+ releasePendingCleanup();
340
+ await new Promise((resolve) => setTimeout(resolve, 10));
341
+ const releasedCleanupRecord = JSON.parse(readFileSync(join(pendingCleanupRun.details.recordDirectory, "run.json"), "utf8"));
342
+ check("deferred cleanup merges into the latest run record", releasedCleanupRecord.status === "completed" && !("cleanupPendingTabIds" in releasedCleanupRecord));
343
+ const shutdownDeferredHerdr = fakeHerdr();
344
+ shutdownDeferredHerdr.isTabFocused = async () => true;
345
+ shutdownDeferredHerdr.waitForTabUnfocused = async (tabId, signal) => {
346
+ shutdownDeferredHerdr.calls.push(["waitForTabUnfocused", { tabId }]);
347
+ return await new Promise((resolve, reject) => {
348
+ if (signal?.aborted) reject(signal.reason);
349
+ else signal?.addEventListener("abort", () => reject(signal.reason), { once: true });
350
+ });
351
+ };
352
+ const shutdownDeferredController = new AbortController();
353
+ await executeHerdrSubagents({ tasks: [{ name: "shutdown-deferred", prompt: "inspect", agent: "explorer" }] }, shutdownDeferredController.signal, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: shutdownDeferredHerdr });
354
+ shutdownDeferredController.abort(new Error("Parent Pi session closed."));
355
+ await new Promise((resolve) => setTimeout(resolve, 10));
356
+ check("parent shutdown force-closes a settled focused tab", shutdownDeferredHerdr.calls.some(([name]) => name === "closeTab"));
357
+ const deferredFailureHerdr = fakeHerdr();
358
+ deferredFailureHerdr.isTabFocused = async () => true;
359
+ deferredFailureHerdr.closeTab = async () => { throw new Error("deferred close failed"); };
360
+ const deferredFailure = await executeHerdrSubagents({ tasks: [{ name: "deferred-cleanup", prompt: "inspect", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: deferredFailureHerdr });
361
+ await new Promise((resolve) => setTimeout(resolve, 10));
362
+ const deferredFailureRecord = JSON.parse(readFileSync(join(deferredFailure.details.recordDirectory, "run.json"), "utf8"));
363
+ check("deferred focus cleanup failure remains durably retryable", deferredFailureRecord.cleanupError === "deferred close failed" && deferredFailureRecord.cleanupPendingTabIds?.includes(deferredFailure.details.tabId));
364
+ const deferredSingleHerdr = fakeHerdr();
365
+ deferredSingleHerdr.isTabFocused = async () => true;
366
+ deferredSingleHerdr.closeTab = async () => { throw new Error("single deferred close failed"); };
367
+ const deferredSingleUpdates = [];
368
+ const deferredSingle = await executeHerdrSubagents({ prompt: "single deferred cleanup", agent: "explorer" }, undefined, (update) => deferredSingleUpdates.push(update), { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: deferredSingleHerdr });
369
+ await new Promise((resolve) => setTimeout(resolve, 10));
370
+ const deferredSingleRun = JSON.parse(readFileSync(join(deferredSingle.details.recordDirectory, "..", "..", "run.json"), "utf8"));
371
+ check("deferred single-task cleanup failure is persisted and surfaced", deferredSingleRun.status === "failed" && deferredSingleRun.cleanupError === "single deferred close failed" && deferredSingleUpdates.some((update) => update.content[0].text.includes("cleanup failed")));
372
+ const cancelledHerdr = fakeHerdr();
373
+ cancelledHerdr.isTabFocused = async (tabId) => { cancelledHerdr.calls.push(["isTabFocused", { tabId }]); return true; };
374
+ cancelledHerdr.waitForTabUnfocused = async () => { throw new Error("shutdown cleanup must not wait for focus"); };
375
+ cancelledHerdr.promptAgent = async (request) => {
376
+ cancelledHerdr.calls.push(["promptAgent", request]);
377
+ await new Promise((resolve, reject) => {
378
+ if (request.signal?.aborted) reject(request.signal.reason);
379
+ else request.signal?.addEventListener("abort", () => reject(request.signal.reason), { once: true });
380
+ });
381
+ };
382
+ const cancellation = new AbortController();
383
+ const cancelledPromise = executeHerdrSubagents({ tasks: [{ name: "cancel-me", prompt: "wait", agent: "explorer" }] }, cancellation.signal, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: cancelledHerdr });
384
+ await new Promise((resolve) => setTimeout(resolve, 5));
385
+ cancellation.abort(new Error("Parent Pi session closed."));
386
+ const cancelledBatch = await cancelledPromise;
387
+ const cancelledTaskRecord = JSON.parse(readFileSync(join(cancelledBatch.details.recordDirectory, "tasks", readdirSync(join(cancelledBatch.details.recordDirectory, "tasks"))[0], "task.json"), "utf8"));
388
+ check("already-aborted parent shutdown force-closes a focused active tab", cancelledBatch.details.status === "cancelled" && cancelledTaskRecord.status === "cancelled" && cancelledHerdr.calls.at(-1)[0] === "closeTab" && !cancelledHerdr.calls.some(([name]) => name === "isTabFocused" || name === "waitForTabUnfocused"));
389
+ const cancelledSingleHerdr = fakeHerdr();
390
+ cancelledSingleHerdr.promptAgent = async (request) => {
391
+ cancelledSingleHerdr.calls.push(["promptAgent", request]);
392
+ await new Promise((resolve, reject) => {
393
+ if (request.signal?.aborted) reject(request.signal.reason);
394
+ else request.signal?.addEventListener("abort", () => reject(request.signal.reason), { once: true });
395
+ });
396
+ };
397
+ const singleCancellation = new AbortController();
398
+ const cancelledSinglePromise = runHerdrSubagents("wait", { cwd: root, herdr: cancelledSingleHerdr, signal: singleCancellation.signal });
399
+ await new Promise((resolve) => setTimeout(resolve, 5));
400
+ singleCancellation.abort(new Error("Parent Pi session closed."));
401
+ const cancelledSingle = await cancelledSinglePromise;
402
+ const cancelledSingleRun = JSON.parse(readFileSync(join(cancelledSingle.recordDirectory, "..", "..", "run.json"), "utf8"));
403
+ check("parent shutdown persists active single task as cancelled", cancelledSingle.status === "cancelled" && cancelledSingleRun.status === "cancelled" && cancelledSingleHerdr.calls.at(-1)[0] === "closeTab");
404
+
405
+ const dispatchEvents = [];
406
+ const dispatcher = new RunDispatcher((snapshot) => dispatchEvents.push(snapshot));
407
+ let releaseFirst;
408
+ let releaseSecond;
409
+ const starts = [];
410
+ const first = dispatcher.submit("run-one", async () => { starts.push("run-one"); await new Promise((resolve) => { releaseFirst = resolve; }); return "one"; });
411
+ const second = dispatcher.submit("run-two", async () => { starts.push("run-two"); await new Promise((resolve) => { releaseSecond = resolve; }); return "two"; });
412
+ await new Promise((resolve) => setTimeout(resolve, 0));
413
+ check("dispatcher runs only one run and exposes queued successors", starts.join(",") === "run-one" && dispatcher.snapshot().activeRunId === "run-one" && dispatcher.snapshot().queuedRunIds.join(",") === "run-two");
414
+ dispatcher.pause();
415
+ releaseFirst();
416
+ check("dispatcher returns first result and advances FIFO", await first === "one");
417
+ await new Promise((resolve) => setTimeout(resolve, 0));
418
+ check("parent shutdown pauses new queued dispatch", starts.join(",") === "run-one" && dispatcher.snapshot().queuedRunIds.join(",") === "run-two");
419
+ dispatcher.resume();
420
+ await new Promise((resolve) => setTimeout(resolve, 0));
421
+ check("dispatcher starts next queued run automatically", starts.join(",") === "run-one,run-two" && dispatcher.snapshot().activeRunId === "run-two");
422
+ releaseSecond();
423
+ check("dispatcher settles queued run", await second === "two");
424
+ const retainedDispatcher = new RunDispatcher();
425
+ const retainedStarts = [];
426
+ const retainedFirst = retainedDispatcher.submit("blocked-run", async () => { retainedStarts.push("blocked-run"); retainedDispatcher.retain("blocked-run"); return "blocked"; });
427
+ const retainedSecond = retainedDispatcher.submit("queued-run", async () => { retainedStarts.push("queued-run"); return "completed"; });
428
+ check("blocked run retains dispatcher ownership after its call returns", await retainedFirst === "blocked" && retainedDispatcher.snapshot().activeRunId === "blocked-run" && retainedStarts.join(",") === "blocked-run" && retainedDispatcher.snapshot().queuedRunIds.join(",") === "queued-run");
429
+ retainedDispatcher.release("blocked-run");
430
+ check("respond settlement releases dispatcher ownership to the queued run", await retainedSecond === "completed" && retainedStarts.join(",") === "blocked-run,queued-run");
431
+ const backgroundId = "stable-background-id";
432
+ const stableHerdr = fakeHerdr();
433
+ const stable = await executeHerdrSubagents({ tasks: [{ name: "stable", prompt: "stable", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: stableHerdr, runId: backgroundId });
434
+ check("preallocated background run id stays stable", stable.details.runId === backgroundId && readFileSync(join(stable.details.recordDirectory, "run.json"), "utf8").includes(backgroundId));
435
+
436
+ const blockedHerdr = fakeHerdr("blocked");
437
+ const blocked = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "question", prompt: "inspect branch", agent: "explorer" }, { name: "after-answer", prompt: "continue queued", agent: "reviewer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: blockedHerdr });
438
+ check("needs-input returns foreground control and preserves tab", blocked.details.status === "blocked" && blocked.details.tasks[0].question === "Which branch should I inspect?" && !blockedHerdr.calls.some(([name]) => name === "closeTab"));
439
+ const activeCleanup = await executeHerdrSubagents({ action: "cleanup", runId: blocked.details.runId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: blockedHerdr });
440
+ check("cleanup refuses to delete an active blocked run", activeCleanup.isError && activeCleanup.content[0].text.includes("still active") && existsSync(blocked.details.recordDirectory));
441
+ const resumeUpdates = [];
442
+ const resumed = await executeHerdrSubagents({ action: "respond", runId: blocked.details.runId, answer: "Inspect main." }, undefined, (update) => resumeUpdates.push(update.details), { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: blockedHerdr });
443
+ const blockedStarts = blockedHerdr.calls.filter(([name]) => name === "startAgent").map(([, request]) => request);
444
+ const blockedPrompts = blockedHerdr.calls.filter(([name]) => name === "promptAgent").map(([, request]) => request);
445
+ check("answer resumes original live Agent and queued sibling gets a new pane", !resumed.isError && resumed.details.status === "completed" && blockedPrompts.length === 3 && blockedPrompts[1].target === blockedPrompts[0].target && blockedStarts.length === 2 && new Set(blockedStarts.map((request) => request.paneId)).size === 2);
446
+ check("answer streams live Fleet rows for resumed and newly allocated panes", resumeUpdates.some((update) => update.activity?.[0]?.status === "running" && update.activity[0].paneId) && resumeUpdates.some((update) => update.activity?.[1]?.status === "running" && update.activity[1].paneId));
447
+ const resumedTasks = readdirSync(join(blocked.details.recordDirectory, "tasks")).sort().map((entry) => JSON.parse(readFileSync(join(blocked.details.recordDirectory, "tasks", entry, "task.json"), "utf8")));
448
+ const resumedRunRecord = JSON.parse(readFileSync(join(blocked.details.recordDirectory, "run.json"), "utf8"));
449
+ check("completion after answer continues queued siblings and settles original run", resumedRunRecord.status === "completed" && resumedTasks.every((task) => task.status === "completed") && blockedHerdr.calls.at(-1)[0] === "closeTab");
450
+ check("blocked continuation preserves newly allocated pane IDs", resumedTasks.every((task) => resumedRunRecord.paneIds.includes(task.paneId)));
451
+ const resumedTurn = JSON.parse(readFileSync(resumedTasks[0].currentTurnFile, "utf8"));
452
+ check("authoritative response clears stale blocked fields", !("question" in resumedTasks[0]) && !("error" in resumedTasks[0]) && !("question" in resumedTurn) && !("error" in resumedTurn));
453
+ const twiceBlockedHerdr = fakeHerdr("blocked-twice");
454
+ const twiceBlocked = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "first-question", prompt: "first", agent: "explorer" }, { name: "second-question", prompt: "second", agent: "reviewer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: twiceBlockedHerdr });
455
+ const secondQuestion = await executeHerdrSubagents({ action: "respond", runId: twiceBlocked.details.runId, answer: "main" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: twiceBlockedHerdr });
456
+ check("queued sibling blocking returns its current question and Fleet pane", secondQuestion.details.status === "blocked" && secondQuestion.details.question === "Which environment should I inspect?" && secondQuestion.content[0].text.includes("environment") && secondQuestion.details.activity[1].status === "blocked" && Boolean(secondQuestion.details.activity[1].paneId));
457
+ const failedAfterBlockHerdr = fakeHerdr("blocked");
458
+ const failedAfterBlock = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "question-first", prompt: "question", agent: "explorer" }, { name: "fails-later", prompt: "FAIL sibling", agent: "reviewer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: failedAfterBlockHerdr });
459
+ const failedAnswer = await executeHerdrSubagents({ action: "respond", runId: failedAfterBlock.details.runId, answer: "main" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: failedAfterBlockHerdr });
460
+ check("failed sibling after answer appears in compact result", failedAnswer.isError && failedAnswer.details.status === "partial" && failedAnswer.content[0].text.includes("fails-later") && failedAnswer.content[0].text.includes("failed"));
461
+ const responseFailureHerdr = fakeHerdr("blocked-then-post-fail");
462
+ const responseFailureRun = await executeHerdrSubagents({ tasks: [{ name: "response-fails", prompt: "question", agent: "explorer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: responseFailureHerdr });
463
+ const responseFailure = await executeHerdrSubagents({ action: "respond", runId: responseFailureRun.details.runId, answer: "main" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: responseFailureHerdr });
464
+ const responseFailureTask = JSON.parse(readFileSync(join(responseFailureRun.details.recordDirectory, "tasks", readdirSync(join(responseFailureRun.details.recordDirectory, "tasks"))[0], "task.json"), "utf8"));
465
+ check("failed blocked response settles durable task instead of leaving running", responseFailure.isError && responseFailure.details.status === "failed" && responseFailureTask.status === "failed" && responseFailureHerdr.calls.at(-1)[0] === "closeTab");
466
+ const siblingFailureHerdr = fakeHerdr("blocked-then-post-fail");
467
+ const siblingFailureRun = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "response-fails", prompt: "question", agent: "explorer" }, { name: "queued-sibling", prompt: "later", agent: "reviewer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: siblingFailureHerdr });
468
+ await executeHerdrSubagents({ action: "respond", runId: siblingFailureRun.details.runId, answer: "main" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: siblingFailureHerdr });
469
+ const siblingFailureTasks = readdirSync(join(siblingFailureRun.details.recordDirectory, "tasks")).map((entry) => JSON.parse(readFileSync(join(siblingFailureRun.details.recordDirectory, "tasks", entry, "task.json"), "utf8")));
470
+ check("failed blocked response releases its slot and continues queued siblings", siblingFailureTasks.every((task) => ["failed", "completed"].includes(task.status)) && siblingFailureTasks.every((task) => task.status !== "cancelled") && siblingFailureHerdr.calls.filter(([name]) => name === "promptAgent").length === 3);
471
+ const abortedContinuationHerdr = fakeHerdr("blocked");
472
+ const abortedContinuationRun = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "already-done", prompt: "question", agent: "explorer" }, { name: "cancel-me", prompt: "later", agent: "reviewer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: abortedContinuationHerdr });
473
+ const abortedTaskDirectories = readdirSync(join(abortedContinuationRun.details.recordDirectory, "tasks")).sort().map((entry) => join(abortedContinuationRun.details.recordDirectory, "tasks", entry));
474
+ const firstAbortedTask = JSON.parse(readFileSync(join(abortedTaskDirectories[0], "task.json"), "utf8"));
475
+ writeFileSync(join(abortedTaskDirectories[0], "task.json"), JSON.stringify({ ...firstAbortedTask, status: "completed" }));
476
+ const continuationAbort = new AbortController();
477
+ continuationAbort.abort(new Error("Parent Pi session closed."));
478
+ abortedContinuationHerdr.promptAgent = async () => { throw new Error("Parent Pi session closed."); };
479
+ await continueQueuedRun(abortedContinuationRun.details.recordDirectory, abortedContinuationHerdr, { cleanup: false, signal: continuationAbort.signal });
480
+ const cancelledContinuationTask = JSON.parse(readFileSync(join(abortedTaskDirectories[1], "task.json"), "utf8"));
481
+ const cancelledContinuationRun = JSON.parse(readFileSync(join(abortedContinuationRun.details.recordDirectory, "run.json"), "utf8"));
482
+ const cancelledContinuationReport = JSON.parse(readFileSync(join(abortedTaskDirectories[1], "report.json"), "utf8"));
483
+ check("aborted queued continuation is persisted as cancelled", cancelledContinuationTask.status === "cancelled" && cancelledContinuationRun.status === "cancelled" && cancelledContinuationReport.summary.includes("cancelled"));
484
+ const deadPaneHerdr = fakeHerdr("blocked");
485
+ const deadPaneRun = await executeHerdrSubagents({ tasks: [{ name: "dead-pane", prompt: "question", agent: "explorer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: deadPaneHerdr });
486
+ deadPaneHerdr.paneExists = async () => false;
487
+ const deadPaneResume = await executeHerdrSubagents({ action: "respond", runId: deadPaneRun.details.runId, answer: "main" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: deadPaneHerdr });
488
+ const deadPaneTaskDirectory = join(deadPaneRun.details.recordDirectory, "tasks", readdirSync(join(deadPaneRun.details.recordDirectory, "tasks"))[0]);
489
+ const deadPaneTask = JSON.parse(readFileSync(join(deadPaneTaskDirectory, "task.json"), "utf8"));
490
+ check("dead blocked pane recovers a coherent attempt with its persona prompt", !deadPaneResume.isError && deadPaneHerdr.calls.filter(([name]) => name === "createTab").length === 2 && deadPaneTask.tabId === "w1:t2" && deadPaneTask.attempt === 2 && deadPaneTask.currentTurnFile.endsWith("02.json") && existsSync(deadPaneTask.currentTurnFile) && readFileSync(join(deadPaneTaskDirectory, "resume-system-prompt.md"), "utf8").includes("Investigate the assigned question without modifying files"));
491
+ const staleTabHerdr = fakeHerdr("blocked");
492
+ const staleTabRun = await executeHerdrSubagents({ tasks: [{ name: "stale-tab", prompt: "question", agent: "explorer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: staleTabHerdr });
493
+ staleTabHerdr.paneExists = async () => false;
494
+ staleTabHerdr.createTab = async (request) => { staleTabHerdr.calls.push(["createTab", request]); return { tabId: "w1:t-new", paneId: "w1:p-new" }; };
495
+ staleTabHerdr.closeTab = async (tabId) => { staleTabHerdr.calls.push(["closeTab", { tabId }]); if (tabId === "w1:t2") throw new Error("stale tab still busy"); };
496
+ const staleTabUpdates = [];
497
+ const staleTabResume = await executeHerdrSubagents({ action: "respond", runId: staleTabRun.details.runId, answer: "main" }, undefined, (update) => staleTabUpdates.push(update), { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: staleTabHerdr });
498
+ const staleTabRecord = JSON.parse(readFileSync(join(staleTabRun.details.recordDirectory, "run.json"), "utf8"));
499
+ check("dead-pane migration persists and surfaces stale tab cleanup failure", !staleTabResume.isError && staleTabRecord.cleanupError === "stale tab still busy" && staleTabRecord.staleTabIds?.includes("w1:t2") && staleTabUpdates.some((update) => update.content[0].text.includes("Could not close stale Herdr tab")));
500
+ const invalidTurnHerdr = fakeHerdr("blocked");
501
+ const invalidTurnRun = await executeHerdrSubagents({ tasks: [{ name: "invalid-turn", prompt: "question", agent: "explorer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: invalidTurnHerdr });
502
+ const invalidTurnTaskDirectory = join(invalidTurnRun.details.recordDirectory, "tasks", readdirSync(join(invalidTurnRun.details.recordDirectory, "tasks"))[0]);
503
+ const invalidTurnTaskFile = join(invalidTurnTaskDirectory, "task.json");
504
+ const invalidTurnTask = JSON.parse(readFileSync(invalidTurnTaskFile, "utf8"));
505
+ const invalidTurnFile = join(invalidTurnTaskDirectory, "turns", "01.json");
506
+ mkdirSync(join(invalidTurnTaskDirectory, "turns"), { recursive: true });
507
+ writeFileSync(invalidTurnFile, "{truncated");
508
+ invalidTurnTask.currentTurnFile = invalidTurnFile;
509
+ writeFileSync(invalidTurnTaskFile, JSON.stringify(invalidTurnTask));
510
+ const invalidTurnUpdates = [];
511
+ await executeHerdrSubagents({ action: "respond", runId: invalidTurnRun.details.runId, answer: "main" }, undefined, (update) => invalidTurnUpdates.push(update), { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: invalidTurnHerdr });
512
+ check("invalid durable turn is archived and surfaced before recovery", readdirSync(join(invalidTurnTaskDirectory, "turns")).some((name) => name.startsWith("01.json.invalid-")) && invalidTurnUpdates.some((update) => update.content[0].text.includes("Archived invalid durable turn")));
513
+ const corruptLookupRoot = join(root, "corrupt-run-lookup");
514
+ mkdirSync(join(corruptLookupRoot, ".pi", "herdr-subagents", "runs", "bad"), { recursive: true });
515
+ writeFileSync(join(corruptLookupRoot, ".pi", "herdr-subagents", "runs", "bad", "run.json"), "{truncated");
516
+ const corruptLookup = await executeHerdrSubagents({ action: "respond", runId: "missing", answer: "main" }, undefined, undefined, { cwd: corruptLookupRoot }, { agentsDirectory: join(process.cwd(), "agents"), herdr: fakeHerdr() });
517
+ check("blocked response surfaces unreadable run records instead of reporting unknown", corruptLookup.isError && corruptLookup.content[0].text.includes("Could not inspect Subagent runs"));
518
+ const survivingPaneRunFile = join(root, "surviving-pane-run.json");
519
+ const survivingPaneRun = { id: "surviving-pane", label: "surviving-pane", status: "blocked", cwd: root, tabId: "w1:t-surviving", paneIds: ["w1:p-live", "w1:p-dead"] };
520
+ writeFileSync(survivingPaneRunFile, JSON.stringify(survivingPaneRun));
521
+ const survivingPaneHerdr = fakeHerdr();
522
+ survivingPaneHerdr.paneExists = async (paneId) => paneId === "w1:p-live";
523
+ const survivingPaneAllocator = new RunPaneAllocator({ herdr: survivingPaneHerdr, run: survivingPaneRun, runFile: survivingPaneRunFile, validateExistingPane: true });
524
+ await survivingPaneAllocator.allocate(root);
525
+ check("missing allocation anchor preserves and splits a surviving sibling pane", survivingPaneHerdr.calls.some(([name, request]) => name === "splitPane" && request.paneId === "w1:p-live") && !survivingPaneHerdr.calls.some(([name]) => name === "createTab" || name === "closeTab") && survivingPaneRun.paneIds[0] === "w1:p-live" && survivingPaneRun.paneIds.length === 2);
526
+ const multiStaleRoot = join(root, "multi-stale-recovery");
527
+ const multiStaleDirectory = join(multiStaleRoot, ".pi", "herdr-subagents", "runs", "multi-stale");
528
+ mkdirSync(multiStaleDirectory, { recursive: true });
529
+ const multiStaleRunFile = join(multiStaleDirectory, "run.json");
530
+ const multiStaleRun = { id: "multi-stale", label: "multi-stale", status: "blocked", cwd: multiStaleRoot, tabId: "w1:t-old-a", paneIds: ["w1:p-dead-a"] };
531
+ writeFileSync(multiStaleRunFile, JSON.stringify(multiStaleRun));
532
+ const multiStaleHerdr = fakeHerdr();
533
+ multiStaleHerdr.paneExists = async () => false;
534
+ let replacementNumber = 0;
535
+ multiStaleHerdr.createTab = async (request) => { multiStaleHerdr.calls.push(["createTab", request]); replacementNumber += 1; return { tabId: `w1:t-new-${replacementNumber}`, paneId: `w1:p-new-${replacementNumber}` }; };
536
+ multiStaleHerdr.closeTab = async (tabId) => { multiStaleHerdr.calls.push(["closeTab", { tabId }]); throw new Error(`cannot close ${tabId}`); };
537
+ const multiStaleErrors = [];
538
+ const multiStaleAllocator = new RunPaneAllocator({ herdr: multiStaleHerdr, run: multiStaleRun, runFile: multiStaleRunFile, validateExistingPane: true, onCleanupError: (message) => multiStaleErrors.push(message) });
539
+ await multiStaleAllocator.allocate(multiStaleRoot);
540
+ await multiStaleAllocator.allocate(multiStaleRoot);
541
+ const pendingMultiStale = JSON.parse(readFileSync(multiStaleRunFile, "utf8"));
542
+ pendingMultiStale.status = "completed";
543
+ writeFileSync(multiStaleRunFile, JSON.stringify(pendingMultiStale));
544
+ check("successive stale-tab cleanup failures remain visible and durable", multiStaleErrors.length === 2 && ["w1:t-old-a", "w1:t-new-1"].every((tabId) => pendingMultiStale.cleanupPendingTabIds?.includes(tabId)));
545
+ const multiStaleRecoveryHerdr = fakeHerdr();
546
+ await reconcileSubagentRuns(multiStaleRoot, multiStaleRecoveryHerdr);
547
+ const recoveredMultiStale = JSON.parse(readFileSync(multiStaleRunFile, "utf8"));
548
+ const recoveredTabIds = multiStaleRecoveryHerdr.calls.filter(([name]) => name === "closeTab").map(([, request]) => request.tabId);
549
+ check("startup reconciliation retries every pending stale tab", ["w1:t-old-a", "w1:t-new-1"].every((tabId) => recoveredTabIds.includes(tabId)) && !("cleanupPendingTabIds" in recoveredMultiStale));
550
+
551
+ const history = await listSubagentHistory(root);
552
+ const stableHistory = history.find((run) => run.id === backgroundId);
553
+ check("history lists compact project-local runs and tasks", Boolean(stableHistory?.tasks.length) && historyText(history).includes("stable") && !historyText(history).includes("FULL stable"));
554
+ const historyHerdr = fakeHerdr();
555
+ const historicalResume = await executeHerdrSubagents({ action: "resume", runId: backgroundId, task: 1, prompt: "follow up" }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: historyHerdr });
556
+ const historyStartRequest = historyHerdr.calls.find(([name]) => name === "startAgent")[1];
557
+ check("historical resume preserves the saved Pi session and persona prompt", !historicalResume.isError && historyHerdr.calls[0][0] === "createTab" && historyStartRequest.args.includes(stableHistory.tasks[0].sessionFile) && historyStartRequest.args.includes("fake/model") && historyStartRequest.args.some((value) => String(value).includes("subagent_report")) && existsSync(join(stableHistory.tasks[0].recordDirectory, "turns", "02.json")) && readFileSync(join(stableHistory.tasks[0].recordDirectory, "followup-system-prompt.md"), "utf8").includes("Investigate the assigned question without modifying files"));
558
+ const historicalTaskRecord = JSON.parse(readFileSync(join(stableHistory.tasks[0].recordDirectory, "task.json"), "utf8"));
559
+ const historicalTurnRecord = JSON.parse(readFileSync(join(stableHistory.tasks[0].recordDirectory, "turns", "02.json"), "utf8"));
560
+ check("historical task and turn retain their tab identity", historicalTaskRecord.tabId === historicalResume.details.tabId && historicalTurnRecord.tabId === historicalResume.details.tabId);
561
+ const resumeRaceRoot = join(root, "resume-race");
562
+ const resumeRaceSeed = await runHerdrSubagents("original", { cwd: resumeRaceRoot, herdr: fakeHerdr() });
563
+ const resumeRaceHerdr = fakeHerdr();
564
+ const createRaceTab = resumeRaceHerdr.createTab.bind(resumeRaceHerdr);
565
+ let beginFirstCreate;
566
+ const firstCreateStarted = new Promise((resolve) => { beginFirstCreate = resolve; });
567
+ let releaseFirstCreate;
568
+ const firstCreateGate = new Promise((resolve) => { releaseFirstCreate = resolve; });
569
+ let raceCreates = 0;
570
+ resumeRaceHerdr.createTab = async (request) => {
571
+ raceCreates += 1;
572
+ if (raceCreates === 1) { beginFirstCreate(); await firstCreateGate; }
573
+ return createRaceTab(request);
574
+ };
575
+ const firstResume = executeHerdrSubagents({ action: "resume", runId: resumeRaceSeed.runId, task: 1, prompt: "first" }, undefined, undefined, { cwd: resumeRaceRoot }, { agentsDirectory: join(process.cwd(), "agents"), herdr: resumeRaceHerdr });
576
+ await firstCreateStarted;
577
+ const competingResume = await executeHerdrSubagents({ action: "resume", runId: resumeRaceSeed.runId, task: 1, prompt: "second" }, undefined, undefined, { cwd: resumeRaceRoot }, { agentsDirectory: join(process.cwd(), "agents"), herdr: resumeRaceHerdr });
578
+ releaseFirstCreate();
579
+ await firstResume;
580
+ check("concurrent historical resume cannot start a second Pi session writer", competingResume.isError && raceCreates === 1 && resumeRaceHerdr.calls.filter(([name]) => name === "startAgent").length === 1);
581
+ check("historical resume releases the run lock after settlement", !existsSync(join(resumeRaceSeed.recordDirectory, ".resume.lock")));
582
+ const partialResumeHerdr = fakeHerdr();
583
+ const partialResumeTaskFile = join((await listSubagentHistory(root)).find((run) => run.id === failedAfterBlock.details.runId).tasks[0].recordDirectory, "task.json");
584
+ const stalePartialTask = JSON.parse(readFileSync(partialResumeTaskFile, "utf8"));
585
+ Object.assign(stalePartialTask, { error: "stale failure", question: "stale question" });
586
+ writeFileSync(partialResumeTaskFile, JSON.stringify(stalePartialTask));
587
+ await executeHerdrSubagents({ action: "resume", runId: failedAfterBlock.details.runId, task: 1, prompt: "follow up completed task" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: partialResumeHerdr });
588
+ const partialResumeRun = JSON.parse(readFileSync(join(failedAfterBlock.details.recordDirectory, "run.json"), "utf8"));
589
+ check("historical follow-up preserves partial batch aggregation", partialResumeRun.status === "partial");
590
+ const partialResumeTask = JSON.parse(readFileSync(partialResumeTaskFile, "utf8"));
591
+ check("historical follow-up clears stale failure fields", partialResumeTask.status === "completed" && !("error" in partialResumeTask) && !("question" in partialResumeTask));
592
+ const historicalCancellationHerdr = fakeHerdr();
593
+ historicalCancellationHerdr.promptAgent = async (request) => await new Promise((resolve, reject) => {
594
+ if (request.signal?.aborted) reject(request.signal.reason);
595
+ else request.signal?.addEventListener("abort", () => reject(request.signal.reason), { once: true });
596
+ });
597
+ const historicalCancellationController = new AbortController();
598
+ const historicalCancellationPromise = executeHerdrSubagents({ action: "resume", runId: failedAfterBlock.details.runId, task: 1, prompt: "cancel this follow-up" }, historicalCancellationController.signal, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: historicalCancellationHerdr });
599
+ await new Promise((resolve) => setTimeout(resolve, 5));
600
+ historicalCancellationController.abort(new Error("Parent Pi session closed."));
601
+ await historicalCancellationPromise;
602
+ const historicalCancelledRun = JSON.parse(readFileSync(join(failedAfterBlock.details.recordDirectory, "run.json"), "utf8"));
603
+ check("parent shutdown marks a historical follow-up run cancelled despite completed siblings", historicalCancelledRun.status === "cancelled" && historicalCancelledRun.error.includes("Parent Pi session closed"));
604
+ const followupBlockedHerdr = fakeHerdr("blocked");
605
+ const blockedFollowup = await executeHerdrSubagents({ action: "resume", runId: backgroundId, task: 1, prompt: "ask a follow-up" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: followupBlockedHerdr });
606
+ const duplicateResumeHerdr = fakeHerdr();
607
+ const duplicateResume = await executeHerdrSubagents({ action: "resume", runId: backgroundId, task: 1, prompt: "duplicate live follow-up" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: duplicateResumeHerdr });
608
+ check("historical resume rejects an active run without starting a second Agent", duplicateResume.isError && duplicateResume.content[0].text.includes("still active") && duplicateResumeHerdr.calls.length === 0);
609
+ const answeredFollowup = await executeHerdrSubagents({ action: "respond", runId: backgroundId, answer: "follow-up answer" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: followupBlockedHerdr });
610
+ const historicalTurns = join(stableHistory.tasks[0].recordDirectory, "turns");
611
+ const answeredTurn = JSON.parse(readFileSync(join(historicalTurns, "03.json"), "utf8"));
612
+ check("blocked historical follow-up remains publicly resumable with durable turns", blockedFollowup.details.status === "blocked" && !answeredFollowup.isError && answeredFollowup.details.status === "completed" && existsSync(join(historicalTurns, "01.json")) && answeredTurn.status === "completed" && answeredTurn.events.some((event) => event.type === "answer"));
613
+ const prePromptFailureTaskFile = join(stableHistory.tasks[0].recordDirectory, "task.json");
614
+ const prePromptStaleTask = JSON.parse(readFileSync(prePromptFailureTaskFile, "utf8"));
615
+ Object.assign(prePromptStaleTask, { question: "stale question", error: "stale error", completedAt: "2000-01-01T00:00:00.000Z" });
616
+ writeFileSync(prePromptFailureTaskFile, JSON.stringify(prePromptStaleTask));
617
+ const prePromptFailureHerdr = fakeHerdr();
618
+ prePromptFailureHerdr.startAgent = async () => { throw new Error("historical startup failed"); };
619
+ const prePromptFailure = await executeHerdrSubagents({ action: "resume", runId: backgroundId, task: 1, prompt: "cannot start" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: prePromptFailureHerdr });
620
+ const prePromptFailedTask = JSON.parse(readFileSync(prePromptFailureTaskFile, "utf8"));
621
+ const prePromptFailureReport = JSON.parse(readFileSync(join(stableHistory.tasks[0].recordDirectory, "report.json"), "utf8"));
622
+ check("historical pre-prompt failure replaces stale state and report", prePromptFailure.isError && prePromptFailedTask.status === "failed" && !("question" in prePromptFailedTask) && prePromptFailedTask.error.includes("historical startup failed") && prePromptFailedTask.completedAt !== "2000-01-01T00:00:00.000Z" && prePromptFailureReport.status === "failed" && prePromptFailureReport.error.includes("historical startup failed"));
623
+ check("historical resume releases the run lock after failure", !existsSync(join(stableHistory.recordDirectory, ".resume.lock")));
624
+ const postPromptFailureHerdr = fakeHerdr("post-fail");
625
+ const postPromptFailure = await executeHerdrSubagents({ action: "resume", runId: backgroundId, task: 1, prompt: "fails after submit" }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: postPromptFailureHerdr });
626
+ const postPromptFailureReport = JSON.parse(readFileSync(join(stableHistory.tasks[0].recordDirectory, "report.json"), "utf8"));
627
+ check("historical post-prompt failure persists its authoritative report", postPromptFailure.isError && postPromptFailureReport.status === "failed" && postPromptFailureReport.error.includes("connection lost after prompt submission"));
628
+ const shutdownBlockedHerdr = fakeHerdr("blocked");
629
+ const shutdownBlocked = await executeHerdrSubagents({ tasks: [{ name: "blocked-at-shutdown", prompt: "need input", agent: "explorer" }] }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: shutdownBlockedHerdr });
630
+ await cancelActiveSubagentRuns(root, shutdownBlockedHerdr);
631
+ const shutdownBlockedRecord = JSON.parse(readFileSync(join(shutdownBlocked.details.recordDirectory, "run.json"), "utf8"));
632
+ check("parent shutdown cancels blocked work and closes its tab", shutdownBlockedRecord.status === "cancelled" && shutdownBlockedHerdr.calls.at(-1)[0] === "closeTab");
633
+ const ignoreFile = join(root, ".pi", ".gitignore");
634
+ writeFileSync(ignoreFile, `${readFileSync(ignoreFile, "utf8")}unrelated-state/\n`);
635
+ await ensureRuntimeIgnored(root);
636
+ const ignoreText = readFileSync(ignoreFile, "utf8");
637
+ check("runtime archive is ignored without hiding personas or overwriting unrelated rules", ignoreText.includes("herdr-subagents/runs/") && !ignoreText.split(/\r?\n/).includes("herdr-subagents/") && ignoreText.includes("unrelated-state/") && ignoreText.match(/herdr-subagents\/runs\//g)?.length === 1);
638
+ const remainingRunId = history.find((run) => run.id !== backgroundId)?.id;
639
+ const cleaned = await executeHerdrSubagents({ action: "cleanup", runId: backgroundId }, undefined, undefined, { cwd: root }, { agentsDirectory: join(process.cwd(), "agents"), herdr: fakeHerdr() });
640
+ const afterCleanup = await listSubagentHistory(root);
641
+ check("explicit cleanup removes only the selected run", !cleaned.isError && !afterCleanup.some((run) => run.id === backgroundId) && (!remainingRunId || afterCleanup.some((run) => run.id === remainingRunId)));
642
+
643
+ const retryHerdr = fakeHerdr();
644
+ const createTab = retryHerdr.createTab.bind(retryHerdr);
645
+ let createAttempts = 0;
646
+ retryHerdr.createTab = async (request) => { createAttempts += 1; if (createAttempts === 1) { await createTab(request); throw new Error("tab created but response lost"); } return await createTab(request); };
647
+ const retried = await executeHerdrSubagents({ tasks: [{ name: "retry", prompt: "retry", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: retryHerdr });
648
+ check("uncertain tab creation is not retried", retried.isError && createAttempts === 1 && retryHerdr.calls.filter(([name]) => name === "promptAgent").length === 0);
649
+ const splitFailureRoot = join(root, "split-failure");
650
+ const splitFailureHerdr = fakeHerdr();
651
+ let splitAttempts = 0;
652
+ splitFailureHerdr.splitPane = async () => { splitAttempts += 1; throw new Error("split committed but response lost"); };
653
+ const splitFailure = await executeHerdrSubagents({ concurrency: 1, tasks: [{ name: "first", prompt: "first", agent: "explorer" }, { name: "second", prompt: "second", agent: "reviewer" }] }, undefined, undefined, { cwd: splitFailureRoot }, { agentsDirectory: join(process.cwd(), "agents"), herdr: splitFailureHerdr });
654
+ check("uncertain pane split is not retried", splitFailure.isError && splitAttempts === 1);
655
+ const permanentStartupHerdr = fakeHerdr();
656
+ let permanentAttempts = 0;
657
+ permanentStartupHerdr.createTab = async () => { permanentAttempts += 1; throw new Error("permanent startup failure"); };
658
+ const permanentStartup = await executeHerdrSubagents({ label: "permanent-startup", tasks: [{ name: "never-started", prompt: "never", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: permanentStartupHerdr });
659
+ const permanentHistory = (await listSubagentHistory(root)).find((run) => run.label === "permanent-startup");
660
+ check("tab creation failure persists failed run", permanentStartup.isError && permanentAttempts === 1 && permanentHistory?.status === "failed" && permanentHistory.tasks.every((task) => task.status === "failed"));
661
+ const startRetryHerdr = fakeHerdr();
662
+ const originalStart = startRetryHerdr.startAgent.bind(startRetryHerdr);
663
+ let startAttempts = 0;
664
+ startRetryHerdr.startAgent = async (request) => { startAttempts += 1; await originalStart(request); if (startAttempts === 1) throw new Error("Agent started but response lost"); };
665
+ const startRetryRun = await executeHerdrSubagents({ tasks: [{ name: "start-retry", prompt: "start retry", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: startRetryHerdr });
666
+ check("uncertain Agent startup is not retried", startRetryRun.isError && startAttempts === 1 && startRetryHerdr.calls.filter(([name]) => name === "promptAgent").length === 0);
667
+ const failedStartHerdr = fakeHerdr();
668
+ let failedStartAttempts = 0;
669
+ failedStartHerdr.startAgent = async () => { failedStartAttempts += 1; throw new Error("permanent child startup failure"); };
670
+ const failedStartRun = await executeHerdrSubagents({ concurrency: 2, tasks: [{ name: "first", prompt: "first", agent: "explorer" }, { name: "second", prompt: "second", agent: "reviewer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: failedStartHerdr });
671
+ check("failed Agent startup releases other workers", failedStartRun.isError && failedStartAttempts === 2 && failedStartRun.details.tasks.every((task) => task.status === "failed"));
672
+ const postFailureHerdr = fakeHerdr("post-fail");
673
+ const postFailure = await executeHerdrSubagents({ tasks: [{ name: "no-retry", prompt: "side effect", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: postFailureHerdr });
674
+ check("failure after prompt submission is preserved without rerun", postFailure.isError && postFailureHerdr.calls.filter(([name]) => name === "promptAgent").length === 1 && postFailure.details.tasks[0].error.includes("after prompt"));
675
+ const authoritativeAfterPromptFailure = await executeHerdrSubagents({ tasks: [{ name: "authoritative", prompt: "finish then disconnect", agent: "explorer" }] }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: fakeHerdr("report-then-post-fail") });
676
+ const authoritativeTaskDirectory = join(authoritativeAfterPromptFailure.details.recordDirectory, "tasks", readdirSync(join(authoritativeAfterPromptFailure.details.recordDirectory, "tasks"))[0]);
677
+ const authoritativeTask = JSON.parse(readFileSync(join(authoritativeTaskDirectory, "task.json"), "utf8"));
678
+ const authoritativeReport = JSON.parse(readFileSync(join(authoritativeTaskDirectory, "report.json"), "utf8"));
679
+ check("valid persisted report remains authoritative after Herdr prompt transport failure", !authoritativeAfterPromptFailure.isError && authoritativeTask.status === "completed" && authoritativeReport.status === "completed" && authoritativeAfterPromptFailure.details.tasks[0].summary === authoritativeReport.summary);
680
+ check("production batch path never passes print mode", batchStarts.every((request) => !request.args.includes("--print")));
681
+
682
+ const atomicFile = join(root, "atomic.json");
683
+ await Promise.all(Array.from({ length: 12 }, (_, index) => writeJsonAtomic(atomicFile, { index, payload: "x".repeat(1000) })));
684
+ const atomicValue = JSON.parse(readFileSync(atomicFile, "utf8"));
685
+ check("atomic snapshots remain parseable under replacement", Number.isInteger(atomicValue.index) && !readdirSync(root).some((entry) => entry.startsWith("atomic.json.tmp-")));
686
+ await persistQueuedRun(root, "queued-persisted", { runId: "queued-persisted", background: true, tasks: [{ name: "later", prompt: "later", agent: "explorer" }] });
687
+ check("queued background submission survives parent shutdown", (await loadQueuedRuns(root)).some((run) => run.runId === "queued-persisted"));
688
+ await cancelQueuedRuns(root, ["queued-persisted"]);
689
+ const cancelledQueued = (await listSubagentHistory(root)).find((run) => run.id === "queued-persisted");
690
+ check("parent shutdown archives queued work as cancelled", !(await loadQueuedRuns(root)).some((run) => run.runId === "queued-persisted") && cancelledQueued?.status === "cancelled" && cancelledQueued.tasks[0].status === "cancelled");
691
+ await persistQueuedRun(root, "live-foreign-queue", { background: true, tasks: [{ name: "foreign", prompt: "foreign" }] }, { sessionId: "foreign-owner", processId: process.pid });
692
+ await cancelQueuedRuns(root, ["live-foreign-queue"], { reason: "orphan cleanup", onlyOrphaned: true });
693
+ check("startup leaves another live parent process queue untouched", (await loadQueuedRuns(root)).some((run) => run.runId === "live-foreign-queue"));
694
+ await persistQueuedRun(root, "stale-same-process-queue", { background: true, tasks: [{ name: "stale", prompt: "stale" }] }, { sessionId: "old-session", processId: process.pid });
695
+ await cancelQueuedRuns(root, ["stale-same-process-queue"], { reason: "orphan cleanup", owner: { sessionId: "new-session", processId: process.pid }, onlyOrphaned: true });
696
+ check("startup cancels a prior session queue owned by the same process", !(await loadQueuedRuns(root)).some((run) => run.runId === "stale-same-process-queue") && (await listSubagentHistory(root)).some((run) => run.id === "stale-same-process-queue" && run.status === "cancelled"));
697
+ const malformedQueueDirectory = join(root, "malformed-queue", ".pi", "herdr-subagents", "queue");
698
+ mkdirSync(malformedQueueDirectory, { recursive: true });
699
+ writeFileSync(join(malformedQueueDirectory, "broken.json"), "{truncated");
700
+ let malformedQueueError = "";
701
+ try { await loadQueuedRuns(join(root, "malformed-queue")); } catch (error) { malformedQueueError = error.message; }
702
+ check("malformed queued records fail visibly", malformedQueueError.includes("Could not read queued Subagent run") && malformedQueueError.includes("broken.json"));
703
+ const isolatedQueueErrors = [];
704
+ const isolatedQueueRecords = await loadQueuedRuns(join(root, "malformed-queue"), (message) => isolatedQueueErrors.push(message));
705
+ check("startup queue inspection isolates malformed records", isolatedQueueRecords.length === 0 && isolatedQueueErrors.some((message) => message.includes("broken.json")));
706
+ const ownershipRoot = join(root, "ownership");
707
+ for (const [suffix, owner] of [["a", "owner-a"], ["b", "owner-b"]]) {
708
+ const directory = join(ownershipRoot, ".pi", "herdr-subagents", "runs", suffix);
709
+ const taskDirectory = join(directory, "tasks", "01-task");
710
+ mkdirSync(taskDirectory, { recursive: true });
711
+ writeFileSync(join(directory, "run.json"), JSON.stringify({ id: `run-${suffix}`, label: suffix, status: "blocked", cwd: ownershipRoot, ownerSessionId: owner, ownerProcessId: process.pid, tabId: `w1:t${suffix}`, taskIds: [`task-${suffix}`] }));
712
+ writeFileSync(join(taskDirectory, "task.json"), JSON.stringify({ id: `task-${suffix}`, runId: `run-${suffix}`, order: 1, name: suffix, status: "blocked", sessionFile: join(taskDirectory, "session.jsonl") }));
713
+ }
714
+ const ownershipHerdr = fakeHerdr();
715
+ await cancelActiveSubagentRuns(ownershipRoot, ownershipHerdr, { reason: "shutdown", owner: { sessionId: "owner-a" } });
716
+ check("graceful shutdown cancels only runs owned by that parent session", JSON.parse(readFileSync(join(ownershipRoot, ".pi", "herdr-subagents", "runs", "a", "run.json"), "utf8")).status === "cancelled" && JSON.parse(readFileSync(join(ownershipRoot, ".pi", "herdr-subagents", "runs", "b", "run.json"), "utf8")).status === "blocked");
717
+ await reconcileSubagentRuns(ownershipRoot, ownershipHerdr, { sessionId: "new-owner", processId: process.pid + 1 });
718
+ check("startup reconciliation leaves another live parent process untouched", JSON.parse(readFileSync(join(ownershipRoot, ".pi", "herdr-subagents", "runs", "b", "run.json"), "utf8")).status === "blocked");
719
+ const pendingRecoveryRoot = join(root, "pending-cleanup-recovery");
720
+ const pendingRecoveryRun = join(pendingRecoveryRoot, ".pi", "herdr-subagents", "runs", "run");
721
+ mkdirSync(pendingRecoveryRun, { recursive: true });
722
+ writeFileSync(join(pendingRecoveryRun, "run.json"), JSON.stringify({ id: "pending-recovery", label: "pending-recovery", status: "completed", cwd: pendingRecoveryRoot, ownerSessionId: "stale-owner", ownerProcessId: 99999999, tabId: "w1:t-pending", cleanupPendingTabIds: ["w1:t-pending"] }));
723
+ const pendingRecoveryHerdr = fakeHerdr();
724
+ await reconcileSubagentRuns(pendingRecoveryRoot, pendingRecoveryHerdr, { sessionId: "new-owner", processId: process.pid });
725
+ const recoveredPendingRun = JSON.parse(readFileSync(join(pendingRecoveryRun, "run.json"), "utf8"));
726
+ check("startup reconciliation finishes stale terminal deferred cleanup", pendingRecoveryHerdr.calls.some(([name, request]) => name === "closeTab" && request.tabId === "w1:t-pending") && !("cleanupPendingTabIds" in recoveredPendingRun));
727
+ const failedRecoveryRoot = join(root, "failed-cleanup-recovery");
728
+ const failedRecoveryRun = join(failedRecoveryRoot, ".pi", "herdr-subagents", "runs", "run");
729
+ mkdirSync(failedRecoveryRun, { recursive: true });
730
+ writeFileSync(join(failedRecoveryRun, "run.json"), JSON.stringify({ id: "failed-recovery", label: "failed-recovery", status: "running", cwd: failedRecoveryRoot, ownerSessionId: "stale-owner", ownerProcessId: 99999999, tabId: "w1:t-failed" }));
731
+ await reconcileSubagentRuns(failedRecoveryRoot, fakeHerdr("close-failed"), { sessionId: "new-owner", processId: process.pid });
732
+ const failedRecoveryPending = JSON.parse(readFileSync(join(failedRecoveryRun, "run.json"), "utf8"));
733
+ await reconcileSubagentRuns(failedRecoveryRoot, fakeHerdr(), { sessionId: "new-owner", processId: process.pid });
734
+ const retriedRecovery = JSON.parse(readFileSync(join(failedRecoveryRun, "run.json"), "utf8"));
735
+ check("reconciliation cleanup failure is durable and retried on startup", failedRecoveryPending.status === "failed" && failedRecoveryPending.cleanupPendingTabIds?.includes("w1:t-failed") && !("cleanupPendingTabIds" in retriedRecovery));
736
+
737
+ const recoveryRoot = join(root, "recovery");
738
+ const recoveryTask = join(recoveryRoot, ".pi", "herdr-subagents", "runs", "run", "tasks", "01-task");
739
+ mkdirSync(recoveryTask, { recursive: true });
740
+ writeFileSync(join(recoveryRoot, ".pi", "herdr-subagents", "runs", "run", "run.json"), JSON.stringify({ id: "recover", label: "recover", status: "running", startedAt: new Date().toISOString() }));
741
+ writeFileSync(join(recoveryTask, "task.json"), JSON.stringify({ id: "task", runId: "recover", order: 1, name: "lost", status: "running", paneId: "w1:missing", sessionFile: join(recoveryTask, "session.jsonl") }));
742
+ const reconciliation = await reconcileSubagentRuns(recoveryRoot, fakeHerdr("missing-pane"), { sessionId: "new-owner" });
743
+ check("reconciliation marks stale child cancelled", reconciliation.cancelledTasks === 1 && JSON.parse(readFileSync(join(recoveryTask, "task.json"), "utf8")).status === "cancelled");
744
+ const malformedRoot = join(root, "malformed-recovery");
745
+ const malformedRun = join(malformedRoot, ".pi", "herdr-subagents", "runs", "run");
746
+ const malformedTask = join(malformedRun, "tasks", "01-task");
747
+ mkdirSync(malformedTask, { recursive: true });
748
+ writeFileSync(join(malformedRun, "run.json"), JSON.stringify({ id: "malformed", label: "malformed", status: "running", cwd: malformedRoot, ownerSessionId: "dead-owner", ownerProcessId: 99999999, tabId: "w1:t-malformed" }));
749
+ writeFileSync(join(malformedTask, "task.json"), JSON.stringify({ id: "malformed-task", runId: "malformed", order: 1, name: "malformed", status: "running", paneId: "w1:p-malformed", sessionFile: join(malformedTask, "session.jsonl") }));
750
+ writeFileSync(join(malformedTask, "report.json"), "{truncated");
751
+ const malformedHerdr = fakeHerdr();
752
+ const malformedReconciliation = await reconcileSubagentRuns(malformedRoot, malformedHerdr, { sessionId: "new-owner", processId: process.pid });
753
+ check("malformed report cannot abort orphan reconciliation", malformedReconciliation.cancelledTasks === 1 && malformedReconciliation.cleanupErrors.some((message) => message.includes("Invalid Subagent report")) && JSON.parse(readFileSync(join(malformedTask, "task.json"), "utf8")).status === "cancelled" && malformedHerdr.calls.some(([name]) => name === "closeTab"));
754
+ const invalidReportRoot = join(root, "invalid-report-recovery");
755
+ const invalidReportRun = join(invalidReportRoot, ".pi", "herdr-subagents", "runs", "run");
756
+ const invalidReportTask = join(invalidReportRun, "tasks", "01-task");
757
+ mkdirSync(invalidReportTask, { recursive: true });
758
+ writeFileSync(join(invalidReportRun, "run.json"), JSON.stringify({ id: "invalid-report", label: "invalid-report", status: "running", startedAt: new Date().toISOString() }));
759
+ writeFileSync(join(invalidReportTask, "task.json"), JSON.stringify({ id: "invalid-report-task", runId: "invalid-report", order: 1, status: "running", paneId: "w1:p-invalid", sessionFile: join(invalidReportTask, "session.jsonl") }));
760
+ writeFileSync(join(invalidReportTask, "report.json"), JSON.stringify({ status: "garbage" }));
761
+ const invalidReportReconciliation = await reconcileSubagentRuns(invalidReportRoot, fakeHerdr());
762
+ const invalidReportTaskRecord = JSON.parse(readFileSync(join(invalidReportTask, "task.json"), "utf8"));
763
+ check("semantically invalid reports recover as explicit failures", invalidReportTaskRecord.status === "failed" && invalidReportTaskRecord.error.includes("Invalid structured report") && invalidReportReconciliation.cleanupErrors.some((message) => message.includes("Invalid report status")));
764
+ const repairedRecoveryReport = JSON.parse(readFileSync(join(invalidReportTask, "report.json"), "utf8"));
765
+ const unrelatedBrokenHistory = join(invalidReportRoot, ".pi", "herdr-subagents", "runs", "broken-history");
766
+ mkdirSync(unrelatedBrokenHistory, { recursive: true });
767
+ writeFileSync(join(unrelatedBrokenHistory, "run.json"), "{truncated");
768
+ const repairedRecoveryHistory = await listSubagentHistory(invalidReportRoot);
769
+ check("recovered malformed report remains usable in history", repairedRecoveryReport.status === "failed" && readdirSync(invalidReportTask).some((entry) => entry.startsWith("report.json.invalid-")) && repairedRecoveryHistory.some((run) => run.id === "invalid-report" && run.tasks[0].status === "failed"));
770
+ check("unreadable history entries do not hide healthy completed records", repairedRecoveryHistory.some((run) => run.id === "invalid-report") && repairedRecoveryHistory.some((run) => run.id === "broken-history" && run.status === "failed"));
771
+ const unreadableTaskRoot = join(root, "unreadable-task-recovery");
772
+ const unreadableTaskRun = join(unreadableTaskRoot, ".pi", "herdr-subagents", "runs", "run");
773
+ const unreadableTaskDirectory = join(unreadableTaskRun, "tasks", "01-task");
774
+ mkdirSync(unreadableTaskDirectory, { recursive: true });
775
+ writeFileSync(join(unreadableTaskRun, "run.json"), JSON.stringify({ id: "unreadable-task", label: "unreadable-task", status: "running", tabId: "w1:t-unreadable", startedAt: new Date().toISOString() }));
776
+ writeFileSync(join(unreadableTaskDirectory, "task.json"), "{truncated");
777
+ const unreadableTaskHerdr = fakeHerdr();
778
+ const unreadableTaskRecovery = await reconcileSubagentRuns(unreadableTaskRoot, unreadableTaskHerdr);
779
+ const unreadableRunRecord = JSON.parse(readFileSync(join(unreadableTaskRun, "run.json"), "utf8"));
780
+ check("unreadable task records cannot aggregate to completed", unreadableRunRecord.status === "failed" && unreadableTaskRecovery.interruptedTasks === 1 && unreadableTaskRecovery.cleanupErrors.some((message) => message.includes("Could not inspect Subagent task")) && unreadableTaskHerdr.calls.some(([name]) => name === "closeTab"));
781
+ const invalidTaskRoot = join(root, "invalid-task-recovery");
782
+ const invalidTaskRun = join(invalidTaskRoot, ".pi", "herdr-subagents", "runs", "run");
783
+ const invalidTaskDirectory = join(invalidTaskRun, "tasks", "01-task");
784
+ mkdirSync(invalidTaskDirectory, { recursive: true });
785
+ writeFileSync(join(invalidTaskRun, "run.json"), JSON.stringify({ id: "invalid-task", label: "invalid-task", status: "running", tabId: "w1:t-invalid", startedAt: new Date().toISOString() }));
786
+ writeFileSync(join(invalidTaskDirectory, "task.json"), JSON.stringify({ id: "invalid", runId: "invalid-task", status: "bogus", sessionFile: join(invalidTaskDirectory, "session.jsonl") }));
787
+ const invalidTaskHerdr = fakeHerdr();
788
+ await reconcileSubagentRuns(invalidTaskRoot, invalidTaskHerdr);
789
+ check("semantic task corruption fails recovery and closes its tab", JSON.parse(readFileSync(join(invalidTaskRun, "run.json"), "utf8")).status === "failed" && invalidTaskHerdr.calls.some(([name]) => name === "closeTab"));
790
+ const missingOrderFile = join(root, "missing-order-task.json");
791
+ writeFileSync(missingOrderFile, JSON.stringify({ id: "missing-order", runId: "invalid-task", status: "completed" }));
792
+ let missingOrderError = "";
793
+ try { await readTaskRecord(missingOrderFile); } catch (error) { missingOrderError = error.message; }
794
+ check("durable task records require a positive integer order", missingOrderError.includes("Invalid task order"));
795
+ const shellRecoveryTask = JSON.parse(readFileSync(join(recoveryTask, "task.json"), "utf8"));
796
+ Object.assign(shellRecoveryTask, { status: "running" });
797
+ writeFileSync(join(recoveryTask, "task.json"), JSON.stringify(shellRecoveryTask));
798
+ writeFileSync(join(recoveryRoot, ".pi", "herdr-subagents", "runs", "run", "run.json"), JSON.stringify({ id: "recover", label: "recover", status: "running", startedAt: new Date().toISOString() }));
799
+ const shellReconciliation = await reconcileSubagentRuns(recoveryRoot, fakeHerdr("shell-only"), { sessionId: "new-owner" });
800
+ check("pane with only a shell is not treated as live child", shellReconciliation.interruptedTasks === 1);
801
+ const nodeProcessHerdr = join(root, "node-process-herdr");
802
+ writeFileSync(nodeProcessHerdr, `#!/bin/sh\nprintf '%s\\n' '{"result":{"process_info":{"foreground_processes":[{"name":"node","argv":["/usr/bin/node","/opt/pi-coding-agent/dist/bundle/cli.js"]}]}}}'\n`);
803
+ chmodSync(nodeProcessHerdr, 0o700);
804
+ check("node-hosted Pi process is treated as live", await new CliHerdrAutomation(nodeProcessHerdr).isTaskRunning("w1:p2"));
805
+ const listenerController = new AbortController();
806
+ await new CliHerdrAutomation(nodeProcessHerdr).isTaskRunning("w1:p2", listenerController.signal);
807
+ const focusCounter = join(root, "focus-counter");
808
+ const focusPollHerdr = join(root, "focus-herdr");
809
+ writeFileSync(focusPollHerdr, `#!/bin/sh\ncount=$(cat '${focusCounter}' 2>/dev/null || echo 0)\ncount=$((count + 1))\nprintf '%s' "$count" > '${focusCounter}'\nif [ "$count" -eq 1 ]; then printf '%s\\n' '{"result":{"tab":{"focused":true}}}'; else printf '%s\\n' '{"result":{"tab":{"focused":false}}}'; fi\n`);
810
+ chmodSync(focusPollHerdr, 0o700);
811
+ await new CliHerdrAutomation(focusPollHerdr).waitForTabUnfocused("w1:t1", listenerController.signal);
812
+ check("completed Herdr commands and focus polling remove abort listeners", getEventListeners(listenerController.signal, "abort").length === 0);
813
+ const transientHerdr = join(root, "transient-herdr");
814
+ writeFileSync(transientHerdr, `#!/bin/sh\necho 'temporary transport failure' >&2\nexit 1\n`);
815
+ chmodSync(transientHerdr, 0o700);
816
+ let processQueryError = "";
817
+ try { await new CliHerdrAutomation(transientHerdr).isTaskRunning("w1:p2"); } catch (error) { processQueryError = error.message; }
818
+ check("transient process query failure is surfaced", processQueryError.includes("temporary transport failure"));
819
+ let paneLookupError = "";
820
+ try { await new CliHerdrAutomation(transientHerdr).paneExists("w1:p2"); } catch (error) { paneLookupError = error.message; }
821
+ check("transient pane lookup failure is surfaced", paneLookupError.includes("temporary transport failure"));
822
+ const missingPaneHerdr = join(root, "missing-pane-herdr");
823
+ writeFileSync(missingPaneHerdr, `#!/bin/sh\necho 'pane_not_found: unknown pane' >&2\nexit 1\n`);
824
+ chmodSync(missingPaneHerdr, 0o700);
825
+ check("confirmed missing pane lookup returns false", !(await new CliHerdrAutomation(missingPaneHerdr).paneExists("w1:missing")));
826
+
827
+ const queuedRecoveryRoot = join(root, "queued-recovery");
828
+ const queuedRunDirectory = join(queuedRecoveryRoot, ".pi", "herdr-subagents", "runs", "run");
829
+ const firstRecoveryTask = join(queuedRunDirectory, "tasks", "01-first");
830
+ const secondRecoveryTask = join(queuedRunDirectory, "tasks", "02-second");
831
+ mkdirSync(firstRecoveryTask, { recursive: true });
832
+ mkdirSync(secondRecoveryTask, { recursive: true });
833
+ writeFileSync(join(queuedRunDirectory, "run.json"), JSON.stringify({ id: "queued-recovery", label: "queued-recovery", status: "running", cwd: queuedRecoveryRoot, effectiveConcurrency: 2, startedAt: new Date().toISOString(), tabId: "w1:t2", paneIds: ["w1:p2"] }));
834
+ writeFileSync(join(firstRecoveryTask, "task.json"), JSON.stringify({ id: "first", runId: "queued-recovery", order: 1, name: "first", status: "running", paneId: "w1:p2", sessionFile: join(firstRecoveryTask, "session.jsonl") }));
835
+ writeFileSync(join(firstRecoveryTask, "report.json"), JSON.stringify({ status: "completed", summary: "first completed", documents: [] }));
836
+ writeFileSync(join(secondRecoveryTask, "task.json"), JSON.stringify({ id: "second", runId: "queued-recovery", order: 2, name: "second", status: "queued", prompt: "second", agent: "explorer", tools: ["read"], skills: [], sessionFile: join(secondRecoveryTask, "session.jsonl") }));
837
+ writeFileSync(join(secondRecoveryTask, "system-prompt.md"), "Report the result.");
838
+ const thirdRecoveryTask = join(queuedRunDirectory, "tasks", "03-third");
839
+ mkdirSync(thirdRecoveryTask, { recursive: true });
840
+ writeFileSync(join(thirdRecoveryTask, "task.json"), JSON.stringify({ id: "third", runId: "queued-recovery", order: 3, name: "third", status: "queued", prompt: "third", agent: "reviewer", tools: ["read"], skills: [], sessionFile: join(thirdRecoveryTask, "session.jsonl") }));
841
+ writeFileSync(join(thirdRecoveryTask, "system-prompt.md"), "Report the result.");
842
+ const queuedRecoveryHerdr = fakeHerdr();
843
+ await reconcileSubagentRuns(queuedRecoveryRoot, queuedRecoveryHerdr);
844
+ check("reconciliation preserves unstarted task records as queued", JSON.parse(readFileSync(join(secondRecoveryTask, "task.json"), "utf8")).status === "queued" && JSON.parse(readFileSync(join(thirdRecoveryTask, "task.json"), "utf8")).status === "queued");
845
+ await continueQueuedRun(queuedRunDirectory, queuedRecoveryHerdr);
846
+ check("startup continuation restores bounded concurrency", queuedRecoveryHerdr.maxActive === 2 && JSON.parse(readFileSync(join(secondRecoveryTask, "task.json"), "utf8")).status === "completed" && JSON.parse(readFileSync(join(thirdRecoveryTask, "task.json"), "utf8")).status === "completed" && JSON.parse(readFileSync(join(queuedRunDirectory, "run.json"), "utf8")).status === "completed");
847
+ const staleContinuationRoot = join(root, "stale-continuation");
848
+ const staleContinuationRun = join(staleContinuationRoot, ".pi", "herdr-subagents", "runs", "run");
849
+ const staleContinuationTask = join(staleContinuationRun, "tasks", "01-task");
850
+ mkdirSync(staleContinuationTask, { recursive: true });
851
+ writeFileSync(join(staleContinuationRun, "run.json"), JSON.stringify({ id: "stale-continuation", label: "stale-continuation", status: "running", cwd: staleContinuationRoot, effectiveConcurrency: 1, tabId: "w1:t-old", paneIds: ["w1:p-old"], startedAt: new Date().toISOString() }));
852
+ writeFileSync(join(staleContinuationTask, "task.json"), JSON.stringify({ id: "stale-task", runId: "stale-continuation", order: 1, name: "stale-task", status: "queued", prompt: "continue", agent: "explorer", skills: [], sessionFile: join(staleContinuationTask, "session.jsonl") }));
853
+ writeFileSync(join(staleContinuationTask, "system-prompt.md"), "Report the result.");
854
+ const staleContinuationHerdr = fakeHerdr();
855
+ staleContinuationHerdr.paneExists = async () => false;
856
+ staleContinuationHerdr.createTab = async (request) => { staleContinuationHerdr.calls.push(["createTab", request]); return { tabId: "w1:t-new", paneId: "w1:p-new" }; };
857
+ await continueQueuedRun(staleContinuationRun, staleContinuationHerdr);
858
+ const staleContinuationRecord = JSON.parse(readFileSync(join(staleContinuationRun, "run.json"), "utf8"));
859
+ check("queued recovery closes a replaced stale tab", staleContinuationRecord.status === "completed" && staleContinuationRecord.tabId === "w1:t-new" && !staleContinuationRecord.staleTabIds && staleContinuationHerdr.calls.some(([name, request]) => name === "closeTab" && request.tabId === "w1:t-old"));
860
+ const failedCleanupRoot = join(root, "stale-continuation-cleanup-failure");
861
+ const failedCleanupRun = join(failedCleanupRoot, ".pi", "herdr-subagents", "runs", "run");
862
+ const failedCleanupTask = join(failedCleanupRun, "tasks", "01-task");
863
+ mkdirSync(failedCleanupTask, { recursive: true });
864
+ writeFileSync(join(failedCleanupRun, "run.json"), JSON.stringify({ id: "stale-cleanup-failure", label: "stale-cleanup-failure", status: "running", cwd: failedCleanupRoot, effectiveConcurrency: 1, tabId: "w1:t-old", paneIds: ["w1:p-old"], startedAt: new Date().toISOString() }));
865
+ writeFileSync(join(failedCleanupTask, "task.json"), JSON.stringify({ id: "stale-cleanup-task", runId: "stale-cleanup-failure", order: 1, name: "stale-cleanup-task", status: "queued", prompt: "continue", agent: "explorer", skills: [], sessionFile: join(failedCleanupTask, "session.jsonl") }));
866
+ writeFileSync(join(failedCleanupTask, "system-prompt.md"), "Report the result.");
867
+ const failedCleanupHerdr = fakeHerdr("close-failed");
868
+ failedCleanupHerdr.paneExists = async () => false;
869
+ failedCleanupHerdr.createTab = async (request) => { failedCleanupHerdr.calls.push(["createTab", request]); return { tabId: "w1:t-new", paneId: "w1:p-new" }; };
870
+ const continuationCleanupErrors = [];
871
+ await continueQueuedRun(failedCleanupRun, failedCleanupHerdr, { cleanup: false, onCleanupError: (message) => continuationCleanupErrors.push(message) });
872
+ const failedCleanupRecord = JSON.parse(readFileSync(join(failedCleanupRun, "run.json"), "utf8"));
873
+ check("queued continuation persists and surfaces stale-tab cleanup failure", failedCleanupRecord.cleanupError?.includes("tab still busy") && failedCleanupRecord.staleTabIds?.includes("w1:t-old") && continuationCleanupErrors.some((message) => message.includes("tab still busy")));
874
+
875
+ const blockedSlotsDirectory = join(root, "blocked-slots", ".pi", "herdr-subagents", "runs", "run");
876
+ mkdirSync(join(blockedSlotsDirectory, "tasks", "01-blocked"), { recursive: true });
877
+ mkdirSync(join(blockedSlotsDirectory, "tasks", "02-queued"), { recursive: true });
878
+ mkdirSync(join(blockedSlotsDirectory, "tasks", "03-queued"), { recursive: true });
879
+ writeFileSync(join(blockedSlotsDirectory, "run.json"), JSON.stringify({ id: "blocked-slots", label: "blocked-slots", status: "blocked", cwd: join(root, "blocked-slots"), effectiveConcurrency: 2, tabId: "w1:t2", paneIds: ["w1:p2"] }));
880
+ writeFileSync(join(blockedSlotsDirectory, "tasks", "01-blocked", "task.json"), JSON.stringify({ id: "blocked", runId: "blocked-slots", order: 1, name: "blocked", status: "blocked", paneId: "w1:p2", sessionFile: join(blockedSlotsDirectory, "tasks", "01-blocked", "session.jsonl") }));
881
+ for (const order of [2, 3]) {
882
+ const directory = join(blockedSlotsDirectory, "tasks", `0${order}-queued`);
883
+ writeFileSync(join(directory, "task.json"), JSON.stringify({ id: `queued-${order}`, runId: "blocked-slots", order, name: `queued-${order}`, status: "queued", prompt: `queued-${order}`, agent: "explorer", tools: ["read"], skills: [], sessionFile: join(directory, "session.jsonl") }));
884
+ writeFileSync(join(directory, "system-prompt.md"), "Report the result.");
885
+ }
886
+ const blockedSlotsHerdr = fakeHerdr();
887
+ await continueQueuedRun(blockedSlotsDirectory, blockedSlotsHerdr, { cleanup: false });
888
+ check("blocked Agent is subtracted from continuation concurrency", blockedSlotsHerdr.maxActive === 1 && blockedSlotsHerdr.calls.filter(([name]) => name === "startAgent").length === 2);
889
+ } finally {
890
+ restoreEnv();
891
+ rmSync(root, { recursive: true, force: true });
892
+ }
893
+
894
+ console.log(`${passed} passed, ${failed} failed`);
895
+ if (failed > 0) process.exitCode = 1;