pi-daddy 0.14.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 (80) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +37 -14
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +10 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/executor.d.ts +38 -0
  7. package/dist/executor.d.ts.map +1 -0
  8. package/dist/executor.js +93 -0
  9. package/dist/executor.js.map +1 -0
  10. package/dist/grant-store.d.ts +68 -0
  11. package/dist/grant-store.d.ts.map +1 -0
  12. package/dist/grant-store.js +142 -0
  13. package/dist/grant-store.js.map +1 -0
  14. package/dist/herdr-cli.d.ts +78 -0
  15. package/dist/herdr-cli.d.ts.map +1 -0
  16. package/dist/herdr-cli.js +113 -0
  17. package/dist/herdr-cli.js.map +1 -0
  18. package/dist/herdr-name.d.ts +37 -0
  19. package/dist/herdr-name.d.ts.map +1 -0
  20. package/dist/herdr-name.js +59 -0
  21. package/dist/herdr-name.js.map +1 -0
  22. package/dist/herdr-poll.d.ts +104 -0
  23. package/dist/herdr-poll.d.ts.map +1 -0
  24. package/dist/herdr-poll.js +150 -0
  25. package/dist/herdr-poll.js.map +1 -0
  26. package/dist/herdr-stage.d.ts +40 -0
  27. package/dist/herdr-stage.d.ts.map +1 -0
  28. package/dist/herdr-stage.js +54 -0
  29. package/dist/herdr-stage.js.map +1 -0
  30. package/dist/ledger-report.d.ts +18 -0
  31. package/dist/ledger-report.d.ts.map +1 -1
  32. package/dist/ledger-report.js +10 -0
  33. package/dist/ledger-report.js.map +1 -1
  34. package/dist/ledger.d.ts +17 -0
  35. package/dist/ledger.d.ts.map +1 -1
  36. package/dist/ledger.js +1 -0
  37. package/dist/ledger.js.map +1 -1
  38. package/dist/pane-reaper.d.ts +66 -4
  39. package/dist/pane-reaper.d.ts.map +1 -1
  40. package/dist/pane-reaper.js +131 -9
  41. package/dist/pane-reaper.js.map +1 -1
  42. package/dist/progress.d.ts +96 -0
  43. package/dist/progress.d.ts.map +1 -0
  44. package/dist/progress.js +167 -0
  45. package/dist/progress.js.map +1 -0
  46. package/dist/run-child.d.ts +27 -0
  47. package/dist/run-child.d.ts.map +1 -1
  48. package/dist/run-child.js +84 -7
  49. package/dist/run-child.js.map +1 -1
  50. package/dist/run-herdr.d.ts +41 -28
  51. package/dist/run-herdr.d.ts.map +1 -1
  52. package/dist/run-herdr.js +150 -167
  53. package/dist/run-herdr.js.map +1 -1
  54. package/dist/skill-packages.d.ts +16 -0
  55. package/dist/skill-packages.d.ts.map +1 -1
  56. package/dist/skill-packages.js +39 -10
  57. package/dist/skill-packages.js.map +1 -1
  58. package/extensions/delegation.ts +94 -2
  59. package/extensions/grants-command.ts +57 -1
  60. package/extensions/grants.ts +109 -165
  61. package/extensions/init-command.ts +132 -0
  62. package/extensions/run-delegation.ts +70 -8
  63. package/extensions/session-report.ts +231 -0
  64. package/extensions/session.ts +138 -17
  65. package/extensions/tripwire.ts +44 -0
  66. package/package.json +17 -1
  67. package/src/cli.ts +10 -4
  68. package/src/executor.ts +122 -0
  69. package/src/grant-store.ts +151 -0
  70. package/src/herdr-cli.ts +125 -0
  71. package/src/herdr-name.ts +61 -0
  72. package/src/herdr-poll.ts +185 -0
  73. package/src/herdr-stage.ts +55 -0
  74. package/src/ledger-report.ts +21 -0
  75. package/src/ledger.ts +18 -0
  76. package/src/pane-reaper.ts +147 -9
  77. package/src/progress.ts +206 -0
  78. package/src/run-child.ts +96 -7
  79. package/src/run-herdr.ts +170 -174
  80. package/src/skill-packages.ts +41 -10
