pi-daddy 0.15.0 → 0.16.0

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 (66) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +27 -12
  3. package/dist/cli.js +0 -0
  4. package/dist/executor.d.ts +38 -0
  5. package/dist/executor.d.ts.map +1 -0
  6. package/dist/executor.js +93 -0
  7. package/dist/executor.js.map +1 -0
  8. package/dist/herdr-cli.d.ts +78 -0
  9. package/dist/herdr-cli.d.ts.map +1 -0
  10. package/dist/herdr-cli.js +113 -0
  11. package/dist/herdr-cli.js.map +1 -0
  12. package/dist/herdr-name.d.ts +37 -0
  13. package/dist/herdr-name.d.ts.map +1 -0
  14. package/dist/herdr-name.js +59 -0
  15. package/dist/herdr-name.js.map +1 -0
  16. package/dist/herdr-poll.d.ts +104 -0
  17. package/dist/herdr-poll.d.ts.map +1 -0
  18. package/dist/herdr-poll.js +150 -0
  19. package/dist/herdr-poll.js.map +1 -0
  20. package/dist/herdr-stage.d.ts +40 -0
  21. package/dist/herdr-stage.d.ts.map +1 -0
  22. package/dist/herdr-stage.js +54 -0
  23. package/dist/herdr-stage.js.map +1 -0
  24. package/dist/ledger-report.d.ts +18 -0
  25. package/dist/ledger-report.d.ts.map +1 -1
  26. package/dist/ledger-report.js +10 -0
  27. package/dist/ledger-report.js.map +1 -1
  28. package/dist/ledger.d.ts +17 -0
  29. package/dist/ledger.d.ts.map +1 -1
  30. package/dist/ledger.js +1 -0
  31. package/dist/ledger.js.map +1 -1
  32. package/dist/pane-reaper.d.ts +66 -4
  33. package/dist/pane-reaper.d.ts.map +1 -1
  34. package/dist/pane-reaper.js +131 -9
  35. package/dist/pane-reaper.js.map +1 -1
  36. package/dist/progress.d.ts +96 -0
  37. package/dist/progress.d.ts.map +1 -0
  38. package/dist/progress.js +167 -0
  39. package/dist/progress.js.map +1 -0
  40. package/dist/run-child.d.ts +27 -0
  41. package/dist/run-child.d.ts.map +1 -1
  42. package/dist/run-child.js +84 -7
  43. package/dist/run-child.js.map +1 -1
  44. package/dist/run-herdr.d.ts +41 -28
  45. package/dist/run-herdr.d.ts.map +1 -1
  46. package/dist/run-herdr.js +150 -167
  47. package/dist/run-herdr.js.map +1 -1
  48. package/extensions/delegation.ts +94 -2
  49. package/extensions/grants-command.ts +26 -1
  50. package/extensions/grants.ts +85 -163
  51. package/extensions/run-delegation.ts +70 -8
  52. package/extensions/session-report.ts +231 -0
  53. package/extensions/session.ts +63 -11
  54. package/extensions/tripwire.ts +44 -0
  55. package/package.json +17 -1
  56. package/src/executor.ts +122 -0
  57. package/src/herdr-cli.ts +125 -0
  58. package/src/herdr-name.ts +61 -0
  59. package/src/herdr-poll.ts +185 -0
  60. package/src/herdr-stage.ts +55 -0
  61. package/src/ledger-report.ts +21 -0
  62. package/src/ledger.ts +18 -0
  63. package/src/pane-reaper.ts +147 -9
  64. package/src/progress.ts +206 -0
  65. package/src/run-child.ts +96 -7
  66. package/src/run-herdr.ts +170 -174
package/dist/run-herdr.js CHANGED
@@ -20,67 +20,33 @@
20
20
  * `bash` escape is unchanged, and a pane is *attachable by design*, so a human can type into a governed
21
21
  * child. Humans are not this project's threat model, but nothing here should be read as containing one.
22
22
  */
23
- import { execFile } from "node:child_process";
24
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
25
- import { tmpdir } from "node:os";
26
- import { join } from "node:path";
23
+ import { rm } from "node:fs/promises";
27
24
  import { DEFAULT_MAX_OUTPUT_BYTES, DEFAULT_TIMEOUT_MS } from "./run-child.js";
