@sema-agent/core 7.35.2 → 7.36.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 +61 -0
- package/dist/brain/anthropic.js +6 -6
- package/dist/brain/errors.d.ts +1 -14
- package/dist/brain/errors.js +2 -6
- package/dist/brain/open-responses.js +7 -7
- package/dist/brain/openai.js +7 -7
- package/dist/brain/provider-echo-shape.d.ts +26 -0
- package/dist/brain/provider-echo-shape.js +12 -0
- package/dist/brain/tool-call-id.d.ts +27 -15
- package/dist/brain/tool-call-id.js +43 -0
- package/dist/core/ask-question.d.ts +0 -30
- package/dist/core/ask-question.js +3 -2
- package/dist/core/checkpoint-store.d.ts +5 -3
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/context-edit.d.ts +4 -0
- package/dist/core/context-edit.js +5 -2
- package/dist/core/engine-notice.d.ts +7 -0
- package/dist/core/gate-lanes.js +12 -10
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/hooks.js +4 -2
- package/dist/core/mode-policy.js +2 -1
- package/dist/core/permission-rule-model.d.ts +2 -2
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rules.js +2 -5
- package/dist/core/runner/active-skill-scope.js +5 -5
- package/dist/core/runner/decide-continuation.js +2 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
- package/dist/core/runner/permission-rule-lanes.js +7 -6
- package/dist/core/runner/prepare-context-lane.d.ts +4 -0
- package/dist/core/runner/prepare-context-lane.js +7 -5
- package/dist/core/runner/prepare-gate-stations.js +17 -12
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +7 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
- package/dist/core/runner/prepare-policy-chain.js +10 -9
- package/dist/core/runner/prepare-safety-scan.js +9 -2
- package/dist/core/runner/prepare-task.js +3 -3
- package/dist/core/runner/resume-apply.js +2 -1
- package/dist/core/runner/run-attachment-seats.js +3 -1
- package/dist/core/runner/run-harness-handlers.js +6 -1
- package/dist/core/runner/run-leg.js +11 -9
- package/dist/core/runner/run-recovery-lanes.js +2 -0
- package/dist/core/runner/session-rule-policy.js +5 -4
- package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
- package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
- package/dist/core/sensitive-path-policy.js +2 -2
- package/dist/core/side-query.d.ts +5 -1
- package/dist/core/side-query.js +5 -1
- package/dist/core/tool-name-readings.d.ts +366 -0
- package/dist/core/tool-name-readings.js +136 -0
- package/dist/core/tool-policy.d.ts +5 -5
- package/dist/core/tool-policy.js +15 -13
- package/dist/core/tool-registry.d.ts +1 -31
- package/dist/core/tool-registry.js +2 -22
- package/dist/core/tool-result-budget.d.ts +3 -0
- package/dist/core/tool-result-budget.js +5 -3
- package/dist/core/tool-roster.d.ts +30 -9
- package/dist/core/tool-roster.js +45 -5
- package/dist/engine/llm/types.d.ts +4 -0
- package/dist/tools/fs/file-read-check.d.ts +1 -1
- package/dist/tools/fs/file-read-check.js +4 -3
- package/dist/tools/fs/shell-read-check.d.ts +15 -1
- package/dist/tools/fs/shell-read-check.js +5 -2
- package/package.json +2 -1
- package/wire-changes/core@7.36.0.json +288 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE table of how every per-call site keyed on an engine tool name reads a call's names, and the read ports a
|
|
3
|
+
* site must go through. A call answers to several names once a caller tool declares aliases (the model's spelling,
|
|
4
|
+
* the wire name the gate resolves it to, the other aliases that resolve to the same row); which of them a site reads
|
|
5
|
+
* is a property of the SITE, declared here once:
|
|
6
|
+
*
|
|
7
|
+
* · `any` — a TIGHTEN keyed on an engine name set, or a rule name a user wrote: the call is held under EVERY name it
|
|
8
|
+
* answers to ({@link heldBy}); a set-valued table is read as the union of its entries ({@link heldValues}).
|
|
9
|
+
* · `seat` — a multi-key seat keyed on engine names: ONE name is chosen ({@link seatOf}) and every key of the seat is
|
|
10
|
+
* read under it. `tier` = the name whose irreversibility tier is strictest (`never < maybe < always`, an
|
|
11
|
+
* absent tier lowest, ties to the earlier name of the call); `member` = the first name the table holds.
|
|
12
|
+
* · `wire` — a GRANT or a ROUTING keyed on an engine name: the call's wire name only ({@link wireOf}).
|
|
13
|
+
* · `spelling` — a record of what the model said (stream frames, transcripts, counters): the harness event's own name;
|
|
14
|
+
* no port — listed so the table is complete.
|
|
15
|
+
*
|
|
16
|
+
* A site passes its {@link SiteId}; the port's parameter type admits only the ids of its kind, so moving a row to another
|
|
17
|
+
* kind fails to compile at the site. `gate:name-readings` holds the rest: a name-keyed table read outside a port, a call's
|
|
18
|
+
* name compared with an engine name outside {@link wireOf}, and a site id spelled at more than one place.
|
|
19
|
+
*
|
|
20
|
+
* Layer-0 vocabulary: depends on nothing but the tool declaration's tier type and, type-only, the mark-source word
|
|
21
|
+
* (`ToolMarkSource`); nothing here runs at import time.
|
|
22
|
+
*/
|
|
23
|
+
import type { ToolSpec } from "./tool-spec.js";
|
|
24
|
+
export type NameReading = {
|
|
25
|
+
readonly kind: "any";
|
|
26
|
+
readonly file: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "wire";
|
|
29
|
+
readonly file: string;
|
|
30
|
+
} | {
|
|
31
|
+
readonly kind: "spelling";
|
|
32
|
+
readonly file: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: "seat";
|
|
35
|
+
readonly rank: "tier" | "member";
|
|
36
|
+
readonly file: string;
|
|
37
|
+
};
|
|
38
|
+
export declare const NAME_READINGS: {
|
|
39
|
+
readonly "gate.compliance": {
|
|
40
|
+
readonly kind: "any";
|
|
41
|
+
readonly file: string;
|
|
42
|
+
};
|
|
43
|
+
readonly "gate.egress": {
|
|
44
|
+
readonly kind: "any";
|
|
45
|
+
readonly file: string;
|
|
46
|
+
};
|
|
47
|
+
readonly "gate.peerMessage": {
|
|
48
|
+
readonly kind: "any";
|
|
49
|
+
readonly file: string;
|
|
50
|
+
};
|
|
51
|
+
readonly "gate.markSources": {
|
|
52
|
+
readonly kind: "any";
|
|
53
|
+
readonly file: string;
|
|
54
|
+
};
|
|
55
|
+
readonly "gate.shellSeat": {
|
|
56
|
+
readonly kind: "seat";
|
|
57
|
+
readonly rank: "tier";
|
|
58
|
+
readonly file: string;
|
|
59
|
+
};
|
|
60
|
+
readonly "gate.sandboxBoundary.egress": {
|
|
61
|
+
readonly kind: "any";
|
|
62
|
+
readonly file: string;
|
|
63
|
+
};
|
|
64
|
+
readonly "gate.sandboxBoundary.hand": {
|
|
65
|
+
readonly kind: "wire";
|
|
66
|
+
readonly file: string;
|
|
67
|
+
};
|
|
68
|
+
readonly "gate.preToolUse": {
|
|
69
|
+
readonly kind: "wire";
|
|
70
|
+
readonly file: string;
|
|
71
|
+
};
|
|
72
|
+
readonly "fold.readStation": {
|
|
73
|
+
readonly kind: "seat";
|
|
74
|
+
readonly rank: "member";
|
|
75
|
+
readonly file: string;
|
|
76
|
+
};
|
|
77
|
+
readonly "lanes.contentQuestionClass": {
|
|
78
|
+
readonly kind: "any";
|
|
79
|
+
readonly file: string;
|
|
80
|
+
};
|
|
81
|
+
readonly "lanes.readOnlyShellAllow": {
|
|
82
|
+
readonly kind: "wire";
|
|
83
|
+
readonly file: string;
|
|
84
|
+
};
|
|
85
|
+
readonly "lanes.modeFileCommand": {
|
|
86
|
+
readonly kind: "wire";
|
|
87
|
+
readonly file: string;
|
|
88
|
+
};
|
|
89
|
+
readonly "lanes.contentAskRoute": {
|
|
90
|
+
readonly kind: "wire";
|
|
91
|
+
readonly file: string;
|
|
92
|
+
};
|
|
93
|
+
readonly "lanes.orgContentAskExemption": {
|
|
94
|
+
readonly kind: "wire";
|
|
95
|
+
readonly file: string;
|
|
96
|
+
};
|
|
97
|
+
readonly "lanes.persistedEditSkip": {
|
|
98
|
+
readonly kind: "wire";
|
|
99
|
+
readonly file: string;
|
|
100
|
+
};
|
|
101
|
+
readonly "lanes.persistedLaneSkip": {
|
|
102
|
+
readonly kind: "wire";
|
|
103
|
+
readonly file: string;
|
|
104
|
+
};
|
|
105
|
+
readonly "lanes.allowLayerQuestionSkip": {
|
|
106
|
+
readonly kind: "wire";
|
|
107
|
+
readonly file: string;
|
|
108
|
+
};
|
|
109
|
+
readonly "lanes.sandboxQuestionSkip": {
|
|
110
|
+
readonly kind: "wire";
|
|
111
|
+
readonly file: string;
|
|
112
|
+
};
|
|
113
|
+
readonly "policy.nameLists": {
|
|
114
|
+
readonly kind: "any";
|
|
115
|
+
readonly file: string;
|
|
116
|
+
};
|
|
117
|
+
readonly "policy.shellToolsList": {
|
|
118
|
+
readonly kind: "any";
|
|
119
|
+
readonly file: string;
|
|
120
|
+
};
|
|
121
|
+
readonly "policy.projectionReplay": {
|
|
122
|
+
readonly kind: "any";
|
|
123
|
+
readonly file: string;
|
|
124
|
+
};
|
|
125
|
+
readonly "policy.sessionRules": {
|
|
126
|
+
readonly kind: "any";
|
|
127
|
+
readonly file: string;
|
|
128
|
+
};
|
|
129
|
+
readonly "policy.sessionRules.effect": {
|
|
130
|
+
readonly kind: "wire";
|
|
131
|
+
readonly file: string;
|
|
132
|
+
};
|
|
133
|
+
readonly "policy.skillScope": {
|
|
134
|
+
readonly kind: "any";
|
|
135
|
+
readonly file: string;
|
|
136
|
+
};
|
|
137
|
+
readonly "policy.skillScope.effect": {
|
|
138
|
+
readonly kind: "wire";
|
|
139
|
+
readonly file: string;
|
|
140
|
+
};
|
|
141
|
+
readonly "policy.permissionRules": {
|
|
142
|
+
readonly kind: "any";
|
|
143
|
+
readonly file: string;
|
|
144
|
+
};
|
|
145
|
+
readonly "policy.persistedGrammar": {
|
|
146
|
+
readonly kind: "any";
|
|
147
|
+
readonly file: string;
|
|
148
|
+
};
|
|
149
|
+
readonly "policy.durableQuestion": {
|
|
150
|
+
readonly kind: "any";
|
|
151
|
+
readonly file: string;
|
|
152
|
+
};
|
|
153
|
+
readonly "policy.sensitivePathGuardedByName": {
|
|
154
|
+
readonly kind: "any";
|
|
155
|
+
readonly file: string;
|
|
156
|
+
};
|
|
157
|
+
readonly "floor.protectivePathTarget": {
|
|
158
|
+
readonly kind: "seat";
|
|
159
|
+
readonly rank: "member";
|
|
160
|
+
readonly file: string;
|
|
161
|
+
};
|
|
162
|
+
readonly "mode.planRosterRow": {
|
|
163
|
+
readonly kind: "wire";
|
|
164
|
+
readonly file: string;
|
|
165
|
+
};
|
|
166
|
+
readonly "offer.laneTool": {
|
|
167
|
+
readonly kind: "wire";
|
|
168
|
+
readonly file: string;
|
|
169
|
+
};
|
|
170
|
+
readonly "offer.orgQuestionExemption": {
|
|
171
|
+
readonly kind: "wire";
|
|
172
|
+
readonly file: string;
|
|
173
|
+
};
|
|
174
|
+
readonly "offer.marks": {
|
|
175
|
+
readonly kind: "seat";
|
|
176
|
+
readonly rank: "tier";
|
|
177
|
+
readonly file: string;
|
|
178
|
+
};
|
|
179
|
+
readonly "offer.marks.egress": {
|
|
180
|
+
readonly kind: "any";
|
|
181
|
+
readonly file: string;
|
|
182
|
+
};
|
|
183
|
+
readonly "park.riskDescriptor": {
|
|
184
|
+
readonly kind: "seat";
|
|
185
|
+
readonly rank: "tier";
|
|
186
|
+
readonly file: string;
|
|
187
|
+
};
|
|
188
|
+
readonly "park.syncFirstRoute": {
|
|
189
|
+
readonly kind: "wire";
|
|
190
|
+
readonly file: string;
|
|
191
|
+
};
|
|
192
|
+
readonly "report.riskAxes": {
|
|
193
|
+
readonly kind: "seat";
|
|
194
|
+
readonly rank: "tier";
|
|
195
|
+
readonly file: string;
|
|
196
|
+
};
|
|
197
|
+
readonly "report.riskAxes.egress": {
|
|
198
|
+
readonly kind: "any";
|
|
199
|
+
readonly file: string;
|
|
200
|
+
};
|
|
201
|
+
readonly "report.riskAxes.negatives": {
|
|
202
|
+
readonly kind: "seat";
|
|
203
|
+
readonly rank: "member";
|
|
204
|
+
readonly file: string;
|
|
205
|
+
};
|
|
206
|
+
readonly "verify.wroteThisRun": {
|
|
207
|
+
readonly kind: "wire";
|
|
208
|
+
readonly file: string;
|
|
209
|
+
};
|
|
210
|
+
readonly "verify.wroteThisRunSeed": {
|
|
211
|
+
readonly kind: "wire";
|
|
212
|
+
readonly file: string;
|
|
213
|
+
};
|
|
214
|
+
readonly "event.toolStartEnd": {
|
|
215
|
+
readonly kind: "spelling";
|
|
216
|
+
readonly file: string;
|
|
217
|
+
};
|
|
218
|
+
readonly "hook.postToolUse": {
|
|
219
|
+
readonly kind: "wire";
|
|
220
|
+
readonly file: string;
|
|
221
|
+
};
|
|
222
|
+
readonly "hook.postToolUseFailure": {
|
|
223
|
+
readonly kind: "wire";
|
|
224
|
+
readonly file: string;
|
|
225
|
+
};
|
|
226
|
+
readonly "hook.postToolBatch": {
|
|
227
|
+
readonly kind: "spelling";
|
|
228
|
+
readonly file: string;
|
|
229
|
+
};
|
|
230
|
+
readonly "stats.subagentToolCounter": {
|
|
231
|
+
readonly kind: "spelling";
|
|
232
|
+
readonly file: string;
|
|
233
|
+
};
|
|
234
|
+
readonly "activity.sink": {
|
|
235
|
+
readonly kind: "spelling";
|
|
236
|
+
readonly file: string;
|
|
237
|
+
};
|
|
238
|
+
readonly "reconcile.orphanEffect": {
|
|
239
|
+
readonly kind: "spelling";
|
|
240
|
+
readonly file: string;
|
|
241
|
+
};
|
|
242
|
+
readonly "inherited.questionFloat": {
|
|
243
|
+
readonly kind: "wire";
|
|
244
|
+
readonly file: string;
|
|
245
|
+
};
|
|
246
|
+
readonly "park.contentKindProjection": {
|
|
247
|
+
readonly kind: "wire";
|
|
248
|
+
readonly file: string;
|
|
249
|
+
};
|
|
250
|
+
readonly "resume.contentAskRow": {
|
|
251
|
+
readonly kind: "wire";
|
|
252
|
+
readonly file: string;
|
|
253
|
+
};
|
|
254
|
+
readonly "resume.editRecheckQuestionSkip": {
|
|
255
|
+
readonly kind: "wire";
|
|
256
|
+
readonly file: string;
|
|
257
|
+
};
|
|
258
|
+
readonly "resume.questionArgsShown": {
|
|
259
|
+
readonly kind: "wire";
|
|
260
|
+
readonly file: string;
|
|
261
|
+
};
|
|
262
|
+
readonly "budget.exemptResult": {
|
|
263
|
+
readonly kind: "wire";
|
|
264
|
+
readonly file: string;
|
|
265
|
+
};
|
|
266
|
+
readonly "compaction.compactableResult": {
|
|
267
|
+
readonly kind: "wire";
|
|
268
|
+
readonly file: string;
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
type Readings = typeof NAME_READINGS;
|
|
272
|
+
export type SiteId = keyof Readings;
|
|
273
|
+
export type AnySite = {
|
|
274
|
+
[K in SiteId]: Readings[K]["kind"] extends "any" ? K : never;
|
|
275
|
+
}[SiteId];
|
|
276
|
+
export type WireSite = {
|
|
277
|
+
[K in SiteId]: Readings[K]["kind"] extends "wire" ? K : never;
|
|
278
|
+
}[SiteId];
|
|
279
|
+
export type SpellingSite = {
|
|
280
|
+
[K in SiteId]: Readings[K]["kind"] extends "spelling" ? K : never;
|
|
281
|
+
}[SiteId];
|
|
282
|
+
export type TierSeatSite = {
|
|
283
|
+
[K in SiteId]: Readings[K] extends {
|
|
284
|
+
kind: "seat";
|
|
285
|
+
rank: "tier";
|
|
286
|
+
} ? K : never;
|
|
287
|
+
}[SiteId];
|
|
288
|
+
export type MemberSeatSite = {
|
|
289
|
+
[K in SiteId]: Readings[K] extends {
|
|
290
|
+
kind: "seat";
|
|
291
|
+
rank: "member";
|
|
292
|
+
} ? K : never;
|
|
293
|
+
}[SiteId];
|
|
294
|
+
export type SeatSite = TierSeatSite | MemberSeatSite;
|
|
295
|
+
/** A call as a site reads it: the wire name and, on a gated request, the face whose `names` lists every name it answers to. */
|
|
296
|
+
export interface NamedCall {
|
|
297
|
+
readonly toolName: string;
|
|
298
|
+
readonly face?: {
|
|
299
|
+
readonly names?: readonly string[];
|
|
300
|
+
} | undefined;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Every name the call answers to — the face's `names` when they include the call's name, else the call's name alone (a
|
|
304
|
+
* bare request, or a face minted for another name). A tool-name rule a host matches reaches the call iff its name is on
|
|
305
|
+
* this list; inside the engine only the ports below read it.
|
|
306
|
+
*/
|
|
307
|
+
export declare function ruleNamesOf(call: NamedCall): readonly string[];
|
|
308
|
+
/** An `any` site's membership read: does `set` hold any of the call's names? */
|
|
309
|
+
export declare function heldBy(site: AnySite, set: {
|
|
310
|
+
has(name: string): boolean;
|
|
311
|
+
}, call: NamedCall): boolean;
|
|
312
|
+
/** An `any` site's value read: every entry `map` holds under any of the call's names, in the call's name order. */
|
|
313
|
+
export declare function heldValues<V>(site: AnySite, map: {
|
|
314
|
+
get(name: string): V | undefined;
|
|
315
|
+
}, call: NamedCall): V[];
|
|
316
|
+
/** A `wire` site's read: the call's wire name. */
|
|
317
|
+
export declare function wireOf(site: WireSite, call: {
|
|
318
|
+
readonly toolName: string;
|
|
319
|
+
}): string;
|
|
320
|
+
/** A tool's irreversibility tier, the one vocabulary `ToolSpec.irreversibility` declares. */
|
|
321
|
+
type IrreversibilityTierWord = NonNullable<ToolSpec["irreversibility"]>;
|
|
322
|
+
/** One seat, read under ONE name: the name the port chose, the value it holds there, and the seat's other keys under that name. */
|
|
323
|
+
export interface Seat<V> {
|
|
324
|
+
readonly name: string;
|
|
325
|
+
readonly value: V;
|
|
326
|
+
/** Another key of this seat, read under the seat's name. */
|
|
327
|
+
read<W>(table: {
|
|
328
|
+
get(name: string): W | undefined;
|
|
329
|
+
}): W | undefined;
|
|
330
|
+
/** Another key of this seat, a membership, read under the seat's name. */
|
|
331
|
+
holds(set: {
|
|
332
|
+
has(name: string): boolean;
|
|
333
|
+
}): boolean;
|
|
334
|
+
}
|
|
335
|
+
/** A tier seat: the risk seat of the engine's irreversibility tables. */
|
|
336
|
+
export interface TierSeat extends Seat<IrreversibilityTierWord> {
|
|
337
|
+
/**
|
|
338
|
+
* The seat's shell-seat provenance: the seat's name is in `shellGated` (the engine's shell seat is the sole positive
|
|
339
|
+
* source of its tier) AND no name the call answers to carries a deployment mark in `markSources` — a mark on any of the
|
|
340
|
+
* call's names outranks the shell seat's attribution.
|
|
341
|
+
*/
|
|
342
|
+
shellGated(shellGated: {
|
|
343
|
+
has(name: string): boolean;
|
|
344
|
+
}, markSources: {
|
|
345
|
+
get(name: string): ReadonlySet<string> | undefined;
|
|
346
|
+
}): boolean;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* A `seat` site's read. A `tier` seat takes the irreversibility tier table and picks the name whose tier is strictest;
|
|
350
|
+
* a `member` seat takes a table (a map, or a set whose value reads `true`) and picks the first name it holds.
|
|
351
|
+
*/
|
|
352
|
+
export declare function seatOf(site: TierSeatSite, tiers: {
|
|
353
|
+
get(name: string): IrreversibilityTierWord | undefined;
|
|
354
|
+
}, call: NamedCall): TierSeat | undefined;
|
|
355
|
+
export declare function seatOf<V>(site: MemberSeatSite, table: {
|
|
356
|
+
get(name: string): V | undefined;
|
|
357
|
+
}, call: NamedCall): Seat<V> | undefined;
|
|
358
|
+
export declare function seatOf(site: MemberSeatSite, table: {
|
|
359
|
+
has(name: string): boolean;
|
|
360
|
+
}, call: NamedCall): Seat<true> | undefined;
|
|
361
|
+
export declare function seatOf<V>(site: MemberSeatSite, table: {
|
|
362
|
+
get(name: string): V | undefined;
|
|
363
|
+
} | {
|
|
364
|
+
has(name: string): boolean;
|
|
365
|
+
}, call: NamedCall): Seat<V | true> | undefined;
|
|
366
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const GATE = "src/core/runner/prepare-gate-stations.ts";
|
|
2
|
+
const CHAIN = "src/core/runner/prepare-policy-chain.ts";
|
|
3
|
+
const LANES = "src/core/gate-lanes.ts";
|
|
4
|
+
const POLICY = "src/core/tool-policy.ts";
|
|
5
|
+
const OFFER = "src/core/runner/permission-rule-lanes.ts";
|
|
6
|
+
const PARK = "src/core/runner/prepare-park-ask.ts";
|
|
7
|
+
const HANDLERS = "src/core/runner/run-harness-handlers.ts";
|
|
8
|
+
const any = (file) => ({ kind: "any", file });
|
|
9
|
+
const wire = (file) => ({ kind: "wire", file });
|
|
10
|
+
const spelling = (file) => ({ kind: "spelling", file });
|
|
11
|
+
const seat = (rank, file) => ({ kind: "seat", rank, file });
|
|
12
|
+
export const NAME_READINGS = {
|
|
13
|
+
"gate.compliance": any(GATE),
|
|
14
|
+
"gate.egress": any(GATE),
|
|
15
|
+
"gate.peerMessage": any(GATE),
|
|
16
|
+
"gate.markSources": any(GATE),
|
|
17
|
+
"gate.shellSeat": seat("tier", GATE),
|
|
18
|
+
"gate.sandboxBoundary.egress": any(CHAIN),
|
|
19
|
+
"gate.sandboxBoundary.hand": wire(CHAIN),
|
|
20
|
+
"gate.preToolUse": wire("src/core/hooks.ts"),
|
|
21
|
+
"fold.readStation": seat("member", "src/tools/fs/shell-read-check.ts"),
|
|
22
|
+
"lanes.contentQuestionClass": any(LANES),
|
|
23
|
+
"lanes.readOnlyShellAllow": wire(LANES),
|
|
24
|
+
"lanes.modeFileCommand": wire(LANES),
|
|
25
|
+
"lanes.contentAskRoute": wire(LANES),
|
|
26
|
+
"lanes.orgContentAskExemption": wire(LANES),
|
|
27
|
+
"lanes.persistedEditSkip": wire(LANES),
|
|
28
|
+
"lanes.persistedLaneSkip": wire(LANES),
|
|
29
|
+
"lanes.allowLayerQuestionSkip": wire(LANES),
|
|
30
|
+
"lanes.sandboxQuestionSkip": wire(LANES),
|
|
31
|
+
"policy.nameLists": any(POLICY),
|
|
32
|
+
"policy.shellToolsList": any(POLICY),
|
|
33
|
+
"policy.projectionReplay": any(POLICY),
|
|
34
|
+
"policy.sessionRules": any("src/core/runner/session-rule-policy.ts"),
|
|
35
|
+
"policy.sessionRules.effect": wire("src/core/runner/session-rule-policy.ts"),
|
|
36
|
+
"policy.skillScope": any("src/core/runner/active-skill-scope.ts"),
|
|
37
|
+
"policy.skillScope.effect": wire("src/core/runner/active-skill-scope.ts"),
|
|
38
|
+
"policy.permissionRules": any("src/core/permission-rules.ts"),
|
|
39
|
+
"policy.persistedGrammar": any("src/core/permission-rule-model.ts"),
|
|
40
|
+
"policy.durableQuestion": any("src/core/ask-question.ts"),
|
|
41
|
+
"policy.sensitivePathGuardedByName": any("src/core/sensitive-path-policy.ts"),
|
|
42
|
+
"floor.protectivePathTarget": seat("member", "src/core/tool-registry.ts"),
|
|
43
|
+
"mode.planRosterRow": wire("src/core/mode-policy.ts"),
|
|
44
|
+
"offer.laneTool": wire(OFFER),
|
|
45
|
+
"offer.orgQuestionExemption": wire(OFFER),
|
|
46
|
+
"offer.marks": seat("tier", OFFER),
|
|
47
|
+
"offer.marks.egress": any(OFFER),
|
|
48
|
+
"park.riskDescriptor": seat("tier", PARK),
|
|
49
|
+
"park.syncFirstRoute": wire(PARK),
|
|
50
|
+
"report.riskAxes": seat("tier", CHAIN),
|
|
51
|
+
"report.riskAxes.egress": any(CHAIN),
|
|
52
|
+
"report.riskAxes.negatives": seat("member", CHAIN),
|
|
53
|
+
"verify.wroteThisRun": wire(HANDLERS),
|
|
54
|
+
"verify.wroteThisRunSeed": wire("src/core/runner/run-attachment-seats.ts"),
|
|
55
|
+
"event.toolStartEnd": spelling(HANDLERS),
|
|
56
|
+
"hook.postToolUse": wire(GATE),
|
|
57
|
+
"hook.postToolUseFailure": wire(GATE),
|
|
58
|
+
"hook.postToolBatch": spelling(HANDLERS),
|
|
59
|
+
"stats.subagentToolCounter": spelling("src/agents/subagent.ts"),
|
|
60
|
+
"activity.sink": spelling(HANDLERS),
|
|
61
|
+
"reconcile.orphanEffect": spelling("src/core/session-reconcile.ts"),
|
|
62
|
+
"inherited.questionFloat": wire(CHAIN),
|
|
63
|
+
"park.contentKindProjection": wire("src/core/checkpoint-store.ts"),
|
|
64
|
+
"resume.contentAskRow": wire("src/core/runner/decide-continuation.ts"),
|
|
65
|
+
"resume.editRecheckQuestionSkip": wire("src/core/runner/resume-apply.ts"),
|
|
66
|
+
"resume.questionArgsShown": wire("src/core/runner/run-leg.ts"),
|
|
67
|
+
"budget.exemptResult": wire("src/core/tool-result-budget.ts"),
|
|
68
|
+
"compaction.compactableResult": wire("src/core/context-edit.ts"),
|
|
69
|
+
};
|
|
70
|
+
export function ruleNamesOf(call) {
|
|
71
|
+
const names = call.face?.names;
|
|
72
|
+
return names !== undefined && names.includes(call.toolName) ? names : [call.toolName];
|
|
73
|
+
}
|
|
74
|
+
function readingOf(site, kind) {
|
|
75
|
+
const r = NAME_READINGS[site];
|
|
76
|
+
if (r.kind !== kind)
|
|
77
|
+
throw new Error(`tool-name reading: site ${JSON.stringify(site)} is a ${r.kind} site, read through the ${kind} port`);
|
|
78
|
+
return r;
|
|
79
|
+
}
|
|
80
|
+
export function heldBy(site, set, call) {
|
|
81
|
+
readingOf(site, "any");
|
|
82
|
+
return ruleNamesOf(call).some((n) => set.has(n));
|
|
83
|
+
}
|
|
84
|
+
export function heldValues(site, map, call) {
|
|
85
|
+
readingOf(site, "any");
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const n of ruleNamesOf(call)) {
|
|
88
|
+
const v = map.get(n);
|
|
89
|
+
if (v !== undefined)
|
|
90
|
+
out.push(v);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
export function wireOf(site, call) {
|
|
95
|
+
readingOf(site, "wire");
|
|
96
|
+
return call.toolName;
|
|
97
|
+
}
|
|
98
|
+
const DEPLOYMENT_MARK = "deployment";
|
|
99
|
+
const TIER_RANK = { never: 1, maybe: 2, always: 3 };
|
|
100
|
+
function seatAt(name, value) {
|
|
101
|
+
return {
|
|
102
|
+
name,
|
|
103
|
+
value,
|
|
104
|
+
read: (table) => table.get(name),
|
|
105
|
+
holds: (set) => set.has(name),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function seatOf(site, table, call) {
|
|
109
|
+
const r = readingOf(site, "seat");
|
|
110
|
+
const names = ruleNamesOf(call);
|
|
111
|
+
const valueAt = (n) => (typeof table.get === "function" ? table.get(n) : table.has(n) ? true : undefined);
|
|
112
|
+
if (r.kind === "seat" && r.rank === "tier") {
|
|
113
|
+
let best;
|
|
114
|
+
for (const n of names) {
|
|
115
|
+
const v = valueAt(n);
|
|
116
|
+
if (v === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
if (best === undefined || TIER_RANK[v] > TIER_RANK[best.value])
|
|
119
|
+
best = { name: n, value: v };
|
|
120
|
+
}
|
|
121
|
+
if (best === undefined)
|
|
122
|
+
return undefined;
|
|
123
|
+
const chosen = seatAt(best.name, best.value);
|
|
124
|
+
const tierSeat = {
|
|
125
|
+
...chosen,
|
|
126
|
+
shellGated: (shellGated, markSources) => shellGated.has(chosen.name) && !names.some((n) => markSources.get(n)?.has(DEPLOYMENT_MARK) === true),
|
|
127
|
+
};
|
|
128
|
+
return tierSeat;
|
|
129
|
+
}
|
|
130
|
+
for (const n of names) {
|
|
131
|
+
const v = valueAt(n);
|
|
132
|
+
if (v !== undefined)
|
|
133
|
+
return seatAt(n, v);
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
@@ -4,10 +4,10 @@ import { type Settlement } from "./gate-outcome.js";
|
|
|
4
4
|
/** A tool call presented to a policy before it executes. */
|
|
5
5
|
/** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
|
|
6
6
|
export interface ToolCallFace {
|
|
7
|
-
/** Every name a tool-name rule may spell this tool by: the
|
|
8
|
-
* it on the leg's roster (`rosterToolName`). A rule
|
|
9
|
-
*
|
|
10
|
-
* (the call's own name is then the list). */
|
|
7
|
+
/** Every name a tool-name rule may spell this tool by: the name the call was made under first (the model's spelling),
|
|
8
|
+
* then the tool's wire name and each other alias that resolves to it on the leg's roster (`rosterToolName`). A rule
|
|
9
|
+
* reaches the call iff its tool name is on this list — read it through `ruleNamesOf`, never off `toolName` alone.
|
|
10
|
+
* Absent on a face rebuilt from a park row that predates it (the call's own name is then the list). */
|
|
11
11
|
names?: readonly string[];
|
|
12
12
|
family: import("./tool-face.js").ToolFamily;
|
|
13
13
|
pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
|
|
@@ -18,7 +18,7 @@ export interface ToolCallFace {
|
|
|
18
18
|
}
|
|
19
19
|
export interface ToolCallRequest {
|
|
20
20
|
/** The tool's wire name — the Runner's gate resolves a call made under an alias to the wire name of the tool it
|
|
21
|
-
* dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are
|
|
21
|
+
* dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are `ruleNamesOf`. */
|
|
22
22
|
toolName: string;
|
|
23
23
|
args: unknown;
|
|
24
24
|
toolCallId: string;
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -8,7 +8,7 @@ import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
|
8
8
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
9
9
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
10
10
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
11
|
-
import {
|
|
11
|
+
import { heldBy } from "./tool-name-readings.js";
|
|
12
12
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
13
13
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
|
|
14
14
|
import { describeRejectedValue } from "./tool-search-setting.js";
|
|
@@ -17,6 +17,8 @@ import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermission
|
|
|
17
17
|
import { protocolOf } from "./protocol-table.js";
|
|
18
18
|
import { isAbsoluteForFamily, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
|
|
19
19
|
import { ASSIGNMENT_PREFIX, writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
|
|
20
|
+
const nameListHolds = (set, req) => heldBy("policy.nameLists", set, req);
|
|
21
|
+
const shellToolsListHolds = (set, req) => heldBy("policy.shellToolsList", set, req);
|
|
20
22
|
export function advertisedWritableRootsSeat(seat) {
|
|
21
23
|
return seat === undefined ? {} : { advertisedWritableRoots: Object.freeze([...seat.roots]) };
|
|
22
24
|
}
|
|
@@ -165,21 +167,21 @@ export function namespacedCoveringHit(covering, toolName) {
|
|
|
165
167
|
return covering.length > 0 && covering.some((e) => namespacedRuleNameCovers(e, toolName));
|
|
166
168
|
}
|
|
167
169
|
export function checkToolPolicyProjection(projection, req) {
|
|
168
|
-
const
|
|
170
|
+
const listed = (list) => heldBy("policy.projectionReplay", { has: (n) => list.includes(n) }, req);
|
|
169
171
|
for (const c of projection.components) {
|
|
170
172
|
if (c.kind === "tool_deny") {
|
|
171
|
-
if (
|
|
173
|
+
if (listed(c.names) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
|
|
172
174
|
return { action: "deny", message: `tool "${req.toolName}" is denied by a frozen inherited policy projection` };
|
|
173
175
|
}
|
|
174
176
|
continue;
|
|
175
177
|
}
|
|
176
178
|
if (c.kind === "tool_allowlist") {
|
|
177
|
-
if (!
|
|
179
|
+
if (!listed(c.names) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
|
|
178
180
|
return { action: "deny", message: `tool "${req.toolName}" is not in a frozen inherited policy projection's allowlist` };
|
|
179
181
|
}
|
|
180
182
|
continue;
|
|
181
183
|
}
|
|
182
|
-
if (!
|
|
184
|
+
if (!listed(c.tools))
|
|
183
185
|
continue;
|
|
184
186
|
const command = req.args?.command;
|
|
185
187
|
if (typeof command !== "string") {
|
|
@@ -388,10 +390,10 @@ export function createAllowDenyPolicy(opts) {
|
|
|
388
390
|
nameSets: [{ ...(opts.allow ? { allow: [...opts.allow] } : {}), ...(opts.deny ? { deny: [...opts.deny] } : {}) }],
|
|
389
391
|
check(req) {
|
|
390
392
|
const toolName = req.toolName;
|
|
391
|
-
if (
|
|
393
|
+
if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
|
|
392
394
|
return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
|
|
393
395
|
}
|
|
394
|
-
if (allow && !
|
|
396
|
+
if (allow && !nameListHolds(allow, req) && !namespacedCoveringHit(allowCovering, toolName)) {
|
|
395
397
|
return { action: "deny", message: `tool "${req.toolName}" is not in the allowlist` };
|
|
396
398
|
}
|
|
397
399
|
return ALLOW;
|
|
@@ -428,10 +430,10 @@ export function createApprovalPolicy(opts) {
|
|
|
428
430
|
],
|
|
429
431
|
async check(req, signal) {
|
|
430
432
|
const toolName = req.toolName;
|
|
431
|
-
if (
|
|
433
|
+
if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
|
|
432
434
|
return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
|
|
433
435
|
}
|
|
434
|
-
if (
|
|
436
|
+
if (nameListHolds(need, req) || namespacedCoveringHit(needCovering, toolName)) {
|
|
435
437
|
const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
|
|
436
438
|
if (signal?.aborted) {
|
|
437
439
|
return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
|
|
@@ -481,7 +483,7 @@ export function createApprovalPolicy(opts) {
|
|
|
481
483
|
}
|
|
482
484
|
return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByPerson("human_refused", undefined));
|
|
483
485
|
}
|
|
484
|
-
if (opts.denyByDefault && !
|
|
486
|
+
if (opts.denyByDefault && !nameListHolds(auto, req) && !namespacedCoveringHit(autoCovering, toolName)) {
|
|
485
487
|
return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
|
|
486
488
|
}
|
|
487
489
|
return ALLOW;
|
|
@@ -621,7 +623,7 @@ export function createCoarseCommandNamePolicy(opts) {
|
|
|
621
623
|
return brandPolicyAskClass({
|
|
622
624
|
projection,
|
|
623
625
|
check(req) {
|
|
624
|
-
if (!
|
|
626
|
+
if (!shellToolsListHolds(shellTools, req))
|
|
625
627
|
return ALLOW;
|
|
626
628
|
const command = req.args?.command;
|
|
627
629
|
if (typeof command !== "string") {
|
|
@@ -875,7 +877,7 @@ export function createUnverifiableDeletePolicy(opts) {
|
|
|
875
877
|
const shellTools = canonicalToolNameSet(opts?.tools);
|
|
876
878
|
return brandPolicyAskClass({
|
|
877
879
|
check(req) {
|
|
878
|
-
if (!
|
|
880
|
+
if (!shellToolsListHolds(shellTools, req))
|
|
879
881
|
return ALLOW;
|
|
880
882
|
const command = req.args?.command;
|
|
881
883
|
if (typeof command !== "string")
|
|
@@ -972,7 +974,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
972
974
|
});
|
|
973
975
|
return brandPolicyAskClass({
|
|
974
976
|
check(req) {
|
|
975
|
-
if (
|
|
977
|
+
if (shellToolsListHolds(shellTools, req)) {
|
|
976
978
|
const command = req.args?.command;
|
|
977
979
|
if (typeof command !== "string")
|
|
978
980
|
return ALLOW;
|
|
@@ -52,36 +52,6 @@ export interface ToolCallLike {
|
|
|
52
52
|
} | undefined;
|
|
53
53
|
}
|
|
54
54
|
type CallPathTarget = ToolCallPathTarget;
|
|
55
|
-
/**
|
|
56
|
-
* The names a tool-name rule may spell this call's tool by — the face's `names` when they include the call's name,
|
|
57
|
-
* else the call's name alone (a bare request, or a face minted for another name). Every name-keyed matcher and the
|
|
58
|
-
* protective path floor read a rule's tool name against THIS list, which is the roster's one canonicalization seen
|
|
59
|
-
* from the call's side: a rule written against an alias and one written against the wire name reach the same calls.
|
|
60
|
-
*/
|
|
61
|
-
export declare function ruleNamesOf(req: Pick<ToolCallLike, "toolName" | "face">): readonly string[];
|
|
62
|
-
/** Does the call answer to `name` — is it one of the call's names ({@link ruleNamesOf})? The engine's TIGHTENS keyed on
|
|
63
|
-
* one of its own tool names read this, so a tool that wears the name under any of its names is tightened (the
|
|
64
|
-
* fail-safe direction); a grant or a routing keyed on an engine tool name reads the wire name. */
|
|
65
|
-
export declare function callAnswersTo(req: Pick<ToolCallLike, "toolName" | "face">, name: string): boolean;
|
|
66
|
-
/** Does a set of rule tool names name this call's tool ({@link ruleNamesOf})? */
|
|
67
|
-
export declare function toolNameSetHas(set: {
|
|
68
|
-
has(name: string): boolean;
|
|
69
|
-
}, req: Pick<ToolCallLike, "toolName" | "face">): boolean;
|
|
70
|
-
/** Does `set` hold any of the call's names ({@link ruleNamesOf})? The read of a MEMBERSHIP tighten keyed on the engine's
|
|
71
|
-
* own tool names (the egress set, a read station's tools, the compliance veto): a tool is held under every name it
|
|
72
|
-
* answers to. A grant or a routing keyed on an engine tool name reads the wire name instead. */
|
|
73
|
-
export declare function anyNamed(set: {
|
|
74
|
-
has(name: string): boolean;
|
|
75
|
-
}, names: readonly string[]): boolean;
|
|
76
|
-
/** The first of the call's names ({@link ruleNamesOf}, the spelling first) that `map` holds an entry for, with the entry.
|
|
77
|
-
* The read of a SEAT keyed on the engine's own tool names (a tier, a mark's sources): every key of one seat is read under
|
|
78
|
-
* the ONE name this returns — a seat is never assembled from two names' entries. */
|
|
79
|
-
export declare function firstNamed<V>(map: {
|
|
80
|
-
get(name: string): V | undefined;
|
|
81
|
-
}, names: readonly string[]): {
|
|
82
|
-
readonly name: string;
|
|
83
|
-
readonly value: V;
|
|
84
|
-
} | undefined;
|
|
85
55
|
/**
|
|
86
56
|
* design/388 B6/B17 — the path target a PROTECTIVE fence reads (a deny-list judging a write's target: sensitive paths,
|
|
87
57
|
* frozen paths, write protection, the transcript gate; edit tracking): the object face's own declaration first, the
|
|
@@ -89,7 +59,7 @@ export declare function firstNamed<V>(map: {
|
|
|
89
59
|
* face that declares none. Reading the catalog for an undeclared same-name tool can only TIGHTEN a deny-list (it adds
|
|
90
60
|
* a slot to judge — the pre-388 posture, never wider), which is exactly why this floor is wrong for a grant.
|
|
91
61
|
*
|
|
92
|
-
* The floor
|
|
62
|
+
* The floor is a `member` seat over every name the call answers to (the face's `names`, else the call's name alone) in
|
|
93
63
|
* the face's order — the name the call was made under first, then the wire name, then the other aliases — and the
|
|
94
64
|
* first name the catalog declares a target for wins: a call made under a catalog name and resolved to a tool that
|
|
95
65
|
* declares none keeps THAT name's slot, also when the tool answers to a second catalog name with another slot.
|