@@ -25,6 +25,7 @@
25
25
  * `source`s; the only strings that may reach it are ones that survived a whitelist here.
26
26
  */
27
27
  import { readdir, readFile, realpath } from "node:fs/promises";
28
+ import { homedir } from "node:os";
28
29
  import { join, resolve, sep } from "node:path";
29
30
  import { ceilingForDefinition, parseSkillDefinition } from "./definitions.js";
30
31
  import { WILDCARD } from "./pi-tools.js";
@@ -179,16 +180,51 @@ export async function readSkillPackage(packageDir) {
179
180
  * `node_modules`: a transitive skill package is not something an operator asked to install definitions
180
181
  * from, and scaffolding one into their project would be a surprise wearing a helpful face.
181
182
  */
183
+ /**
184
+ * Every place a pi package can be installed, most specific first (R-75).
185
+ *
186
+ * **Two roots, because pi has two install paths and only one of them is `npm install`.** `pi install
187
+ * npm:principal-pi-skills` — the documented, pi-native way, and the only one that registers the package so
188
+ * pi will auto-load its extension — puts it in `$PI_CODING_AGENT_DIR/npm/node_modules`, and leaves the
189
+ * project without a `node_modules` at all. Searching only `<cwd>/node_modules` therefore found **nothing**
190
+ * for an operator who followed pi's own instructions, and said "install principal-pi-skills" to somebody
191
+ * who just had.
192
+ *
193
+ * Measured, in a fresh `PI_CODING_AGENT_DIR` with an empty project: `pi install` populates the agent root
194
+ * and creates no project root.
195
+ *
196
+ * Project first when both exist, because a package pinned in the repository is the one the team agreed on.
197
+ */
198
+ export function skillPackageRoots(cwd) {
199
+ const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
200
+ return [join(cwd, "node_modules"), join(agentDir, "npm", "node_modules")];
201
+ }
182
202
  export async function discoverSkillPackages(cwd) {
183
- const root = join(cwd, "node_modules");
203
+ const dirs = [];
204
+ const seenNames = new Set();
205
+ for (const root of skillPackageRoots(cwd))
206
+ await collectFrom(root, dirs);
207
+ const packages = [];
208
+ for (const dir of dirs) {
209
+ const found = await readSkillPackage(dir);
210
+ // First root wins on a name collision: the project's pinned copy outranks the machine-wide one, and
211
+ // silently preferring the other would make a committed lockfile stop meaning anything.
212
+ if (found && !seenNames.has(found.name)) {
213
+ seenNames.add(found.name);
214
+ packages.push(found);
215
+ }
216
+ }
217
+ return packages.sort((a, b) => a.name.localeCompare(b.name));
218
+ }
219
+ /** Append every package directory under one `node_modules`, scoped packages included. */
220
+ async function collectFrom(root, dirs) {
184
221
  let entries;
185
222
  try {
186
223
  entries = await readdir(root);
187
224
  }
188
225
  catch {
189
- return []; // no node_modules is a normal state, not a failure
226
+ return; // a root that does not exist is a normal state, not a failure
190
227
  }
191
- const dirs = [];
192
228
  for (const entry of entries.sort()) {
193
229
  if (entry.startsWith("."))
194
230
  continue; // .bin, .package-lock.json
@@ -205,12 +241,5 @@ export async function discoverSkillPackages(cwd) {
205
241
  dirs.push(join(root, entry));
206
242
  }
207
243
  }
208
- const packages = [];
209
- for (const dir of dirs) {
210
- const found = await readSkillPackage(dir);
211
- if (found)
212
- packages.push(found);
213
- }
214
- return packages.sort((a, b) => a.name.localeCompare(b.name));
215
244
  }
216
245
  //# sourceMappingURL=skill-packages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"skill-packages.js","sourceRoot":"","sources":["../src/skill-packages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAwB,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,cAAc,EAAmB,MAAM,cAAc,CAAC;AAsC/D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAc;IAC7C,MAAM,OAAO,GAAG,4BAA4B,CAAC;IAC7C,OAAO,CACL,IAAI,MAAM,CAAC,uBAAuB,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,MAAM,CAAC,UAAU,OAAO,MAAM,OAAO,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAClE,CAAC;AACJ,CAAC;AAED,6GAA6G;AAC7G,SAAS,WAAW,CAAC,YAA0B;IAC7C,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,cAAc,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,KAAsB;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;IACnC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;IAEvF,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAE1F,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAE7F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8GAA8G;AAC9G,KAAK,UAAU,SAAS,CAAC,UAAkB,EAAE,KAAa;IACxD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC1C,yGAAyG;IACzG,oGAAoG;IACpG,yGAAyG;IACzG,0GAA0G;IAC1G,yGAAyG;IACzG,mDAAmD;IACnD,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;IAC1E,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAC3F,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,cAAc,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAE5D,oGAAoG;QACpG,yGAAyG;QACzG,8EAA8E;QAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QAEhE,MAAM,UAAU,GAAG,oBAAoB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpD,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2GAA2G;AAC3G,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,UAAkB;IACvD,IAAI,QAAwE,CAAC;IAC7E,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnE,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QACxC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACjD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YAClC,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;gBAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,WAAW;QACjE,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,cAAc;QAC3C,UAAU;QACV,OAAO;QACP,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAW;IACrD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IACvC,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,mDAAmD;IAChE,CAAC;IAED,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,2BAA2B;QAChE,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,KAAK,MAAM,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;oBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;YACvG,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"skill-packages.js","sourceRoot":"","sources":["../src/skill-packages.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
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import type { Capability } from "../src/resolve.ts";
16
+ import type { ExecutorChoice } from "../src/executor.ts";
16
17
  import type { Catalog } from "../src/catalog.ts";
17
18
  import type { SkillDefinition } from "../src/definitions.ts";
18
19
  import type { GatedPlan } from "./run-delegation.ts";
@@ -23,6 +24,13 @@ export interface GrantsCommandContext {
23
24
  cwd: string;
24
25
  governed: boolean;
25
26
  ownGrant: Capability[];
27
+ /**
28
+ * Which executor this session settled on, and why — ADR-0031.
29
+ *
30
+ * The whole `ExecutorChoice` rather than a rendered string, so this command cannot compose a different
31
+ * sentence from the one the session banner printed. Two spellings of one fact is R-28.
32
+ */
33
+ executor: ExecutorChoice;
26
34
  observed: boolean;
27
35
  depth: number;
28
36
  maxDepth: number;
@@ -43,6 +51,15 @@ export interface GrantsCommandContext {
43
51
  * R-28 discipline where it belongs: this command asks what would happen instead of working it out.
44
52
  */
45
53
  previewDelegation: (name: string) => Promise<GatedPlan>;
54
+ /**
55
+ * Run `/grants init`: scaffold definitions, ask about the withheld capabilities only, store the answer
56
+ * outside the workspace, and apply it to this session (ADR-0030).
57
+ *
58
+ * Injected rather than imported so this module stays what it is — a read-only diagnostic that cannot
59
+ * become a governance path by accident. The one command here that *writes* takes its ability to do so
60
+ * from the caller, visibly.
61
+ */
62
+ runInit: () => Promise<void>;
46
63
  }
47
64
 
48
65
  /**
@@ -51,6 +68,9 @@ export interface GrantsCommandContext {
51
68
  */
52
69
  const PREVIEW_LIMIT = 12;
53
70
 
71
+ /** The verbs `/grants` answers to. Anything else is refused rather than silently treated as no verb. */
72
+ const KNOWN_SUBCOMMANDS: readonly string[] = ["init", "ledger", "approvals", "revoke"];
73
+
54
74
  export const grantsCommand = {
55
75
  description:
56
76
  "Show this session's capability grant, delegation depth, and known agent-type ceilings; " +
@@ -59,12 +79,17 @@ handler: async (args: string, ctx: any) => {
59
79
  // Everything this command may see, named in one place. Previously these were whatever happened to be in
60
80
  // the enclosing closure — which is how a diagnostic came to disagree with the enforcer (R-28).
61
81
  const {
62
- cwd, governed, ownGrant, observed, depth, maxDepth, ledgerPath,
82
+ cwd, governed, ownGrant, executor, observed, depth, maxDepth, ledgerPath,
63
83
  catalog, definitions, sessionApprovals, inheritedApprovals, snapshotOf, previewDelegation,
64
84
  } = ctx.grants as GrantsCommandContext;
65
85
 
66
86
  const [sub, target] = args.trim().split(/\s+/).filter(Boolean);
67
87
 
88
+ if (sub === "init") {
89
+ await ctx.grants.runInit();
90
+ return;
91
+ }
92
+
68
93
  if (sub === "ledger") {
69
94
  // The detector, made reachable. `verifyLedger` exists because nothing in this package had ever read
70
95
  // a ledger back, so a torn line was indistinguishable from a spawn that never happened — and a
@@ -85,6 +110,17 @@ handler: async (args: string, ctx: any) => {
85
110
  ` integrity ${report.ok ? "OK" : `${report.corrupt.length} UNPARSEABLE LINE(S)`}`,
86
111
  ];
87
112
  for (const bad of report.corrupt.slice(0, 5)) lines.push(` line ${bad.line}: ${bad.text}`);
113
+ // ADR-0031, and R-51's lesson applied on the day the field was added rather than a release later: a field
114
+ // the writer sets and no diagnostic reads is one that needs `jq`, and `docs/SPEC.md` claims the executor is
115
+ // announced "per child in the ledger". `unknown` is shown only when present, because on a fresh ledger it
116
+ // is always zero and a permanent zero is noise; on an upgraded one it is the count of pre-0.16 lines, which
117
+ // is worth seeing.
118
+ lines.push(
119
+ ` executors ${report.executors.herdr} herdr pane(s) · ${report.executors.process} subprocess(es)` +
120
+ (report.executors.unknown > 0
121
+ ? ` · ${report.executors.unknown} not recorded (written before 0.16.0, which added the field)`
122
+ : ""),
123
+ );
88
124
  // R-51. ADR-0018 advertises that the ledger answers "did these four children run the same
89
125
  // instructions?" and "has this definition changed since?" — and nothing read `definitionDigest`, so
90
126
  // both needed hand-written jq and the second was not reproducible with `sha256sum` (the digest covers
@@ -265,10 +301,30 @@ handler: async (args: string, ctx: any) => {
265
301
  return;
266
302
  }
267
303
 
304
+ // **An unrecognised subcommand is refused, not ignored (R-74).** Every `sub` that is not a known verb
305
+ // fell through to this status screen with the word silently dropped, so `/grants init` — a command that
306
+ // does not exist — printed a healthy-looking report and read as though it had run. `/grants ledgr` did
307
+ // the same. A diagnostic that answers a question nobody asked is worse than one that refuses: it is
308
+ // indistinguishable from success, which is the shape this package spends most of its risk register on.
309
+ if (sub !== undefined && !KNOWN_SUBCOMMANDS.includes(sub)) {
310
+ ctx.ui.notify(
311
+ `grants: unknown subcommand "${sub}" — did nothing. Known: ${KNOWN_SUBCOMMANDS.join(", ")}. ` +
312
+ `Plain /grants shows this session's status.`,
313
+ "warning",
314
+ );
315
+ return;
316
+ }
317
+
268
318
  const { valid } = await loadApprovals({ cwd, now: new Date(), snapshotOf });
269
319
  const lines = [
270
320
  governed ? "grants: ACTIVE" : "grants: inactive (set PI_GRANTS_GRANT to govern this session)",
271
321
  ` holding ${ownGrant.join(", ") || "(nothing)"}${observed ? " (observed)" : " (inherited, not yet observed)"}`,
322
+ // ADR-0031/0032. This screen named the grant, the depth, the ledger, the approvals and the catalog, and
323
+ // never said WHERE children run — so an operator on a machine where herdr hosts their whole workspace
324
+ // could not discover that their children were invisible subprocesses. That gap is what produced
325
+ // ADR-0031, and its probe is only defensible because this line exists. Placed next to `holding` so the
326
+ // two facts about what a spawn will be sit together.
327
+ ` executor ${executor.disclosure}`,
272
328
  ` depth ${depth} of max ${maxDepth}${maxDepth <= 0 ? " (spawning disabled)" : ""}`,
273
329
  ` ledger ${ledgerPath ?? "(not recording — set PI_GRANTS_LEDGER)"}`,
274
330
  ` approvals ${sessionApprovals.size} this session, ${valid.size} persisted` +