pi-daddy 0.13.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 (105) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/LICENSE +21 -0
  3. package/README.md +584 -0
  4. package/dist/approval-prompt.d.ts +116 -0
  5. package/dist/approval-prompt.d.ts.map +1 -0
  6. package/dist/approval-prompt.js +124 -0
  7. package/dist/approval-prompt.js.map +1 -0
  8. package/dist/approval-store.d.ts +149 -0
  9. package/dist/approval-store.d.ts.map +1 -0
  10. package/dist/approval-store.js +311 -0
  11. package/dist/approval-store.js.map +1 -0
  12. package/dist/approval.d.ts +221 -0
  13. package/dist/approval.d.ts.map +1 -0
  14. package/dist/approval.js +244 -0
  15. package/dist/approval.js.map +1 -0
  16. package/dist/capabilities.d.ts +31 -0
  17. package/dist/capabilities.d.ts.map +1 -0
  18. package/dist/capabilities.js +45 -0
  19. package/dist/capabilities.js.map +1 -0
  20. package/dist/catalog.d.ts +81 -0
  21. package/dist/catalog.d.ts.map +1 -0
  22. package/dist/catalog.js +164 -0
  23. package/dist/catalog.js.map +1 -0
  24. package/dist/definitions.d.ts +101 -0
  25. package/dist/definitions.d.ts.map +1 -0
  26. package/dist/definitions.js +182 -0
  27. package/dist/definitions.js.map +1 -0
  28. package/dist/delegate.d.ts +133 -0
  29. package/dist/delegate.d.ts.map +1 -0
  30. package/dist/delegate.js +261 -0
  31. package/dist/delegate.js.map +1 -0
  32. package/dist/fanout.d.ts +62 -0
  33. package/dist/fanout.d.ts.map +1 -0
  34. package/dist/fanout.js +84 -0
  35. package/dist/fanout.js.map +1 -0
  36. package/dist/file-lock.d.ts +54 -0
  37. package/dist/file-lock.d.ts.map +1 -0
  38. package/dist/file-lock.js +142 -0
  39. package/dist/file-lock.js.map +1 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +7 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/ledger-report.d.ts +106 -0
  45. package/dist/ledger-report.d.ts.map +1 -0
  46. package/dist/ledger-report.js +157 -0
  47. package/dist/ledger-report.js.map +1 -0
  48. package/dist/ledger.d.ts +139 -0
  49. package/dist/ledger.d.ts.map +1 -0
  50. package/dist/ledger.js +94 -0
  51. package/dist/ledger.js.map +1 -0
  52. package/dist/pane-reaper.d.ts +49 -0
  53. package/dist/pane-reaper.d.ts.map +1 -0
  54. package/dist/pane-reaper.js +120 -0
  55. package/dist/pane-reaper.js.map +1 -0
  56. package/dist/pi-tools.d.ts +20 -0
  57. package/dist/pi-tools.d.ts.map +1 -0
  58. package/dist/pi-tools.js +21 -0
  59. package/dist/pi-tools.js.map +1 -0
  60. package/dist/propagation.d.ts +185 -0
  61. package/dist/propagation.d.ts.map +1 -0
  62. package/dist/propagation.js +255 -0
  63. package/dist/propagation.js.map +1 -0
  64. package/dist/resolve.d.ts +110 -0
  65. package/dist/resolve.d.ts.map +1 -0
  66. package/dist/resolve.js +181 -0
  67. package/dist/resolve.js.map +1 -0
  68. package/dist/run-child.d.ts +50 -0
  69. package/dist/run-child.d.ts.map +1 -0
  70. package/dist/run-child.js +109 -0
  71. package/dist/run-child.js.map +1 -0
  72. package/dist/run-herdr.d.ts +91 -0
  73. package/dist/run-herdr.d.ts.map +1 -0
  74. package/dist/run-herdr.js +306 -0
  75. package/dist/run-herdr.js.map +1 -0
  76. package/dist/spawn.d.ts +60 -0
  77. package/dist/spawn.d.ts.map +1 -0
  78. package/dist/spawn.js +116 -0
  79. package/dist/spawn.js.map +1 -0
  80. package/extensions/approvals.ts +285 -0
  81. package/extensions/delegation.ts +237 -0
  82. package/extensions/grants-command.ts +310 -0
  83. package/extensions/grants.ts +346 -0
  84. package/extensions/run-delegation.ts +270 -0
  85. package/extensions/session.ts +234 -0
  86. package/package.json +122 -0
  87. package/src/approval-prompt.ts +222 -0
  88. package/src/approval-store.ts +383 -0
  89. package/src/approval.ts +364 -0
  90. package/src/capabilities.ts +51 -0
  91. package/src/catalog.ts +186 -0
  92. package/src/definitions.ts +233 -0
  93. package/src/delegate.ts +383 -0
  94. package/src/fanout.ts +96 -0
  95. package/src/file-lock.ts +142 -0
  96. package/src/index.ts +59 -0
  97. package/src/ledger-report.ts +227 -0
  98. package/src/ledger.ts +214 -0
  99. package/src/pane-reaper.ts +132 -0
  100. package/src/pi-tools.ts +25 -0
  101. package/src/propagation.ts +308 -0
  102. package/src/resolve.ts +235 -0
  103. package/src/run-child.ts +149 -0
  104. package/src/run-herdr.ts +357 -0
  105. package/src/spawn.ts +156 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Grant resolution — the entire security surface of capability governance, as a pure function.
