@sema-agent/core 7.4.0 → 7.5.1
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 +69 -0
- package/dist/agents/cascade.d.ts +2 -1
- package/dist/agents/peer-notices.d.ts +11 -1
- package/dist/agents/peer-session-drain.js +2 -0
- package/dist/agents/verify.d.ts +2 -1
- package/dist/core/ask-origin.d.ts +130 -0
- package/dist/core/ask-origin.js +35 -0
- package/dist/core/auto-mode-arming.d.ts +40 -1
- package/dist/core/auto-mode-arming.js +51 -3
- package/dist/core/auto-mode.d.ts +87 -10
- package/dist/core/auto-mode.js +34 -6
- package/dist/core/checkpoint-store.d.ts +41 -6
- package/dist/core/checkpoint-store.js +8 -0
- package/dist/core/hooks.d.ts +63 -19
- package/dist/core/hooks.js +37 -16
- package/dist/core/mcp.d.ts +47 -55
- package/dist/core/mcp.js +89 -31
- package/dist/core/park-selfcheck.js +3 -0
- package/dist/core/permission-rule-consent.d.ts +2 -11
- package/dist/core/permission-rule-consent.js +13 -62
- package/dist/core/permission-rule-org.d.ts +70 -54
- package/dist/core/permission-rule-org.js +47 -61
- package/dist/core/permission-rule-provider.d.ts +162 -0
- package/dist/core/permission-rule-provider.js +148 -0
- package/dist/core/permission-rule-session.d.ts +23 -19
- package/dist/core/permission-rule-session.js +5 -5
- package/dist/core/permission-rule-store.d.ts +46 -21
- package/dist/core/permission-rule-store.js +13 -6
- package/dist/core/permission-rule-sync.d.ts +2 -1
- package/dist/core/permission-rule-sync.js +11 -0
- package/dist/core/runner/assemble-result.d.ts +3 -2
- package/dist/core/runner/checkpoint-scope.d.ts +32 -0
- package/dist/core/runner/checkpoint-scope.js +4 -0
- package/dist/core/runner/contracts.d.ts +1878 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +57 -31
- package/dist/core/runner/denial-limit-arms.js +42 -17
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-announce-once.d.ts +83 -0
- package/dist/core/runner/prepare-announce-once.js +105 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +6 -8
- package/dist/core/runner/prepare-hands-readface.js +3 -3
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-permission-rules.d.ts +132 -0
- package/dist/core/runner/prepare-permission-rules.js +140 -0
- package/dist/core/runner/prepare-project-context.d.ts +131 -0
- package/dist/core/runner/prepare-project-context.js +150 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +182 -0
- package/dist/core/runner/prepare-question-face.d.ts +119 -0
- package/dist/core/runner/prepare-question-face.js +83 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
- package/dist/core/runner/prepare-task.d.ts +11 -1815
- package/dist/core/runner/prepare-task.js +138 -2542
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
- package/dist/core/runner/prepare-wiring-manifest.js +240 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
- package/dist/core/runner/prepare-workspace-restore.js +1 -22
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/runtask.d.ts +11 -2
- package/dist/core/runner/runtask.js +27 -9
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/sensitive-path-policy.d.ts +16 -0
- package/dist/core/sensitive-path-policy.js +1 -1
- package/dist/core/tool-policy.d.ts +57 -9
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/types.d.ts +63 -51
- package/dist/core/wiring-manifest.d.ts +40 -3
- package/dist/core/wiring-manifest.js +4 -3
- package/dist/core/write-protect.d.ts +13 -2
- package/dist/core/write-protect.js +58 -29
- package/dist/engine/harness/types.d.ts +38 -16
- package/dist/engine/harness/types.js +25 -1
- package/dist/engine/session/session.d.ts +3 -11
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -0
- package/dist/stores/file/adoption/adopt.d.ts +1 -1
- package/dist/stores/file/adoption/marker.d.ts +1 -1
- package/dist/stores/file/permission-rule-adopt.js +4 -3
- package/dist/stores/file/permission-rule-store.d.ts +65 -25
- package/dist/stores/file/permission-rule-store.js +215 -37
- package/dist/stores/file/task-list-store.d.ts +1 -1
- package/dist/tools/fs/read-face.d.ts +1 -1
- package/package.json +8 -2
- package/test/export-surface.snapshot.json +76 -28
package/dist/core/auto-mode.js
CHANGED
|
@@ -99,6 +99,23 @@ class AutoModeTimeout extends Error {
|
|
|
99
99
|
super("auto-mode classify timeout");
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
export function readDenialLimitFallback(v) {
|
|
103
|
+
if (typeof v !== "object" || v === null)
|
|
104
|
+
return undefined;
|
|
105
|
+
const o = v;
|
|
106
|
+
const count = (x) => typeof x === "number" && Number.isFinite(x) && x >= 0;
|
|
107
|
+
if (!count(o["consecutive"]) || !count(o["total"]) || !count(o["autoDenyAfterMs"]))
|
|
108
|
+
return undefined;
|
|
109
|
+
if (o["limit"] !== "consecutive" && o["limit"] !== "total")
|
|
110
|
+
return undefined;
|
|
111
|
+
return { consecutive: o["consecutive"], total: o["total"], limit: o["limit"], autoDenyAfterMs: o["autoDenyAfterMs"] };
|
|
112
|
+
}
|
|
113
|
+
export function unarmedWindow(fallback) {
|
|
114
|
+
return { ...countsOf(fallback), autoDenyAfterMs: 0 };
|
|
115
|
+
}
|
|
116
|
+
function countsOf(fallback) {
|
|
117
|
+
return { consecutive: fallback.consecutive, total: fallback.total, limit: fallback.limit };
|
|
118
|
+
}
|
|
102
119
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
103
120
|
export function createAutoModeDenialTracker(opts = {}) {
|
|
104
121
|
const bound = (name, v, dflt) => {
|
|
@@ -121,6 +138,8 @@ export function createAutoModeDenialTracker(opts = {}) {
|
|
|
121
138
|
let consecutive = 0;
|
|
122
139
|
let total = 0;
|
|
123
140
|
let timedFallbackShown = false;
|
|
141
|
+
let windowArmedAt;
|
|
142
|
+
let minted = new WeakSet();
|
|
124
143
|
return {
|
|
125
144
|
recordBlock() {
|
|
126
145
|
consecutive += 1;
|
|
@@ -128,23 +147,32 @@ export function createAutoModeDenialTracker(opts = {}) {
|
|
|
128
147
|
if (consecutive < maxConsecutive && total < maxTotal)
|
|
129
148
|
return { limitReached: false };
|
|
130
149
|
const totalTripped = total >= maxTotal;
|
|
131
|
-
const
|
|
132
|
-
|
|
150
|
+
const fallback = { consecutive, total, limit: totalTripped ? "total" : "consecutive" };
|
|
151
|
+
minted.add(fallback);
|
|
133
152
|
if (totalTripped) {
|
|
134
153
|
consecutive = 0;
|
|
135
154
|
total = 0;
|
|
136
155
|
timedFallbackShown = false;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
windowArmedAt = undefined;
|
|
157
|
+
minted = new WeakSet();
|
|
158
|
+
minted.add(fallback);
|
|
140
159
|
}
|
|
141
160
|
return { limitReached: true, fallback };
|
|
142
161
|
},
|
|
162
|
+
armTimedWindow(fallback) {
|
|
163
|
+
if (!minted.delete(fallback) || fallback.limit === "total" || timedFallbackShown || autoDenyAfterMs === 0)
|
|
164
|
+
return unarmedWindow(fallback);
|
|
165
|
+
timedFallbackShown = true;
|
|
166
|
+
windowArmedAt = Date.now();
|
|
167
|
+
return { ...countsOf(fallback), autoDenyAfterMs };
|
|
168
|
+
},
|
|
143
169
|
recordAllow() {
|
|
144
170
|
consecutive = 0;
|
|
145
171
|
timedFallbackShown = false;
|
|
172
|
+
windowArmedAt = undefined;
|
|
173
|
+
minted = new WeakSet();
|
|
146
174
|
},
|
|
147
|
-
snapshot: () => ({ consecutive, total, timedFallbackShown }),
|
|
175
|
+
snapshot: () => ({ consecutive, total, timedFallbackShown, ...(windowArmedAt !== undefined ? { windowArmedAt } : {}) }),
|
|
148
176
|
};
|
|
149
177
|
}
|
|
150
178
|
export function denialLimitFallbackMessage(fallback, latestBlockedAction) {
|
|
@@ -439,7 +439,9 @@ export declare function buildRiskDescriptor(input: {
|
|
|
439
439
|
* refuses to redeem such a row (the governed boundary must not vanish with a deployment's wiring).
|
|
440
440
|
*/
|
|
441
441
|
export interface RealApprovalGateBit {
|
|
442
|
-
origin
|
|
442
|
+
/** The three members of the closed ask-origin set that can mint the bit — derived FROM that set, so
|
|
443
|
+
* the two vocabularies cannot drift into two spellings of one word. */
|
|
444
|
+
origin: Extract<import("./ask-origin.js").AskOrigin, "org_rule" | "org_unavailable" | "policy">;
|
|
443
445
|
}
|
|
444
446
|
export type CheckpointGate =
|
|
445
447
|
/** F4: a human (or any external authority) must allow/deny a pending tool call. design/80 §D-E:
|
|
@@ -803,6 +805,28 @@ export type PendingAction = {
|
|
|
803
805
|
* consent record threads it as `prepareCardApproval`'s `execCwd`. Present only beside
|
|
804
806
|
* {@link ruleOffers} when the run had a tracked cwd; echo-only, never a control input. */
|
|
805
807
|
execCwd?: string;
|
|
808
|
+
/** #557 (additive; no checkpoint-version bump — the `previewWithheld` precedent: an optional
|
|
809
|
+
* field an older reader ignores and the resume path never reads) — the PARK twin of
|
|
810
|
+
* `AskRequest.requiresRealApproval`: present (`true`) ⇔ this parked ask demanded real human
|
|
811
|
+
* judgment (the gate's `realApproval` bit was minted — the SAME fact, restated where a card
|
|
812
|
+
* reader looks for it, so an inbox needs no gate-kind decoding to say "only a person can clear
|
|
813
|
+
* this"). Absent otherwise (never `false`). Display metadata: the resume belts keep reading the
|
|
814
|
+
* gate's own bit. */
|
|
815
|
+
requiresRealApproval?: true;
|
|
816
|
+
/** #557 (additive, same precedent) — the PARK twin of `AskRequest.denialLimitFallback`: present
|
|
817
|
+
* ⇔ this parked ask is the classifier DENIAL-LIMIT fallback, carrying the counts that tripped
|
|
818
|
+
* the bound. Its `autoDenyAfterMs` is ALWAYS `0` on a row: the window is a fact of the ask's
|
|
819
|
+
* route, armed only at a hand-out to a live approver, and nothing counts down on the parked lane
|
|
820
|
+
* — a park's expiry is the row's own `deadline`/ttl, the engine arms NO timer off this row, and
|
|
821
|
+
* its settlement never reads as the fallback's auto-deny (see `tool_end.autoDenied`). A durable
|
|
822
|
+
* card renders the counts and the limit; it has no countdown to render. Echo-only. */
|
|
823
|
+
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
824
|
+
/** (additive, same precedent) — the PARK twin of `AskRequest.origin`: WHICH AUTHORITY raised the
|
|
825
|
+
* parked ask, one word from the closed {@link import("./ask-origin.js").AskOrigin} set, the same
|
|
826
|
+
* word the synchronous card is rendered by (engine-stamped at the gate, never a policy's claim).
|
|
827
|
+
* Written only when it is a member of the set; a pre-existing row reads absent (unreported).
|
|
828
|
+
* Display metadata — the resume belts keep reading the gate's own bit. */
|
|
829
|
+
origin?: import("./ask-origin.js").AskOrigin;
|
|
806
830
|
/**
|
|
807
831
|
* design/80 D-1 §2 (slice 1a.2): the server-minted **opaque** boundInputHash of {@link args} — a
|
|
808
832
|
* SHA-256 (hex) via {@link import("./canonical-json.js").boundInputHashOf}, computed ONCE here at
|
|
@@ -1157,11 +1181,7 @@ export interface CheckpointState {
|
|
|
1157
1181
|
* catalog left the tool description with Q4). Absent (older checkpoint / never announced) ⇒ drift
|
|
1158
1182
|
* undetectable, silent (conservative, same B-5 direction).
|
|
1159
1183
|
*/
|
|
1160
|
-
announcedListings?:
|
|
1161
|
-
agents?: string[];
|
|
1162
|
-
skills?: string[];
|
|
1163
|
-
models?: string[];
|
|
1164
|
-
};
|
|
1184
|
+
announcedListings?: import("../internal/harness.js").AnnouncedListingSnapshot;
|
|
1165
1185
|
/**
|
|
1166
1186
|
* The git-status frame's announced `(kind, hash)` state at suspend (env-tail migration: the
|
|
1167
1187
|
* run-loop-maintained mirror, advanced at every frame receipt). The LOWEST rung of the resume
|
|
@@ -1842,6 +1862,21 @@ export interface CheckpointSummary {
|
|
|
1842
1862
|
* projection's own materials (a pre-#457 row therefore reads absent, which is honest: unreported).
|
|
1843
1863
|
* ECHO-ONLY display metadata; nothing gates on it. */
|
|
1844
1864
|
previewWithheld?: "oversize" | "unavailable";
|
|
1865
|
+
/** #557 — the row's `PendingAction.tool_approval.requiresRealApproval`, projected so the ONE-CALL
|
|
1866
|
+
* inbox can label "only a person can clear this" without an N+1 `get`. `true` or OMITTED (the
|
|
1867
|
+
* {@link toolCallId} absence-shape contract); pure echo of the persisted bit — a pre-#557 row reads
|
|
1868
|
+
* absent, which is honest (unreported), never a claim that budget may clear it. */
|
|
1869
|
+
requiresRealApproval?: true;
|
|
1870
|
+
/** #557 — the row's `PendingAction.tool_approval.denialLimitFallback`, projected BESIDE the preview
|
|
1871
|
+
* for the same one-call reason: a card that shows the fallback's counts and limit must not re-open
|
|
1872
|
+
* the row to learn them (`autoDenyAfterMs` is `0` on every row — see the row member). Pure echo;
|
|
1873
|
+
* OMITTED when the row carries none. */
|
|
1874
|
+
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1875
|
+
/** The row's `PendingAction.tool_approval.origin`, projected for the same one-call reason: a card
|
|
1876
|
+
* rendered by the ask's origin word must not re-open the row to learn it. Echoed ONLY when the stored
|
|
1877
|
+
* value is a member of the closed set ({@link import("./ask-origin.js").isAskOrigin}) — a bad value
|
|
1878
|
+
* reads as absent, never as a word. OMITTED when the row carries none (a pre-existing row). */
|
|
1879
|
+
origin?: import("./ask-origin.js").AskOrigin;
|
|
1845
1880
|
/**
|
|
1846
1881
|
* #438: PRESENCE ONLY — this pending `tool_approval` row's payload carries at least one DIRECTIONAL
|
|
1847
1882
|
* format control (the {@link import("./tool-policy.js").AskRequest.hasBidiControls} character class;
|
|
@@ -2,6 +2,8 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
3
|
import { PROBE_CAUSE_PATH_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
4
|
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
|
+
import { readDenialLimitFallback } from "./auto-mode.js";
|
|
6
|
+
import { isAskOrigin } from "./ask-origin.js";
|
|
5
7
|
import { renderUntrustedCommandText, stripFormatCharacters } from "./permission-rule-model.js";
|
|
6
8
|
import { redactSecrets } from "./untrusted-egress.js";
|
|
7
9
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
@@ -262,6 +264,12 @@ export function summarizeCheckpoint(cp) {
|
|
|
262
264
|
: {}),
|
|
263
265
|
...(tool?.preview !== undefined ? { preview: tool.preview } : {}),
|
|
264
266
|
...(tool?.previewWithheld === "oversize" || tool?.previewWithheld === "unavailable" ? { previewWithheld: tool.previewWithheld } : {}),
|
|
267
|
+
...(tool?.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
268
|
+
...(() => {
|
|
269
|
+
const fb = readDenialLimitFallback(tool?.denialLimitFallback);
|
|
270
|
+
return fb !== undefined ? { denialLimitFallback: fb } : {};
|
|
271
|
+
})(),
|
|
272
|
+
...(tool !== undefined && isAskOrigin(tool.origin) ? { origin: tool.origin } : {}),
|
|
265
273
|
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
266
274
|
};
|
|
267
275
|
}
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -962,6 +962,19 @@ export interface ToolGateResult {
|
|
|
962
962
|
* `tool_end` frame — the machine-readable "why was this refused" a consumer classifies on.
|
|
963
963
|
*/
|
|
964
964
|
resolution?: import("./tool-policy.js").AskDenyResolution;
|
|
965
|
+
/**
|
|
966
|
+
* `true` ⇔ this BLOCK is the classifier denial-limit fallback's own auto-deny: the ask's window
|
|
967
|
+
* (`AskRequest.denialLimitFallback.autoDenyAfterMs`) elapsed with nobody answering and the resolver
|
|
968
|
+
* refused it itself — the same fact the `permissionDenied` observer payload's `autoDenied` carries,
|
|
969
|
+
* read from the same two places (this gate's own resolution, or the module-private attestation an
|
|
970
|
+
* inherited arm bound to THIS call). Rides to the call's `tool_end` frame so a consumer can tell
|
|
971
|
+
* "the classifier fallback's own 120 s window auto-denied" from "the approval factory's own window
|
|
972
|
+
* (`approvalTimeoutMs`) elapsed" — both settle as `settledBy:"timeout"` / `resolution:"window_expired"`,
|
|
973
|
+
* and the factory's deadline NEVER carries this. A durable park's expiry settles on the decide lane
|
|
974
|
+
* with no `resolution` word, so it is told apart by that absence, not by this bit.
|
|
975
|
+
* Absent everywhere else (a person's no, a policy deny, an executing call).
|
|
976
|
+
*/
|
|
977
|
+
autoDenied?: true;
|
|
965
978
|
/**
|
|
966
979
|
* design/252 G-7 — WHOSE settlement that was: the identifier the approval channel reported, carried
|
|
967
980
|
* out verbatim beside {@link settledBy}. This layer authenticates nothing and compares nothing; the
|
|
@@ -1226,13 +1239,14 @@ export interface ToolGateInput {
|
|
|
1226
1239
|
* design/384 slice 1 — the PER-CALL abort signal, carried in from the `tool_call` hook event's
|
|
1227
1240
|
* own `signal` seat (the loop's turn-scoped controller; the run abort is composed into it). The
|
|
1228
1241
|
* gate itself never awaits on it — it hands the signal verbatim to the four consuming seats
|
|
1229
|
-
* below
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1242
|
+
* below: `adjudicate`, `resolveAsk` and `resolveContentAsk` take it as their optional trailing
|
|
1243
|
+
* argument; `suspendAsk` (slice 2) reads it as `carry.signal` inside its structural seat
|
|
1244
|
+
* ({@link AskCarry}), and its park closure composes it into the halt×park fence. The Runner's
|
|
1245
|
+
* closures compose it with the run signal so a turn interrupt (bare halt / steer-now) releases
|
|
1246
|
+
* the wait — or, on the durable leg, makes the park concede. Absent (a host driving the gate
|
|
1247
|
+
* directly, or an unthreaded emit) ⇒ the three synchronous seats are invoked exactly as before —
|
|
1248
|
+
* same arity, no trailing `undefined` (the threading is a conditional invocation) — the carry has
|
|
1249
|
+
* no `signal` member, and waits stay bound to the run signal alone: the historical shape.
|
|
1236
1250
|
*/
|
|
1237
1251
|
callSignal?: AbortSignal;
|
|
1238
1252
|
/** The combined tool-policy check (abort-bound), or undefined when no policy is wired (→ allow).
|
|
@@ -1321,12 +1335,13 @@ export interface ToolGateInput {
|
|
|
1321
1335
|
* states — every parameter here is positional — and typed `boolean` so a mis-bind against the two
|
|
1322
1336
|
* trailing `string` seats is a compile error rather than a silent re-binding. */
|
|
1323
1337
|
probeMandated?: boolean,
|
|
1324
|
-
/**
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1329
|
-
|
|
1338
|
+
/** The ONE structural seat after the twelve positional ones — everything else the park closure needs
|
|
1339
|
+
* travels inside it ({@link AskCarry}: the per-call signal, the surviving ask's fallback member, its
|
|
1340
|
+
* origin word), so the tuple does not grow a seat per member. ALWAYS passed by the gate (thirteen
|
|
1341
|
+
* arguments, every call) — optional on the TYPE only because the ten seats before it are optional and
|
|
1342
|
+
* a required parameter cannot follow one. The twelve seats above are the historical shape; a host-built
|
|
1343
|
+
* closure that read the signal as the 13th positional argument now reads `carry.signal`. */
|
|
1344
|
+
carry?: AskCarry) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
|
|
1330
1345
|
/**
|
|
1331
1346
|
* design/174 — route a policy `ask` on the reserved question tool to this run's CONTENT-ask channel
|
|
1332
1347
|
* before it can become a park or a refusal. Called in the `ask` branch with the FINAL post-hook,
|
|
@@ -1454,12 +1469,12 @@ export interface ToolGateInput {
|
|
|
1454
1469
|
* resolution for safety asks simply does not arm auto mode. The classifier can never AUTO-ALLOW by
|
|
1455
1470
|
* failing — only an affirmative `<block>no</block>` allows.
|
|
1456
1471
|
* EXCLUDED from that scope (the ask flows the ORIGINAL chain untouched, exactly as if this field
|
|
1457
|
-
* were absent for that one call):
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1461
|
-
*
|
|
1462
|
-
* answer provenance questions identically.
|
|
1472
|
+
* were absent for that one call): every ask whose ORIGIN is not the plain `policy` family — the
|
|
1473
|
+
* closed set and the classifier's per-origin table are in `ask-origin.ts` (an org-constrained ask on
|
|
1474
|
+
* either org arm, the reserved question tool, a call MARKED unresolvable, a hook-raised or
|
|
1475
|
+
* hook-promoted ask, an explicit matched `ask` rule, and the classifier's own denial-limit fallback).
|
|
1476
|
+
* The hook and ask-rule rows are the same pair the persisted-rule lane refuses, so the gate's two
|
|
1477
|
+
* ask→allow seams answer provenance questions identically.
|
|
1463
1478
|
*/
|
|
1464
1479
|
autoMode?: {
|
|
1465
1480
|
decider: import("./auto-mode.js").AutoModeDecider;
|
|
@@ -1723,6 +1738,35 @@ export declare function persistedRuleMandateOf(marks: {
|
|
|
1723
1738
|
* {@link import("./types.js").ReversibilityVerdict.mandated}. */
|
|
1724
1739
|
probeMandated?: boolean;
|
|
1725
1740
|
}): "operator_always" | "tool_marks" | "probe_mandate" | undefined;
|
|
1741
|
+
/**
|
|
1742
|
+
* The park closure's ONE structural seat — what the gate hands the durable park beyond the twelve
|
|
1743
|
+
* positional seats. Every member is optional and read by name, so a new thing the park must know
|
|
1744
|
+
* becomes a member here, never a fourteenth positional argument (each of the last four seats was
|
|
1745
|
+
* appended "at the tail so nothing re-binds", which is the growth this seat ends).
|
|
1746
|
+
*/
|
|
1747
|
+
export interface AskCarry {
|
|
1748
|
+
/** design/384 slice 2 — {@link ToolGateInput.callSignal}, verbatim: the park closure composes it
|
|
1749
|
+
* with the run signal into the halt×park fence's cut signal. Absent ⇒ the run-level binding alone. */
|
|
1750
|
+
readonly signal?: AbortSignal;
|
|
1751
|
+
/** The surviving ask's `denialLimitFallback` (the classifier denial-limit counts) run through the
|
|
1752
|
+
* window-less route station ({@link import("./auto-mode.js").unarmedWindow}) when the gate builds the
|
|
1753
|
+
* carry: a park is not a hand-out to a live approver, so nothing on this lane counts down. The
|
|
1754
|
+
* decision's member cannot state a window (its type has no such member), and this is the one place
|
|
1755
|
+
* the `0` a park row shows is written. Persisted on the row so a durable card shows the counts the
|
|
1756
|
+
* synchronous card shows. Absent then means: not a fallback ask. */
|
|
1757
|
+
readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1758
|
+
/** The surviving ask's engine-stamped origin word ({@link import("./ask-origin.js").AskOrigin}), persisted
|
|
1759
|
+
* on the row so a durable card is rendered by the same word the synchronous card is. */
|
|
1760
|
+
readonly origin?: import("./ask-origin.js").AskOrigin;
|
|
1761
|
+
}
|
|
1762
|
+
/** What a durable row persists of an {@link AskCarry}: the fallback member COPIED (the row never aliases
|
|
1763
|
+
* the gate's object; its window is already `0` — the gate's carry mint is the one writer of that
|
|
1764
|
+
* number) and the origin word only when it is a member of the closed set (the carry is typed, but the
|
|
1765
|
+
* row is the boundary a host reads back through — the same screen the summary applies). */
|
|
1766
|
+
export declare function askCarryRowMembers(carry: AskCarry | undefined): {
|
|
1767
|
+
denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
|
|
1768
|
+
origin?: import("./ask-origin.js").AskOrigin;
|
|
1769
|
+
};
|
|
1726
1770
|
/**
|
|
1727
1771
|
* The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
|
|
1728
1772
|
* structural ("a hook's `allow` cannot bypass the policy's `deny`/`ask`"):
|
package/dist/core/hooks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { coreMintedAutoDeniedOf, coreMintedResolutionOf, decisionText, describeThrown, isAskDenyResolution, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
2
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
3
|
-
import {
|
|
3
|
+
import { askOriginOf, classifierMayAnswer, isAskOrigin } from "./ask-origin.js";
|
|
4
|
+
import { denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
|
|
4
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
5
6
|
import { mintSystemReminder } from "./reminder-mint.js";
|
|
6
7
|
import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
|
|
@@ -424,6 +425,17 @@ export function persistedRuleMandateOf(marks) {
|
|
|
424
425
|
? "tool_marks"
|
|
425
426
|
: undefined;
|
|
426
427
|
}
|
|
428
|
+
export function askCarryRowMembers(carry) {
|
|
429
|
+
if (carry === undefined)
|
|
430
|
+
return {};
|
|
431
|
+
return {
|
|
432
|
+
...(carry.denialLimitFallback !== undefined ? { denialLimitFallback: { ...carry.denialLimitFallback } } : {}),
|
|
433
|
+
...(isAskOrigin(carry.origin) ? { origin: carry.origin } : {}),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function parkWith(suspendAsk, parkArgs, carry) {
|
|
437
|
+
return suspendAsk(...parkArgs, carry);
|
|
438
|
+
}
|
|
427
439
|
export async function runToolGate(input) {
|
|
428
440
|
const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
|
|
429
441
|
const { toolCallId, toolName } = event;
|
|
@@ -632,6 +644,11 @@ export async function runToolGate(input) {
|
|
|
632
644
|
}
|
|
633
645
|
let orgRealApprovalRequired = false;
|
|
634
646
|
let orgAskOrigin;
|
|
647
|
+
const askOriginFacts = () => ({
|
|
648
|
+
contentQuestion: toolName === ASK_USER_QUESTION_TOOL_NAME,
|
|
649
|
+
markedUnresolvable: input.isMarkedUnresolvable?.(input.event.toolCallId) === true,
|
|
650
|
+
org: orgAskOrigin,
|
|
651
|
+
});
|
|
635
652
|
let orgTightenCount = 0;
|
|
636
653
|
let resolvedApprover;
|
|
637
654
|
let orgEvidence = (() => {
|
|
@@ -798,17 +815,12 @@ export async function runToolGate(input) {
|
|
|
798
815
|
};
|
|
799
816
|
}
|
|
800
817
|
}
|
|
818
|
+
const originFacts = askOriginFacts();
|
|
801
819
|
if (decision.action === "ask") {
|
|
802
|
-
|
|
820
|
+
const stamped = { ...decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
|
|
821
|
+
decision = { ...stamped, origin: askOriginOf(stamped, originFacts) };
|
|
803
822
|
}
|
|
804
|
-
if (input.autoMode &&
|
|
805
|
-
!orgRealApprovalRequired &&
|
|
806
|
-
decision.action === "ask" &&
|
|
807
|
-
decision.decisionReason !== "hook" &&
|
|
808
|
-
decision.matchedAskRule === undefined &&
|
|
809
|
-
decision.denialLimitFallback === undefined &&
|
|
810
|
-
req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
811
|
-
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true) {
|
|
823
|
+
if (input.autoMode && decision.action === "ask" && decision.origin !== undefined && classifierMayAnswer(decision.origin)) {
|
|
812
824
|
const verdict = await input.autoMode.decider
|
|
813
825
|
.decide({ req, askMessage: decisionText(decision) }, input.abortSignal)
|
|
814
826
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -826,13 +838,14 @@ export async function runToolGate(input) {
|
|
|
826
838
|
const category = verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
827
839
|
const tracked = input.autoMode.denialTracking?.recordBlock();
|
|
828
840
|
if (tracked?.limitReached === true) {
|
|
829
|
-
|
|
841
|
+
const fallbackAsk = {
|
|
830
842
|
...decision,
|
|
831
843
|
message: denialLimitFallbackMessage(tracked.fallback, reason || category || toolName),
|
|
832
844
|
decisionReason: "classifier",
|
|
833
845
|
requiresRealApproval: true,
|
|
834
846
|
denialLimitFallback: tracked.fallback,
|
|
835
847
|
};
|
|
848
|
+
decision = { ...fallbackAsk, origin: askOriginOf(fallbackAsk, originFacts) };
|
|
836
849
|
denySource = "classifier";
|
|
837
850
|
}
|
|
838
851
|
else {
|
|
@@ -875,9 +888,14 @@ export async function runToolGate(input) {
|
|
|
875
888
|
const realApprovalOf = (d) => d.action === "ask" && d.requiresRealApproval === true
|
|
876
889
|
? { origin: orgAskOrigin !== undefined ? `org_${orgAskOrigin}` : "policy" }
|
|
877
890
|
: undefined;
|
|
891
|
+
const carryOf = (d) => ({
|
|
892
|
+
...(callSignal !== undefined ? { signal: callSignal } : {}),
|
|
893
|
+
...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
|
|
894
|
+
...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
|
|
895
|
+
});
|
|
878
896
|
if (suspendAsk && decision.action === "ask") {
|
|
879
897
|
const parkArgs = [req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined];
|
|
880
|
-
const suspended = await (
|
|
898
|
+
const suspended = await parkWith(suspendAsk, parkArgs, carryOf(decision));
|
|
881
899
|
if (suspended) {
|
|
882
900
|
if ("parkFailed" in suspended)
|
|
883
901
|
parkFailed = suspended.parkFailed;
|
|
@@ -897,7 +915,7 @@ export async function runToolGate(input) {
|
|
|
897
915
|
}
|
|
898
916
|
if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
|
|
899
917
|
const parkArgs = [req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined];
|
|
900
|
-
const suspended = await (
|
|
918
|
+
const suspended = await parkWith(suspendAsk, parkArgs, carryOf(decision));
|
|
901
919
|
if (suspended) {
|
|
902
920
|
if ("parkFailed" in suspended)
|
|
903
921
|
parkFailed = suspended.parkFailed;
|
|
@@ -932,7 +950,7 @@ export async function runToolGate(input) {
|
|
|
932
950
|
decision = resolved;
|
|
933
951
|
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
|
|
934
952
|
const parkArgs = [req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeMandated : undefined];
|
|
935
|
-
const suspended = await (
|
|
953
|
+
const suspended = await parkWith(suspendAsk, parkArgs, carryOf(askBeforeResolve));
|
|
936
954
|
if (suspended) {
|
|
937
955
|
if ("parkFailed" in suspended)
|
|
938
956
|
parkFailed = suspended.parkFailed;
|
|
@@ -949,7 +967,7 @@ export async function runToolGate(input) {
|
|
|
949
967
|
askDenyResolution === "approver_unavailable" ||
|
|
950
968
|
resolved.approverUnavailable === true) {
|
|
951
969
|
try {
|
|
952
|
-
input.onHeadlessDenialLimit?.({ toolName, toolCallId, fallback: askBeforeResolve.denialLimitFallback });
|
|
970
|
+
input.onHeadlessDenialLimit?.({ toolName, toolCallId, fallback: unarmedWindow(askBeforeResolve.denialLimitFallback) });
|
|
953
971
|
}
|
|
954
972
|
catch {
|
|
955
973
|
}
|
|
@@ -1035,6 +1053,7 @@ export async function runToolGate(input) {
|
|
|
1035
1053
|
editRewrittenSinceHuman = true;
|
|
1036
1054
|
}
|
|
1037
1055
|
const orgTightensBefore = orgTightenCount;
|
|
1056
|
+
orgAskOrigin = undefined;
|
|
1038
1057
|
recheck = await applyOrgLayer(recheck, editArgs);
|
|
1039
1058
|
const orgRaisedThisRound = orgTightenCount > orgTightensBefore;
|
|
1040
1059
|
if (recheck.action === "deny") {
|
|
@@ -1052,7 +1071,8 @@ export async function runToolGate(input) {
|
|
|
1052
1071
|
editArgs = recheck.updatedInput;
|
|
1053
1072
|
editRewrittenSinceHuman = true;
|
|
1054
1073
|
}
|
|
1055
|
-
const
|
|
1074
|
+
const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
|
|
1075
|
+
const editAskDecision = { ...editAskSnapshot, origin: askOriginOf(editAskSnapshot, askOriginFacts()) };
|
|
1056
1076
|
const editAskReq = { toolName, args: editArgs, toolCallId };
|
|
1057
1077
|
const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
|
|
1058
1078
|
resolvedApprover = rr.action !== "ask" ? rr.approver : undefined;
|
|
@@ -1105,6 +1125,7 @@ export async function runToolGate(input) {
|
|
|
1105
1125
|
reason: formatHookFeedback(denyReason, input.reminderMark),
|
|
1106
1126
|
...(denySettledBy !== undefined ? { settledBy: denySettledBy } : {}),
|
|
1107
1127
|
...(denyResolution !== undefined ? { resolution: denyResolution } : {}),
|
|
1128
|
+
...(denyAutoDenied ? { autoDenied: true } : {}),
|
|
1108
1129
|
...(denyApprover !== undefined ? { approver: denyApprover } : {}),
|
|
1109
1130
|
preToolContext,
|
|
1110
1131
|
};
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -1,58 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP client integration. Protocol revision `2025-11-25`, supplied by `@modelcontextprotocol/sdk` 1.30.0
|
|
3
|
-
* (core pins the SDK EXACTLY and does not negotiate the revision itself). Client side only — core never
|
|
4
|
-
* implements an MCP server.
|
|
5
|
-
*
|
|
6
|
-
* ── DEPRECATION-IMMUNITY REGISTRY (MCP revision `2026-07-28`) ────────────────────────────────────────
|
|
7
|
-
* The `2026-07-28` revision deprecates four surfaces this module has NEVER implemented. That is not an
|
|
8
|
-
* accident and not an unfinished edge: each one was declined on its own merits, and the revision has now
|
|
9
|
-
* put all four on a deprecation clock. So there is no migration debt here — and, symmetrically, none of
|
|
10
|
-
* the four may be "completed" later on the grounds that the client looks incomplete next to the older
|
|
11
|
-
* revision's feature list:
|
|
12
|
-
*
|
|
13
|
-
* 1. **roots** (`roots/list`, `notifications/roots/list_changed`) — never implemented. The replacement the
|
|
14
|
-
* revision itself names is what this module already does: directory/file scope travels as ordinary tool
|
|
15
|
-
* arguments or resource URIs, never as a client-hosted root list a server can enumerate.
|
|
16
|
-
* 2. **sampling** (`sampling/createMessage`) — never implemented. A server that wants model inference uses
|
|
17
|
-
* its own provider; core never lends the caller's model channel (or budget) to a connected server.
|
|
18
|
-
* 3. **logging** (`logging/setLevel`, `notifications/message`) — never implemented. Server diagnostics
|
|
19
|
-
* arrive as stdio stderr, or as ordinary tool/resource content, or not at all.
|
|
20
|
-
* 4. **HTTP+SSE transport** — never implemented. `buildTransport` builds stdio and Streamable HTTP only;
|
|
21
|
-
* the SDK's `SSEClientTransport` is not imported anywhere. The revision's SSE off-ramp is therefore a
|
|
22
|
-
* no-op here.
|
|
23
|
-
*
|
|
24
|
-
* Consequence for future edits: adding any of the four is a REGRESSION, not a gap being closed. The bar for
|
|
25
|
-
* reopening one is a LATER protocol revision that revives the surface, or a concrete consumer requirement
|
|
26
|
-
* recorded under its own ticket — not the absence of the feature.
|
|
27
|
-
*
|
|
28
|
-
* THE `2026-07-28` DELIVERY FORM, and why it does not narrow the four (design/384 §1, §2-J). That revision
|
|
29
|
-
* does not carry `roots/list`, `sampling/createMessage` or `elicitation/create` as standalone JSON-RPC
|
|
30
|
-
* methods at all. Its request-method table (as a 2026-era client dispatches it — CC 2.1.250's bundled era
|
|
31
|
-
* descriptor) is exactly: `tools/call`, `tools/list`, `prompts/get`, `prompts/list`, `resources/list`,
|
|
32
|
-
* `resources/templates/list`, `resources/read`, `completion/complete`, `server/discover`,
|
|
33
|
-
* `subscriptions/listen`. The three arrive instead through a DIFFERENT door — as embedded `inputRequests`
|
|
34
|
-
* inside an `input_required` result of `tools/call` / `prompts/get` / `resources/read`, answered by
|
|
35
|
-
* RETRYING the original request with `inputResponses`.
|
|
36
|
-
*
|
|
37
|
-
* That is a change of DELIVERY FORM ONLY, and it buys no exemption. Items 1 and 2 are reasoned about what
|
|
38
|
-
* the capability MEANS, not about how it is framed on the wire: an embedded `roots/list` enumerates the
|
|
39
|
-
* very root list item 1 refuses to host, and an embedded `sampling/createMessage` spends the very model
|
|
40
|
-
* channel and budget item 2 refuses to lend. So embedded roots/sampling are banned ON THE SAME TERMS and
|
|
41
|
-
* must clear the SAME reopening bar — a new door is not a reason, and "the registry only named the legacy
|
|
42
|
-
* method" is not a loophole. What this paragraph exists to prevent is the OPPOSITE misreading: items 1-4
|
|
43
|
-
* were written against the `2025-11-25` surface and are not themselves a ruling on the 2026 mechanics.
|
|
44
|
-
* `elicitation/create` — which core DOES implement (opt-in, form mode) and which is NOT one of the four —
|
|
45
|
-
* comes through that same door, so an era connection would have to decide how it is driven; that is
|
|
46
|
-
* design/384 P3's question, not this registry's. Nothing here is reachable today: core speaks `2025-11-25`,
|
|
47
|
-
* where all three are standalone methods, and core's `initialize` declares neither `roots` nor `sampling`
|
|
48
|
-
* (pinned).
|
|
49
|
-
*
|
|
50
|
-
* Two more revision-relevant absences, same reasoning, different cause:
|
|
51
|
-
* - `resources/subscribe` / `resources/unsubscribe` — the revision REMOVES the methods. Tool-list freshness
|
|
52
|
-
* here is served by the explicit `RefreshMcpTools` tool over the existing connection, which is also the
|
|
53
|
-
* only thing this module's connect-per-task connection lifetime can honestly support.
|
|
54
|
-
* - `ping` — likewise removed by the revision. Liveness here is the idle watchdog plus per-call timeouts.
|
|
55
|
-
*/
|
|
56
1
|
import { type ProtocolId } from "./protocol-table.js";
|
|
57
2
|
import type { AgentTool } from "../internal/harness-types.js";
|
|
58
3
|
import type { ImageContent, TextContent } from "../internal/llm.js";
|
|
@@ -313,6 +258,11 @@ export interface McpServerStatus {
|
|
|
313
258
|
};
|
|
314
259
|
toolNames?: string[];
|
|
315
260
|
error?: string;
|
|
261
|
+
/** Present iff `status === "failed"` — the failure's CLASS, judged from the thrown error object at
|
|
262
|
+
* connect time ({@link mcpFailureCodeOf}; the closed set is {@link MCP_FAILURE_CODES} plus the
|
|
263
|
+
* `http_<status>` form). A consumer keys an action off THIS (a `http_401` is a re-authorization
|
|
264
|
+
* door, a `spawn_failed` is a missing binary) instead of parsing the neutralized `error` text. */
|
|
265
|
+
errorCode?: McpFailureCode;
|
|
316
266
|
/**
|
|
317
267
|
* RB-437-a — set to `true` once core OBSERVES this server's transport close during the task; absent
|
|
318
268
|
* otherwise (never `false`: absence means "no close was observed", which is not the same as "alive").
|
|
@@ -460,6 +410,48 @@ export interface HttpTransportFailure {
|
|
|
460
410
|
* reached them" means.
|
|
461
411
|
*/
|
|
462
412
|
export declare function describeHttpTransportFailure(err: unknown): HttpTransportFailure | undefined;
|
|
413
|
+
/**
|
|
414
|
+
* The closed vocabulary of MCP connect-failure classes a per-server status record may carry
|
|
415
|
+
* ({@link McpServerStatus.errorCode}, relayed onto the wiring manifest's `mcp[]` read face). The
|
|
416
|
+
* transport family is named by the failure's SHAPE wherever the SDK exposes one (error class, JSON-RPC
|
|
417
|
+
* code, errno on the cause chain, HTTP status), so a consumer can map a class to an action without
|
|
418
|
+
* parsing the neutralized error string. Two words are the exception and are text-derived, because
|
|
419
|
+
* undici and the SDK surface those two conditions as bare messages with no code: `network` (a
|
|
420
|
+
* `TypeError` whose message matches `fetch failed|terminated|network` and carries no errno) and the
|
|
421
|
+
* non-`McpError` arm of `connection_closed` (a message matching `not connected|connection closed`).
|
|
422
|
+
* A wording change in those libraries moves such a failure to `unknown`, never to a wrong word; the
|
|
423
|
+
* classifier table pins both regexes. An HTTP endpoint that answered with a status is
|
|
424
|
+
* reported in the `http_<status>` form (`http_401` = the endpoint wants (re-)authorization, `http_5xx` =
|
|
425
|
+
* the endpoint is down) — a template member of the type beside these fixed words. Additive-only: a new
|
|
426
|
+
* word here is a new arm for consumers, never a re-meaning of an existing one.
|
|
427
|
+
* · `connect_refused` — a connect-phase errno (ECONNREFUSED / ENOTFOUND / …): the request provably never left;
|
|
428
|
+
* · `connection_failed` — a network errno past the connect phase (reset / timed out mid-exchange);
|
|
429
|
+
* · `network` — the HTTP request failed at the network layer with no errno on the cause chain;
|
|
430
|
+
* · `not_mcp_response` — the HTTP endpoint answered something that is not an MCP payload (no status to name);
|
|
431
|
+
* · `spawn_failed` — a stdio server process could not be started (a missing/unexecutable command);
|
|
432
|
+
* · `connection_closed` — the transport closed during the handshake (the server process exited, the stream ended);
|
|
433
|
+
* · `timeout` — the handshake's request timed out;
|
|
434
|
+
* · `protocol` — the server answered the handshake with an MCP/JSON-RPC error (a version mismatch, a
|
|
435
|
+
* rejected capability, …) — `error` carries the named condition;
|
|
436
|
+
* · `invalid_config` — the declaration itself could not be dialed (a malformed `url`): a configuration
|
|
437
|
+
* fault to fix in the spec, never an outage to wait out;
|
|
438
|
+
* · `unknown` — none of the above could be established from the thrown value.
|
|
439
|
+
*/
|
|
440
|
+
export declare const MCP_FAILURE_CODES: readonly ["connect_refused", "connection_failed", "network", "not_mcp_response", "spawn_failed", "connection_closed", "timeout", "protocol", "invalid_config", "unknown"];
|
|
441
|
+
export type McpFailureCode = (typeof MCP_FAILURE_CODES)[number] | `http_${number}`;
|
|
442
|
+
/**
|
|
443
|
+
* Classify one connect-time failure into {@link McpFailureCode}. Reads the thrown value's SHAPE
|
|
444
|
+
* (error class, JSON-RPC code, errno on the cause chain, HTTP status) first; only the two
|
|
445
|
+
* message-shaped conditions named on {@link MCP_FAILURE_CODES} (`network`, the non-`McpError`
|
|
446
|
+
* `connection_closed`) are decided by a fixed regex over the message, and no other word can be chosen
|
|
447
|
+
* by message text — a remote's own wording can at most leave a failure at `unknown`.
|
|
448
|
+
* `transport` disambiguates the errno family: on a stdio server an errno is the child
|
|
449
|
+
* process failing to spawn, on an HTTP server it is the socket; a caller that could not establish the
|
|
450
|
+
* kind passes `undefined` and the errno family reads as the transport-neutral `connection_failed`.
|
|
451
|
+
* Total: every input maps to a word, and `unknown` is the honest word for a value this table cannot
|
|
452
|
+
* read (a caller must not receive a guess).
|
|
453
|
+
*/
|
|
454
|
+
export declare function mcpFailureCodeOf(err: unknown, transport: "stdio" | "http" | undefined): McpFailureCode;
|
|
463
455
|
export { normalizeNameSegment as normalizeMcpName, clampNameSegment } from "./protocol-naming.js";
|
|
464
456
|
export * from "./image-downsample.js";
|
|
465
457
|
/**
|