@sema-agent/server 7.103.2 → 7.105.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 +378 -2
- package/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/USAGE.md +320 -16
- package/deploy/sema-up/README.md +57 -7
- package/deploy/sema-up/cluster-up.sh +18 -4
- package/deploy/sema-up/kube-up.sh +16 -0
- package/deploy/sema-up/lib/dataroot.sh +142 -0
- package/deploy/sema-up/lib/flagshape.sh +69 -0
- package/deploy/sema-up/lib/lanes.py +14 -4
- package/deploy/sema-up/lib/shell-access.sh +41 -0
- package/deploy/sema-up/preflight.sh +14 -1
- package/deploy/sema-up/sema-up.sh +134 -37
- package/deploy/sema-up/smoke.sh +5 -0
- package/dist/auto-mode-face.js +3 -4
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/config-center.d.ts +6 -2
- package/dist/boot/config-center.js +5 -59
- package/dist/boot/execution-env.d.ts +11 -0
- package/dist/boot/execution-env.js +14 -0
- package/dist/boot/execution-lane.d.ts +25 -0
- package/dist/boot/execution-lane.js +95 -0
- package/dist/boot/memory-consolidation.d.ts +185 -9
- package/dist/boot/memory-consolidation.js +192 -32
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +28 -24
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +6 -1
- package/dist/boot/session-faces.d.ts +5 -0
- package/dist/boot/session-faces.js +18 -2
- package/dist/boot/session-incarnation-seat.d.ts +41 -0
- package/dist/boot/session-incarnation-seat.js +69 -0
- package/dist/boot/stage-01-config.js +2 -2
- package/dist/boot/stage-02-registry-pull.js +3 -1
- package/dist/boot/stage-04-stores.d.ts +2 -0
- package/dist/boot/stage-04-stores.js +3 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -0
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +4 -1
- package/dist/boot/stage-06-runners.js +7 -14
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-09-leader.d.ts +6 -1
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +6 -1
- package/dist/boot/stage-10-http-server.js +4 -1
- package/dist/brain.d.ts +1 -1
- package/dist/config-catalog.d.ts +16 -1
- package/dist/config-catalog.js +9 -9
- package/dist/config-center/restart-signal.d.ts +4 -4
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +37 -6
- package/dist/config-provider.d.ts +42 -10
- package/dist/config-provider.js +18 -16
- package/dist/config-types.d.ts +37 -16
- package/dist/config-types.js +1 -0
- package/dist/config.d.ts +28 -30
- package/dist/config.js +95 -77
- package/dist/env-facts.d.ts +13 -3
- package/dist/env-facts.js +15 -3
- package/dist/execution-lane-caps.d.ts +15 -0
- package/dist/execution-lane-caps.js +8 -0
- package/dist/file-history-capture.d.ts +42 -35
- package/dist/file-history-capture.js +15 -10
- package/dist/file-history-disclosure.d.ts +7 -31
- package/dist/file-history-disclosure.js +12 -0
- package/dist/fleet/fleet-bus.d.ts +5 -1
- package/dist/http/admission.d.ts +12 -5
- package/dist/http/admission.js +5 -1
- package/dist/http/dispatch.d.ts +18 -0
- package/dist/http/dispatch.js +19 -6
- package/dist/http/principal-gate.d.ts +22 -0
- package/dist/http/principal-gate.js +20 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +9 -5
- package/dist/http/route-table.d.ts +15 -4
- package/dist/http/route-table.js +6 -5
- package/dist/http/routes/agents-roster.d.ts +7 -0
- package/dist/http/routes/agents-roster.js +6 -2
- package/dist/http/routes/approvals-assistant.d.ts +48 -19
- package/dist/http/routes/approvals-assistant.js +117 -50
- package/dist/http/routes/capabilities.js +3 -2
- package/dist/http/routes/config-catalog.js +1 -0
- package/dist/http/routes/fleet.d.ts +3 -2
- package/dist/http/routes/images.js +0 -1
- package/dist/http/routes/memory-consolidation.d.ts +3 -2
- package/dist/http/routes/memory-policy.js +3 -3
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +29 -10
- package/dist/http/routes/session-sync.js +14 -15
- package/dist/http/routes/sessions-list.js +3 -2
- package/dist/http/routes/sessions.js +49 -25
- package/dist/http/routes/shared-memory.js +2 -2
- package/dist/http/routes/side-query.js +8 -1
- package/dist/http/routes/tasks.js +22 -19
- package/dist/http/routes/trace-usage.d.ts +2 -0
- package/dist/http/routes/trace-usage.js +8 -13
- package/dist/http/send.d.ts +12 -0
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +30 -8
- package/dist/http/server.js +11 -5
- package/dist/http/sse-lifecycle.d.ts +1 -1
- package/dist/http/sse-log.d.ts +12 -9
- package/dist/http/sse-log.js +1 -8
- package/dist/http/sync-submit-body.d.ts +43 -0
- package/dist/http/sync-submit-body.js +4 -0
- package/dist/http/wire-types.d.ts +23 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/leader/merge.d.ts +8 -3
- package/dist/memory-incident-sink.js +2 -1
- package/dist/model-select.d.ts +9 -0
- package/dist/model-select.js +5 -1
- package/dist/observability/corrupt-read-seat.d.ts +151 -16
- package/dist/observability/corrupt-read-seat.js +158 -15
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.d.ts +60 -10
- package/dist/observability/run-terminal.js +119 -9
- package/dist/parked-decide.d.ts +8 -3
- package/dist/parked-decide.js +56 -20
- package/dist/parked-revive-surface.d.ts +14 -7
- package/dist/parked-revive-surface.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -1
- package/dist/plugins/approval-ask-store-file.js +9 -12
- package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
- package/dist/plugins/approval-ask-store-sql.js +4 -1
- package/dist/plugins/approval-exemption-store.d.ts +21 -1
- package/dist/plugins/approval-exemption-store.js +36 -7
- package/dist/plugins/approval-nonce-store.d.ts +16 -2
- package/dist/plugins/approval-nonce-store.js +17 -7
- package/dist/plugins/caching-session-store.d.ts +5 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
- package/dist/plugins/file-resume-anchor-store.js +19 -5
- package/dist/plugins/file-run-store.d.ts +21 -8
- package/dist/plugins/file-run-store.js +60 -38
- package/dist/plugins/image-bake-store-sql.d.ts +0 -2
- package/dist/plugins/image-bake-store-sql.js +0 -5
- package/dist/plugins/local-session-store.d.ts +22 -0
- package/dist/plugins/local-session-store.js +74 -11
- package/dist/plugins/memory-run-store.d.ts +7 -5
- package/dist/plugins/memory-run-store.js +11 -4
- package/dist/plugins/one-time-migrations.d.ts +38 -1
- package/dist/plugins/one-time-migrations.js +143 -15
- package/dist/plugins/permission-rule-store-file.d.ts +9 -2
- package/dist/plugins/permission-rule-store-file.js +37 -51
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +19 -10
- package/dist/plugins/retention-store-sql.js +1 -0
- package/dist/plugins/run-store-sql.d.ts +15 -6
- package/dist/plugins/run-store-sql.js +15 -13
- package/dist/plugins/schema-indexes.js +1 -0
- package/dist/plugins/send-file-ledger.d.ts +5 -1
- package/dist/plugins/send-file-ledger.js +11 -6
- package/dist/plugins/session-event-payload.d.ts +14 -4
- package/dist/plugins/session-event-payload.js +5 -2
- package/dist/plugins/session-policy-store-sql.js +18 -5
- package/dist/plugins/store-backend.d.ts +13 -7
- package/dist/plugins/store-backend.js +10 -6
- package/dist/plugins/store-contracts.d.ts +7 -2
- package/dist/plugins/tidb-pool.js +12 -5
- package/dist/plugins/tidb-session-store.d.ts +6 -1
- package/dist/plugins/tidb-session-store.js +17 -10
- package/dist/project-identity.d.ts +4 -6
- package/dist/read-face-posture.d.ts +21 -0
- package/dist/read-face-posture.js +9 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +72 -3
- package/dist/request-value-closure.js +12 -4
- package/dist/roster-bound-http-status.d.ts +6 -0
- package/dist/roster-bound-http-status.js +6 -0
- package/dist/rules-consent.js +0 -11
- package/dist/run-local.js +22 -27
- package/dist/runs.d.ts +22 -2
- package/dist/runs.js +29 -7
- package/dist/sealed-key.d.ts +31 -4
- package/dist/sealed-key.js +9 -0
- package/dist/security.d.ts +5 -1
- package/dist/session-children-retire.d.ts +92 -0
- package/dist/session-children-retire.js +85 -0
- package/dist/session-error-wire.d.ts +58 -0
- package/dist/session-error-wire.js +38 -0
- package/dist/session-incarnation.d.ts +61 -0
- package/dist/session-incarnation.js +43 -0
- package/dist/session-policy-wire.d.ts +98 -12
- package/dist/session-policy-wire.js +71 -8
- package/dist/session-records-skipped-notice.d.ts +20 -0
- package/dist/session-records-skipped-notice.js +27 -0
- package/dist/tool-approval.d.ts +65 -0
- package/dist/tool-approval.js +36 -9
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +3 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/project.d.ts +11 -6
- package/dist/trace/project.js +12 -3
- package/dist/trace/redact.d.ts +7 -0
- package/dist/trace/redact.js +153 -61
- package/dist/trace/wire-projection-faces.d.ts +10 -3
- package/dist/trace/wire-projection-faces.js +8 -2
- package/package.json +2 -2
- package/dist/plugins/memory-session-policy-store.d.ts +0 -46
- package/dist/plugins/memory-session-policy-store.js +0 -52
package/dist/http/resume-legs.js
CHANGED
|
@@ -2,6 +2,9 @@ import { redactErrorMessage, redactHead } from "../observability/run-terminal.js
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
4
4
|
import { decideParkedAgent, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
5
|
+
const sessionFaceUnavailable = async (sessionId) => {
|
|
6
|
+
throw new Error(`parked decide cannot verify which occupant session ${sessionId} belongs to: this deployment's session store has no sessionFaceOf read (every built-in store has one) — refusing rather than resuming unverified`);
|
|
7
|
+
};
|
|
5
8
|
import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
6
9
|
import { evictIfConflict, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
7
10
|
import { failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane } from "../terminal.js";
|
|
@@ -36,6 +39,7 @@ import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflic
|
|
|
36
39
|
import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js";
|
|
37
40
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
38
41
|
import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
42
|
+
import { httpErrorCode } from "./send.js";
|
|
39
43
|
function isCheckpointReopenedFailure(r) {
|
|
40
44
|
const code = failureCodeOf(r.terminal);
|
|
41
45
|
return code === "resume.env_failed" || code === "resume.tool_unavailable" || code === "resume.tool_contract_mismatch";
|
|
@@ -258,7 +262,7 @@ export function createResumeLegs(ctx) {
|
|
|
258
262
|
checkpointStore: cs,
|
|
259
263
|
registry: defaultTaskRegistry,
|
|
260
264
|
reviveSurface: parkedSurface,
|
|
261
|
-
|
|
265
|
+
sessionFaceOf: deps.sessionStorage?.sessionFaceOf ? deps.sessionStorage.sessionFaceOf.bind(deps.sessionStorage) : sessionFaceUnavailable,
|
|
262
266
|
...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
|
|
263
267
|
...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
|
|
264
268
|
...(deps.logger ? { warn: parkWarn } : {}),
|
|
@@ -341,7 +345,7 @@ export function createResumeLegs(ctx) {
|
|
|
341
345
|
ok: false,
|
|
342
346
|
answer: {
|
|
343
347
|
status: 409,
|
|
344
|
-
body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy",
|
|
348
|
+
body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", blockedBy: httpErrorCode(e.status, e.code), ...(e.extra ?? {}) },
|
|
345
349
|
},
|
|
346
350
|
};
|
|
347
351
|
}
|
|
@@ -908,7 +912,7 @@ export function createResumeLegs(ctx) {
|
|
|
908
912
|
settleFleet("failed");
|
|
909
913
|
}
|
|
910
914
|
else if (retriable) {
|
|
911
|
-
await (outcome.gate === "plan_review" ? deps.runStore.setNeedsReview(taskId) : deps.runStore.setSuspended(taskId)).catch(() => undefined);
|
|
915
|
+
await (outcome.gate === "plan_review" ? deps.runStore.setNeedsReview(taskId, null) : deps.runStore.setSuspended(taskId, null)).catch(() => undefined);
|
|
912
916
|
settleFleet(outcome.gate === "plan_review" ? "needs_review" : "suspended");
|
|
913
917
|
}
|
|
914
918
|
else {
|
|
@@ -973,9 +977,9 @@ export function createResumeLegs(ctx) {
|
|
|
973
977
|
const resumePlane = outwardPlane(sealed.terminal);
|
|
974
978
|
if (taskId && deps.runStore) {
|
|
975
979
|
if (resumePlane.status === "suspended" || checkpointReopened)
|
|
976
|
-
await deps.runStore.setSuspended(taskId);
|
|
980
|
+
await deps.runStore.setSuspended(taskId, sealed.terminal.kind === "paused" ? sealed : null);
|
|
977
981
|
else if (resumePlane.status === "needs_review")
|
|
978
|
-
await deps.runStore.setNeedsReview(taskId);
|
|
982
|
+
await deps.runStore.setNeedsReview(taskId, sealed);
|
|
979
983
|
else
|
|
980
984
|
await deps.runStore.setTerminal(taskId, resumePlane.status, sealed, resumePlane.errorMessage ?? null);
|
|
981
985
|
}
|
|
@@ -55,7 +55,17 @@ import type { FlatServiceDeps } from "./server.js";
|
|
|
55
55
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
56
56
|
import type { RouteDecl, RouteDispatchDecl, RouteMatch, RouteCtx, RouteMethod } from "./route-ctx.js";
|
|
57
57
|
/** 一只域 handler 相对**全局服务凭据门**的位置。门前 = 未鉴权可达(每一只都要单独论证),门后 = 先过凭据。 */
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* 一行路由在全局服务凭据门的哪一侧、吃哪道门(闭集,三员;S-672 起由两员拆成三员):
|
|
60
|
+
* · `"read-credential"` —— 门**之前**,但过**一只**读路谓词(`dispatch.ts` 的 `readCredentialOk`:部署配了 service
|
|
61
|
+
* 凭据时要「服务凭据(含只读 TRACE_TOKEN)∨ 已验 principal」,没配时与全局门同样放行)。只读路才能坐这一段:
|
|
62
|
+
* 它们在门前只为收只读 TRACE_TOKEN 单凭据的调用方,不是为了对端口全开;
|
|
63
|
+
* · `"public"` —— 门之前、**不吃**任何凭据门:按裁定匿名可达的面(今天只有 A2A agent card,R-11「公网匿名可发现」,
|
|
64
|
+
* 零存量披露);
|
|
65
|
+
* · `"post-credential"` —— 全局服务凭据门(+ 无凭据写门 / 排空 503 / roster-pending 503 / 破坏性会话写门)之后。
|
|
66
|
+
* 修前是 `"pre-credential" | "post-credential"` 两员:门前四族一律不吃任何门,三族只读路因此对端口全开(T-72)。
|
|
67
|
+
*/
|
|
68
|
+
export type RoutePosition = "read-credential" | "public" | "post-credential";
|
|
59
69
|
export interface RouteRow {
|
|
60
70
|
/** 域入口(`routes/*.ts` 的 `handleX`)。签名一致性是编译期钉:入口漂了形 = 编译红,不是运行时静默 404。
|
|
61
71
|
*
|
|
@@ -79,9 +89,10 @@ export interface RouteRow {
|
|
|
79
89
|
readonly enabled?: (deps: FlatServiceDeps) => boolean;
|
|
80
90
|
}
|
|
81
91
|
export declare const ROUTES: readonly RouteRow[];
|
|
82
|
-
/**
|
|
83
|
-
*
|
|
84
|
-
export declare const
|
|
92
|
+
/** 三段(`dispatch.ts` 的分派循环按 read-credential → public → 全局门 → post-credential 各跑一段)。段内序 = 本表行序;
|
|
93
|
+
* 「三段各自连续、按这个次序排」由 `test/route-order-freeze.test.ts` 钉住。 */
|
|
94
|
+
export declare const READ_CREDENTIAL_ROUTES: readonly RouteRow[];
|
|
95
|
+
export declare const PUBLIC_ROUTES: readonly RouteRow[];
|
|
85
96
|
export declare const POST_CREDENTIAL_ROUTES: readonly RouteRow[];
|
|
86
97
|
/**
|
|
87
98
|
* **探针路径**(k8s / 编排器 / 抓取器的常态高频采样):不计 admission inflight、不推活性时钟(`http/server.ts`
|
package/dist/http/route-table.js
CHANGED
|
@@ -38,10 +38,10 @@ function row(spec) {
|
|
|
38
38
|
return spec;
|
|
39
39
|
}
|
|
40
40
|
export const ROUTES = [
|
|
41
|
-
row({ handler: handleTraceUsage, decls: TRACE_USAGE_ROUTES, position: "
|
|
42
|
-
row({ handler: handleWorkflows, decls: WORKFLOWS_ROUTES, position: "
|
|
43
|
-
row({ handler: handleFleet, decls: FLEET_ROUTES, position: "
|
|
44
|
-
row({ handler: handleA2aServe, decls: A2A_SERVE_ROUTES, position: "
|
|
41
|
+
row({ handler: handleTraceUsage, decls: TRACE_USAGE_ROUTES, position: "read-credential", label: "trace-usage" }),
|
|
42
|
+
row({ handler: handleWorkflows, decls: WORKFLOWS_ROUTES, position: "read-credential", label: "workflows" }),
|
|
43
|
+
row({ handler: handleFleet, decls: FLEET_ROUTES, position: "read-credential", label: "fleet" }),
|
|
44
|
+
row({ handler: handleA2aServe, decls: A2A_SERVE_ROUTES, position: "public", label: "a2a-serve" }),
|
|
45
45
|
row({ handler: handleCapabilities, decls: CAPABILITIES_ROUTES, position: "post-credential", label: "capabilities" }),
|
|
46
46
|
row({ handler: handleSideQuery, decls: SIDE_QUERY_ROUTES, position: "post-credential", label: "side-query" }),
|
|
47
47
|
row({ handler: handleTasks, decls: TASKS_ROUTES, position: "post-credential", label: "tasks" }),
|
|
@@ -78,7 +78,8 @@ export const ROUTES = [
|
|
|
78
78
|
row({ handler: handleAttachments, decls: ATTACHMENTS_ROUTES, position: "post-credential", label: "attachments" }),
|
|
79
79
|
row({ handler: handleNotifyWake, decls: NOTIFY_WAKE_ROUTES, position: "post-credential", label: "notify-wake" }),
|
|
80
80
|
];
|
|
81
|
-
export const
|
|
81
|
+
export const READ_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "read-credential");
|
|
82
|
+
export const PUBLIC_ROUTES = ROUTES.filter((r) => r.position === "public");
|
|
82
83
|
export const POST_CREDENTIAL_ROUTES = ROUTES.filter((r) => r.position === "post-credential");
|
|
83
84
|
const PIPELINE_ROUTES = [
|
|
84
85
|
{ id: "health", path: "/health", methods: ["GET"] },
|
|
@@ -25,10 +25,17 @@
|
|
|
25
25
|
* · **能力位同源**:`capabilities.agentRoster` 与这里的 501 谓词是同一个 `deps.backgroundAgentStore`。
|
|
26
26
|
*/
|
|
27
27
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
28
|
+
import type { BackgroundAgentRowSummary } from "@sema-agent/core";
|
|
28
29
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
29
30
|
/** 名册窗的扫描上限(与 fleet 的终态窗同姿势:读面要有界)。窗满时回体带 `truncated:true`,
|
|
30
31
|
* `total` 于是诚实地是「窗内行数」而不是租户全量 —— 一个悄悄封顶的 total 比没有 total 更坏。 */
|
|
31
32
|
export declare const ROSTER_SCAN_LIMIT = 500;
|
|
33
|
+
/**
|
|
34
|
+
* 后台子代在 wire 上的 **id 投影**(唯一一只):名册行的 `handle` 与 `DELETE /v1/sessions/:id` 的 409
|
|
35
|
+
* `conflict.session_children_alive` 体里 `remaining[]` 的每一项都经它出门(S-790,裁定 2026-09-28)—— 两个面上
|
|
36
|
+
* 「同一只子代」必须是同一个串,消费方拿删除答复里的 id 才对得上名册里的行、才能去它运行的地方停它。
|
|
37
|
+
*/
|
|
38
|
+
export declare function rosterAgentId(row: Pick<BackgroundAgentRowSummary, "handle">): string;
|
|
32
39
|
export declare function handleAgentRoster(req: IncomingMessage, res: ServerResponse, match: RouteMatch<AgentRosterRouteId>, ctx: RouteCtx): Promise<void>;
|
|
33
40
|
export declare const AGENT_ROSTER_ROUTES: readonly [{
|
|
34
41
|
readonly id: "agents-roster";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sendJson, sendError } from "../send.js";
|
|
2
|
+
import { sendUnauthorized } from "../principal-gate.js";
|
|
2
3
|
import { assertNever } from "../route-ctx.js";
|
|
3
4
|
export const ROSTER_SCAN_LIMIT = 500;
|
|
4
5
|
const ROSTER_STATUSES = ["running", "parked", "completed", "failed", "killed"];
|
|
@@ -18,9 +19,12 @@ const ROSTER_KEY_DISPOSITION = {
|
|
|
18
19
|
usage: "strip",
|
|
19
20
|
};
|
|
20
21
|
void ROSTER_KEY_DISPOSITION;
|
|
22
|
+
export function rosterAgentId(row) {
|
|
23
|
+
return row.handle;
|
|
24
|
+
}
|
|
21
25
|
function projectRow(r) {
|
|
22
26
|
return {
|
|
23
|
-
handle: r
|
|
27
|
+
handle: rosterAgentId(r),
|
|
24
28
|
...(r.name !== undefined ? { name: r.name } : {}),
|
|
25
29
|
...(r.agentType !== undefined ? { agentType: r.agentType } : {}),
|
|
26
30
|
status: r.status,
|
|
@@ -56,7 +60,7 @@ export async function handleAgentRoster(req, res, match, ctx) {
|
|
|
56
60
|
case "agents-roster": {
|
|
57
61
|
const { fleetWide, gateOwner } = sessionOwnerScope(req);
|
|
58
62
|
if (!fleetWide && gateOwner === null) {
|
|
59
|
-
|
|
63
|
+
sendUnauthorized(res, "identity");
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
62
66
|
const store = deps.backgroundAgentStore;
|
|
@@ -3,31 +3,59 @@ import type { QuestionAnswer } from "@sema-agent/core";
|
|
|
3
3
|
import type { CheckpointStoreFull } from "../../plugins/store-backend.js";
|
|
4
4
|
import { type ToolApprovalCoordinator } from "../../tool-approval.js";
|
|
5
5
|
import { type GovernancePosture } from "../../runtime-governance.js";
|
|
6
|
-
import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
6
|
+
import { type RouteCtx, type RouteDecl, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
7
7
|
import { type KeyClosureIssue } from "../../request-key-closure.js";
|
|
8
|
+
/** 族的成员 = 路由表行 id(改名 / 删行 ⇒ 下表的键当场编译红)。 */
|
|
9
|
+
export type DecisionDoorId = Extract<ApprovalsAssistantRouteId, "approval-decide" | "assistant-task-plan-review">;
|
|
10
|
+
/** 一道门的静态描述 —— 只写**这道门自己**的东西;路径、路径段、别门的键一概不在这里写(从来源读)。 */
|
|
11
|
+
export interface DecisionDoor {
|
|
12
|
+
/** 体的受理集 = 本门的 `*_BODY_DECLARED_KEYS`(那张表的双向编译期门照旧咬着它的类型)。 */
|
|
13
|
+
readonly accepted: readonly string[];
|
|
14
|
+
/** `unknown` 桶标签里的门名(「not a <这里> key — check the spelling」)。 */
|
|
15
|
+
readonly requestNoun: string;
|
|
16
|
+
/** 拒体引子里「这道门上拼错一个键最贵的后果」那半句(人读;每门一句)。 */
|
|
17
|
+
readonly costliestTypo: string;
|
|
18
|
+
}
|
|
19
|
+
export type DecisionDoorFamily = {
|
|
20
|
+
readonly [K in DecisionDoorId]: DecisionDoor;
|
|
21
|
+
};
|
|
22
|
+
/** 「名字是真的、只是不在这个体上」的三族(闭集;加第四族 = 下面 `switch` 编译红)。 */
|
|
23
|
+
type NeighborFamily = "sibling_field" | "path_segment" | "submit_body";
|
|
24
|
+
/** 一个邻键:它属哪一族 + 它在哪儿是真的(wire 路径,可多条)。 */
|
|
25
|
+
interface NeighborKey {
|
|
26
|
+
readonly family: NeighborFamily;
|
|
27
|
+
readonly where: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/** 派生结果里一道门的全部判据(受理集 + 邻键表 + 两句人读)。 */
|
|
30
|
+
export interface DerivedDecisionDoor {
|
|
31
|
+
readonly accepted: ReadonlySet<string>;
|
|
32
|
+
readonly neighbors: ReadonlyMap<string, NeighborKey>;
|
|
33
|
+
readonly requestNoun: string;
|
|
34
|
+
readonly costliestTypo: string;
|
|
35
|
+
}
|
|
36
|
+
export type DecisionDoorKeyTable = {
|
|
37
|
+
readonly [K in DecisionDoorId]: DerivedDecisionDoor;
|
|
38
|
+
};
|
|
8
39
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 🔴 **重放不额外过门**,也不需要:本腿的幂等回放(`decideIdempotentReplay`)重放的是**同一次请求的同一份
|
|
15
|
-
* 体**,它与首决走同一条路由、同一道门;而 checkpoint 的 resume 重建腿根本不读这个体(它读盘上的 body,
|
|
16
|
-
* 走 `resolveSpec` 的宽容层)⇒ 4xx 砖不到任何存量 parked 任务。这与 tasks 面「RESUME 重放不过门」是同一条律
|
|
17
|
-
* 的两种兑现方式:**门只站在人**(或客户端)**现写的那份字节**上。
|
|
40
|
+
* 回决门族的键闭集判据表 —— **唯一**的派生口(纯函数:同一份族描述 + 同一张路由表 ⇒ 同一张表)。
|
|
41
|
+
* 生产只在本模块尾调一次(路由表声明在文件尾,见 {@link DECISION_DOOR_KEY_TABLE});测试可以喂一份构造的族
|
|
42
|
+
* (「给兄弟门受理集加一个假键 ⇒ 本门的 unsupported 集自动多这一键」就是这么钉的)。
|
|
43
|
+
* 🔴 族两两不交是**前提**不是愿望:同一个键若既是某门的体字段又是某条 URL 的路径段,它的去处就有两个答案
|
|
44
|
+
* ⇒ 抛(模块加载期 = 启动即炸,不是请求期 500)。
|
|
18
45
|
*/
|
|
19
|
-
export declare function
|
|
46
|
+
export declare function deriveDecisionDoorKeyTable(family: DecisionDoorFamily, routes: readonly RouteDecl[]): DecisionDoorKeyTable;
|
|
20
47
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* {
|
|
24
|
-
*
|
|
48
|
+
* 回决门族**一道门**的顶层键闭集裁决:受理集之外的键 ⇒ 拒体(调用方 400 `request.body_shape`);全部受理 ⇒ `null`。
|
|
49
|
+
* 判据边界与两个孪生门(`taskBodyKeyIssue` / `taskSettingsKeyIssue`)逐字同一条:**只判键名**(值形校验归各自
|
|
50
|
+
* 既有的门,本门零抢话);体本身非对象 ⇒ 本门不判(留给「body must be { decision … }」那道根形门,免得同一个体
|
|
51
|
+
* 被两句话抢答)。
|
|
25
52
|
*
|
|
26
|
-
* 🔴
|
|
27
|
-
*
|
|
28
|
-
*
|
|
53
|
+
* 🔴 **重放不额外过门**,也不需要:`/decide` 的幂等回放(`decideIdempotentReplay`)重放的是**同一次请求的同一份
|
|
54
|
+
* 体**、走同一条路由同一道门;plan_review 的反重放是一次性 nonce(S-280),守的也是同一份现写的字节;而续跑重建
|
|
55
|
+
* 读的是盘上的体、走 `resolveSpec` 的宽容层 ⇒ 4xx 砖不到任何存量挂起的任务。**门只站在人(或客户端)现写的
|
|
56
|
+
* 那份字节上**(与 tasks 面「RESUME 重放不过门」同一条律)。
|
|
29
57
|
*/
|
|
30
|
-
export declare function
|
|
58
|
+
export declare function decisionBodyKeyIssue(door: DecisionDoorId, raw: unknown, table?: DecisionDoorKeyTable): KeyClosureIssue | null;
|
|
31
59
|
/**
|
|
32
60
|
* S-342 —— 「空串的理由 = 没有理由 = 这是一次**裸拒**」。这条语义只适用于**本仓自铸**的那一个域。
|
|
33
61
|
*
|
|
@@ -147,4 +175,5 @@ export declare const APPROVALS_ASSISTANT_ROUTES: readonly [{
|
|
|
147
175
|
}];
|
|
148
176
|
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
149
177
|
export type ApprovalsAssistantRouteId = RouteIdsOf<typeof APPROVALS_ASSISTANT_ROUTES>;
|
|
178
|
+
export {};
|
|
150
179
|
//# sourceMappingURL=approvals-assistant.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { screenedGate } from "../../trace/project.js";
|
|
|
2
2
|
import { hostDecisionFacts } from "../../host-decision.js";
|
|
3
3
|
import { sameExecutionRecord } from "@sema-agent/core";
|
|
4
4
|
import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
|
|
5
|
-
import { APPROVAL_GATE_KINDS, SETTLED_BY_DENY_ONLY, SETTLED_BY_MUST_BE_STRING } from "../../tool-approval.js";
|
|
5
|
+
import { APPROVAL_GATE_KINDS, SETTLED_BY_DENY_ONLY, SETTLED_BY_MUST_BE_STRING, SETTLED_BY_TOO_WIDE, judgeSettledByBody } from "../../tool-approval.js";
|
|
6
6
|
import { isParkedRunStatus } from "../../plugins/store-contracts.js";
|
|
7
7
|
import { principalFrom, decodeCheckpointScope, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../../security.js";
|
|
8
8
|
import { editedPlanBinding, isWellFormedUnicode, verifyDirectDoorProof } from "../../principal-jwt.js";
|
|
@@ -27,30 +27,6 @@ const DECIDE_BODY_DECLARED_KEYS = [
|
|
|
27
27
|
];
|
|
28
28
|
const DECIDE_PROOF_COVERED_KEYS = ["answer", "boundCallId", "boundInputHash", "decision", "reason"];
|
|
29
29
|
const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "remember", "settledBy", "updatedInput"];
|
|
30
|
-
const ACCEPTED_DECIDE_BODY = new Set(DECIDE_BODY_DECLARED_KEYS);
|
|
31
|
-
const UNSUPPORTED_DECIDE_BODY = new Set(["editedPlan", "sessionId", "taskId"]);
|
|
32
|
-
export function decideBodyKeyIssue(raw) {
|
|
33
|
-
if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
|
|
34
|
-
return null;
|
|
35
|
-
const buckets = { unknown: [], unsupported: [] };
|
|
36
|
-
scanKeyClosure(raw, "", ACCEPTED_DECIDE_BODY, UNSUPPORTED_DECIDE_BODY, buckets);
|
|
37
|
-
if (buckets.unknown.length === 0 && buckets.unsupported.length === 0)
|
|
38
|
-
return null;
|
|
39
|
-
buckets.unknown.sort();
|
|
40
|
-
buckets.unsupported.sort();
|
|
41
|
-
const parts = [
|
|
42
|
-
...(buckets.unknown.length > 0 ? [keyClosureClause(buckets.unknown, "unknown (not a decide-request key — check the spelling)")] : []),
|
|
43
|
-
...(buckets.unsupported.length > 0 ? [keyClosureClause(buckets.unsupported, "unsupported (a sibling approval door's field, or a path parameter — never read from this body)")] : []),
|
|
44
|
-
];
|
|
45
|
-
return {
|
|
46
|
-
message: `body: unrecognized top-level key(s) — refused rather than dropped, because a dropped key reads as "in effect" to the caller ` +
|
|
47
|
-
`(a misspelled \`remember\` or \`boundInputHash\` silently turns off an exemption / the decision binding). ` +
|
|
48
|
-
`${parts.join("; ")}. ` +
|
|
49
|
-
`Accepted: ${[...ACCEPTED_DECIDE_BODY].sort().join(",")}`,
|
|
50
|
-
unknownKeys: buckets.unknown.slice(0, MAX_KEY_ECHO),
|
|
51
|
-
unsupportedKeys: buckets.unsupported.slice(0, MAX_KEY_ECHO),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
30
|
const PLAN_REVIEW_BODY_DECLARED_KEYS = [
|
|
55
31
|
"boundCallId", "decision", "editedPlan", "permissionModeAfter", "reason",
|
|
56
32
|
];
|
|
@@ -71,29 +47,108 @@ function buildApprovalEnvelope(sessionId, covered, reading) {
|
|
|
71
47
|
Object.assign(envelope, { [PROOF_SLOT_OF[key]]: reading[key] });
|
|
72
48
|
return envelope;
|
|
73
49
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
50
|
+
const DECISION_DOOR_FAMILY = {
|
|
51
|
+
"approval-decide": {
|
|
52
|
+
accepted: DECIDE_BODY_DECLARED_KEYS,
|
|
53
|
+
requestNoun: "decide-request",
|
|
54
|
+
costliestTypo: "a misspelled `remember` or `boundInputHash` silently turns off an exemption / the decision binding",
|
|
55
|
+
},
|
|
56
|
+
"assistant-task-plan-review": {
|
|
57
|
+
accepted: PLAN_REVIEW_BODY_DECLARED_KEYS,
|
|
58
|
+
requestNoun: "plan-review-request",
|
|
59
|
+
costliestTypo: "a misspelled `permissionModeAfter` silently leaves this task read-only after you approved its plan",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
const SUBMIT_BODY_NEAR_MISS_KEYS = ["permissionMode"];
|
|
63
|
+
const SUBMIT_ENDPOINT = "POST /v1/tasks";
|
|
64
|
+
function neighborLabel(n) {
|
|
65
|
+
const where = n.where.join(" / ");
|
|
66
|
+
switch (n.family) {
|
|
67
|
+
case "sibling_field":
|
|
68
|
+
return `unsupported (a body field of ${where} — send it there, this door never reads it)`;
|
|
69
|
+
case "path_segment":
|
|
70
|
+
return `unsupported (a path segment of ${where} — identity travels in the URL, never in a body)`;
|
|
71
|
+
case "submit_body":
|
|
72
|
+
return `unsupported (a submit-body key of ${where} — set when the task is submitted, this door never reads it)`;
|
|
73
|
+
default: {
|
|
74
|
+
const unreachable = n.family;
|
|
75
|
+
throw new Error(`decision-door key closure: unknown neighbor family ${String(unreachable)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function doorEndpointOf(routes, id) {
|
|
80
|
+
const decl = routes.find((r) => "id" in r && r.id === id);
|
|
81
|
+
if (decl === undefined || !("pattern" in decl) || decl.label === undefined) {
|
|
82
|
+
throw new Error(`decision-door family: route "${id}" is not a labelled pattern row of the route table — the key-closure hints are derived from it`);
|
|
83
|
+
}
|
|
84
|
+
const groups = new RegExp(`${decl.pattern.source}|`, decl.pattern.flags).exec("")?.groups ?? {};
|
|
85
|
+
return { endpoint: `${decl.methods.join("|")} ${decl.label}`, pathSegments: Object.keys(groups) };
|
|
86
|
+
}
|
|
87
|
+
export function deriveDecisionDoorKeyTable(family, routes) {
|
|
88
|
+
const ids = Object.keys(family);
|
|
89
|
+
const endpoint = new Map(ids.map((id) => [id, doorEndpointOf(routes, id)]));
|
|
90
|
+
const collect = (pairs) => {
|
|
91
|
+
const m = new Map();
|
|
92
|
+
for (const [k, where] of pairs)
|
|
93
|
+
m.set(k, [...(m.get(k) ?? []), where]);
|
|
94
|
+
return m;
|
|
95
|
+
};
|
|
96
|
+
const siblingWhere = collect(ids.flatMap((id) => family[id].accepted.map((k) => [k, endpoint.get(id).endpoint])));
|
|
97
|
+
const pathWhere = collect(ids.flatMap((id) => endpoint.get(id).pathSegments.map((k) => [k, endpoint.get(id).endpoint])));
|
|
98
|
+
const submitWhere = collect(SUBMIT_BODY_NEAR_MISS_KEYS.map((k) => [k, SUBMIT_ENDPOINT]));
|
|
99
|
+
const sources = [
|
|
100
|
+
["sibling_field", siblingWhere],
|
|
101
|
+
["path_segment", pathWhere],
|
|
102
|
+
["submit_body", submitWhere],
|
|
103
|
+
];
|
|
104
|
+
for (let i = 0; i < sources.length; i++) {
|
|
105
|
+
for (let j = i + 1; j < sources.length; j++) {
|
|
106
|
+
const [fa, a] = sources[i];
|
|
107
|
+
const [fb, b] = sources[j];
|
|
108
|
+
const both = [...a.keys()].filter((k) => b.has(k));
|
|
109
|
+
if (both.length > 0)
|
|
110
|
+
throw new Error(`decision-door family: key(s) ${both.join(", ")} belong to both ${fa} and ${fb} — a refused key must have exactly one destination`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const derive = (self) => {
|
|
114
|
+
const accepted = new Set(family[self].accepted);
|
|
115
|
+
const neighbors = new Map();
|
|
116
|
+
for (const [fam, source] of sources) {
|
|
117
|
+
for (const [k, where] of source)
|
|
118
|
+
if (!accepted.has(k))
|
|
119
|
+
neighbors.set(k, { family: fam, where });
|
|
120
|
+
}
|
|
121
|
+
return { accepted, neighbors, requestNoun: family[self].requestNoun, costliestTypo: family[self].costliestTypo };
|
|
122
|
+
};
|
|
123
|
+
return { "approval-decide": derive("approval-decide"), "assistant-task-plan-review": derive("assistant-task-plan-review") };
|
|
124
|
+
}
|
|
125
|
+
export function decisionBodyKeyIssue(door, raw, table = DECISION_DOOR_KEY_TABLE) {
|
|
80
126
|
if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
|
|
81
127
|
return null;
|
|
128
|
+
const d = table[door];
|
|
82
129
|
const buckets = { unknown: [], unsupported: [] };
|
|
83
|
-
scanKeyClosure(raw, "",
|
|
130
|
+
scanKeyClosure(raw, "", d.accepted, new Set(d.neighbors.keys()), buckets);
|
|
84
131
|
if (buckets.unknown.length === 0 && buckets.unsupported.length === 0)
|
|
85
132
|
return null;
|
|
86
133
|
buckets.unknown.sort();
|
|
87
134
|
buckets.unsupported.sort();
|
|
135
|
+
const byLabel = new Map();
|
|
136
|
+
for (const k of buckets.unsupported) {
|
|
137
|
+
const n = d.neighbors.get(k);
|
|
138
|
+
if (n === undefined)
|
|
139
|
+
throw new Error(`decision-door key closure: "${k}" was classified unsupported but has no destination`);
|
|
140
|
+
const label = neighborLabel(n);
|
|
141
|
+
byLabel.set(label, [...(byLabel.get(label) ?? []), k]);
|
|
142
|
+
}
|
|
88
143
|
const parts = [
|
|
89
|
-
...(buckets.unknown.length > 0 ? [keyClosureClause(buckets.unknown,
|
|
90
|
-
...
|
|
144
|
+
...(buckets.unknown.length > 0 ? [keyClosureClause(buckets.unknown, `unknown (not a ${d.requestNoun} key — check the spelling)`)] : []),
|
|
145
|
+
...[...byLabel].map(([label, keys]) => keyClosureClause(keys, label)),
|
|
91
146
|
];
|
|
92
147
|
return {
|
|
93
148
|
message: `body: unrecognized top-level key(s) — refused rather than dropped, because a dropped key reads as "in effect" to the caller ` +
|
|
94
|
-
`(
|
|
149
|
+
`(${d.costliestTypo}). ` +
|
|
95
150
|
`${parts.join("; ")}. ` +
|
|
96
|
-
`Accepted: ${[...
|
|
151
|
+
`Accepted: ${[...d.accepted].sort().join(",")}`,
|
|
97
152
|
unknownKeys: buckets.unknown.slice(0, MAX_KEY_ECHO),
|
|
98
153
|
unsupportedKeys: buckets.unsupported.slice(0, MAX_KEY_ECHO),
|
|
99
154
|
};
|
|
@@ -437,7 +492,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
437
492
|
return;
|
|
438
493
|
}
|
|
439
494
|
{
|
|
440
|
-
const keyIssue =
|
|
495
|
+
const keyIssue = decisionBodyKeyIssue("assistant-task-plan-review", body);
|
|
441
496
|
if (keyIssue) {
|
|
442
497
|
sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
|
|
443
498
|
return;
|
|
@@ -570,7 +625,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
570
625
|
return;
|
|
571
626
|
}
|
|
572
627
|
{
|
|
573
|
-
const keyIssue =
|
|
628
|
+
const keyIssue = decisionBodyKeyIssue("approval-decide", body);
|
|
574
629
|
if (keyIssue) {
|
|
575
630
|
sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
|
|
576
631
|
return;
|
|
@@ -637,18 +692,29 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
637
692
|
sendError(res, 400, "checkpoint_token_not_in_proof", "checkpointToken is not supported on a direct-door worker (not covered by the decision proof); bind the decision with boundCallId and boundInputHash instead");
|
|
638
693
|
return;
|
|
639
694
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
695
|
+
const settledByVerdict = judgeSettledByBody(body.settledBy, decision, deps.config.directDoorActive);
|
|
696
|
+
switch (settledByVerdict.kind) {
|
|
697
|
+
case "not_in_proof":
|
|
698
|
+
sendError(res, 400, "settled_by_not_in_proof", "settledBy is not supported on a direct-door worker (not covered by the decision proof)");
|
|
699
|
+
return;
|
|
700
|
+
case "deny_only":
|
|
701
|
+
sendError(res, 400, "request.field_conflict", SETTLED_BY_DENY_ONLY);
|
|
702
|
+
return;
|
|
703
|
+
case "not_string":
|
|
704
|
+
sendError(res, 400, "request.field_invalid", SETTLED_BY_MUST_BE_STRING);
|
|
705
|
+
return;
|
|
706
|
+
case "too_wide":
|
|
707
|
+
sendError(res, 400, "request.field_invalid", SETTLED_BY_TOO_WIDE);
|
|
708
|
+
return;
|
|
709
|
+
case "absent":
|
|
710
|
+
case "accepted":
|
|
711
|
+
break;
|
|
712
|
+
default: {
|
|
713
|
+
const unreachable = settledByVerdict;
|
|
714
|
+
throw new Error(`unhandled settledBy verdict: ${JSON.stringify(unreachable)}`);
|
|
715
|
+
}
|
|
651
716
|
}
|
|
717
|
+
const settledBy = settledByVerdict.kind === "accepted" ? settledByVerdict.settledBy : undefined;
|
|
652
718
|
const binding = {
|
|
653
719
|
checkpointToken: body.checkpointToken,
|
|
654
720
|
boundCallId: body.boundCallId,
|
|
@@ -697,7 +763,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
697
763
|
pendingGrant = { sessionId: grant.overrideSessionId ?? sessionId, toolName: canonical };
|
|
698
764
|
}
|
|
699
765
|
: undefined;
|
|
700
|
-
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(
|
|
766
|
+
const out = await resumeCheckpoint(sessionId, decision, body.reason ?? undefined, hostDecisionFacts(settledBy, deciderPrincipal), req, answer, binding, grantOnCommit, true, { principal: deciderPrincipal, explicitOperator });
|
|
701
767
|
const outErrorCode = out.body.errorCode;
|
|
702
768
|
if (pendingGrant !== undefined && out.status === 200 && outErrorCode !== "cancelled" && deps.approvalExemptionStore) {
|
|
703
769
|
try {
|
|
@@ -832,4 +898,5 @@ export const APPROVALS_ASSISTANT_ROUTES = [
|
|
|
832
898
|
{ id: "assistant-task-plan-review", pattern: /^\/v1\/assistant\/tasks\/(?<taskId>[^/]+)\/plan_review$/, label: "/v1/assistant/tasks/:id/plan_review", billable: true, methods: ["POST"] },
|
|
833
899
|
{ prefix: "/v1/approvals", billable: true },
|
|
834
900
|
];
|
|
901
|
+
const DECISION_DOOR_KEY_TABLE = deriveDecisionDoorKeyTable(DECISION_DOOR_FAMILY, APPROVALS_ASSISTANT_ROUTES);
|
|
835
902
|
//# sourceMappingURL=approvals-assistant.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
2
|
import { isModelAllowlisted, selectableCatalog } from "../../model-select.js";
|
|
3
3
|
import { resolveModelApiKey } from "../../key-resolver.js";
|
|
4
|
-
import { isSealedKeyPoison } from "../../sealed-key.js";
|
|
4
|
+
import { isSealedKeyPoison, projectSealedKeyCapability } from "../../sealed-key.js";
|
|
5
5
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
6
6
|
import { taskApproverPostureHonored, taskWriteFaceRefusal } from "../../hosted-posture.js";
|
|
7
7
|
import { autonomyOverrides } from "../../runtime-governance.js";
|
|
@@ -176,7 +176,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
176
176
|
rewindFiles: Boolean(deps.fileHistoryStore),
|
|
177
177
|
restoreFiles: Boolean(deps.fileHistoryStore),
|
|
178
178
|
rewindFilesTo: Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && deps.sessionStorage?.getLeafId),
|
|
179
|
-
fileHistoryCapture: fileHistoryCaptureOf(deps.fileHistoryStore),
|
|
179
|
+
fileHistoryCapture: fileHistoryCaptureOf(deps.fileHistoryStore, deps.config.fileHistoryCapture),
|
|
180
180
|
scheduler: Boolean(deps.config.schedulerEnabled && deps.config.requirePrincipal !== true && deps.config.remoteExec?.provider === "host"),
|
|
181
181
|
sessionSync: Boolean(deps.backend && deps.sessionStorage?.exportEntries && deps.sessionStorage?.beginImportStaging && deps.fileHistoryStore),
|
|
182
182
|
sendUserFile: Boolean(deps.sendUserFile && deps.config.sendUserFile?.publicEndpoint),
|
|
@@ -199,6 +199,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
199
199
|
})(),
|
|
200
200
|
manualCompact: Boolean(deps.runStore),
|
|
201
201
|
configCatalog: deps.config.configCatalogEnabled === true && deps.config.operatorPrincipals.length > 0,
|
|
202
|
+
sealedKey: projectSealedKeyCapability(deps.sealedKeyPublic),
|
|
202
203
|
});
|
|
203
204
|
return;
|
|
204
205
|
}
|
|
@@ -23,6 +23,7 @@ export async function handleConfigCatalog(req, res, match, ctx) {
|
|
|
23
23
|
sendJson(res, 200, buildConfigCatalog({
|
|
24
24
|
config: deps.config,
|
|
25
25
|
env: process.env,
|
|
26
|
+
boot: { ...(deps.memoryConsolidationValve !== undefined ? { memoryConsolidationValve: deps.memoryConsolidationValve } : {}) },
|
|
26
27
|
knobs: configKnobTable(),
|
|
27
28
|
centerDomains: DOMAINS,
|
|
28
29
|
centerManagedKeys: deps.configCenterManagedKeys?.(),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* [ref] A9 域模块 —— MF-Fleet 实时舰队流。
|
|
3
3
|
*
|
|
4
|
-
* 路由:`GET /v1/fleet/stream`(SSE PUSH:连上先快照,之后每条 per-row delta)
|
|
5
|
-
* (
|
|
4
|
+
* 路由:`GET /v1/fleet/stream`(SSE PUSH:连上先快照,之后每条 per-row delta)。跑在服务凭据门**之前**的
|
|
5
|
+
* 读路谓词段(S-672:先过 `dispatch.ts` 的 `readCredentialOk`「服务凭据 ∨ 已验 principal」,再按 principal 自门,
|
|
6
|
+
* 与 workflow 只读路由同段)。本域零可变状态。
|
|
6
7
|
*/
|
|
7
8
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
9
|
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
@@ -420,7 +420,6 @@ async function streamBakeEvents(req, res, bakes, bakeId, staleMs) {
|
|
|
420
420
|
return bake ? { status: bake.status, updatedAt: bake.updatedAt } : undefined;
|
|
421
421
|
},
|
|
422
422
|
getEvents: (id, after) => bakes.getEvents(id, after),
|
|
423
|
-
retainedFrom: (id) => bakes.retainedFrom(id),
|
|
424
423
|
formatEvent: (ev) => ({
|
|
425
424
|
id: ev.seq,
|
|
426
425
|
event: ev.kind,
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* ── 为什么缺席形是 **404 族**而不是 501(D4)───────────────────────────────────────────────────────
|
|
13
13
|
* 本仓的 501 `capability.*` 说的是「**这家店**在本部署上不存在,换部署形态才行」(记忆引擎没接线、
|
|
14
|
-
* 后端没有某个复合面)。本族不是那个形:阀门关着是一句**运维表态**(`MEMORY_CONSOLIDATION_DRIVER=off
|
|
15
|
-
*
|
|
14
|
+
* 后端没有某个复合面)。本族不是那个形:阀门关着是一句**运维表态**(`MEMORY_CONSOLIDATION_DRIVER=off`),或
|
|
15
|
+
* 出厂缺省派生成关(S-824 / [ref]:未设而整理座落主模型 —— 起服时有一行提示点名 `MODEL_CHEAP_ID`),不是部署能力的
|
|
16
|
+
* 缺失。而阀门开着却结构上跑不了的三种形(引擎没接线 / 后端没有控制面
|
|
16
17
|
* 归属 / provenance=off)在**启动期**就被拒了(`boot/memory-consolidation.ts` 的
|
|
17
18
|
* `assertConsolidationDriverWirable`)—— 那些机器根本起不来,不会有一个"挂着的 501"去代表它们。
|
|
18
19
|
* ⇒ 剩下的唯一缺席成因就是「阀门没开」,而它的诚实形是**这条路径不存在**:挂载条件写在 server.ts 的
|
|
@@ -2,7 +2,7 @@ import { formatUserScope } from "@sema-agent/core";
|
|
|
2
2
|
import { parseMemorySyncRequest } from "../../memory-sync.js";
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
4
|
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
5
|
-
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
5
|
+
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
6
6
|
import { assertNever } from "../route-ctx.js";
|
|
7
7
|
const ORG_PREFIX = "org:";
|
|
8
8
|
export const MEMORY_EXPORT_MAX_LIMIT = 500;
|
|
@@ -68,7 +68,7 @@ export async function handleMemoryPolicy(req, res, match, ctx) {
|
|
|
68
68
|
case "memory-export": {
|
|
69
69
|
const principal = gatedPrincipal(req, deps.config);
|
|
70
70
|
if (!principal) {
|
|
71
|
-
|
|
71
|
+
sendUnauthorized(res, "identity");
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
if (deps.config.memoryEngineBackend === "file" || !deps.memoryExport) {
|
|
@@ -128,7 +128,7 @@ export async function handleMemoryPolicy(req, res, match, ctx) {
|
|
|
128
128
|
case "memory-sync-scope": {
|
|
129
129
|
const principal = gatedPrincipal(req, deps.config);
|
|
130
130
|
if (!principal) {
|
|
131
|
-
|
|
131
|
+
sendUnauthorized(res, "identity");
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
134
|
if (deps.config.memoryEngineBackend === "file" || !deps.memorySync) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { sendJson, sendError, httpErrorCode } from "../send.js";
|
|
3
|
-
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
3
|
+
import { gatedPrincipal, explicitOperatorOk, sendUnauthorized } from "../principal-gate.js";
|
|
4
4
|
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
import { createLogger } from "../../observability/logger.js";
|
|
6
6
|
import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
@@ -90,7 +90,7 @@ export async function handleRules(req, res, match, ctx) {
|
|
|
90
90
|
return;
|
|
91
91
|
const principal = gatedPrincipal(req, deps.config);
|
|
92
92
|
if (principal === undefined) {
|
|
93
|
-
|
|
93
|
+
sendUnauthorized(res, "identity");
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
const lane = deps.ruleConsent;
|