3
+ *
4
+ * The invariant (ADR-0008): a capability set may only ever SHRINK as it passes down a delegation
5
+ * tree. Escalation is impossible by construction rather than by policy, because a parent can never
6
+ * confer what it does not itself hold.
7
+ *
8
+ * effective = ( requested ∩ parentGrant ∩ ceiling ) \ (gated \ approved)
9
+ *
10
+ * Being pure — no I/O, no model, no network — this is exhaustively testable, which matters because it
11
+ * is the only place an escalation could be introduced.
12
+ */
13
+ /** `tool:read` · `ext:pi-web-access/web_search` · `skill:review` · `agent:researcher` */
14
+ export type Capability = string;
15
+ /**
16
+ * Capabilities that transitively confer everything else. Granting one is equivalent to granting the
17
+ * whole catalog, so they can never be a *narrowing* grant.
18
+ *
19
+ * `ext:pi-fabric/fabric_exec` is here on measured evidence, not suspicion: a child granted
20
+ * `tools: []` (nothing at all) plus `recursive: true` still reached `pi.write` and `pi.bash` and
21
+ * spawned a grandchild that wrote to disk. See docs/probes/pi-fabric-eval (probes 2, 4, 7, 8).
22
+ */
23
+ export declare const UNIVERSAL_CAPABILITIES: readonly Capability[];
24
+ /**
25
+ * Capabilities that functionally contain others.
26
+ *
27
+ * `bash` can run `grep`, `find`, `ls`, `cat`, and `sed` — so a session holding it can already do
28
+ * everything the file and search tools do, whatever the tool list says. Modelling this explicitly serves
29
+ * two purposes:
30
+ *
31
+ * 1. It removes false escalation reports. pi's *default* surface is only `read`, `bash`, `edit`, `write`
32
+ * (measured, not assumed), so an agent type declaring `tools: read, grep, find, ls` would otherwise
33
+ * look like an escalation from any normal parent — despite being strictly weaker.
34
+ * 2. It makes the uncomfortable truth visible rather than implied: **a grant containing `bash` is not a
35
+ * narrow grant.** `subsumedBy` in the result says so, so a reviewer can see what the grant really means.
36
+ */
37
+ export declare const SUBSUMPTION: Readonly<Record<Capability, readonly Capability[]>>;
38
+ /** Expand a grant to everything it functionally confers. */
39
+ export declare function expandSubsumed(grant: Capability[]): Capability[];
40
+ /**
41
+ * "Any definition" — ADR-0023, and one of two wildcards this module understands.
42
+ *
43
+ * Declared here rather than beside `WILDCARD` because `pi-tools.ts` imports `Capability` from this module.
44
+ * That import is `import type`, so it is erased and the runtime dependency runs one way only — which is
45
+ * what makes importing `WILDCARD` back safe.
46
+ *
47
+ * Deliberately weaker than `tool:*`: it confers **no tool authority**, so `agent:*,tool:read` may spawn
48
+ * every definition on disk and hand each of them nothing but `read`. It exists because the alternative was
49
+ * `tool:*` — authority to grant every tool — which made the safe configuration the laborious one.
50
+ */
51
+ export declare const AGENT_WILDCARD: Capability;
52
+ export interface ResolveInput {
53
+ /** What the delegating agent asked to give the child. */
54
+ requested: Capability[];
55
+ /** What the delegating agent itself holds. The root's grant is configured, never defaulted to all. */
56
+ parentGrant: Capability[];
57
+ /** Declarative maximum for the child's agent type (its frontmatter). Omit for no ceiling. */
58
+ ceiling?: Capability[];
59
+ /** Destructive capabilities that may never enter a grant without explicit human approval. */
60
+ gated?: Capability[];
61
+ /** Gated capabilities a human has approved for this specific spawn. */
62
+ approved?: Capability[];
63
+ /**
64
+ * Honour functional subsumption when deciding what the parent covers (default true).
65
+ * Set false for a strict name-equality check.
66
+ */
67
+ subsumption?: boolean;
68
+ }
69
+ export interface ResolveResult {
70
+ /** The capability set the child may hold. */
71
+ effective: Capability[];
72
+ /** Requested but NOT held by the parent — the escalation-attempt signal. Log every one. */
73
+ denied: Capability[];
74
+ /** Held by the parent but outside the child type's declared ceiling. */
75
+ clipped: Capability[];
76
+ /** Allowed by the tree but gated and unapproved. */
77
+ gatedBlocked: Capability[];
78
+ /** Universal capabilities that survived resolution — see `assertNarrowing`. */
79
+ universal: Capability[];
80
+ /**
81
+ * Capabilities the parent covers only through subsumption, not by holding them directly — e.g. `grep`
82
+ * covered because the parent holds `bash`. Non-empty means the grant is broader than its list suggests.
83
+ */
84
+ subsumedBy: Capability[];
85
+ }
86
+ /**
87
+ * Resolve a child's grant. Total and side-effect free; every rejected capability is reported rather
88
+ * than silently dropped, because a grant nobody can audit proves nothing.
89
+ */
90
+ export declare function resolve(input: ResolveInput): ResolveResult;
91
+ /**
92
+ * Fail closed on a grant that cannot actually narrow anything.
93
+ *
94
+ * A universal capability in an "attenuated" grant is not a narrow grant with one extra item — it is
95
+ * full authority wearing a narrow grant's clothing. Callers must opt in explicitly rather than
96
+ * discover this at runtime.
97
+ */
98
+ export declare function assertNarrowing(result: ResolveResult, allowUniversal?: boolean): void;
99
+ /**
100
+ * Project an effective grant onto pi's `--tools` allowlist.
101
+ *
102
+ * pi core is the enforcement point — verified: `--tools` and `--no-tools` both hard-block extension
103
+ * tools, and an explicitly `-e`-loaded extension cannot re-add its tool past them
104
+ * (docs/probes/pi-fabric-eval probes 9–11). That is why enforcement needs no in-descendant runtime.
105
+ *
106
+ * Returns `null` when the grant contains no callable tools, meaning the caller should pass
107
+ * `--no-tools` rather than an empty `--tools` (an empty list is not a valid allowlist).
108
+ */
109
+ export declare function toPiToolsAllowlist(effective: Capability[]): string[] | null;
110
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,UAAU,EAGvD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,UAAU,EAAE,CAAC,CAU3E,CAAC;AAEF,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAMhE;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,EAAE,UAAsB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,sGAAsG;IACtG,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,6FAA6F;IAC7F,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,6FAA6F;IAC7F,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,2FAA2F;IAC3F,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,oDAAoD;IACpD,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB;;;OAGG;IACH,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B;AAID;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CA6E1D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,cAAc,UAAQ,GAAG,IAAI,CAQnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAQ3E"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Grant resolution — the entire security surface of capability governance, as a pure function.
3
+ *
4
+ * The invariant (ADR-0008): a capability set may only ever SHRINK as it passes down a delegation
5
+ * tree. Escalation is impossible by construction rather than by policy, because a parent can never
6
+ * confer what it does not itself hold.
7
+ *
8
+ * effective = ( requested ∩ parentGrant ∩ ceiling ) \ (gated \ approved)
9
+ *
10
+ * Being pure — no I/O, no model, no network — this is exhaustively testable, which matters because it
11
+ * is the only place an escalation could be introduced.
12
+ */
13
+ /**
14
+ * Capabilities that transitively confer everything else. Granting one is equivalent to granting the
15
+ * whole catalog, so they can never be a *narrowing* grant.
16
+ *
17
+ * `ext:pi-fabric/fabric_exec` is here on measured evidence, not suspicion: a child granted
18
+ * `tools: []` (nothing at all) plus `recursive: true` still reached `pi.write` and `pi.bash` and
19
+ * spawned a grandchild that wrote to disk. See docs/probes/pi-fabric-eval (probes 2, 4, 7, 8).
20
+ */
21
+ export const UNIVERSAL_CAPABILITIES = [
22
+ "ext:pi-fabric/fabric_exec",
23
+ "tool:fabric_exec",
24
+ ];
25
+ /**
26
+ * Capabilities that functionally contain others.
27
+ *
28
+ * `bash` can run `grep`, `find`, `ls`, `cat`, and `sed` — so a session holding it can already do
29
+ * everything the file and search tools do, whatever the tool list says. Modelling this explicitly serves
30
+ * two purposes:
31
+ *
32
+ * 1. It removes false escalation reports. pi's *default* surface is only `read`, `bash`, `edit`, `write`
33
+ * (measured, not assumed), so an agent type declaring `tools: read, grep, find, ls` would otherwise
34
+ * look like an escalation from any normal parent — despite being strictly weaker.
35
+ * 2. It makes the uncomfortable truth visible rather than implied: **a grant containing `bash` is not a
36
+ * narrow grant.** `subsumedBy` in the result says so, so a reviewer can see what the grant really means.
37
+ */
38
+ export const SUBSUMPTION = {
39
+ "tool:bash": [
40
+ "tool:grep",
41
+ "tool:find",
42
+ "tool:ls",
43
+ "tool:read",
44
+ "tool:write",
45
+ "tool:edit",
46
+ "tool:edit-diff",
47
+ ],
48
+ };
49
+ /** Expand a grant to everything it functionally confers. */
50
+ export function expandSubsumed(grant) {
51
+ const expanded = new Set(grant);
52
+ for (const held of grant) {
53
+ for (const implied of SUBSUMPTION[held] ?? [])
54
+ expanded.add(implied);
55
+ }
56
+ return [...expanded].sort();
57
+ }
58
+ import { WILDCARD } from "./pi-tools.js";
59
+ /**
60
+ * "Any definition" — ADR-0023, and one of two wildcards this module understands.
61
+ *
62
+ * Declared here rather than beside `WILDCARD` because `pi-tools.ts` imports `Capability` from this module.
63
+ * That import is `import type`, so it is erased and the runtime dependency runs one way only — which is
64
+ * what makes importing `WILDCARD` back safe.
65
+ *
66
+ * Deliberately weaker than `tool:*`: it confers **no tool authority**, so `agent:*,tool:read` may spawn
67
+ * every definition on disk and hand each of them nothing but `read`. It exists because the alternative was
68
+ * `tool:*` — authority to grant every tool — which made the safe configuration the laborious one.
69
+ */
70
+ export const AGENT_WILDCARD = "agent:*";
71
+ const unique = (xs) => [...new Set(xs)].sort();
72
+ /**
73
+ * Resolve a child's grant. Total and side-effect free; every rejected capability is reported rather
74
+ * than silently dropped, because a grant nobody can audit proves nothing.
75
+ */
76
+ export function resolve(input) {
77
+ const requested = unique(input.requested);
78
+ const held = new Set(input.parentGrant);
79
+ const parent = input.subsumption === false ? held : new Set(expandSubsumed(input.parentGrant));
80
+ /**
81
+ * `agent:*` covers any `agent:<name>` — ADR-0023, and the ONLY wildcard rule in this function.
82
+ *
83
+ * `resolve` is otherwise exact-match plus subsumption, deliberately: `tool:*` works not because anything
84
+ * here understands it, but because `deriveOwnGrant` *enumerates* a session's observed tool names beside
85
+ * it. Definitions are not tools, so nothing enumerates them — which is why `maySpawnDefinition` had to
86
+ * special-case the wildcard, and why this needs stating rather than falling out.
87
+ *
88
+ * Scoped to one namespace on purpose. There is no generalised `<ns>:*` rule, so a namespace added later
89
+ * does not silently acquire a wildcard; adding one is a deliberate edit and another decision.
90
+ */
91
+ const anyDefinition = held.has(AGENT_WILDCARD);
92
+ /**
93
+ * `tool:*` satisfies **any** capability, including `skill:` and `agent:` ids.
94
+ *
95
+ * Missing until 0.11.2, and the omission broke the one rule this package must never break by accident:
96
+ * *governance is opt-in*. An ungoverned session holds `[tool:*, …observed tools]` and nothing else, so
97
+ * spawning a definition whose `allowed-tools` names `agent:worker` or `skill:review` — the composition
98
+ * ADR-0017 created and ADR-0023's own example uses — was refused with **"capability escalation
99
+ * blocked"**, and recorded as an escalation attempt, in a session that had opted out.
100
+ *
101
+ * `maySpawnDefinition` had always honoured `tool:*` for definition ids and `docs/SPEC.md` had always
102
+ * claimed it "satisfies any capability". This function disagreed with both, which is R-28's shape: two
103
+ * spellings of one rule, and the enforcing one was wrong.
104
+ */
105
+ const anyCapability = held.has(WILDCARD);
106
+ const covered = (c) => parent.has(c) || anyCapability || (anyDefinition && c.startsWith("agent:"));
107
+ const ceiling = input.ceiling === undefined ? null : new Set(input.ceiling);
108
+ const gated = new Set(input.gated ?? []);
109
+ const approved = new Set(input.approved ?? []);
110
+ // Order is irrelevant to the outcome (set intersection is commutative), so each rejection reason is
111
+ // reported independently rather than being masked by whichever filter happened to run first.
112
+ const denied = requested.filter((c) => !covered(c));
113
+ const clipped = requested.filter((c) => covered(c) && ceiling !== null && !ceiling.has(c));
114
+ /**
115
+ * Is this capability gated, directly or by subsuming something gated?
116
+ *
117
+ * ADR-0012. Exact-name gating was defeatable by handing down a broader capability: the package's own
118
+ * `SUBSUMPTION` table says `bash` confers `write`, so gating `write` produced **no prompt** when `bash`
119
+ * was granted instead. The gate read as satisfied because the string never appeared.
120
+ *
121
+ * The direction is load-bearing and easy to invert. A capability is gated when it **subsumes**
122
+ * something gated — never when it **is subsumed by** something gated. Gating `bash` must not quietly
123
+ * gate a plain `write` grant, which would make gating a broad capability restrict the narrow ones and
124
+ * invert least privilege.
125
+ */
126
+ const isGated = (c) => {
127
+ if (gated.has(c))
128
+ return true;
129
+ if (input.subsumption === false)
130
+ return false;
131
+ return (SUBSUMPTION[c] ?? []).some((implied) => gated.has(implied));
132
+ };
133
+ const gatedBlocked = requested.filter((c) => covered(c) && (ceiling === null || ceiling.has(c)) && isGated(c) && !approved.has(c));
134
+ const rejected = new Set([...denied, ...clipped, ...gatedBlocked]);
135
+ const effective = requested.filter((c) => !rejected.has(c));
136
+ return {
137
+ effective,
138
+ denied,
139
+ clipped,
140
+ gatedBlocked,
141
+ universal: effective.filter((c) => UNIVERSAL_CAPABILITIES.includes(c)),
142
+ // F9: capabilities covered by a WILDCARD are not "subsumed" — this field means "the grant is broader
143
+ // than its list suggests", which is the `bash`-covers-`grep` warning. A wildcard holder already knows
144
+ // its grant is broad; listing every id under it would bury the signal the field exists to carry.
145
+ subsumedBy: effective.filter((c) => !held.has(c) && !anyCapability && !(anyDefinition && c.startsWith("agent:"))),
146
+ };
147
+ }
148
+ /**
149
+ * Fail closed on a grant that cannot actually narrow anything.
150
+ *
151
+ * A universal capability in an "attenuated" grant is not a narrow grant with one extra item — it is
152
+ * full authority wearing a narrow grant's clothing. Callers must opt in explicitly rather than
153
+ * discover this at runtime.
154
+ */
155
+ export function assertNarrowing(result, allowUniversal = false) {
156
+ if (!allowUniversal && result.universal.length > 0) {
157
+ throw new Error(`refusing to issue a grant containing universal capabilities: ${result.universal.join(", ")}. ` +
158
+ `These transitively confer the whole catalog, so the grant would not narrow anything. ` +
159
+ `Pass allowUniversal to override deliberately.`);
160
+ }
161
+ }
162
+ /**
163
+ * Project an effective grant onto pi's `--tools` allowlist.
164
+ *
165
+ * pi core is the enforcement point — verified: `--tools` and `--no-tools` both hard-block extension
166
+ * tools, and an explicitly `-e`-loaded extension cannot re-add its tool past them
167
+ * (docs/probes/pi-fabric-eval probes 9–11). That is why enforcement needs no in-descendant runtime.
168
+ *
169
+ * Returns `null` when the grant contains no callable tools, meaning the caller should pass
170
+ * `--no-tools` rather than an empty `--tools` (an empty list is not a valid allowlist).
171
+ */
172
+ export function toPiToolsAllowlist(effective) {
173
+ const names = effective
174
+ .filter((c) => c.startsWith("tool:") || c.startsWith("ext:"))
175
+ // `tool:read` -> `read`; `ext:pkg/web_search` -> `web_search` (pi matches on bare tool name)
176
+ .map((c) => (c.startsWith("tool:") ? c.slice(5) : c.slice(c.lastIndexOf("/") + 1)))
177
+ .filter((n) => n.length > 0);
178
+ const deduped = [...new Set(names)].sort();
179
+ return deduped.length > 0 ? deduped : null;
180
+ }
181
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Run a governed child process under hard limits.
3
+ *
4
+ * G8 (review findings A-R1/B-I5, A-R3). `delegate` used to spawn `pi` with no output cap, no timeout,
5
+ * an abort listener attached too late to observe an already-aborted signal, and a non-zero exit reported
6
+ * to the model as an ordinary result. Each of those is a way for a child to outlive or overwhelm the
7
+ * orchestrator that is supposed to be governing it — which is the whole premise of this package.
8
+ *
9
+ * It lives here, out of `extensions/grants.ts`, so it can be tested against real processes without pi.
10
+ */
11
+ /**
12
+ * Operator override for the child wall-clock limit, in seconds.
13
+ *
14
+ * Deliberately NOT in `GRANT_ENV_KEYS`: those are stripped from a child's environment and re-supplied
15
+ * only by the spawn plan, which is right for capability state and wrong for an operator preference. This
16
+ * one should simply inherit, so a bound set at the root applies all the way down.
17
+ */
18
+ export declare const ENV_CHILD_TIMEOUT = "PI_GRANTS_CHILD_TIMEOUT";
19
+ /** Read the override, falling back to the default on absent *or* malformed input (G7's rule). */
20
+ export declare function timeoutFromEnv(raw: string | undefined): number;
21
+ export interface ChildRunRequest {
22
+ command: string;
23
+ args: string[];
24
+ env: NodeJS.ProcessEnv;
25
+ cwd: string;
26
+ signal?: AbortSignal;
27
+ /** Hard cap on captured output. Beyond it the child is killed and the result flagged. */
28
+ maxOutputBytes?: number;
29
+ /** Wall-clock cap. On expiry: SIGTERM, then SIGKILL after `killGraceMs`. */
30
+ timeoutMs?: number;
31
+ killGraceMs?: number;
32
+ }
33
+ export interface ChildRunResult {
34
+ /** Exit code, or `null` when nothing was spawned or the child was killed by a signal. */
35
+ code: number | null;
36
+ text: string;
37
+ truncated: boolean;
38
+ timedOut: boolean;
39
+ aborted: boolean;
40
+ /** Set when the process could not be started at all. */
41
+ spawnError?: string;
42
+ }
43
+ /** 1 MiB. A delegation returns a summary; anything larger is a runaway, not an answer. */
44
+ export declare const DEFAULT_MAX_OUTPUT_BYTES: number;
45
+ /** 10 minutes. Long enough for a real sub-agent task, short enough that a hang is not forever. */
46
+ export declare const DEFAULT_TIMEOUT_MS: number;
47
+ /** Grace between SIGTERM and SIGKILL. A child that ignores SIGTERM must not make the timeout advisory. */
48
+ export declare const DEFAULT_KILL_GRACE_MS = 5000;
49
+ export declare function runChild(request: ChildRunRequest): Promise<ChildRunResult>;
50
+ //# sourceMappingURL=run-child.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-child.d.ts","sourceRoot":"","sources":["../src/run-child.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,4BAA4B,CAAC;AAE3D,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK9D;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,yFAAyF;IACzF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,QAAc,CAAC;AACpD,kGAAkG;AAClG,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AACjD,0GAA0G;AAC1G,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAsF1E"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Run a governed child process under hard limits.
3
+ *
4
+ * G8 (review findings A-R1/B-I5, A-R3). `delegate` used to spawn `pi` with no output cap, no timeout,
5
+ * an abort listener attached too late to observe an already-aborted signal, and a non-zero exit reported
6
+ * to the model as an ordinary result. Each of those is a way for a child to outlive or overwhelm the
7
+ * orchestrator that is supposed to be governing it — which is the whole premise of this package.
8
+ *
9
+ * It lives here, out of `extensions/grants.ts`, so it can be tested against real processes without pi.
10
+ */
11
+ import { spawn } from "node:child_process";
12
+ import { parseBound } from "./propagation.js";
13
+ /**
14
+ * Operator override for the child wall-clock limit, in seconds.
15
+ *
16
+ * Deliberately NOT in `GRANT_ENV_KEYS`: those are stripped from a child's environment and re-supplied
17
+ * only by the spawn plan, which is right for capability state and wrong for an operator preference. This
18
+ * one should simply inherit, so a bound set at the root applies all the way down.
19
+ */
20
+ export const ENV_CHILD_TIMEOUT = "PI_GRANTS_CHILD_TIMEOUT";
21
+ /** Read the override, falling back to the default on absent *or* malformed input (G7's rule). */
22
+ export function timeoutFromEnv(raw) {
23
+ const seconds = parseBound(raw);
24
+ // `null` (malformed) and `0` both fall back rather than disabling the limit: a timeout that can be
25
+ // switched off by a typo is the A-S4 defect wearing different clothes.
26
+ return seconds === undefined || seconds === null || seconds === 0 ? DEFAULT_TIMEOUT_MS : seconds * 1000;
27
+ }
28
+ /** 1 MiB. A delegation returns a summary; anything larger is a runaway, not an answer. */
29
+ export const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
30
+ /** 10 minutes. Long enough for a real sub-agent task, short enough that a hang is not forever. */
31
+ export const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
32
+ /** Grace between SIGTERM and SIGKILL. A child that ignores SIGTERM must not make the timeout advisory. */
33
+ export const DEFAULT_KILL_GRACE_MS = 5000;
34
+ export function runChild(request) {
35
+ const maxOutputBytes = request.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
36
+ const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
37
+ const killGraceMs = request.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
38
+ // A-R3: checked BEFORE spawning. `AbortSignal` does not replay, so a listener attached after an
39
+ // `await` cannot observe an abort that already happened — and the child would then run to completion
40
+ // outside the cancellation that was supposed to stop it. Nothing is started at all here.
41
+ if (request.signal?.aborted) {
42
+ return Promise.resolve({ code: null, text: "", truncated: false, timedOut: false, aborted: true });
43
+ }
44
+ return new Promise((settle) => {
45
+ let child;
46
+ try {
47
+ child = spawn(request.command, request.args, {
48
+ env: request.env,
49
+ cwd: request.cwd,
50
+ stdio: ["ignore", "pipe", "pipe"],
51
+ });
52
+ }
53
+ catch (error) {
54
+ settle({ code: null, text: "", truncated: false, timedOut: false, aborted: false, spawnError: String(error) });
55
+ return;
56
+ }
57
+ let text = "";
58
+ let bytes = 0;
59
+ let truncated = false;
60
+ let timedOut = false;
61
+ let aborted = false;
62
+ let done = false;
63
+ const timers = [];
64
+ const clearTimers = () => timers.forEach(clearTimeout);
65
+ /** SIGTERM, then SIGKILL if the child is still alive — so a handler cannot ignore its way out. */
66
+ const stop = () => {
67
+ child.kill("SIGTERM");
68
+ timers.push(setTimeout(() => child.kill("SIGKILL"), killGraceMs));
69
+ };
70
+ const capture = (chunk) => {
71
+ if (truncated)
72
+ return;
73
+ const s = String(chunk);
74
+ bytes += Buffer.byteLength(s);
75
+ if (bytes > maxOutputBytes) {
76
+ // Keep what fits, mark it, and stop the child: an unbounded producer must not be able to
77
+ // exhaust the orchestrator's memory just because it was granted a tool that prints.
78
+ text += s;
79
+ text = text.slice(0, maxOutputBytes);
80
+ truncated = true;
81
+ stop();
82
+ return;
83
+ }
84
+ text += s;
85
+ };
86
+ child.stdout?.on("data", capture);
87
+ child.stderr?.on("data", capture);
88
+ timers.push(setTimeout(() => {
89
+ timedOut = true;
90
+ stop();
91
+ }, timeoutMs));
92
+ const onAbort = () => {
93
+ aborted = true;
94
+ stop();
95
+ };
96
+ request.signal?.addEventListener("abort", onAbort, { once: true });
97
+ const finish = (result) => {
98
+ if (done)
99
+ return;
100
+ done = true;
101
+ clearTimers();
102
+ request.signal?.removeEventListener("abort", onAbort);
103
+ settle(result);
104
+ };
105
+ child.on("error", (error) => finish({ code: null, text, truncated, timedOut, aborted, spawnError: String(error) }));
106
+ child.on("close", (code) => finish({ code, text, truncated, timedOut, aborted }));
107
+ });
108
+ }
109
+ //# sourceMappingURL=run-child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-child.js","sourceRoot":"","sources":["../src/run-child.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Run a governed child in a herdr pane — ADR-0016 point 6.
3
+ *
4
+ * The second executor for the same plan. `runChild` spawns `pi` directly and captures its stdout;
5
+ * `runHerdrPane` asks herdr to launch it in a visible, attachable terminal pane. `planSpawn` produces the
6
+ * argv either way, so the grant is identical and only the *place it runs* differs.
7
+ *
8
+ * **Why go through herdr's CLI rather than the third-party `pi-herdr` extension.** That extension exposes
9
+ * `agentArgs` and `env` as MODEL-facing tool parameters (R-30), which hands a model an argv array and the
10
+ * environment variable the grant travels on. Here the model chooses a definition and a task; this package
11
+ * builds the argv. Measured facts this relies on (`docs/probes/g16-herdr`):
12
+ *
13
+ * - `herdr agent start … -- <args>` delivers argv **verbatim**, echoed back in the reply.
14
+ * - `--tools` is enforced inside a pane exactly as it is for a direct spawn; `--no-tools` yields none.
15
+ * - `herdr agent start` has **no `--env`**, but `tab create` / `pane split` do, and a pane's environment
16
+ * reaches the shell that launches the agent — verified by reading `$PI_GRANTS_GRANT` back out of a
17
+ * pane created with it. That is how the grant, depth and ledger path propagate on this path.
18
+ *
19
+ * **What a pane is not: a boundary.** It is a terminal. `--tools` remains the enforcement point, ADR-0012's
20
+ * `bash` escape is unchanged, and a pane is *attachable by design*, so a human can type into a governed
21
+ * child. Humans are not this project's threat model, but nothing here should be read as containing one.
22
+ */
23
+ import type { ChildRunResult } from "./run-child.ts";
24
+ /** One herdr CLI invocation. Injectable so every rule below is testable without herdr installed. */
25
+ export type HerdrExec = (args: string[]) => Promise<{
26
+ code: number | null;
27
+ stdout: string;
28
+ stderr: string;
29
+ }>;
30
+ export interface HerdrRunRequest {
31
+ /** `planSpawn` args **without** the prompt — see `prompt`. */
32
+ args: string[];
33
+ /**
34
+ * The task, delivered with `herdr agent prompt` rather than as an argv element.
35
+ *
36
+ * This is strictly safer than the direct-spawn path, which has to defend a model-authored string from
37
+ * pi's argv parser by prefixing a space (`neutralisePrompt`, `docs/probes/g1-argv`). Here the task never
38
+ * reaches argv at all, so there is no parser in front of it.
39
+ */
40
+ prompt: string;
41
+ /** Grant/depth/ledger variables. Set on the PANE, which the agent's shell inherits. */
42
+ env: Record<string, string>;
43
+ cwd: string;
44
+ /** Unique pane and agent name. */
45
+ name: string;
46
+ /** herdr workspace to create the tab in. Omitted lets herdr choose. */
47
+ workspace?: string;
48
+ signal?: AbortSignal;
49
+ timeoutMs?: number;
50
+ maxOutputBytes?: number;
51
+ /**
52
+ * Leave the pane open after the run so a human can read or resume it.
53
+ *
54
+ * Default **false**: a fan-out that leaks a pane per child fills the operator's workspace, and
55
+ * `docs/probes/g16-herdr` records that panes are not trivially closable once orphaned.
56
+ */
57
+ keepPane?: boolean;
58
+ exec?: HerdrExec;
59
+ }
60
+ /**
61
+ * Move a multi-line `--append-system-prompt` out of argv, because herdr cannot encode it.
62
+ *
63
+ * **Measured.** `herdr agent start` types the argv into the pane's shell, so a value containing newlines
64
+ * is rejected outright: `invalid_agent_argument — agent arguments cannot be encoded safely for the target
65
+ * shell`. A definition's `SKILL.md` body is always multi-line, so every `delegate({agent})` spawn would
66
+ * fail on this path.
67
+ *
68
+ * pi accepts a **file path** there as readily as literal text (`resolvePromptInput` + `existsSync` in
69
+ * `dist/core/resource-loader.js`), so the fix is to write the body to a temp file and pass its path — one
70
+ * short, shell-safe argument.
71
+ *
72
+ * The split lives here rather than in `planSpawn` because the constraint is **herdr's**, not pi's: the
73
+ * direct executor passes the same text inline with no trouble, and a plan builder that pre-emptively wrote
74
+ * temp files for everybody would be paying one executor's tax on both paths.
75
+ */
76
+ export declare function splitSystemPrompt(args: string[]): {
77
+ args: string[];
78
+ systemPrompt?: string;
79
+ };
80
+ /** How often to poll `agent get` while waiting for the child to settle. */
81
+ export declare const POLL_INTERVAL_MS = 750;
82
+ /** How often to retry `agent start` while a freshly created pane is still reaching its shell prompt. */
83
+ export declare const PANE_READY_POLL_MS = 300;
84
+ /**
85
+ * Run one governed child in a pane and return its output.
86
+ *
87
+ * Deliberately returns `ChildRunResult` — the same shape as `runChild` — so the extension can choose an
88
+ * executor without knowing which one it got.
89
+ */
90
+ export declare function runHerdrPane(request: HerdrRunRequest): Promise<ChildRunResult>;
91
+ //# sourceMappingURL=run-herdr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-herdr.d.ts","sourceRoot":"","sources":["../src/run-herdr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD,oGAAoG;AACpG,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE7G,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAI3F;AAKD,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,wGAAwG;AACxG,eAAO,MAAM,kBAAkB,MAAM,CAAC;AA8BtC;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA8HpF"}