28
- import { trackPane, untrackPane } from "./pane-reaper.js";
25
+ import { MAX_OPEN_PANES, markPaneSettled, trackPane, trimOpenPanes, untrackPane } from "./pane-reaper.js";
26
+ import { defaultExec, parseReply } from "./herdr-cli.js";
27
+ import { readPane, waitForSettled } from "./herdr-poll.js";
28
+ import { stageSystemPrompt } from "./herdr-stage.js";
29
+ import { uniqueAgentName } from "./herdr-name.js";
29
30
  /**
30
- * Move a multi-line `--append-system-prompt` out of argv, because herdr cannot encode it.
31
+ * Re-exported so importers of the executor still reach the protocol at the name they always used.
31
32
  *
32
- * **Measured.** `herdr agent start` types the argv into the pane's shell, so a value containing newlines
33
- * is rejected outright: `invalid_agent_argument — agent arguments cannot be encoded safely for the target
34
- * shell`. A definition's `SKILL.md` body is always multi-line, so every `delegate({agent})` spawn would
35
- * fail on this path.
36
- *
37
- * pi accepts a **file path** there as readily as literal text (`resolvePromptInput` + `existsSync` in
38
- * `dist/core/resource-loader.js`), so the fix is to write the body to a temp file and pass its path — one
39
- * short, shell-safe argument.
40
- *
41
- * The split lives here rather than in `planSpawn` because the constraint is **herdr's**, not pi's: the
42
- * direct executor passes the same text inline with no trouble, and a plan builder that pre-emptively wrote
43
- * temp files for everybody would be paying one executor's tax on both paths.
33
+ * Deliberate rather than lazy: `test/run-herdr.test.ts` imports `HerdrExec` from here and must pass
34
+ * **unmodified** across this extraction — that is the only available proof the move changed no behaviour.
44
35
  */
45
- export function splitSystemPrompt(args) {
46
- const at = args.indexOf("--append-system-prompt");
47
- if (at === -1 || at + 1 >= args.length)
48
- return { args };
49
- return { args: [...args.slice(0, at), ...args.slice(at + 2)], systemPrompt: args[at + 1] };
50
- }
51
- /** Statuses herdr reports for a settled agent. `blocked` counts: it is waiting for a human, not working. */
52
- const TERMINAL = new Set(["idle", "done", "blocked"]);
53
- /** How often to poll `agent get` while waiting for the child to settle. */
54
- export const POLL_INTERVAL_MS = 750;
55
- /** How often to retry `agent start` while a freshly created pane is still reaching its shell prompt. */
56
- export const PANE_READY_POLL_MS = 300;
57
- const defaultExec = (args) => new Promise((settle) => {
58
- execFile("herdr", args, { maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
59
- const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
60
- settle({ code, stdout: String(stdout), stderr: String(stderr) });
61
- });
62
- });
36
+ export { parseReply } from "./herdr-cli.js";
37
+ /** Re-exported: `splitSystemPrompt` moved to `./herdr-stage.ts` under the ceiling, its tests import it here. */
38
+ export { splitSystemPrompt, stageSystemPrompt } from "./herdr-stage.js";
39
+ /** Re-exported: the name rules moved to `./herdr-name.ts` under the ceiling; tests import them here. */
40
+ export { uniqueAgentName } from "./herdr-name.js";
63
41
  /**
64
- * Parse herdr's JSON envelope. Every command replies `{id, result}` or `{id, error:{code,message}}`.
42
+ * Re-exported so `test/run-herdr.test.ts` and any importer keep reaching these where they always were.
65
43
  *
66
- * `stderr` is folded into the message because the first end-to-end run failed with an EMPTY stdout and the
67
- * real reason on stderr, producing the useless diagnostic "unparseable herdr reply: ". A wrapper that
68
- * hides the substrate's own error message costs more time than it saves.
44
+ * `POLL_INTERVAL_MS` and `newSuffix` moved to `./herdr-poll.ts` under the 400-line ceiling; the names are part
45
+ * of this module's surface and moving a file should not move an export.
69
46
  */
