@sema-agent/server 7.133.0 → 7.134.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 +87 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +129 -11
- package/deploy/sema-up/README.md +16 -0
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
- package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +14 -0
- package/deploy/sema-up/chart/templates/server.yaml +14 -0
- package/deploy/sema-up/chart/values-full-single.yaml +5 -1
- package/deploy/sema-up/chart/values.yaml +34 -0
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-card.d.ts +4 -4
- package/dist/approval-card.js +1 -2
- package/dist/approval-content-kind.d.ts +5 -2
- package/dist/approval-content-kind.js +16 -3
- package/dist/approval-gate-currency.d.ts +72 -0
- package/dist/approval-gate-currency.js +94 -0
- package/dist/approval.d.ts +6 -0
- package/dist/approval.js +31 -14
- package/dist/boot/coordinators.d.ts +6 -1
- package/dist/boot/coordinators.js +6 -3
- package/dist/boot/leader.d.ts +2 -0
- package/dist/boot/leader.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -2
- package/dist/boot/runner-deps.d.ts +18 -5
- package/dist/boot/runner-deps.js +9 -0
- package/dist/boot/stage-06-runners.d.ts +4 -3
- package/dist/boot/stage-06-runners.js +15 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -1
- package/dist/boot/stage-08-reapers.d.ts +2 -1
- package/dist/boot/stage-09-leader.d.ts +2 -1
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -1
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/stores.d.ts +8 -0
- package/dist/boot/stores.js +8 -12
- package/dist/boot/web-admin.d.ts +6 -0
- package/dist/boot/web-admin.js +11 -2
- package/dist/budget.js +5 -1
- package/dist/caller-text-echo.d.ts +2 -0
- package/dist/caller-text-echo.js +2 -1
- package/dist/canonical-json.d.ts +2 -41
- package/dist/canonical-json.js +2 -13
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/mcp-probe.js +2 -2
- package/dist/capability-facts.d.ts +2 -0
- package/dist/capability-facts.js +4 -4
- package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
- package/dist/checkpoint-ctx-seal-m28.js +19 -0
- package/dist/field-conflict.d.ts +2 -1
- package/dist/field-conflict.js +1 -0
- package/dist/file-history-disclosure.d.ts +34 -5
- package/dist/file-history-disclosure.js +2 -2
- package/dist/fleet/fleet-bus.d.ts +4 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/admission.js +8 -0
- package/dist/http/approval-sweeps.js +3 -1
- package/dist/http/checkpoint-ctx.d.ts +157 -2
- package/dist/http/checkpoint-ctx.js +158 -2
- package/dist/http/dispatch.js +3 -3
- package/dist/http/durable-run-leg.js +3 -2
- package/dist/http/idempotency.d.ts +2 -2
- package/dist/http/idempotency.js +2 -2
- package/dist/http/principal-gate.d.ts +32 -6
- package/dist/http/principal-gate.js +27 -4
- package/dist/http/refusal-body.d.ts +12 -0
- package/dist/http/refusal-body.js +11 -0
- package/dist/http/request-guards.d.ts +0 -9
- package/dist/http/request-guards.js +2 -16
- package/dist/http/resume-ctx-read.d.ts +48 -1
- package/dist/http/resume-ctx-read.js +26 -3
- package/dist/http/resume-legs.d.ts +2 -1
- package/dist/http/resume-legs.js +37 -22
- package/dist/http/route-ctx.d.ts +0 -9
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +43 -9
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/fleet.js +4 -4
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +4 -4
- package/dist/http/routes/runs.js +30 -9
- package/dist/http/routes/session-sync.d.ts +3 -2
- package/dist/http/routes/session-sync.js +38 -20
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions-list.js +3 -4
- package/dist/http/routes/sessions.js +4 -4
- package/dist/http/routes/tasks.js +21 -16
- package/dist/http/routes/trace-usage.js +7 -9
- package/dist/http/run-declared-families.d.ts +16 -0
- package/dist/http/run-declared-families.js +12 -0
- package/dist/http/server.d.ts +6 -0
- package/dist/http/server.js +5 -2
- package/dist/http/submit-ctx.d.ts +4 -3
- package/dist/http/submit-ctx.js +19 -3
- package/dist/http/wire-types.d.ts +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +4 -0
- package/dist/leader/wire.js +8 -7
- package/dist/main.js +1 -1
- package/dist/memory-origin-fence.js +2 -2
- package/dist/model-compat.d.ts +1 -1
- package/dist/observability/prompt-manifest.d.ts +19 -8
- package/dist/observability/prompt-manifest.js +9 -8
- package/dist/observability/tool-trace.js +8 -9
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/orchestration/workflow-park-decision.js +1 -0
- package/dist/parked-decide.d.ts +4 -5
- package/dist/parked-decide.js +3 -1
- package/dist/permission-rule-vocab.d.ts +1 -23
- package/dist/permission-rule-vocab.js +0 -13
- package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
- package/dist/plugins/approval-payload-fingerprint.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +1 -1
- package/dist/plugins/caching-session-store.js +4 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +22 -1
- package/dist/plugins/checkpoint-store-sql.js +45 -1
- package/dist/plugins/local-checkpoint-store.d.ts +13 -2
- package/dist/plugins/local-checkpoint-store.js +26 -0
- package/dist/plugins/local-session-store.d.ts +12 -9
- package/dist/plugins/local-session-store.js +23 -14
- package/dist/plugins/local-session-window.d.ts +12 -6
- package/dist/plugins/local-session-window.js +1 -17
- package/dist/plugins/one-time-migrations.d.ts +11 -0
- package/dist/plugins/one-time-migrations.js +12 -0
- package/dist/plugins/permission-rule-store-file.js +10 -6
- package/dist/plugins/permission-rule-store-sql.js +8 -8
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +39 -17
- package/dist/plugins/remote-env-device.d.ts +14 -7
- package/dist/plugins/remote-env-device.js +23 -15
- package/dist/plugins/session-ownership.d.ts +11 -0
- package/dist/plugins/session-ownership.js +11 -0
- package/dist/plugins/session-placement.d.ts +12 -14
- package/dist/plugins/session-placement.js +2 -15
- package/dist/plugins/tidb-session-store.d.ts +1 -1
- package/dist/plugins/tidb-session-store.js +39 -17
- package/dist/request-tool-axes.d.ts +12 -0
- package/dist/request-tool-axes.js +20 -0
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -3
- package/dist/sealed-key.d.ts +32 -0
- package/dist/sealed-key.js +55 -1
- package/dist/security.d.ts +9 -7
- package/dist/session-error-wire.d.ts +12 -5
- package/dist/session-error-wire.js +4 -1
- package/dist/session-sync-kernel.d.ts +37 -10
- package/dist/session-sync-kernel.js +10 -0
- package/dist/task-a2a.js +4 -3
- package/dist/task-mcp.d.ts +2 -2
- package/dist/task-mcp.js +20 -8
- package/dist/task-settings.d.ts +5 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +151 -52
- package/dist/tool-approval.js +113 -20
- package/dist/tool-rule-names.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +4 -3
- package/dist/trace/exit-seat.d.ts +13 -1
- package/dist/trace/exit-seat.js +55 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -4
- package/dist/trace/redact.d.ts +6 -3
- package/dist/trace/redact.js +191 -23
- package/dist/trace/self-minted-wire.d.ts +4 -0
- package/dist/trace/self-minted-wire.js +1 -0
- package/dist/trace/wire-projection-faces.d.ts +6 -0
- package/dist/trace/wire-projection-faces.js +6 -0
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +2 -3
- package/dist/web-admin-client/types.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +34 -2
- package/scripts/drain-check.mysql.sql +20 -2
- package/scripts/drain-check.pg.sql +20 -2
- package/dist/mcp-tool-faces.d.ts +0 -69
- package/dist/mcp-tool-faces.js +0 -104
|
@@ -5,10 +5,10 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
5
5
|
import { exportSessionManifest, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, wipeSessionAttendants } from "../../session-sync.js";
|
|
6
6
|
import * as facts from "../../capability-facts.js";
|
|
7
7
|
import { classifySyncRelationshipByIds, SyncConflictError } from "../../session-sync-kernel.js";
|
|
8
|
-
import { isUuidShape } from "../../security.js";
|
|
8
|
+
import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
|
|
9
9
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
|
|
10
10
|
import { buildDisplaySafeUrl } from "../../trace/redact.js";
|
|
11
|
-
import { gatedPrincipal, explicitOperatorOk, sendUnauthorized } from "../principal-gate.js";
|
|
11
|
+
import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sendUnauthorized, sessionCallerOf } from "../principal-gate.js";
|
|
12
12
|
const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
|
|
13
13
|
const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
14
14
|
const SYNC_IMPORT_BATCH = 500;
|
|
@@ -34,7 +34,7 @@ export function createSessionSyncLocal() {
|
|
|
34
34
|
}
|
|
35
35
|
export async function handleSessionSync(req, res, match, ctx) {
|
|
36
36
|
const { deps } = ctx;
|
|
37
|
-
const { readJson, readRawBody, safeDecode,
|
|
37
|
+
const { readJson, readRawBody, safeDecode, rateLimited } = ctx.helpers;
|
|
38
38
|
const { pendingStagings, importLeases, touchImportLease, cleanupStaging } = ctx.local.sessionSync;
|
|
39
39
|
{
|
|
40
40
|
{
|
|
@@ -44,7 +44,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
const { backend, sessionStorage, historyStore } = sync;
|
|
47
|
-
const scope =
|
|
47
|
+
const scope = fleetViewOf(req, deps, "write");
|
|
48
48
|
if (!scope.fleetWide && scope.gateOwner === null) {
|
|
49
49
|
sendUnauthorized(res, "identity");
|
|
50
50
|
return;
|
|
@@ -54,23 +54,25 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
54
54
|
sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
const
|
|
57
|
+
const judgeSessionGate = async () => {
|
|
58
|
+
const face = await sessionStorage.sessionFaceOf(sessionId);
|
|
59
|
+
const mayTouch = face !== undefined && (scope.fleetWide || face.owner === scope.gateOwner || legacyUnownedSessionOk(deps.config.requirePrincipal, sessionCallerOf(req, deps), await sessionOwnerCellOf(sessionStorage, sessionId, face.owner)));
|
|
60
|
+
return { face, mayPull: mayTouch, mayPush: face === undefined || mayTouch };
|
|
61
|
+
};
|
|
62
|
+
const gate = await judgeSessionGate();
|
|
63
|
+
const gateFace = gate.face;
|
|
58
64
|
const owner = gateFace === undefined ? undefined : gateFace.owner;
|
|
59
|
-
const
|
|
65
|
+
const sendSessionAbsent = () => sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
60
66
|
const pullGateOk = () => {
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
if (!ownsOrFleet(owner)) {
|
|
66
|
-
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
67
|
+
if (!gate.mayPull) {
|
|
68
|
+
sendSessionAbsent();
|
|
67
69
|
return false;
|
|
68
70
|
}
|
|
69
71
|
return true;
|
|
70
72
|
};
|
|
71
73
|
const pushGateOk = () => {
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
+
if (!gate.mayPush) {
|
|
75
|
+
sendSessionAbsent();
|
|
74
76
|
return false;
|
|
75
77
|
}
|
|
76
78
|
return true;
|
|
@@ -93,7 +95,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
93
95
|
throw e;
|
|
94
96
|
}
|
|
95
97
|
if (out === null) {
|
|
96
|
-
|
|
98
|
+
sendSessionAbsent();
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
101
|
sendJson(res, 200, { manifest: out.manifest });
|
|
@@ -114,7 +116,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
114
116
|
}
|
|
115
117
|
const it = await sessionStorage.exportEntriesStream(sessionId, afterSeq === undefined ? undefined : { afterSeq });
|
|
116
118
|
if (it === null) {
|
|
117
|
-
|
|
119
|
+
sendSessionAbsent();
|
|
118
120
|
return;
|
|
119
121
|
}
|
|
120
122
|
res.writeHead(200, { "content-type": "application/x-ndjson", "transfer-encoding": "chunked" });
|
|
@@ -387,7 +389,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
387
389
|
importLeases.set(sessionId, { at: Date.now(), stagingId });
|
|
388
390
|
pendingStagings.set(stagingId, {
|
|
389
391
|
handle, realSessionId: sessionId, owner: importingPrincipal, ...(resolution ? { resolution } : {}), operatorOk,
|
|
390
|
-
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: scope.gateOwner,
|
|
392
|
+
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: scope.gateOwner, expectedOwner: owner,
|
|
391
393
|
});
|
|
392
394
|
sendJson(res, 200, { stagingId, relation: relA.relation });
|
|
393
395
|
return;
|
|
@@ -403,7 +405,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
403
405
|
sendError(res, 404, "not_found.staging", "staging not found");
|
|
404
406
|
return;
|
|
405
407
|
}
|
|
406
|
-
if (!
|
|
408
|
+
if (!scope.fleetWide && pending.openedBy !== scope.gateOwner) {
|
|
407
409
|
sendError(res, 404, "not_found.staging", "staging not found");
|
|
408
410
|
return;
|
|
409
411
|
}
|
|
@@ -485,7 +487,23 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
485
487
|
return;
|
|
486
488
|
throw e;
|
|
487
489
|
}
|
|
488
|
-
|
|
490
|
+
let mayPushAtCommit;
|
|
491
|
+
let activeAtCommit;
|
|
492
|
+
try {
|
|
493
|
+
mayPushAtCommit = (await judgeSessionGate()).mayPush;
|
|
494
|
+
activeAtCommit = mayPushAtCommit ? await deps.runStore?.getActiveTaskId(sessionId) : undefined;
|
|
495
|
+
}
|
|
496
|
+
catch (e) {
|
|
497
|
+
await pending.handle.abort().catch(() => undefined);
|
|
498
|
+
cleanupStaging(stagingId);
|
|
499
|
+
throw e;
|
|
500
|
+
}
|
|
501
|
+
if (!mayPushAtCommit) {
|
|
502
|
+
await pending.handle.abort().catch(() => undefined);
|
|
503
|
+
cleanupStaging(stagingId);
|
|
504
|
+
sendSessionAbsent();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
489
507
|
if (activeAtCommit) {
|
|
490
508
|
await pending.handle.abort().catch(() => undefined);
|
|
491
509
|
cleanupStaging(stagingId);
|
|
@@ -493,7 +511,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
493
511
|
return;
|
|
494
512
|
}
|
|
495
513
|
try {
|
|
496
|
-
const committed = await pending.handle.commit(pending.owner, pending.resolution ? { resolution: pending.resolution } :
|
|
514
|
+
const committed = await pending.handle.commit(pending.owner, { expectedOwner: pending.expectedOwner, ...(pending.resolution ? { resolution: pending.resolution } : {}) });
|
|
497
515
|
if (pending.resolution === "overwrite-dst" && (committed.relation === "fork" || committed.relation === "stale")) {
|
|
498
516
|
await wipeSessionAttendants({ fileHistoryStore: historyStore, sessionPolicyStore: deps.sessionPolicyStore, resumeAnchorStore: deps.resumeAnchorStore }, sessionId);
|
|
499
517
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* [ref] A9 域模块 —— 会话列表 / 发件链接列表。
|
|
3
3
|
*
|
|
4
4
|
* 路由:`GET /v1/sessions`(CC /resume picker,keyset 分页)、`GET /v1/sendfile-links`。
|
|
5
|
-
* owner-scope 与 trace 列表同一权威模型(
|
|
5
|
+
* owner-scope 与 trace 列表同一权威模型(`fleetViewOf` 读档:service 凭据不报主体 / 只读 trace 凭据 / dev-open = fleet-wide;带验过的主体 = 钉住那位主体)。本域零可变状态。
|
|
6
6
|
*/
|
|
7
7
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
8
|
import { type SessionListItem } from "../../security.js";
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { isUuidV7, legacyUnownedAmnestyOpen } from "../../security.js";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
|
-
import { sendUnauthorized } from "../principal-gate.js";
|
|
3
|
+
import { fleetViewOf, sendUnauthorized } from "../principal-gate.js";
|
|
4
4
|
import { filterFingerprint } from "../cursor-fingerprint.js";
|
|
5
5
|
import { sessionTempoOfRunStatus } from "../../plugins/store-contracts.js";
|
|
6
6
|
import { assertNever } from "../route-ctx.js";
|
|
7
7
|
import * as facts from "../../capability-facts.js";
|
|
8
8
|
export async function handleSessionsList(req, res, match, ctx) {
|
|
9
9
|
const { deps } = ctx;
|
|
10
|
-
const { isFleetWide, sessionOwnerScope } = ctx.helpers;
|
|
11
10
|
switch (match.decl.id) {
|
|
12
11
|
case "sessions-list": {
|
|
13
|
-
const { fleetWide, gateOwner } =
|
|
12
|
+
const { fleetWide, gateOwner } = fleetViewOf(req, deps, "read");
|
|
14
13
|
if (!fleetWide && gateOwner === null) {
|
|
15
14
|
sendUnauthorized(res, "identity");
|
|
16
15
|
return;
|
|
@@ -26,7 +25,7 @@ export async function handleSessionsList(req, res, match, ctx) {
|
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
27
|
case "sendfile-links": {
|
|
29
|
-
const { fleetWide, gateOwner } =
|
|
28
|
+
const { fleetWide, gateOwner } = fleetViewOf(req, deps, "read");
|
|
30
29
|
if (!fleetWide && gateOwner === null) {
|
|
31
30
|
sendUnauthorized(res, "identity");
|
|
32
31
|
return;
|
|
@@ -9,7 +9,7 @@ import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.
|
|
|
9
9
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
10
10
|
import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
|
|
11
11
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
|
|
12
|
-
import { gatedPrincipal, explicitOperatorOk, sessionCallerOf, sessionOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
12
|
+
import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sessionCallerOf, sessionOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
13
13
|
import { runRowReadableOnSessionFace } from "../owner-access.js";
|
|
14
14
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
15
15
|
import * as facts from "../../capability-facts.js";
|
|
@@ -71,7 +71,7 @@ async function readLastLegMcp(deps, sessionId, caller) {
|
|
|
71
71
|
}
|
|
72
72
|
export async function handleSessions(req, res, match, ctx) {
|
|
73
73
|
const { deps } = ctx;
|
|
74
|
-
const { readJson, safeDecode, sessionOwnerOk
|
|
74
|
+
const { readJson, safeDecode, sessionOwnerOk } = ctx.helpers;
|
|
75
75
|
const readOwnedSessionView = async (sessionAudit, sessionId, readOpts) => {
|
|
76
76
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
77
77
|
if (ownerOf !== undefined) {
|
|
@@ -699,7 +699,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
699
699
|
}
|
|
700
700
|
const fork = forkFact.sessionStorage.fork.bind(forkFact.sessionStorage);
|
|
701
701
|
const ownerOf = forkFact.sessionStorage.ownerOf.bind(forkFact.sessionStorage);
|
|
702
|
-
const scope =
|
|
702
|
+
const scope = fleetViewOf(req, deps, "write");
|
|
703
703
|
if (!scope.fleetWide && scope.gateOwner === null) {
|
|
704
704
|
sendUnauthorized(res, "identity");
|
|
705
705
|
return;
|
|
@@ -737,7 +737,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
737
737
|
}
|
|
738
738
|
const purge = deleteFact.purge;
|
|
739
739
|
const ownerOf = deleteFact.sessionStorage.ownerOf.bind(deleteFact.sessionStorage);
|
|
740
|
-
const scope =
|
|
740
|
+
const scope = fleetViewOf(req, deps, "write");
|
|
741
741
|
if (!scope.fleetWide && scope.gateOwner === null) {
|
|
742
742
|
sendUnauthorized(res, "identity");
|
|
743
743
|
return;
|
|
@@ -21,7 +21,7 @@ import { replayOfExistingRun } from "../submit-replay.js";
|
|
|
21
21
|
import { httpErrorCode, sendJson, sendError, sseHeaders } from "../send.js";
|
|
22
22
|
import { collectSsePreamble } from "../sse-log.js";
|
|
23
23
|
import { buildApprovalPreamble, buildApprovalPreambleSseFrames } from "../../approval-card.js";
|
|
24
|
-
import { createApprovalCardEmitter, resolveApprovalLeg, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
24
|
+
import { approvalPreambleReplays, createApprovalCardEmitter, declaredApprovalFrameFamilies, resolveApprovalLeg, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
25
25
|
import { buildActiveRunConflict, isActiveRunConflictBody, toDoneFrameResult } from "../active-run-conflict.js";
|
|
26
26
|
import { commitPlacement, devicePlacementErrorBody } from "../device-placement.js";
|
|
27
27
|
import { commitSessionInheritEnv } from "../session-inherit-env.js";
|
|
@@ -150,6 +150,18 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
150
150
|
const prepared = await prepareSpec(req, res, keyedBody, undefined, { liveStream });
|
|
151
151
|
if (!prepared)
|
|
152
152
|
return;
|
|
153
|
+
const declaredFamilies = declaredApprovalFrameFamilies(prepared.body.settings);
|
|
154
|
+
const approvalLegInput = (streamApprovalOn) => {
|
|
155
|
+
const legWalltimeMs = typeof prepared.spec.limits?.maxWalltimeMs === "number" ? prepared.spec.limits.maxWalltimeMs : undefined;
|
|
156
|
+
return {
|
|
157
|
+
streamApprovalOn,
|
|
158
|
+
windowMs: deps.config.streamApproval?.windowMs,
|
|
159
|
+
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
160
|
+
...(legWalltimeMs !== undefined ? { legWalltimeMs } : {}),
|
|
161
|
+
declaredFamilies,
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const assembleApprovalLeg = (streamApprovalOn) => resolveApprovalLeg({ ...approvalLegInput(streamApprovalOn), nowMonotonicMs: performance.now() });
|
|
153
165
|
const principal = prepared.auth?.principal;
|
|
154
166
|
const wiringOperatorFace = explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config);
|
|
155
167
|
let wiringReceipt;
|
|
@@ -215,9 +227,9 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
215
227
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
216
228
|
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
217
229
|
backend: deps.backend,
|
|
218
|
-
|
|
230
|
+
checkpointStore: deps.checkpointStore,
|
|
219
231
|
});
|
|
220
|
-
const replaySessionId = replayGate.active ? prepared.spec.sessionId : undefined;
|
|
232
|
+
const replaySessionId = approvalPreambleReplays(approvalLegInput(replayGate.active)) ? prepared.spec.sessionId : undefined;
|
|
221
233
|
const presentedOnConnection = new Set();
|
|
222
234
|
const presentedCompleted = new Set();
|
|
223
235
|
const admitPresentedComplete = (askId) => {
|
|
@@ -663,18 +675,11 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
663
675
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
664
676
|
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
665
677
|
backend: deps.backend,
|
|
666
|
-
|
|
678
|
+
checkpointStore: deps.checkpointStore,
|
|
667
679
|
}).active;
|
|
668
|
-
const
|
|
669
|
-
const approvalLeg = resolveApprovalLeg({
|
|
670
|
-
streamApprovalOn,
|
|
671
|
-
windowMs: deps.config.streamApproval?.windowMs,
|
|
672
|
-
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
673
|
-
...(legWalltimeMs !== undefined ? { legWalltimeMs } : {}),
|
|
674
|
-
nowMonotonicMs: performance.now(),
|
|
675
|
-
});
|
|
680
|
+
const approvalLeg = assembleApprovalLeg(streamApprovalOn);
|
|
676
681
|
const legDeadlineMonotonic = approvalLeg.legDeadlineMonotonic;
|
|
677
|
-
const
|
|
682
|
+
const unattended = approvalLeg.unattended;
|
|
678
683
|
const emitCard = createApprovalCardEmitter({
|
|
679
684
|
...(ledgerSink
|
|
680
685
|
? {
|
|
@@ -696,7 +701,7 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
696
701
|
if (!res.writableEnded && !res.destroyed)
|
|
697
702
|
emitAsk(frame);
|
|
698
703
|
};
|
|
699
|
-
const approvalCtx = deps.toolApproval &&
|
|
704
|
+
const approvalCtx = deps.toolApproval && unattended === undefined
|
|
700
705
|
? {
|
|
701
706
|
taskId: askTaskId,
|
|
702
707
|
owner: askOwner,
|
|
@@ -714,8 +719,8 @@ export async function handleTasks(req, res, match, ctx) {
|
|
|
714
719
|
: undefined;
|
|
715
720
|
if (deps.toolApproval) {
|
|
716
721
|
const toolApproval = deps.toolApproval;
|
|
717
|
-
prepared.spec.onAsk =
|
|
718
|
-
? toolApproval.boundUnattendedAsk({ owner: askOwner, taskId: askTaskId, ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}) })
|
|
722
|
+
prepared.spec.onAsk = unattended
|
|
723
|
+
? toolApproval.boundUnattendedAsk({ owner: askOwner, taskId: askTaskId, ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}) }, unattended)
|
|
719
724
|
: toolApproval.boundAsk({
|
|
720
725
|
owner: askOwner,
|
|
721
726
|
taskId: askTaskId,
|
|
@@ -6,16 +6,16 @@ import { sleep } from "../sse-log.js";
|
|
|
6
6
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
7
7
|
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
8
8
|
import { filterFingerprint } from "../cursor-fingerprint.js";
|
|
9
|
-
import {
|
|
9
|
+
import { fleetViewOf, sendUnauthorized } from "../principal-gate.js";
|
|
10
10
|
import { assertNever } from "../route-ctx.js";
|
|
11
11
|
import * as facts from "../../capability-facts.js";
|
|
12
12
|
const TOOL_RESULT_LIMIT_CAP = 262_144;
|
|
13
13
|
const TOOL_RESULT_OFFSET_CAP = 2_147_483_646;
|
|
14
14
|
export async function handleTraceUsage(req, res, match, ctx) {
|
|
15
15
|
const { deps } = ctx;
|
|
16
|
-
const {
|
|
16
|
+
const { safeDecode } = ctx.helpers;
|
|
17
17
|
if (match.decl.id === "tasks-source-summary") {
|
|
18
|
-
if (!
|
|
18
|
+
if (!fleetViewOf(req, deps, "read").fleetWide) {
|
|
19
19
|
sendUnauthorized(res, "operator");
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
@@ -35,8 +35,7 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
35
35
|
match.decl.id === "task-trace-stream" ||
|
|
36
36
|
match.decl.id === "task-trace-artifacts" ||
|
|
37
37
|
match.decl.id === "task-tool-result") {
|
|
38
|
-
const fleetWide =
|
|
39
|
-
const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
|
|
38
|
+
const { fleetWide, gateOwner } = fleetViewOf(req, deps, "read");
|
|
40
39
|
if (!fleetWide && gateOwner === null) {
|
|
41
40
|
sendUnauthorized(res, "identity");
|
|
42
41
|
return;
|
|
@@ -89,9 +88,8 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
89
88
|
return;
|
|
90
89
|
}
|
|
91
90
|
const runStore = runs.runStore;
|
|
92
|
-
const fleetWide =
|
|
93
|
-
|
|
94
|
-
if (!fleetWide && !jwtOwner) {
|
|
91
|
+
const { fleetWide, gateOwner } = fleetViewOf(req, deps, "read");
|
|
92
|
+
if (!fleetWide && gateOwner === null) {
|
|
95
93
|
sendUnauthorized(res, "identity");
|
|
96
94
|
return;
|
|
97
95
|
}
|
|
@@ -109,7 +107,7 @@ export async function handleTraceUsage(req, res, match, ctx) {
|
|
|
109
107
|
sendError(res, 400, "request.query_invalid", "invalid from/to (ISO-8601 or epoch-ms; from < to)");
|
|
110
108
|
return;
|
|
111
109
|
}
|
|
112
|
-
const owner =
|
|
110
|
+
const owner = gateOwner ?? (q.get("principal") ?? undefined);
|
|
113
111
|
try {
|
|
114
112
|
const scan = await runStore.usageScan(from, to, owner !== undefined ? { owner } : undefined);
|
|
115
113
|
const base = { from: new Date(from).toISOString(), to: new Date(to).toISOString(), ...(scan.truncated ? { truncated: true } : {}) };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一条 run 的**持久化声明**读口(H5):它声明了哪些审批开卡帧族(`settings.approvalFrameFamilies`)。
|
|
3
|
+
*
|
|
4
|
+
* 声明随受理过的提交体落盘 —— 提交腿(`POST /v1/tasks` 两腿 / `POST /v1/runs` / A2A)在 run 认领之后把整份提交体写进续跑输入(`checkpoint_ctx`,会话键,
|
|
5
|
+
* `http/submit-ctx.ts` 的 `persistSubmitCtx`),所以**事后**才发生的读(耐久事件流的开流 preamble)读得到「这条 run 当初声明了什么」。读法只有一个:
|
|
6
|
+
* `declaredApprovalFrameFamilies`(S-1914 的唯一读者;缺席 / 形坏 ⇒ 空集),本模块不另判声明的合法性。
|
|
7
|
+
*
|
|
8
|
+
* 失效方向是**不重放**:续跑输入不在 / 体读不出 ⇒ 空集 ⇒ preamble 零帧(与「未声明」同形;空集 = 这条腿没有活审批通道)。店与会话 id 由类型保证在场:
|
|
9
|
+
* 唯一的调用方(`routes/runs.ts` 事件流 preamble)只在上场门为真时读 —— 上场门的 park 设施项就是「checkpoint 店在场」,run 行的会话 id 是必填串。
|
|
10
|
+
* 体读不出(店的拒绝契约)时报一次 `onUnreadable(where)`,让「为什么这条 run 的卡没被重放」在运维日志里有迹可循;店抖动 / 连接错按读口的刻意保守折成「不在」(`readResumeCtx` 头注)。
|
|
11
|
+
* 读的是 `KEEP_SEALED` 形:`approvalFrameFamilies` 不是凭据位,不为一个声明位开封任何凭据。
|
|
12
|
+
*/
|
|
13
|
+
import type { ApprovalFrameFamily } from "../tool-approval.js";
|
|
14
|
+
import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
15
|
+
export declare function readRunDeclaredFamilies(cs: Pick<CheckpointStoreFull, "getCtx">, sessionId: string, onUnreadable?: (where: string) => void): Promise<ReadonlySet<ApprovalFrameFamily>>;
|
|
16
|
+
//# sourceMappingURL=run-declared-families.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { declaredApprovalFrameFamilies } from "../tool-approval.js";
|
|
2
|
+
import { KEEP_SEALED, readResumeCtx } from "./resume-ctx-read.js";
|
|
3
|
+
export async function readRunDeclaredFamilies(cs, sessionId, onUnreadable) {
|
|
4
|
+
const read = await readResumeCtx(cs.getCtx(sessionId), KEEP_SEALED);
|
|
5
|
+
if (read.kind === "corrupt")
|
|
6
|
+
onUnreadable?.(read.where);
|
|
7
|
+
if (read.kind !== "ok")
|
|
8
|
+
return new Set();
|
|
9
|
+
const body = read.ctx.body;
|
|
10
|
+
return declaredApprovalFrameFamilies(typeof body === "object" && body !== null ? Reflect.get(body, "settings") : undefined);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=run-declared-families.js.map
|
package/dist/http/server.d.ts
CHANGED
|
@@ -153,6 +153,12 @@ export interface ServiceStoreDeps {
|
|
|
153
153
|
/** Durable-checkpoint store ([ref], core 1.67): durable F4 suspend/resume. When present, the resume
|
|
154
154
|
* endpoint + suspended-run surfacing are active. Set only when DURABLE_APPROVAL + a TiDB pool. */
|
|
155
155
|
checkpointStore?: CheckpointStoreFull;
|
|
156
|
+
/** S-1907:停驻行凭据的**封签器**(`sealed-key.ts` `ensureCheckpointCtxSealing`,这一域自己的保管店 `<数据根>/sealed-key/checkpoint-ctx-keys/`;规则属主 = `checkpoint-ctx.ts` 末段
|
|
157
|
+
* 「凭据位密封」)。提交体里请求面凭据位上的值在 `checkpoint_ctx` 里只以封签存在:写 ctx 的每一笔经它封、续跑上下文读口经它开。
|
|
158
|
+
* 🔴 **与 `checkpointStore` 同在**:店在而它缺席 ⇒ `createHttpServer` 拒启(句子 = `CHECKPOINT_CTX_SEALING_REQUIRED`)—— 凭据只以封签落盘,
|
|
159
|
+
* 封不了就不落,没有「明文照写」的第二种装配。嵌入方:`checkpointCtxSealing: await ensureCheckpointCtxSealing(sealedKeyDir)`
|
|
160
|
+
* (保管目录要跨重启持久、多副本各副本是同一份钥匙,否则封它的那把钥匙不在 ⇒ 409 `conflict.checkpoint_ctx_unsealable` `reason:"key_not_held"`)。 */
|
|
161
|
+
checkpointCtxSealing?: import("./checkpoint-ctx.js").CheckpointCtxSealing;
|
|
156
162
|
/** E6 durable SessionPolicyStore (operator-tightened per-session tool rules). Enables PUT/GET /v1/sessions/:id/policy
|
|
157
163
|
* when present; core reads the rules at prepare-time (subtract-only) via RunnerDeps.sessionPolicyStore (wired separately). */
|
|
158
164
|
sessionPolicyStore?: ServiceSessionPolicyStore;
|
package/dist/http/server.js
CHANGED
|
@@ -21,6 +21,7 @@ import { sendError, sendHttpError, sendSessionError, logRequestError } from "./s
|
|
|
21
21
|
import { createRequestGuards } from "./request-guards.js";
|
|
22
22
|
import { sqlEngineSeatIssue } from "../sql-engine-posture.js";
|
|
23
23
|
import { parkDecisionStoreRefusal } from "../orchestration/workflow-park-decision.js";
|
|
24
|
+
import { CHECKPOINT_CTX_SEALING_REQUIRED } from "./checkpoint-ctx.js";
|
|
24
25
|
import { retiredServiceDepsRefusal } from "./retired-service-deps.js";
|
|
25
26
|
import { createAdmission, MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills } from "./admission.js";
|
|
26
27
|
import { createResumeLegs } from "./resume-legs.js";
|
|
@@ -85,6 +86,8 @@ export function createHttpServer(rawDeps) {
|
|
|
85
86
|
if (refusal !== undefined)
|
|
86
87
|
throw new Error(`createHttpServer: ${refusal}`);
|
|
87
88
|
}
|
|
89
|
+
if (deps.checkpointStore !== undefined && deps.checkpointCtxSealing === undefined)
|
|
90
|
+
throw new Error(`createHttpServer: ${CHECKPOINT_CTX_SEALING_REQUIRED}`);
|
|
88
91
|
const idemCache = new IdempotencyCache();
|
|
89
92
|
const inflightRuns = new Map();
|
|
90
93
|
const preemptableRuns = new DrainYieldingPreemptBook(() => deps.drainState?.draining === true);
|
|
@@ -140,9 +143,9 @@ export function createHttpServer(rawDeps) {
|
|
|
140
143
|
}
|
|
141
144
|
};
|
|
142
145
|
}
|
|
143
|
-
const { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode,
|
|
146
|
+
const { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, readJson, readRawBody } = createRequestGuards({ deps });
|
|
144
147
|
const { prepareSpec } = createAdmission({ deps, readJson, runnerFor });
|
|
145
|
-
const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode,
|
|
148
|
+
const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor });
|
|
146
149
|
const { denyExpiredApprovals, redeemParkedAsk } = createApprovalSweeps({ deps, resumeCheckpoint, mayDiscloseRowError });
|
|
147
150
|
const { handle } = createDispatch({ deps, noteActivity, routeCtxBase, counters });
|
|
148
151
|
const server = http.createServer((req, res) => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { RunStore } from "../plugins/store-backend.js";
|
|
2
2
|
import type { Logger } from "../observability/logger.js";
|
|
3
3
|
import type { Metrics } from "../observability/metrics.js";
|
|
4
|
-
import { buildCheckpointCtx } from "./checkpoint-ctx.js";
|
|
4
|
+
import { buildCheckpointCtx, type CheckpointCtxSealer, type SealedCheckpointCtx } from "./checkpoint-ctx.js";
|
|
5
5
|
import { type RefusalBody } from "./refusal-body.js";
|
|
6
6
|
/** 写不进的那一种可答复的结局(各腿把它交回自己的出口:同步 / durable 腿直接回,活流腿编码成终帧)。 */
|
|
7
7
|
export interface SubmitCtxRefusal {
|
|
8
|
-
readonly status: 413;
|
|
8
|
+
readonly status: 413 | 409;
|
|
9
9
|
readonly body: RefusalBody;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
@@ -14,10 +14,11 @@ export interface SubmitCtxRefusal {
|
|
|
14
14
|
* 返回 `undefined` = 写进了,腿照常起跑。
|
|
15
15
|
*/
|
|
16
16
|
export declare function persistSubmitCtx(store: {
|
|
17
|
-
putCtx(sessionId: string, ctx:
|
|
17
|
+
putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
|
|
18
18
|
}, deps: {
|
|
19
19
|
readonly logger?: Logger | undefined;
|
|
20
20
|
readonly metrics?: Metrics | undefined;
|
|
21
|
+
readonly checkpointCtxSealing?: CheckpointCtxSealer | undefined;
|
|
21
22
|
}, claim: {
|
|
22
23
|
readonly runStore: RunStore;
|
|
23
24
|
readonly taskId: string;
|
package/dist/http/submit-ctx.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { StoreRowTooLargeError } from "../plugins/sql-errors.js";
|
|
2
2
|
import { releaseClaimedRun } from "./claimed-run.js";
|
|
3
|
-
import { buildCheckpointCtx } from "./checkpoint-ctx.js";
|
|
3
|
+
import { buildCheckpointCtx, CheckpointCtxUnsealableError } from "./checkpoint-ctx.js";
|
|
4
4
|
import { refusalBody } from "./refusal-body.js";
|
|
5
5
|
import { limitFactsUnmeasured } from "../limit-refusal.js";
|
|
6
|
+
function unsealableRefusal(e) {
|
|
7
|
+
return {
|
|
8
|
+
status: 409,
|
|
9
|
+
body: refusalBody({
|
|
10
|
+
errorCode: "conflict.checkpoint_ctx_unsealable",
|
|
11
|
+
message: "this deployment cannot store this task's credentials (MCP / A2A headers or env values, settings.env, settings.webSearch.apiKey, or http hook headers) for resuming it: they are kept only sealed, and this server has no sealed-key store — nothing was run; ask whoever operates this deployment to give the service a writable sealed-key directory (<data root>/sealed-key/), or submit the task without those credentials",
|
|
12
|
+
facts: { field: e.field, reason: e.reason },
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
6
16
|
function rowTooLargeRefusal(tooLarge) {
|
|
7
17
|
switch (tooLarge.wall) {
|
|
8
18
|
case "row":
|
|
@@ -19,18 +29,24 @@ function rowTooLargeRefusal(tooLarge) {
|
|
|
19
29
|
}
|
|
20
30
|
export async function persistSubmitCtx(store, deps, claim, sessionId, fields) {
|
|
21
31
|
try {
|
|
22
|
-
await store.putCtx(sessionId, buildCheckpointCtx(fields));
|
|
32
|
+
await store.putCtx(sessionId, buildCheckpointCtx(fields, deps.checkpointCtxSealing, sessionId));
|
|
23
33
|
return undefined;
|
|
24
34
|
}
|
|
25
35
|
catch (err) {
|
|
26
36
|
const tooLarge = err instanceof StoreRowTooLargeError ? err : undefined;
|
|
37
|
+
const unsealable = err instanceof CheckpointCtxUnsealableError ? err : undefined;
|
|
38
|
+
const known = tooLarge ?? unsealable;
|
|
27
39
|
if (claim !== undefined) {
|
|
28
|
-
const why =
|
|
40
|
+
const why = known !== undefined ? known.message : `the run's resume inputs could not be stored: ${err instanceof Error ? err.message : String(err)}`;
|
|
29
41
|
await releaseClaimedRun(claim.runStore, claim.taskId, why, (releaseErr, step) => {
|
|
30
42
|
deps.logger?.warn("submit_ctx_run_release_failed", { taskId: claim.taskId, sessionId, step, err: releaseErr instanceof Error ? releaseErr.message : String(releaseErr) });
|
|
31
43
|
deps.metrics?.inc("submit_ctx_run_release_failed_total", { step });
|
|
32
44
|
});
|
|
33
45
|
}
|
|
46
|
+
if (unsealable !== undefined) {
|
|
47
|
+
deps.logger?.warn("submit_ctx_unsealable", { sessionId, taskId: claim?.taskId, field: unsealable.field, reason: unsealable.reason });
|
|
48
|
+
return unsealableRefusal(unsealable);
|
|
49
|
+
}
|
|
34
50
|
if (tooLarge === undefined)
|
|
35
51
|
throw err;
|
|
36
52
|
deps.logger?.warn("submit_ctx_row_too_large", { sessionId, taskId: claim?.taskId, wall: tooLarge.wall, db: tooLarge.dbMessage });
|
|
@@ -402,10 +402,10 @@ export interface TaskRequestBody {
|
|
|
402
402
|
* 记忆面暗的部署(引擎未接线 / 多租户)上 `memory` 整键本就缺席,本声明无处落也无事可关。
|
|
403
403
|
* Rides the persisted body onto resume legs(与 `memoryWrite` 同姿势)。
|
|
404
404
|
* ✅ **子代地板自 core 7.22.0 到货,本仓零接线**:core 的 `parentSpecSeatOf` 从**这条 run 自己的**
|
|
405
|
-
* `spec.memory.enabled === false`
|
|
406
|
-
*
|
|
405
|
+
* `spec.memory.enabled === false` 铸座位的 `memory: { enabled: false }`(core 7.54.0 起是夹表座的一行;此前叫 `memoryOff`,已退役),
|
|
406
|
+
* `applyParentSpecSeat` 按表**夹**到子代上(改写成 `{ …子代自己的 scopes, enabled:false }`,永不删键;子代的 `enabled:true` 抬不起)⇒ 父关 ⇒ 子代 / 孙代 /
|
|
407
407
|
* workflow 子 spec / 跨 run 唤醒与保留 resume 一律无记忆面。🚧 **tier-3 耐久复活不带**(core 已记残余)。
|
|
408
|
-
* 链路的机器锚:`test/memory-off-request.test.ts` ⑥ 段(
|
|
408
|
+
* 链路的机器锚:`test/memory-off-request.test.ts` ⑥ 段(经 core 包根 `parentSpecSeatOf` / `applyParentSpecSeat` 读铸与施)。 */
|
|
409
409
|
memory?: "off";
|
|
410
410
|
/** 142-S4 projectId 线程化([ref] §2):这个 run 归属的项目(center 登记簿键,generic lowercase
|
|
411
411
|
* UUID — core S1 `resolveProjectId` marker 或 S3 无仓 mint 的产物;客户端只透传,零铸造权威)。是「哪个
|
|
@@ -572,6 +572,10 @@ export type DecideBinding = {
|
|
|
572
572
|
boundInputHash?: string;
|
|
573
573
|
/** [ref] edit: operator-corrected tool args, applied by core AFTER the binding check, on approve only. */
|
|
574
574
|
updatedInput?: unknown;
|
|
575
|
+
/** core 7.54.0: the words the approving person wrote on the card ("and tell the agent what to do next"). The decide door admits
|
|
576
|
+
* the key only on a person's `approve` (a `deny` or a policy decide with it is a 400 `request.field_conflict`), and the outcome
|
|
577
|
+
* mints carry it verbatim when present — the engine owns what counts as written (blank = nothing) and judges it again pre-CAS. */
|
|
578
|
+
feedback?: string;
|
|
575
579
|
};
|
|
576
580
|
/** [ref]([ref]④→[ref] 坐标轮换判定):409 `approval_stale` 拒体上的 additive 机读指路 ——
|
|
577
581
|
* **本会话当前 pending** 的 D-1 回显坐标,让持旧坐标的壳一跳重定位(免重拉列表)。
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export { ensureTiDBTaskListSchema, ensurePgTaskListSchema } from "./plugins/task
|
|
|
23
23
|
export { createAuthorizer, principalFrom, HttpError, type AuthContext, type OwnerAwareSessionStore, } from "./security.js";
|
|
24
24
|
export { memoryScopeFor, memoryEngineBackendFor } from "./memory-scope.js";
|
|
25
25
|
export type { TaskRequestBody } from "./http/wire-types.js";
|
|
26
|
+
export { ensureCheckpointCtxSealing } from "./sealed-key.js";
|
|
27
|
+
export type { CheckpointCtxSealing } from "./http/checkpoint-ctx.js";
|
|
26
28
|
export { createHttpServer, type ServiceDeps, type RequestAuth, type FlatServiceDeps, type ServiceDepGroups, type ServiceCoreDeps, type ServiceStoreDeps, type ServiceCoordinatorDeps, type ServiceSeamDeps, type ServiceObservabilityDeps, type ServiceGovernanceDeps, type ServiceDeploymentDeps, type ServiceKnobDeps, } from "./http/server.js";
|
|
27
29
|
export { createSideQueryLane, type KeyResolver, type SideQueryLane, type SideQueryLaneCtx } from "./boot/side-query-lane.js";
|
|
28
30
|
export { createKeyResolver, resolveModelApiKey, type ModelKeyRef } from "./key-resolver.js";
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ export { createTaskListLane, taskListKeyFor, MAX_TASK_LIST_SESSIONS } from "./bo
|
|
|
20
20
|
export { ensureTiDBTaskListSchema, ensurePgTaskListSchema } from "./plugins/task-list-store-sql.js";
|
|
21
21
|
export { createAuthorizer, principalFrom, HttpError, } from "./security.js";
|
|
22
22
|
export { memoryScopeFor, memoryEngineBackendFor } from "./memory-scope.js";
|
|
23
|
+
export { ensureCheckpointCtxSealing } from "./sealed-key.js";
|
|
23
24
|
export { createHttpServer, } from "./http/server.js";
|
|
24
25
|
export { createSideQueryLane } from "./boot/side-query-lane.js";
|
|
25
26
|
export { createKeyResolver, resolveModelApiKey } from "./key-resolver.js";
|
package/dist/leader/wire.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Brain, type Model, type ModelRoles, type ModelPricing, type TaskSpec, type ExecutionEnvFactory, type RemoteExecutionEnv, type ToolResultStore, type SessionStore, type ExecutionEnv, type RunnerDeps } from "@sema-agent/core";
|
|
2
2
|
import type { Autonomy } from "../runtime-governance.js";
|
|
3
|
+
import { type CheckpointCtxSealer } from "../http/checkpoint-ctx.js";
|
|
3
4
|
import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
4
5
|
import { type LeaderDeps, type LeaderResult } from "./leader.js";
|
|
5
6
|
import { type DeploymentPostureSeats } from "../boot/runner-deps.js";
|
|
@@ -7,6 +8,9 @@ import type { LeaderRequestBody } from "./endpoint.js";
|
|
|
7
8
|
/** leader worker 的 durable 审批座(`LeaderWireConfig.durable` 函数席每次产出的一份)。 */
|
|
8
9
|
export interface LeaderDurableSeat {
|
|
9
10
|
readonly checkpointStore: CheckpointStoreFull;
|
|
11
|
+
/** S-1907:写 worker 续跑上下文的封口(`ServiceDeps.checkpointCtxSealing` 同一只;规则属主 `http/checkpoint-ctx.ts` 末段「凭据位密封」)。
|
|
12
|
+
* worker 的续跑体只有入口词 + 子任务 objective、不带凭据位 ⇒ 实际不封任何值,但写 ctx 的每一笔都经同一只封口(没有不封的第二种写法)。 */
|
|
13
|
+
readonly ctxSealer: CheckpointCtxSealer | undefined;
|
|
10
14
|
readonly requireApproval: string[];
|
|
11
15
|
readonly deny?: string[];
|
|
12
16
|
readonly ttlMs?: number;
|
package/dist/leader/wire.js
CHANGED
|
@@ -274,9 +274,10 @@ export function createLeaderRunner(cfg) {
|
|
|
274
274
|
const provisionWorker = async (sub) => {
|
|
275
275
|
const sessionId = `leader-${sub.workerId}-${Date.now()}`;
|
|
276
276
|
const durable = cfg.durable?.();
|
|
277
|
-
const
|
|
277
|
+
const resumeBody = { ...entry, objective: sub.spec.objective };
|
|
278
|
+
const storedResumeCtx = durable !== undefined ? buildCheckpointCtx({ body: resumeBody, unattendedLeg: true, writerVersion: serviceVersion() }, durable.ctxSealer, sessionId) : undefined;
|
|
278
279
|
let durableSpec = {};
|
|
279
|
-
if (durable) {
|
|
280
|
+
if (durable && storedResumeCtx !== undefined) {
|
|
280
281
|
const d = durable;
|
|
281
282
|
durableSpec = {
|
|
282
283
|
toolPolicy: combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({ requireApproval: d.requireApproval, ...(d.deny ? { deny: d.deny } : {}), ...(d.autoBudget ? { autoBudget: d.autoBudget } : {}), ...(d.neverAuto ? { neverAuto: d.neverAuto } : {}) })),
|
|
@@ -284,7 +285,7 @@ export function createLeaderRunner(cfg) {
|
|
|
284
285
|
durableApproval: { scope: "_leader", ...(d.ttlMs ? { ttlMs: d.ttlMs } : {}) },
|
|
285
286
|
onQuestion: QUESTION_AWAITS_RESUME,
|
|
286
287
|
};
|
|
287
|
-
await d.checkpointStore.putCtx(sessionId,
|
|
288
|
+
await d.checkpointStore.putCtx(sessionId, storedResumeCtx).catch((e) => {
|
|
288
289
|
cfg.logger?.warn?.("leader_putctx_failed", { workerId: sub.workerId, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
|
|
289
290
|
});
|
|
290
291
|
}
|
|
@@ -313,13 +314,13 @@ export function createLeaderRunner(cfg) {
|
|
|
313
314
|
}
|
|
314
315
|
const keepCtxWarm = (runner) => {
|
|
315
316
|
const d = durable;
|
|
316
|
-
if (!d)
|
|
317
|
+
if (!d || storedResumeCtx === undefined)
|
|
317
318
|
return runner;
|
|
318
319
|
const orig = runner.runTaskStream.bind(runner);
|
|
319
320
|
runner.runTaskStream = (spec, resume) => {
|
|
320
321
|
const stream = orig(spec, resume);
|
|
321
322
|
const iv = setInterval(() => {
|
|
322
|
-
void d.checkpointStore.putCtx(sessionId,
|
|
323
|
+
void d.checkpointStore.putCtx(sessionId, storedResumeCtx).catch(() => { });
|
|
323
324
|
}, 45_000);
|
|
324
325
|
iv.unref?.();
|
|
325
326
|
void stream.result().then(() => clearInterval(iv), () => clearInterval(iv));
|
|
@@ -359,7 +360,7 @@ export function createLeaderRunner(cfg) {
|
|
|
359
360
|
},
|
|
360
361
|
})),
|
|
361
362
|
fetchDiff: () => fetchUploadedDiff(getUrl),
|
|
362
|
-
spec: withLeaderLeg({ ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec(), objective: `${sub.spec.objective}${uploadStepSuffix(W)}` },
|
|
363
|
+
spec: withLeaderLeg({ ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec(), objective: `${sub.spec.objective}${uploadStepSuffix(W)}` }, resumeBody),
|
|
363
364
|
};
|
|
364
365
|
}
|
|
365
366
|
if (!cfg.e2bApiKey)
|
|
@@ -374,7 +375,7 @@ export function createLeaderRunner(cfg) {
|
|
|
374
375
|
runner: keepCtxWarm(new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...deploymentPostureSeat(), ...tracerSeat, ...usageWindowSeat, ...promptSourceSeat, ...onErrorSeat, ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}), ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}), executionEnv: env })),
|
|
375
376
|
diffEnv: env,
|
|
376
377
|
destroy: () => env.destroy().then(() => { }),
|
|
377
|
-
spec: withLeaderLeg({ ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec() },
|
|
378
|
+
spec: withLeaderLeg({ ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec, ...principalSpec() }, resumeBody),
|
|
378
379
|
};
|
|
379
380
|
};
|
|
380
381
|
const provisionIntegrationSandbox = async () => {
|