@sema-agent/server 7.135.0 → 7.136.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/MIGRATION.md +64 -2
- package/USAGE.md +62 -25
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +1 -0
- package/dist/approval-content-kind.js +1 -1
- package/dist/auth-bridge.d.ts +34 -5
- package/dist/auth-bridge.js +20 -3
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/resolve-spec.js +10 -7
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stores.js +2 -3
- package/dist/boot/web-admin.js +10 -17
- package/dist/brain.js +12 -3
- package/dist/budget.d.ts +3 -3
- package/dist/budget.js +4 -0
- package/dist/capabilities/mcp-probe.d.ts +5 -4
- package/dist/capability-facts.d.ts +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- package/dist/config-catalog.js +12 -10
- package/dist/config-d-migration.js +1 -1
- package/dist/config-invariants.d.ts +1 -1
- package/dist/config-invariants.js +8 -0
- package/dist/config-lkg.d.ts +0 -38
- package/dist/config-lkg.js +1 -83
- package/dist/config-types.d.ts +12 -4
- package/dist/config.js +26 -8
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +6 -4
- package/dist/file-history-disclosure.js +22 -6
- package/dist/fleet-client.d.ts +32 -1
- package/dist/fleet-client.js +24 -4
- package/dist/fleet-lease.d.ts +7 -3
- package/dist/fleet-lease.js +31 -23
- package/dist/hooks/hook-llm.js +14 -8
- package/dist/hooks/hook-runner.d.ts +4 -4
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +28 -9
- package/dist/http/checkpoint-ctx.js +5 -1
- package/dist/http/dispatch.js +9 -9
- package/dist/http/resume-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- package/dist/http/routes/approvals-assistant.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +47 -12
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/session-sync.js +31 -9
- package/dist/http/routes/sessions.js +18 -14
- package/dist/http/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +5 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/model-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -1
- package/dist/object-store-buckets.d.ts +13 -0
- package/dist/object-store-buckets.js +3 -0
- package/dist/observability/fail-open.d.ts +6 -6
- package/dist/observability/fail-open.js +6 -6
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -1
- package/dist/parked-decide.d.ts +4 -3
- package/dist/parked-decide.js +26 -7
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +5 -5
- package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -0
- package/dist/plugins/blob-backend.js +2 -1
- package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
- package/dist/plugins/checkpoint-index-columns.js +28 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +27 -3
- package/dist/plugins/checkpoint-store-sql.js +58 -29
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +30 -23
- package/dist/plugins/local-checkpoint-store.js +4 -3
- package/dist/plugins/one-time-migrations.d.ts +21 -28
- package/dist/plugins/one-time-migrations.js +39 -123
- package/dist/plugins/remote-env-k8s.d.ts +18 -5
- package/dist/plugins/remote-env-k8s.js +24 -7
- package/dist/plugins/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
- package/dist/plugins/snapshot-bucket-presence.js +35 -7
- package/dist/plugins/sql-errors.d.ts +39 -0
- package/dist/plugins/sql-errors.js +49 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +8 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -2
- package/dist/request-declaration-refusal.d.ts +94 -0
- package/dist/request-declaration-refusal.js +78 -0
- package/dist/request-image-urls.d.ts +6 -0
- package/dist/request-image-urls.js +27 -0
- package/dist/request-key-closure.d.ts +5 -0
- package/dist/request-key-closure.js +4 -1
- package/dist/runtime-caps-resolver.js +1 -1
- package/dist/sandbox-pkg-source.d.ts +28 -1
- package/dist/sandbox-pkg-source.js +1 -2
- package/dist/sealed-key.d.ts +19 -0
- package/dist/sealed-key.js +47 -4
- package/dist/session-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- package/dist/session-row.d.ts +3 -17
- package/dist/session-row.js +4 -0
- package/dist/session-sync.d.ts +15 -3
- package/dist/session-sync.js +5 -2
- package/dist/task-a2a.d.ts +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +6 -14
- package/dist/task-mcp.js +16 -51
- package/dist/tool-approval.d.ts +23 -10
- package/dist/tool-approval.js +51 -36
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/exit-seat.js +37 -13
- package/dist/trace/redact.d.ts +28 -12
- package/dist/trace/redact.js +233 -94
- package/dist/trace/self-minted-wire.d.ts +267 -0
- package/dist/trace/self-minted-wire.js +79 -0
- package/dist/url-credential-refusal.d.ts +52 -0
- package/dist/url-credential-refusal.js +14 -0
- package/dist/url-refusal.d.ts +13 -4
- package/dist/url-refusal.js +8 -2
- package/dist/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/package.json +3 -2
- package/scripts/drain-check-local.mjs +2 -1
- package/scripts/drain-check.mysql.sql +3 -1
- package/scripts/drain-check.pg.sql +3 -1
- package/skills/schedule.md +1 -1
- package/wire-changes/server@7.104.0.json +134 -0
- package/wire-changes/server@7.105.0.json +53 -0
- package/wire-changes/server@7.106.0.json +475 -0
- package/wire-changes/server@7.107.0.json +64 -0
- package/wire-changes/server@7.108.0.json +153 -0
- package/wire-changes/server@7.109.0.json +46 -0
- package/wire-changes/server@7.110.0.json +90 -0
- package/wire-changes/server@7.111.0.json +86 -0
- package/wire-changes/server@7.112.0.json +60 -0
- package/wire-changes/server@7.113.0.json +26 -0
- package/wire-changes/server@7.114.0.json +26 -0
- package/wire-changes/server@7.115.0.json +97 -0
- package/wire-changes/server@7.116.0.json +218 -0
- package/wire-changes/server@7.116.1.json +26 -0
- package/wire-changes/server@7.117.0.json +65 -0
- package/wire-changes/server@7.118.0.json +129 -0
- package/wire-changes/server@7.119.0.json +65 -0
- package/wire-changes/server@7.120.0.json +89 -0
- package/wire-changes/server@7.121.0.json +219 -0
- package/wire-changes/server@7.122.0.json +54 -0
- package/wire-changes/server@7.123.0.json +26 -0
- package/wire-changes/server@7.124.0.json +336 -0
- package/wire-changes/server@7.124.1.json +26 -0
- package/wire-changes/server@7.125.0.json +111 -0
- package/wire-changes/server@7.126.0.json +285 -0
- package/wire-changes/server@7.127.0.json +77 -0
- package/wire-changes/server@7.127.1.json +26 -0
- package/wire-changes/server@7.127.2.json +53 -0
- package/wire-changes/server@7.128.0.json +26 -0
- package/wire-changes/server@7.129.0.json +106 -0
- package/wire-changes/server@7.129.1.json +26 -0
- package/wire-changes/server@7.130.0.json +26 -0
- package/wire-changes/server@7.131.0.json +118 -0
- package/wire-changes/server@7.132.0.json +226 -0
- package/wire-changes/server@7.133.0.json +1155 -0
- package/wire-changes/server@7.134.0.json +107 -0
- package/wire-changes/server@7.135.0.json +174 -0
- package/wire-changes/server@7.136.0.json +448 -0
- package/dist/url-credential.d.ts +0 -52
- package/dist/url-credential.js +0 -49
package/dist/task-a2a.js
CHANGED
|
@@ -1,68 +1,213 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { describeNotStringArray, describeValueShape, findUrlCredential, isStringArray } from "@sema-agent/settings-schema";
|
|
3
|
+
import { errorKindOf } from "./observability/err-text.js";
|
|
4
4
|
import { readPlainStringRecord } from "./plain-string-record.js";
|
|
5
5
|
import { readRequestToolAxes } from "./request-tool-axes.js";
|
|
6
|
-
import { echoKeyName } from "./request-key-closure.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
6
|
+
import { MAX_KEY_ECHO, echoKeyName, keyClosureClause, scanKeyNames } from "./request-key-closure.js";
|
|
7
|
+
import { urlCredentialHeaderExit, urlCredentialRefusalReason } from "./url-credential-refusal.js";
|
|
8
|
+
import { declarationEntryNotObject, declarationIssueFacts, declarationNameIssue, declarationOverCapFacts, declaredName, judgeDeclarationKeys, requestDeclarationRefusal, throwDeclarationRefusal, } from "./request-declaration-refusal.js";
|
|
10
9
|
import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
|
|
11
10
|
export const MAX_REQUEST_A2A_PEERS = 32;
|
|
12
11
|
export function a2aInjectionHonored(config) {
|
|
13
|
-
if (config.lockedConfigKeys?.includes("a2a") === true)
|
|
14
|
-
return false;
|
|
15
|
-
if (config.compliancePosture !== undefined) {
|
|
16
|
-
const denies = resolveComplianceDenies(config.compliancePosture);
|
|
17
|
-
if (denies.has("a2a_peers"))
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
12
|
return !DEPLOYMENT_SHAPE_PREDICATES.multiTenant.holds(config);
|
|
21
13
|
}
|
|
22
14
|
const HTTP_URL_RE = /^https?:\/\//i;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
15
|
+
export const A2A_PEER_ENTRY_KEYS = ["name", "url", "cardUrl", "headers", "principalHeader", "allowSkills", "toolAxes"];
|
|
16
|
+
const A2A_PEER_ENTRY_KEY_SET = new Set(A2A_PEER_ENTRY_KEYS);
|
|
17
|
+
const NO_TOLERATED_KEYS = new Set();
|
|
18
|
+
const A2A_SEAT = {
|
|
19
|
+
list: "a2aPeers",
|
|
20
|
+
cap: MAX_REQUEST_A2A_PEERS,
|
|
21
|
+
subject: "peer",
|
|
22
|
+
declaration: "an A2A peer declaration",
|
|
23
|
+
declarations: "A2A peer declarations",
|
|
24
|
+
};
|
|
25
|
+
function peerUrlIssue(key) {
|
|
26
|
+
return (entry, at) => {
|
|
27
|
+
const value = entry[key];
|
|
28
|
+
if (key === "cardUrl" && value === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (typeof value !== "string" || !HTTP_URL_RE.test(value)) {
|
|
31
|
+
return { path: key, reason: `must be a string starting with "http://" or "https://" (never file://, never a bare host) — got ${describeValueShape(value)}` };
|
|
32
|
+
}
|
|
33
|
+
const found = findUrlCredential(value);
|
|
34
|
+
return found === undefined ? undefined : { path: key, reason: urlCredentialRefusalReason(found, urlCredentialHeaderExit(`${at}.headers`)), credential: found };
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const HEADERS_RESERVED_MEMBER_PATH = "headers.__proto__";
|
|
38
|
+
const PEER_SNAPSHOT = Symbol("a2aPeerSnapshot");
|
|
39
|
+
function openCloneNode(v, ancestors) {
|
|
40
|
+
if (v === null || v === undefined || typeof v === "string" || typeof v === "number" || typeof v === "boolean")
|
|
41
|
+
return { done: { ok: true, value: v } };
|
|
42
|
+
if (typeof v !== "object")
|
|
43
|
+
return { done: { ok: false, because: `a ${typeof v} value` } };
|
|
44
|
+
if (types.isProxy(v))
|
|
45
|
+
return { done: { ok: false, because: "not inspectable" } };
|
|
46
|
+
if (ancestors.has(v))
|
|
47
|
+
return { done: { ok: false, because: "cyclic" } };
|
|
48
|
+
const list = Array.isArray(v);
|
|
49
|
+
const proto = Object.getPrototypeOf(v);
|
|
50
|
+
if (list ? proto !== Array.prototype : proto !== Object.prototype && proto !== null)
|
|
51
|
+
return { done: { ok: false, because: "not plain data" } };
|
|
52
|
+
return { frame: { source: v, keys: Object.getOwnPropertyNames(v), target: list ? [] : {}, next: 0 } };
|
|
53
|
+
}
|
|
54
|
+
function putCloneMember(target, key, value, enumerable) {
|
|
55
|
+
if (enumerable && key !== "__proto__")
|
|
56
|
+
target[key] = value;
|
|
57
|
+
else
|
|
58
|
+
Object.defineProperty(target, key, { value, enumerable, writable: true, configurable: true });
|
|
59
|
+
}
|
|
60
|
+
function cloneDeclaration(value) {
|
|
61
|
+
try {
|
|
62
|
+
const ancestors = new Set();
|
|
63
|
+
const root = openCloneNode(value, ancestors);
|
|
64
|
+
if ("done" in root)
|
|
65
|
+
return root.done;
|
|
66
|
+
ancestors.add(root.frame.source);
|
|
67
|
+
const stack = [root.frame];
|
|
68
|
+
for (let frame = stack[stack.length - 1]; frame !== undefined; frame = stack[stack.length - 1]) {
|
|
69
|
+
const key = frame.keys[frame.next];
|
|
70
|
+
if (key === undefined) {
|
|
71
|
+
if (Array.isArray(frame.source) && Array.isArray(frame.target))
|
|
72
|
+
frame.target.length = frame.source.length;
|
|
73
|
+
ancestors.delete(frame.source);
|
|
74
|
+
stack.pop();
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
frame.next += 1;
|
|
78
|
+
if (key === "length" && Array.isArray(frame.target))
|
|
79
|
+
continue;
|
|
80
|
+
const descriptor = Object.getOwnPropertyDescriptor(frame.source, key);
|
|
81
|
+
if (descriptor === undefined)
|
|
82
|
+
return { ok: false, because: "not inspectable" };
|
|
83
|
+
if (!("value" in descriptor))
|
|
84
|
+
return { ok: false, because: "an accessor member" };
|
|
85
|
+
const child = openCloneNode(descriptor.value, ancestors);
|
|
86
|
+
if ("done" in child) {
|
|
87
|
+
if (!child.done.ok)
|
|
88
|
+
return child.done;
|
|
89
|
+
putCloneMember(frame.target, key, child.done.value, descriptor.enumerable === true);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
putCloneMember(frame.target, key, child.frame.target, descriptor.enumerable === true);
|
|
93
|
+
ancestors.add(child.frame.source);
|
|
94
|
+
stack.push(child.frame);
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, value: root.frame.target };
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return { ok: false, because: `not inspectable: ${errorKindOf(error)}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function isPeerSnapshot(value) {
|
|
103
|
+
return Object.hasOwn(value, PEER_SNAPSHOT);
|
|
104
|
+
}
|
|
105
|
+
function mintPeerSnapshot(entry, reread) {
|
|
106
|
+
const source = entry;
|
|
107
|
+
const allowSkills = source.allowSkills;
|
|
108
|
+
const headers = source.headers;
|
|
109
|
+
const toolAxes = source.toolAxes;
|
|
110
|
+
const toolAxesRead = toolAxes === undefined ? undefined : readRequestToolAxes(toolAxes, "a2a");
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
[PEER_SNAPSHOT]: true,
|
|
113
|
+
name: source.name,
|
|
114
|
+
url: source.url,
|
|
115
|
+
cardUrl: source.cardUrl,
|
|
116
|
+
principalHeader: source.principalHeader,
|
|
117
|
+
allowSkills: Array.isArray(allowSkills) ? Array.from(allowSkills) : allowSkills,
|
|
118
|
+
headers: headers === undefined ? undefined : readPlainStringRecord(headers),
|
|
119
|
+
toolAxes: toolAxesRead,
|
|
120
|
+
toolAxesDeclared: reread && toolAxesRead !== undefined && !toolAxesRead.ok ? cloneDeclaration(toolAxes) : undefined,
|
|
121
|
+
unknownKeys: Object.keys(source).filter((name) => !A2A_PEER_ENTRY_KEY_SET.has(name)),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function snapshotPeer(entry, reread) {
|
|
125
|
+
return isPeerSnapshot(entry) ? entry : mintPeerSnapshot(entry, reread);
|
|
126
|
+
}
|
|
127
|
+
export function snapshotRequestA2a(raw, options) {
|
|
128
|
+
if (!Array.isArray(raw))
|
|
129
|
+
return raw;
|
|
130
|
+
const kept = raw.slice(0, MAX_REQUEST_A2A_PEERS).map((e) => (e !== null && typeof e === "object" && !Array.isArray(e) ? snapshotPeer(e, options.reread) : e));
|
|
131
|
+
if (raw.length > MAX_REQUEST_A2A_PEERS)
|
|
132
|
+
kept.length = raw.length;
|
|
133
|
+
return kept;
|
|
134
|
+
}
|
|
135
|
+
function toolAxesIssue(read) {
|
|
136
|
+
return { path: "toolAxes", reason: read.problem };
|
|
137
|
+
}
|
|
138
|
+
function toolAxesNotCloneableIssue(read, clone) {
|
|
139
|
+
const because = clone === undefined || clone.ok ? "unavailable" : clone.because;
|
|
140
|
+
return { path: "toolAxes", reason: `declaration not cloneable (${because}) — the one-time migration re-reads the stored declaration as plain JSON data and this one is not; ${read.problem}` };
|
|
141
|
+
}
|
|
142
|
+
const A2A_PEER_KEY_JUDGES = {
|
|
143
|
+
name: declarationNameIssue,
|
|
144
|
+
url: peerUrlIssue("url"),
|
|
145
|
+
cardUrl: peerUrlIssue("cardUrl"),
|
|
146
|
+
headers: (o) => {
|
|
147
|
+
const read = o.headers;
|
|
148
|
+
if (read === undefined)
|
|
149
|
+
return undefined;
|
|
150
|
+
if (!read.ok)
|
|
151
|
+
return { path: "headers", reason: `must be a plain object of string values — ${read.member === undefined ? read.fault : `its member "${echoKeyName("", read.member)}" ${read.fault}`}` };
|
|
152
|
+
const reserved = read.stripped[0];
|
|
153
|
+
return reserved === undefined ? undefined : { path: HEADERS_RESERVED_MEMBER_PATH, reason: reserved.fault };
|
|
154
|
+
},
|
|
155
|
+
principalHeader: (o) => (o.principalHeader === undefined || typeof o.principalHeader === "string" ? undefined : { path: "principalHeader", reason: `must be a string (a header name) — got ${describeValueShape(o.principalHeader)}` }),
|
|
156
|
+
allowSkills: (o) => o.allowSkills === undefined || isStringArray(o.allowSkills)
|
|
157
|
+
? undefined
|
|
158
|
+
: { path: "allowSkills", reason: `must be an array of strings (omit the key to leave the peer's skills unrestricted) — got ${describeNotStringArray(o.allowSkills)}` },
|
|
159
|
+
toolAxes: (o) => {
|
|
160
|
+
const read = o.toolAxes;
|
|
161
|
+
return read === undefined || read.ok ? undefined : toolAxesIssue(read);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
function normalizeA2aPeer(e, leg, at, storedToolAxes) {
|
|
165
|
+
const dropBecause = (issue) => ({ spec: null, because: declarationIssueFacts(at, issue) });
|
|
166
|
+
const notObject = declarationEntryNotObject(A2A_SEAT, e, at);
|
|
167
|
+
if (notObject !== undefined)
|
|
168
|
+
return { spec: null, because: { field: at, reason: notObject } };
|
|
169
|
+
const o = snapshotPeer(e, storedToolAxes !== undefined);
|
|
170
|
+
for (const key of A2A_PEER_ENTRY_KEYS) {
|
|
171
|
+
const issue = A2A_PEER_KEY_JUDGES[key](o, at);
|
|
172
|
+
if (issue === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
if (issue.credential !== undefined)
|
|
175
|
+
continue;
|
|
176
|
+
if (leg === "resume" && issue.path === HEADERS_RESERVED_MEMBER_PATH)
|
|
177
|
+
continue;
|
|
178
|
+
if (key === "toolAxes" && storedToolAxes !== undefined)
|
|
179
|
+
continue;
|
|
180
|
+
return dropBecause(issue);
|
|
181
|
+
}
|
|
42
182
|
const spec = { name: o.name, url: o.url };
|
|
43
|
-
if (
|
|
183
|
+
if (o.cardUrl !== undefined)
|
|
44
184
|
spec.cardUrl = o.cardUrl;
|
|
185
|
+
const { headers, toolAxes: axes } = o;
|
|
45
186
|
if (headers?.ok === true)
|
|
46
187
|
spec.headers = headers.record;
|
|
47
|
-
|
|
188
|
+
const lost = headers?.ok === true && headers.stripped.length > 0 ? { headersLost: headers.stripped.map((m) => echoKeyName("headers.", m.name)) } : {};
|
|
189
|
+
if (o.principalHeader !== undefined)
|
|
48
190
|
spec.principalHeader = o.principalHeader;
|
|
49
|
-
if (
|
|
191
|
+
if (o.allowSkills !== undefined)
|
|
50
192
|
spec.allowSkills = o.allowSkills;
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
193
|
+
if (axes !== undefined) {
|
|
194
|
+
if (!axes.ok) {
|
|
195
|
+
const unread = dropBecause(toolAxesIssue(axes));
|
|
196
|
+
if (storedToolAxes === undefined)
|
|
197
|
+
return unread;
|
|
198
|
+
const declared = o.toolAxesDeclared;
|
|
199
|
+
if (declared === undefined || !declared.ok)
|
|
200
|
+
return dropBecause(toolAxesNotCloneableIssue(axes, declared));
|
|
201
|
+
const before = storedToolAxes.a2a(declared.value);
|
|
55
202
|
if (before === null)
|
|
56
|
-
return
|
|
203
|
+
return unread;
|
|
57
204
|
const again = readRequestToolAxes(before, "a2a");
|
|
58
205
|
if (!again.ok)
|
|
59
|
-
return
|
|
206
|
+
return unread;
|
|
60
207
|
if (again.toolAxes !== undefined)
|
|
61
208
|
spec.toolAxes = again.toolAxes;
|
|
62
209
|
return { spec, toolAxesReread: axes.problem, ...lost };
|
|
63
210
|
}
|
|
64
|
-
if (!axes.ok)
|
|
65
|
-
return dropped;
|
|
66
211
|
if (axes.toolAxes !== undefined)
|
|
67
212
|
spec.toolAxes = axes.toolAxes;
|
|
68
213
|
}
|
|
@@ -81,59 +226,44 @@ function validateRequestA2aEntries(raw, leg, storedToolAxes) {
|
|
|
81
226
|
const dropped = [];
|
|
82
227
|
const toolAxesReread = [];
|
|
83
228
|
const headersLost = [];
|
|
84
|
-
for (const e of raw.slice(0, MAX_REQUEST_A2A_PEERS)) {
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
ok.push(
|
|
88
|
-
if (
|
|
89
|
-
toolAxesReread.push({ peer:
|
|
90
|
-
if (
|
|
91
|
-
headersLost.push({ peer:
|
|
229
|
+
for (const [i, e] of raw.slice(0, MAX_REQUEST_A2A_PEERS).entries()) {
|
|
230
|
+
const read = normalizeA2aPeer(e, leg, `${A2A_SEAT.list}[${i}]`, storedToolAxes);
|
|
231
|
+
if (read.spec !== null) {
|
|
232
|
+
ok.push(read.spec);
|
|
233
|
+
if (read.toolAxesReread !== undefined)
|
|
234
|
+
toolAxesReread.push({ peer: read.spec.name, problem: read.toolAxesReread });
|
|
235
|
+
if (read.headersLost !== undefined)
|
|
236
|
+
headersLost.push({ peer: read.spec.name, lost: read.headersLost });
|
|
92
237
|
}
|
|
93
238
|
else
|
|
94
|
-
dropped.push(
|
|
239
|
+
dropped.push({ peer: declaredName(e).slice(0, 128), ...read.because });
|
|
240
|
+
}
|
|
241
|
+
if (raw.length > MAX_REQUEST_A2A_PEERS) {
|
|
242
|
+
dropped.push({ peer: `(+${raw.length - MAX_REQUEST_A2A_PEERS} over cap of ${MAX_REQUEST_A2A_PEERS})`, ...declarationOverCapFacts(A2A_SEAT, raw.length) });
|
|
95
243
|
}
|
|
96
|
-
if (raw.length > MAX_REQUEST_A2A_PEERS)
|
|
97
|
-
dropped.push(`(+${raw.length - MAX_REQUEST_A2A_PEERS} over cap of ${MAX_REQUEST_A2A_PEERS})`);
|
|
98
244
|
return { ok, dropped, toolAxesReread, headersLost };
|
|
99
245
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
246
|
+
function requestA2aPeerRefusal(entry, at) {
|
|
247
|
+
const snapshot = snapshotPeer(entry, false);
|
|
248
|
+
const buckets = { unknown: [], unsupported: [] };
|
|
249
|
+
scanKeyNames(snapshot.unknownKeys, `${at}.`, A2A_PEER_ENTRY_KEY_SET, NO_TOLERATED_KEYS, buckets);
|
|
250
|
+
if (buckets.unknown.length > 0) {
|
|
251
|
+
const unknownKeys = buckets.unknown.sort();
|
|
252
|
+
return {
|
|
253
|
+
errorCode: "request.body_shape",
|
|
254
|
+
message: `body: unrecognized key(s) on an A2A peer declaration — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
|
|
255
|
+
`${keyClosureClause(unknownKeys, "unknown (not an a2aPeers[] key — check the spelling)")}. ` +
|
|
256
|
+
`Accepted per entry: ${[...A2A_PEER_ENTRY_KEYS].sort().join(",")}`,
|
|
257
|
+
unknownKeys: unknownKeys.slice(0, MAX_KEY_ECHO),
|
|
258
|
+
};
|
|
108
259
|
}
|
|
109
|
-
|
|
110
|
-
if (peer === null || typeof peer !== "object")
|
|
111
|
-
continue;
|
|
112
|
-
const entry = peer;
|
|
113
|
-
for (const key of A2A_PEER_URL_KEYS) {
|
|
114
|
-
const value = entry[key];
|
|
115
|
-
const found = typeof value === "string" ? findUrlCredential(value) : undefined;
|
|
116
|
-
if (found === undefined)
|
|
117
|
-
continue;
|
|
118
|
-
const at = `a2aPeers[${i}]`;
|
|
119
|
-
return buildRequestUrlCredentialRefusal(`${at}.${key}`, found, urlCredentialHeaderExit(`${at}.headers`), typeof entry.name === "string" && entry.name !== "" ? `peer "${echoKeyName("", entry.name)}"` : undefined);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return undefined;
|
|
260
|
+
return judgeDeclarationKeys(A2A_SEAT, snapshot, at, A2A_PEER_ENTRY_KEYS, A2A_PEER_KEY_JUDGES);
|
|
123
261
|
}
|
|
124
|
-
export function
|
|
125
|
-
|
|
126
|
-
if (refusal !== undefined)
|
|
127
|
-
throw createCodedHttpError(400, "request.field_invalid", refusal.message, refusal.facts);
|
|
262
|
+
export function requestA2aDeclarationRefusal(list) {
|
|
263
|
+
return requestDeclarationRefusal(A2A_SEAT, list, requestA2aPeerRefusal);
|
|
128
264
|
}
|
|
129
|
-
export function
|
|
130
|
-
|
|
131
|
-
return;
|
|
132
|
-
if (bodyA2a === undefined || bodyA2a === null)
|
|
133
|
-
return;
|
|
134
|
-
throw new HttpError(GOVERNANCE_HTTP_STATUS["config.locked_key"], "`a2aPeers` is administratively locked by this deployment (locked key \"a2a\") — task-supplied A2A peers are refused; locks are config-time and there is no request-time unlock", {
|
|
135
|
-
code: "config.locked_key",
|
|
136
|
-
});
|
|
265
|
+
export function assertRequestA2aEntries(bodyA2a) {
|
|
266
|
+
throwDeclarationRefusal(requestA2aDeclarationRefusal(bodyA2a));
|
|
137
267
|
}
|
|
138
268
|
export function mergeRequestA2a(baseline, gated) {
|
|
139
269
|
if (gated.length === 0)
|
package/dist/task-mcp.d.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { type CompliancePosture, type EngineNotice, type LockedKey, type McpInjectionDropReason, type McpServerSpec } from "@sema-agent/core";
|
|
26
26
|
import { type McpIdentityHeadersIssue, type RequestMcpServerSpec } from "./mcp-assertion-header.js";
|
|
27
|
-
import { type
|
|
27
|
+
import { type DeclarationRefusal } from "./request-declaration-refusal.js";
|
|
28
28
|
import type { StoredToolAxesRereadM19 } from "./stored-request-tool-axes-m19.js";
|
|
29
29
|
/** Bound on caller-supplied servers (anti-DoS; a real local `.mcp.json` has a handful). */
|
|
30
30
|
export declare const MAX_REQUEST_MCP_SERVERS = 32;
|
|
@@ -142,30 +142,22 @@ export declare function assertRequestMcpUnlocked(bodyMcp: unknown, lockedKeys: R
|
|
|
142
142
|
/** 一条**原始**请求条目上两个身份头的问题类(没有问题 / 不是对象 / 不是 http 传输 ⇒ `undefined`)—— 提交腿的门、探测口、重放腿的披露三处共读,
|
|
143
143
|
* 判据本体在 `mcp-assertion-header.ts`;这里只负责把「条目 → 传输对象」那一步读出来。 */
|
|
144
144
|
export declare function requestEntryIdentityIssue(entry: unknown): McpIdentityHeadersIssue | undefined;
|
|
145
|
-
/** 请求面 `mcpServers` 的一次拒:码 + 句 + 词表外的键名表(只在闭集那一形上在场)+ 机读位(只在「url 带凭据」那一形上在场)。两扇门(fresh 提交腿 / 探测口)同一份,状态恒 400。 */
|
|
146
|
-
export interface RequestMcpRefusal {
|
|
147
|
-
readonly errorCode: "request.body_shape" | "request.field_invalid";
|
|
148
|
-
readonly message: string;
|
|
149
|
-
/** 词表外的键,全路径形(`mcpServers[0].transport.header`;已清洗 + 截长 + 排序 + 封顶)。 */
|
|
150
|
-
readonly unknownKeys?: readonly string[];
|
|
151
|
-
/** [ref]:`transport.url` 带凭据 ⇒ 拒体顶层的 `field`(`mcpServers[i].transport.url`)+ `reason`(判官的拒因词,闭集)—— 与 `request.field_invalid` 上既有的 `field` / `reason` 同名同读。 */
|
|
152
|
-
readonly facts?: UrlCredentialFacts;
|
|
153
|
-
}
|
|
154
145
|
/**
|
|
155
146
|
* 请求面 `mcpServers`「这份声明能不能原样用」的**唯一回答者** —— fresh 提交腿({@link assertRequestMcpEntries})与探测口
|
|
156
147
|
* (`capabilities/mcp-probe.ts` 的 `readMcpProbeBody`)都问它、都原样转述它的答案(码与句逐字相同)。`undefined` = 能原样用,或列表缺席 / `null`(没声明;各门自己定缺席的意思)。
|
|
157
148
|
*
|
|
158
149
|
* ## 律:一个回答者、一个拒形,先列表后条目、条目里先键名后键值,第一处坏形即答
|
|
159
|
-
* ① **列表**:在场却不是数组 / 超过 {@link MAX_REQUEST_MCP_SERVERS} 条 / 某一条不是对象 ⇒ `400 request.field_invalid`,点名 `mcpServers` 或 `mcpServers[i]`(句尾同一句,见
|
|
150
|
+
* ① **列表**:在场却不是数组 / 超过 {@link MAX_REQUEST_MCP_SERVERS} 条 / 某一条不是对象 ⇒ `400 request.field_invalid`,点名 `mcpServers` 或 `mcpServers[i]`(句尾同一句,见 `request-declaration-refusal.ts` 的 `LIST_REFUSAL_TAIL`);
|
|
151
|
+
* 超上限是撞帽拒的全仓同一形(`limit-refusal.ts`:头一句 `mcpServers has N entries — at most 32 are accepted`,体带 `field` · `reason:"over_cap"` · `count` · `cap`)。
|
|
160
152
|
* 整份拒而不是挂前 32 条 / 摘掉读不出的那条 / 整只忽略:一份被悄悄截短、少了几条或整只没生效的声明,在发送方那里读起来是「生效了」。
|
|
161
153
|
* ② **键名**:条目层、或 `transport` 按 `kind` 那一形的键闭集之外的键 ⇒ `400 request.body_shape`,拒体带 `unknownKeys`(拼错的键被悄悄不读,同样读起来像生效了)。
|
|
162
154
|
* ③ **身份头的键组合**(`principalHeader` 与 `assertionHeader` 同设 / `assertionHeader` 非串)⇒ {@link requestEntryIdentityRefusal}(同设那一句对契约包解析期原句逐字)。
|
|
163
|
-
* ④ **受理键的值** ⇒ 逐键问判官表 {@link ENTRY_KEY_JUDGES},`400 request.field_invalid`,拒句点名 `mcpServers[i].<键路径>`、server 名与**该键判官的原因句**({@link
|
|
155
|
+
* ④ **受理键的值** ⇒ 逐键问判官表 {@link ENTRY_KEY_JUDGES},`400 request.field_invalid`,拒句点名 `mcpServers[i].<键路径>`、server 名与**该键判官的原因句**({@link declarationFieldRefusal})。
|
|
164
156
|
* 判官**就是读器用的那一只**,不是第二份:`toolAxes` = {@link readRequestToolAxes}(引擎根导出的 `readToolAxes` + 请求腿不收 `boundaryFlow`);`toolFaces` = 引擎根导出的 `readToolFaces`;
|
|
165
157
|
* `startupTimeoutMs` = 引擎根导出的 `mcpStartupTimeoutMs`(坏值抛 `config.mcp_startup_timeout_invalid`,本门把那一句作原因句);`contentOrigin` = `isContentOriginWord`;`source` = `isMcpSourceLabel`;
|
|
166
158
|
* `name` = `isUsableWireName`;`allowTools` / `transport.args` = {@link isStringArray};`transport.env` / `headers` = 契约包读器 {@link readRequestMcpTransport} 的纯数据记录读法;`elicitation` = {@link isElicitationFlag};
|
|
167
159
|
* `transport` 的硬字段(`kind` / `command` / `url`)与软字段(`args` / `env` / `headers` / `principalHeader`)= {@link readRequestMcpTransport}(读器投影 transport 的那一只函数,门读它报的 `lost`;读器在契约包,本仓只据 `lost[0]` 拒、据 `lost` 在重放腿逐处点名);
|
|
168
|
-
* 读器交回的 http `url` 再问「带不带凭据」=
|
|
160
|
+
* 读器交回的 http `url` 再问「带不带凭据」= 契约包根的 `findUrlCredential`([ref];拒体另带机读 `field` + `reason`,见 {@link transportUrlCredentialIssue})。
|
|
169
161
|
* 于是判官受理的条目,读器({@link normalizeServer})交回的就是申报本身(只差判官自己的合法归一:空容器 / `skillScopeEligible:false` 折成缺席、`toolAxes` 取引擎的拷贝)——
|
|
170
162
|
* 探测口拨的那一份因此就是提交腿会挂的那一份,不另比一遍(另比就是第二只回答者)。唯一按门分的后果是「声明了 `assertionHeader`」这一个事实(引擎尚无座,
|
|
171
163
|
* 承载、不发送):新提交照挂 + 运维 info,探测口整条拒 —— 判据是同一只谓词 `specWithholdsMcpAssertionHeader`(开关 `MCP_ASSERTION_HEADER_SENT` 假时「声明了」即「被扣下」),不是第二份判法(见 `readMcpProbeBody`)。
|
|
@@ -176,7 +168,7 @@ export interface RequestMcpRefusal {
|
|
|
176
168
|
* 关于 `contentOrigin` 的信任语义(core 把它定性为**部署面**键):请求腿的条目只有在 {@link mcpInjectionHonored} 为真(单用户 = 请求者就是这台 worker 的超管)时才被读进 `TaskSpec.mcp`,
|
|
177
169
|
* 多租户部署整只数组被忽略 ⇒ 租户**结构上**够不着这个键;名字撞车时 baseline 恒赢({@link mergeRequestMcp})⇒ 调用方也改写不了一台**已配置** server 的声明。
|
|
178
170
|
*/
|
|
179
|
-
export declare function requestMcpDeclarationRefusal(list: unknown):
|
|
171
|
+
export declare function requestMcpDeclarationRefusal(list: unknown): DeclarationRefusal | undefined;
|
|
180
172
|
/**
|
|
181
173
|
* fresh 提交腿的 `mcpServers` 门:{@link requestMcpDeclarationRefusal} 有答案 ⇒ 同步 400(码 / 句 / `unknownKeys` 原样)。
|
|
182
174
|
*
|
package/dist/task-mcp.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { mcpInjectionDroppedNotice, mcpStartupTimeoutMs, readToolFaces, resolveComplianceDenies } from "@sema-agent/core";
|
|
2
|
-
import { MCP_CONTENT_ORIGINS, describeBadContentOrigin,
|
|
3
|
-
import { HttpError
|
|
2
|
+
import { MCP_CONTENT_ORIGINS, describeBadContentOrigin, describeNotStringArray, describeValueShape, findUrlCredential, isContentOriginWord, isStringArray, isUsableWireName, readRequestMcpTransport, stripControlAndBidi, } from "@sema-agent/settings-schema";
|
|
3
|
+
import { HttpError } from "./security.js";
|
|
4
4
|
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
5
5
|
import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
|
|
6
6
|
import { MCP_SOURCE_LABEL_MAX, isMcpSourceLabel, readMcpSourceLabel } from "./mcp-source-label.js";
|
|
7
7
|
import { describeMcpAssertionHeaderNotAString, describeMcpIdentityHeadersConflict, mcpIdentityHeadersIssue, noteMcpAssertionHeaderIgnored, noteMcpAssertionHeaderNotSent, specWithholdsMcpAssertionHeader, toCoreMcpServerSpec } from "./mcp-assertion-header.js";
|
|
8
8
|
import { MAX_KEY_ECHO, echoKeyName, keyClosureClause, scanKeyClosure } from "./request-key-closure.js";
|
|
9
9
|
import { readRequestToolAxes } from "./request-tool-axes.js";
|
|
10
|
-
import {
|
|
10
|
+
import { urlCredentialHeaderExit, urlCredentialRefusalReason } from "./url-credential-refusal.js";
|
|
11
|
+
import { MALFORMED_NAME, declarationFieldRefusal, declarationNameIssue, declaredName, judgeDeclarationKeys, requestDeclarationRefusal, throwDeclarationRefusal, } from "./request-declaration-refusal.js";
|
|
11
12
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
12
13
|
export const MAX_REQUEST_MCP_DROP_NAMES = MAX_REQUEST_MCP_SERVERS;
|
|
13
14
|
export const MCP_SERVER_ENTRY_KEYS = ["allowTools", "contentOrigin", "elicitation", "name", "source", "startupTimeoutMs", "toolAxes", "toolFaces", "transport"];
|
|
@@ -27,12 +28,6 @@ function isElicitationFlag(v) {
|
|
|
27
28
|
return typeof v === "boolean";
|
|
28
29
|
}
|
|
29
30
|
const bad = (field) => ({ ok: false, field });
|
|
30
|
-
const MALFORMED_NAME = "<malformed>";
|
|
31
|
-
function declaredName(e) {
|
|
32
|
-
return e && typeof e === "object" && typeof e.name === "string" && e.name !== ""
|
|
33
|
-
? String(e.name)
|
|
34
|
-
: MALFORMED_NAME;
|
|
35
|
-
}
|
|
36
31
|
function normalizeServer(e, noteBadOrigin, storedToolAxes) {
|
|
37
32
|
if (!e || typeof e !== "object")
|
|
38
33
|
return bad("entry");
|
|
@@ -133,34 +128,21 @@ function requestEntryIdentityRefusal(entry, at) {
|
|
|
133
128
|
return { errorCode: "request.body_shape", message: describeMcpIdentityHeadersConflict(echoKeyName("", declaredName(entry))) };
|
|
134
129
|
const transport = entry.transport;
|
|
135
130
|
const value = isRecord(transport) ? transport.assertionHeader : undefined;
|
|
136
|
-
return
|
|
131
|
+
return declarationFieldRefusal(MCP_SEAT, entry, at, { path: "transport.assertionHeader", reason: describeMcpAssertionHeaderNotAString(value) });
|
|
137
132
|
}
|
|
138
133
|
export function requestMcpDeclarationRefusal(list) {
|
|
139
|
-
|
|
140
|
-
return undefined;
|
|
141
|
-
if (!Array.isArray(list))
|
|
142
|
-
return listRefusal(`mcpServers is ${describeValueShape(list)}, not an array of MCP server declarations`);
|
|
143
|
-
if (list.length > MAX_REQUEST_MCP_SERVERS)
|
|
144
|
-
return listRefusal(`mcpServers carries ${list.length} declarations, above this deployment's ceiling of ${MAX_REQUEST_MCP_SERVERS}`);
|
|
145
|
-
for (let i = 0; i < list.length; i++) {
|
|
146
|
-
const refusal = requestEntryRefusal(list[i], `mcpServers[${i}]`);
|
|
147
|
-
if (refusal !== undefined)
|
|
148
|
-
return refusal;
|
|
149
|
-
}
|
|
150
|
-
return undefined;
|
|
134
|
+
return requestDeclarationRefusal(MCP_SEAT, list, requestEntryRefusal);
|
|
151
135
|
}
|
|
152
136
|
export function assertRequestMcpEntries(bodyMcp) {
|
|
153
|
-
|
|
154
|
-
if (refusal === undefined)
|
|
155
|
-
return;
|
|
156
|
-
const extra = { ...(refusal.unknownKeys === undefined ? {} : { unknownKeys: [...refusal.unknownKeys] }), ...(refusal.facts ?? {}) };
|
|
157
|
-
throw createCodedHttpError(400, refusal.errorCode, refusal.message, Object.keys(extra).length === 0 ? undefined : extra);
|
|
158
|
-
}
|
|
159
|
-
const LIST_REFUSAL_TAIL = "The request is refused rather than acting on part of the list: declarations that silently did not apply would read as in effect to whoever sent them. " +
|
|
160
|
-
"Fix the list (an empty list declares none).";
|
|
161
|
-
function listRefusal(head) {
|
|
162
|
-
return { errorCode: "request.field_invalid", message: `${head}. ${LIST_REFUSAL_TAIL}` };
|
|
137
|
+
throwDeclarationRefusal(requestMcpDeclarationRefusal(bodyMcp));
|
|
163
138
|
}
|
|
139
|
+
const MCP_SEAT = {
|
|
140
|
+
list: "mcpServers",
|
|
141
|
+
cap: MAX_REQUEST_MCP_SERVERS,
|
|
142
|
+
subject: "server",
|
|
143
|
+
declaration: "an MCP server declaration",
|
|
144
|
+
declarations: "MCP server declarations",
|
|
145
|
+
};
|
|
164
146
|
const ENTRY_KEY_SET = new Set(MCP_SERVER_ENTRY_KEYS);
|
|
165
147
|
const STDIO_TRANSPORT_KEY_SET = new Set(STDIO_MCP_TRANSPORT_KEYS);
|
|
166
148
|
const HTTP_TRANSPORT_KEY_SET = new Set(HTTP_MCP_TRANSPORT_KEYS);
|
|
@@ -194,7 +176,7 @@ const ENTRY_KEY_JUDGES = {
|
|
|
194
176
|
` (omit the key for the default: this server's tools classify as external content)`,
|
|
195
177
|
},
|
|
196
178
|
elicitation: (o) => (o.elicitation === undefined || isElicitationFlag(o.elicitation) ? undefined : { path: "elicitation", reason: `must be a boolean — got ${describeValueShape(o.elicitation)}` }),
|
|
197
|
-
name:
|
|
179
|
+
name: declarationNameIssue,
|
|
198
180
|
source: (o) => o.source === undefined || isMcpSourceLabel(o.source)
|
|
199
181
|
? undefined
|
|
200
182
|
: { path: "source", reason: `must be a non-empty string of at most ${MCP_SOURCE_LABEL_MAX} characters (a display label) — got ${describeValueShape(o.source)}` },
|
|
@@ -228,19 +210,7 @@ const ENTRY_KEY_JUDGES = {
|
|
|
228
210
|
return read.transport.kind === "http" ? transportUrlCredentialIssue(read.transport.url) : undefined;
|
|
229
211
|
},
|
|
230
212
|
};
|
|
231
|
-
const ENTRY_REFUSAL_TAIL = "The request is refused rather than mounting the server without it: a declaration that silently did not apply would leave the server running differently from what you declared. " +
|
|
232
|
-
"Fix the entry (an optional key may also be omitted).";
|
|
233
|
-
function entryFieldRefusal(entry, at, issue) {
|
|
234
|
-
const server = echoKeyName("", declaredName(entry));
|
|
235
|
-
return {
|
|
236
|
-
errorCode: "request.field_invalid",
|
|
237
|
-
message: `${at}.${issue.path} (server "${server}") is not a usable declaration — ${stripControlAndBidi(issue.reason)}. ${ENTRY_REFUSAL_TAIL}`,
|
|
238
|
-
...(issue.credential === undefined ? {} : { facts: urlCredentialFacts(`${at}.${issue.path}`, issue.credential) }),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
213
|
function requestEntryRefusal(entry, at) {
|
|
242
|
-
if (!isRecord(entry))
|
|
243
|
-
return listRefusal(`${at} is ${describeValueShape(entry)}, not an MCP server declaration (an object)`);
|
|
244
214
|
const unknownKeys = entryUnknownKeys(entry, at);
|
|
245
215
|
if (unknownKeys.length > 0) {
|
|
246
216
|
return {
|
|
@@ -254,12 +224,7 @@ function requestEntryRefusal(entry, at) {
|
|
|
254
224
|
const identity = requestEntryIdentityRefusal(entry, at);
|
|
255
225
|
if (identity !== undefined)
|
|
256
226
|
return identity;
|
|
257
|
-
|
|
258
|
-
const issue = ENTRY_KEY_JUDGES[key](entry);
|
|
259
|
-
if (issue !== undefined)
|
|
260
|
-
return entryFieldRefusal(entry, at, issue);
|
|
261
|
-
}
|
|
262
|
-
return undefined;
|
|
227
|
+
return judgeDeclarationKeys(MCP_SEAT, entry, at, MCP_SERVER_ENTRY_KEYS, ENTRY_KEY_JUDGES);
|
|
263
228
|
}
|
|
264
229
|
export function deploymentClosedMcpNames(baseline, reservedNames) {
|
|
265
230
|
return new Set([...(baseline ?? []).map((s) => s.name), ...reservedNames]);
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -507,7 +507,8 @@ export interface ToolApprovalFrame {
|
|
|
507
507
|
* `approval-content-kind.ts` 的 `readApprovalCard` 闭集窄读(集外 ⇒ 缺席 + 计一次 closed-word-out-of-set)。`question` = 问用户的问题(引擎保留的
|
|
508
508
|
* 问答工具),不是副作用工具。**本仓不从工具名回推卡**(工具名只作展示):此前按名字铸的 `livePending` 行 / 队列行 `contentKind` 随本键删。
|
|
509
509
|
* 停驻孪生 = 门的 `gate.approvalCard`(运维队列行顶层同名键,两只店读同一只 `parkedApprovalCard`)。display:本仓不按它裁决。
|
|
510
|
-
* 🔴 缺席即缺席(宿主自建的 ask / 老引擎),不编 `generic
|
|
510
|
+
* 🔴 缺席即缺席(宿主自建的 ask / 老引擎),不编 `generic`。7.136.0 起卡面同带(`ApprovalCardSchema.approvalCard`:发帧点把这一格交给铸卡 ⇒
|
|
511
|
+
* `card_json` 与断连重放帧的卡上是同一个词)。
|
|
511
512
|
*/
|
|
512
513
|
approvalCard?: ToolApprovalCard;
|
|
513
514
|
/**
|
|
@@ -1969,16 +1970,21 @@ export declare class ToolApprovalCoordinator {
|
|
|
1969
1970
|
status: number;
|
|
1970
1971
|
body: unknown;
|
|
1971
1972
|
}>;
|
|
1973
|
+
/** [ref]:这条登记**有没有问账行**(有 ⇒ 属主的唯一来源是行上当下的 owner 列;没有 ⇒ 登记记下的那一位是唯一可读的属主事实)。 */
|
|
1974
|
+
private registrationHasRow;
|
|
1972
1975
|
/**
|
|
1973
|
-
* [ref]
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
1976
|
+
* [ref]([ref] 的接管登记一臂并进来):**读行判属主**的应答 —— 接管登记恒走这里;活登记在登记记下的属主不认调用方时走这里(行可能已被收编改给调用方)。
|
|
1977
|
+
* 属主门读**行当下的** owner:登记上记下的那一格是铸卡(或接管)那一刻的进程内快照,`/v1/adoption` 改写行之后它就落后了(契约:应答口的授权判据取
|
|
1978
|
+
* 当下的持久事实,收编之后旧属主手里的卡作废、新属主照常答得了 —— 与耐久决断口同答)。行仍 `STREAM_PENDING` ⇒ {@link answerRegistration}(同一把
|
|
1979
|
+
* `decideAsk` CAS,绑门刚读到的那位属主,[ref];活登记上那条在等的腿照常收到决议);行已有终局(别处答了 / 对账器收了,本副本还没闻讯)⇒ 按行的真相答
|
|
1980
|
+
* (与晚答口同一张表)。读不出行 ⇒ 不知道属主是谁 ⇒ 与未知 id 同形同码(属主门 fail-closed)。
|
|
1976
1981
|
*/
|
|
1977
|
-
private
|
|
1982
|
+
private respondByRow;
|
|
1978
1983
|
/** 一条已过属主门的登记(活登记或接管登记)的应答 —— 两者同一条路:规则文本前门 → 要人门 → CAS → 兑付。
|
|
1979
1984
|
* `judgedOwner` = 门判过的那位属主([ref]:决议作用域的属主维,CAS 与确认读都绑它;见 `askRowCarriesOwner`)。 */
|
|
1980
1985
|
private answerRegistration;
|
|
1981
|
-
/** 一次 live 回决**落定之后**的两件兑付(部署名单那一问的豁免、「不再询问」的规则)—— 两件都只在 200 那一支、都读同一只资格。
|
|
1986
|
+
/** 一次 live 回决**落定之后**的两件兑付(部署名单那一问的豁免、「不再询问」的规则)—— 两件都只在 200 那一支、都读同一只资格。
|
|
1987
|
+
* `judgedOwner` = 这次决议的门判过的那位属主([ref]:一次决议只读一次属主 —— 决议 CAS、会话放行、规则落店、豁免记账都用这一份,不回头重读登记上的那一格)。 */
|
|
1982
1988
|
private afterLiveDecision;
|
|
1983
1989
|
/**
|
|
1984
1990
|
* **部署名单(`APPROVAL_REQUIRE`)那一问的「本会话放行」只落 server 的会话豁免店**(谁问的,谁的站着的答案算数):名单的问是 server 铸的,
|
|
@@ -2026,8 +2032,13 @@ export declare class ToolApprovalCoordinator {
|
|
|
2026
2032
|
status: number;
|
|
2027
2033
|
body: unknown;
|
|
2028
2034
|
}>;
|
|
2029
|
-
/**
|
|
2030
|
-
|
|
2035
|
+
/**
|
|
2036
|
+
* [ref]:有行的登记的按问读口 —— 行可见且仍 `STREAM_PENDING` ⇒ `pending`(列表面同一只行投影);否则按行的态答(与读行半场同一张表)。
|
|
2037
|
+
* 🔴 行**读不出**(店抛 / 超时)⇒ 只有**本协调器自己铸的活登记**回落:登记记下的属主认调用方 ⇒ `pending`,否则与未知 id 同答 —— 与同一刻的应答口同答
|
|
2038
|
+
* (活登记的应答拿登记属主当候选、不多读一次店,店抖动不挡真实回决,D5),也与 7.135.x 的读口同答。**接管登记**([ref],起服从问账接管、没有引擎腿在等)的
|
|
2039
|
+
* 属主跟着行走,登记上那一格不是权威 —— 应答口对它恒读行判、读不出 ⇒ 404,读口同样 404(两口同答;7.135.x 两口都 404)。读得出的「没有这一行」不回落(那是行的真相)。
|
|
2040
|
+
*/
|
|
2041
|
+
private readByRow;
|
|
2031
2042
|
/** {@link readApproval} 的读行半场。 */
|
|
2032
2043
|
private readAskRowState;
|
|
2033
2044
|
/**
|
|
@@ -2068,8 +2079,10 @@ export declare class ToolApprovalCoordinator {
|
|
|
2068
2079
|
/** {@link respondLate} 的读行:按 id 读出行,交 {@link dispatchRowTruth}。 */
|
|
2069
2080
|
private dispatchLateByRow;
|
|
2070
2081
|
/**
|
|
2071
|
-
* 按 wire 身份(= 行的键)
|
|
2072
|
-
*
|
|
2082
|
+
* 按 wire 身份(= 行的键)读行 —— 三态分开交回({@link AskRowRead}):读到了 / 读得出的「没有这一行」/ 读不出(店抛或超时,记账)。
|
|
2083
|
+
* 「读不出」与「不在」不折成一个值:调用点各自表态 —— 应答口与晚答口按「查无」答(读不出 = **不知道**这只 ask 落在哪一态,而受理臂都在对客户端做承诺,
|
|
2084
|
+
* 不知道 ⇒ 回那句 404,绝不把未知说成受理;D5 的 fail-open 只覆盖「不挡人的真实回决」那一面,这条腿方向相反),按问读口对本协调器自己铸的活登记回落那一份登记
|
|
2085
|
+
* (接管登记不回落,{@link readByRow})。
|
|
2073
2086
|
*/
|
|
2074
2087
|
private askRowFor;
|
|
2075
2088
|
/**
|