70
- function parseReply(reply) {
71
- try {
72
- const parsed = JSON.parse(reply.stdout);
73
- if (parsed.error)
74
- return { error: parsed.error.message ?? parsed.error.code ?? "herdr reported an error" };
75
- return { result: parsed.result };
76
- }
77
- catch {
78
- // A non-JSON reply is a herdr-version or PATH problem, not a governance decision. Surfaced as a spawn
79
- // error so the caller reports "could not start" rather than "the child produced nothing".
80
- const detail = [reply.stdout.trim(), reply.stderr.trim()].filter((t) => t.length > 0).join(" | ");
81
- return { error: `unparseable herdr reply: ${detail.slice(0, 300) || "(no output)"}` };
82
- }
83
- }
47
+ export { DEFAULT_SNAPSHOT_LINES, POLL_INTERVAL_MS, readPane, tailLines, waitForSettled } from "./herdr-poll.js";
48
+ /** How often to retry `agent start` while a freshly created pane is still reaching its shell prompt. */
49
+ export const PANE_READY_POLL_MS = 300;
84
50
  /**
85
51
  * Run one governed child in a pane and return its output.
86
52
  *
@@ -109,30 +75,31 @@ export async function runHerdrPane(request) {
109
75
  "and never be detected. Build the plan with `print: false` for this executor.",
110
76
  };
111
77
  }
112
- // A multi-line system prompt becomes a file; see `splitSystemPrompt`. Removed in `cleanup`, so a
113
- // definition's instructions do not accumulate in /tmp across a fan-out.
114
- const split = splitSystemPrompt(request.args);
115
- let promptDir;
116
- let effectiveArgs = split.args;
117
- if (split.systemPrompt !== undefined) {
118
- try {
119
- promptDir = await mkdtemp(join(tmpdir(), "grants-herdr-"));
120
- const file = join(promptDir, "system-prompt.md");
121
- await writeFile(file, split.systemPrompt, "utf8");
122
- effectiveArgs = [...split.args, "--append-system-prompt", file];
123
- }
124
- catch (error) {
125
- return { ...empty, spawnError: `could not stage the system prompt for herdr: ${String(error)}` };
126
- }
127
- }
78
+ // A multi-line system prompt becomes a file; see `stageSystemPrompt`. Removed on every path that does not
79
+ // end up tracking a pane, because only a tracked pane's dir is reachable by a sweep.
80
+ const staged = await stageSystemPrompt(request.args);
81
+ if (staged.error)
82
+ return { ...empty, spawnError: staged.error };
83
+ const promptDir = staged.promptDir;
84
+ const effectiveArgs = staged.args;
128
85
  const create = ["tab", "create", "--label", request.name, "--cwd", request.cwd];
129
86
  if (request.workspace)
130
87
  create.push("--workspace", request.workspace);
131
88
  for (const [key, value] of Object.entries(request.env))
132
89
  create.push("--env", `${key}=${value}`);
90
+ // The name herdr will actually know this agent by. Every later command in this function uses `agentName`,
91
+ // never `request.name`, which is only a base.
92
+ const agentName = uniqueAgentName(request.name);
133
93
  const created = parseReply(await exec(create));
134
- if (created.error)
94
+ if (created.error) {
95
+ // **The staged prompt is removed here too.** This path returned without touching it, and nothing tracked it,
96
+ // so neither sweep could ever reach it — a permanent `/tmp/grants-herdr-*` per failed `tab create`. Measured
97
+ // by a reviewer, who also found leftovers from earlier runs already sitting in `/tmp`. It is the most likely
98
+ // failure on a machine where herdr is not running, i.e. the common case.
99
+ if (promptDir)
100
+ await rm(promptDir, { recursive: true, force: true }).catch(() => undefined);
135
101
  return { ...empty, spawnError: `herdr tab create failed: ${created.error}` };
102
+ }
136
103
  const rootPane = (created.result?.root_pane ?? {});
137
104
  const paneId = rootPane.pane_id;
138
105
  const tabId = rootPane.tab_id;
@@ -140,8 +107,34 @@ export async function runHerdrPane(request) {
140
107
  // so registering later leaves a window — one herdr round-trip wide — in which a killed process orphans a
141
108
  // tab nothing would reap. The normal path had no such window and the error path did, which is backwards:
142
109
  // the error path is the one more likely to be taken while something is already going wrong.
143
- if (tabId && !request.keepPane)
144
- trackPane({ tab: tabId, name: request.name, promptDir });
110
+ if (tabId && request.keepPane && promptDir) {
111
+ // `keepPane` keeps the PANE, and the staged prompt with it — a human inspecting the pane may want to see
112
+ // what the child was told. Registered `settled` so the trim never touches it, but registered nonetheless so
113
+ // the `exit` sweep can remove the temp dir rather than leaving one per kept pane forever. Measured: this
114
+ // path leaked a `/tmp/grants-herdr-*` that neither sweep could reach.
115
+ trackPane({ tab: tabId, name: agentName, promptDir, settled: true, keepTab: true });
116
+ }
117
+ if (tabId && !request.keepPane) {
118
+ trackPane({ tab: tabId, name: agentName, promptDir });
119
+ // ADR-0032: panes now live until `agent_settled`, so they accumulate across one agent run — and a plain
120
+ // blocking `delegate` spends nothing from the fan-out budget, so the count is otherwise unbounded. Trimmed
121
+ // here rather than at settle time so the bound holds DURING a long run, not only after it.
122
+ //
123
+ // Whatever is closed is reported through `onOutput` rather than dropped: an operator whose pane vanished
124
+ // while they were reading it deserves to know why (R-48's rule, applied to a display).
125
+ const dropped = await trimOpenPanes(exec);
126
+ if (dropped.length > 0 && request.onSnapshot) {
127
+ try {
128
+ request.onSnapshot([
129
+ `[grants] closed ${dropped.length} older pane(s) to stay within the ${MAX_OPEN_PANES}-pane limit: ` +
130
+ dropped.join(", "),
131
+ ]);
132
+ }
133
+ catch {
134
+ /* display only */
135
+ }
136
+ }
137
+ }
145
138
  if (!paneId) {
146
139
  // The tab may exist even though the reply carried no pane id, and this return used to be BEFORE
147
140
  // `cleanup` was defined — so the one path where herdr half-succeeded was the one that leaked a tab.
@@ -152,65 +145,117 @@ export async function runHerdrPane(request) {
152
145
  if (reply !== undefined && !parseReply(reply).error)
153
146
  untrackPane(tabId);
154
147
  }
155
- // `keepPane` keeps the staged prompt for the same reason `cleanup` does — a human inspecting the pane
156
- // may want to see what the child was told. This branch used to remove it unconditionally, which threw
157
- // that away on the one path where there is no agent in the pane to ask instead.
148
+ // There is no agent in this pane to ask, so the staged prompt has no reader — remove it. Kept only under
149
+ // `keepPane`, where the operator asked to inspect whatever exists.
158
150
  if (promptDir && !request.keepPane)
159
151
  await rm(promptDir, { recursive: true, force: true }).catch(() => undefined);
160
152
  return { ...empty, spawnError: "herdr tab create returned no pane id" };
161
153
  }
