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.
- package/CHANGELOG.md +187 -0
- package/LICENSE +21 -0
- package/README.md +584 -0
- package/dist/approval-prompt.d.ts +116 -0
- package/dist/approval-prompt.d.ts.map +1 -0
- package/dist/approval-prompt.js +124 -0
- package/dist/approval-prompt.js.map +1 -0
- package/dist/approval-store.d.ts +149 -0
- package/dist/approval-store.d.ts.map +1 -0
- package/dist/approval-store.js +311 -0
- package/dist/approval-store.js.map +1 -0
- package/dist/approval.d.ts +221 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/approval.js +244 -0
- package/dist/approval.js.map +1 -0
- package/dist/capabilities.d.ts +31 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +45 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/catalog.d.ts +81 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +164 -0
- package/dist/catalog.js.map +1 -0
- package/dist/definitions.d.ts +101 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +182 -0
- package/dist/definitions.js.map +1 -0
- package/dist/delegate.d.ts +133 -0
- package/dist/delegate.d.ts.map +1 -0
- package/dist/delegate.js +261 -0
- package/dist/delegate.js.map +1 -0
- package/dist/fanout.d.ts +62 -0
- package/dist/fanout.d.ts.map +1 -0
- package/dist/fanout.js +84 -0
- package/dist/fanout.js.map +1 -0
- package/dist/file-lock.d.ts +54 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +142 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger-report.d.ts +106 -0
- package/dist/ledger-report.d.ts.map +1 -0
- package/dist/ledger-report.js +157 -0
- package/dist/ledger-report.js.map +1 -0
- package/dist/ledger.d.ts +139 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +94 -0
- package/dist/ledger.js.map +1 -0
- package/dist/pane-reaper.d.ts +49 -0
- package/dist/pane-reaper.d.ts.map +1 -0
- package/dist/pane-reaper.js +120 -0
- package/dist/pane-reaper.js.map +1 -0
- package/dist/pi-tools.d.ts +20 -0
- package/dist/pi-tools.d.ts.map +1 -0
- package/dist/pi-tools.js +21 -0
- package/dist/pi-tools.js.map +1 -0
- package/dist/propagation.d.ts +185 -0
- package/dist/propagation.d.ts.map +1 -0
- package/dist/propagation.js +255 -0
- package/dist/propagation.js.map +1 -0
- package/dist/resolve.d.ts +110 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +181 -0
- package/dist/resolve.js.map +1 -0
- package/dist/run-child.d.ts +50 -0
- package/dist/run-child.d.ts.map +1 -0
- package/dist/run-child.js +109 -0
- package/dist/run-child.js.map +1 -0
- package/dist/run-herdr.d.ts +91 -0
- package/dist/run-herdr.d.ts.map +1 -0
- package/dist/run-herdr.js +306 -0
- package/dist/run-herdr.js.map +1 -0
- package/dist/spawn.d.ts +60 -0
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +116 -0
- package/dist/spawn.js.map +1 -0
- package/extensions/approvals.ts +285 -0
- package/extensions/delegation.ts +237 -0
- package/extensions/grants-command.ts +310 -0
- package/extensions/grants.ts +346 -0
- package/extensions/run-delegation.ts +270 -0
- package/extensions/session.ts +234 -0
- package/package.json +122 -0
- package/src/approval-prompt.ts +222 -0
- package/src/approval-store.ts +383 -0
- package/src/approval.ts +364 -0
- package/src/capabilities.ts +51 -0
- package/src/catalog.ts +186 -0
- package/src/definitions.ts +233 -0
- package/src/delegate.ts +383 -0
- package/src/fanout.ts +96 -0
- package/src/file-lock.ts +142 -0
- package/src/index.ts +59 -0
- package/src/ledger-report.ts +227 -0
- package/src/ledger.ts +214 -0
- package/src/pane-reaper.ts +132 -0
- package/src/pi-tools.ts +25 -0
- package/src/propagation.ts +308 -0
- package/src/resolve.ts +235 -0
- package/src/run-child.ts +149 -0
- package/src/run-herdr.ts +357 -0
- package/src/spawn.ts +156 -0
package/dist/approval.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval model for gated capabilities — pure, so it is exhaustively testable without pi.
|
|
3
|
+
*
|
|
4
|
+
* `resolve()` computes `gatedBlocked`: capabilities a session legitimately holds but which may not enter
|
|
5
|
+
* a child's grant without a human saying so. This module decides WHAT a yes means — how far it reaches,
|
|
6
|
+
* how long it lasts, and what it is keyed to. It never performs I/O and never prompts; see
|
|
7
|
+
* `approval-store.ts` and `approval-prompt.ts` for those.
|
|
8
|
+
*
|
|
9
|
+
* ADR-0010 records the four decisions encoded here.
|
|
10
|
+
*/
|
|
11
|
+
import { WILDCARD } from "./pi-tools.js";
|
|
12
|
+
/**
|
|
13
|
+
* Subject used for delegate-path approvals.
|
|
14
|
+
*
|
|
15
|
+
* The delegate tool has no human-authored subject: the only things naming a child are the task string and
|
|
16
|
+
* the tool list, both chosen by the model. A key the model controls is not a key, so delegate approvals
|
|
17
|
+
* use this fixed literal — "allow write for delegations this session" — and are never persisted.
|
|
18
|
+
* Angle brackets cannot appear in an agent-type name (`parseAgentType` reads an identifier), so this can
|
|
19
|
+
* never collide with a real type.
|
|
20
|
+
*/
|
|
21
|
+
export const DELEGATE_SUBJECT = "<delegate>";
|
|
22
|
+
/**
|
|
23
|
+
* Lifetime of a persisted approval.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately a constant rather than an environment variable: a knob that silently extends every gate is
|
|
26
|
+
* the first thing an impatient operator would reach for.
|
|
27
|
+
*/
|
|
28
|
+
export const APPROVAL_TTL_DAYS = 30;
|
|
29
|
+
const DAY_MS = 86_400_000;
|
|
30
|
+
/** `tool:write` + `docs-writer` -> `tool:write@docs-writer`. */
|
|
31
|
+
export function approvalKey(capability, subject) {
|
|
32
|
+
return `${capability}@${subject}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* May this resolution raise an approval dialog?
|
|
36
|
+
*
|
|
37
|
+
* `resolve()` computes `denied` and `gatedBlocked` INDEPENDENTLY (deliberately — each rejection reason is
|
|
38
|
+
* reported rather than masked by whichever filter ran first), and both `decideSpawn` and `planDelegation`
|
|
39
|
+
* refuse outright on `denied` while still attaching the full result. So a request mixing a gated capability
|
|
40
|
+
* with one the session does not hold at all would otherwise prompt a human about the gated one and then be
|
|
41
|
+
* refused anyway for the other.
|
|
42
|
+
*
|
|
43
|
+
* That is not merely noise. A *session* yes is recorded and republished to children, and an *always* yes is
|
|
44
|
+
* written to disk for 30 days — both banked against a spawn that never happened, and both reachable by a
|
|
45
|
+
* model that appends one unheld capability to an otherwise ordinary request. A person is therefore only
|
|
46
|
+
* asked about a spawn that would actually proceed if they said yes.
|
|
47
|
+
*
|
|
48
|
+
* `clipped` is deliberately NOT a bar: it does not refuse a spawn, it just drops those capabilities from
|
|
49
|
+
* the child's grant, so the spawn does proceed and the question is real.
|
|
50
|
+
*/
|
|
51
|
+
export function shouldSeekApproval(result) {
|
|
52
|
+
if (!result)
|
|
53
|
+
return false;
|
|
54
|
+
if (result.denied.length > 0)
|
|
55
|
+
return false;
|
|
56
|
+
// ADR-0011. A grant retaining a universal capability is refused by `assertNarrowing` no matter what
|
|
57
|
+
// a human says, so asking is worse than useless: the dialog cannot change the outcome, and a
|
|
58
|
+
// `session`- or `always`-scoped yes given there is banked and reused for later spawns that DO
|
|
59
|
+
// proceed. Same harm the `denied` guard above exists to prevent.
|
|
60
|
+
if (result.universal.length > 0)
|
|
61
|
+
return false;
|
|
62
|
+
return result.gatedBlocked.length > 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Scopes a given call site may offer. `always` requires a **human-authored subject**.
|
|
66
|
+
*
|
|
67
|
+
* ADR-0019. The rule is unchanged; which paths satisfy it is not. `"interceptor"` used to be the only one
|
|
68
|
+
* and ADR-0016 deleted it, leaving `always` offerable from nowhere — 220 lines of persistence no live path
|
|
69
|
+
* could write to. `"definition"` is `delegate({agent})`, where ADR-0017 makes the subject an
|
|
70
|
+
* operator-authored file the session must hold `agent:<name>` to name at all. `"delegate"` is the `tools:`
|
|
71
|
+
* form, where the original reasoning stands untouched: the only things naming that child are the task and
|
|
72
|
+
* the tool list, both model-chosen, and a key the model controls is not a key.
|
|
73
|
+
*/
|
|
74
|
+
export function offeredScopes(path) {
|
|
75
|
+
return path === "definition" ? ["once", "session", "always"] : ["once", "session"];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What a child may inherit, as `capability@subject` keys.
|
|
79
|
+
*
|
|
80
|
+
* ADR-0014 changed this in two ways, and both were cases of a human's explicit choice being discarded
|
|
81
|
+
* one hop down:
|
|
82
|
+
*
|
|
83
|
+
* - **`once` is dropped** (A-S1). The scope chosen was not carried, so a `once` approval was written
|
|
84
|
+
* into the child's `PI_GRANTS_APPROVED` and republished onward — the most conservative answer a human
|
|
85
|
+
* can give produced the least conservative outcome, across an entire descendant subtree.
|
|
86
|
+
* - **The subject is kept** (A-S6). Bare capabilities were published, so a `<delegate>`-subject approval
|
|
87
|
+
* matched *any* subject below. `approvalKey`'s own doc argues at length that a model-controlled name
|
|
88
|
+
* is not a key; erasing the subject made that argument moot exactly where it mattered.
|
|
89
|
+
*
|
|
90
|
+
* The clamp to `grant` is unchanged and still load-bearing: **approval cannot conjure a capability**, so
|
|
91
|
+
* an inherited yes is only ever honoured for something the child independently holds.
|
|
92
|
+
*/
|
|
93
|
+
export function inheritApprovals(approved, grant) {
|
|
94
|
+
const held = new Set(grant);
|
|
95
|
+
return [
|
|
96
|
+
...new Set(approved
|
|
97
|
+
.filter((a) => a.scope !== "once" && a.capability !== WILDCARD && held.has(a.capability))
|
|
98
|
+
// A definition subject MUST carry a pin to cross a boundary (ADR-0022, hardened after F1).
|
|
99
|
+
//
|
|
100
|
+
// `verifyInherited` honours an unpinned entry by decision — `<delegate>` names no file and a
|
|
101
|
+
// pre-0.11 parent sends none — so an unpinned entry for a subject that *does* name a file is an
|
|
102
|
+
// approval exempt from the digest check. Enforcing it at the point of PUBLICATION rather than
|
|
103
|
+
// trusting each caller to attach a digest is what makes it structural: two call sites build these
|
|
104
|
+
// entries, one of them forgot, and every fresh approval crossed unpinned as a result. A caller that
|
|
105
|
+
// cannot produce a digest (the definition is not on disk here) now publishes nothing, which is the
|
|
106
|
+
// fail-closed direction.
|
|
107
|
+
.filter((a) => a.subject === DELEGATE_SUBJECT || Boolean(a.bodySha256))
|
|
108
|
+
.map((a) => approvalKey(a.capability, a.subject) + (a.bodySha256 ? `#${a.bodySha256}` : ""))),
|
|
109
|
+
].sort();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Read the inherited set back on the child side.
|
|
113
|
+
*
|
|
114
|
+
* Anything that is not a well-formed `capability@subject` pair is dropped rather than guessed at. An
|
|
115
|
+
* unparseable entry granting nothing is a missing prompt; an unparseable entry granting *something* is a
|
|
116
|
+
* silent escalation, so the direction of the failure is not a matter of taste.
|
|
117
|
+
*/
|
|
118
|
+
export function parseInherited(raw) {
|
|
119
|
+
const out = new Map();
|
|
120
|
+
for (const item of (raw ?? "").split(",")) {
|
|
121
|
+
const trimmed = item.trim();
|
|
122
|
+
// ADR-0022 appends `#<sha256>`. Split it off FIRST: a `#` cannot appear in a capability id or in a
|
|
123
|
+
// definition name (identity comes from a path component), so this is unambiguous.
|
|
124
|
+
const hash = trimmed.indexOf("#");
|
|
125
|
+
const key = hash === -1 ? trimmed : trimmed.slice(0, hash);
|
|
126
|
+
const digest = hash === -1 ? undefined : trimmed.slice(hash + 1);
|
|
127
|
+
const at = key.indexOf("@");
|
|
128
|
+
if (at <= 0 || at === key.length - 1)
|
|
129
|
+
continue;
|
|
130
|
+
if (hash !== -1 && !digest)
|
|
131
|
+
continue; // `key#` promises a pin and carries none — drop it, do not guess
|
|
132
|
+
out.set(key, digest);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Drop inherited approvals that were given for different instructions (ADR-0022).
|
|
138
|
+
*
|
|
139
|
+
* The hole this closes: `resolveApprovals` checks `inherited` FIRST and none of it had ever been through
|
|
140
|
+
* `entryVerdict`, so ADR-0019's headline property — an approval is void once the instructions change — held
|
|
141
|
+
* on the one path that persists and neither of the two that do not. A child is a fresh process: it re-reads
|
|
142
|
+
* the definition from disk, so a `git pull` between the parent's approval and the child's spawn meant the
|
|
143
|
+
* child ran a rewritten body under a yes given about the old one, recording `approvalSource: "inherited"`.
|
|
144
|
+
*
|
|
145
|
+
* An entry with **no** digest is honoured, and that is not a hole: `<delegate>` legitimately has none, and
|
|
146
|
+
* neither does a pre-0.11 parent. It is the same trade `entryVerdict` refuses to make for a *persisted*
|
|
147
|
+
* entry — but that entry is 30 days old and read off disk, whereas this one was handed over by a live parent
|
|
148
|
+
* process in the same tree, which is a much shorter chain to trust.
|
|
149
|
+
*/
|
|
150
|
+
export function verifyInherited(parsed, snapshotOf) {
|
|
151
|
+
const out = new Set();
|
|
152
|
+
for (const [key, digest] of parsed) {
|
|
153
|
+
if (digest === undefined) {
|
|
154
|
+
out.add(key);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const current = snapshotOf(key.slice(key.indexOf("@") + 1));
|
|
158
|
+
if (current && current.bodySha256 === digest)
|
|
159
|
+
out.add(key);
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/** When an approval granted now stops being valid. Computed once at write time and stored, so an entry's
|
|
164
|
+
* lifetime is visible in the file rather than implied by whichever version of the code reads it. */
|
|
165
|
+
export function expiryFor(approvedAt) {
|
|
166
|
+
return new Date(approvedAt.getTime() + APPROVAL_TTL_DAYS * DAY_MS).toISOString();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Decide whether a persisted approval still means what the human meant.
|
|
170
|
+
*
|
|
171
|
+
* Four ways it can stop meaning that, and each is reported distinctly so `/grants approvals` can explain
|
|
172
|
+
* itself rather than silently showing fewer rows:
|
|
173
|
+
*
|
|
174
|
+
* - `foreign-cwd` — the file was copied or committed and opened somewhere else. Nobody in THIS checkout
|
|
175
|
+
* was asked, so it authorises nothing here (R-27).
|
|
176
|
+
* - `expired` — a gate opened during one project must not still be open next quarter.
|
|
177
|
+
* - `type-changed` — the confused deputy. The key names a file whose contents can change after approval:
|
|
178
|
+
* approve `tool:write@docs-writer` when it declares `read, write`, and later that file
|
|
179
|
+
* gains `bash`. The entry would still match the key while describing something the
|
|
180
|
+
* human never saw.
|
|
181
|
+
* - `instructions-changed` — the same confused deputy, one level deeper. The tools are untouched but the
|
|
182
|
+
* BODY was rewritten, so the child would now be told to do something the human never
|
|
183
|
+
* saw. Only checkable since ADR-0018 gave the body a digest; an entry carrying no body
|
|
184
|
+
* pin lands here too, because unverifiable is not the same as unchanged.
|
|
185
|
+
* - `type-missing` — the type was deleted or renamed; a new file could later claim the same name.
|
|
186
|
+
*/
|
|
187
|
+
export function entryVerdict(input) {
|
|
188
|
+
if (input.entry.cwd !== input.cwd)
|
|
189
|
+
return "foreign-cwd";
|
|
190
|
+
const expiresAt = new Date(input.entry.expiresAt).getTime();
|
|
191
|
+
// NaN <= x is false in JS, so an unparseable date would otherwise fail OPEN. A cache we cannot
|
|
192
|
+
// read the expiry of is a cache we do not trust — treat it as expired, per the spec's rule that a
|
|
193
|
+
// broken cache grants nothing.
|
|
194
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= input.now.getTime())
|
|
195
|
+
return "expired";
|
|
196
|
+
if (input.current === null)
|
|
197
|
+
return "type-missing";
|
|
198
|
+
// Compare as sorted lists: reformatting or reordering a `tools:` line is not a change; adding,
|
|
199
|
+
// removing, or renaming a capability is.
|
|
200
|
+
const approved = [...input.entry.grantAtApproval].sort().join(",");
|
|
201
|
+
const current = [...input.current.ceiling].sort().join(",");
|
|
202
|
+
if (approved !== current)
|
|
203
|
+
return "type-changed";
|
|
204
|
+
// Fail closed on an unpinned entry: it was written before bodies were digested, so "unchanged" is not
|
|
205
|
+
// something this code can assert about it. One re-approval is the honest cost.
|
|
206
|
+
if (input.entry.bodyAtApproval !== input.current.bodySha256)
|
|
207
|
+
return "instructions-changed";
|
|
208
|
+
return "valid";
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Satisfy as much of `gated` as possible without asking anyone.
|
|
212
|
+
*
|
|
213
|
+
* Precedence is inherited -> session -> persisted -> prompt. Order matters only for what gets REPORTED
|
|
214
|
+
* (the ledger's `approvalSource`); any hit satisfies equally. Checking all three before prompting is what
|
|
215
|
+
* stops an orchestrator's tenth delegation from raising a tenth identical dialog.
|
|
216
|
+
*/
|
|
217
|
+
export function resolveApprovals(input) {
|
|
218
|
+
// ADR-0014: these are `capability@subject` keys now, not bare capabilities, so an approval given for
|
|
219
|
+
// one subject can no longer satisfy another.
|
|
220
|
+
const inherited = input.inherited ?? new Set();
|
|
221
|
+
const approved = [];
|
|
222
|
+
const needsPrompt = [];
|
|
223
|
+
const sources = {};
|
|
224
|
+
for (const capability of [...new Set(input.gated)].sort()) {
|
|
225
|
+
const key = approvalKey(capability, input.subject);
|
|
226
|
+
if (inherited.has(key)) {
|
|
227
|
+
approved.push(capability);
|
|
228
|
+
sources[capability] = "inherited";
|
|
229
|
+
}
|
|
230
|
+
else if (input.sessionApprovals.has(key)) {
|
|
231
|
+
approved.push(capability);
|
|
232
|
+
sources[capability] = "session";
|
|
233
|
+
}
|
|
234
|
+
else if (input.persisted.has(key)) {
|
|
235
|
+
approved.push(capability);
|
|
236
|
+
sources[capability] = "persisted";
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
needsPrompt.push(capability);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { approved, needsPrompt, sources };
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability ids: their grammar, and the two authority predicates over them.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `delegate.ts` when ADR-0024 pushed that file past the 400-line ceiling and
|
|
5
|
+
* `test/file-size.test.ts` refused it — for the second time, and answered the same way as the first: split
|
|
6
|
+
* the file the way the failure message says rather than raise the cap on the guard.
|
|
7
|
+
*
|
|
8
|
+
* The seam is not arbitrary. Three modules outside `delegate.ts` already imported these
|
|
9
|
+
* (`extensions/session.ts` for `DELEGATE_CAPABILITY`, `extensions/delegation.ts` and
|
|
10
|
+
* `extensions/run-delegation.ts` for `maySpawnDefinition`), which is the evidence that they were a separate
|
|
11
|
+
* concern living in the wrong file: *what an id means and who may use it* is a different question from
|
|
12
|
+
* *what one delegation does*.
|
|
13
|
+
*/
|
|
14
|
+
import { type Capability } from "./resolve.ts";
|
|
15
|
+
/** The capability that authorises spawning a definition (ADR-0017). `tool:*` satisfies any of them. */
|
|
16
|
+
export declare const agentCapability: (name: string) => Capability;
|
|
17
|
+
/**
|
|
18
|
+
* May this grant spawn that definition? (ADR-0017.)
|
|
19
|
+
*
|
|
20
|
+
* `resolve()` is exact-match plus subsumption and has no wildcard rule — a wildcard session works only
|
|
21
|
+
* because `deriveOwnGrant` *enumerates* its observed tools alongside `tool:*`. Definitions are not tools,
|
|
22
|
+
* so nothing enumerates them, and the wildcard has to be honoured here explicitly. Without that an
|
|
23
|
+
* UNGOVERNED session would stop being able to spawn, and "governance is opt-in" is the one rule this
|
|
24
|
+
* package must never break by accident.
|
|
25
|
+
*/
|
|
26
|
+
export declare function maySpawnDefinition(ownGrant: Capability[], name: string): boolean;
|
|
27
|
+
/** The tool name that confers the ability to delegate further. */
|
|
28
|
+
export declare const DELEGATE_CAPABILITY: Capability;
|
|
29
|
+
/** Accept `read` or `tool:read` or `ext:pkg/tool` and normalise to a capability id. */
|
|
30
|
+
export declare function normaliseCapability(raw: string): Capability;
|
|
31
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D,uGAAuG;AACvG,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,UAA6B,CAAC;AAE7E;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAShF;AAED,kEAAkE;AAClE,eAAO,MAAM,mBAAmB,EAAE,UAA4B,CAAC;AAE/D,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAM3D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability ids: their grammar, and the two authority predicates over them.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `delegate.ts` when ADR-0024 pushed that file past the 400-line ceiling and
|
|
5
|
+
* `test/file-size.test.ts` refused it — for the second time, and answered the same way as the first: split
|
|
6
|
+
* the file the way the failure message says rather than raise the cap on the guard.
|
|
7
|
+
*
|
|
8
|
+
* The seam is not arbitrary. Three modules outside `delegate.ts` already imported these
|
|
9
|
+
* (`extensions/session.ts` for `DELEGATE_CAPABILITY`, `extensions/delegation.ts` and
|
|
10
|
+
* `extensions/run-delegation.ts` for `maySpawnDefinition`), which is the evidence that they were a separate
|
|
11
|
+
* concern living in the wrong file: *what an id means and who may use it* is a different question from
|
|
12
|
+
* *what one delegation does*.
|
|
13
|
+
*/
|
|
14
|
+
import { AGENT_WILDCARD } from "./resolve.js";
|
|
15
|
+
import { WILDCARD } from "./pi-tools.js";
|
|
16
|
+
/** The capability that authorises spawning a definition (ADR-0017). `tool:*` satisfies any of them. */
|
|
17
|
+
export const agentCapability = (name) => `agent:${name}`;
|
|
18
|
+
/**
|
|
19
|
+
* May this grant spawn that definition? (ADR-0017.)
|
|
20
|
+
*
|
|
21
|
+
* `resolve()` is exact-match plus subsumption and has no wildcard rule — a wildcard session works only
|
|
22
|
+
* because `deriveOwnGrant` *enumerates* its observed tools alongside `tool:*`. Definitions are not tools,
|
|
23
|
+
* so nothing enumerates them, and the wildcard has to be honoured here explicitly. Without that an
|
|
24
|
+
* UNGOVERNED session would stop being able to spawn, and "governance is opt-in" is the one rule this
|
|
25
|
+
* package must never break by accident.
|
|
26
|
+
*/
|
|
27
|
+
export function maySpawnDefinition(ownGrant, name) {
|
|
28
|
+
// ADR-0023 adds the middle case. `tool:*` is authority to grant every tool and satisfies this too;
|
|
29
|
+
// `agent:*` is authority to spawn any definition and grants no tools at all, which is the configuration
|
|
30
|
+
// an operator wanting "any of our definitions, narrow tools" previously had to fake with `tool:*`.
|
|
31
|
+
return (ownGrant.includes(WILDCARD) ||
|
|
32
|
+
ownGrant.includes(AGENT_WILDCARD) ||
|
|
33
|
+
ownGrant.includes(agentCapability(name)));
|
|
34
|
+
}
|
|
35
|
+
/** The tool name that confers the ability to delegate further. */
|
|
36
|
+
export const DELEGATE_CAPABILITY = "tool:delegate";
|
|
37
|
+
/** Accept `read` or `tool:read` or `ext:pkg/tool` and normalise to a capability id. */
|
|
38
|
+
export function normaliseCapability(raw) {
|
|
39
|
+
const value = raw.trim();
|
|
40
|
+
if (value.startsWith("tool:") || value.startsWith("ext:") || value.startsWith("skill:") || value.startsWith("agent:")) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
return `tool:${value}`;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,cAAc,EAAmB,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,uGAAuG;AACvG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAc,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC;AAE7E;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAsB,EAAE,IAAY;IACrE,mGAAmG;IACnG,wGAAwG;IACxG,mGAAmG;IACnG,OAAO,CACL,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC3B,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC;QACjC,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CACzC,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,MAAM,mBAAmB,GAAe,eAAe,CAAC;AAE/D,uFAAuF;AACvF,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtH,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,KAAK,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live capability catalog — what *can* be granted, enumerated at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Until now grants were checked against agent-type files alone, which covers `tool:` capabilities and
|
|
5
|
+
* nothing else. That leaves two gaps: extension tools are invisible (so `ext:` grants cannot be
|
|
6
|
+
* validated), and **skills were ungovernable** despite "skills and tools" being half the requirement.
|
|
7
|
+
*
|
|
8
|
+
* Sources, and why each is trusted:
|
|
9
|
+
* - **tools** — the `tools` array of a live provider request. Authoritative: it is exactly what pi sent
|
|
10
|
+
* the model, so it includes extension-registered tools and reflects any `--tools` allowlist already in
|
|
11
|
+
* force. Nothing else can see the real surface.
|
|
12
|
+
* - **skills** — `SKILL.md` directories and top-level `.md` files under pi's skill roots.
|
|
13
|
+
* - **definitions** — spawnable `SKILL.md` agents (ADR-0016), as `agent:<name>`.
|
|
14
|
+
*
|
|
15
|
+
* Provenance note: a provider payload gives tool NAMES, not owning packages, so extension tools cannot be
|
|
16
|
+
* qualified as `ext:<pkg>/<tool>` from that source alone. They are catalogued as `tool:<name>` — which is
|
|
17
|
+
* also how pi's `--tools` matches — and marked `kind: "extension"` for display. `ext:` ids remain
|
|
18
|
+
* supported for hand-authored grants; `deriveOwnGrant` already matches them by bare name.
|
|
19
|
+
*/
|
|
20
|
+
import { type SkillDefinition } from "./definitions.ts";
|
|
21
|
+
import { type Capability } from "./resolve.ts";
|
|
22
|
+
export type CapabilityKind = "builtin" | "extension" | "skill" | "agentType";
|
|
23
|
+
export interface CatalogEntry {
|
|
24
|
+
capability: Capability;
|
|
25
|
+
kind: CapabilityKind;
|
|
26
|
+
/** Where it was found, for display and debugging. */
|
|
27
|
+
source?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Catalog {
|
|
30
|
+
entries: CatalogEntry[];
|
|
31
|
+
/** Every capability id in the catalog. */
|
|
32
|
+
all: Capability[];
|
|
33
|
+
byKind(kind: CapabilityKind): Capability[];
|
|
34
|
+
has(capability: Capability): boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Split observed tool names into pi built-ins and extension-provided tools. */
|
|
37
|
+
export declare function classifyToolNames(observed: string[]): CatalogEntry[];
|
|
38
|
+
/** Skill roots pi discovers, project first. */
|
|
39
|
+
export declare function skillDirs(cwd: string): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Discover skills: a directory containing `SKILL.md` is one skill named after the directory; a top-level
|
|
42
|
+
* `.md` file is a skill named after the file. Mirrors pi's documented convention.
|
|
43
|
+
*
|
|
44
|
+
* Directories are not descended into beyond one level, matching pi's rule that a directory containing
|
|
45
|
+
* `SKILL.md` is a single skill rather than a tree to explore.
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadSkills(cwd: string): Promise<CatalogEntry[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Spawnable definitions, as `agent:<name>` capabilities.
|
|
50
|
+
*
|
|
51
|
+
* A definition is BOTH a skill (loadable into a session) and an agent (spawnable as a child) — ADR-0016
|
|
52
|
+
* collapsed those into one file — so the same `SKILL.md` legitimately appears twice in the catalog under
|
|
53
|
+
* two capability ids. That is not duplication: `skill:review` means "may load these instructions" and
|
|
54
|
+
* `agent:review` means "may spawn a child running them", and a grant can hold either without the other.
|
|
55
|
+
*/
|
|
56
|
+
export declare function definitionEntries(definitions: Map<string, SkillDefinition>): CatalogEntry[];
|
|
57
|
+
/** Assemble a catalog from parts. Pure, so it is testable without a filesystem. */
|
|
58
|
+
export declare function makeCatalog(entries: CatalogEntry[]): Catalog;
|
|
59
|
+
/** Build the live catalog. `observedTools` comes from a provider payload; null when not yet seen. */
|
|
60
|
+
export declare function buildCatalog(input: {
|
|
61
|
+
cwd: string;
|
|
62
|
+
observedTools: string[] | null;
|
|
63
|
+
}): Promise<Catalog>;
|
|
64
|
+
/**
|
|
65
|
+
* Capabilities requested that the catalog does not contain.
|
|
66
|
+
*
|
|
67
|
+
* Reported separately from `denied` because the causes differ and so do the fixes: `denied` means the
|
|
68
|
+
* delegator lacks authority, `unknown` means the capability does not exist here — usually a typo or a
|
|
69
|
+
* stale grant referring to an uninstalled package. Silently treating unknown as denied hides that.
|
|
70
|
+
*/
|
|
71
|
+
export declare function unknownCapabilities(requested: Capability[], catalog: Catalog): Capability[];
|
|
72
|
+
/**
|
|
73
|
+
* Skill name -> absolute path, for `planSpawn`'s `--skill` flags (R-32).
|
|
74
|
+
*
|
|
75
|
+
* Derived from the catalog's own `source` field rather than re-scanning, so what a child is handed
|
|
76
|
+
* cannot drift from what was discovered and offered. A skill entry without a source is omitted, which
|
|
77
|
+
* makes it *unresolvable* rather than silently absent — `planDelegation` refuses on that, because a
|
|
78
|
+
* grant naming a skill the child never receives is a ledger line that lies.
|
|
79
|
+
*/
|
|
80
|
+
export declare function skillPathsFromCatalog(catalog: Catalog): Record<string, string>;
|
|
81
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEzE,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,0CAA0C;IAC1C,GAAG,EAAE,UAAU,EAAE,CAAC;IAClB,MAAM,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,EAAE,CAAC;IAC3C,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;CACtC;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAMpE;AAED,+CAA+C;AAC/C,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA+BrE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,YAAY,EAAE,CAM3F;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAY5D;AAED,qGAAqG;AACrG,wBAAsB,YAAY,CAAC,KAAK,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAChC,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBnB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,EAAE,CAQ3F;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO9E"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live capability catalog — what *can* be granted, enumerated at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Until now grants were checked against agent-type files alone, which covers `tool:` capabilities and
|
|
5
|
+
* nothing else. That leaves two gaps: extension tools are invisible (so `ext:` grants cannot be
|
|
6
|
+
* validated), and **skills were ungovernable** despite "skills and tools" being half the requirement.
|
|
7
|
+
*
|
|
8
|
+
* Sources, and why each is trusted:
|
|
9
|
+
* - **tools** — the `tools` array of a live provider request. Authoritative: it is exactly what pi sent
|
|
10
|
+
* the model, so it includes extension-registered tools and reflects any `--tools` allowlist already in
|
|
11
|
+
* force. Nothing else can see the real surface.
|
|
12
|
+
* - **skills** — `SKILL.md` directories and top-level `.md` files under pi's skill roots.
|
|
13
|
+
* - **definitions** — spawnable `SKILL.md` agents (ADR-0016), as `agent:<name>`.
|
|
14
|
+
*
|
|
15
|
+
* Provenance note: a provider payload gives tool NAMES, not owning packages, so extension tools cannot be
|
|
16
|
+
* qualified as `ext:<pkg>/<tool>` from that source alone. They are catalogued as `tool:<name>` — which is
|
|
17
|
+
* also how pi's `--tools` matches — and marked `kind: "extension"` for display. `ext:` ids remain
|
|
18
|
+
* supported for hand-authored grants; `deriveOwnGrant` already matches them by bare name.
|
|
19
|
+
*/
|
|
20
|
+
import { readdir, stat } from "node:fs/promises";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { loadDefinitions } from "./definitions.js";
|
|
24
|
+
import { PI_BUILTIN_TOOLS, WILDCARD } from "./pi-tools.js";
|
|
25
|
+
import { AGENT_WILDCARD } from "./resolve.js";
|
|
26
|
+
/** Split observed tool names into pi built-ins and extension-provided tools. */
|
|
27
|
+
export function classifyToolNames(observed) {
|
|
28
|
+
const builtins = new Set(PI_BUILTIN_TOOLS);
|
|
29
|
+
return [...new Set(observed)].sort().map((name) => ({
|
|
30
|
+
capability: `tool:${name}`,
|
|
31
|
+
kind: builtins.has(name) ? "builtin" : "extension",
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
/** Skill roots pi discovers, project first. */
|
|
35
|
+
export function skillDirs(cwd) {
|
|
36
|
+
return [join(cwd, ".pi", "skills"), join(homedir(), ".pi", "agent", "skills")];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Discover skills: a directory containing `SKILL.md` is one skill named after the directory; a top-level
|
|
40
|
+
* `.md` file is a skill named after the file. Mirrors pi's documented convention.
|
|
41
|
+
*
|
|
42
|
+
* Directories are not descended into beyond one level, matching pi's rule that a directory containing
|
|
43
|
+
* `SKILL.md` is a single skill rather than a tree to explore.
|
|
44
|
+
*/
|
|
45
|
+
export async function loadSkills(cwd) {
|
|
46
|
+
const found = new Map();
|
|
47
|
+
for (const dir of skillDirs(cwd)) {
|
|
48
|
+
let names;
|
|
49
|
+
try {
|
|
50
|
+
names = await readdir(dir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue; // absent skill root is normal
|
|
54
|
+
}
|
|
55
|
+
for (const name of names.sort()) {
|
|
56
|
+
const path = join(dir, name);
|
|
57
|
+
try {
|
|
58
|
+
const info = await stat(path);
|
|
59
|
+
if (info.isDirectory()) {
|
|
60
|
+
const inner = await readdir(path).catch(() => []);
|
|
61
|
+
if (inner.includes("SKILL.md") && !found.has(name)) {
|
|
62
|
+
found.set(name, { capability: `skill:${name}`, kind: "skill", source: path });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (name.endsWith(".md")) {
|
|
66
|
+
const skill = name.replace(/\.md$/, "");
|
|
67
|
+
if (!found.has(skill)) {
|
|
68
|
+
found.set(skill, { capability: `skill:${skill}`, kind: "skill", source: path });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// An unreadable entry is simply not catalogued; it therefore cannot be granted, which is the
|
|
74
|
+
// fail-closed direction.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [...found.values()];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Spawnable definitions, as `agent:<name>` capabilities.
|
|
82
|
+
*
|
|
83
|
+
* A definition is BOTH a skill (loadable into a session) and an agent (spawnable as a child) — ADR-0016
|
|
84
|
+
* collapsed those into one file — so the same `SKILL.md` legitimately appears twice in the catalog under
|
|
85
|
+
* two capability ids. That is not duplication: `skill:review` means "may load these instructions" and
|
|
86
|
+
* `agent:review` means "may spawn a child running them", and a grant can hold either without the other.
|
|
87
|
+
*/
|
|
88
|
+
export function definitionEntries(definitions) {
|
|
89
|
+
return [...definitions.values()].map((d) => ({
|
|
90
|
+
capability: `agent:${d.name}`,
|
|
91
|
+
kind: "agentType",
|
|
92
|
+
source: d.source,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** Assemble a catalog from parts. Pure, so it is testable without a filesystem. */
|
|
96
|
+
export function makeCatalog(entries) {
|
|
97
|
+
const deduped = new Map();
|
|
98
|
+
for (const entry of entries)
|
|
99
|
+
if (!deduped.has(entry.capability))
|
|
100
|
+
deduped.set(entry.capability, entry);
|
|
101
|
+
const list = [...deduped.values()].sort((a, b) => a.capability.localeCompare(b.capability));
|
|
102
|
+
const ids = list.map((e) => e.capability);
|
|
103
|
+
const idSet = new Set(ids);
|
|
104
|
+
return {
|
|
105
|
+
entries: list,
|
|
106
|
+
all: ids,
|
|
107
|
+
byKind: (kind) => list.filter((e) => e.kind === kind).map((e) => e.capability),
|
|
108
|
+
has: (capability) => idSet.has(capability),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Build the live catalog. `observedTools` comes from a provider payload; null when not yet seen. */
|
|
112
|
+
export async function buildCatalog(input) {
|
|
113
|
+
const [skills, definitions] = await Promise.all([loadSkills(input.cwd), loadDefinitions(input.cwd)]);
|
|
114
|
+
return makeCatalog([
|
|
115
|
+
// pi's built-ins are seeded unconditionally, because they are known statically and the catalog is
|
|
116
|
+
// consulted BEFORE any provider request has happened — `/grants` runs at that point. Without this,
|
|
117
|
+
// every capability looked "unknown" until the first model call, so the preview refused grants that
|
|
118
|
+
// enforcement would have allowed: R-28's failure shape (a diagnostic disagreeing with the enforcer)
|
|
119
|
+
// reappearing through a different door.
|
|
120
|
+
//
|
|
121
|
+
// The trade-off, stated plainly: in a session started with `--tools read`, this still lists `bash`
|
|
122
|
+
// as an existing capability, so a delegation naming it passes the *unknown* check and is refused by
|
|
123
|
+
// the *grant* check instead ("this session does not hold it"). That is the better error anyway, and
|
|
124
|
+
// the grant check — not this catalog — is the authority. Nothing here grants anything.
|
|
125
|
+
...PI_BUILTIN_TOOLS.map((name) => ({ capability: `tool:${name}`, kind: "builtin" })),
|
|
126
|
+
...(input.observedTools ? classifyToolNames(input.observedTools) : []),
|
|
127
|
+
...skills,
|
|
128
|
+
...definitionEntries(definitions),
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Capabilities requested that the catalog does not contain.
|
|
133
|
+
*
|
|
134
|
+
* Reported separately from `denied` because the causes differ and so do the fixes: `denied` means the
|
|
135
|
+
* delegator lacks authority, `unknown` means the capability does not exist here — usually a typo or a
|
|
136
|
+
* stale grant referring to an uninstalled package. Silently treating unknown as denied hides that.
|
|
137
|
+
*/
|
|
138
|
+
export function unknownCapabilities(requested, catalog) {
|
|
139
|
+
// Wildcards are GRAMMAR, not entries. Nothing enumerates them into the catalog — `definitionEntries`
|
|
140
|
+
// emits `agent:<name>` per discovered definition and `PI_BUILTIN_TOOLS` contains no `*` — so this check
|
|
141
|
+
// reported `agent:*` as *"not present in this session's catalog (typo, or an uninstalled package?)"* and
|
|
142
|
+
// refused it BEFORE `resolve` could apply ADR-0023's rule. That made the ADR's "a parent holding
|
|
143
|
+
// `agent:*` may hand down `agent:*`" false, and made a definition declaring `allowed-tools: agent:*`
|
|
144
|
+
// unspawnable from any grant. The wildcard is live only at the root without this.
|
|
145
|
+
return requested.filter((c) => c !== WILDCARD && c !== AGENT_WILDCARD && !catalog.has(c)).sort();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Skill name -> absolute path, for `planSpawn`'s `--skill` flags (R-32).
|
|
149
|
+
*
|
|
150
|
+
* Derived from the catalog's own `source` field rather than re-scanning, so what a child is handed
|
|
151
|
+
* cannot drift from what was discovered and offered. A skill entry without a source is omitted, which
|
|
152
|
+
* makes it *unresolvable* rather than silently absent — `planDelegation` refuses on that, because a
|
|
153
|
+
* grant naming a skill the child never receives is a ledger line that lies.
|
|
154
|
+
*/
|
|
155
|
+
export function skillPathsFromCatalog(catalog) {
|
|
156
|
+
const paths = {};
|
|
157
|
+
for (const entry of catalog.entries) {
|
|
158
|
+
if (entry.kind !== "skill" || !entry.source)
|
|
159
|
+
continue;
|
|
160
|
+
paths[entry.capability.slice("skill:".length)] = entry.source;
|
|
161
|
+
}
|
|
162
|
+
return paths;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
|