@sema-agent/server 7.148.0 → 7.150.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 +551 -9
- package/README.md +17 -2
- package/README.zh-CN.md +5 -1
- package/USAGE.md +126 -68
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +33 -33
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/approval-leg-origin.d.ts +33 -0
- package/dist/approval-leg-origin.js +4 -0
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +2 -2
- package/dist/auth-bridge.d.ts +9 -1
- package/dist/auth-bridge.js +4 -4
- package/dist/auth-keys.d.ts +10 -1
- package/dist/auth-keys.js +8 -0
- package/dist/background-session-reap.d.ts +1 -1
- package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
- package/dist/boot/deferred-sandbox-path-env.js +1 -2
- package/dist/boot/execution-env.js +10 -11
- package/dist/boot/execution-lane.d.ts +6 -4
- package/dist/boot/execution-lane.js +3 -1
- package/dist/boot/org-delivery.d.ts +3 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -5
- package/dist/boot/parked-revive-gate.js +2 -3
- package/dist/boot/resolve-spec.d.ts +4 -5
- package/dist/boot/resolve-spec.js +66 -28
- package/dist/boot/retention-lane.d.ts +1 -1
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/session-faces.js +21 -21
- package/dist/boot/stage-01-config.d.ts +1 -0
- package/dist/boot/stage-01-config.js +4 -3
- package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
- package/dist/boot/stage-02-web-admin-pull.js +10 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +4 -4
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +1 -0
- package/dist/boot/stage-06-runners.d.ts +2 -1
- package/dist/boot/stage-06-runners.js +1 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -1
- package/dist/boot/stage-07-capability-layer.js +1 -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-10-http-server.d.ts +35 -2
- package/dist/boot/stage-10-http-server.js +9 -4
- package/dist/boot/stores.d.ts +3 -0
- package/dist/boot/stores.js +22 -19
- package/dist/boot/web-admin.d.ts +1 -1
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/capability-facts.d.ts +70 -60
- package/dist/capability-facts.js +45 -49
- package/dist/card-settle-stored-scope.d.ts +56 -0
- package/dist/card-settle-stored-scope.js +27 -0
- package/dist/card-void.d.ts +7 -12
- package/dist/card-void.js +2 -7
- package/dist/config-catalog.d.ts +12 -0
- package/dist/config-catalog.js +37 -33
- package/dist/config-invariants.d.ts +5 -4
- package/dist/config-invariants.js +4 -4
- package/dist/config-types.d.ts +49 -16
- package/dist/config.d.ts +6 -5
- package/dist/config.js +35 -24
- package/dist/context-window-notice.d.ts +31 -0
- package/dist/context-window-notice.js +28 -0
- package/dist/deployment-governance.d.ts +7 -28
- package/dist/deployment-governance.js +1 -14
- package/dist/deployment-shape.d.ts +20 -11
- package/dist/deployment-shape.js +1 -0
- package/dist/device-ws-hub.d.ts +1 -0
- package/dist/device-ws-hub.js +64 -28
- package/dist/device-ws-protocol.d.ts +20 -6
- package/dist/device-ws-protocol.js +9 -2
- package/dist/engine-identity.d.ts +7 -1
- package/dist/engine-identity.js +5 -0
- package/dist/env-name-allowlist-knobs.d.ts +6 -5
- package/dist/env-name-allowlist-knobs.js +5 -0
- package/dist/execution-lane-caps.d.ts +20 -5
- package/dist/execution-lane-caps.js +18 -4
- package/dist/field-conflict.d.ts +2 -2
- package/dist/fleet/engine-signal.d.ts +14 -1
- package/dist/fleet/engine-signal.js +5 -1
- package/dist/fleet/fleet-reconciler.d.ts +1 -1
- package/dist/fleet/serving-lease.d.ts +18 -3
- package/dist/fleet/serving-lease.js +81 -35
- package/dist/fleet-client.d.ts +13 -11
- package/dist/fleet-client.js +53 -33
- package/dist/history-anchor.js +1 -1
- package/dist/hitl-delivery.d.ts +1 -3
- package/dist/hitl-delivery.js +1 -1
- package/dist/hooks/command-spawn.d.ts +7 -2
- package/dist/hooks/command-spawn.js +13 -6
- package/dist/hooks/hook-runner.d.ts +5 -0
- package/dist/hooks/hook-runner.js +11 -7
- package/dist/http/active-run-conflict.d.ts +3 -7
- package/dist/http/active-run-conflict.js +2 -2
- package/dist/http/admission.js +5 -5
- package/dist/http/approval-sweeps.js +5 -3
- package/dist/http/caller-trust.d.ts +2 -2
- package/dist/http/caller-trust.js +3 -3
- package/dist/http/checkpoint-ctx.d.ts +18 -63
- package/dist/http/checkpoint-ctx.js +1 -24
- package/dist/http/claim-heartbeat.d.ts +6 -6
- package/dist/http/claim-heartbeat.js +6 -6
- package/dist/http/claimed-run.d.ts +16 -9
- package/dist/http/claimed-run.js +8 -0
- package/dist/http/dispatch.js +7 -5
- package/dist/http/durable-run-leg.d.ts +8 -1
- package/dist/http/durable-run-leg.js +34 -15
- package/dist/http/execution-context.d.ts +115 -14
- package/dist/http/execution-context.js +130 -40
- package/dist/http/owner-access.d.ts +246 -21
- package/dist/http/owner-access.js +186 -8
- package/dist/http/principal-gate.d.ts +18 -11
- package/dist/http/principal-gate.js +3 -1
- package/dist/http/readiness.d.ts +4 -2
- package/dist/http/resume-ctx-read.d.ts +14 -3
- package/dist/http/resume-ctx-read.js +24 -3
- package/dist/http/resume-legs.d.ts +35 -3
- package/dist/http/resume-legs.js +262 -106
- package/dist/http/route-ctx.d.ts +29 -11
- package/dist/http/routes/a2a-serve.js +17 -14
- package/dist/http/routes/approvals-assistant.d.ts +3 -2
- package/dist/http/routes/approvals-assistant.js +11 -11
- package/dist/http/routes/capabilities.js +5 -4
- package/dist/http/routes/config-catalog.d.ts +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -1
- package/dist/http/routes/diagnostics.js +30 -5
- package/dist/http/routes/mcp-reconnect.js +2 -2
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/notify-wake.js +2 -3
- package/dist/http/routes/principal-inbox.d.ts +3 -1
- package/dist/http/routes/principal-inbox.js +3 -2
- package/dist/http/routes/projects.d.ts +1 -0
- package/dist/http/routes/projects.js +5 -3
- package/dist/http/routes/retention-ops.d.ts +1 -1
- package/dist/http/routes/run-permission-mode.d.ts +1 -1
- package/dist/http/routes/run-permission-mode.js +2 -2
- package/dist/http/routes/runs.d.ts +1 -1
- package/dist/http/routes/runs.js +33 -37
- package/dist/http/routes/session-memory-sync.js +4 -5
- package/dist/http/routes/session-sync.d.ts +24 -5
- package/dist/http/routes/session-sync.js +45 -37
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions.d.ts +1 -1
- package/dist/http/routes/sessions.js +18 -44
- package/dist/http/routes/tasks.js +136 -101
- package/dist/http/routes/trace-usage.js +14 -14
- package/dist/http/routes/workflows.d.ts +1 -0
- package/dist/http/routes/workflows.js +20 -38
- package/dist/http/server.d.ts +12 -9
- package/dist/http/server.js +35 -6
- package/dist/http/stored-body-replay.d.ts +3 -24
- package/dist/http/stored-body-replay.js +2 -12
- package/dist/http/submit-ctx.d.ts +2 -2
- package/dist/http/submit-ctx.js +5 -5
- package/dist/http/submit-replay.d.ts +15 -2
- package/dist/http/submit-replay.js +10 -4
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/leader/endpoint.js +9 -8
- package/dist/leg-facts.d.ts +13 -2
- package/dist/leg-facts.js +2 -0
- package/dist/local-store-declaration.d.ts +54 -0
- package/dist/local-store-declaration.js +59 -0
- package/dist/mcp-assertion-header.d.ts +1 -1
- package/dist/mcp-assertion-header.js +1 -1
- package/dist/mcp-authorization-face.d.ts +18 -14
- package/dist/mcp-authorization-face.js +3 -2
- package/dist/memory-project-layer.js +1 -1
- package/dist/memory-scope.d.ts +4 -4
- package/dist/memory-scope.js +4 -4
- package/dist/memory-store-refusal.d.ts +1 -1
- package/dist/memory-sync-client.js +2 -2
- package/dist/model-context-window.d.ts +50 -11
- package/dist/model-context-window.js +20 -1
- package/dist/model-entry-refusal.d.ts +5 -4
- package/dist/model-entry-refusal.js +17 -3
- package/dist/model-reach-scope.d.ts +2 -1
- package/dist/observability/corrupt-read-seat.d.ts +1 -1
- package/dist/observability/fail-open.d.ts +19 -7
- package/dist/observability/fail-open.js +19 -7
- package/dist/observability/logger.d.ts +7 -0
- package/dist/observability/logger.js +3 -1
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/org-delivery-gate.d.ts +31 -6
- package/dist/org-delivery-gate.js +7 -4
- package/dist/owner-faces.d.ts +30 -5
- package/dist/owner-faces.js +2 -0
- package/dist/park-row-commit.d.ts +67 -0
- package/dist/park-row-commit.js +76 -0
- package/dist/parked-decide.d.ts +9 -7
- package/dist/parked-decide.js +25 -6
- package/dist/peer-directory.d.ts +1 -1
- package/dist/peer-seat.d.ts +1 -1
- package/dist/peer-seat.js +52 -59
- package/dist/plugins/adoption-log-sql.d.ts +3 -3
- package/dist/plugins/adoption-log-sql.js +19 -19
- package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
- package/dist/plugins/approval-ask-store-sql.js +3 -5
- package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
- package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
- package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
- package/dist/plugins/approval-exemption-skill-m46.js +6 -10
- package/dist/plugins/approval-exemption-store.d.ts +26 -1
- package/dist/plugins/approval-exemption-store.js +28 -19
- package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
- package/dist/plugins/ensure-schema-lock-policy.js +75 -9
- package/dist/plugins/exec-path-law.d.ts +34 -0
- package/dist/plugins/exec-path-law.js +13 -0
- package/dist/plugins/file-run-store.d.ts +22 -9
- package/dist/plugins/file-run-store.js +51 -23
- package/dist/plugins/local-checkpoint-store.d.ts +8 -1
- package/dist/plugins/local-checkpoint-store.js +5 -1
- package/dist/plugins/local-data-root-admission.d.ts +52 -0
- package/dist/plugins/local-data-root-admission.js +100 -0
- package/dist/plugins/local-engine-high-water.d.ts +28 -7
- package/dist/plugins/local-engine-high-water.js +27 -22
- package/dist/plugins/local-session-store.d.ts +134 -49
- package/dist/plugins/local-session-store.js +149 -108
- package/dist/plugins/memory-run-store.d.ts +14 -9
- package/dist/plugins/memory-run-store.js +48 -22
- package/dist/plugins/one-time-migration-marker.d.ts +19 -3
- package/dist/plugins/one-time-migration-marker.js +7 -1
- package/dist/plugins/one-time-migrations.d.ts +110 -59
- package/dist/plugins/one-time-migrations.js +224 -136
- package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
- package/dist/plugins/org-delivery-quota-store.js +29 -14
- package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
- package/dist/plugins/permission-rule-store-sql.js +5 -9
- package/dist/plugins/pg-pool.d.ts +1 -1
- package/dist/plugins/pg-pool.js +14 -12
- package/dist/plugins/posix-shell-fs.js +4 -2
- package/dist/plugins/remote-env-adb.js +3 -2
- package/dist/plugins/remote-env-device.d.ts +3 -2
- package/dist/plugins/remote-env-device.js +8 -6
- package/dist/plugins/remote-env-e2b.d.ts +4 -8
- package/dist/plugins/remote-env-e2b.js +21 -15
- package/dist/plugins/remote-env-host.d.ts +51 -10
- package/dist/plugins/remote-env-host.js +97 -31
- package/dist/plugins/remote-env-k8s.js +7 -4
- package/dist/plugins/remote-env-local-docker.d.ts +6 -3
- package/dist/plugins/remote-env-local-docker.js +11 -25
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/remote-shell.d.ts +3 -1
- package/dist/plugins/remote-shell.js +3 -3
- package/dist/plugins/replica-registration.d.ts +35 -0
- package/dist/plugins/replica-registration.js +48 -0
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/run-store-sql.d.ts +27 -8
- package/dist/plugins/run-store-sql.js +46 -18
- package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
- package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
- package/dist/plugins/schema-consistency.d.ts +91 -5
- package/dist/plugins/schema-consistency.js +189 -7
- package/dist/plugins/schema-drift-sentence.d.ts +6 -5
- package/dist/plugins/schema-drift-sentence.js +13 -5
- package/dist/plugins/serving-requirement.d.ts +49 -0
- package/dist/plugins/serving-requirement.js +53 -0
- package/dist/plugins/session-placement.d.ts +5 -4
- package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
- package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
- package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
- package/dist/plugins/stop-then-start-sentence.js +51 -0
- package/dist/plugins/store-backend.d.ts +23 -9
- package/dist/plugins/store-backend.js +58 -24
- package/dist/plugins/store-contracts.d.ts +45 -5
- package/dist/plugins/store-contracts.js +19 -0
- package/dist/plugins/tidb-pool.d.ts +11 -6
- package/dist/plugins/tidb-pool.js +22 -12
- package/dist/plugins/workspace-custody.d.ts +44 -0
- package/dist/plugins/workspace-custody.js +47 -0
- package/dist/process-instance.d.ts +3 -0
- package/dist/process-instance.js +7 -0
- package/dist/project-context.d.ts +66 -0
- package/dist/project-context.js +70 -0
- package/dist/project-identity.d.ts +8 -7
- package/dist/project-identity.js +40 -17
- package/dist/project-layer-key.d.ts +13 -2
- package/dist/project-layer-key.js +9 -1
- package/dist/question.d.ts +9 -21
- package/dist/question.js +28 -19
- package/dist/read-deny-floor-notice.d.ts +36 -0
- package/dist/read-deny-floor-notice.js +40 -0
- package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
- package/dist/request-declaration-disposition.js +39 -0
- package/dist/run-cancel-card.d.ts +28 -0
- package/dist/run-cancel-card.js +20 -0
- package/dist/run-cancel-context.d.ts +10 -0
- package/dist/run-cancel-context.js +1 -0
- package/dist/run-local.js +28 -13
- package/dist/runs.d.ts +31 -7
- package/dist/runs.js +34 -16
- package/dist/security.d.ts +3 -3
- package/dist/server-secret-env.d.ts +34 -20
- package/dist/server-secret-env.js +61 -18
- package/dist/session-agent.d.ts +4 -12
- package/dist/session-agent.js +0 -6
- package/dist/session-children-retire.d.ts +6 -3
- package/dist/session-children-retire.js +2 -5
- package/dist/session-incarnation.d.ts +18 -4
- package/dist/session-incarnation.js +4 -1
- package/dist/session-notify-door.d.ts +4 -4
- package/dist/session-notify-door.js +5 -6
- package/dist/session-rules-seat.d.ts +4 -3
- package/dist/session-rules-seat.js +3 -11
- package/dist/session-sync.js +3 -3
- package/dist/session-workspace.d.ts +3 -3
- package/dist/store-admission.d.ts +33 -0
- package/dist/store-admission.js +49 -0
- package/dist/store-posture.d.ts +5 -0
- package/dist/store-posture.js +18 -1
- package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
- package/dist/stores-sql/checkpoint-store-sql.js +109 -23
- package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
- package/dist/stores-sql/file-history-store-sql.js +3 -5
- package/dist/stores-sql/retention-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
- package/dist/stores-sql/tool-result-store-sql.js +4 -6
- package/dist/task-a2a.d.ts +3 -8
- package/dist/task-a2a.js +25 -27
- package/dist/task-cwd.d.ts +27 -10
- package/dist/task-cwd.js +3 -0
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-mcp.js +1 -1
- package/dist/task-settings.d.ts +13 -0
- package/dist/task-settings.js +14 -7
- package/dist/terminal.d.ts +2 -0
- package/dist/terminal.js +21 -6
- package/dist/tool-approval.d.ts +50 -41
- package/dist/tool-approval.js +96 -73
- package/dist/trace/engine-notice-wire.d.ts +38 -21
- package/dist/trace/engine-notice-wire.js +25 -4
- package/dist/trace/ledger-sink.d.ts +3 -1
- package/dist/trace/project.d.ts +8 -5
- package/dist/trace/project.js +11 -9
- package/dist/trace/projection-table.d.ts +33 -2
- package/dist/trace/redact.d.ts +12 -0
- package/dist/trace/redact.js +5 -1
- package/dist/trace/self-minted-wire.d.ts +268 -56
- package/dist/trace/self-minted-wire.js +110 -40
- package/dist/trace/tool-card.d.ts +3 -0
- package/dist/trace/tool-card.js +24 -0
- package/dist/trace/tool-output-media.d.ts +2 -1
- package/dist/unattended-permission-mode.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.js +22 -9
- package/dist/web-admin-client/effective-keys.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
- package/docs/error-text-lexicon.json +317 -0
- package/package.json +4 -2
- package/scripts/embed-minimal.mjs +183 -0
- package/wire-changes/server@7.149.0.json +372 -0
- package/wire-changes/server@7.150.0.json +335 -0
- package/dist/plain-string-record.js +0 -70
|
@@ -13,7 +13,7 @@ export declare function handleRuns(req: IncomingMessage, res: ServerResponse, ma
|
|
|
13
13
|
/** 同上纪律(子代理 / 任务句柄 / HITL 应答段)。 */
|
|
14
14
|
export declare function handleRunVerbs(req: IncomingMessage, res: ServerResponse, match: RouteMatch<RunVerbsRouteId>, ctx: RouteCtx): Promise<void>;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* [ref] 车2 件2 —— `POST /v1/runs` 的**建行 + 驱动**段,从路由体里原样搬出来的具名腿。
|
|
17
17
|
*
|
|
18
18
|
* 🔴 为什么提取:A2A 的 `message/send`(routes/a2a-serve.ts)必须走**同一条**提交链 —— 第二条提交
|
|
19
19
|
* 路径就是第二个写者,而这条链上挂着 durable taskId 幂等重放、lease/治理窗 pre-admission、
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -6,6 +6,8 @@ import { uuidv7, mintCheckpointToken, CheckpointError, policyCannotAnswer, valid
|
|
|
6
6
|
import { isHolderDeathVerdict, isTerminalRunStatus, isParkedRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
7
7
|
import { askRowCarriesOwner } from "../../plugins/approval-ask-store-sql.js";
|
|
8
8
|
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-face.js";
|
|
9
|
+
import { runCancelCardOf } from "../../run-cancel-card.js";
|
|
10
|
+
import { CARD_EXPIRE_OUTCOME, expireCardUnderStoredScope } from "../../card-settle-stored-scope.js";
|
|
9
11
|
import { redactSecrets } from "../../trace/redact.js";
|
|
10
12
|
import { isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
11
13
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
@@ -28,15 +30,15 @@ import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
|
28
30
|
import * as facts from "../../capability-facts.js";
|
|
29
31
|
import { assertNever } from "../route-ctx.js";
|
|
30
32
|
import { readRunFace } from "../run-read-face.js";
|
|
31
|
-
import { replayOfExistingRun } from "../submit-replay.js";
|
|
33
|
+
import { replayOfExistingRun, replayOfKey } from "../submit-replay.js";
|
|
32
34
|
import { mintAndDriveDurableRun } from "../durable-run-leg.js";
|
|
33
|
-
import {
|
|
35
|
+
import { SUBMISSION_CLAIM_FACE } from "../execution-context.js";
|
|
36
|
+
import { admitRunById, admitRunByIdem, enter, readVisibleRun, sessionFactsReadOf } from "../owner-access.js";
|
|
34
37
|
import { keyAdmission, ownerCellOf, recoveredSubject } from "../../owner-faces.js";
|
|
35
38
|
import { personOf } from "../../owner-brands.js";
|
|
36
39
|
import { withSubject } from "../../observability/principal-context.js";
|
|
37
40
|
import { readResumeCtx, resumeCtxUnreadableAnswer } from "../resume-ctx-read.js";
|
|
38
41
|
import { storedBodyReplayAnswer, storedBodyReplayLogFields } from "../checkpoint-ctx.js";
|
|
39
|
-
import { headReadOf } from "../../session-incarnation.js";
|
|
40
42
|
import { serviceVersion } from "../../service-version.js";
|
|
41
43
|
import { storedBodyReplayRefusal } from "../stored-body-replay.js";
|
|
42
44
|
import { haltBodyIssue, steerBodyKeyIssue } from "../../request-key-closure.js";
|
|
@@ -103,7 +105,7 @@ function sendHandleMiss(res, run, thisInstanceId) {
|
|
|
103
105
|
}
|
|
104
106
|
return false;
|
|
105
107
|
}
|
|
106
|
-
async function streamRunEvents(req, res, deps, runStore, taskId) {
|
|
108
|
+
async function streamRunEvents(req, res, deps, runStore, entry, taskId) {
|
|
107
109
|
await streamSseLog(req, res, {
|
|
108
110
|
preamble: async (signal) => {
|
|
109
111
|
const gate = resolveStreamApprovalGate({
|
|
@@ -132,10 +134,7 @@ async function streamRunEvents(req, res, deps, runStore, taskId) {
|
|
|
132
134
|
return buildApprovalPreambleSseFrames(frames);
|
|
133
135
|
},
|
|
134
136
|
onPreambleFailure: (reason, err) => deps.logger?.warn?.("approval_preamble_failed", { taskId, reason, face: "run.events", error: err instanceof Error ? err.message : String(err ?? "") }),
|
|
135
|
-
statusOf: async (id) =>
|
|
136
|
-
const run = await runStore.getRun(id);
|
|
137
|
-
return run ? runRowAt("run-events", runStore, run) : undefined;
|
|
138
|
-
},
|
|
137
|
+
statusOf: async (id) => (await readVisibleRun(entry, runStore, id))?.row,
|
|
139
138
|
getEvents: (id, after) => runStore.getEvents(id, after),
|
|
140
139
|
formatEvent: (ev) => {
|
|
141
140
|
const type = normalizeRunEventType(ev.type);
|
|
@@ -205,9 +204,9 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
205
204
|
return;
|
|
206
205
|
}
|
|
207
206
|
const inflight = idemCache.peek(inflightKeyOf(idem, "durable", clientTaskId));
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
const replayed = inflight ? undefined : await replayOfKey({ idemKey: idem.key }, { subject: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) }, "durable", deps, runStore);
|
|
208
|
+
const resp = inflight ? await inflight : replayed;
|
|
209
|
+
if (resp !== undefined) {
|
|
211
210
|
sendJson(res, resp.status, resp.body);
|
|
212
211
|
return;
|
|
213
212
|
}
|
|
@@ -243,26 +242,23 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
243
242
|
const entry = enter(req, res, deps, match.decl.id);
|
|
244
243
|
if (entry === undefined)
|
|
245
244
|
return;
|
|
246
|
-
let
|
|
247
|
-
let pathId;
|
|
245
|
+
let admitted;
|
|
248
246
|
if (match.decl.id === "runs-by-idem") {
|
|
249
247
|
const rawKey = new URLSearchParams(match.search).get("idempotencyKey");
|
|
250
248
|
if (!rawKey) {
|
|
251
249
|
sendError(res, 400, "request.query_invalid", "idempotencyKey is required: pass the raw Idempotency-Key the submission was sent with");
|
|
252
250
|
return;
|
|
253
251
|
}
|
|
254
|
-
|
|
252
|
+
admitted = await admitRunByIdem(entry, res, runStore, idemKeyOf(rawKey, idemCallerOf(req, ctx), "run"));
|
|
255
253
|
}
|
|
256
254
|
else {
|
|
257
|
-
|
|
258
|
-
run = await runStore.getRun(pathId);
|
|
255
|
+
admitted = await admitRunById(entry, res, runStore, match.params.taskId);
|
|
259
256
|
}
|
|
260
|
-
const admitted = await admitRunRow(entry, res, runStore, run, pathId);
|
|
261
257
|
if (admitted === undefined)
|
|
262
258
|
return;
|
|
263
259
|
const taskId = admitted.row.taskId;
|
|
264
260
|
if (match.decl.id === "run-events") {
|
|
265
|
-
await streamRunEvents(req, res, deps, runStore, taskId);
|
|
261
|
+
await streamRunEvents(req, res, deps, runStore, entry, taskId);
|
|
266
262
|
}
|
|
267
263
|
else {
|
|
268
264
|
sendJson(res, 200, await readRunFace(deps, runStore, admitted.row));
|
|
@@ -285,16 +281,15 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
285
281
|
return;
|
|
286
282
|
const run = cancelAdmitted.row;
|
|
287
283
|
const rs = runStore;
|
|
288
|
-
const cancelSuspended = async () => {
|
|
284
|
+
const cancelSuspended = async (parked) => {
|
|
289
285
|
const cs = deps.checkpointStore;
|
|
290
286
|
let note = "cancelled while suspended (pending approval settled)";
|
|
291
287
|
if (cs) {
|
|
292
|
-
const token = await cs
|
|
288
|
+
const token = await runCancelCardOf(cs, run.sessionId);
|
|
293
289
|
if (token) {
|
|
294
|
-
const
|
|
295
|
-
const won = cp ? await cs.expire(token, cp.scope) : false;
|
|
290
|
+
const won = (await expireCardUnderStoredScope(cs, token)) === CARD_EXPIRE_OUTCOME.expired;
|
|
296
291
|
if (!won) {
|
|
297
|
-
const now = await rs
|
|
292
|
+
const now = (await readVisibleRun(entry, rs, taskId))?.row;
|
|
298
293
|
const st = now?.status;
|
|
299
294
|
if (st !== undefined && (isParkedRunStatus(st) || st === "running")) {
|
|
300
295
|
sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
|
|
@@ -308,7 +303,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
308
303
|
else {
|
|
309
304
|
const claimed = await rs.markResuming(taskId, deps.instanceId ?? "default");
|
|
310
305
|
if (!claimed) {
|
|
311
|
-
const now = await rs
|
|
306
|
+
const now = (await readVisibleRun(entry, rs, taskId))?.row;
|
|
312
307
|
const st = now?.status;
|
|
313
308
|
if (st !== undefined && (st === "running" || isParkedRunStatus(st))) {
|
|
314
309
|
sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
|
|
@@ -326,7 +321,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
326
321
|
}
|
|
327
322
|
const err = note;
|
|
328
323
|
try {
|
|
329
|
-
const result = mintCancelledResult({ taskId, sessionId: run.sessionId, startedAtMs: Date.parse(run.createdAt), message: err, cause: "cancel_verb" });
|
|
324
|
+
const result = mintCancelledResult({ taskId, sessionId: run.sessionId, startedAtMs: Date.parse(run.createdAt), message: err, cause: "cancel_verb", parkedResult: parked });
|
|
330
325
|
let lastErr;
|
|
331
326
|
for (let attempt = 0;; attempt++) {
|
|
332
327
|
try {
|
|
@@ -348,7 +343,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
348
343
|
sendError(res, 500, "internal.cancel_not_terminalized", `cancel progressed (${note}) but could not terminalize the run row (retried) — retry cancel; a stuck row is reaped after the stale window (${e instanceof Error ? e.message : String(e)})`, { taskId });
|
|
349
344
|
return;
|
|
350
345
|
}
|
|
351
|
-
const finalRow = await rs
|
|
346
|
+
const finalRow = (await readVisibleRun(entry, rs, taskId).catch(() => undefined))?.row;
|
|
352
347
|
if (finalRow && finalRow.errorCode !== "cancelled" && isTerminalRunStatus(finalRow.status)) {
|
|
353
348
|
sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, cancelRequested: finalRow.cancelRequested, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
|
|
354
349
|
return;
|
|
@@ -359,9 +354,9 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
359
354
|
let flagged = await runStore.requestCancel(taskId, run.owner);
|
|
360
355
|
const CANCEL_RECLASSIFY_ATTEMPTS = 3;
|
|
361
356
|
for (let attempt = 0; !flagged; attempt++) {
|
|
362
|
-
const now = await runStore
|
|
357
|
+
const now = (await readVisibleRun(entry, runStore, taskId))?.row;
|
|
363
358
|
if (now !== undefined && isParkedRunStatus(now.status)) {
|
|
364
|
-
await cancelSuspended();
|
|
359
|
+
await cancelSuspended(now.result);
|
|
365
360
|
return;
|
|
366
361
|
}
|
|
367
362
|
if (now?.status === "running" && attempt < CANCEL_RECLASSIFY_ATTEMPTS) {
|
|
@@ -379,7 +374,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
379
374
|
sendJson(res, 202, { taskId, status: "cancelling", cancelRequested: true });
|
|
380
375
|
}
|
|
381
376
|
else if (isParkedRunStatus(run.status)) {
|
|
382
|
-
await cancelSuspended();
|
|
377
|
+
await cancelSuspended(run.result);
|
|
383
378
|
}
|
|
384
379
|
else {
|
|
385
380
|
sendJson(res, 202, { taskId, status: run.status, cancelRequested: run.cancelRequested, note: "run already terminal — cancel is a no-op" });
|
|
@@ -503,7 +498,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
503
498
|
}
|
|
504
499
|
if (code !== "steering.not_running")
|
|
505
500
|
throw e;
|
|
506
|
-
const freshBeforePark = await runStore
|
|
501
|
+
const freshBeforePark = (await readVisibleRun(entry, runStore, taskId).catch(() => undefined))?.row;
|
|
507
502
|
const raced = await tryPark();
|
|
508
503
|
if (raced === "parked") {
|
|
509
504
|
sendParked(freshBeforePark?.status ?? run.status);
|
|
@@ -552,8 +547,8 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
552
547
|
}
|
|
553
548
|
return;
|
|
554
549
|
}
|
|
555
|
-
const
|
|
556
|
-
if (deps.checkpointStore &&
|
|
550
|
+
const sessionFactsOf = sessionFactsReadOf(deps.sessionStorage);
|
|
551
|
+
if (deps.checkpointStore && sessionFactsOf) {
|
|
557
552
|
const activeTaskId = await runStore.getActiveTaskId?.(run.sessionId).catch(() => undefined);
|
|
558
553
|
if (activeTaskId !== undefined && activeTaskId !== null && activeTaskId !== taskId) {
|
|
559
554
|
sendNotRunning(`run is ${run.status} and its session has an ACTIVE run (${activeTaskId}) — steer that run instead`);
|
|
@@ -603,7 +598,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
603
598
|
mintResult.duplicateKey = true;
|
|
604
599
|
return;
|
|
605
600
|
}
|
|
606
|
-
const leafId = await
|
|
601
|
+
const leafId = await sessionFactsOf(run.sessionId).then((h) => h?.leafId).catch(() => undefined);
|
|
607
602
|
if (leafId === undefined || leafId === null)
|
|
608
603
|
return;
|
|
609
604
|
const resumeRead = await readResumeCtx(cs.getCtx(run.sessionId), { sessionId: run.sessionId, opener: deps.checkpointCtxSealing });
|
|
@@ -777,7 +772,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
777
772
|
catch (e) {
|
|
778
773
|
if (e.code !== "steering.not_running")
|
|
779
774
|
throw e;
|
|
780
|
-
const freshRun = await runStore
|
|
775
|
+
const freshRun = (await readVisibleRun(entry, runStore, taskId).catch(() => undefined))?.row;
|
|
781
776
|
sendNoLiveTurn(freshRun?.status ?? run.status, true);
|
|
782
777
|
return;
|
|
783
778
|
}
|
|
@@ -1507,9 +1502,9 @@ async function createDurableRunHeld(req, res, ctx, prepared, idem) {
|
|
|
1507
1502
|
const submitter = gatedSubject(req, deps.config);
|
|
1508
1503
|
const caller = { subject: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) };
|
|
1509
1504
|
if (clientTaskId) {
|
|
1510
|
-
const
|
|
1511
|
-
if (
|
|
1512
|
-
return
|
|
1505
|
+
const replayed = await replayOfKey({ taskId: clientTaskId }, caller, "durable", deps, runStore);
|
|
1506
|
+
if (replayed !== undefined)
|
|
1507
|
+
return replayed;
|
|
1513
1508
|
}
|
|
1514
1509
|
if ((await leaseDenied(req, res, seat)) || (await usageWindowDenied(req, res, seat)))
|
|
1515
1510
|
return null;
|
|
@@ -1526,6 +1521,7 @@ async function createDurableRunHeld(req, res, ctx, prepared, idem) {
|
|
|
1526
1521
|
source,
|
|
1527
1522
|
identity: runIdentityOf(req, deps.config, prepared.auth?.subject ?? null),
|
|
1528
1523
|
runSlot,
|
|
1524
|
+
face: SUBMISSION_CLAIM_FACE,
|
|
1529
1525
|
});
|
|
1530
1526
|
const receipt = out.existing ? await replayOfExistingRun(out.existing, caller, "durable", deps, runStore) : out;
|
|
1531
1527
|
await prepared.seat.close();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { memorySyncReportOf } from "@sema-agent/core";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
|
-
import { admitSessionById, enter } from "../owner-access.js";
|
|
3
|
+
import { admitSessionById, enter, latestRunIdOf, readVisibleSession } from "../owner-access.js";
|
|
4
4
|
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
import * as facts from "../../capability-facts.js";
|
|
6
6
|
import { leafOfAdmittedHead } from "../../session-incarnation.js";
|
|
@@ -94,7 +94,6 @@ export async function handleSessionMemorySync(req, res, match, ctx) {
|
|
|
94
94
|
sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
-
const readHead = () => sessionStorage.headOf(sessionId);
|
|
98
97
|
const admitted = await admitSessionById(entry, res, sessionStorage, sessionId);
|
|
99
98
|
if (admitted === undefined)
|
|
100
99
|
return;
|
|
@@ -122,10 +121,10 @@ export async function handleSessionMemorySync(req, res, match, ctx) {
|
|
|
122
121
|
const quiet = async () => {
|
|
123
122
|
if ((await runStore.getActiveTaskId(sessionId)) !== undefined)
|
|
124
123
|
return false;
|
|
125
|
-
if ((await runStore
|
|
124
|
+
if ((await latestRunIdOf(runStore, sessionId)) !== observedTaskId)
|
|
126
125
|
return false;
|
|
127
|
-
const now = await
|
|
128
|
-
return now !== undefined && leafOfAdmittedHead(now) === sourceRevision;
|
|
126
|
+
const now = await readVisibleSession(entry, sessionStorage, sessionId);
|
|
127
|
+
return now !== undefined && leafOfAdmittedHead(now.row) === sourceRevision;
|
|
129
128
|
};
|
|
130
129
|
const fenced = () => reportOf({ target, sourceRevision, scopes, refused: { code: "session_active", message: SESSION_ACTIVE_SYNC_MESSAGE } });
|
|
131
130
|
let report;
|
|
@@ -36,16 +36,35 @@ export type PendingStaging = {
|
|
|
36
36
|
/** The owner cell the gate judged when it opened this staging (head-read reading `headOf(id)?.owner`: `undefined` = no such session) — the commit lands only on
|
|
37
37
|
* this same cell (`StagedCommitOptions.expectedOwner`, `session-sync-kernel.ts`); `owner` above (the re-stamped importing principal) is derived from it for a fleet caller. */
|
|
38
38
|
expectedOwner: string | null | undefined;
|
|
39
|
+
/** 开这张暂存时占下的导入租约的键({@link importLeaseKey});续期与放掉都按它,不再从会话 id 重算。 */
|
|
40
|
+
leaseKey: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 导入租约的键 = 这次导入**落在哪一格属主上**(与提交的属主 CAS 同一格)+ 会话 id。一条读法:
|
|
44
|
+
* · id 被占着(活行 ∪ 在删行 —— 「被占着」的唯一读法,门读的 `occupant`)⇒ 落点是住客的属主格(`null` = 无主):所有碰得到这一行的调用方(属主、fleet、存量无主互认)共一把 —— 同一份会话上第二份在飞导入,谁发起的都 409;
|
|
45
|
+
* · id 空闲 ⇒ 还没有谁的会话,落点是这位调用方自己将成的属主(`importingPrincipal`):各占各的,先提交者得(提交的属主 CAS 要求那一格仍空着,输的那一份
|
|
46
|
+
* 在提交前复判被拒)。一个主体的在飞导入既不挡、也不透给另一个主体 —— 租约键不含主体时,空闲 id 上任一主体都能占位,并从 409 读到「此 id 此刻有人在导」。
|
|
47
|
+
* 键的主体只来自门读的 `occupant` 与 `importingPrincipal`(两者都派生自属主存取器的 `listFilterOf` 与门读的会话面,不另读调用方),且占租约在 `pushGateOk` 之后。
|
|
48
|
+
*/
|
|
49
|
+
export declare function importLeaseKey(landingOwner: string | null, sessionId: string): string;
|
|
50
|
+
/** 一把导入租约:最后活动时刻 + 持有它的暂存 + 它守的会话 id(清扫与运维日志用,不从键里反解)。 */
|
|
51
|
+
export type ImportLease = {
|
|
52
|
+
at: number;
|
|
53
|
+
stagingId: string;
|
|
54
|
+
sessionId: string;
|
|
39
55
|
};
|
|
40
56
|
/** 单域独占状态 + 它的两个操作。每个 server 实例一份。 */
|
|
41
57
|
export interface SessionSyncLocal {
|
|
42
58
|
pendingStagings: Map<string, PendingStaging>;
|
|
43
|
-
importLeases: Map<string,
|
|
44
|
-
|
|
45
|
-
stagingId: string;
|
|
46
|
-
}>;
|
|
47
|
-
touchImportLease(sessionId: string): void;
|
|
59
|
+
importLeases: Map<string, ImportLease>;
|
|
60
|
+
touchImportLease(leaseKey: string): void;
|
|
48
61
|
cleanupStaging(stagingId: string): void;
|
|
62
|
+
/** 把闲置 ≥ `staleMs` 的租约连同它的暂存登记一并放掉(`staleMs <= 0` = 关,什么都不清);返回被放掉的(供运维日志)。 */
|
|
63
|
+
sweepStaleLeases(staleMs: number): Array<{
|
|
64
|
+
readonly sessionId: string;
|
|
65
|
+
readonly stagingId: string;
|
|
66
|
+
readonly ageMs: number;
|
|
67
|
+
}>;
|
|
49
68
|
}
|
|
50
69
|
export declare function createSessionSyncLocal(): SessionSyncLocal;
|
|
51
70
|
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。 */
|
|
@@ -7,10 +7,9 @@ import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntr
|
|
|
7
7
|
import { isUuidShape } from "../../security.js";
|
|
8
8
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
|
|
9
9
|
import { StampedSessionArchiveError } from "../../session-error-wire.js";
|
|
10
|
-
import { readSessionAgent } from "../../session-agent.js";
|
|
11
10
|
import { buildDisplaySafeUrl } from "../../trace/redact.js";
|
|
12
11
|
import { storeFaultClassOf } from "../../plugins/store-fault.js";
|
|
13
|
-
import { enter,
|
|
12
|
+
import { enter, readSessionAgent, readSyncGate, refuse } from "../owner-access.js";
|
|
14
13
|
import { listFilterOf, ownerKeyOf, sameKey } from "../../owner-faces.js";
|
|
15
14
|
const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
|
|
16
15
|
const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -18,26 +17,43 @@ import { SHA256_HEX_RE } from "../../digest-form.js";
|
|
|
18
17
|
import { assertNever } from "../route-ctx.js";
|
|
19
18
|
import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
|
|
20
19
|
import { readSessionPinned, readSessionTitle } from "../../session-row.js";
|
|
21
|
-
import { sessionFaceOfHead } from "../../session-incarnation.js";
|
|
22
20
|
import { defaultTaskRegistry } from "@sema-agent/core";
|
|
23
21
|
import { SessionActiveRunError, SessionForkInProgressError, SessionWriterPresentError } from "../../execution-placement.js";
|
|
24
22
|
import { probeSessionChildren, sessionTenantScope } from "../../session-children-retire.js";
|
|
25
23
|
import { liveChildrenRefusal } from "./background.js";
|
|
24
|
+
export function importLeaseKey(landingOwner, sessionId) {
|
|
25
|
+
return JSON.stringify([landingOwner, sessionId]);
|
|
26
|
+
}
|
|
26
27
|
export function createSessionSyncLocal() {
|
|
27
28
|
const pendingStagings = new Map();
|
|
28
29
|
const importLeases = new Map();
|
|
29
|
-
const touchImportLease = (
|
|
30
|
-
const cur = importLeases.get(
|
|
30
|
+
const touchImportLease = (leaseKey) => {
|
|
31
|
+
const cur = importLeases.get(leaseKey);
|
|
31
32
|
if (cur)
|
|
32
|
-
importLeases.set(
|
|
33
|
+
importLeases.set(leaseKey, { ...cur, at: Date.now() });
|
|
33
34
|
};
|
|
34
35
|
const cleanupStaging = (stagingId) => {
|
|
35
36
|
const p = pendingStagings.get(stagingId);
|
|
36
37
|
if (p)
|
|
37
|
-
importLeases.delete(p.
|
|
38
|
+
importLeases.delete(p.leaseKey);
|
|
38
39
|
pendingStagings.delete(stagingId);
|
|
39
40
|
};
|
|
40
|
-
|
|
41
|
+
const sweepStaleLeases = (staleMs) => {
|
|
42
|
+
if (staleMs <= 0)
|
|
43
|
+
return [];
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const swept = [];
|
|
46
|
+
for (const [key, lease] of [...importLeases]) {
|
|
47
|
+
const ageMs = now - lease.at;
|
|
48
|
+
if (ageMs < staleMs)
|
|
49
|
+
continue;
|
|
50
|
+
swept.push({ sessionId: lease.sessionId, stagingId: lease.stagingId, ageMs });
|
|
51
|
+
cleanupStaging(lease.stagingId);
|
|
52
|
+
importLeases.delete(key);
|
|
53
|
+
}
|
|
54
|
+
return swept;
|
|
55
|
+
};
|
|
56
|
+
return { pendingStagings, importLeases, touchImportLease, cleanupStaging, sweepStaleLeases };
|
|
41
57
|
}
|
|
42
58
|
async function firstPhaseRefusal(sessionStorage, sessionId, relation, consented) {
|
|
43
59
|
if (consented || (relation !== "fork" && relation !== "stale"))
|
|
@@ -57,7 +73,7 @@ async function firstPhaseRefusal(sessionStorage, sessionId, relation, consented)
|
|
|
57
73
|
export async function handleSessionSync(req, res, match, ctx) {
|
|
58
74
|
const { deps } = ctx;
|
|
59
75
|
const { readJson, readRawBody, safeDecode, rateLimited } = ctx.helpers;
|
|
60
|
-
const { pendingStagings, importLeases, touchImportLease, cleanupStaging } = ctx.local.sessionSync;
|
|
76
|
+
const { pendingStagings, importLeases, touchImportLease, cleanupStaging, sweepStaleLeases } = ctx.local.sessionSync;
|
|
61
77
|
{
|
|
62
78
|
{
|
|
63
79
|
const sync = facts.sessionSync(deps);
|
|
@@ -76,9 +92,8 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
76
92
|
return;
|
|
77
93
|
}
|
|
78
94
|
const judgeSessionGate = async () => {
|
|
79
|
-
const face =
|
|
80
|
-
|
|
81
|
-
return { face, mayPull: mayTouch, mayPush: face === undefined || mayTouch };
|
|
95
|
+
const { face, occupant, mayTouch } = await readSyncGate(syncEntry, sessionStorage, sessionId);
|
|
96
|
+
return { face, occupant, mayPull: face !== undefined && mayTouch, mayPush: occupant === undefined || mayTouch };
|
|
82
97
|
};
|
|
83
98
|
let gate;
|
|
84
99
|
try {
|
|
@@ -408,6 +423,8 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
408
423
|
if (!payloadVerified)
|
|
409
424
|
digestSaysDiffer = true;
|
|
410
425
|
}
|
|
426
|
+
const importingPrincipal = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
|
|
427
|
+
const operatorOk = syncEntry.caller.operator;
|
|
411
428
|
const placed = gateFace === undefined ? undefined : await sessionStorage.executionPlacement?.read(sessionId);
|
|
412
429
|
if (placed !== undefined) {
|
|
413
430
|
switch (placed.cell.role) {
|
|
@@ -464,10 +481,9 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
464
481
|
return;
|
|
465
482
|
}
|
|
466
483
|
}
|
|
467
|
-
const opOk = syncEntry.caller.operator;
|
|
468
484
|
if (deps.sessionPolicyStore) {
|
|
469
485
|
try {
|
|
470
|
-
await replayPolicyRecords(deps.sessionPolicyStore, sessionId, policy,
|
|
486
|
+
await replayPolicyRecords(deps.sessionPolicyStore, sessionId, policy, operatorOk);
|
|
471
487
|
}
|
|
472
488
|
catch (e) {
|
|
473
489
|
if (sendSessionPolicyError(res, e))
|
|
@@ -475,10 +491,9 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
475
491
|
throw e;
|
|
476
492
|
}
|
|
477
493
|
}
|
|
478
|
-
const importingP = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
|
|
479
494
|
if (deps.resumeAnchorStore)
|
|
480
495
|
for (const a of anchors)
|
|
481
|
-
await deps.resumeAnchorStore.put(sessionId, a.eventId, a.entryId,
|
|
496
|
+
await deps.resumeAnchorStore.put(sessionId, a.eventId, a.entryId, importingPrincipal);
|
|
482
497
|
if (!(await replaySessionRow(row, gateFace))) {
|
|
483
498
|
sendError(res, 409, "conflict", "sync conflict");
|
|
484
499
|
return;
|
|
@@ -490,33 +505,26 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
490
505
|
});
|
|
491
506
|
return;
|
|
492
507
|
}
|
|
493
|
-
const
|
|
508
|
+
const leaseKey = importLeaseKey(gate.occupant !== undefined ? gate.occupant.owner : importingPrincipal, sessionId);
|
|
509
|
+
const staleSecRaw = deps.config.syncImportLeaseStaleSec;
|
|
510
|
+
const staleSec = typeof staleSecRaw === "number" && Number.isFinite(staleSecRaw) ? staleSecRaw : 600;
|
|
511
|
+
const staleMs = staleSec * 1000;
|
|
512
|
+
for (const swept of sweepStaleLeases(staleMs))
|
|
513
|
+
deps.logger?.warn?.("sync_import_lease_taken_over", { sessionId: swept.sessionId, ageMs: swept.ageMs, staleMs, stagingId: swept.stagingId });
|
|
514
|
+
const heldLease = importLeases.get(leaseKey);
|
|
494
515
|
if (heldLease) {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (staleMs > 0 && ageMs >= staleMs) {
|
|
500
|
-
deps.logger?.warn?.("sync_import_lease_taken_over", { sessionId, ageMs, staleMs, stagingId: heldLease.stagingId });
|
|
501
|
-
cleanupStaging(heldLease.stagingId);
|
|
502
|
-
importLeases.delete(sessionId);
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
const retryAfterSec = staleMs > 0 ? Math.max(1, Math.ceil((staleMs - ageMs) / 1000)) : undefined;
|
|
506
|
-
sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
|
|
507
|
-
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined);
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
516
|
+
const retryAfterSec = staleMs > 0 ? Math.max(1, Math.ceil((staleMs - (Date.now() - heldLease.at)) / 1000)) : undefined;
|
|
517
|
+
sendError(res, 409, "import_in_flight", "an import is already in flight for this session"
|
|
518
|
+
+ (retryAfterSec !== undefined ? ` (a stalled holder is taken over after ${staleSec}s of inactivity; retry in ~${retryAfterSec}s)` : ""), retryAfterSec !== undefined ? { retryAfterSec } : undefined);
|
|
519
|
+
return;
|
|
510
520
|
}
|
|
511
|
-
const importingPrincipal = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
|
|
512
|
-
const operatorOk = syncEntry.caller.operator;
|
|
513
521
|
const token = newStagingToken();
|
|
514
522
|
const handle = sessionStorage.beginImportStaging(sessionId, token);
|
|
515
523
|
const stagingId = handle.stagingId;
|
|
516
|
-
importLeases.set(
|
|
524
|
+
importLeases.set(leaseKey, { at: Date.now(), stagingId, sessionId });
|
|
517
525
|
pendingStagings.set(stagingId, {
|
|
518
526
|
handle, realSessionId: sessionId, owner: importingPrincipal, ...(resolution ? { resolution } : {}), operatorOk,
|
|
519
|
-
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOwner: owner,
|
|
527
|
+
fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOwner: owner, leaseKey,
|
|
520
528
|
});
|
|
521
529
|
sendJson(res, 200, { stagingId, relation: relA.relation });
|
|
522
530
|
return;
|
|
@@ -550,7 +558,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
550
558
|
const utf8 = new StringDecoder("utf8");
|
|
551
559
|
try {
|
|
552
560
|
for await (const chunk of req) {
|
|
553
|
-
touchImportLease(pending.
|
|
561
|
+
touchImportLease(pending.leaseKey);
|
|
554
562
|
lineBuf += utf8.write(chunk);
|
|
555
563
|
if (Buffer.byteLength(lineBuf, "utf8") > SYNC_NDJSON_LINE_MAX_BYTES)
|
|
556
564
|
throw new PrecheckError(413, "NDJSON line too large");
|
|
@@ -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 列表同一权威模型(列表面 `list.read` 读档(`listFilterOf`):service 凭据不报主体〔部署声明 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` 时不算〕/ 只读 trace 凭据 / dev-open = fleet-wide
|
|
5
|
+
* owner-scope 与 trace 列表同一权威模型(列表面 `list.read` 读档(`listFilterOf`):service 凭据不报主体〔部署声明 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` 时不算〕/ 只读 trace 凭据 / dev-open 上不报主体 = fleet-wide;报了主体 = 钉住那位主体,部署形不改这一句)。本域零可变状态。
|
|
6
6
|
*/
|
|
7
7
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
8
|
import { type SessionListItem } from "../../security.js";
|
|
@@ -26,7 +26,7 @@ export interface SessionsLocal {
|
|
|
26
26
|
degraded?: boolean;
|
|
27
27
|
}>;
|
|
28
28
|
}>;
|
|
29
|
-
/**
|
|
29
|
+
/** [ref] 件4:`/a2a` 面板的短 TTL single-flight 缓存。与 `mcpStatusCache` **分开一张表**而不是
|
|
30
30
|
* 共用带前缀的键:两张面板的体不同形(`peers` vs `servers`),塞进一张表就得靠 unknown 兜住两形,
|
|
31
31
|
* 于是「取到的是哪一形」变成运行期约定 —— 两张表让它回到类型上。 */
|
|
32
32
|
a2aStatusCache: Map<string, {
|
|
@@ -14,8 +14,8 @@ import { readWritableSessionPermissionRules } from "../../session-policy-wire.js
|
|
|
14
14
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
|
|
15
15
|
import { gatedPerson, gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
|
|
16
16
|
import { personOf } from "../../owner-brands.js";
|
|
17
|
-
import {
|
|
18
|
-
import { admitSessionById, admitSessionRow, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter,
|
|
17
|
+
import { leafOfAdmittedHead, sessionFaceOfHead } from "../../session-incarnation.js";
|
|
18
|
+
import { admitSessionById, admitSessionForDeletion, admitSessionRow, admitSessionView, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter, judgeSessionById, protocolEntry, readOwnerCell, readVisibleLatestRun, refuse, sessionOwnerUnchanged } from "../owner-access.js";
|
|
19
19
|
import { assertNever } from "../route-ctx.js";
|
|
20
20
|
import * as facts from "../../capability-facts.js";
|
|
21
21
|
import { rosterAgentId } from "./agents-roster.js";
|
|
@@ -67,11 +67,9 @@ async function readLastLegMcp(deps, sessionId, entry) {
|
|
|
67
67
|
const runStore = deps.runStore;
|
|
68
68
|
if (!runStore || sessionId === null)
|
|
69
69
|
return undefined;
|
|
70
|
-
const latest = await runStore
|
|
70
|
+
const latest = await readVisibleLatestRun(entry, runStore, sessionId);
|
|
71
71
|
if (latest === undefined)
|
|
72
72
|
return undefined;
|
|
73
|
-
if (!seesRun(entry, latest.owner))
|
|
74
|
-
return undefined;
|
|
75
73
|
const events = await runStore.getEvents(latest.taskId, 0);
|
|
76
74
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
77
75
|
const ev = events[i];
|
|
@@ -87,26 +85,7 @@ async function readLastLegMcp(deps, sessionId, entry) {
|
|
|
87
85
|
export async function handleSessions(req, res, match, ctx) {
|
|
88
86
|
const { deps } = ctx;
|
|
89
87
|
const { readJson, safeDecode } = ctx.helpers;
|
|
90
|
-
const readOwnedSessionView =
|
|
91
|
-
const headOf = headReadOf(deps.sessionStorage);
|
|
92
|
-
if (headOf !== undefined) {
|
|
93
|
-
const head = await readOwnerCell(entry, sessionId, () => headOf(sessionId));
|
|
94
|
-
if ((await admitSessionRow(entry, res, deps.sessionStorage, sessionId, head)) === undefined)
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
97
|
-
let audit;
|
|
98
|
-
try {
|
|
99
|
-
audit = await sessionAudit(sessionId, readOpts);
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
102
|
-
if (headOf !== undefined)
|
|
103
|
-
throw e;
|
|
104
|
-
throw new Error(`session view read failed before ownership could be established: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
105
|
-
}
|
|
106
|
-
if ((await admitSessionRow(entry, res, deps.sessionStorage, sessionId, audit)) === undefined)
|
|
107
|
-
return undefined;
|
|
108
|
-
return audit;
|
|
109
|
-
};
|
|
88
|
+
const readOwnedSessionView = (entry, sessionAudit, sessionId, readOpts) => admitSessionView(entry, res, deps.sessionStorage, sessionAudit, sessionId, readOpts);
|
|
110
89
|
const sse = ctx.local.sessions;
|
|
111
90
|
const { mcpStatusCache, a2aStatusCache } = ctx.local.sessions;
|
|
112
91
|
{
|
|
@@ -262,8 +241,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
262
241
|
}
|
|
263
242
|
{
|
|
264
243
|
if (match.decl.id === "session-head") {
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
244
|
+
const headFact = facts.sessionOwnership(deps);
|
|
245
|
+
if (!headFact.available) {
|
|
267
246
|
sendError(res, 501, "capability.session_store_required", "session head probe requires a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
268
247
|
return;
|
|
269
248
|
}
|
|
@@ -273,8 +252,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
273
252
|
return;
|
|
274
253
|
}
|
|
275
254
|
const headEntry = callerEntry(req, deps, match.decl.id);
|
|
276
|
-
const
|
|
277
|
-
const admittedHead = await admitSessionRow(headEntry, res, deps.sessionStorage, headSession, headRead);
|
|
255
|
+
const admittedHead = await admitSessionById(headEntry, res, headFact.sessionStorage, headSession);
|
|
278
256
|
if (admittedHead === undefined)
|
|
279
257
|
return;
|
|
280
258
|
const leafId = leafOfAdmittedHead(admittedHead.row);
|
|
@@ -317,8 +295,6 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
317
295
|
return;
|
|
318
296
|
}
|
|
319
297
|
const evEntry = callerEntry(req, deps, match.decl.id);
|
|
320
|
-
const readHead = (id) => readOwnerCell(evEntry, id, () => evFact.sessionStorage.headOf(id));
|
|
321
|
-
const readOwner = async (id) => (await readHead(id))?.owner;
|
|
322
298
|
const watch = evFact.watch;
|
|
323
299
|
const evSession = safeDecode(match.params.sessionId);
|
|
324
300
|
if (evSession === null) {
|
|
@@ -354,7 +330,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
354
330
|
return;
|
|
355
331
|
}
|
|
356
332
|
sse.sseOwnerLookups++;
|
|
357
|
-
const admissionP =
|
|
333
|
+
const admissionP = judgeSessionById(evEntry, evFact.sessionStorage, evSession);
|
|
358
334
|
void admissionP.catch(() => undefined).then(() => { sse.sseOwnerLookups--; });
|
|
359
335
|
let stallTimer;
|
|
360
336
|
let admission;
|
|
@@ -381,12 +357,13 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
381
357
|
refuse(evEntry, res, "missing");
|
|
382
358
|
return;
|
|
383
359
|
}
|
|
384
|
-
|
|
360
|
+
const evAdmitted = deliverVerdict(evEntry, res, admission);
|
|
361
|
+
if (evAdmitted === undefined) {
|
|
385
362
|
cleanup();
|
|
386
363
|
return;
|
|
387
364
|
}
|
|
388
|
-
leafOfAdmittedHead(
|
|
389
|
-
const owner =
|
|
365
|
+
leafOfAdmittedHead(evAdmitted.row);
|
|
366
|
+
const owner = evAdmitted.row.owner;
|
|
390
367
|
const q = new URLSearchParams(match.search);
|
|
391
368
|
const lastLeafId = q.get("lastLeafId");
|
|
392
369
|
let first = true;
|
|
@@ -485,9 +462,9 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
485
462
|
}
|
|
486
463
|
ownerCheckPending = true;
|
|
487
464
|
ownerCheckStartedAt = Date.now();
|
|
488
|
-
void
|
|
465
|
+
void sessionOwnerUnchanged(evEntry, evFact.sessionStorage, evSession, owner).then((same) => {
|
|
489
466
|
ownerCheckPending = false;
|
|
490
|
-
if (!closed &&
|
|
467
|
+
if (!closed && !same)
|
|
491
468
|
onEnd();
|
|
492
469
|
}).catch(() => { ownerCheckPending = false; });
|
|
493
470
|
}, hbMs);
|
|
@@ -706,14 +683,11 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
706
683
|
const store = rowFact.sessionStorage;
|
|
707
684
|
const sessionId = match.params.sessionId;
|
|
708
685
|
const patchEntry = callerEntry(req, deps, match.decl.id);
|
|
709
|
-
const
|
|
710
|
-
if (
|
|
711
|
-
refuse(patchEntry, res, "missing");
|
|
686
|
+
const patchAdmitted = await admitSessionById(patchEntry, res, store, sessionId);
|
|
687
|
+
if (patchAdmitted === undefined)
|
|
712
688
|
return;
|
|
713
|
-
|
|
689
|
+
const head = patchAdmitted.row;
|
|
714
690
|
const face = sessionFaceOfHead(sessionId, head);
|
|
715
|
-
if ((await admitSessionRow(patchEntry, res, store, sessionId, face)) === undefined)
|
|
716
|
-
return;
|
|
717
691
|
leafOfAdmittedHead(head);
|
|
718
692
|
const read = readSessionRowPatch(await readJson(req));
|
|
719
693
|
if ("problem" in read) {
|
|
@@ -903,7 +877,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
903
877
|
sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
|
|
904
878
|
return;
|
|
905
879
|
}
|
|
906
|
-
const deleteAdmitted = await
|
|
880
|
+
const deleteAdmitted = await admitSessionForDeletion(deleteEntry, res, deleteFact.sessionStorage, sessionId);
|
|
907
881
|
if (deleteAdmitted === undefined)
|
|
908
882
|
return;
|
|
909
883
|
const owner = deleteAdmitted.row.owner;
|