162
- /** Close what we opened, whatever happened. A leaked pane per child is how fan-out fills a workspace. */
163
- const cleanup = async () => {
164
- await exec(["agent", "stop", request.name]).catch(() => undefined);
165
- let closed = false;
166
- if (!request.keepPane && tabId) {
167
- // **The reply must be PARSED, not merely awaited.** `defaultExec` resolves with `{code: 1}` on
168
- // failure and never rejects, so the `.catch` here was dead code and a herdr that REFUSED to close the
169
- // pane looked identical to one that closed it. The pane was then untracked, so the exit reaper — the
170
- // one thing built for exactly this failure — would not retry it. The single case the reaper exists
171
- // for was the case that disabled it.
172
- const reply = await exec(["tab", "close", tabId]).catch(() => undefined);
173
- closed = reply !== undefined && !parseReply(reply).error;
174
- }
175
- // Kept when the pane is kept: a human inspecting the pane may want to see what the child was told.
176
- if (promptDir && !request.keepPane)
177
- await rm(promptDir, { recursive: true, force: true }).catch(() => undefined);
178
- // Untrack only what is genuinely gone. A pane we failed to close stays registered so `exit` tries once
179
- // more; `openPaneCount()` is what a test asserts to prove the registry does not grow per delegation.
180
- if (tabId && (closed || request.keepPane))
154
+ /**
155
+ * End this call. **A child that did not settle has its tab closed, because that is the only way to stop it.**
156
+ *
157
+ * ADR-0032 keeps a pane alive past its tool call so a human can read it. The original version of that also
158
+ * called `herdr agent stop` and claimed *"the agent is still stopped here… what survives is a terminal, not a
159
+ * running descendant."* **`herdr agent stop` does not exist.** Measured against herdr 0.7.5: the `agent`
160
+ * subcommands are `list get read send-keys prompt rename focus wait attach start explain`, and `agent stop`
161
+ * prints the usage banner and exits 0 — which `defaultExec` reports as a success, so nothing ever noticed.
162
+ * `docs/probes/g16-herdr/README.md` asserted it worked, in a *How to rerun* block that was never run.
163
+ *
164
+ * So there is exactly one kill available: closing the tab. That forces the distinction below.
165
+ *
166
+ * - **Settled cleanly** (`idle`/`done`/`blocked` with an advanced counter): the child has answered and is
167
+ * sitting idle. Its pane stays, which is the whole feature, and the `agent_settled` sweep reaps it.
168
+ * - **Anything else** — a timeout, an abort, a failed start, a failed prompt, an unreadable pane: the child
169
+ * may still be **working, with its grant**, after its tool call has already returned a result. Leaving
170
+ * that running is a governance failure, not an untidy workspace, so the tab is closed here and now.
171
+ *
172
+ * `keepPane` is the operator overriding the whole question, and it is honoured either way: nothing is tracked
173
+ * and nothing is closed, which is what "keep this pane for me to inspect" has to mean.
174
+ */
175
+ const cleanup = async (settledCleanly) => {
176
+ if (request.keepPane)
177
+ return;
178
+ if (settledCleanly)
179
+ return;
180
+ if (!tabId)
181
+ return;
182
+ const reply = await exec(["tab", "close", tabId]).catch(() => undefined);
183
+ // Untracked only if provably closed — R-62's rule. A close herdr refused stays the reaper's problem rather
184
+ // than being dropped on the assumption that it worked.
185
+ if (reply !== undefined && !parseReply(reply).error) {
181
186
  untrackPane(tabId);
187
+ if (promptDir)
188
+ await rm(promptDir, { recursive: true, force: true }).catch(() => undefined);
189
+ }
182
190
  };
191
+ // ADR-0032: the pane exists and has a name, so tell the caller NOW. A pane id that arrives with the result is
192
+ // useless — the point is switching to a child while it is still working. Guarded because a renderer must not
193
+ // be able to strand a pane we have already created.
194
+ if (request.onPane) {
195
+ try {
196
+ request.onPane(paneId, agentName);
197
+ }
198
+ catch {
199
+ /* display only */
200
+ }
201
+ }
202
+ // Set only on the one path where the child answered and stopped working. Everything else — including an
203
+ // early `return` from a failed start or prompt — leaves it false, so `cleanup` closes the tab.
204
+ let settledCleanly = false;
183
205
  try {
184
- const started = await startAgent(exec, request.name, paneId, effectiveArgs, deadline);
206
+ const started = await startAgent(exec, agentName, paneId, effectiveArgs, deadline);
185
207
  if (started.error)
186
208
  return { ...empty, spawnError: `herdr agent start failed: ${started.error}` };
187
209
  // The state counter BEFORE prompting is what makes the wait correct — see the R-33 note below.
188
210
  const before = seqOf(started.result);
189
- const prompted = parseReply(await exec(["agent", "prompt", request.name, request.prompt]));
211
+ const prompted = parseReply(await exec(["agent", "prompt", agentName, request.prompt]));
190
212
  if (prompted.error)
191
213
  return { ...empty, spawnError: `herdr agent prompt failed: ${prompted.error}` };
192
- const settled = await waitForSettled(exec, request, before, deadline);
214
+ const settled = await waitForSettled(exec, { ...request, name: agentName }, before, deadline, maxOutputBytes);
193
215
  if (settled.aborted || settled.timedOut) {
194
216
  // Still read: a timed-out child usually produced something, and a partial answer labelled partial is
195
217
  // more useful than none. R-03's rule — a missing result must never look like an empty one.
196
- const partial = await readPane(exec, request.name, maxOutputBytes);
197
- return { ...empty, ...settled, text: partial.text, truncated: partial.truncated };
218
+ const partial = await readPane(exec, agentName, maxOutputBytes);
219
+ return { ...empty, ...settled, text: partial.readFailed ? "" : partial.text, truncated: partial.truncated };
198
220
  }
199
221
  if (settled.spawnError)
200
222
  return { ...empty, spawnError: settled.spawnError };
201
- const out = await readPane(exec, request.name, maxOutputBytes);
223
+ const out = await readPane(exec, agentName, maxOutputBytes);
224
+ // **A failed read is a failed spawn, not an empty answer.** This branch used to return `readPane`'s own
225
+ // diagnostic as `text` with `code: 0`, so the orchestrator received
226
+ // `[grants] could not read the agent pane: …` **as the sub-agent's report** — R-03 exactly, and measured.
227
+ // The child may well have done its work; what we cannot do is claim to know what it said.
228
+ if (out.readFailed) {
229
+ return {
230
+ ...empty,
231
+ spawnError: `the child settled but its pane could not be read (${out.readFailed}), so its answer is unknown. ` +
232
+ `The work may have been done — check pane ${paneId} if it is still open.`,
233
+ };
234
+ }
235
+ settledCleanly = true;
236
+ // The trim may now reclaim this pane if the cap is exceeded. Until this point it must not: closing the tab
237
+ // kills the child, and the child was still working.
238
+ if (tabId)
239
+ markPaneSettled(tabId);
202
240
  return {
203
241
  code: settled.status === "blocked" ? 1 : 0,
204
- text: settled.status === "blocked"
205
- ? `${out.text}\n\n[grants] this agent is BLOCKED waiting for a human in pane ${paneId}.`
206
- : out.text,
242
+ // `truncated` is returned in the result too, but it was never SAID on this path, so a model received a
243
+ // tail with no indication that a head existed. The direct executor has always flagged it; a delegation
244
+ // that silently drops the first megabyte of an answer is the same defect wearing the other executor.
245
+ text: [
246
+ out.truncated ? `[grants] this pane exceeded the output cap; only its most recent output is below.\n` : "",
247
+ out.text,
248
+ settled.status === "blocked"
249
+ ? `\n\n[grants] this agent is BLOCKED waiting for a human in pane ${paneId}.`
250
+ : "",
251
+ ].join(""),
207
252
  truncated: out.truncated,
208
253
  timedOut: false,
209
254
  aborted: false,
210
255
  };
211
256
  }
212
257
  finally {
213
- await cleanup();
258
+ await cleanup(settledCleanly);
214
259
  }
215
260
  }
