@sema-agent/server 7.151.0 → 7.152.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 +266 -0
- package/README.md +15 -3
- package/README.zh-CN.md +10 -2
- package/USAGE.md +93 -64
- package/deploy/sema-up/README.md +5 -4
- package/deploy/sema-up/cluster-up.sh +6 -6
- package/deploy/sema-up/kube-up.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +36 -8
- package/deploy/sema-up/machines.example.yaml +3 -1
- package/deploy/sema-up/sema-up.sh +22 -16
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-ask-machine.d.ts +4 -10
- package/dist/approval-ask-retention.d.ts +1 -1
- package/dist/approval-card.d.ts +10 -12
- package/dist/approval-gate-currency.d.ts +8 -8
- package/dist/approval-gate-currency.js +3 -4
- package/dist/approval-hmac.d.ts +17 -1
- package/dist/approval-hmac.js +7 -1
- package/dist/approval-queue-kind.d.ts +1 -1
- package/dist/approval.d.ts +2 -2
- package/dist/approval.js +2 -2
- package/dist/bind-host.d.ts +1 -3
- package/dist/bind-host.js +2 -3
- package/dist/boot/coordinators.d.ts +1 -1
- package/dist/boot/coordinators.js +9 -10
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/execution-env.d.ts +1 -0
- package/dist/boot/execution-env.js +31 -5
- package/dist/boot/leader.d.ts +31 -30
- package/dist/boot/leader.js +38 -28
- package/dist/boot/leg-path-adjudication.d.ts +40 -0
- package/dist/boot/leg-path-adjudication.js +18 -0
- package/dist/boot/parked-revive-gate.js +3 -15
- package/dist/boot/reapers.d.ts +1 -1
- package/dist/boot/reapers.js +1 -1
- package/dist/boot/resolve-spec.js +12 -26
- package/dist/boot/runner-deps.d.ts +39 -35
- package/dist/boot/runner-deps.js +92 -89
- package/dist/boot/shutdown.d.ts +6 -0
- package/dist/boot/shutdown.js +10 -4
- package/dist/boot/stage-02-web-admin-pull.js +4 -5
- package/dist/boot/stage-05-execution-env.js +2 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +10 -4
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +8 -0
- package/dist/boot/stage-10-http-server.js +11 -15
- package/dist/boot/stores.js +7 -4
- package/dist/boot/web-admin.js +2 -7
- package/dist/capabilities/approvals-push.d.ts +3 -3
- package/dist/capabilities/scenarios.d.ts +28 -13
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capability-facts.d.ts +22 -20
- package/dist/capability-facts.js +18 -34
- package/dist/card-rule-offers.d.ts +32 -0
- package/dist/card-rule-offers.js +130 -0
- package/dist/config-catalog.js +12 -11
- package/dist/config-dir-trust.js +4 -2
- package/dist/config-invariants.d.ts +18 -7
- package/dist/config-invariants.js +51 -8
- package/dist/config-lkg.d.ts +5 -19
- package/dist/config-lkg.js +8 -62
- package/dist/config-provider.d.ts +1 -1
- package/dist/config-root-migration.d.ts +2 -2
- package/dist/config-types.d.ts +28 -38
- package/dist/config.d.ts +7 -5
- package/dist/config.js +55 -16
- package/dist/control-socket.d.ts +51 -0
- package/dist/control-socket.js +115 -0
- package/dist/deployment-governance.d.ts +2 -1
- package/dist/device-ws-hub.d.ts +7 -0
- package/dist/device-ws-hub.js +7 -1
- package/dist/elicitation.js +2 -2
- package/dist/entry-problems.d.ts +40 -3
- package/dist/entry-problems.js +3 -1
- package/dist/execution-env-refusal.d.ts +8 -2
- package/dist/execution-env-refusal.js +3 -0
- package/dist/fleet/engine-signal.d.ts +12 -29
- package/dist/fleet/engine-signal.js +1 -7
- package/dist/fleet-client.js +2 -5
- package/dist/gate-settlement.d.ts +1 -8
- package/dist/gate-settlement.js +0 -4
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +2 -3
- package/dist/hitl-delivery.js +0 -2
- package/dist/hooks/hook-llm.js +4 -2
- package/dist/hooks/hook-runner.d.ts +3 -2
- package/dist/hooks/hook-runner.js +16 -1
- package/dist/host-decision.d.ts +11 -11
- package/dist/http/active-run-conflict.d.ts +35 -2
- package/dist/http/active-run-conflict.js +48 -19
- package/dist/http/approval-sweeps.d.ts +1 -1
- package/dist/http/approval-sweeps.js +5 -2
- package/dist/http/checkpoint-ctx.d.ts +2 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/dispatch.d.ts +6 -2
- package/dist/http/dispatch.js +25 -5
- package/dist/http/durable-run-leg.js +0 -1
- package/dist/http/health-reader.d.ts +16 -9
- package/dist/http/health-reader.js +2 -1
- package/dist/http/health-self-report.js +1 -1
- package/dist/http/owner-access.d.ts +2 -3
- package/dist/http/owner-access.js +3 -4
- package/dist/http/principal-gate.d.ts +49 -21
- package/dist/http/principal-gate.js +61 -11
- package/dist/http/request-channel.d.ts +23 -0
- package/dist/http/request-channel.js +9 -0
- package/dist/http/request-guards.js +17 -2
- package/dist/http/resume-ctx-read.js +20 -3
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +111 -41
- package/dist/http/route-ctx.d.ts +73 -7
- package/dist/http/route-ctx.js +17 -0
- package/dist/http/route-table.d.ts +4 -0
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/admin-config-refresh.js +3 -3
- package/dist/http/routes/admin-drain.d.ts +3 -0
- package/dist/http/routes/admin-drain.js +7 -7
- package/dist/http/routes/admin-memory-optout.js +3 -3
- package/dist/http/routes/adoption.js +3 -5
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +95 -48
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +30 -5
- package/dist/http/routes/config-catalog.js +2 -2
- package/dist/http/routes/devices.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +19 -7
- package/dist/http/routes/images.js +10 -10
- package/dist/http/routes/memory-bundle.js +3 -3
- package/dist/http/routes/memory-compliance.js +3 -3
- package/dist/http/routes/memory-consolidation.js +2 -2
- package/dist/http/routes/memory-origin.js +3 -3
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/observability.js +3 -3
- package/dist/http/routes/retention-ops.js +3 -3
- package/dist/http/routes/rules.js +5 -5
- package/dist/http/routes/run-permission-mode.d.ts +2 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +4 -9
- package/dist/http/routes/runs.js +73 -33
- package/dist/http/routes/session-sync.d.ts +18 -6
- package/dist/http/routes/session-sync.js +38 -36
- package/dist/http/routes/sessions.js +20 -9
- package/dist/http/routes/tasks.js +28 -37
- package/dist/http/routes/workflows.js +1 -1
- package/dist/http/server.d.ts +9 -3
- package/dist/http/server.js +15 -3
- package/dist/http/submit-ctx.d.ts +1 -1
- package/dist/http/submit-ctx.js +2 -0
- package/dist/human-facing-args.d.ts +33 -0
- package/dist/human-facing-args.js +33 -0
- package/dist/index.d.ts +1 -1
- package/dist/k8s-api-credentials.d.ts +29 -0
- package/dist/k8s-api-credentials.js +25 -0
- package/dist/lane-home-probe.d.ts +1 -1
- package/dist/lane-home-probe.js +2 -1
- package/dist/leader/diffup.d.ts +19 -0
- package/dist/leader/diffup.js +7 -0
- package/dist/leader/leader.d.ts +114 -10
- package/dist/leader/leader.js +69 -5
- package/dist/leader/repair-wire.js +2 -2
- package/dist/leader/run-entry.d.ts +2 -1
- package/dist/leader/run-entry.js +1 -1
- package/dist/leader/wire.d.ts +42 -132
- package/dist/leader/wire.js +81 -67
- package/dist/memory-scope.d.ts +20 -9
- package/dist/memory-scope.js +15 -3
- package/dist/model-entry-headers.d.ts +9 -10
- package/dist/model-entry-headers.js +0 -4
- package/dist/model-entry-refusal.d.ts +7 -4
- package/dist/model-entry-refusal.js +11 -5
- package/dist/model-plane-face.d.ts +1 -1
- package/dist/model-plane-face.js +3 -1
- package/dist/model-route-endpoint.d.ts +13 -7
- package/dist/model-route-endpoint.js +14 -11
- package/dist/model-select.d.ts +22 -0
- package/dist/model-select.js +9 -0
- package/dist/model-unreachable-notice.d.ts +11 -0
- package/dist/model-unreachable-notice.js +4 -4
- package/dist/object-store-buckets.d.ts +640 -6
- package/dist/object-store-buckets.js +166 -0
- package/dist/observability/fail-open.d.ts +20 -16
- package/dist/observability/fail-open.js +20 -16
- package/dist/observability/logger.js +3 -1
- package/dist/operator-roster.d.ts +68 -0
- package/dist/operator-roster.js +32 -0
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/outbound-dispatcher.d.ts +71 -8
- package/dist/outbound-dispatcher.js +48 -7
- package/dist/owner-faces.d.ts +1 -1
- package/dist/parked-decide.d.ts +2 -2
- package/dist/permission-mode-admission.d.ts +59 -26
- package/dist/permission-mode-admission.js +37 -10
- package/dist/plugins/approval-ask-store-file.d.ts +4 -0
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
- package/dist/plugins/approval-ask-store-sql.js +12 -1
- package/dist/plugins/blob-backend.d.ts +83 -7
- package/dist/plugins/blob-backend.js +57 -6
- package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
- package/dist/plugins/checkpoint-index-columns.js +3 -6
- package/dist/plugins/checkpoint-store-face.d.ts +119 -61
- package/dist/plugins/checkpoint-store-face.js +134 -55
- package/dist/plugins/landed-attendants.d.ts +2 -2
- package/dist/plugins/local-checkpoint-store.d.ts +13 -3
- package/dist/plugins/local-checkpoint-store.js +38 -49
- package/dist/plugins/one-time-migrations.d.ts +51 -21
- package/dist/plugins/one-time-migrations.js +67 -45
- package/dist/plugins/pg-pool.js +11 -9
- package/dist/plugins/remote-env-host.d.ts +29 -10
- package/dist/plugins/remote-env-host.js +99 -19
- package/dist/plugins/remote-env-k8s.d.ts +68 -2
- package/dist/plugins/remote-env-k8s.js +107 -24
- package/dist/plugins/replica-registration.d.ts +1 -1
- package/dist/plugins/replica-registration.js +1 -1
- package/dist/plugins/s3-presign.d.ts +23 -0
- package/dist/plugins/s3-presign.js +37 -3
- package/dist/plugins/scheduler-support.d.ts +2 -2
- package/dist/plugins/scheduler-support.js +0 -3
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-file-ledger.d.ts +2 -2
- package/dist/plugins/send-file-ledger.js +2 -2
- package/dist/plugins/send-user-file.d.ts +3 -4
- package/dist/plugins/send-user-file.js +56 -52
- package/dist/plugins/session-blob-claim-token.d.ts +15 -0
- package/dist/plugins/session-blob-claim-token.js +20 -0
- package/dist/plugins/session-blob-namespace.d.ts +46 -0
- package/dist/plugins/session-blob-namespace.js +116 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
- package/dist/plugins/snapshot-bucket-presence.js +8 -2
- package/dist/plugins/sql-json-column.d.ts +1 -1
- package/dist/plugins/store-backend.d.ts +44 -2
- package/dist/plugins/store-backend.js +75 -53
- package/dist/plugins/task-attachment-store.d.ts +2 -1
- package/dist/plugins/task-attachment-store.js +5 -1
- package/dist/plugins/tidb-pool.js +19 -24
- package/dist/plugins/workspace-custody.d.ts +4 -3
- package/dist/question.d.ts +2 -1
- package/dist/question.js +5 -13
- package/dist/request-form.d.ts +6 -1
- package/dist/request-form.js +4 -1
- package/dist/rule-issue-reading.d.ts +1 -1
- package/dist/rules-consent.d.ts +1 -1
- package/dist/run-local.js +8 -9
- package/dist/runs.d.ts +17 -8
- package/dist/runs.js +15 -2
- package/dist/runtime-governance.d.ts +2 -2
- package/dist/sema-config-dir.d.ts +2 -1
- package/dist/session-sync.d.ts +27 -11
- package/dist/session-sync.js +15 -7
- package/dist/stores-sql/blob-index-sql.d.ts +30 -9
- package/dist/stores-sql/blob-index-sql.js +40 -21
- package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
- package/dist/stores-sql/checkpoint-store-sql.js +70 -146
- package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
- package/dist/stores-sql/file-history-store-sql.js +100 -28
- package/dist/stores-sql/retention-store-sql.js +3 -9
- package/dist/task-settings.d.ts +6 -5
- package/dist/task-settings.js +3 -3
- package/dist/tool-approval.d.ts +342 -518
- package/dist/tool-approval.js +498 -1207
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/ledger-events.d.ts +3 -3
- package/dist/trace/project.d.ts +2 -3
- package/dist/trace/redact.js +3 -2
- package/dist/trace/self-minted-wire.d.ts +287 -157
- package/dist/trace/self-minted-wire.js +93 -48
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/read-warnings.js +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/dist/workspace-space.d.ts +10 -4
- package/dist/workspace-space.js +4 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
- package/package.json +6 -2
- package/scripts/embed-minimal.mjs +3 -3
- package/wire-changes/server@7.152.0.json +900 -0
- package/dist/config-d-migration.d.ts +0 -37
- package/dist/config-d-migration.js +0 -60
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const REQUEST_CHANNELS = ["tcp", "control-socket"];
|
|
2
|
+
const controlSocketRequests = new WeakSet();
|
|
3
|
+
export function markControlSocketRequest(req) {
|
|
4
|
+
controlSocketRequests.add(req);
|
|
5
|
+
}
|
|
6
|
+
export function requestChannelOf(req) {
|
|
7
|
+
return controlSocketRequests.has(req) ? "control-socket" : "tcp";
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=request-channel.js.map
|
|
@@ -9,6 +9,7 @@ import { escapedStatementBytes, packetWallStatementBytes, rowWallPayloadBytes }
|
|
|
9
9
|
import { MAX_JSON_BODY_BYTES } from "../json-body-limit.js";
|
|
10
10
|
import { deliverVia } from "../submission-exit.js";
|
|
11
11
|
import { canonicalAddress } from "../net-address.js";
|
|
12
|
+
import { requestChannelOf } from "./request-channel.js";
|
|
12
13
|
export function refuseSubmitBody(walls, body) {
|
|
13
14
|
const rowWall = walls.maxRowBytes;
|
|
14
15
|
const packetWall = walls.maxPacketBytes;
|
|
@@ -171,8 +172,7 @@ export function createRequestGuards(ctx) {
|
|
|
171
172
|
function rateLimited(req, res) {
|
|
172
173
|
if (!deps.rateLimiter)
|
|
173
174
|
return false;
|
|
174
|
-
const
|
|
175
|
-
const key = (deps.config.directDoorActive ? undefined : principalFrom(req, deps.config)) ?? (peer === undefined ? "anon" : (canonicalAddress(peer) ?? peer));
|
|
175
|
+
const key = (deps.config.directDoorActive ? undefined : principalFrom(req, deps.config)) ?? rateLimitAddressKeyOf(req);
|
|
176
176
|
const d = deps.rateLimiter.check(key);
|
|
177
177
|
if (d.allowed)
|
|
178
178
|
return false;
|
|
@@ -220,4 +220,19 @@ export function createRequestGuards(ctx) {
|
|
|
220
220
|
}
|
|
221
221
|
return { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode, readJson, readRawBody };
|
|
222
222
|
}
|
|
223
|
+
function rateLimitAddressKeyOf(req) {
|
|
224
|
+
const channel = requestChannelOf(req);
|
|
225
|
+
switch (channel) {
|
|
226
|
+
case "tcp": {
|
|
227
|
+
const peer = req.socket.remoteAddress;
|
|
228
|
+
return peer === undefined ? "anon" : (canonicalAddress(peer) ?? peer);
|
|
229
|
+
}
|
|
230
|
+
case "control-socket":
|
|
231
|
+
return "control-socket";
|
|
232
|
+
default: {
|
|
233
|
+
const unknownChannel = channel;
|
|
234
|
+
throw new Error(`rate limit: request channel ${String(unknownChannel)} has no address key`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
223
238
|
//# sourceMappingURL=request-guards.js.map
|
|
@@ -3,6 +3,7 @@ import { storeFaultClassOf } from "../plugins/store-fault.js";
|
|
|
3
3
|
import { refusalBody } from "./refusal-body.js";
|
|
4
4
|
import { frozenProjectContextOf } from "../project-context.js";
|
|
5
5
|
import { frozenMemorySeedOf } from "../project-registry.js";
|
|
6
|
+
import { permissionModeWriteOf } from "../permission-mode-admission.js";
|
|
6
7
|
export async function readResumeCtx(load, opening) {
|
|
7
8
|
const read = await readStoredCtx(load);
|
|
8
9
|
if (read.kind !== "ok")
|
|
@@ -11,9 +12,10 @@ export async function readResumeCtx(load, opening) {
|
|
|
11
12
|
if (opened.kind !== "ok")
|
|
12
13
|
return { kind: "unsealable", field: opened.field, reason: opened.reason };
|
|
13
14
|
const where = (key) => ({ kind: "corrupt", where: `checkpoint ctx for session ${JSON.stringify(opening.sessionId)}: ${key}` });
|
|
14
|
-
const { memorySeed: _storedSeed, projectContext: _storedContext, ...rest } = opened.ctx;
|
|
15
|
+
const { memorySeed: _storedSeed, projectContext: _storedContext, permissionModeWrite: _storedWrite, ...rest } = opened.ctx;
|
|
15
16
|
const seed = frozenMemorySeedOf(opened.ctx.memorySeed);
|
|
16
17
|
const frozen = frozenProjectContextOf(opened.ctx.projectContext);
|
|
18
|
+
const written = permissionModeWriteOf(opened.ctx.permissionModeWrite);
|
|
17
19
|
let seedPart;
|
|
18
20
|
switch (seed.kind) {
|
|
19
21
|
case "absent":
|
|
@@ -32,18 +34,33 @@ export async function readResumeCtx(load, opening) {
|
|
|
32
34
|
throw new Error(`resume-ctx-read: unplaced frozen memory seed reading ${JSON.stringify(unhandled)}`);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
37
|
+
let contextPart;
|
|
35
38
|
switch (frozen.kind) {
|
|
36
39
|
case "absent":
|
|
37
|
-
|
|
40
|
+
contextPart = {};
|
|
41
|
+
break;
|
|
38
42
|
case "malformed":
|
|
39
43
|
return where("projectContext");
|
|
40
44
|
case "ok":
|
|
41
|
-
|
|
45
|
+
contextPart = { projectContext: frozen.context };
|
|
46
|
+
break;
|
|
42
47
|
default: {
|
|
43
48
|
const unhandled = frozen;
|
|
44
49
|
throw new Error(`resume-ctx-read: unplaced frozen project context reading ${JSON.stringify(unhandled)}`);
|
|
45
50
|
}
|
|
46
51
|
}
|
|
52
|
+
switch (written.kind) {
|
|
53
|
+
case "absent":
|
|
54
|
+
return { kind: "ok", ctx: { ...rest, ...seedPart, ...contextPart } };
|
|
55
|
+
case "malformed":
|
|
56
|
+
return where("permissionModeWrite");
|
|
57
|
+
case "ok":
|
|
58
|
+
return { kind: "ok", ctx: { ...rest, ...seedPart, ...contextPart, permissionModeWrite: written.write } };
|
|
59
|
+
default: {
|
|
60
|
+
const unhandled = written;
|
|
61
|
+
throw new Error(`resume-ctx-read: unplaced permission-mode write reading ${JSON.stringify(unhandled)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
47
64
|
}
|
|
48
65
|
async function readStoredCtx(load) {
|
|
49
66
|
let stored;
|
|
@@ -21,7 +21,7 @@ import type { HostDecision } from "../host-decision.js";
|
|
|
21
21
|
import { type GateSighting } from "../approval-gate-currency.js";
|
|
22
22
|
import { type CancelRunReader } from "../run-cancel-card.js";
|
|
23
23
|
import { type PlanApprovePermissionModeAfter } from "../task-settings.js";
|
|
24
|
-
import type { RouteCtxBase, DriveResumeArgs, PreparedPlanReview, RememberSessionRequest, ResumeCheckpointOutcome, ResumeLegOutcome, WorkflowDecisionDelivery } from "./route-ctx.js";
|
|
24
|
+
import type { RouteCtxBase, DriveResumeArgs, PersistRuleRequest, PreparedPlanReview, RememberSessionRequest, ResumeCheckpointOutcome, ResumeLegOutcome, WorkflowDecisionDelivery } from "./route-ctx.js";
|
|
25
25
|
import type { RunRegistry, RouteHelpers, RouteLegs } from "./route-ctx.js";
|
|
26
26
|
import type { createRequestGuards } from "./request-guards.js";
|
|
27
27
|
import type { FlatServiceDeps } from "./server.js";
|
|
@@ -121,7 +121,7 @@ export declare function createResumeLegs(ctx: {
|
|
|
121
121
|
runnerFor: FlatServiceDeps["runnerFor"];
|
|
122
122
|
} & RunRegistry & RouteHelpers & Pick<RouteLegs, "prepareSpec" | "finalizeTaskResult"> & Pick<ReturnType<typeof createRequestGuards>, "ownerCostQuotaDenied" | "ownerFleetLeaseDenied" | "usageWindowDenialFor">): {
|
|
123
123
|
routeCtxBase: RouteCtxBase;
|
|
124
|
-
resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, answer: QuestionAnswer | undefined, binding: DecideBinding, remember?: RememberSessionRequest, acceptEarly?: boolean, decider?: KeyCaller, restatement?: ResumeRestatement) => Promise<ResumeCheckpointOutcome>;
|
|
124
|
+
resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, answer: QuestionAnswer | undefined, binding: DecideBinding, remember?: RememberSessionRequest, acceptEarly?: boolean, decider?: KeyCaller, restatement?: ResumeRestatement, persistRule?: PersistRuleRequest) => Promise<ResumeCheckpointOutcome>;
|
|
125
125
|
driveResumeIntoRunLog: (args: DriveResumeArgs) => Promise<ResumeLegOutcome>;
|
|
126
126
|
driveResumeLeg: (args: DriveResumeArgs, onAccepted?: (receipt: {
|
|
127
127
|
status: number;
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -8,7 +8,7 @@ import { isAdoptionQuiescing, trackMaintenanceLeg } from "../adoption/quiesce.js
|
|
|
8
8
|
import { runFactsOf, sealRouteDeps, sessionFactsReadOf, sessionOccupantReadOf } from "./owner-access.js";
|
|
9
9
|
import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
|
|
10
10
|
const DELIVERY_NOT_RECORDED = "workflow park decide: this delivery no longer holds the host session (its claim was released) — the decision was not recorded and this delivery was not started";
|
|
11
|
-
import { HttpError, checkpointPartsOf, decodeCheckpointScope, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
11
|
+
import { HttpError, checkpointPartsOf, decodeCheckpointScope, ownerOfCheckpointScope, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
12
12
|
import { keyAdmits } from "../owner-faces.js";
|
|
13
13
|
import { personOf } from "../owner-brands.js";
|
|
14
14
|
import { recoveredSubject } from "../owner-faces.js";
|
|
@@ -24,7 +24,7 @@ import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotific
|
|
|
24
24
|
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../fleet/fleet-bus.js";
|
|
25
25
|
import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
|
|
26
26
|
import { currentApprovalGateToken, currentGateFacesOf, sightsCurrentGate } from "../approval-gate-currency.js";
|
|
27
|
-
import { approverFacingArgs, declaredApprovalFrameFamilies, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
|
|
27
|
+
import { approverFacingArgs, declaredApprovalFrameFamilies, parkRowStandingAnswerFacts, readCardRuleRecordOfAskRow, readCardRuleRecordOfParkRow, redeemCardRule, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
|
|
28
28
|
import { createHitlTransport } from "../hitl-delivery.js";
|
|
29
29
|
import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
|
|
30
30
|
import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
|
|
@@ -72,7 +72,7 @@ import { buildCheckpointVersionKeys, resumeAssemblyRefusalAnswer, resumeRefusalW
|
|
|
72
72
|
import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
|
|
73
73
|
import { storedBodyReplayRefusal } from "./stored-body-replay.js";
|
|
74
74
|
import { restateResumeCtx } from "./resume-restatement.js";
|
|
75
|
-
import { stampReplayEntryMode } from "../permission-mode-admission.js";
|
|
75
|
+
import { stampReplayEntryMode, withStoredPermissionMode } from "../permission-mode-admission.js";
|
|
76
76
|
import { httpErrorRefusal } from "./send.js";
|
|
77
77
|
import { createRunAcceptance } from "../run-acceptance.js";
|
|
78
78
|
import { createSubmissionSeat } from "../submission-exit.js";
|
|
@@ -219,6 +219,35 @@ function decisionRefusalCauseOf(e, gate) {
|
|
|
219
219
|
function notResumableAnswer() {
|
|
220
220
|
return { status: 409, body: refusalBody({ errorCode: "conflict.not_resumable", message: "run is not in a resumable (suspended) state (already resumed, decided, or expired)" }) };
|
|
221
221
|
}
|
|
222
|
+
function planeOfEndedRow(row) {
|
|
223
|
+
return { status: row?.status ?? "failed", errorCode: row?.errorCode ?? undefined, errorMessage: row?.error ?? undefined };
|
|
224
|
+
}
|
|
225
|
+
async function landedTerminalRow(runStore, taskId, arm) {
|
|
226
|
+
let row;
|
|
227
|
+
try {
|
|
228
|
+
row = await runStore.getRun(taskId);
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
recordFailOpen("server.resume.cancel-answer-row-unreadable", `task=${taskId} arm=${arm} err=${errorText(e)}`);
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
return row !== undefined && isTerminalRunStatus(row.status) ? row : undefined;
|
|
235
|
+
}
|
|
236
|
+
function settledLegAnswer(o) {
|
|
237
|
+
return {
|
|
238
|
+
status: 200,
|
|
239
|
+
body: {
|
|
240
|
+
...(o.taskId !== undefined ? { taskId: o.taskId } : {}),
|
|
241
|
+
sessionId: o.sessionId,
|
|
242
|
+
status: o.plane.status,
|
|
243
|
+
bindingEnforced: true,
|
|
244
|
+
...(o.plane.errorCode ? { errorCode: o.plane.errorCode } : {}),
|
|
245
|
+
...(o.plane.errorMessage ? { errorMessage: redactErrorMessage(o.plane.errorMessage) ?? "" } : {}),
|
|
246
|
+
...(o.reparked ? { retriable: true } : {}),
|
|
247
|
+
...o.evidence,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
222
251
|
function resumeClaimFaultAnswerOf(e) {
|
|
223
252
|
return faultedAnswerOf(e, CONTINUATION_CLAIM_FACE.faulted);
|
|
224
253
|
}
|
|
@@ -279,7 +308,7 @@ function acceptableFormFacts(detail) {
|
|
|
279
308
|
const rewritten = detail !== null && typeof detail === "object" ? detail.editRewritten : undefined;
|
|
280
309
|
if (rewritten === null || typeof rewritten !== "object" || !Object.hasOwn(rewritten, "acceptableForm"))
|
|
281
310
|
return {};
|
|
282
|
-
const shown = approverFacingArgs(rewritten.acceptableForm);
|
|
311
|
+
const shown = approverFacingArgs(rewritten.acceptableForm, undefined);
|
|
283
312
|
return shown === undefined ? {} : { acceptableForm: shown };
|
|
284
313
|
}
|
|
285
314
|
export function checkpointRefusalAnswer(e, at) {
|
|
@@ -315,8 +344,7 @@ export function createResumeLegs(ctx) {
|
|
|
315
344
|
const restated = restateResumeCtx(stored, restatement);
|
|
316
345
|
if (restated.unattendedLeg === true)
|
|
317
346
|
return restated;
|
|
318
|
-
|
|
319
|
-
return body === restated.body ? restated : { ...restated, body };
|
|
347
|
+
return stampReplayEntryMode(restated, deps.config);
|
|
320
348
|
};
|
|
321
349
|
const resumeFleetScope = (req, auth) => req ? (gatedSubject(req, deps.config) ?? "default") : (auth.subject ?? "default");
|
|
322
350
|
function createRememberIntent(sessionId, remember) {
|
|
@@ -343,9 +371,23 @@ export function createResumeLegs(ctx) {
|
|
|
343
371
|
},
|
|
344
372
|
};
|
|
345
373
|
}
|
|
346
|
-
|
|
374
|
+
function createRuleIntent(persistRule) {
|
|
375
|
+
let reading;
|
|
376
|
+
return {
|
|
377
|
+
record: (r) => {
|
|
378
|
+
reading = r;
|
|
379
|
+
},
|
|
380
|
+
settle: async (committed) => {
|
|
381
|
+
if (!committed || reading === undefined)
|
|
382
|
+
return { rulePersisted: false, ruleRefusal: "rule_lane_unavailable" };
|
|
383
|
+
return redeemCardRule(deps.ruleConsent, reading, persistRule.slot, { inputWasEdited: persistRule.inputWasEdited, eligible: true });
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, remember, acceptEarly, decider, restatement, persistRule) {
|
|
347
388
|
const cs = deps.checkpointStore;
|
|
348
389
|
const intent = remember !== undefined ? createRememberIntent(sessionId, remember) : undefined;
|
|
390
|
+
const ruleIntent = persistRule !== undefined ? createRuleIntent(persistRule) : undefined;
|
|
349
391
|
const token = await currentApprovalGateToken(cs, sessionId);
|
|
350
392
|
if (binding.checkpointToken && !sightsCurrentGate({ face: "checkpointToken", value: binding.checkpointToken }, token ? { checkpointToken: token } : null)) {
|
|
351
393
|
let seenUnreadable = false;
|
|
@@ -476,12 +518,36 @@ export function createResumeLegs(ctx) {
|
|
|
476
518
|
return approvalStaleAnswer(s57CurrentPending ? { currentPending: s57CurrentPending } : {});
|
|
477
519
|
}
|
|
478
520
|
const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
|
|
521
|
+
const parkFacts = paForGrant !== undefined && paForGrant.toolName ? parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config, deploymentListAsks(deps.config, { toolName: paForGrant.toolName })) : undefined;
|
|
479
522
|
const rememberableTool = paForGrant !== undefined &&
|
|
480
|
-
|
|
523
|
+
parkFacts !== undefined &&
|
|
481
524
|
paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
482
|
-
standingAnswerDurablyRecordable(
|
|
525
|
+
standingAnswerDurablyRecordable(parkFacts)
|
|
483
526
|
? paForGrant.toolName
|
|
484
527
|
: null;
|
|
528
|
+
if (ruleIntent !== undefined) {
|
|
529
|
+
const owner = ownerOfCheckpointScope(cp.scope);
|
|
530
|
+
if (paForGrant === undefined || parkFacts === undefined || !token || !paForGrant.toolCallId || !paForGrant.boundInputHash)
|
|
531
|
+
ruleIntent.record({ kind: "absent", refusal: "rule_material_absent" });
|
|
532
|
+
else {
|
|
533
|
+
const asked = deps.toolApproval !== undefined ? await deps.toolApproval.readParkedAskOfGate(cp.sessionId, { boundCallId: paForGrant.toolCallId, boundInputHash: paForGrant.boundInputHash }) : { kind: "none" };
|
|
534
|
+
switch (asked.kind) {
|
|
535
|
+
case "row":
|
|
536
|
+
ruleIntent.record(readCardRuleRecordOfAskRow(asked.row, owner));
|
|
537
|
+
break;
|
|
538
|
+
case "unreadable":
|
|
539
|
+
ruleIntent.record({ kind: "absent", refusal: "rule_store_error" });
|
|
540
|
+
break;
|
|
541
|
+
case "none":
|
|
542
|
+
ruleIntent.record(readCardRuleRecordOfParkRow(paForGrant, { owner, governanceForced: parkFacts.governanceForced, scopeRoot: await cs.cardScopeRootOf(token) }));
|
|
543
|
+
break;
|
|
544
|
+
default: {
|
|
545
|
+
const unhandled = asked;
|
|
546
|
+
throw new Error(`resumeCheckpoint: unhandled parked-ask reading ${JSON.stringify(unhandled)}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
485
551
|
const boundGrant = intent
|
|
486
552
|
? (overrideSessionId) => intent.record({ ...(overrideSessionId !== undefined ? { overrideSessionId } : {}), rememberableToolName: rememberableTool })
|
|
487
553
|
: undefined;
|
|
@@ -506,7 +572,11 @@ export function createResumeLegs(ctx) {
|
|
|
506
572
|
const landAfterCommit = async (out) => {
|
|
507
573
|
const committed = decisionCommittedOf(out);
|
|
508
574
|
noteLateSettleIfAccepted(committed);
|
|
509
|
-
return
|
|
575
|
+
return {
|
|
576
|
+
...out,
|
|
577
|
+
...(intent !== undefined ? { rememberApplied: await intent.settle(committed) } : {}),
|
|
578
|
+
...(ruleIntent !== undefined ? { ruleReceipt: await ruleIntent.settle(committed) } : {}),
|
|
579
|
+
};
|
|
510
580
|
};
|
|
511
581
|
const parkedForkQuotaDenial = req !== undefined ? ownerCostQuotaDenied(checkpointPartsOf(cp.scope)?.person) : undefined;
|
|
512
582
|
if (parkedForkQuotaDenial)
|
|
@@ -771,7 +841,6 @@ export function createResumeLegs(ctx) {
|
|
|
771
841
|
const legKey = createHash("sha256").update(String(token)).digest("hex");
|
|
772
842
|
const streamApprovalOn = resolveStreamApprovalGate({
|
|
773
843
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
774
|
-
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
775
844
|
backend: deps.backend,
|
|
776
845
|
checkpointStore: deps.checkpointStore,
|
|
777
846
|
}).active;
|
|
@@ -797,7 +866,7 @@ export function createResumeLegs(ctx) {
|
|
|
797
866
|
let legClaim;
|
|
798
867
|
let engineHandedOff = false;
|
|
799
868
|
let restored;
|
|
800
|
-
const
|
|
869
|
+
const claimWindowCancelRow = () => (restored?.kind === "ended" && restored.row?.errorCode === CANCELLED_CODE ? restored.row : undefined);
|
|
801
870
|
if (args.mintFreshRun && taskId && deps.runStore) {
|
|
802
871
|
return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, turnActivity: readTurnActivityMs }, sessionId, taskId) };
|
|
803
872
|
}
|
|
@@ -948,7 +1017,8 @@ export function createResumeLegs(ctx) {
|
|
|
948
1017
|
if (placementReject) {
|
|
949
1018
|
if (inflightRuns.get(claimedTask) === cancelCtrl)
|
|
950
1019
|
inflightRuns.delete(claimedTask);
|
|
951
|
-
|
|
1020
|
+
const cancelledRow = claimWindowCancelRow();
|
|
1021
|
+
return cancelledRow !== undefined ? settledLegAnswer({ taskId: claimedTask, sessionId, plane: planeOfEndedRow(cancelledRow), reparked: false, evidence: NO_SETTLEMENT_EVIDENCE }) : placementReject;
|
|
952
1022
|
}
|
|
953
1023
|
}
|
|
954
1024
|
markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, subject);
|
|
@@ -1433,16 +1503,19 @@ export function createResumeLegs(ctx) {
|
|
|
1433
1503
|
const disposition = resumeRefusalDisposition(e);
|
|
1434
1504
|
if (disposition === "cancelled") {
|
|
1435
1505
|
const abortedText = (await settleCardForCancel()) ? CARD_SETTLED_BY_CANCEL_MESSAGE : e.message;
|
|
1506
|
+
const handleCause = cancelCtrl.cause();
|
|
1507
|
+
if (handleCause === undefined)
|
|
1508
|
+
recordFailOpen("server.runs.abort-cause-missing", `task=${taskId ?? ""} arm=resume-aborted`);
|
|
1509
|
+
const c = mintCancelledResult({ taskId: taskId ?? "", sessionId, startedAtMs: resumeLegStartedAt, message: abortedText, cause: handleCause ?? "cancel_verb" });
|
|
1510
|
+
const cancelled = outwardPlane(c.terminal);
|
|
1511
|
+
let landed;
|
|
1436
1512
|
if (claimedRow && taskId && deps.runStore) {
|
|
1437
|
-
const handleCause = cancelCtrl.cause();
|
|
1438
|
-
if (handleCause === undefined)
|
|
1439
|
-
recordFailOpen("server.runs.abort-cause-missing", `task=${taskId} arm=resume-aborted`);
|
|
1440
|
-
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: abortedText, cause: handleCause ?? "cancel_verb" });
|
|
1441
1513
|
await cancelLedgerAppend?.("failed", buildCancelledLedgerRow(c)).catch((err) => recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=resume-append-resume-aborted ${errorText(err)}`));
|
|
1442
|
-
await deps.runStore.setTerminal(taskId, "failed", c,
|
|
1514
|
+
await deps.runStore.setTerminal(taskId, "failed", c, cancelled.errorMessage ?? null).catch(() => undefined);
|
|
1515
|
+
landed = await landedTerminalRow(deps.runStore, taskId, "resume-aborted");
|
|
1443
1516
|
}
|
|
1444
1517
|
settleFleet("failed");
|
|
1445
|
-
return {
|
|
1518
|
+
return settledLegAnswer({ taskId, sessionId, plane: landed !== undefined ? planeOfEndedRow(landed) : cancelled, reparked: false, evidence: NO_SETTLEMENT_EVIDENCE });
|
|
1446
1519
|
}
|
|
1447
1520
|
const retriable = disposition === "retriable";
|
|
1448
1521
|
let reparkEnded = false;
|
|
@@ -1469,22 +1542,26 @@ export function createResumeLegs(ctx) {
|
|
|
1469
1542
|
}
|
|
1470
1543
|
const thrownCause = cancelCtrl.cause();
|
|
1471
1544
|
if (thrownCause !== undefined) {
|
|
1545
|
+
const c = mintCancelledResult({ taskId: taskId ?? "", sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user", cause: thrownCause });
|
|
1546
|
+
const cancelled = outwardPlane(c.terminal);
|
|
1547
|
+
let landed;
|
|
1472
1548
|
if (taskId && deps.runStore) {
|
|
1473
1549
|
await voidHeldParkCard(heldPark, sessionId, deps.checkpointStore, "resume");
|
|
1474
|
-
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user", cause: thrownCause });
|
|
1475
1550
|
await cancelLedgerAppend?.("failed", buildCancelledLedgerRow(c)).catch((err) => recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=resume-append-thrown-cancel ${errorText(err)}`));
|
|
1476
|
-
await deps.runStore.setTerminal(taskId, "failed", c,
|
|
1551
|
+
await deps.runStore.setTerminal(taskId, "failed", c, cancelled.errorMessage ?? null).catch(() => undefined);
|
|
1552
|
+
landed = await landedTerminalRow(deps.runStore, taskId, "thrown-cancel");
|
|
1477
1553
|
}
|
|
1478
1554
|
settleFleet("failed");
|
|
1479
|
-
return {
|
|
1555
|
+
return settledLegAnswer({ taskId, sessionId, plane: landed !== undefined ? planeOfEndedRow(landed) : cancelled, reparked: false, evidence: NO_SETTLEMENT_EVIDENCE });
|
|
1480
1556
|
}
|
|
1481
1557
|
if (!engineHandedOff && legClaim !== undefined) {
|
|
1482
1558
|
const claim = legClaim;
|
|
1483
1559
|
await withdrawClaim(deps, claim, `the resume could not be handed to the engine: ${outwardThrownMessage(e, errorText(e))}`, "faulted");
|
|
1484
1560
|
const parkedAgain = claim.kind === "resumed" && restored?.kind !== "ended";
|
|
1485
1561
|
settleFleet(parkedAgain ? reparkStatus : "failed");
|
|
1486
|
-
|
|
1487
|
-
|
|
1562
|
+
const cancelledRow = claimWindowCancelRow();
|
|
1563
|
+
if (cancelledRow !== undefined)
|
|
1564
|
+
return settledLegAnswer({ taskId, sessionId, plane: planeOfEndedRow(cancelledRow), reparked: false, evidence: NO_SETTLEMENT_EVIDENCE });
|
|
1488
1565
|
return resumeClaimFaultAnswerOf(e);
|
|
1489
1566
|
}
|
|
1490
1567
|
const heldParkWord = heldPark === undefined ? undefined : heldParkOf(heldPark);
|
|
@@ -1539,20 +1616,13 @@ export function createResumeLegs(ctx) {
|
|
|
1539
1616
|
await deps.runStore.setTerminal(taskId, resumePlane.status, sealed, resumePlane.errorMessage ?? null);
|
|
1540
1617
|
}
|
|
1541
1618
|
settleFleet(parkEnded !== undefined ? "failed" : checkpointReopened ? reparkStatus : resumePlane.status, fleetRunResiduals(sealed, sealed ? failureCodeOf(sealed.terminal) : undefined));
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
bindingEnforced: true,
|
|
1550
|
-
...(answered.errorCode ? { errorCode: answered.errorCode } : {}),
|
|
1551
|
-
...(answered.errorMessage ? { errorMessage: redactErrorMessage(answered.errorMessage) ?? "" } : {}),
|
|
1552
|
-
...(checkpointReopened && parkEnded === undefined ? { retriable: true } : {}),
|
|
1553
|
-
...buildSettlementEvidence({ at: "engine_terminal", commitment: args.commitment }),
|
|
1554
|
-
},
|
|
1555
|
-
};
|
|
1619
|
+
return settledLegAnswer({
|
|
1620
|
+
taskId,
|
|
1621
|
+
sessionId,
|
|
1622
|
+
plane: parkEnded !== undefined ? planeOfEndedRow(parkEnded.row) : resumePlane,
|
|
1623
|
+
reparked: checkpointReopened && parkEnded === undefined,
|
|
1624
|
+
evidence: buildSettlementEvidence({ at: "engine_terminal", commitment: args.commitment }),
|
|
1625
|
+
});
|
|
1556
1626
|
}
|
|
1557
1627
|
async function mayDiscloseRowError(cs, token, decider) {
|
|
1558
1628
|
if (decider === undefined || decider.operator)
|
|
@@ -1683,7 +1753,7 @@ export function createResumeLegs(ctx) {
|
|
|
1683
1753
|
const { spec, engineNotices } = resolved;
|
|
1684
1754
|
const hostSpec = { ...spec, objective };
|
|
1685
1755
|
const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
|
|
1686
|
-
prepared: { spec: hostSpec, runner: resolved.runner, legFacts: resolved.legFacts, auth, body: ctx.body, ...(objectiveWithheldIn(ctx) ? { bodyFromScreenedCtx: true } : {}), seat: createSubmissionSeat({ deployment: deps.config }), acceptance: createRunAcceptance({}, { objective, screened: false }), peerLease: resolved.peerLease, execution: resolved.execution, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
|
|
1756
|
+
prepared: { spec: hostSpec, runner: resolved.runner, legFacts: resolved.legFacts, auth, body: ctx.body, ...(objectiveWithheldIn(ctx) ? { bodyFromScreenedCtx: true } : {}), bodyPermissionModeWrite: { stored: ctx.permissionModeWrite }, seat: createSubmissionSeat({ deployment: deps.config }), acceptance: createRunAcceptance({}, { objective, screened: false }), peerLease: resolved.peerLease, execution: resolved.execution, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
|
|
1687
1757
|
fleetScope: resumeFleetScope(req, auth),
|
|
1688
1758
|
elicitOwner: subject ?? null,
|
|
1689
1759
|
source: "workflow_parked_decide",
|
|
@@ -1709,7 +1779,7 @@ export function createResumeLegs(ctx) {
|
|
|
1709
1779
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
1710
1780
|
if (!token)
|
|
1711
1781
|
return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
|
|
1712
|
-
const who = { subject: caller, operator: explicitOperatorOk(caller === undefined ? undefined : personOf(caller), deps.config) };
|
|
1782
|
+
const who = { subject: caller, operator: explicitOperatorOk(caller === undefined ? undefined : personOf(caller), deps.config, "operate") };
|
|
1713
1783
|
if (!who.operator) {
|
|
1714
1784
|
const scope = await cs.peekPendingScope(sessionId, wakeBinding).then((s) => (s === undefined ? cs.peekPendingScope(sessionId) : s)).catch(() => undefined);
|
|
1715
1785
|
if (!keyAdmits("scope.decide", who, scopeCellOf(scope ?? CHECKPOINT_PUBLIC_SCOPE))) {
|
|
@@ -2061,7 +2131,7 @@ export function createResumeLegs(ctx) {
|
|
|
2061
2131
|
throw new Error("drivePlanReview: permissionModeAfter was passed for a task whose stored permission mode is not \"plan\" — the caller must refuse this before any state changes (see the plan_review route's pre-nonce gate)");
|
|
2062
2132
|
}
|
|
2063
2133
|
const approvedMode = planReviewDecisionExitsPlan(decision) && effMode === "plan" ? (permissionModeAfter ?? "default") : undefined;
|
|
2064
|
-
const rebuildCtx = approvedMode === undefined ? ctx :
|
|
2134
|
+
const rebuildCtx = approvedMode === undefined ? ctx : withStoredPermissionMode(ctx, approvedMode, "plan_review");
|
|
2065
2135
|
const resolved = await resolveResumeSpecOrBlocked(rebuildCtx, req, auth);
|
|
2066
2136
|
if (!resolved.ok)
|
|
2067
2137
|
return resolved.answer;
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
18
18
|
import type { HostDecision } from "../host-decision.js";
|
|
19
|
+
import type { Refusal } from "./refusal-body.js";
|
|
19
20
|
import type { EntryProblem } from "../entry-problems.js";
|
|
20
21
|
import type { SessionNotifyDoor } from "../session-notify-door.js";
|
|
21
22
|
import type { TaskStream, TaskSpec, TaskResult, QuestionAnswer, Checkpoint, CheckpointToken, ResumeOutcome, RunInternals, Runner, EngineNotice, PendingSteerInput } from "@sema-agent/core";
|
|
@@ -26,7 +27,9 @@ import type { ImagesLocal } from "./routes/images.js";
|
|
|
26
27
|
import type { CapabilitiesLocal } from "./routes/capabilities.js";
|
|
27
28
|
import type { SessionsLocal } from "./routes/sessions.js";
|
|
28
29
|
import type { SessionSyncLocal } from "./routes/session-sync.js";
|
|
30
|
+
import type { OperatorPower } from "../operator-roster.js";
|
|
29
31
|
import type { PlanApprovePermissionModeAfter } from "../task-settings.js";
|
|
32
|
+
import type { PermissionModeWrite } from "../permission-mode-admission.js";
|
|
30
33
|
import type { GateSighting } from "../approval-gate-currency.js";
|
|
31
34
|
import type { VerifyRoundsSpec } from "./verify-rounds.js";
|
|
32
35
|
import type { RunAbort } from "../run-cancel-context.js";
|
|
@@ -36,6 +39,7 @@ import type { RootedTaskSpec } from "./execution-context.js";
|
|
|
36
39
|
import type { GateCommitment, GateSettlementEvidence } from "../gate-settlement.js";
|
|
37
40
|
import type { ResumeRestatement } from "./resume-restatement.js";
|
|
38
41
|
import type { RememberLaneCannotRecord } from "../parked-decide.js";
|
|
42
|
+
import type { ParsedPersistRule, RuleSlotReceipt } from "../tool-approval.js";
|
|
39
43
|
import type { RowWayOut } from "./resume-legs.js";
|
|
40
44
|
import type { KeyCaller } from "../owner-faces.js";
|
|
41
45
|
import type { Subject } from "../owner-brands.js";
|
|
@@ -156,6 +160,14 @@ export interface PreparedTaskSubmission {
|
|
|
156
160
|
* 它能不能展示在它第一次落盘时就定了,这一跑的受理座答不了。缺席 = 体是这次请求自己的,标不标由受理座定。
|
|
157
161
|
*/
|
|
158
162
|
bodyFromScreenedCtx?: true;
|
|
163
|
+
/**
|
|
164
|
+
* `body` 是从一份存量续跑输入里取回的(与 {@link bodyFromScreenedCtx} 同一种提交:`/decide` 的宿主新 turn)⇒ 这一跑新写的续跑输入**照抄**那一份的写据
|
|
165
|
+
* (`CheckpointCtx.permissionModeWrite`):这一跑没有写体顶层 `permissionMode` 那一格,只是抄了体。`stored` 缺席 = 那一份没有写据(7.152.0 之前写下的)⇒ 照缺席。
|
|
166
|
+
* 本位缺席 = 体是这次请求自己的 ⇒ 写据是提交写者的那一张(`submit-ctx.ts` `persistSubmissionCtx`)。
|
|
167
|
+
*/
|
|
168
|
+
bodyPermissionModeWrite?: {
|
|
169
|
+
readonly stored: PermissionModeWrite | undefined;
|
|
170
|
+
};
|
|
159
171
|
/**
|
|
160
172
|
* 这次执行的**执行上下文**(唯一解析器 `http/execution-context.ts` `resolveExecutionContext` 的产物:根 · 住客 · 放置态 · 车道 · device 车道的设备意图)。
|
|
161
173
|
* 提交门在装配之前铸(fresh);`/decide` 的宿主新 turn 交续跑折叠器铸的那一只(continuation)。每个 `runStore.createRun(` 成功点**必须**把它交给
|
|
@@ -237,6 +249,15 @@ export interface CheckpointCtx {
|
|
|
237
249
|
* 形只有一种读法(`frozenMemorySeedOf`,读口 `resume-ctx-read.ts`)。
|
|
238
250
|
*/
|
|
239
251
|
memorySeed?: import("../project-registry.js").MemorySeed | null;
|
|
252
|
+
/**
|
|
253
|
+
* **写据**:谁写了 `body` 顶层 `permissionMode` 那一格、写下的是什么(形与规则的属主 = `permission-mode-admission.ts` 顶注「写据」)。**服务端写、不来自请求体**
|
|
254
|
+
* (与 {@link unattendedLeg} 同为 `body` 的兄弟键,客户端够不着)。写点:`buildCheckpointCtx`(必填键:提交写口写 `submission`;`/decide` 宿主新 turn 照抄宿主那一行的,
|
|
255
|
+
* 见 {@link PreparedTaskSubmission.bodyPermissionModeWrite};leader worker 写 `submission`)与改写口 `withStoredPermissionMode`(切档 `switch` · 续跑口的钳 `replay` ·
|
|
256
|
+
* `plan_review` 出口 `plan_review`)。续跑腿的写回传原 ctx / 其展开 ⇒ 随行。读者只有一只:续跑口 `stampReplayEntryMode`(地板在场时问「这一格是不是这块地板的出口写下的、
|
|
257
|
+
* 且还是它写下的那一格」)。形只有一种读法(`permissionModeWriteOf`,读口 `resume-ctx-read.ts`:读不出 ⇒ 坏体)。
|
|
258
|
+
* 缺席 = 7.152.0 之前写下的行(不回填:没有证据可补),或从这样的行照抄的体 ⇒ 不是出口写的,地板在场时照钳。
|
|
259
|
+
*/
|
|
260
|
+
permissionModeWrite?: PermissionModeWrite;
|
|
240
261
|
}
|
|
241
262
|
/**
|
|
242
263
|
* `/decide` 第三条车道交给宿主投递腿(wake:`DriveResumeArgs` / fresh:`DurableRunLegArgs`)的**一件**:这一跑在内存里带着的决定,
|
|
@@ -489,7 +510,9 @@ export interface RouteLegs {
|
|
|
489
510
|
* 同一属主判(缺席 ⇒ 恒不造键;判据属主=server.ts 铸键点注)。 */
|
|
490
511
|
decider?: KeyCaller,
|
|
491
512
|
/** 续跑请求的重述键(`mcpServers` / `a2aPeers`),门读出并判过的那一份;缺席 = 冻结读法。语义属主 = `resume-restatement.ts` 顶注。 */
|
|
492
|
-
restatement?: ResumeRestatement
|
|
513
|
+
restatement?: ResumeRestatement,
|
|
514
|
+
/** 这次决定带着「不再询问」请求({@link PersistRuleRequest})⇒ 本函数是它的**唯一铸点**,结局的 `ruleReceipt` 是它的回执({@link ResumeCheckpointOutcome})。 */
|
|
515
|
+
persistRule?: PersistRuleRequest): Promise<ResumeCheckpointOutcome>;
|
|
493
516
|
/** 全 resume 家族的共同下半场(lease admission + 写 run log + fleet 发布)。 */
|
|
494
517
|
driveResumeIntoRunLog(args: DriveResumeArgs): Promise<ResumeLegOutcome>;
|
|
495
518
|
/** assistant 抢占腿的复位(gate-kind 守卫 + 行级属主复核 + 驱动)。 */
|
|
@@ -719,6 +742,12 @@ interface RouteDeclIdentity {
|
|
|
719
742
|
* 不带它 —— 那条流就是 sync 腿本身(关流 = 断连语义),照旧算在飞。漏标一条订阅的方向是**多算在飞**(排空多等),不是少等。
|
|
720
743
|
*/
|
|
721
744
|
readonly subscription?: true;
|
|
745
|
+
/**
|
|
746
|
+
* 「本路由对持这只运维权力的**非人档**开放;本口的运维门按它判」(档表 `operator-roster.ts`)。缺席 = 没声明 —— 非人档(服务主体)的身份在本路由上不算数,
|
|
747
|
+
* 分派层整次请求 403 `auth.principal_confined`(`principal-gate.ts` 的 `requestConfinedTier`,只扫带本列的行)。只住在可分派行上(纯门行是前缀、比任何路由都宽,
|
|
748
|
+
* 型上就开不出一段前缀的权力)。今天只有排空口声明 `power: "drain"`。
|
|
749
|
+
*/
|
|
750
|
+
readonly power?: OperatorPower;
|
|
722
751
|
}
|
|
723
752
|
/**
|
|
724
753
|
* **可分派行**:一条真路由。匹配式二选一 —— `path`(精确;标签**就是** path 自身,所以没有 label 列,
|
|
@@ -760,23 +789,60 @@ export interface ResumeLegOutcome {
|
|
|
760
789
|
body: object;
|
|
761
790
|
}
|
|
762
791
|
/**
|
|
763
|
-
* 「本会话不再询问」的请求 —— **两扇门同一只**:`/decide` 体上的 `remember:"session"
|
|
764
|
-
* `
|
|
792
|
+
* 「本会话不再询问」的请求 —— **两扇门同一只**:`/decide` 体上的 `remember:"session"`、决断口(`POST /v1/tasks/:taskId/asks/:askId/decision`)命中停驻行的
|
|
793
|
+
* `remember:"session"`。铸点只有一只:{@link RouteLegs.resumeCheckpoint} —— 可记与否从**已属主校验的载入停驻行**判(`standingAnswerDurablyRecordable`,
|
|
765
794
|
* 与运维队列行的 `rememberRefused` 同一只判据)、锚由车道定(任务级 = 这个会话;bg 停驻子代 = 行的 root/host 会话)、落豁免店**只在**这次决定
|
|
766
795
|
* 被证明已结清之后(`decisionCommittedOf`)、`rememberApplied` = 落没落(店抛 ⇒ `false` + warn,这次的放行不回滚)。门自己不写店、不判可记。
|
|
767
796
|
* 两口唯一的差 = `laneCannotRecord`(记不住的车道上怎么答;闭集与理由的属主 = `parked-decide.ts` 的 {@link RememberLaneCannotRecord})。
|
|
768
797
|
*/
|
|
769
798
|
export interface RememberSessionRequest {
|
|
770
|
-
/** 豁免行的签发人(`grantedBy`)= 门已验的身份(`/decide` 的决断方 /
|
|
799
|
+
/** 豁免行的签发人(`grantedBy`)= 门已验的身份(`/decide` 的决断方 / 决断口的调用方);匿名开放部署 ⇒ `null`。 */
|
|
771
800
|
readonly grantedBy: string | null;
|
|
772
801
|
readonly laneCannotRecord: RememberLaneCannotRecord;
|
|
773
802
|
}
|
|
774
|
-
/**
|
|
775
|
-
*
|
|
776
|
-
*
|
|
803
|
+
/**
|
|
804
|
+
* 决定体上「本会话放行」那一键(`remember`)的**唯一读法** —— 两扇人决口同调:有问行的卡的决断口(`POST /v1/tasks/:taskId/asks/:askId/decision`)与
|
|
805
|
+
* 运维队列的耐久门(`POST /v1/approvals/:sessionId/decide`)。闭集一词 `"session"`;只配 `approve`(「记住的拒」是另一种语义,不从这一键带进来)。
|
|
806
|
+
* 拒句与码两口逐字同一(由本读法铸);各口自己的门(直连门的签名覆盖、豁免店在场)在本读法之后各判各的。
|
|
807
|
+
* 拒 = 一只 {@link Refusal}(码 + 句 + 机读位在本读法一处铸,冲突形的机读位随拒同铸),两扇口原样发 `400`、不再按码二次分支。
|
|
808
|
+
*/
|
|
809
|
+
export type RememberSlotReading = {
|
|
810
|
+
readonly kind: "absent";
|
|
811
|
+
} | {
|
|
812
|
+
readonly kind: "session";
|
|
813
|
+
} | {
|
|
814
|
+
readonly kind: "refused";
|
|
815
|
+
readonly refusal: Refusal;
|
|
816
|
+
};
|
|
817
|
+
/** `remember` 闭集外的拒句(`400 request.field_invalid`;两扇人决口同一句,由 {@link readRememberSlot} 铸)。 */
|
|
818
|
+
export declare const REMEMBER_MUST_BE_SESSION = "remember must be \"session\"";
|
|
819
|
+
/** `remember` 配 deny 的拒句(`400 request.field_conflict` + 机读位 `remember_decision`;同上)。 */
|
|
820
|
+
export declare const REMEMBER_REQUIRES_APPROVE = "remember requires decision 'approve'";
|
|
821
|
+
export declare function readRememberSlot(raw: unknown, decision: "approve" | "deny"): RememberSlotReading;
|
|
822
|
+
/**
|
|
823
|
+
* 「不再询问」的请求 —— **两扇门同一只**(「本会话放行」{@link RememberSessionRequest} 的孪生):`/decide` 体上的 `persistRule`、赎回席交来的那一问(活卡回决口
|
|
824
|
+
* 命中停驻行时卡上的 `persistRule`)。体形判官只有一只(`tool-approval.ts` 的 `readPersistRuleSlot`)。铸点只有一只:{@link RouteLegs.resumeCheckpoint} 在已属主
|
|
825
|
+
* 校验的载入停驻行上读这张卡的记录(有问行读问行,没有读停驻行),在结清点(`landAfterCommit`,与「本会话放行」同一读 `decisionCommittedOf`)交唯一兑付函数
|
|
826
|
+
* `redeemCardRule`。证不出提交 ⇒ 不兑,回执 `rulePersisted:false` + `rule_lane_unavailable`;**不 400**:规则位的成文律是「裁决本身绝不因规则没存上而改判」
|
|
827
|
+
* (与 `remember` 在 `/decide` 上「硬意图键,记不住就 400」刻意不同 —— 两个键各守各的成文律)。
|
|
828
|
+
*/
|
|
829
|
+
export interface PersistRuleRequest {
|
|
830
|
+
readonly slot: ParsedPersistRule;
|
|
831
|
+
/** 这次决定带了 `updatedInput`(编辑放行)⇒ 兑付答 `rule_input_edited`:生效的是编辑后的调用,候选是从原始命令铸的。 */
|
|
832
|
+
readonly inputWasEdited: boolean;
|
|
833
|
+
}
|
|
834
|
+
/** {@link RouteLegs.resumeCheckpoint} 的结局 = 腿的应答 + 带了「本会话放行」请求时铸点的答(`rememberApplied`:落没落)+ 带了「不再询问」请求时铸点的回执(`ruleReceipt`)。
|
|
835
|
+
* 两位只在证得出提交的两条车道(bg 停驻子代赎回 / 任务级续跑)的结清点上铸;其余出口(拒 / workflow 投递 / 抛)缺席 ⇒ 门读作「没记住」(`=== true`)/ 规则位
|
|
836
|
+
* 由 {@link ruleReceiptOf} 补成「没存上」。两位都是进程内的结局位,不进 `body` —— 由门挂到这次真做了授予判定的回执上(幂等回放不挂)。 */
|
|
777
837
|
export type ResumeCheckpointOutcome = ResumeLegOutcome & {
|
|
778
838
|
readonly rememberApplied?: boolean;
|
|
839
|
+
readonly ruleReceipt?: RuleSlotReceipt;
|
|
779
840
|
};
|
|
841
|
+
/**
|
|
842
|
+
* 一次带了 `persistRule` 的续跑在回执上的规则位(两扇门同调这一只:`/decide` 与赎回席)—— 结清点铸了就是它;没铸(不经结清点的出口:workflow 投递 / 证不出提交)
|
|
843
|
+
* ⇒ `rulePersisted:false` + `rule_lane_unavailable`(「请求带了、结局没答」只在这里补一处)。没带 ⇒ `undefined`(一个键都不加)。只在 2xx 上挂(调用方判)。
|
|
844
|
+
*/
|
|
845
|
+
export declare function ruleReceiptOf(out: ResumeCheckpointOutcome, persistRule: PersistRuleRequest | undefined): RuleSlotReceipt | undefined;
|
|
780
846
|
/**
|
|
781
847
|
* **「决定/续跑已受理」那一枚 200 收据的单一铸点**([ref])。
|
|
782
848
|
*
|
package/dist/http/route-ctx.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sendJson } from "./send.js";
|
|
2
|
+
import { fieldConflictFacts } from "../field-conflict.js";
|
|
2
3
|
export function noteResumeClientGone(res, out, logger) {
|
|
3
4
|
if (out.status !== 200 || out.body.status !== "resuming")
|
|
4
5
|
return;
|
|
@@ -21,6 +22,22 @@ export function assertNever(x) {
|
|
|
21
22
|
throw new Error(`unreachable route id: ${String(x)}`);
|
|
22
23
|
}
|
|
23
24
|
export const ROUTE_METHOD_ORDER = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
25
|
+
export const REMEMBER_MUST_BE_SESSION = `remember must be "session"`;
|
|
26
|
+
export const REMEMBER_REQUIRES_APPROVE = "remember requires decision 'approve'";
|
|
27
|
+
export function readRememberSlot(raw, decision) {
|
|
28
|
+
if (raw === undefined)
|
|
29
|
+
return { kind: "absent" };
|
|
30
|
+
if (raw !== "session")
|
|
31
|
+
return { kind: "refused", refusal: { errorCode: "request.field_invalid", message: REMEMBER_MUST_BE_SESSION } };
|
|
32
|
+
if (decision !== "approve")
|
|
33
|
+
return { kind: "refused", refusal: { errorCode: "request.field_conflict", message: REMEMBER_REQUIRES_APPROVE, facts: fieldConflictFacts("remember_decision", ["remember", "decision"]) } };
|
|
34
|
+
return { kind: "session" };
|
|
35
|
+
}
|
|
36
|
+
export function ruleReceiptOf(out, persistRule) {
|
|
37
|
+
if (persistRule === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
return out.ruleReceipt ?? { rulePersisted: false, ruleRefusal: "rule_lane_unavailable" };
|
|
40
|
+
}
|
|
24
41
|
export function acceptedResumingReceipt(taskId, sessionId, evidence) {
|
|
25
42
|
return { status: 200, body: { taskId, sessionId, status: "resuming", bindingEnforced: true, ...evidence } };
|
|
26
43
|
}
|
|
@@ -55,6 +55,7 @@ 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
|
import type { BillableKind } from "./readiness.js";
|
|
58
|
+
import type { OperatorPower } from "../operator-roster.js";
|
|
58
59
|
/** 一只域 handler 相对**全局服务凭据门**的位置。门前 = 未鉴权可达(每一只都要单独论证),门后 = 先过凭据。 */
|
|
59
60
|
/**
|
|
60
61
|
* 一行路由在全局服务凭据门的哪一侧、吃哪道门(闭集,三员;[ref] 起由两员拆成三员):
|
|
@@ -144,6 +145,9 @@ export declare function isCredentialGatedRewrite(method: string, url: string): b
|
|
|
144
145
|
/** [ref]:这次请求是不是一条**只读订阅**(`RouteDeclFacts.subscription` 那一列;方法也要对上 —— 订阅行恒只认 GET)。
|
|
145
146
|
* 分派器在 admission 那一拍(第一个 await 之前)同步判它:订阅不进在飞计数,只进附着集与订阅集。 */
|
|
146
147
|
export declare function isSubscriptionRequest(method: string, url: string): boolean;
|
|
148
|
+
/** 这次请求(方法 + 路径)命中的那一行声明的运维权力(声明行的 `power` 列;`undefined` = 没命中任何带 `power` 的行)—— 分派层的圈禁判只读这一只
|
|
149
|
+
* (`principal-gate.ts` 的 `requestConfinedTier`)。方法也要对上:路径命中而方法不在列的请求对非人档同答圈禁(不再区分 404 / 405)。 */
|
|
150
|
+
export declare function declaredPowerOf(method: string, url: string): OperatorPower | undefined;
|
|
147
151
|
/** 见 `RouteDeclFacts.methodDispatched` 的头注(单 URL 多方法端点)。 */
|
|
148
152
|
export declare function isMethodDispatchedSubmitPath(url: string): boolean;
|
|
149
153
|
/** 一次分派查找的结果:命中的行与它的参数,外加**这条路径上所有行**的方法并集(405 的 `Allow`)。 */
|