@sema-agent/core 7.17.7 → 7.19.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 +233 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/anthropic.js +6 -3
- package/dist/brain/open-responses.js +10 -4
- package/dist/brain/openai.js +5 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/ask-origin.d.ts +16 -0
- package/dist/core/ask-origin.js +8 -0
- package/dist/core/auto-mode-arming.d.ts +6 -0
- package/dist/core/auto-mode-arming.js +5 -5
- package/dist/core/auto-mode-defaults.d.ts +43 -15
- package/dist/core/auto-mode-defaults.js +4 -1
- package/dist/core/auto-mode.d.ts +184 -59
- package/dist/core/auto-mode.js +188 -13
- package/dist/core/checkpoint-store.d.ts +23 -2
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/engine-notice.d.ts +77 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-fold.js +5 -1
- package/dist/core/gate-lanes.js +9 -8
- package/dist/core/gate-outcome.d.ts +42 -2
- package/dist/core/gate-outcome.js +23 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +227 -0
- package/dist/core/runner/classifier-ladder.js +214 -0
- package/dist/core/runner/contracts.d.ts +71 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/denial-limit-arms.js +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
- package/dist/core/runner/prepare-config-doors.d.ts +7 -1
- package/dist/core/runner/prepare-config-doors.js +13 -13
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +7 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +19 -14
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-apply.js +3 -3
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +45 -45
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +15 -5
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +83 -48
- package/dist/core/runner/synthetic-tools.js +69 -32
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner/turn-attachments.d.ts +43 -12
- package/dist/core/runner/turn-attachments.js +58 -14
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/sensitive-path-policy.d.ts +58 -17
- package/dist/core/sensitive-path-policy.js +33 -5
- package/dist/core/skill-listing-budget.d.ts +112 -0
- package/dist/core/skill-listing-budget.js +58 -0
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +46 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +19 -4
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +45 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/core/write-protect.d.ts +8 -3
- package/dist/core/write-protect.js +8 -5
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +13 -8
- package/dist/index.js +11 -7
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
- package/dist/tools/fs/bash-readonly-classifier.js +180 -558
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +165 -25
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +18 -3
- package/dist/tools/fs/safety.js +11 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +99 -13
- package/wire-changes/core@7.17.7.json +49 -0
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -115,8 +115,74 @@ export function isDelegatedAgentTerminal(n) {
|
|
|
115
115
|
export function isTerminalTaskNotification(n) {
|
|
116
116
|
return TERMINAL_STATUSES.has(n.status);
|
|
117
117
|
}
|
|
118
|
+
export function createLiveNotificationLane(seat) {
|
|
119
|
+
const handedOff = new Set();
|
|
120
|
+
const enter = (admits) => (payload, priority, onDisposition) => {
|
|
121
|
+
const dedupKey = taskNotificationDedupKey(payload);
|
|
122
|
+
if (seat.alreadyDelivered(dedupKey))
|
|
123
|
+
return "duplicate";
|
|
124
|
+
if (!admits(dedupKey))
|
|
125
|
+
return "unavailable";
|
|
126
|
+
const accepted = seat.queue.enqueue({ kind: "task_notification", priority, dedupKey, payload, ...(onDisposition !== undefined ? { onDisposition } : {}) });
|
|
127
|
+
if (!accepted)
|
|
128
|
+
return "pending";
|
|
129
|
+
return "taken";
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
take: enter(() => seat.isLive()),
|
|
133
|
+
takeAsDestination: enter((dedupKey) => seat.isLive() && seat.canTakeNow() && !handedOff.has(dedupKey)),
|
|
134
|
+
handOff: (payload) => {
|
|
135
|
+
if (!seat.isLive())
|
|
136
|
+
return;
|
|
137
|
+
handedOff.add(taskNotificationDedupKey(payload));
|
|
138
|
+
if (handedOff.size > MAX_DEDUP_KEYS) {
|
|
139
|
+
for (const k of handedOff) {
|
|
140
|
+
handedOff.delete(k);
|
|
141
|
+
if (handedOff.size <= MAX_DEDUP_KEYS)
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
release: () => handedOff.clear(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export class SessionLiveNotificationLanes {
|
|
150
|
+
lanes = new Map();
|
|
151
|
+
register(sessionId, lane) {
|
|
152
|
+
const existing = this.lanes.get(sessionId);
|
|
153
|
+
if (existing === undefined)
|
|
154
|
+
this.lanes.set(sessionId, [lane]);
|
|
155
|
+
else if (!existing.includes(lane))
|
|
156
|
+
existing.push(lane);
|
|
157
|
+
}
|
|
158
|
+
unregister(sessionId, lane) {
|
|
159
|
+
const existing = this.lanes.get(sessionId);
|
|
160
|
+
if (existing === undefined)
|
|
161
|
+
return;
|
|
162
|
+
const at = existing.indexOf(lane);
|
|
163
|
+
if (at !== -1)
|
|
164
|
+
existing.splice(at, 1);
|
|
165
|
+
if (existing.length === 0)
|
|
166
|
+
this.lanes.delete(sessionId);
|
|
167
|
+
}
|
|
168
|
+
deliver(sessionId, payload, priority, opts) {
|
|
169
|
+
const existing = this.lanes.get(sessionId);
|
|
170
|
+
if (existing === undefined)
|
|
171
|
+
return "unavailable";
|
|
172
|
+
for (let i = existing.length - 1; i >= 0; i -= 1) {
|
|
173
|
+
const outcome = existing[i](payload, priority, opts?.onDisposition);
|
|
174
|
+
if (outcome !== "unavailable")
|
|
175
|
+
return outcome;
|
|
176
|
+
}
|
|
177
|
+
return "unavailable";
|
|
178
|
+
}
|
|
179
|
+
get size() {
|
|
180
|
+
return this.lanes.size;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
118
183
|
export class PendingSessionNotifications {
|
|
119
184
|
sessions = new Map();
|
|
185
|
+
liveLanes = new SessionLiveNotificationLanes();
|
|
120
186
|
droppedSessions = 0;
|
|
121
187
|
evictedSessions = new Map();
|
|
122
188
|
unattributedDrops = 0;
|
|
@@ -163,6 +229,7 @@ export class PendingSessionNotifications {
|
|
|
163
229
|
evictOldest(s, () => true);
|
|
164
230
|
}
|
|
165
231
|
drain(sessionId) {
|
|
232
|
+
this.routed.delete(sessionId);
|
|
166
233
|
const s = this.sessions.get(sessionId);
|
|
167
234
|
if (s !== undefined) {
|
|
168
235
|
this.sessions.delete(sessionId);
|
|
@@ -182,10 +249,49 @@ export class PendingSessionNotifications {
|
|
|
182
249
|
out.droppedWholeSession = (out.droppedWholeSession ?? 0) + (tomb ?? 0) + carried;
|
|
183
250
|
return out;
|
|
184
251
|
}
|
|
252
|
+
routed = new Map();
|
|
253
|
+
holdsKey(sessionId, dedupKey) {
|
|
254
|
+
return this.sessions.get(sessionId)?.keys.has(dedupKey) === true || this.routed.get(sessionId)?.has(dedupKey) === true;
|
|
255
|
+
}
|
|
256
|
+
markDelivered(sessionId, dedupKey) {
|
|
257
|
+
let mine = this.routed.get(sessionId);
|
|
258
|
+
if (mine === undefined) {
|
|
259
|
+
if (this.routed.size >= MAX_PENDING_SESSIONS) {
|
|
260
|
+
const oldest = this.routed.keys().next().value;
|
|
261
|
+
if (oldest !== undefined)
|
|
262
|
+
this.routed.delete(oldest);
|
|
263
|
+
}
|
|
264
|
+
mine = new Set();
|
|
265
|
+
this.routed.set(sessionId, mine);
|
|
266
|
+
}
|
|
267
|
+
mine.add(dedupKey);
|
|
268
|
+
if (mine.size > MAX_PENDING_EVENTS_PER_SESSION) {
|
|
269
|
+
for (const k of mine) {
|
|
270
|
+
mine.delete(k);
|
|
271
|
+
if (mine.size <= MAX_PENDING_EVENTS_PER_SESSION)
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
185
276
|
get size() {
|
|
186
277
|
return this.sessions.size;
|
|
187
278
|
}
|
|
188
279
|
}
|
|
280
|
+
export function routeOrParkNotification(store, sessionId, payload, priority, onDisposition) {
|
|
281
|
+
const dedupKey = taskNotificationDedupKey(payload);
|
|
282
|
+
if (store.holdsKey(sessionId, dedupKey)) {
|
|
283
|
+
onDisposition?.("parked");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const routed = store.liveLanes.deliver(sessionId, payload, priority, { ...(onDisposition !== undefined ? { onDisposition } : {}) });
|
|
287
|
+
if (routed === "unavailable") {
|
|
288
|
+
store.pend(sessionId, payload, priority);
|
|
289
|
+
onDisposition?.("parked");
|
|
290
|
+
}
|
|
291
|
+
else if (routed !== "taken") {
|
|
292
|
+
onDisposition?.("queued");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
189
295
|
function evictOldest(s, match) {
|
|
190
296
|
let idx = s.items.findIndex((i) => match(i) && !TERMINAL_STATUSES.has(i.status));
|
|
191
297
|
if (idx === -1)
|
|
@@ -541,7 +541,15 @@ export interface TaskResult {
|
|
|
541
541
|
* `"open"` observation is never forwarded) closes that escape for ANY follow-on, not just the
|
|
542
542
|
* verifier. See {@link effectiveReadDenyPatterns} for the deny-set half.
|
|
543
543
|
*/
|
|
544
|
-
effectiveReadFace?: import("../tools/fs/
|
|
544
|
+
effectiveReadFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
545
|
+
/**
|
|
546
|
+
* The WRITE-face containment state in force on this leg, the read seat's twin as an engine-filled
|
|
547
|
+
* OBSERVATION. Same in-presence condition as {@link effectiveReadFace} (every leg that completes
|
|
548
|
+
* prepare; absent only on prepare-failure terminals) and the same purpose: a POST-COMPLETION follow-on
|
|
549
|
+
* leg folds it stricter-wins into its own spec. Only `"roots"` is ever worth forwarding; an `"open"`
|
|
550
|
+
* observation never widens anything by itself.
|
|
551
|
+
*/
|
|
552
|
+
effectiveWriteFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
545
553
|
/**
|
|
546
554
|
* The sensitive-path deny ADDITIONS in force on this leg beyond the
|
|
547
555
|
* built-in table, normalized (deployment ∪ task ∪ checkpoint-frozen seed), as an engine-filled
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -795,15 +795,32 @@ export interface TaskSpec {
|
|
|
795
795
|
readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
|
|
796
796
|
/**
|
|
797
797
|
* design/199 件A — the TASK layer's read-face containment declaration
|
|
798
|
-
* ({@link import("../tools/fs/
|
|
798
|
+
* ({@link import("../tools/fs/containment-face.js").ContainmentFace}). "roots" pins this task (and, via the
|
|
799
799
|
* tighten-only clamp, its whole delegation subtree) to the historical containment; "open" lifts
|
|
800
800
|
* the roots judgment for the structured read faces — refused loudly under org governance (the
|
|
801
801
|
* task layer only tightens there; the deployment opens via {@link RunnerDeps.readFace}) and
|
|
802
802
|
* beside `handsReadOnly: true` (the verifier containment is load-bearing). Absent ⇒ the
|
|
803
803
|
* deployment seat, then the engine default ("roots" — D-1b: an upgrade never opens implicitly).
|
|
804
|
-
* Bad values refuse loudly (#123). Never affects the write faces
|
|
804
|
+
* Bad values refuse loudly (#123). Never affects the write faces — that is {@link writeFace}, a
|
|
805
|
+
* separate seat with its own default.
|
|
805
806
|
*/
|
|
806
|
-
readFace?: import("../tools/fs/
|
|
807
|
+
readFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
808
|
+
/**
|
|
809
|
+
* The TASK layer's WRITE-face containment declaration
|
|
810
|
+
* ({@link import("../tools/fs/containment-face.js").ContainmentFace}) — the read seat's twin,
|
|
811
|
+
* resolved through the SAME order (`resolveContainmentFace`, one body, one set of rows). "roots"
|
|
812
|
+
* pins this task (and, via the tighten-only clamp, its whole delegation subtree) to the historical
|
|
813
|
+
* write fence; "open" lifts the roots judgment for the structured WRITE faces (`edit_file` /
|
|
814
|
+
* `write_file` / `notebook_edit`) — refused loudly under org governance (the task layer only
|
|
815
|
+
* tightens there; the deployment opens via {@link RunnerDeps.writeFace}) and beside
|
|
816
|
+
* `handsReadOnly: true` (that mount carries no write faces at all). Absent ⇒ the deployment seat,
|
|
817
|
+
* then the engine default ("roots" — an upgrade never opens implicitly). Bad values refuse loudly.
|
|
818
|
+
*
|
|
819
|
+
* What "open" does NOT change: the write-side sensitive-path policy, the blocked-device gate, the
|
|
820
|
+
* UNC out-of-set refusal, the read-before-edit requirement and the deployment's approval policy. Seat
|
|
821
|
+
* independence is owned by `fs.containment_face.seat_independence` ({@link import("../tools/fs/containment-face.js").resolveContainmentFace}).
|
|
822
|
+
*/
|
|
823
|
+
writeFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
807
824
|
/**
|
|
808
825
|
* design/80 D-B — opt in to PLAN MODE: mount the first-party `present_plan` tool (CC `ExitPlanMode` parity).
|
|
809
826
|
* The model calls it to present a plan; the engine then pauses with a durable `plan_review` checkpoint
|
|
@@ -918,6 +935,32 @@ export interface TaskSpec {
|
|
|
918
935
|
* end-user-authored skills are responsible for vetting them: a skill IS instructions by design.
|
|
919
936
|
*/
|
|
920
937
|
skills?: SkillSpec[];
|
|
938
|
+
/**
|
|
939
|
+
* Fraction of the model's context window the `<skills>` listing may spend. Absent ⇒ `0.01` (1%).
|
|
940
|
+
* Domain `(0, 1]`; anything else — including `0`, a string, `NaN` — REFUSES at the door with
|
|
941
|
+
* `config.skill_listing_budget_invalid` rather than folding back to the default.
|
|
942
|
+
*
|
|
943
|
+
* The budget is `(contextTokens ?? contextWindow ?? 200 000) × 4 × fraction`, in UTF-8 bytes, and the
|
|
944
|
+
* listing degrades inside it: every skill stays listed, the lower-priority ones (declaration order)
|
|
945
|
+
* lose their descriptions first. Raising this is opting in to a higher per-turn context cost.
|
|
946
|
+
*
|
|
947
|
+
* ⚠️ ONE-WAY IN PRACTICE (`@contract skills.listing_budget_ceiling`): the listing rides the turn-
|
|
948
|
+
* attachment lane, whose bundle cap is a hard structural limit, so the resolved budget is clamped to
|
|
949
|
+
* 8 000 bytes. A fraction that asks for MORE lowers nothing and raises nothing; a fraction that asks
|
|
950
|
+
* for less takes effect exactly. When the clamp costs a skill its description, both numbers ride the
|
|
951
|
+
* `skills.listing_truncated` notice.
|
|
952
|
+
*
|
|
953
|
+
* ⚠️ A model declaring a window SMALLER than 200 000 tokens now gets a proportionally smaller listing
|
|
954
|
+
* budget than the flat 8 000 bytes that preceded this seat.
|
|
955
|
+
*/
|
|
956
|
+
skillListingBudgetFraction?: number;
|
|
957
|
+
/**
|
|
958
|
+
* Per-skill description cap inside the `<skills>` listing, in code units, ellipsis included. Absent ⇒
|
|
959
|
+
* `1536`. Must be a positive integer; anything else REFUSES with
|
|
960
|
+
* `config.skill_listing_budget_invalid`. A description longer than the cap is truncated to exactly
|
|
961
|
+
* this many units; lowering it is how a deployment fits more skills' descriptions inside one budget.
|
|
962
|
+
*/
|
|
963
|
+
skillListingMaxDescChars?: number;
|
|
921
964
|
/**
|
|
922
965
|
* design/129 — lifetime scope for BACKGROUND children (Agent/Fork `run_in_background`). Default `"task"`:
|
|
923
966
|
* the parent runTask's terminal reaps them (design/115 — no orphans burning tokens; turn-end killed
|
|
@@ -25,11 +25,13 @@ export function tightenTaskSpec(base, overrides) {
|
|
|
25
25
|
merged[field] = [...new Set([...(b ?? []), ...(o ?? [])])];
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
for (const faceKey of ["readFace", "writeFace"]) {
|
|
29
|
+
if (base[faceKey] === "roots" && overrides[faceKey] === "open") {
|
|
30
|
+
throw new TaskSpecTightenError(`tightenTaskSpec: cannot loosen ${faceKey} from "roots" to "open" (override may only tighten)`);
|
|
31
|
+
}
|
|
32
|
+
if (base[faceKey] !== undefined || overrides[faceKey] !== undefined) {
|
|
33
|
+
merged[faceKey] = base[faceKey] === "roots" || overrides[faceKey] === "roots" ? "roots" : (overrides[faceKey] ?? base[faceKey]);
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
36
|
if (base.readDenyPatterns !== undefined || overrides.readDenyPatterns !== undefined) {
|
|
35
37
|
const seen = new Set();
|
|
@@ -67,7 +67,7 @@ export declare const TOOL_MODEL_GATE_CLASSES: Readonly<Record<string, ToolModelG
|
|
|
67
67
|
*/
|
|
68
68
|
export declare function isModelGatedForClass(modelId: string, rule: ToolModelGateRule): boolean;
|
|
69
69
|
/**
|
|
70
|
-
* #123 value screen for `TaskSpec.restoreGatedTools` (same posture as `
|
|
70
|
+
* #123 value screen for `TaskSpec.restoreGatedTools` (same posture as `assertContainmentFaceValue`: the
|
|
71
71
|
* VALUE is screened at the door, unconditionally — a garbage restore list must refuse loudly on
|
|
72
72
|
* every leg, never be silently read as "no restore" in the narrowing direction). Legal: absent,
|
|
73
73
|
* literal `true` (restore every class), or an array of strings (wire names; unknown names are
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AskOrigin } from "./ask-origin.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type Settlement } from "./gate-outcome.js";
|
|
3
3
|
/** A tool call presented to a policy before it executes. */
|
|
4
4
|
/** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
|
|
5
5
|
export interface ToolCallFace {
|
|
@@ -310,6 +310,11 @@ export type PermissionResult = {
|
|
|
310
310
|
* in the one direction the stamp cannot reach (a caller composing its own ask) the claim is
|
|
311
311
|
* tightening-only: it can make an ask outlive a rule, never turn one into an allow. */
|
|
312
312
|
probeMandated?: true;
|
|
313
|
+
/** The probe named a directory that would clear this ask
|
|
314
|
+
* ({@link import("./types.js").ReversibilityVerdict.readRootCandidate}). Display metadata like the two
|
|
315
|
+
* `probe*` above; ENGINE-STAMPED and VALIDATED at the tighten, and deliberately NOT merged onto an ask
|
|
316
|
+
* another layer raised, which a grant leaves standing. */
|
|
317
|
+
probeReadRootCandidate?: import("./types.js").ReadRootGrantCandidate;
|
|
313
318
|
/** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules`
|
|
314
319
|
* shape); carries the matched rule's text. Present ⇔ a rule someone WROTE says "ask about this",
|
|
315
320
|
* never for a `defaultAction:"ask"` fallback (an unmatched call is default-closed posture, not a
|
|
@@ -1013,6 +1018,15 @@ export interface AskRequest {
|
|
|
1013
1018
|
* structure: render the entries as data, never re-derive structure by splitting or joining them.
|
|
1014
1019
|
* The durable park route carries the same value as `RiskDescriptor.probeCause`. */
|
|
1015
1020
|
readonly probeCause?: import("./checkpoint-store.js").ProbeCause;
|
|
1021
|
+
/**
|
|
1022
|
+
* The ask-facing face of {@link import("./types.js").ReversibilityVerdict.readRootCandidate}, which owns
|
|
1023
|
+
* the meaning, the absence reading and the spelling contract: PRESENT ⇒ declaring this one directory
|
|
1024
|
+
* clears THIS ask, so a card can offer "add it and stop being asked" beside "yes, this once". Filled at
|
|
1025
|
+
* the synchronous MINT station by the tighten that ran the probe — the three INHERITED-lane sites present
|
|
1026
|
+
* an ancestor's decision from upstream of it and carry none, and the durable park row has NO twin: a
|
|
1027
|
+
* parked out-of-root ask renders the confirmation alone.
|
|
1028
|
+
*/
|
|
1029
|
+
readonly readRootCandidate?: import("./types.js").ReadRootGrantCandidate;
|
|
1016
1030
|
/** design/252 G-2: the RULE-PROVENANCE evidence behind this ask — the org snapshot revision and the
|
|
1017
1031
|
* matched rules' own identity keys, each present as a value or as a named absence (see
|
|
1018
1032
|
* {@link AskRuleEvidence}). Present on EVERY ask the engine mints, so a consumer never has to tell a
|
|
@@ -1121,9 +1135,10 @@ export interface AskRequest {
|
|
|
1121
1135
|
* probe declared structural — for the built-in shell probe, a read outside the directories this
|
|
1122
1136
|
* session declared. A surface must NOT point at rule-writing here — "allow rules silence the
|
|
1123
1137
|
* classifier's questions, never a mandated one", so a rule minted in answer to this line would
|
|
1124
|
-
* never take effect where it was minted.
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1138
|
+
* never take effect where it was minted. Whether a DIRECTORY grant is worth pointing at is not this
|
|
1139
|
+
* seat's to say: it is answered per ask by {@link readRootCandidate}, which is present exactly when
|
|
1140
|
+
* widening the session's read directories would clear this one and absent for every other member of
|
|
1141
|
+
* the family — so a surface reads that seat and never infers an answer from this word;
|
|
1127
1142
|
* · `"shadowed"` — a rule the person already wrote is speaking and does not clear this ask (a
|
|
1128
1143
|
* matching allow rule that the lane refuses to honor here, or their own explicit `ask` rule);
|
|
1129
1144
|
* · `"lane_cannot_speak"` — the lane is armed and this ask IS clearable, but the offer lane
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
4
|
+
import { decidersNote, settledByPerson } from "./gate-outcome.js";
|
|
4
5
|
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
5
6
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
6
7
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
@@ -18,9 +19,6 @@ const DECISION_REASON_SET = new Set(DECISION_REASONS);
|
|
|
18
19
|
function settledByNobody(kind) {
|
|
19
20
|
return { kind, who: { party: "none" }, when: Date.now() };
|
|
20
21
|
}
|
|
21
|
-
function settledByPerson(kind, approver, note) {
|
|
22
|
-
return { kind, who: { party: "person", ...(approver !== undefined ? { approver } : {}) }, when: Date.now(), ...(note !== undefined ? { note } : {}) };
|
|
23
|
-
}
|
|
24
22
|
const ENGINE_SETTLEMENTS = new WeakMap();
|
|
25
23
|
function attestEngineSettlement(d, record, call) {
|
|
26
24
|
const s = record.settlement;
|
|
@@ -1273,7 +1271,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1273
1271
|
settlement: settledByNobody("approver_contract"),
|
|
1274
1272
|
};
|
|
1275
1273
|
}
|
|
1276
|
-
reasonText = typeof suppliedReason === "string"
|
|
1274
|
+
reasonText = typeof suppliedReason === "string" ? suppliedReason : undefined;
|
|
1277
1275
|
}
|
|
1278
1276
|
catch (err) {
|
|
1279
1277
|
return {
|
|
@@ -1283,15 +1281,16 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1283
1281
|
settlement: settledByNobody("approver_contract"),
|
|
1284
1282
|
};
|
|
1285
1283
|
}
|
|
1284
|
+
const note = decidersNote(reasonText);
|
|
1286
1285
|
return {
|
|
1287
1286
|
action: "deny",
|
|
1288
1287
|
message: supplied === "timeout"
|
|
1289
|
-
? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${
|
|
1290
|
-
: humanRefusalMessage(req,
|
|
1288
|
+
? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${note !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", note, REVIEWER_NOTE_MAX_BODY)}` : ""}`
|
|
1289
|
+
: humanRefusalMessage(req, note),
|
|
1291
1290
|
decisionReason: "mode",
|
|
1292
1291
|
settlement: supplied === "timeout"
|
|
1293
1292
|
? { kind: "approval_window_expired", who: { party: "host" }, when: Date.now() }
|
|
1294
|
-
: settledByPerson("human_refused", attribution.approver,
|
|
1293
|
+
: settledByPerson("human_refused", attribution.approver, note),
|
|
1295
1294
|
};
|
|
1296
1295
|
}
|
|
1297
1296
|
if (suppliedEdit === undefined)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deferral gate's POLICY SEAT as vocabulary: the three states a deployment may declare
|
|
3
|
+
* (`RunnerDeps.toolSearch`), the word a disclosure reports, and the ONE reader that lowers the seat to the
|
|
4
|
+
* single number the gate compares against. Pure — no harness, no roster, no I/O. `runner/tool-defer-gate.ts`
|
|
5
|
+
* owns the gate that consumes the number.
|
|
6
|
+
*/
|
|
7
|
+
/** The default threshold percent: with no seat, the candidate set defers when its inlined size reaches this
|
|
8
|
+
* percent of the model's context window. `DEFER_THRESHOLD_FRACTION` is derived from it. */
|
|
9
|
+
export declare const TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT = 10;
|
|
10
|
+
/** The typed refusal a `toolSearch` seat outside its domain raises: a bad value never selects an arm
|
|
11
|
+
* silently and never folds back to the default gate. */
|
|
12
|
+
export declare const TOOL_SEARCH_INVALID_CODE = "config.tool_search_invalid";
|
|
13
|
+
/**
|
|
14
|
+
* The deployment's deferral-gate SETTING: `true` = the candidate set defers on every request, `false` = it never defers (and with no caller list no
|
|
15
|
+
* ToolSearch mounts), `{mode:"auto", thresholdPercent}` = the threshold gate at that percent of the context window (omitted ⇒
|
|
16
|
+
* {@link TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT}). Absent ⇒ that default — the pre-seat behavior.
|
|
17
|
+
*
|
|
18
|
+
* @contract defer_gate.one_threshold — the three states are THREE VALUES OF ONE NUMBER, never three code paths: {@link resolveToolSearch} lowers the seat to
|
|
19
|
+
* one threshold fraction (`true` ⇒ `0`, so every candidate byte is over budget; `false` ⇒ `Infinity`, so no candidate set reaches it; `auto:N` ⇒ `N/100`) and
|
|
20
|
+
* `deferralGate` keeps its ONE comparison `gatedChars ≥ thresholdChars`; a second arm for a new state breaks this contract rather than extending it. It
|
|
21
|
+
* governs rule ② ONLY — `TaskSpec.deferTools` still defers under every state, `alwaysLoadTools` still pins first, and `RunnerDeps.deferMode` remains the
|
|
22
|
+
* orthogonal knob that WIDENS rule ②'s candidate set.
|
|
23
|
+
*/
|
|
24
|
+
export type ToolSearchSetting = boolean | {
|
|
25
|
+
mode: "auto";
|
|
26
|
+
thresholdPercent?: number;
|
|
27
|
+
};
|
|
28
|
+
/** The three arms of {@link ToolSearchSetting} as one word — the disclosure face's vocabulary. */
|
|
29
|
+
export declare const TOOL_SEARCH_POLICIES: readonly ["always", "never", "auto"];
|
|
30
|
+
export type ToolSearchPolicy = (typeof TOOL_SEARCH_POLICIES)[number];
|
|
31
|
+
/** The lowered seat: the word a disclosure reports, and the ONE number the gate compares against. */
|
|
32
|
+
export interface ResolvedToolSearch {
|
|
33
|
+
policy: ToolSearchPolicy;
|
|
34
|
+
/** Present iff `policy === "auto"` — the percent the deployment asked for (or the default). */
|
|
35
|
+
thresholdPercent?: number;
|
|
36
|
+
/** The fraction of the context window the candidate set must reach: `0` (always) / `Infinity` (never) /
|
|
37
|
+
* `thresholdPercent / 100`. This is the gate's only input from the seat. */
|
|
38
|
+
fraction: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The ONE reader of {@link ToolSearchSetting} (`@contract defer_gate.one_threshold`). A value outside the domain refuses loudly with
|
|
42
|
+
* {@link TOOL_SEARCH_INVALID_CODE} — an unparseable knob must not fall back to the default gate while the deployment believes its declaration took effect.
|
|
43
|
+
* The object form's MEMBER SET is part of that domain (`mode` + `thresholdPercent`, nothing else): an unknown key is the same silent fold one level down,
|
|
44
|
+
* since a misspelled percent lowers to the default while the declaration looks like it took effect.
|
|
45
|
+
* `thresholdPercent` must be a finite number in `[0, 100]`, and the two ends are NOT symmetric: `auto:0` is
|
|
46
|
+
* an exact alias for `true` (threshold 0), while `auto:100` is the FULL-WINDOW threshold, not `false`. Only
|
|
47
|
+
* `false` never defers.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveToolSearch(setting: ToolSearchSetting | undefined): ResolvedToolSearch;
|
|
50
|
+
/**
|
|
51
|
+
* Describe a REJECTED value for a refusal message — the ONE describer the config seats share — without ever
|
|
52
|
+
* throwing, and without letting the rejected value NARRATE ITSELF.
|
|
53
|
+
*
|
|
54
|
+
* Two failures, one root. `JSON.stringify` is not total (a BigInt and a circular object make it throw, and a
|
|
55
|
+
* formatter that throws inside a refusal replaces the coded refusal with an uncoded `TypeError`) — and where
|
|
56
|
+
* it does not throw it calls the VALUE'S OWN `toJSON`, i.e. it asks the thing being rejected to say what it
|
|
57
|
+
* is. Measured: `{ toJSON: () => "open" }` described itself as `"open"`, so a refusal of an illegal object
|
|
58
|
+
* read as a refusal of a legal word; `{ toJSON: () => undefined }` fell through to `String(value)` and read
|
|
59
|
+
* `[object Object]`; a function printed its whole source (unbounded, and equally forgeable through
|
|
60
|
+
* `toString`); and `NaN` / `Infinity` — the two numbers these seats reject most — both came out as `null`,
|
|
61
|
+
* the spelling of a value the caller never wrote.
|
|
62
|
+
*
|
|
63
|
+
* So: PRIMITIVES are spelled exactly (a number through `String`, which is total and keeps `NaN`, `Infinity`
|
|
64
|
+
* and the `-0` a caller can only have written deliberately); anything else is DESCRIBED structurally — its
|
|
65
|
+
* shape, its constructor's name and its own top-level keys, bounded. Reading those may still hit a proxy's
|
|
66
|
+
* traps (every read is guarded), but NOTHING a read hands back is turned into text unless it is already a
|
|
67
|
+
* primitive of the expected domain — a formatting hook of the value is never what writes this sentence. A description is worth less to a reader than the value would be; a WRONG value is worth less than
|
|
68
|
+
* nothing, because it sends them to fix something they did not write.
|
|
69
|
+
*/
|
|
70
|
+
export declare function describeRejectedValue(value: unknown): string;
|
|
71
|
+
/**
|
|
72
|
+
* The INERT spelling of a lowered policy — the exact seat value that lowers back to it, with no accessor,
|
|
73
|
+
* proxy or prototype left on the path. A nested accessor (`get thresholdPercent()`) answers every later read
|
|
74
|
+
* differently, so a phase that runs several passes over one cached object still measures several different
|
|
75
|
+
* numbers. Round-trip: `resolveToolSearch(toolSearchSettingOf(r))` deep-equals `r` for every `r` this module
|
|
76
|
+
* mints.
|
|
77
|
+
*/
|
|
78
|
+
export declare function toolSearchSettingOf(resolved: ResolvedToolSearch): ToolSearchSetting;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT = 10;
|
|
2
|
+
export const TOOL_SEARCH_INVALID_CODE = "config.tool_search_invalid";
|
|
3
|
+
export const TOOL_SEARCH_POLICIES = ["always", "never", "auto"];
|
|
4
|
+
export function resolveToolSearch(setting) {
|
|
5
|
+
if (setting === undefined)
|
|
6
|
+
return { policy: "auto", thresholdPercent: TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT, fraction: TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT / 100 };
|
|
7
|
+
if (setting === true)
|
|
8
|
+
return { policy: "always", fraction: 0 };
|
|
9
|
+
if (setting === false)
|
|
10
|
+
return { policy: "never", fraction: Number.POSITIVE_INFINITY };
|
|
11
|
+
if (typeof setting !== "object" || setting === null) {
|
|
12
|
+
throw toolSearchRefusal(`invalid value ${describeRejectedValue(setting)} — expected true, false, {mode:"auto"} or {mode:"auto", thresholdPercent:N}`);
|
|
13
|
+
}
|
|
14
|
+
let mode;
|
|
15
|
+
let percent;
|
|
16
|
+
let keys = [];
|
|
17
|
+
try {
|
|
18
|
+
keys = Object.keys(setting);
|
|
19
|
+
mode = setting.mode;
|
|
20
|
+
percent = setting.thresholdPercent;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw toolSearchRefusal(`its members could not be read (a throwing accessor, or a revoked proxy) — expected true, false, {mode:"auto"} or {mode:"auto", thresholdPercent:N}`);
|
|
24
|
+
}
|
|
25
|
+
if (mode !== "auto") {
|
|
26
|
+
throw toolSearchRefusal(`invalid value ${describeRejectedValue(setting)} — expected true, false, {mode:"auto"} or {mode:"auto", thresholdPercent:N}`);
|
|
27
|
+
}
|
|
28
|
+
const stray = keys.find((k) => k !== "mode" && k !== "thresholdPercent");
|
|
29
|
+
if (stray !== undefined) {
|
|
30
|
+
throw toolSearchRefusal(`unknown member ${JSON.stringify(stray)} — the object form carries mode and thresholdPercent only`);
|
|
31
|
+
}
|
|
32
|
+
if (percent === undefined)
|
|
33
|
+
return { policy: "auto", thresholdPercent: TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT, fraction: TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT / 100 };
|
|
34
|
+
if (typeof percent !== "number" || !Number.isFinite(percent) || percent < 0 || percent > 100) {
|
|
35
|
+
throw toolSearchRefusal(`thresholdPercent ${describeRejectedValue(percent)} is not a finite percent in [0, 100]`);
|
|
36
|
+
}
|
|
37
|
+
return { policy: "auto", thresholdPercent: percent, fraction: percent / 100 };
|
|
38
|
+
}
|
|
39
|
+
export function describeRejectedValue(value) {
|
|
40
|
+
try {
|
|
41
|
+
switch (typeof value) {
|
|
42
|
+
case "bigint":
|
|
43
|
+
return `${value}n`;
|
|
44
|
+
case "symbol":
|
|
45
|
+
return String(value);
|
|
46
|
+
case "string":
|
|
47
|
+
return JSON.stringify(value);
|
|
48
|
+
case "number":
|
|
49
|
+
return Object.is(value, -0) ? "-0" : String(value);
|
|
50
|
+
case "boolean":
|
|
51
|
+
case "undefined":
|
|
52
|
+
return String(value);
|
|
53
|
+
case "function":
|
|
54
|
+
return "a function";
|
|
55
|
+
}
|
|
56
|
+
if (value === null || value === undefined)
|
|
57
|
+
return String(value);
|
|
58
|
+
let ctor = "";
|
|
59
|
+
try {
|
|
60
|
+
const name = value.constructor?.name;
|
|
61
|
+
if (typeof name === "string" && name !== "" && name !== "Object" && name !== "Array")
|
|
62
|
+
ctor = ` (${name.slice(0, 40)})`;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
const n = value.length;
|
|
68
|
+
return `an array${ctor}${typeof n === "number" && Number.isSafeInteger(n) && n >= 0 ? ` of ${n} member(s)` : ""}`;
|
|
69
|
+
}
|
|
70
|
+
const keys = Object.keys(value);
|
|
71
|
+
if (keys.length === 0)
|
|
72
|
+
return `an object${ctor} with no keys`;
|
|
73
|
+
return `an object${ctor} with keys [${keys.slice(0, 5).map((k) => k.slice(0, 40)).join(", ")}${keys.length > 5 ? ", …" : ""}]`;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return `a value of type ${value === null ? "null" : typeof value} that cannot be described`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function toolSearchSettingOf(resolved) {
|
|
80
|
+
if (resolved.policy === "always")
|
|
81
|
+
return true;
|
|
82
|
+
if (resolved.policy === "never")
|
|
83
|
+
return false;
|
|
84
|
+
return { mode: "auto", thresholdPercent: resolved.thresholdPercent ?? TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT };
|
|
85
|
+
}
|
|
86
|
+
function toolSearchRefusal(problem) {
|
|
87
|
+
const e = new Error(`RunnerDeps.toolSearch: ${problem}; a bad value refuses loudly, nothing falls back to the default gate.`);
|
|
88
|
+
e.code = TOOL_SEARCH_INVALID_CODE;
|
|
89
|
+
return e;
|
|
90
|
+
}
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -62,12 +62,45 @@ export interface ReversibilityVerdict {
|
|
|
62
62
|
* its own deployment a re-confirmation and can never turn an ask into an allow.
|
|
63
63
|
*
|
|
64
64
|
* The built-in shell probe sets it for exactly one family: a listed reader naming a path outside the
|
|
65
|
-
* session's allowed directories.
|
|
66
|
-
*
|
|
67
|
-
* it
|
|
68
|
-
*
|
|
65
|
+
* session's allowed directories. That demotion HAS a clearing channel — widening the declared read
|
|
66
|
+
* directories retires it — and where the probe can name the directory, {@link readRootCandidate} carries
|
|
67
|
+
* it. A mandated ask WITHOUT that member has no configuration behind it: the rest of the family (an
|
|
68
|
+
* operator `shellGate:"always"`, the tool's egress/irreversibility marks, an org demand, an out-of-root
|
|
69
|
+
* arm with no named directory) is confirm-or-refuse, and must not be worded as though a grant answers it.
|
|
69
70
|
*/
|
|
70
71
|
mandated?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The directory-scoped GRANT that would clear THIS ask, when the probe can name one: PRESENT ⇒ the only
|
|
74
|
+
* thing between this call and a plain `reversible: true` verdict is the session's declared read
|
|
75
|
+
* containment, and adding {@link ReadRootGrantCandidate.dir} to it makes the same call run with no card.
|
|
76
|
+
*
|
|
77
|
+
* ABSENT is the ordinary world and no claim that nothing would clear the ask — it covers every demotion
|
|
78
|
+
* with no directory answer (a deny row, a read the walk could not read, an unenumerable expansion, a
|
|
79
|
+
* shape refused whatever the roots say) and every probe silent on this member. Read presence, never
|
|
80
|
+
* absence. ADVISORY display metadata, never adjudication input.
|
|
81
|
+
*/
|
|
82
|
+
readRootCandidate?: ReadRootGrantCandidate;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A directory-scoped grant a surface can offer on an approval card — see
|
|
86
|
+
* {@link ReversibilityVerdict.readRootCandidate} and `AskRequest.readRootCandidate`.
|
|
87
|
+
*
|
|
88
|
+
* @contract shell.read_boundary.grant_candidate_is_the_grant — {@link dir} is the string to ADD, not a
|
|
89
|
+
* string to show: a consumer that renders a shortened or home-relative form must send this exact spelling
|
|
90
|
+
* back when the person accepts. Core mints one spelling for both errands, and the promise is over THAT
|
|
91
|
+
* spelling reaching the directories the boundary compares against: a deployment that REWRITES a declared
|
|
92
|
+
* directory — canonicalizing a symlinked one included — must rewrite the command's spelling the same way,
|
|
93
|
+
* or it will compare a rewritten root against an unrewritten operand and ask again. Where the two differ
|
|
94
|
+
* the ask was unclearable before this seat existed and still is; the offer names the directory the command
|
|
95
|
+
* spelled, which is the only one derivable from the text.
|
|
96
|
+
*/
|
|
97
|
+
export interface ReadRootGrantCandidate {
|
|
98
|
+
/** An absolute, lexically folded directory in the boundary's own spelling; never a file or a pattern. */
|
|
99
|
+
readonly dir: string;
|
|
100
|
+
/** Always the literal `true` — a shape, not a value, so the seat cannot be widened into a "maybe this
|
|
101
|
+
* helps" hint without a visible type change. It claims THIS ask: not the deny layer (judged by pattern
|
|
102
|
+
* rather than by root, and unmoved by any grant), and not another ask the same call may raise. */
|
|
103
|
+
readonly clearsThisAsk: true;
|
|
71
104
|
}
|
|
72
105
|
export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
73
106
|
/**
|
|
@@ -852,6 +885,14 @@ export interface ToolExecuteContext {
|
|
|
852
885
|
* ONLY as the delegation carrier — no tool adapts its model-facing behavior on it.
|
|
853
886
|
*/
|
|
854
887
|
readFace?: "roots";
|
|
888
|
+
/**
|
|
889
|
+
* The WRITE-face clamp carrier ({@link TaskSpec.writeFace}), the read carrier's twin: present (as
|
|
890
|
+
* "roots") ONLY when this run RESOLVED to the roots write face, so a delegation tool pins every
|
|
891
|
+
* child spec to roots — without it a roots-narrowed parent's children would resolve the shared
|
|
892
|
+
* deployment seat (possibly "open") and write wider than the parent could. Strictly a tightening
|
|
893
|
+
* carrier, and independent of {@link readFace}.
|
|
894
|
+
*/
|
|
895
|
+
writeFace?: "roots";
|
|
855
896
|
/**
|
|
856
897
|
* design/199 件B — the parent task's OWN deny-set additions ({@link TaskSpec.readDenyPatterns}),
|
|
857
898
|
* traveling the delegation tree add-only (a child judges at least every entry its parent judged).
|
package/dist/core/types.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { AgentTool, ThinkingLevel, ToolInputValidationContext, ToolInputVer
|
|
|
13
13
|
import type { Model, StreamFn } from "../internal/llm.js";
|
|
14
14
|
export type { ModelRef, ModelRole, RoleSpec, ModelRoles, Brain } from "./model-seat.js";
|
|
15
15
|
export type { StaleToolResultOffloadOptions, ImageInput, TaskSpec, ShellGateDoctrine } from "./task-spec.js";
|
|
16
|
-
export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
|
|
16
|
+
export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ReadRootGrantCandidate, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
|
|
17
17
|
export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
|
|
18
18
|
export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
|
|
19
19
|
export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
|
|
@@ -28,5 +28,5 @@ export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegatio
|
|
|
28
28
|
export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
|
|
29
29
|
export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
|
|
30
30
|
export type { EngineNotice } from "./engine-notice.js";
|
|
31
|
-
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, undrainedUserInputNotices } from "./engine-notice.js";
|
|
31
|
+
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, undrainedUserInputNotices } from "./engine-notice.js";
|
|
32
32
|
export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
|
package/dist/core/types.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
2
2
|
export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
|
|
3
|
-
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, undrainedUserInputNotices } from "./engine-notice.js";
|
|
3
|
+
export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, undrainedUserInputNotices } from "./engine-notice.js";
|