216
261
  /**
@@ -241,66 +286,4 @@ function seqOf(result) {
241
286
  const agent = (result?.agent ?? {});
242
287
  return typeof agent.state_change_seq === "number" ? agent.state_change_seq : -1;
243
288
  }
244
- /**
245
- * Wait for the child to settle, without accepting the state it was already in.
246
- *
247
- * **R-33, measured.** `herdr agent wait --until idle` called right after `agent prompt` returned
248
- * *immediately*, matching the agent's **pre-existing** idle state with `state_change_seq` unchanged — a
249
- * reply indistinguishable from a completed run. For fan-out that is not an inconvenience but a
250
- * correctness bug: an orchestrator would "collect" N children that never ran and merge N empty results
251
- * into a confident summary (R-03 with a new cause).
252
- *
253
- * So this polls `agent get` and requires **both** that the status is terminal **and** that
254
- * `state_change_seq` has advanced past the value observed before prompting. `agent wait` is deliberately
255
- * not used at all: its contract cannot express "settled *after* this point".
256
- */
257
- async function waitForSettled(exec, request, before, deadline) {
258
- for (;;) {
259
- if (request.signal?.aborted)
260
- return { aborted: true };
261
- if (Date.now() >= deadline)
262
- return { timedOut: true };
263
- const reply = parseReply(await exec(["agent", "get", request.name]));
264
- if (reply.error)
265
- return { spawnError: `herdr agent get failed: ${reply.error}` };
266
- const agent = (reply.result?.agent ?? reply.result ?? {});
267
- const status = agent.agent_status;
268
- const seq = typeof agent.state_change_seq === "number" ? agent.state_change_seq : -1;
269
- if (status && TERMINAL.has(status) && seq > before)
270
- return { status };
271
- await new Promise((r) => setTimeout(r, Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now()))));
272
- }
273
- }
274
- /**
275
- * Read the pane's contents.
276
- *
277
- * `agent read` is the ONE command that does not return herdr's JSON envelope — it writes the terminal's
278
- * text straight to stdout. Running it through `parseReply` turned every successful read into
279
- * "unparseable herdr reply", i.e. reported the child's actual answer as a failure to read it. Found by the
280
- * end-to-end run; the unit fake had been written to the envelope shape and so agreed with the bug.
281
- *
282
- * A JSON envelope is still accepted first, because an `error` reply here IS JSON and must not be mistaken
283
- * for terminal output.
284
- */
285
- async function readPane(exec, name, maxOutputBytes) {
286
- const reply = await exec(["agent", "read", name]);
287
- let text;
288
- try {
289
- const parsed = JSON.parse(reply.stdout);
290
- if (parsed.error) {
291
- return { text: `[grants] could not read the agent pane: ${parsed.error.message ?? "unknown error"}`, truncated: false };
292
- }
293
- const raw = parsed.result?.output ?? parsed.result?.text ?? parsed.result?.content ?? "";
294
- text = typeof raw === "string" ? raw : JSON.stringify(raw);
295
- }
296
- catch {
297
- text = reply.stdout;
298
- }
299
- if (Buffer.byteLength(text) <= maxOutputBytes)
300
- return { text, truncated: false };
301
- // Keep the TAIL, not the head: a terminal's useful content is its most recent output, and the head is
302
- // the startup banner. `runChild` keeps the head because it streams and must stop a runaway producer;
303
- // here the output is already complete, so the choice is free and the tail is the answer.
304
- return { text: text.slice(-maxOutputBytes), truncated: true };
305
- }
306
289
  //# sourceMappingURL=run-herdr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-herdr.js","sourceRoot":"","sources":["../src/run-herdr.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run-herdr.js","sourceRoot":"","sources":["../src/run-herdr.ts"],"names":[],"mappings":"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"}
