@sema-agent/server 7.135.0 → 7.137.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 +237 -2
- package/USAGE.md +146 -41
- package/deploy/sema-up/README.md +15 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +7 -0
- package/dist/approval-content-kind.js +8 -1
- package/dist/approval-gate-currency.d.ts +48 -6
- package/dist/approval-gate-currency.js +16 -9
- package/dist/approval-hmac.d.ts +13 -1
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.js +3 -3
- package/dist/auth-bridge.d.ts +34 -5
- package/dist/auth-bridge.js +22 -5
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/execution-env.d.ts +24 -1
- package/dist/boot/execution-env.js +69 -6
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +15 -1
- package/dist/boot/resolve-spec.d.ts +4 -0
- package/dist/boot/resolve-spec.js +24 -22
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-04-stores.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.d.ts +4 -2
- package/dist/boot/stage-05-execution-env.js +11 -2
- package/dist/boot/stage-06-runners.d.ts +4 -2
- package/dist/boot/stage-06-runners.js +5 -1
- package/dist/boot/stage-07-capability-layer.d.ts +5 -2
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +5 -2
- package/dist/boot/stage-08-reapers.js +2 -1
- package/dist/boot/stage-09-leader.d.ts +5 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -2
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.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/broker/broker.d.ts +89 -0
- package/dist/broker/broker.js +287 -0
- package/dist/broker/constants.d.ts +39 -0
- package/dist/broker/constants.js +15 -0
- package/dist/broker/http1.d.ts +111 -0
- package/dist/broker/http1.js +256 -0
- package/dist/broker/lease-book.d.ts +87 -0
- package/dist/broker/lease-book.js +142 -0
- package/dist/broker/lease-file.d.ts +60 -0
- package/dist/broker/lease-file.js +127 -0
- package/dist/broker/main.d.ts +2 -0
- package/dist/broker/main.js +20 -0
- package/dist/budget.d.ts +3 -3
- package/dist/budget.js +4 -0
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/engine-capabilities.js +1 -0
- package/dist/capabilities/mcp-probe.d.ts +5 -4
- package/dist/capabilities/scenarios.d.ts +23 -8
- package/dist/capabilities/scenarios.js +13 -8
- package/dist/capabilities/skills.d.ts +28 -5
- package/dist/capabilities/skills.js +8 -0
- package/dist/capability-facts.d.ts +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential-renewer.js +5 -1
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
- package/dist/checkpoint-ctx-seal-m28.js +1 -1
- package/dist/checkpoint-identity.d.ts +3 -3
- package/dist/config-catalog.js +21 -16
- 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 +21 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.js +60 -16
- package/dist/deployment-governance.d.ts +2 -2
- package/dist/deployment-governance.js +7 -9
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +8 -4
- package/dist/file-history-disclosure.js +24 -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/classic-stations.d.ts +11 -54
- 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/admission.js +10 -7
- package/dist/http/approval-sweeps.js +4 -3
- package/dist/http/caller-trust.d.ts +2 -1
- package/dist/http/caller-trust.js +1 -3
- package/dist/http/checkpoint-ctx.d.ts +76 -16
- package/dist/http/checkpoint-ctx.js +45 -1
- package/dist/http/dispatch.js +24 -19
- package/dist/http/durable-run-leg.d.ts +5 -1
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/health-self-report.d.ts +2 -2
- package/dist/http/mcp-remote-text.d.ts +5 -0
- package/dist/http/mcp-remote-text.js +7 -0
- package/dist/http/principal-gate.d.ts +15 -10
- package/dist/http/principal-gate.js +48 -9
- package/dist/http/resume-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- package/dist/http/resume-legs.d.ts +2 -20
- package/dist/http/resume-legs.js +52 -38
- package/dist/http/route-ctx.d.ts +8 -2
- package/dist/http/routes/approvals-assistant.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +65 -16
- package/dist/http/routes/capabilities.js +11 -1
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +5 -7
- package/dist/http/routes/runs.js +4 -3
- package/dist/http/routes/session-sync.js +70 -12
- package/dist/http/routes/sessions.js +20 -15
- package/dist/http/routes/tasks.js +8 -6
- package/dist/http/server.d.ts +4 -0
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +6 -1
- package/dist/http/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +57 -30
- package/dist/identity-source.d.ts +65 -0
- package/dist/identity-source.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/memory-store-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.d.ts +2 -0
- package/dist/model-entry-refusal.js +11 -0
- package/dist/model-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -1
- package/dist/model-select.d.ts +6 -17
- package/dist/model-select.js +0 -3
- package/dist/namespaced-rule-name.d.ts +1 -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 +19 -7
- package/dist/observability/fail-open.js +19 -7
- package/dist/observability/metrics.js +2 -2
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/operator-ask.d.ts +2 -2
- package/dist/org-delivery-gate.js +1 -1
- package/dist/org-memory-admission.d.ts +2 -2
- package/dist/org-memory-admission.js +39 -2
- package/dist/parked-decide.d.ts +4 -3
- package/dist/parked-decide.js +29 -9
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +7 -5
- package/dist/permission-rule-list.d.ts +2 -2
- 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 +46 -12
- package/dist/plugins/checkpoint-store-sql.js +64 -35
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +37 -37
- package/dist/plugins/file-run-store.js +8 -2
- package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
- package/dist/plugins/k8s-exec-protocol.js +116 -28
- package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
- package/dist/plugins/k8s-pod-delete-failure.js +17 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
- package/dist/plugins/k8s-pod-reaper.js +122 -0
- package/dist/plugins/local-checkpoint-store.d.ts +3 -6
- package/dist/plugins/local-checkpoint-store.js +7 -6
- package/dist/plugins/memory-run-store.js +2 -0
- package/dist/plugins/one-time-migrations.d.ts +49 -37
- package/dist/plugins/one-time-migrations.js +87 -128
- package/dist/plugins/permission-rule-store-file.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
- package/dist/plugins/permission-rule-store-sql.js +16 -17
- package/dist/plugins/pg-pool.js +4 -0
- package/dist/plugins/pg-session-storage.js +2 -2
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +4 -0
- package/dist/plugins/remote-env-e2b.d.ts +3 -0
- package/dist/plugins/remote-env-e2b.js +1 -0
- package/dist/plugins/remote-env-file-error.d.ts +8 -1
- package/dist/plugins/remote-env-file-error.js +7 -1
- package/dist/plugins/remote-env-host.d.ts +7 -1
- package/dist/plugins/remote-env-host.js +7 -1
- package/dist/plugins/remote-env-k8s.d.ts +154 -14
- package/dist/plugins/remote-env-k8s.js +300 -21
- package/dist/plugins/remote-env-local-docker.d.ts +3 -0
- package/dist/plugins/remote-env-local-docker.js +1 -0
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/run-store-sql.js +10 -4
- package/dist/plugins/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- package/dist/plugins/session-ownership.d.ts +17 -3
- package/dist/plugins/session-ownership.js +19 -5
- 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 +60 -0
- package/dist/plugins/sql-errors.js +64 -1
- package/dist/plugins/store-contracts.d.ts +10 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +10 -0
- package/dist/plugins/tidb-pool.js +8 -1
- package/dist/plugins/tidb-session-store.js +2 -2
- 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 +5 -2
- package/dist/request-value-closure.d.ts +31 -2
- package/dist/request-value-closure.js +7 -1
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +2 -2
- package/dist/rules-consent.js +7 -4
- package/dist/run-local.d.ts +2 -2
- package/dist/runs.js +2 -1
- package/dist/runtime-caps-resolver.js +1 -1
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sandbox-git-lease.d.ts +49 -0
- package/dist/sandbox-git-lease.js +60 -0
- package/dist/sandbox-git-plane.d.ts +84 -0
- package/dist/sandbox-git-plane.js +105 -0
- 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/security.d.ts +16 -4
- package/dist/security.js +8 -3
- package/dist/server-secret-env.d.ts +6 -0
- package/dist/server-secret-env.js +14 -0
- package/dist/session-error-wire.d.ts +14 -6
- package/dist/session-error-wire.js +2 -1
- package/dist/session-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-policy-wire.js +2 -2
- 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/spec-fields.d.ts +43 -13
- package/dist/spec-fields.js +33 -12
- package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
- package/dist/stored-request-prompt-profile-m30.js +25 -0
- package/dist/task-a2a.d.ts +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +7 -15
- package/dist/task-mcp.js +16 -51
- package/dist/task-settings.d.ts +2 -2
- package/dist/tool-approval.d.ts +35 -12
- package/dist/tool-approval.js +59 -39
- package/dist/tool-rule-names.d.ts +5 -0
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.js +37 -13
- package/dist/trace/ledger-sink.d.ts +4 -5
- package/dist/trace/ledger-sink.js +3 -7
- package/dist/trace/project.d.ts +37 -3
- package/dist/trace/project.js +20 -6
- package/dist/trace/redact.d.ts +40 -12
- package/dist/trace/redact.js +332 -128
- package/dist/trace/self-minted-wire.d.ts +641 -0
- package/dist/trace/self-minted-wire.js +201 -0
- package/dist/trace/wire-projection-faces.d.ts +5 -3
- package/dist/trace/wire-projection-faces.js +7 -3
- 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/web-admin-client/apply-effective.js +0 -1
- package/dist/web-admin-client/center-request.d.ts +20 -2
- package/dist/web-admin-client/center-request.js +40 -5
- package/dist/web-admin-client/http-client.js +6 -6
- package/dist/web-admin-client/types.d.ts +5 -5
- package/dist/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
- package/package.json +4 -3
- 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/wire-changes/server@7.137.0.json +825 -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
|
*
|
|
@@ -198,7 +190,7 @@ export declare function assertRequestMcpEntries(bodyMcp: unknown): void;
|
|
|
198
190
|
* A caller can ADD servers but can never SHADOW or REFILL a deployment-declared one: a center entry that was skipped
|
|
199
191
|
* (disabled / missing env ref / malformed `toolFaces`) still occupies its name, otherwise "skipped" would silently
|
|
200
192
|
* mean "free for the caller to remount without the operator's `allowTools` / `toolFaces`" (车CF codex r2 [high]).
|
|
201
|
-
* Same posture as `
|
|
193
|
+
* Same posture as `buildRunSkills`.
|
|
202
194
|
*/
|
|
203
195
|
export declare function deploymentClosedMcpNames(baseline: McpServerSpec[] | undefined, reservedNames: readonly string[]): ReadonlySet<string>;
|
|
204
196
|
/**
|
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/task-settings.d.ts
CHANGED
|
@@ -73,7 +73,7 @@ export declare const PERMISSION_MODE_WORDS: string;
|
|
|
73
73
|
* through verbatim (`dontAsk` included: folding the auto-deny mode to `default`, the asking mode, would turn a refusal
|
|
74
74
|
* into a card). An UNKNOWN string coerces to `default` (fail-safe: the most-asking mode).
|
|
75
75
|
* [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-member value
|
|
76
|
-
* (admission.ts, sibling of
|
|
76
|
+
* (admission.ts, sibling of toolMaterializeStrategy) and the wire type is a closed enum. This lenient fold stays for the one
|
|
77
77
|
* population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
|
|
78
78
|
* two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
|
|
79
79
|
*/
|
|
@@ -477,7 +477,7 @@ export declare const REQUEST_SETTINGS_REFUSAL_PREFIX = "request settings cannot
|
|
|
477
477
|
/**
|
|
478
478
|
* Build the per-request permission ToolPolicy from the parsed settings: the two **rule lists** (`deny` / `ask`) through ONE compiler
|
|
479
479
|
* ({@link createRuleListPolicy} → core `createPermissionRulePolicy`) — a `deny` rule hard-blocks, an `ask` rule forces an approval
|
|
480
|
-
* gate, `deny` > `ask` on overlap. 两表的 **covering 语义同源**([ref]):底下是 core 的同一只 `
|
|
480
|
+
* gate, `deny` > `ask` on overlap. 两表的 **covering 语义同源**([ref]):底下是 core 的同一只 `ruleNameCovers`,本仓不持第二套 glob。
|
|
481
481
|
* The `allow` bucket is not here: a policy says deny / ask only (core 7.42.0); the list rides into `TaskSpec.allowedTools` at
|
|
482
482
|
* {@link applyTaskSettings}, the one write point.
|
|
483
483
|
*
|