@sema-agent/core 7.17.6 → 7.18.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 +165 -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/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -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/auto-mode.d.ts +141 -45
- package/dist/core/auto-mode.js +109 -3
- package/dist/core/checkpoint-store.d.ts +16 -2
- package/dist/core/engine-notice.d.ts +47 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +17 -2
- package/dist/core/gate-outcome.js +16 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +8 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -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 +89 -0
- package/dist/core/runner/classifier-ladder.js +65 -0
- package/dist/core/runner/contracts.d.ts +69 -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/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-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
- package/dist/core/runner/prepare-config-doors.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.js +5 -3
- 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.js +4 -2
- 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 +18 -13
- 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-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 +35 -41
- 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 +3 -1
- 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 +16 -19
- package/dist/core/runner/synthetic-tools.js +18 -10
- 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-deps.d.ts +48 -7
- 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 +20 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- 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 +8 -0
- package/dist/core/types.d.ts +1 -1
- 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/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 +10 -5
- package/dist/index.js +9 -5
- 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 +54 -16
- package/dist/tools/fs/bash-readonly-classifier.js +375 -530
- 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 +158 -22
- 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/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +3 -3
- package/dist/tools/fs/safety.js +4 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -9
- package/wire-changes/core@7.17.6.json +81 -0
- package/wire-changes/core@7.17.7.json +49 -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
|
|
@@ -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
|
|
@@ -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
|
@@ -852,6 +852,14 @@ export interface ToolExecuteContext {
|
|
|
852
852
|
* ONLY as the delegation carrier — no tool adapts its model-facing behavior on it.
|
|
853
853
|
*/
|
|
854
854
|
readFace?: "roots";
|
|
855
|
+
/**
|
|
856
|
+
* The WRITE-face clamp carrier ({@link TaskSpec.writeFace}), the read carrier's twin: present (as
|
|
857
|
+
* "roots") ONLY when this run RESOLVED to the roots write face, so a delegation tool pins every
|
|
858
|
+
* child spec to roots — without it a roots-narrowed parent's children would resolve the shared
|
|
859
|
+
* deployment seat (possibly "open") and write wider than the parent could. Strictly a tightening
|
|
860
|
+
* carrier, and independent of {@link readFace}.
|
|
861
|
+
*/
|
|
862
|
+
writeFace?: "roots";
|
|
855
863
|
/**
|
|
856
864
|
* design/199 件B — the parent task's OWN deny-set additions ({@link TaskSpec.readDenyPatterns}),
|
|
857
865
|
* traveling the delegation tree add-only (a child judges at least every entry its parent judged).
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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";
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import type { RunnerDeps, TaskSpec } from "./types.js";
|
|
24
24
|
import { type StoreDurability } from "./checkpoint-store.js";
|
|
25
|
+
import type { WiringManifestHookEntry } from "./hook-seats.js";
|
|
25
26
|
import type { McpDelivered, McpFailureKind } from "./mcp-failure.js";
|
|
26
27
|
export type { StoreDurability };
|
|
27
28
|
/** The manifest-face projection of {@link StoreDurability}: `"declared_durable"` (the name says it —
|
|
@@ -110,11 +111,27 @@ export interface WiringManifestMcpEntry {
|
|
|
110
111
|
error?: string;
|
|
111
112
|
toolCount: number;
|
|
112
113
|
}
|
|
114
|
+
/** Which LSP lanes this leg armed. The two lanes are INDEPENDENT and the word is their product: `tool` = the
|
|
115
|
+
* code-intelligence tool mounted and nothing more; `diagnostics` = the push lane armed (a registry-feeding
|
|
116
|
+
* manager, write-capable hands, no opt-out) while the tool is NOT on the roster; `tool_and_diagnostics` =
|
|
117
|
+
* both. A leg whose `LSP` tool was excluded can still be pushing diagnostics. */
|
|
118
|
+
export declare const LSP_LANES: readonly ["tool", "diagnostics", "tool_and_diagnostics"];
|
|
119
|
+
export type LspLane = (typeof LSP_LANES)[number];
|
|
120
|
+
/** The contract id the LSP tool mounts under (`createLspTool`) — the roster's structural discriminator, which
|
|
121
|
+
* a disclosure keys on instead of re-spelling the wire name. */
|
|
122
|
+
export declare const LSP_CONTRACT_ID = "core.lsp@1";
|
|
123
|
+
/** The manifest's LSP seam from its two INDEPENDENT facts (see {@link WiringManifest.lsp}): the lane word is
|
|
124
|
+
* their product, and is absent only when neither lane is live. */
|
|
125
|
+
export declare function deriveLspSeam(mounted: boolean, diagnosticsLive: boolean): {
|
|
126
|
+
mounted: boolean;
|
|
127
|
+
lane?: LspLane;
|
|
128
|
+
};
|
|
113
129
|
/**
|
|
114
130
|
* design/173 §2.2/§8.4 — the wiring manifest. Consumers MUST ignore unknown fields
|
|
115
131
|
* (`schemaVersion` bumps only on a semantic break of an EXISTING field). The TaskEvent face is the
|
|
116
132
|
* host/operator plane — the manifest never enters model context.
|
|
117
133
|
*/
|
|
134
|
+
export type { WiringManifestHookEntry };
|
|
118
135
|
export interface WiringManifest {
|
|
119
136
|
schemaVersion: 1;
|
|
120
137
|
/** Present on EFFECTIVE (per-leg) manifests only; the static half has no leg. */
|
|
@@ -280,6 +297,28 @@ export interface WiringManifest {
|
|
|
280
297
|
* `tool_roster_delta` event, never as a re-emitted manifest.
|
|
281
298
|
*/
|
|
282
299
|
tools?: import("./tool-roster.js").ToolRoster;
|
|
300
|
+
/**
|
|
301
|
+
* EFFECTIVE half only: the hook seats this leg wired, one entry per family, in {@link import("./hook-seats.js").HOOK_FAMILIES} order. PRESENT IFF at least one
|
|
302
|
+
* seat is wired — a deployment that wires none reports nothing here and its manifest (and {@link configFingerprint}) is byte-for-byte what it was; an empty
|
|
303
|
+
* array is never emitted, so absence reads as "this leg wired no hook RECORD of its own" — NOT as "no hook runs": a delegated child with no local record can
|
|
304
|
+
* still be screened (and refused) by an ancestor's `preToolUse` through the gate's constraint fold, which this face deliberately does not call a hook of
|
|
305
|
+
* this leg. Every entry is deployment code the engine awaits on the critical path, fail-closed on the two decision seats. EXCLUDED from
|
|
306
|
+
* {@link configFingerprint}: the record is a per-task seat, so one assembly's legs legitimately differ on it.
|
|
307
|
+
*/
|
|
308
|
+
hooks?: readonly WiringManifestHookEntry[];
|
|
309
|
+
/**
|
|
310
|
+
* EFFECTIVE half only: the LSP code-intelligence seam. PRESENT IFF a manager seat resolved
|
|
311
|
+
* (`spec.lspManager ?? deps.lspManager`); a deployment that wires none reports nothing (byte-for-byte
|
|
312
|
+
* unchanged, fingerprint included). `mounted` is the ROSTER fact, not the seat fact — a wired manager
|
|
313
|
+
* whose `LSP` tool was excluded reports `false` — and `lane` is INDEPENDENT of it: the diagnostics push
|
|
314
|
+
* lane arms on the manager, the hands and the opt-out, never on the mount, so
|
|
315
|
+
* `{mounted:false, lane:"diagnostics"}` is a real and reachable state. `lane` is absent only when
|
|
316
|
+
* neither lane is live.
|
|
317
|
+
*/
|
|
318
|
+
lsp?: {
|
|
319
|
+
mounted: boolean;
|
|
320
|
+
lane?: LspLane;
|
|
321
|
+
};
|
|
283
322
|
/** EFFECTIVE half only — a short, non-sensitive fingerprint (sha256 prefix over the canonical
|
|
284
323
|
* JSON of this manifest's own resolved facts; every field here is an enum/boolean/count, no
|
|
285
324
|
* secrets) so an operator can correlate legs that ran under the same resolved assembly. */
|
|
@@ -351,6 +390,13 @@ export interface WiringFacts {
|
|
|
351
390
|
mcp?: readonly WiringManifestMcpEntry[];
|
|
352
391
|
/** Effective half only — the leg's minted tool roster (see {@link WiringManifest.tools}). */
|
|
353
392
|
tools?: import("./tool-roster.js").ToolRoster;
|
|
393
|
+
/** Effective half only — the wired hook seats ({@link WiringManifest.hooks}); an empty array and an absent value derive the same thing (no section). */
|
|
394
|
+
hooks?: readonly WiringManifestHookEntry[];
|
|
395
|
+
/** Effective half only — the LSP seam ({@link WiringManifest.lsp}); absent ⇒ no manager wired. */
|
|
396
|
+
lsp?: {
|
|
397
|
+
mounted: boolean;
|
|
398
|
+
lane?: LspLane;
|
|
399
|
+
};
|
|
354
400
|
}
|
|
355
401
|
/** Named view of the deps seats the static half reads (a `Pick` of the real {@link RunnerDeps} —
|
|
356
402
|
* single-source shapes, no parallel hand-copied interface). */
|
|
@@ -4,6 +4,12 @@ import { resolveCheckpointStore } from "./checkpoint-store.js";
|
|
|
4
4
|
import { isLiveQuestionFace } from "./ask-question.js";
|
|
5
5
|
import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
6
6
|
export const AUTO_MODE_ARM_REASONS = ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
|
|
7
|
+
export const LSP_LANES = ["tool", "diagnostics", "tool_and_diagnostics"];
|
|
8
|
+
export const LSP_CONTRACT_ID = "core.lsp@1";
|
|
9
|
+
export function deriveLspSeam(mounted, diagnosticsLive) {
|
|
10
|
+
const lane = mounted ? (diagnosticsLive ? "tool_and_diagnostics" : "tool") : diagnosticsLive ? "diagnostics" : undefined;
|
|
11
|
+
return { mounted, ...(lane !== undefined ? { lane } : {}) };
|
|
12
|
+
}
|
|
7
13
|
export function resolveDeclaredDurability(store, storeName) {
|
|
8
14
|
const declared = store?.durability;
|
|
9
15
|
if (declared === undefined)
|
|
@@ -133,9 +139,11 @@ export function deriveWiringManifest(facts) {
|
|
|
133
139
|
...(facts.half === "effective" && facts.autoMode !== undefined ? { autoMode: readAutoModeFact(facts.autoMode) } : {}),
|
|
134
140
|
...(facts.half === "effective" && facts.mcp !== undefined ? { mcp: facts.mcp.map((e) => ({ ...e })) } : {}),
|
|
135
141
|
...(facts.half === "effective" && facts.tools !== undefined ? { tools: facts.tools } : {}),
|
|
142
|
+
...(facts.half === "effective" && facts.hooks !== undefined && facts.hooks.length > 0 ? { hooks: facts.hooks.map((h) => ({ ...h })) } : {}),
|
|
143
|
+
...(facts.half === "effective" && facts.lsp !== undefined ? { lsp: { ...facts.lsp } } : {}),
|
|
136
144
|
};
|
|
137
145
|
if (facts.half === "effective") {
|
|
138
|
-
const { leg: _leg, mcp: _mcp, tools: _tools, ...assembly } = manifest;
|
|
146
|
+
const { leg: _leg, mcp: _mcp, tools: _tools, lsp: _lsp, hooks: _hooks, ...assembly } = manifest;
|
|
139
147
|
const { provenance: _askSeat, ...askForHash } = assembly.ask;
|
|
140
148
|
const { provenance: _questionSeat, ...questionForHash } = assembly.question;
|
|
141
149
|
manifest.configFingerprint = createHash("sha256")
|
|
@@ -359,7 +359,7 @@ export class NodeExecutionEnv {
|
|
|
359
359
|
const stderrTail = new RollingTailBuffer();
|
|
360
360
|
let settled = false;
|
|
361
361
|
let timedOut = false;
|
|
362
|
-
let
|
|
362
|
+
let terminationInFlight = false;
|
|
363
363
|
let callbackError;
|
|
364
364
|
let child;
|
|
365
365
|
const timeoutRef = {};
|
|
@@ -391,7 +391,7 @@ export class NodeExecutionEnv {
|
|
|
391
391
|
return { stdout: markTruncated(so.text, so.droppedBytes), stderr: markTruncated(se.text, se.droppedBytes) };
|
|
392
392
|
};
|
|
393
393
|
const onAbort = () => {
|
|
394
|
-
|
|
394
|
+
terminationInFlight = true;
|
|
395
395
|
if (child?.pid) {
|
|
396
396
|
const abortTarget = child;
|
|
397
397
|
killProcessTree(child.pid, { stillRunning: () => abortTarget.exitCode === null && abortTarget.signalCode === null });
|
|
@@ -480,10 +480,11 @@ export class NodeExecutionEnv {
|
|
|
480
480
|
timeoutMs === undefined
|
|
481
481
|
? undefined
|
|
482
482
|
: setTimeout(() => {
|
|
483
|
-
if (options?.autoBackgroundOnTimeout && !
|
|
483
|
+
if (options?.autoBackgroundOnTimeout && !terminationInFlight && adoptOnTimeoutRef.current) {
|
|
484
484
|
adoptOnTimeoutRef.current();
|
|
485
485
|
return;
|
|
486
486
|
}
|
|
487
|
+
terminationInFlight = true;
|
|
487
488
|
timedOut = true;
|
|
488
489
|
if (child?.pid) {
|
|
489
490
|
const timeoutTarget = child;
|
|
@@ -598,9 +599,11 @@ export class NodeExecutionEnv {
|
|
|
598
599
|
spoolPumpRef.current.unref?.();
|
|
599
600
|
}
|
|
600
601
|
const onDetach = (cause) => {
|
|
601
|
-
if (settled || !child)
|
|
602
|
+
if (settled || terminationInFlight || !child)
|
|
602
603
|
return false;
|
|
603
604
|
pumpSpoolRef.current?.();
|
|
605
|
+
if (settled || terminationInFlight)
|
|
606
|
+
return false;
|
|
604
607
|
const shellId = this.adoptRunningChild(child, stdoutTail.result(), stderrTail.result(), execSpoolRef.current !== undefined ? { spool: execSpoolRef.current, cursors: { out: spoolCursor.out, err: spoolCursor.err } } : undefined);
|
|
605
608
|
if (shellId === undefined)
|
|
606
609
|
return false;
|
|
@@ -616,6 +619,9 @@ export class NodeExecutionEnv {
|
|
|
616
619
|
adoptOnTimeoutRef.current = () => {
|
|
617
620
|
if (onDetach("timeout"))
|
|
618
621
|
return;
|
|
622
|
+
if (terminationInFlight)
|
|
623
|
+
return;
|
|
624
|
+
terminationInFlight = true;
|
|
619
625
|
timedOut = true;
|
|
620
626
|
if (child?.pid) {
|
|
621
627
|
const adoptFallbackTarget = child;
|