@@ -13,9 +13,90 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
  import { Type } from "typebox";
14
14
  import { maySpawnDefinition } from "../src/delegate.ts";
15
15
  import { MAX_CHILDREN_PER_CALL, childSpawnId, splitBudget } from "../src/fanout.ts";
16
+ import {
17
+ PAINT_INTERVAL_MS,
18
+ appendTail,
19
+ emptyTail,
20
+ renderProgress,
21
+ replaceTail,
22
+ throttle,
23
+ type ChildProgress,
24
+ } from "../src/progress.ts";
16
25
  import { runOneDelegation } from "./run-delegation.ts";
17
26
  import { type GrantsSession } from "./session.ts";
18
27
 
28
+ /**
29
+ * Wire a set of children to pi's partial-result channel — ADR-0032.
30
+ *
31
+ * **One block for the whole call**, not one per child: `onUpdate` replaces the tool's rendered result, so N
32
+ * independent painters would each overwrite the others and the operator would see one child flickering.
33
+ * `delegate` is simply the one-child case of the same thing, which is why both tools come through here.
34
+ *
35
+ * Painting is throttled, and the trailing frame matters more than the throttling: without it the final paint —
36
+ * the one showing every child settled — is the one most likely to be dropped, so the block would freeze
37
+ * mid-run. `flush()` at the end is what guarantees the last state is the one left on screen.
38
+ *
39
+ * **Nothing here is the result.** The tool's content still comes from each child's returned text.
40
+ */
41
+ function progressReporter(
42
+ session: GrantsSession,
43
+ labels: string[],
44
+ onUpdate: ((partial: { content: Array<{ type: "text"; text: string }> }) => void) | undefined,
45
+ ) {
46
+ const started = Date.now();
47
+ const children: ChildProgress[] = labels.map((label) => ({
48
+ label,
49
+ state: "starting",
50
+ startedAt: started,
51
+ tail: emptyTail,
52
+ }));
53
+
54
+ const paint = throttle(() => {
55
+ onUpdate?.({
56
+ content: [{ type: "text", text: renderProgress(children, session.executor.kind, Date.now()) }],
57
+ });
58
+ }, PAINT_INTERVAL_MS);
59
+
60
+ return {
61
+ /** One sink per child index, shaped for `runOneDelegation`'s `onProgress`. */
62
+ sink: (index: number) => (update: {
63
+ chunk?: string;
64
+ snapshot?: string[];
65
+ paneId?: string;
66
+ agentName?: string;
67
+ state?: ChildProgress["state"];
68
+ }) => {
69
+ const child = children[index];
70
+ if (!child) return;
71
+ if (update.paneId) child.paneId = update.paneId;
72
+ // **Reported, not derived.** It used to be rebuilt here from the label and the child id, which was true
73
+ // until `runHerdrPane` began uniquifying the name to stop herdr's `agent_name_taken` — after which the
74
+ // block named an agent that did not exist, and an operator following it would find nothing. The name is
75
+ // known only where it is minted, so it travels from there.
76
+ if (update.agentName) child.agentName = update.agentName;
77
+ // Two shapes, deliberately distinct, because the two executors are: a subprocess APPENDS bytes, a pane
78
+ // reports a SNAPSHOT that replaces. Collapsing them into one "output" field is what caused the herdr
79
+ // path's amplification, so the difference is spelled out in the type rather than remembered.
80
+ if (update.chunk) child.tail = appendTail(child.tail, update.chunk);
81
+ if (update.snapshot) child.tail = replaceTail(update.snapshot);
82
+ if (update.state) child.state = update.state;
83
+ else if (child.state === "starting") child.state = "running";
84
+ paint.call();
85
+ },
86
+ /** Record how each child ended and leave the final frame on screen. */
87
+ settle: (outcomes: Array<{ ok: boolean }>) => {
88
+ const at = Date.now();
89
+ outcomes.forEach((outcome, index) => {
90
+ const child = children[index];
91
+ if (!child) return;
92
+ child.state = outcome.ok ? "completed" : "failed";
93
+ child.settledAt = at;
94
+ });
95
+ paint.flush();
96
+ },
97
+ };
98
+ }
99
+
19
100
  export interface DelegationRegistration {
20
101
  /**
21
102
  * Re-derive "which definitions may this session spawn?" into the registered tool schemas.
@@ -113,7 +194,10 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
113
194
  "written by the operator. Use 'tools' only when no definition fits. Grant 'delegate' if the " +
114
195
  "sub-agent must itself delegate further; withhold it to make the sub-agent a leaf.",
115
196
  parameters: delegateParams,
116
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
197
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
198
+ // ADR-0032: one child, same block. `_onUpdate` was discarded here, so a delegation showed the bare word
199
+ // `delegate` for up to DEFAULT_TIMEOUT_MS — ten minutes of nothing.
200
+ const progress = progressReporter(session, [params.agent ?? "delegate"], onUpdate as never);
117
201
  const outcome = await runOneDelegation(
118
202
  session,
119
203
  { task: params.task, agent: params.agent, tools: params.tools, model: params.model },
@@ -124,7 +208,9 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
124
208
  session.fanoutBudget,
125
209
  ctx,
126
210
  signal,
211
+ progress.sink(0),
127
212
  );
213
+ progress.settle([outcome]);
128
214
 
129
215
  if (!outcome.ok) {
130
216
  // THROW, do not return. `AgentToolResult` has no `isError` field: pi sets it only when `execute`
@@ -168,7 +254,7 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
168
254
  "when independent tasks can proceed in parallel — several reviewers over one diff, say — and read " +
169
255
  "every child's outcome, because one can be refused while the others succeed.",
170
256
  parameters: delegateAllParams,
171
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
257
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
172
258
  const children = params.children ?? [];
173
259
  const split = splitBudget(session.fanoutBudget, children.length);
174
260
  if (!split.ok) {
@@ -177,6 +263,10 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
177
263
  throw new Error(`fan-out refused: ${split.reason}`);
178
264
  }
179
265
 
266
+ // ADR-0032: ONE status block covering every child. `onUpdate` replaces the tool's rendered result, so a
267
+ // painter per child would have each overwriting the others.
268
+ const progress = progressReporter(session, children.map((c) => c.agent ?? "delegate"), onUpdate as never);
269
+
180
270
  // Concurrent by construction. Each child gets its own budget share and its own ledger id, so the
181
271
  // records form a tree and two siblings can never be confused for one another.
182
272
  const outcomes = await Promise.all(
@@ -188,9 +278,11 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
188
278
  split.perChild,
189
279
  ctx,
190
280
  signal,
281
+ progress.sink(index),
191
282
  ),
192
283
  ),
193
284
  );
285
+ progress.settle(outcomes);
194
286
 
195
287
  const failed = outcomes.filter((o) => !o.ok);
196
288
  // Every child is reported, including the ones that failed. R-03's rule: a missing result must never