@sema-agent/core 7.21.1 → 7.23.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 +290 -1
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +2 -1
- package/dist/core/ask-origin.d.ts +1 -0
- package/dist/core/ask-origin.js +3 -2
- package/dist/core/auto-compaction.d.ts +2 -2
- package/dist/core/auto-mode-rebuild.js +6 -2
- package/dist/core/auto-mode.d.ts +40 -18
- package/dist/core/auto-mode.js +63 -45
- package/dist/core/checkpoint-store.d.ts +9 -13
- package/dist/core/context-edit.js +1 -1
- package/dist/core/engine-notice.d.ts +62 -1
- package/dist/core/engine-notice.js +14 -1
- package/dist/core/gate-bounded-await.d.ts +13 -3
- package/dist/core/gate-fold.js +3 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/mcp.d.ts +65 -0
- package/dist/core/mcp.js +190 -41
- package/dist/core/memory-engine/consolidation.d.ts +5 -0
- package/dist/core/memory-engine/consolidation.js +7 -3
- package/dist/core/memory-engine/engine.d.ts +8 -0
- package/dist/core/memory-engine/engine.js +25 -19
- package/dist/core/memory-engine/layout.d.ts +47 -9
- package/dist/core/memory-engine/layout.js +157 -57
- package/dist/core/memory-engine/session-incarnation.d.ts +27 -0
- package/dist/core/memory-engine/session-incarnation.js +63 -0
- package/dist/core/parent-spec-seat.d.ts +15 -3
- package/dist/core/parent-spec-seat.js +5 -0
- package/dist/core/park-selfcheck.js +1 -1
- package/dist/core/path-identity.d.ts +11 -4
- package/dist/core/path-identity.js +5 -2
- package/dist/core/permission-rule-model.d.ts +1 -2
- package/dist/core/read-root-candidate.js +11 -1
- package/dist/core/retention.d.ts +8 -1
- package/dist/core/retention.js +2 -0
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +25 -8
- package/dist/core/runner/classifier-ladder.js +1 -0
- package/dist/core/runner/contracts.d.ts +26 -15
- package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
- package/dist/core/runner/inherited-ask-grants.js +1 -0
- package/dist/core/runner/mcp-redial.d.ts +109 -0
- package/dist/core/runner/mcp-redial.js +60 -0
- package/dist/core/runner/memory-consolidation-driver.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +9 -12
- package/dist/core/runner/prepare-context-lane.d.ts +43 -1
- package/dist/core/runner/prepare-context-lane.js +25 -4
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -0
- package/dist/core/runner/prepare-hands-readface.js +13 -3
- package/dist/core/runner/prepare-memory.js +3 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +6 -1
- package/dist/core/runner/prepare-protocol-tools.js +20 -38
- package/dist/core/runner/prepare-task.js +4 -4
- package/dist/core/runner/prepare-turn-wiring.js +2 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +2 -1
- package/dist/core/runner/run-harness-handlers.js +4 -2
- package/dist/core/runner/run-identity-wiring.d.ts +3 -1
- package/dist/core/runner/run-identity-wiring.js +4 -1
- package/dist/core/runner/run-recovery-lanes.js +6 -3
- package/dist/core/runner/run-settle-and-teardown.d.ts +3 -1
- package/dist/core/runner/run-settle-and-teardown.js +6 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
- package/dist/core/runner/run-terminal-adoption.d.ts +4 -1
- package/dist/core/runner/run-terminal-adoption.js +2 -6
- package/dist/core/runner/runtask.js +8 -5
- package/dist/core/runner/stream-lifecycle-verbs.d.ts +5 -1
- package/dist/core/runner/stream-lifecycle-verbs.js +23 -2
- package/dist/core/runner/write-protection-station.d.ts +7 -25
- package/dist/core/runner/write-protection-station.js +18 -0
- package/dist/core/runner-deps.d.ts +16 -7
- package/dist/core/skill-listing-budget.d.ts +5 -4
- package/dist/core/task-registry.d.ts +0 -6
- package/dist/core/task-result.d.ts +31 -12
- package/dist/core/task-spec.d.ts +2 -1
- package/dist/core/task-stream.d.ts +26 -0
- package/dist/core/tool-spec.d.ts +5 -1
- package/dist/core/trace.d.ts +5 -0
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +23 -0
- package/dist/core/wiring-manifest.js +3 -0
- package/dist/core/write-protect.d.ts +64 -12
- package/dist/core/write-protect.js +88 -34
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
- package/dist/engine/execution-env/node-execution-env.js +5 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +2 -2
- package/dist/tools/fs/bash-program-position.d.ts +26 -5
- package/dist/tools/fs/bash-program-position.js +63 -16
- package/dist/tools/fs/bash-readonly-classifier.d.ts +27 -17
- package/dist/tools/fs/bash-readonly-classifier.js +134 -55
- package/dist/tools/fs/fs-bash.js +43 -18
- package/dist/tools/fs/fs-read.js +2 -1
- package/dist/tools/fs/fs-shared.d.ts +33 -7
- package/dist/tools/fs/fs-shared.js +12 -1
- package/dist/tools/fs/index.d.ts +4 -2
- package/dist/tools/fs/read-deny.d.ts +25 -11
- package/dist/tools/fs/read-deny.js +5 -1
- package/dist/tools/fs/safety.d.ts +38 -4
- package/dist/tools/fs/safety.js +31 -3
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +11 -1
- package/wire-changes/core@7.22.0.json +637 -0
- package/wire-changes/core@7.23.0.json +316 -0
package/dist/core/auto-mode.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ import type { ToolCallRequest } from "./tool-policy.js";
|
|
|
4
4
|
* sentence's parenthetical and the `auto_mode.classified` trace frame's `cause`, so the three spell one
|
|
5
5
|
* word (the deny's structural face carries only the round's FORM, `GateDisposition.cause`). `error` = the model leg
|
|
6
6
|
* threw or rejected (a route failure at classify time reads here too — the derived-route pre-flight fell
|
|
7
|
-
* back BEFORE any decide, so there is no separate word for it
|
|
7
|
+
* back BEFORE any decide, so there is no separate word for it; so does a question that never ASSEMBLED,
|
|
8
|
+
* whether its build threw or outran its cap — nothing was dispatched either way); `timeout` = a rung's
|
|
9
|
+
* DISPATCH cap.
|
|
8
10
|
*/
|
|
9
11
|
export declare const AUTO_MODE_UNAVAILABLE_CAUSES: readonly ["error", "timeout"];
|
|
10
12
|
export type AutoModeUnavailableCause = (typeof AUTO_MODE_UNAVAILABLE_CAUSES)[number];
|
|
@@ -100,8 +102,9 @@ export interface AutoModeClassifierRound {
|
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
/**
|
|
103
|
-
* The
|
|
104
|
-
*
|
|
105
|
+
* The word for "this classification was NEVER PUT TO A MODEL" — the failure is in assembling the QUESTION (the
|
|
106
|
+
* transcript window every rung shares), not in a rung's answer. The decider raises it for its own
|
|
107
|
+
* {@link AutoModeDeciderOptions.assemble} seat; a hook that owns assembly throws an error carrying this code.
|
|
105
108
|
*
|
|
106
109
|
* WHY IT EXISTS. The ladder advances on "the rung produced no ruling", and a hook that rejects looks exactly
|
|
107
110
|
* like that. But a classification whose prompt could not be built fails IDENTICALLY on every rung (the build is
|
|
@@ -113,9 +116,8 @@ export interface AutoModeClassifierRound {
|
|
|
113
116
|
* Carried as a `code` on the thrown error rather than a class so any classify hook — including one this
|
|
114
117
|
* package never constructed — can raise it without importing a constructor.
|
|
115
118
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* a slow transcript store from a slow model.
|
|
119
|
+
* A build that OUTRUNS its cap is the same fact and takes the same exit — see
|
|
120
|
+
* {@link AutoModeDeciderOptions.assemble}, the seat that bounds it.
|
|
119
121
|
*/
|
|
120
122
|
export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
|
|
121
123
|
/** Mint the marker above around whatever failed, keeping the original as `cause` for the host's own logs.
|
|
@@ -216,15 +218,28 @@ export interface AutoModeLadderRung {
|
|
|
216
218
|
readonly index: number;
|
|
217
219
|
readonly model: string;
|
|
218
220
|
}
|
|
219
|
-
/** The pluggable model leg: given the classify input
|
|
220
|
-
*
|
|
221
|
+
/** The pluggable model leg: given the classify input, the RUNG the ladder is on and whatever
|
|
222
|
+
* {@link AutoModeAssembleFn} built for this classification, return that model's raw text response.
|
|
223
|
+
* Deployment-injected; MAY throw / reject — the decider fail-closes. A rejection carrying
|
|
221
224
|
* {@link AUTO_MODE_ROUND_NOT_ASKED_CODE} says the failure was in assembling the QUESTION rather than in this
|
|
222
225
|
* rung's answer, and stops the walk instead of blaming the next rung. `rung` is `undefined`
|
|
223
|
-
* for a decider wired with no ladder
|
|
224
|
-
*
|
|
225
|
-
|
|
226
|
-
export
|
|
227
|
-
|
|
226
|
+
* for a decider wired with no ladder; `assembled` is `undefined` for one wired with no `assemble` seat (the
|
|
227
|
+
* leg then owns assembly, inside this rung's cap). All trailing parameters are OPTIONAL, not `| undefined`:
|
|
228
|
+
* a caller INVOKING it with one, two or three arguments must keep compiling. */
|
|
229
|
+
export type AutoModeClassifyFn<T = void> = (input: AutoModeClassifyInput, signal?: AbortSignal, rung?: AutoModeLadderRung, assembled?: T) => Promise<string>;
|
|
230
|
+
/** The pluggable ASSEMBLY leg: build this classification's question ONCE, before any rung is asked, on its own
|
|
231
|
+
* turn of {@link AutoModeDeciderOptions.timeoutMs}. The question is built from state that is not the model's
|
|
232
|
+
* (a transcript window, a store read), so its cost is not a rung's: a build that fails or outruns the cap is
|
|
233
|
+
* the {@link AUTO_MODE_ROUND_NOT_ASKED_CODE} fact (nothing dispatched, no rung blamed), and the rung cap
|
|
234
|
+
* bounds a DISPATCH alone — which is what makes that cap's `timeout` word true. MAY throw / reject; the
|
|
235
|
+
* decider fail-closes and does not start the walk. Its signal aborts when the run does AND at the cap.
|
|
236
|
+
* Omitted ⇒ `assembled` reaches `classify` as `undefined`. */
|
|
237
|
+
export type AutoModeAssembleFn<T> = (input: AutoModeClassifyInput, signal?: AbortSignal) => Promise<T>;
|
|
238
|
+
export interface AutoModeDeciderOptions<T = void> {
|
|
239
|
+
classify: AutoModeClassifyFn<T>;
|
|
240
|
+
/** Build the question ONCE per classification, before the walk, on its own turn of {@link timeoutMs} — see
|
|
241
|
+
* {@link AutoModeAssembleFn}. Omitted = the leg assembles inside `classify`, inside the rung cap. */
|
|
242
|
+
assemble?: AutoModeAssembleFn<T>;
|
|
228
243
|
/**
|
|
229
244
|
* The CANDIDATE LADDER this decider walks, in order — the models a classification may be asked on, NOT a
|
|
230
245
|
* second way to say what `classify` says (that hook is the DISPATCH; this is the sequence it dispatches
|
|
@@ -234,12 +249,14 @@ export interface AutoModeDeciderOptions {
|
|
|
234
249
|
*
|
|
235
250
|
* Omitted = a one-rung anonymous ladder: the leg as wired, no attribution on the verdict. An EMPTY array,
|
|
236
251
|
* or a rung that is not a non-empty string, is REFUSED loudly at construction (a deployment that computed
|
|
237
|
-
* its ladder and got nothing has a bug, not a default). COST: {@link timeoutMs} bounds ONE rung
|
|
238
|
-
* worst case
|
|
252
|
+
* its ladder and got nothing has a bug, not a default). COST: {@link timeoutMs} bounds ONE rung's dispatch
|
|
253
|
+
* and, once, the {@link assemble} call — worst case, the cap times the rungs walked, plus one.
|
|
239
254
|
*/
|
|
240
255
|
candidates?: readonly string[];
|
|
241
|
-
/** Hard cap on one classification round-trip — one LADDER RUNG, not the whole classification
|
|
242
|
-
* {@link candidates})
|
|
256
|
+
/** Hard cap on one classification round-trip — one LADDER RUNG's DISPATCH, not the whole classification
|
|
257
|
+
* (see {@link candidates}) — and, separately, the one {@link assemble} call that precedes the walk (ONE
|
|
258
|
+
* number: a second knob would be a second answer to "how long may a classification make the gate wait").
|
|
259
|
+
* Default 15_000 ms: a permission gate must not stall the whole run on a slow classifier. */
|
|
243
260
|
timeoutMs?: number;
|
|
244
261
|
/**
|
|
245
262
|
* Fired once per `decide` call with the verdict the gate is about to act on and the wall time it waited
|
|
@@ -259,6 +276,11 @@ export interface AutoModeClassified {
|
|
|
259
276
|
cause?: Extract<AutoModeVerdict, {
|
|
260
277
|
kind: "unavailable";
|
|
261
278
|
}>["cause"];
|
|
279
|
+
/** Present exactly when no model was asked because the QUESTION did not assemble: `"timeout"` = the build
|
|
280
|
+
* outran its cap, `"failed"` = it threw (also what a classify hook owning its own assembly reports). ABSENT
|
|
281
|
+
* = a model was asked. The verdict is `unavailable` / `error` either way — nothing was dispatched — so this
|
|
282
|
+
* is what tells an operator "the build is slow" from "the provider is down". */
|
|
283
|
+
notAsked?: "failed" | "timeout";
|
|
262
284
|
/** The classification's model attribution, present exactly when the decider was wired with a candidate ladder
|
|
263
285
|
* ({@link AutoModeClassifierRound}) — the same object the verdict carries. */
|
|
264
286
|
classifier?: AutoModeClassifierRound;
|
|
@@ -270,7 +292,7 @@ export interface AutoModeDecider {
|
|
|
270
292
|
/** The per-run decider: timeout + fail-closed error mapping, nothing carried between rounds. One instance per
|
|
271
293
|
* run (it binds the run's model leg and assembled prompt); a round that failed says nothing about the next,
|
|
272
294
|
* which asks the model again. */
|
|
273
|
-
export declare function createAutoModeDecider(opts: AutoModeDeciderOptions): AutoModeDecider;
|
|
295
|
+
export declare function createAutoModeDecider<T = void>(opts: AutoModeDeciderOptions<T>): AutoModeDecider;
|
|
274
296
|
/** The deployment's bounds for the denial limit (`RunnerDeps.autoMode.denialLimit`). Every member
|
|
275
297
|
* optional; an omitted member takes its CC default. A present member with a bad value is REFUSED
|
|
276
298
|
* loudly at construction (never clamped, never read as the default). */
|
package/dist/core/auto-mode.js
CHANGED
|
@@ -164,7 +164,8 @@ export function createAutoModeDecider(opts) {
|
|
|
164
164
|
const ladder = opts.candidates ?? [undefined];
|
|
165
165
|
const decide = async (input, signal) => {
|
|
166
166
|
const startedAt = performance.now();
|
|
167
|
-
const
|
|
167
|
+
const round = {};
|
|
168
|
+
const verdict = await decideUnreported(input, round, signal);
|
|
168
169
|
if (opts.onClassified !== undefined) {
|
|
169
170
|
try {
|
|
170
171
|
opts.onClassified({
|
|
@@ -172,6 +173,7 @@ export function createAutoModeDecider(opts) {
|
|
|
172
173
|
ms: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
173
174
|
verdict: verdict.kind,
|
|
174
175
|
...(verdict.kind === "unavailable" ? { cause: verdict.cause } : {}),
|
|
176
|
+
...(round.notAsked !== undefined ? { notAsked: round.notAsked } : {}),
|
|
175
177
|
...(verdict.classifier !== undefined ? { classifier: verdict.classifier } : {}),
|
|
176
178
|
});
|
|
177
179
|
}
|
|
@@ -181,22 +183,22 @@ export function createAutoModeDecider(opts) {
|
|
|
181
183
|
return verdict;
|
|
182
184
|
};
|
|
183
185
|
return { decide };
|
|
184
|
-
async function decideUnreported(input, signal) {
|
|
186
|
+
async function decideUnreported(input, round, signal) {
|
|
185
187
|
const causes = [];
|
|
186
|
-
const round = { notAsked: false };
|
|
187
188
|
let verdict = { kind: "unavailable", cause: "error" };
|
|
188
189
|
let i = 0;
|
|
189
|
-
|
|
190
|
+
const assembled = await assembleRound(input, round, signal);
|
|
191
|
+
for (; round.notAsked === undefined && i < ladder.length; i++) {
|
|
190
192
|
const name = ladder[i];
|
|
191
|
-
verdict = await askRung(input, signal, name !== undefined ? { index: i, model: name } : undefined, round);
|
|
193
|
+
verdict = await askRung(input, signal, name !== undefined ? { index: i, model: name } : undefined, round, assembled);
|
|
192
194
|
const cause = rungDidNotRule(verdict);
|
|
193
195
|
if (cause === undefined)
|
|
194
196
|
break;
|
|
195
197
|
causes.push(cause);
|
|
196
|
-
if (round.notAsked || signal?.aborted)
|
|
198
|
+
if (round.notAsked !== undefined || signal?.aborted)
|
|
197
199
|
break;
|
|
198
200
|
}
|
|
199
|
-
const landed = round.notAsked ? 0 : Math.min(i, ladder.length - 1);
|
|
201
|
+
const landed = round.notAsked !== undefined ? 0 : Math.min(i, ladder.length - 1);
|
|
200
202
|
const requested = ladder[0];
|
|
201
203
|
const used = ladder[landed];
|
|
202
204
|
if (requested === undefined || used === undefined)
|
|
@@ -212,55 +214,71 @@ export function createAutoModeDecider(opts) {
|
|
|
212
214
|
}),
|
|
213
215
|
};
|
|
214
216
|
}
|
|
215
|
-
async function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
async function assembleRound(input, round, signal) {
|
|
218
|
+
const assemble = opts.assemble;
|
|
219
|
+
if (assemble === undefined)
|
|
220
|
+
return undefined;
|
|
221
|
+
try {
|
|
222
|
+
return await withDeadline(timeoutMs, signal, (inner) => assemble(input, inner));
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
round.notAsked = isAutoModeTimeout(e) ? "timeout" : "failed";
|
|
226
|
+
return undefined;
|
|
224
227
|
}
|
|
228
|
+
}
|
|
229
|
+
async function askRung(input, signal, rung, round, assembled) {
|
|
225
230
|
try {
|
|
226
|
-
|
|
227
|
-
let settled = false;
|
|
228
|
-
timer = setTimeout(() => {
|
|
229
|
-
if (!settled) {
|
|
230
|
-
settled = true;
|
|
231
|
-
inner.abort();
|
|
232
|
-
reject(new AutoModeTimeout());
|
|
233
|
-
}
|
|
234
|
-
}, timeoutMs);
|
|
235
|
-
opts.classify(input, inner.signal, rung).then((v) => {
|
|
236
|
-
if (!settled) {
|
|
237
|
-
settled = true;
|
|
238
|
-
resolve(v);
|
|
239
|
-
}
|
|
240
|
-
}, (e) => {
|
|
241
|
-
if (!settled) {
|
|
242
|
-
settled = true;
|
|
243
|
-
reject(e);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
return parseAutoModeResponse(raw);
|
|
231
|
+
return parseAutoModeResponse(await withDeadline(timeoutMs, signal, (inner) => opts.classify(input, inner, rung, assembled)));
|
|
248
232
|
}
|
|
249
233
|
catch (e) {
|
|
250
234
|
if (isAutoModeRoundNotAsked(e)) {
|
|
251
|
-
round.notAsked =
|
|
235
|
+
round.notAsked = "failed";
|
|
252
236
|
return { kind: "unavailable", cause: "error" };
|
|
253
237
|
}
|
|
254
238
|
if (signal?.aborted)
|
|
255
239
|
return { kind: "unavailable", cause: "error" };
|
|
256
240
|
return { kind: "unavailable", cause: isAutoModeTimeout(e) ? "timeout" : "error" };
|
|
257
241
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async function withDeadline(capMs, signal, call) {
|
|
245
|
+
let timer;
|
|
246
|
+
const inner = new AbortController();
|
|
247
|
+
const onOuterAbort = () => inner.abort();
|
|
248
|
+
if (signal !== undefined) {
|
|
249
|
+
if (signal.aborted)
|
|
250
|
+
inner.abort();
|
|
251
|
+
else
|
|
252
|
+
signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
return await new Promise((resolve, reject) => {
|
|
256
|
+
let settled = false;
|
|
257
|
+
timer = setTimeout(() => {
|
|
258
|
+
if (!settled) {
|
|
259
|
+
settled = true;
|
|
260
|
+
inner.abort();
|
|
261
|
+
reject(new AutoModeTimeout());
|
|
262
|
+
}
|
|
263
|
+
}, capMs);
|
|
264
|
+
call(inner.signal).then((v) => {
|
|
265
|
+
if (!settled) {
|
|
266
|
+
settled = true;
|
|
267
|
+
resolve(v);
|
|
268
|
+
}
|
|
269
|
+
}, (e) => {
|
|
270
|
+
if (!settled) {
|
|
271
|
+
settled = true;
|
|
272
|
+
reject(e);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
if (timer !== undefined)
|
|
279
|
+
clearTimeout(timer);
|
|
280
|
+
if (signal !== undefined)
|
|
281
|
+
signal.removeEventListener("abort", onOuterAbort);
|
|
264
282
|
}
|
|
265
283
|
}
|
|
266
284
|
class AutoModeTimeout extends Error {
|
|
@@ -102,11 +102,11 @@ export type PlatformLimitReason = Extract<ResourceLimitReason, "env_lifetime" |
|
|
|
102
102
|
export interface SafetyAxis {
|
|
103
103
|
/** design/70: the tool is egress-marked (`ToolSpec.egress` — an external write: push, open PR, send). */
|
|
104
104
|
egress?: boolean;
|
|
105
|
-
/** design/77 §4:
|
|
106
|
-
* operator's `shellGate:"always"`, the probe's structural verdict (mandated / unanswered), or the shell
|
|
107
|
-
* gate's own tighten under its `"maybe"` tier; a policy ask the doctrine's probe neither tightened nor
|
|
108
|
-
* mandated carries no bit (its park is the budgetable kind). */
|
|
105
|
+
/** design/77 §4: an irreversibility MANDATE **of the TOOL's own** (its `always`/`maybe` tier); a SHELL SEAT raise is NOT this axis, see {@link shell}. */
|
|
109
106
|
irreversible?: boolean;
|
|
107
|
+
/** The engine's SHELL SEAT raised this ask. Its question is about TEXT — not about undoing an act — so
|
|
108
|
+
* the two axes are mutually exclusive. */
|
|
109
|
+
shell?: boolean;
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* design/80 §D-E: a DETERMINISTIC, REDACTED risk summary attached AT MINT to an escalation
|
|
@@ -948,10 +948,10 @@ export interface CheckpointState {
|
|
|
948
948
|
/**
|
|
949
949
|
* design/199 — the READ-face containment state at suspend (v9,
|
|
950
950
|
* {@link FACE_CHECKPOINT_VERSION}): the RESOLVED face plus the NORMALIZED deny-set ADDITIONS in
|
|
951
|
-
* force (deployment ∪ task layers; the built-in table is engine code
|
|
952
|
-
*
|
|
953
|
-
* non-default (face "open", or additions present) — an absent section IS the default posture and
|
|
954
|
-
* resumes as roots
|
|
951
|
+
* force (deployment ∪ task layers; the built-in table is engine code under the RESUMING deployment's
|
|
952
|
+
* own tier selection — empty unless it made one — never frozen here). Written ONLY when the posture
|
|
953
|
+
* is non-default (face "open", or additions present) — an absent section IS the default posture and
|
|
954
|
+
* resumes as roots plus whatever built-in tiers the resuming worker selected. Resume folds STRICTER-WINS: face = roots unless the
|
|
955
955
|
* section says open AND the live resolution says open; deny additions = frozen ∪ live (add-only,
|
|
956
956
|
* monotone — a resume can never widen what the cut leg could read).
|
|
957
957
|
*
|
|
@@ -1677,11 +1677,7 @@ export interface CheckpointRow {
|
|
|
1677
1677
|
humanReview?: {
|
|
1678
1678
|
count: number;
|
|
1679
1679
|
totalWaitMs: number;
|
|
1680
|
-
gates:
|
|
1681
|
-
kind: string;
|
|
1682
|
-
waitMs: number;
|
|
1683
|
-
decision?: string;
|
|
1684
|
-
}>;
|
|
1680
|
+
gates: import("./task-result.js").HumanReviewGateRow[];
|
|
1685
1681
|
};
|
|
1686
1682
|
/**
|
|
1687
1683
|
* design/80 D-1 (persist-winner): the winning resume binding, recorded by {@link CheckpointStore.resolve}
|
|
@@ -238,7 +238,7 @@ export function planRejectionClears(messages, opts) {
|
|
|
238
238
|
const cleared = clearable.map(({ m, at }) => {
|
|
239
239
|
const source = at.occurrence !== undefined && isToolResult(at.occurrence) ? at.occurrence : m;
|
|
240
240
|
const clearedMsg = clearOneResult(source, opts.offload);
|
|
241
|
-
return { key: at.key, groupCount: at.groupCount, marker: clearedMsg.content[0]?.text ?? CC_CLEARED_MARKER, fp: at.fp };
|
|
241
|
+
return { key: at.key, groupCount: at.groupCount, marker: clearedMsg.content[0]?.text ?? CC_CLEARED_MARKER, fp: at.fp, toolCallId: m.toolCallId };
|
|
242
242
|
});
|
|
243
243
|
return { cleared, tokensSavedEstimate };
|
|
244
244
|
}
|
|
@@ -34,6 +34,19 @@ export interface EngineNotice {
|
|
|
34
34
|
* prepared leg, never per judgment and never re-fired for a mid-run roster delta; absent ⇒ the judge
|
|
35
35
|
* has its target view, or no covered write tool was mounted AT PREPARE, or there is no judge —
|
|
36
36
|
* `wiringManifest.writeProtection` reports the judge's reading either way.
|
|
37
|
+
* - `"config.write_protection_unresolved"` (#869) — this leg's write-protection judge HAS a target view
|
|
38
|
+
* and the environment behind it could not answer: it refused a spelling, could not follow a link, or
|
|
39
|
+
* threw. That view then does not vote and the write is judged by its SPELLING alone (the judge's
|
|
40
|
+
* absence law), so the disclosure is the only place a deployment learns that its write floor is
|
|
41
|
+
* running on half a judgment. Announce, never narrow — no verdict moves because of this notice; the
|
|
42
|
+
* verdict already moved when the environment failed to answer. Audience `operator`.
|
|
43
|
+
* `detail: { reason, cause, sessionId, runId }` — `reason` is `"unresolved-symlink"` (a link whose
|
|
44
|
+
* real target is unknowable) or `"unresolvable-target"` (everything else: a refused spelling, an
|
|
45
|
+
* errored or throwing probe), and `cause` is the environment's own first sentence for that reason.
|
|
46
|
+
* ONCE PER LEG PER `reason` — the fact is about the ENVIRONMENT, not about a write, so it carries no
|
|
47
|
+
* call identity and a second failing write of the same shape adds nothing. Absent ⇒ every judgment
|
|
48
|
+
* this leg made had the views it was compiled for, OR the judge never finished at all (a judgment the
|
|
49
|
+
* gate's bounded wait gave up on is reported on the hook-error lane instead, `onHookError`).
|
|
37
50
|
* - `"config.tool_card_undeclared"` (design/388 L4) — a tool minted a `details.type` card its definition
|
|
38
51
|
* (or its caller declaration's `renderHints.resultCards`) never declared; the card was dropped from
|
|
39
52
|
* `tool_end.structured`; once per leg per type; `detail: { type, sessionId, runId }`.
|
|
@@ -109,6 +122,18 @@ export interface EngineNotice {
|
|
|
109
122
|
* its explicit {@link NOTICE_AUDIENCE} row (#433 made the registry total over the catalog —
|
|
110
123
|
* no engine-minted code is audience-defaulted any more). The refusal itself
|
|
111
124
|
* (`mcp.server_revoked`) is a tool RESULT code, not a notice.
|
|
125
|
+
* - `"mcp.server_redialed"` — a host asked this run to RE-DIAL one MCP server (the run
|
|
126
|
+
* handle's `reconnectMcpServer` verb) and this is what came of it. `detail: { server, outcome,
|
|
127
|
+
* added, removed, reason?, sessionId, runId }` — `outcome` is the result's own three-word answer
|
|
128
|
+
* (`reconnected` / `refused` / `not_declared`) and `added`/`removed` are COUNTS of namespaced
|
|
129
|
+
* tool names, never the names themselves (a server's tool names are server-authored text and
|
|
130
|
+
* this is an operator line, not a disclosure channel). ONE code rather than a success code and a
|
|
131
|
+
* failure code: the outcome is data about one event, not two kinds of event, and both would
|
|
132
|
+
* carry the same presentation tier — a consumer alerting on failures reads one field.
|
|
133
|
+
* `"operator"` audience: re-dialing is deployment plumbing and a failed one leaves the server
|
|
134
|
+
* DOWN, which only the operator can act on. Dedup unit: once per ATTEMPT — never folded per run,
|
|
135
|
+
* because two attempts on one server are two different answers and folding the second would hide
|
|
136
|
+
* the one that says the server is still down.
|
|
112
137
|
* - `"mcp.injection_dropped"` (L-167) — an entry of the user's own request-lane MCP injection
|
|
113
138
|
* was NOT mounted for the leg. The HOST owns the drop sites and supplies the facts; CORE composes
|
|
114
139
|
* the notice ({@link import("./mcp-injection-drop.js").mcpInjectionDroppedNotice}, the one mint).
|
|
@@ -707,6 +732,28 @@ export declare function deliverEngineNotice(onNotice: ((notice: EngineNotice) =>
|
|
|
707
732
|
* one repeated disclosure about one run. The account is per-RUN by its own contract sentence ("at most
|
|
708
733
|
* once per family, per run"), so it must carry the run's own id. Same omit-never-fabricate rule.
|
|
709
734
|
*/
|
|
735
|
+
/** What a settled leg still owes a person BEFORE the compensation lane has had its say: the facts the
|
|
736
|
+
* terminal-adoption lane holds, carried to the settle lane that closes the account
|
|
737
|
+
* ({@link settleOwedUserInput}). */
|
|
738
|
+
export interface OwedUserInputAccount {
|
|
739
|
+
/** The harness's at-agent_end snapshot, held because the tail still had something to say about it. */
|
|
740
|
+
readonly held: {
|
|
741
|
+
steer: number;
|
|
742
|
+
followUp: number;
|
|
743
|
+
} | undefined;
|
|
744
|
+
/** What a durable park's migration carried onto the new checkpoint, by family. */
|
|
745
|
+
readonly migrated: {
|
|
746
|
+
steer: number;
|
|
747
|
+
followUp: number;
|
|
748
|
+
};
|
|
749
|
+
/** The resumed leg's undelivered continuation frames, and how many of them the migration carried. */
|
|
750
|
+
readonly continuation: {
|
|
751
|
+
frames: ReadonlyArray<{
|
|
752
|
+
seq?: number;
|
|
753
|
+
}>;
|
|
754
|
+
carried: number;
|
|
755
|
+
};
|
|
756
|
+
}
|
|
710
757
|
/**
|
|
711
758
|
* What this leg still OWES a person, one count per family — the input to {@link undrainedUserInputNotices}.
|
|
712
759
|
* TWO lanes accept caller input and either can be holding some when a run ends: the harness's live queue
|
|
@@ -727,11 +774,25 @@ export declare function owedUserInputCounts(held: {
|
|
|
727
774
|
seq?: number;
|
|
728
775
|
}>;
|
|
729
776
|
carried: number;
|
|
730
|
-
|
|
777
|
+
rowReturned: boolean;
|
|
731
778
|
}): {
|
|
732
779
|
steer: number;
|
|
733
780
|
followUp: number;
|
|
734
781
|
};
|
|
782
|
+
/** Close the account — the ONE settlement point, called AFTER the reopen compensation with what that
|
|
783
|
+
* compensation actually did. Ordering is the whole rule: the counts are arithmetic, and the only fact they
|
|
784
|
+
* cannot be computed without is one the lane before this one does not have yet. EXACTLY ONCE per account,
|
|
785
|
+
* which is what lets the teardown call it again as a backstop for a settle lane that threw on its way here
|
|
786
|
+
* (an account nobody closes is the silence this seat exists to end; a second row would be the same loss
|
|
787
|
+
* counted twice). Keyed on the account OBJECT — one per run, minted by the lane that assembles it. NEVER
|
|
788
|
+
* THROWS: a station calls it from a teardown path where the run is already failing, and a disclosure that
|
|
789
|
+
* takes the cleanup down with it is worse than the silence it was fixing (the absent-sink arm of
|
|
790
|
+
* {@link deliverEngineNotice} ends at a bare `console.warn`, which a host-replaced console can throw from). */
|
|
791
|
+
export declare function settleOwedUserInput(account: OwedUserInputAccount, rowReturned: boolean, ids: {
|
|
792
|
+
taskId?: string;
|
|
793
|
+
sessionId?: string;
|
|
794
|
+
runId?: string;
|
|
795
|
+
}, onNotice: ((notice: EngineNotice) => void) | undefined): void;
|
|
735
796
|
export declare function undrainedUserInputNotices(counts: {
|
|
736
797
|
steer: number;
|
|
737
798
|
followUp: number;
|
|
@@ -32,12 +32,25 @@ export function deliverEngineNotice(onNotice, notice) {
|
|
|
32
32
|
console.warn(notice.message);
|
|
33
33
|
}
|
|
34
34
|
export function owedUserInputCounts(held, migrated, continuation) {
|
|
35
|
-
const owedContinuation = (continuation.
|
|
35
|
+
const owedContinuation = (continuation.rowReturned ? continuation.frames.filter((f) => f.seq === undefined).length : continuation.frames.length) - continuation.carried;
|
|
36
36
|
return {
|
|
37
37
|
steer: Math.max(0, (held?.steer ?? 0) - migrated.steer) + Math.max(0, owedContinuation),
|
|
38
38
|
followUp: Math.max(0, (held?.followUp ?? 0) - migrated.followUp),
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
export function settleOwedUserInput(account, rowReturned, ids, onNotice) {
|
|
42
|
+
if (SETTLED_OWED_ACCOUNTS.has(account))
|
|
43
|
+
return;
|
|
44
|
+
SETTLED_OWED_ACCOUNTS.add(account);
|
|
45
|
+
try {
|
|
46
|
+
const owed = owedUserInputCounts(account.held, account.migrated, { ...account.continuation, rowReturned });
|
|
47
|
+
for (const notice of undrainedUserInputNotices(owed, ids.taskId, ids.sessionId, ids.runId))
|
|
48
|
+
deliverEngineNotice(onNotice, notice);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const SETTLED_OWED_ACCOUNTS = new WeakSet();
|
|
41
54
|
export function undrainedUserInputNotices(counts, taskId, sessionId, runId) {
|
|
42
55
|
const tid = taskId !== undefined ? { taskId } : {};
|
|
43
56
|
const sid = sessionId !== undefined ? { sessionId } : {};
|
|
@@ -36,8 +36,18 @@ export declare function createGateDeadline(input: {
|
|
|
36
36
|
approvalTimeoutMs?: number;
|
|
37
37
|
}, notifier: Parameters<typeof traceHookCrash>[2]): () => number;
|
|
38
38
|
/**
|
|
39
|
-
* The write-protection judge's wait, at both of its call sites
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* The write-protection judge's wait, at both of its call sites — and the ONE minter of the `unresolvable`
|
|
40
|
+
* verdict. An elapsed or aborted judgment is not "a target the environment could not resolve" (the judge
|
|
41
|
+
* answers that one itself, by dropping the view that could not contribute and letting the lexical half
|
|
42
|
+
* stand); it is NO judgment at all — not even the lexical half's answer came back — and there is nothing
|
|
43
|
+
* for the fold or the approved-edit arm to act on but their own fail-closed arm. A wait that produced
|
|
44
|
+
* nothing knows nothing about what it was waiting for, which is why `unresolvedSymlink` is `false` here
|
|
45
|
+
* and not a guess.
|
|
46
|
+
*
|
|
47
|
+
* KNOWN, AND NARROWER THAN IT LOOKS: the lexical half is computed synchronously at the top of `judge`, so
|
|
48
|
+
* a spelling HIT is already on a settled promise before this wait begins and cannot elapse; what this arm
|
|
49
|
+
* refuses is a spelling MISS whose environment half never came back. Closing even that would mean handing
|
|
50
|
+
* the deadline INTO the judge so the missing half could be dropped there too — a seat change on a public
|
|
51
|
+
* type, deliberately not taken here.
|
|
42
52
|
*/
|
|
43
53
|
export declare function awaitWriteProtectionJudgment(judged: Promise<WriteProtectionVerdict>, ms: number, signal?: AbortSignal, onFailure?: (err: unknown) => void): Promise<WriteProtectionVerdict>;
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -110,8 +110,8 @@ export async function runGateFold(pass) {
|
|
|
110
110
|
};
|
|
111
111
|
pass.tightenedBy = "safety";
|
|
112
112
|
}
|
|
113
|
-
const
|
|
114
|
-
if ((
|
|
113
|
+
const writeProtectionStanding = pass.decision.action;
|
|
114
|
+
if ((writeProtectionStanding === "allow" || writeProtectionStanding === "ask") && input.writeProtectionCheck !== undefined) {
|
|
115
115
|
const judged = input.writeProtectionCheck(toolName, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, pass.req.face, input.trackedCwd?.(), pass.callSignal ?? input.abortSignal);
|
|
116
116
|
const verdict = judged === null
|
|
117
117
|
? undefined
|
|
@@ -126,7 +126,7 @@ export async function runGateFold(pass) {
|
|
|
126
126
|
if (verdict !== undefined && writeProtectionUnresolved(verdict))
|
|
127
127
|
pass.writeProtectionUnresolved = true;
|
|
128
128
|
const decision = verdict === undefined ? undefined : writeProtectionTightenDecision(toolName, verdict);
|
|
129
|
-
if (decision !== undefined &&
|
|
129
|
+
if (decision !== undefined && writeProtectionStanding === "allow") {
|
|
130
130
|
pass.decision = decision;
|
|
131
131
|
if (decision.action === "ask")
|
|
132
132
|
pass.tightenedBy = "safety";
|
|
@@ -119,7 +119,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
119
119
|
* the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
|
|
120
120
|
* so the catalog is not written a second time here and the two cannot drift.
|
|
121
121
|
*/
|
|
122
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
122
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
|
|
123
123
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
124
124
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
125
125
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -18,6 +18,8 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
18
18
|
"memory.layout_unowned_root_content",
|
|
19
19
|
"memory.tail",
|
|
20
20
|
"memory.pollution_mark_failed",
|
|
21
|
+
"memory.session_incarnation_mismatch",
|
|
22
|
+
"memory.session_incarnation_unreadable",
|
|
21
23
|
"memory.session_polluted",
|
|
22
24
|
"memory.harvest_quarantined",
|
|
23
25
|
"memory.delegation_static_mark_waived",
|
|
@@ -112,6 +114,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
112
114
|
"config.artifact_host_invalid",
|
|
113
115
|
"config.task_root_not_canonical",
|
|
114
116
|
"config.write_protection_target_view_absent",
|
|
117
|
+
"config.write_protection_unresolved",
|
|
115
118
|
"config.execution_env_capability_invalid",
|
|
116
119
|
"config.tool_card_undeclared",
|
|
117
120
|
"config.tool_face_undeclared",
|
|
@@ -132,6 +135,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
132
135
|
"delegation.ask_unresolvable",
|
|
133
136
|
"mcp.injection_dropped",
|
|
134
137
|
"mcp.revocation_probe_failed",
|
|
138
|
+
"mcp.server_redialed",
|
|
135
139
|
"workflow.governance_key_stripped",
|
|
136
140
|
"workflow.agent_option_ignored",
|
|
137
141
|
"memory.session_polluted",
|
|
@@ -201,6 +205,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
201
205
|
"config.artifact_host_invalid": "operator",
|
|
202
206
|
"config.task_root_not_canonical": "operator",
|
|
203
207
|
"config.write_protection_target_view_absent": "operator",
|
|
208
|
+
"config.write_protection_unresolved": "operator",
|
|
204
209
|
"config.execution_env_capability_invalid": "operator",
|
|
205
210
|
"config.tool_card_undeclared": "operator",
|
|
206
211
|
"config.tool_face_undeclared": "operator",
|
|
@@ -214,6 +219,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
214
219
|
"checkpoint.execution_outcome_unrecorded": "operator",
|
|
215
220
|
"mcp.injection_dropped": "user",
|
|
216
221
|
"mcp.revocation_probe_failed": "operator",
|
|
222
|
+
"mcp.server_redialed": "operator",
|
|
217
223
|
"workflow.governance_key_stripped": "operator",
|
|
218
224
|
"workflow.agent_option_ignored": "operator",
|
|
219
225
|
"memory.content_class_declared": "operator",
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -190,6 +190,25 @@ export interface MaterializedMcp {
|
|
|
190
190
|
* misroute).
|
|
191
191
|
*/
|
|
192
192
|
refresh: (server?: string) => Promise<McpRefreshResult[]>;
|
|
193
|
+
/**
|
|
194
|
+
* RE-DIAL one named server — the action {@link refresh} deliberately is not. It closes
|
|
195
|
+
* whatever handle the server still has and dials it again through the SAME `connectServer` the
|
|
196
|
+
* materialization used, so the schema gate, the namespacing, the axis derivation and the metadata
|
|
197
|
+
* cache seeding cannot drift between a first connect and a re-dial: one function, not one
|
|
198
|
+
* convention.
|
|
199
|
+
*
|
|
200
|
+
* ONE SERVER, NAMED — CC 2.1.259's control-channel request carries a REQUIRED `serverName` and has
|
|
201
|
+
* no all-servers form, and an all-servers arm would close HEALTHY connections to re-dial them (a
|
|
202
|
+
* stdio server owning a device or a port is exactly the one that cannot be re-dialed while its
|
|
203
|
+
* predecessor still holds the device). CLOSE-THEN-DIAL, so a re-dial that FAILS leaves the server
|
|
204
|
+
* DISCONNECTED until another re-dial or the next leg's materialize, and calls in flight on the
|
|
205
|
+
* replaced connection fail with it — inherent to re-dialing, and CC's behavior too.
|
|
206
|
+
*
|
|
207
|
+
* NOT A MODEL FACE (CC has no such tool either): a verb that can leave a connection down is a host
|
|
208
|
+
* act, and its consumer entrance is the run handle's `reconnectMcpServer`. LEG-LOCAL like
|
|
209
|
+
* {@link refresh}: the result rides no checkpoint, and a durable resume takes a fresh listing.
|
|
210
|
+
*/
|
|
211
|
+
reconnect: (server: string) => Promise<McpReconnectResult>;
|
|
193
212
|
dispose: () => Promise<void>;
|
|
194
213
|
}
|
|
195
214
|
/** One per-server entry of {@link MaterializedMcp.refresh}. `added`/`removed` are namespaced
|
|
@@ -244,6 +263,52 @@ export interface McpRefreshResult {
|
|
|
244
263
|
*/
|
|
245
264
|
listingIncomplete?: McpListingIncomplete;
|
|
246
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* The result of {@link MaterializedMcp.reconnect} — ONE server, because the face takes one.
|
|
268
|
+
*
|
|
269
|
+
* `outcome` is the whole top level, three words, so a consumer mapping it onto a three-state wire
|
|
270
|
+
* answer needs no table: `reconnected` is the only success, and everything that did not reconnect is
|
|
271
|
+
* either `refused` (the engine declined, or the dial/first listing failed — `reason` says which) or
|
|
272
|
+
* `not_declared` (no server of that name in this leg's spec). Sub-reasons ride `reason`; a fourth
|
|
273
|
+
* word would be a new state for every downstream to learn.
|
|
274
|
+
*
|
|
275
|
+
* @contract mcp.redial.roster_assertion — a re-dial result carries `tools` IF AND ONLY IF it asserts a
|
|
276
|
+
* roster for its `prefix` domain, and that one rule is what every consumer of it reads: the fresh intake
|
|
277
|
+
* on `reconnected`, the EMPTY roster on a dial that failed after the old connection was already closed
|
|
278
|
+
* (the server is down, so its tools are withdrawn rather than left mounted and unusable), and NOTHING at
|
|
279
|
+
* all on an arm that never touched the connection — an unknown name, a revoked server, a re-dial already
|
|
280
|
+
* in flight, a disposed materialization — whose mounted roster is left exactly as it was.
|
|
281
|
+
*/
|
|
282
|
+
export interface McpReconnectResult {
|
|
283
|
+
server: string;
|
|
284
|
+
/** The consumer's splice domain, same meaning and same mint as {@link McpRefreshResult.prefix}. */
|
|
285
|
+
prefix: string;
|
|
286
|
+
outcome: "reconnected" | "refused" | "not_declared";
|
|
287
|
+
/** Why, on every non-`reconnected` outcome (neutralized + bounded when it embeds remote text).
|
|
288
|
+
* Never the only machine-readable fact: see {@link status}. */
|
|
289
|
+
reason?: string;
|
|
290
|
+
toolCount: number;
|
|
291
|
+
/** Namespaced names this re-dial added / took away relative to what the server had mounted a moment
|
|
292
|
+
* ago — a first successful dial adds its whole roster, a failed dial removes it. */
|
|
293
|
+
added: string[];
|
|
294
|
+
removed: string[];
|
|
295
|
+
/** See the type docstring: present iff this result asserts a roster for `prefix`. */
|
|
296
|
+
tools?: AgentTool[];
|
|
297
|
+
axes?: McpToolAxis[];
|
|
298
|
+
dropped?: McpDroppedTool[];
|
|
299
|
+
/** The OPERATOR lines this re-dial produced (an incomplete listing walk, a kept-but-defective tool
|
|
300
|
+
* schema, a spec naming tools a resources-only server cannot serve). Also appended to
|
|
301
|
+
* {@link MaterializedMcp.warnings}, which the runner drains ONCE at prepare — so the copy here is
|
|
302
|
+
* what the run handle's verb forwards to `onError(phase:"mcp")`. One production, two drains. */
|
|
303
|
+
warnings?: string[];
|
|
304
|
+
/** The NEW connect-time status record (also swapped into {@link MaterializedMcp.statuses}), present
|
|
305
|
+
* whenever the dial was attempted: a consumer keys its next action off `errorCode` / `httpStatus`
|
|
306
|
+
* (a 401 is a re-authorization door, a `spawn_failed` a missing binary), not off {@link reason}. */
|
|
307
|
+
status?: McpServerStatus;
|
|
308
|
+
/** The re-dial's own `tools/list` walk did not finish — {@link McpRefreshResult.listingIncomplete}
|
|
309
|
+
* minus the retention arm: a re-dial has no previous listing on this connection to retain from. */
|
|
310
|
+
listingIncomplete?: McpListingIncomplete;
|
|
311
|
+
}
|
|
247
312
|
/** design/99 §E9 — projected per-server MCP status (see {@link MaterializedMcp.statuses}). NOTE: `serverInfo`
|
|
248
313
|
* and `error` are SERVER-controlled strings (verbatim from the remote) — UNTRUSTED; a consumer rendering them
|
|
249
314
|
* into a TUI/log must treat them as such (a misbehaving server could embed ANSI/break-out sequences). `toolNames`
|