@sema-agent/server 7.132.0 → 7.133.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 +8 -9
- package/README.zh-CN.md +7 -6
- package/USAGE.md +174 -55
- package/deploy/sema-up/README.md +19 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
- package/deploy/sema-up/chart/templates/server.yaml +5 -2
- package/deploy/sema-up/stack/compose.yaml +3 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
- package/dist/adoption/plan.d.ts +3 -2
- package/dist/adoption/plan.js +7 -5
- package/dist/adoption/runner.js +3 -10
- package/dist/adoption/sql.d.ts +3 -2
- package/dist/adoption/sql.js +3 -5
- package/dist/adoption/wire.d.ts +4 -1
- package/dist/adoption/wire.js +1 -0
- package/dist/approval-ask-audit-store.d.ts +31 -13
- package/dist/approval-ask-audit-store.js +272 -74
- package/dist/approval-ask-machine.d.ts +15 -0
- package/dist/approval-ask-machine.js +6 -0
- package/dist/approval-ask-retention.d.ts +136 -0
- package/dist/approval-ask-retention.js +102 -0
- package/dist/approval-card.d.ts +79 -19
- package/dist/approval-card.js +44 -13
- package/dist/approval-deny-reasons.d.ts +1 -1
- package/dist/approval.d.ts +9 -0
- package/dist/approval.js +24 -3
- package/dist/audit.d.ts +27 -10
- package/dist/audit.js +12 -16
- package/dist/bake-runner/main.js +3 -2
- package/dist/boot/deferred-sandbox-path-env.js +1 -1
- package/dist/boot/execution-env.js +9 -7
- package/dist/boot/execution-lane.js +11 -0
- package/dist/boot/leader.js +2 -0
- package/dist/boot/memory-boundary.js +2 -2
- package/dist/boot/memory-consolidation.d.ts +5 -4
- package/dist/boot/memory-consolidation.js +7 -6
- package/dist/boot/org-delivery.d.ts +28 -0
- package/dist/boot/org-delivery.js +20 -0
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +21 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +60 -81
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/session-faces.d.ts +1 -0
- package/dist/boot/session-faces.js +4 -2
- package/dist/boot/stage-02-web-admin-pull.js +4 -1
- package/dist/boot/stage-06-runners.d.ts +2 -0
- package/dist/boot/stage-06-runners.js +6 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -0
- package/dist/boot/stage-07-capability-layer.js +24 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -0
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +3 -0
- package/dist/boot/stage-10-http-server.d.ts +3 -0
- package/dist/boot/stage-10-http-server.js +5 -6
- package/dist/boot/stores.js +2 -2
- package/dist/boot/web-admin.d.ts +4 -0
- package/dist/boot/web-admin.js +151 -63
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/brain.js +8 -3
- package/dist/breaker-key.d.ts +6 -0
- package/dist/breaker-key.js +29 -0
- package/dist/caller-text-echo.d.ts +9 -6
- package/dist/caller-text-echo.js +8 -11
- package/dist/capabilities/center-plugins.d.ts +7 -0
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +21 -0
- package/dist/capabilities/hands-lane.js +10 -0
- package/dist/capabilities/mcp-probe.d.ts +36 -4
- package/dist/capabilities/mcp-probe.js +27 -4
- package/dist/capabilities/scenarios.d.ts +63 -7
- package/dist/capabilities/scenarios.js +39 -7
- package/dist/capability-facts.d.ts +34 -4
- package/dist/capability-facts.js +28 -2
- package/dist/center-config-tier.d.ts +69 -0
- package/dist/center-config-tier.js +42 -0
- package/dist/checkpoint-identity.d.ts +16 -0
- package/dist/checkpoint-identity.js +19 -0
- package/dist/config-catalog.d.ts +17 -10
- package/dist/config-catalog.js +43 -28
- package/dist/config-d-migration.d.ts +3 -1
- package/dist/config-d-migration.js +2 -2
- package/dist/config-dir-trust.d.ts +114 -0
- package/dist/config-dir-trust.js +388 -0
- package/dist/config-invariants.d.ts +18 -5
- package/dist/config-invariants.js +35 -3
- package/dist/config-provider.d.ts +6 -1
- package/dist/config-provider.js +6 -2
- package/dist/config-types.d.ts +71 -14
- package/dist/config-types.js +8 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +121 -59
- package/dist/delegated-session-owner.d.ts +185 -0
- package/dist/delegated-session-owner.js +119 -0
- package/dist/device-protocol.d.ts +5 -3
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.js +21 -2
- package/dist/device-ws-protocol.d.ts +70 -6
- package/dist/device-ws-protocol.js +50 -23
- package/dist/elicitation.js +3 -2
- package/dist/entry-problems.d.ts +34 -0
- package/dist/entry-problems.js +14 -0
- package/dist/execution-lane-caps.d.ts +49 -10
- package/dist/execution-lane-caps.js +43 -4
- package/dist/field-conflict.d.ts +43 -0
- package/dist/field-conflict.js +20 -0
- package/dist/fleet/fleet-bus.d.ts +80 -6
- package/dist/fleet/fleet-bus.js +41 -9
- package/dist/hooks/classic-stations.d.ts +10 -5
- package/dist/hooks/classic-stations.js +4 -4
- package/dist/hooks/command-spawn.d.ts +57 -0
- package/dist/hooks/command-spawn.js +139 -0
- package/dist/hooks/hook-llm.js +2 -2
- package/dist/hooks/hook-runner.d.ts +127 -25
- package/dist/hooks/hook-runner.js +668 -287
- package/dist/hooks/hook-source.d.ts +44 -0
- package/dist/hooks/hook-source.js +40 -0
- package/dist/http/active-run-conflict.d.ts +5 -13
- package/dist/http/active-run-conflict.js +17 -27
- package/dist/http/admission.js +103 -60
- package/dist/http/approval-sweeps.js +55 -18
- package/dist/http/caller-trust.d.ts +77 -0
- package/dist/http/caller-trust.js +143 -0
- package/dist/http/checkpoint-ctx.d.ts +16 -16
- package/dist/http/checkpoint-ctx.js +13 -10
- package/dist/http/claimed-run.d.ts +4 -3
- package/dist/http/device-placement.d.ts +2 -4
- package/dist/http/device-placement.js +2 -1
- package/dist/http/dispatch.js +10 -4
- package/dist/http/durable-run-leg.d.ts +4 -32
- package/dist/http/durable-run-leg.js +34 -10
- package/dist/http/owner-access.d.ts +56 -0
- package/dist/http/owner-access.js +42 -0
- package/dist/http/principal-gate.d.ts +48 -23
- package/dist/http/principal-gate.js +39 -12
- package/dist/http/readiness.d.ts +11 -5
- package/dist/http/readiness.js +10 -3
- package/dist/http/request-guards.d.ts +16 -23
- package/dist/http/request-guards.js +30 -15
- package/dist/http/resume-ctx-read.d.ts +2 -5
- package/dist/http/resume-ctx-read.js +7 -4
- package/dist/http/resume-legs.d.ts +3 -4
- package/dist/http/resume-legs.js +126 -69
- package/dist/http/retired-service-deps.d.ts +43 -0
- package/dist/http/retired-service-deps.js +18 -0
- package/dist/http/route-ctx.d.ts +44 -28
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/a2a-serve.js +3 -4
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.js +1 -1
- package/dist/http/routes/admin-memory-optout.js +5 -1
- package/dist/http/routes/adoption.js +2 -9
- package/dist/http/routes/approvals-assistant.js +49 -48
- package/dist/http/routes/background.js +3 -2
- package/dist/http/routes/capabilities.js +20 -4
- package/dist/http/routes/config-catalog.d.ts +3 -3
- package/dist/http/routes/config-catalog.js +1 -1
- package/dist/http/routes/devices.js +3 -17
- package/dist/http/routes/diagnostics.js +5 -1
- package/dist/http/routes/images.js +6 -7
- package/dist/http/routes/leader.js +1 -8
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +3 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/memory-origin.js +1 -1
- package/dist/http/routes/memory-policy.d.ts +1 -1
- package/dist/http/routes/memory-policy.js +3 -2
- package/dist/http/routes/notify-wake.js +8 -21
- package/dist/http/routes/observability.js +2 -2
- package/dist/http/routes/principal-inbox.d.ts +39 -0
- package/dist/http/routes/principal-inbox.js +76 -0
- package/dist/http/routes/retention-ops.js +2 -9
- package/dist/http/routes/rules.js +5 -18
- package/dist/http/routes/runs.d.ts +5 -0
- package/dist/http/routes/runs.js +33 -106
- package/dist/http/routes/session-sync.js +15 -28
- package/dist/http/routes/sessions.js +30 -40
- package/dist/http/routes/side-query.js +6 -4
- package/dist/http/routes/tasks.js +50 -14
- package/dist/http/routes/workflows.d.ts +351 -0
- package/dist/http/routes/workflows.js +153 -119
- package/dist/http/send.d.ts +24 -3
- package/dist/http/send.js +14 -2
- package/dist/http/server.d.ts +17 -18
- package/dist/http/server.js +15 -13
- package/dist/http/stored-body-replay.js +17 -4
- package/dist/http/submit-ctx.js +4 -3
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +5 -4
- package/dist/http/wire-types.d.ts +10 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/lane-home-probe.d.ts +78 -9
- package/dist/lane-home-probe.js +78 -12
- package/dist/leader/endpoint.d.ts +4 -1
- package/dist/leader/endpoint.js +11 -10
- package/dist/limit-refusal.d.ts +17 -5
- package/dist/limit-refusal.js +4 -1
- package/dist/mcp-assertion-header.d.ts +31 -10
- package/dist/mcp-assertion-header.js +10 -3
- package/dist/mcp-tool-faces.js +1 -3
- package/dist/memory-project-layer.d.ts +37 -0
- package/dist/memory-project-layer.js +46 -0
- package/dist/memory-scope.d.ts +23 -16
- package/dist/memory-scope.js +24 -8
- package/dist/memory-sync-client.d.ts +27 -13
- package/dist/memory-sync-client.js +107 -18
- package/dist/model-entry-refusal.d.ts +9 -1
- package/dist/model-entry-refusal.js +6 -1
- package/dist/model-plane-face.d.ts +51 -0
- package/dist/model-plane-face.js +24 -0
- package/dist/model-provider.d.ts +5 -0
- package/dist/model-provider.js +4 -2
- package/dist/model-route-endpoint.d.ts +8 -11
- package/dist/model-route-endpoint.js +4 -7
- package/dist/model-route-env.d.ts +22 -0
- package/dist/model-route-env.js +2 -0
- package/dist/model-route-gap.d.ts +60 -30
- package/dist/model-route-gap.js +21 -15
- package/dist/observability/corrupt-read-seat.d.ts +7 -0
- package/dist/observability/corrupt-read-seat.js +9 -0
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +18 -4
- package/dist/observability/fail-open.d.ts +35 -9
- package/dist/observability/fail-open.js +35 -9
- package/dist/observability/metrics.js +1 -0
- package/dist/observability/prompt-manifest.d.ts +3 -17
- package/dist/observability/prompt-manifest.js +45 -42
- package/dist/observability/tool-trace.js +9 -9
- package/dist/operator-ask.d.ts +10 -0
- package/dist/operator-ask.js +3 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
- package/dist/orchestration/workflow-agent-session-index.js +9 -13
- package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
- package/dist/orchestration/workflow-completion-inbox.js +13 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
- package/dist/orchestration/workflow-notify-journal.js +27 -34
- package/dist/orchestration/workflow-park-decision.d.ts +89 -0
- package/dist/orchestration/workflow-park-decision.js +118 -0
- package/dist/org-delivery-gate.d.ts +81 -0
- package/dist/org-delivery-gate.js +59 -0
- package/dist/panel-tool-names.d.ts +23 -0
- package/dist/panel-tool-names.js +8 -0
- package/dist/parked-decide.d.ts +39 -6
- package/dist/parked-decide.js +98 -51
- package/dist/parked-revive-surface.d.ts +9 -2
- package/dist/parked-revive-surface.js +11 -8
- package/dist/permission-rule-list.d.ts +1 -1
- package/dist/permission-rule-list.js +2 -2
- package/dist/plugins/approval-ask-store-file.d.ts +16 -1
- package/dist/plugins/approval-ask-store-file.js +21 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
- package/dist/plugins/approval-ask-store-memory.js +34 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
- package/dist/plugins/approval-ask-store-sql.js +37 -1
- package/dist/plugins/blob-backend.d.ts +38 -25
- package/dist/plugins/blob-backend.js +29 -19
- package/dist/plugins/breaker-state-sql.d.ts +2 -2
- package/dist/plugins/breaker-state-sql.js +4 -4
- package/dist/plugins/caching-session-store.d.ts +3 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
- package/dist/plugins/checkpoint-store-sql.js +20 -2
- package/dist/plugins/file-history-store-sql.d.ts +5 -2
- package/dist/plugins/file-history-store-sql.js +3 -1
- package/dist/plugins/file-run-store.d.ts +5 -13
- package/dist/plugins/file-run-store.js +9 -0
- package/dist/plugins/fork-routing-session-store.d.ts +9 -1
- package/dist/plugins/fork-routing-session-store.js +12 -1
- package/dist/plugins/host-platform.d.ts +2 -0
- package/dist/plugins/host-platform.js +2 -1
- package/dist/plugins/local-checkpoint-store.d.ts +2 -0
- package/dist/plugins/local-checkpoint-store.js +6 -1
- package/dist/plugins/local-session-store.d.ts +1 -1
- package/dist/plugins/local-session-window.d.ts +39 -0
- package/dist/plugins/local-session-window.js +54 -0
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
- package/dist/plugins/mailbox-store-sql.d.ts +30 -48
- package/dist/plugins/mailbox-store-sql.js +77 -52
- package/dist/plugins/memory-embedder-fingerprint.js +2 -1
- package/dist/plugins/memory-run-store.d.ts +5 -13
- package/dist/plugins/memory-run-store.js +8 -0
- package/dist/plugins/one-time-migrations.d.ts +110 -5
- package/dist/plugins/one-time-migrations.js +320 -8
- package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
- package/dist/plugins/org-delivery-quota-store.js +228 -0
- package/dist/plugins/pg-pool.js +39 -7
- package/dist/plugins/pg-session-storage.d.ts +17 -4
- package/dist/plugins/pg-session-storage.js +29 -6
- package/dist/plugins/remote-env-adb.d.ts +6 -12
- package/dist/plugins/remote-env-adb.js +12 -2
- package/dist/plugins/remote-env-k8s.d.ts +8 -12
- package/dist/plugins/remote-env-k8s.js +23 -5
- package/dist/plugins/remote-env-local-docker.d.ts +10 -15
- package/dist/plugins/remote-env-local-docker.js +12 -9
- package/dist/plugins/remote-env-ssh.d.ts +10 -16
- package/dist/plugins/remote-env-ssh.js +17 -11
- package/dist/plugins/run-store-sql.d.ts +16 -14
- package/dist/plugins/run-store-sql.js +22 -2
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-user-file.js +2 -2
- package/dist/plugins/session-epoch-anchor.d.ts +32 -6
- package/dist/plugins/session-epoch-anchor.js +2 -3
- package/dist/plugins/session-store.d.ts +18 -8
- package/dist/plugins/session-store.js +15 -4
- package/dist/plugins/sql-driver.d.ts +74 -11
- package/dist/plugins/sql-driver.js +210 -12
- package/dist/plugins/sql-errors.d.ts +23 -0
- package/dist/plugins/sql-errors.js +21 -0
- package/dist/plugins/store-backend.d.ts +36 -1
- package/dist/plugins/store-backend.js +40 -14
- package/dist/plugins/store-contracts.d.ts +42 -0
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/tidb-pool.d.ts +6 -1
- package/dist/plugins/tidb-pool.js +44 -10
- package/dist/plugins/tidb-session-storage.d.ts +3 -2
- package/dist/plugins/tidb-session-storage.js +2 -2
- package/dist/plugins/tidb-session-store.d.ts +24 -4
- package/dist/plugins/tidb-session-store.js +28 -4
- package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
- package/dist/plugins/workflow-journal-store-sql.js +19 -6
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
- package/dist/plugins/workflow-run-store-sql.js +50 -9
- package/dist/posture-source.d.ts +18 -10
- package/dist/posture-source.js +4 -1
- package/dist/project-identity.d.ts +45 -6
- package/dist/project-identity.js +162 -8
- package/dist/project-layer-key.d.ts +28 -0
- package/dist/project-layer-key.js +29 -0
- package/dist/question.js +3 -2
- package/dist/read-deny-entry.d.ts +25 -0
- package/dist/read-deny-entry.js +12 -0
- package/dist/read-face-posture.d.ts +37 -11
- package/dist/read-face-posture.js +32 -3
- package/dist/request-key-closure.d.ts +7 -0
- package/dist/request-key-closure.js +9 -1
- package/dist/request-value-closure.d.ts +5 -0
- package/dist/request-value-closure.js +11 -3
- package/dist/rule-entry-list.d.ts +26 -2
- package/dist/rule-entry-list.js +72 -12
- package/dist/rules-consent.js +3 -4
- package/dist/run-acceptance.d.ts +19 -3
- package/dist/run-acceptance.js +13 -1
- package/dist/run-local.js +36 -6
- package/dist/runs.d.ts +10 -9
- package/dist/runs.js +21 -3
- package/dist/runtime-caps-resolver.d.ts +16 -6
- package/dist/runtime-caps-resolver.js +18 -5
- package/dist/sandbox-pkg-source.d.ts +19 -1
- package/dist/sandbox-pkg-source.js +29 -7
- package/dist/security.d.ts +87 -9
- package/dist/security.js +52 -9
- package/dist/session-error-wire.d.ts +19 -2
- package/dist/session-error-wire.js +5 -1
- package/dist/session-titled-frame.d.ts +33 -0
- package/dist/session-titled-frame.js +4 -0
- package/dist/session-titler.d.ts +9 -1
- package/dist/session-titler.js +15 -4
- package/dist/spec-fields.js +21 -2
- package/dist/sql-engine-posture.d.ts +3 -1
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +3 -3
- package/dist/task-mcp.js +7 -66
- package/dist/task-settings.d.ts +89 -88
- package/dist/task-settings.js +71 -63
- package/dist/text-bidi.d.ts +0 -4
- package/dist/text-bidi.js +1 -3
- package/dist/tighten-only-seats.d.ts +150 -0
- package/dist/tighten-only-seats.js +196 -0
- package/dist/tool-approval.d.ts +303 -87
- package/dist/tool-approval.js +623 -239
- package/dist/trace/core-keyset-guard.d.ts +104 -22
- package/dist/trace/exit-seat.d.ts +28 -0
- package/dist/trace/exit-seat.js +76 -0
- package/dist/trace/ledger-events.d.ts +3 -2
- package/dist/trace/project.d.ts +80 -12
- package/dist/trace/project.js +41 -28
- package/dist/trace/projection-table.d.ts +100 -0
- package/dist/trace/projection-table.js +130 -0
- package/dist/trace/redact.d.ts +54 -0
- package/dist/trace/redact.js +778 -152
- package/dist/trace/self-minted-wire.d.ts +345 -1
- package/dist/trace/self-minted-wire.js +117 -1
- package/dist/trace/wire-projection-faces.d.ts +298 -2
- package/dist/trace/wire-projection-faces.js +154 -13
- package/dist/url-refusal.js +2 -0
- package/dist/web-admin-client/apply-effective.d.ts +12 -0
- package/dist/web-admin-client/apply-effective.js +56 -5
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/read-face.js +5 -9
- package/dist/web-admin-client/skills-mcp.js +4 -5
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
- package/package.json +7 -3
package/dist/http/resume-legs.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
4
|
-
import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
4
|
+
import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
5
|
+
import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
|
|
6
|
+
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";
|
|
5
7
|
const sessionFaceUnavailable = async (sessionId) => {
|
|
6
8
|
throw new Error(`parked decide cannot verify which occupant session ${sessionId} belongs to: this deployment's session store has no sessionFaceOf read (every built-in store has one) — refusing rather than resuming unverified`);
|
|
7
9
|
};
|
|
@@ -9,6 +11,7 @@ import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../se
|
|
|
9
11
|
import { parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
10
12
|
import { failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane } from "../terminal.js";
|
|
11
13
|
import { buildCancelledLedgerRow, createRunAbort, mintCancelledResult } from "../run-cancel-context.js";
|
|
14
|
+
import { isTerminalRunStatus } from "../plugins/store-contracts.js";
|
|
12
15
|
import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
|
|
13
16
|
import { recordResumeResourceWindow } from "../resource-window.js";
|
|
14
17
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
@@ -32,7 +35,7 @@ import { errorText } from "../observability/err-text.js";
|
|
|
32
35
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
33
36
|
import { verifyRoundsFromBody } from "./verify-rounds.js";
|
|
34
37
|
import { planReviewDecisionExitsPlan, storedBodyPermissionMode } from "../task-settings.js";
|
|
35
|
-
import { acceptedResumingReceipt, buildHostLegOutcome } from "./route-ctx.js";
|
|
38
|
+
import { acceptedResumingReceipt, acceptedTaskIdOf, buildHostLegOutcome } from "./route-ctx.js";
|
|
36
39
|
import { mintAndDriveDurableRun } from "./durable-run-leg.js";
|
|
37
40
|
import { createSessionsLocal } from "./routes/sessions.js";
|
|
38
41
|
import { createSessionSyncLocal } from "./routes/session-sync.js";
|
|
@@ -50,6 +53,7 @@ import { refusalBody } from "./refusal-body.js";
|
|
|
50
53
|
import { storedBodyReplayRefusal } from "./stored-body-replay.js";
|
|
51
54
|
import { httpErrorRefusal } from "./send.js";
|
|
52
55
|
import { createRunAcceptance } from "../run-acceptance.js";
|
|
56
|
+
import { fieldConflictFacts } from "../field-conflict.js";
|
|
53
57
|
function legMayHaveReopenedCheckpoint(r) {
|
|
54
58
|
return r.terminal.kind === "failed" && nestedPauseOf(r.terminal) === undefined;
|
|
55
59
|
}
|
|
@@ -170,7 +174,7 @@ function decisionRefusalCauseOf(e, gate) {
|
|
|
170
174
|
return role === undefined ? undefined : FIELD_ROLE_CAUSE[role];
|
|
171
175
|
}
|
|
172
176
|
function notResumableAnswer() {
|
|
173
|
-
return { status: 409, body: {
|
|
177
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.not_resumable", message: "run is not in a resumable (suspended) state (already resumed, decided, or expired)" }) };
|
|
174
178
|
}
|
|
175
179
|
export function checkpointRefusalAnswer(e, at) {
|
|
176
180
|
const refusalCause = e.code === "checkpoint.invalid_outcome" ? decisionRefusalCauseOf(e, at.gate) : undefined;
|
|
@@ -212,7 +216,7 @@ export function createResumeLegs(ctx) {
|
|
|
212
216
|
}
|
|
213
217
|
if (!token && !attributable && !seenUnreadable) {
|
|
214
218
|
deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true });
|
|
215
|
-
return { status: 404, body: {
|
|
219
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
|
|
216
220
|
}
|
|
217
221
|
let currentPending;
|
|
218
222
|
if (token && decider !== undefined) {
|
|
@@ -225,7 +229,7 @@ export function createResumeLegs(ctx) {
|
|
|
225
229
|
const scopeOk = cur !== null && (decider.explicitOperator || curOwner === undefined || curOwner === decider.principal);
|
|
226
230
|
if (cur && cur.sessionId === sessionId && !scopeOk) {
|
|
227
231
|
deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true, foreignCurrentSuppressed: true });
|
|
228
|
-
return { status: 404, body: {
|
|
232
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
|
|
229
233
|
}
|
|
230
234
|
if (cur && cur.status === "pending" && cur.sessionId === sessionId && scopeOk && curPa?.kind === "tool_approval" && curPa.toolCallId) {
|
|
231
235
|
currentPending = {
|
|
@@ -237,22 +241,24 @@ export function createResumeLegs(ctx) {
|
|
|
237
241
|
}
|
|
238
242
|
return {
|
|
239
243
|
status: 409,
|
|
240
|
-
body: {
|
|
241
|
-
error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
|
|
244
|
+
body: refusalBody({
|
|
242
245
|
errorCode: "approval_stale",
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
message: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
|
|
247
|
+
facts: {
|
|
248
|
+
...(attributable ? { terminal: "resolved" } : {}),
|
|
249
|
+
...(currentPending ? { currentPending } : {}),
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
246
252
|
};
|
|
247
253
|
}
|
|
248
254
|
if (!token) {
|
|
249
255
|
deps.logger?.info?.("approval_decide_no_pending", { sessionId });
|
|
250
|
-
return { status: 404, body: {
|
|
256
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
|
|
251
257
|
}
|
|
252
258
|
const cpPromise = cs.get(token);
|
|
253
259
|
const ctxPromise = cs.getCtx(sessionId);
|
|
254
260
|
void ctxPromise.catch(() => { });
|
|
255
|
-
const noApproval = { status: 404, body: {
|
|
261
|
+
const noApproval = { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
|
|
256
262
|
let cp;
|
|
257
263
|
try {
|
|
258
264
|
cp = await cpPromise;
|
|
@@ -263,18 +269,18 @@ export function createResumeLegs(ctx) {
|
|
|
263
269
|
deps.logger?.warn?.("approval_decide_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
|
|
264
270
|
return noApproval;
|
|
265
271
|
}
|
|
266
|
-
return { status: 409, body: {
|
|
272
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) };
|
|
267
273
|
}
|
|
268
274
|
if (!cp) {
|
|
269
275
|
if (!(await mayDiscloseRowError(cs, token, decider)))
|
|
270
276
|
return noApproval;
|
|
271
|
-
return { status: 404, body: {
|
|
277
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) };
|
|
272
278
|
}
|
|
273
279
|
if (decider !== undefined && !decider.explicitOperator) {
|
|
274
280
|
const cpOwner = decodeCheckpointScope(cp.scope);
|
|
275
281
|
if (cpOwner !== undefined && cpOwner !== decider.principal) {
|
|
276
282
|
deps.logger?.warn?.("approval_decide_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
|
|
277
|
-
return { status: 404, body: {
|
|
283
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
|
|
278
284
|
}
|
|
279
285
|
}
|
|
280
286
|
const decideGateKind = cp.gate?.kind;
|
|
@@ -287,21 +293,25 @@ export function createResumeLegs(ctx) {
|
|
|
287
293
|
: null;
|
|
288
294
|
return {
|
|
289
295
|
status: 409,
|
|
290
|
-
body: {
|
|
291
|
-
error: `task is suspended on a '${decideGateKind ?? "unknown"}' gate, not a tool approval — a plan_review gate is resolved via POST /v1/assistant/tasks/:id/plan_review, a resource_limit suspension via POST /v1/assistant/tasks/:id/resume`,
|
|
296
|
+
body: refusalBody({
|
|
292
297
|
errorCode: "gate_not_tool_approval",
|
|
293
|
-
|
|
294
|
-
|
|
298
|
+
message: `task is suspended on a '${decideGateKind ?? "unknown"}' gate, not a tool approval — a plan_review gate is resolved via POST /v1/assistant/tasks/:id/plan_review, a resource_limit suspension via POST /v1/assistant/tasks/:id/resume`,
|
|
299
|
+
facts: {
|
|
300
|
+
...(decideGateKind ? { pendingGate: { kind: decideGateKind, ...(gnPath ? { decidePath: gnPath } : {}) } } : {}),
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
295
303
|
};
|
|
296
304
|
}
|
|
297
305
|
if (req !== undefined && !binding?.checkpointToken && !binding?.boundCallId) {
|
|
298
306
|
return {
|
|
299
307
|
status: 400,
|
|
300
|
-
body: {
|
|
301
|
-
error: "decide requires the decision-action binding — echo checkpointToken and/or boundCallId from the pending approval (GET /v1/approvals)",
|
|
308
|
+
body: refusalBody({
|
|
302
309
|
errorCode: "decide_requires_binding",
|
|
303
|
-
|
|
304
|
-
|
|
310
|
+
message: "decide requires the decision-action binding — echo checkpointToken and/or boundCallId from the pending approval (GET /v1/approvals)",
|
|
311
|
+
facts: {
|
|
312
|
+
bindingEnforced: true,
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
305
315
|
};
|
|
306
316
|
}
|
|
307
317
|
const s57RowCallId = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.toolCallId : undefined;
|
|
@@ -316,11 +326,13 @@ export function createResumeLegs(ctx) {
|
|
|
316
326
|
}
|
|
317
327
|
return {
|
|
318
328
|
status: 409,
|
|
319
|
-
body: {
|
|
320
|
-
error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
|
|
329
|
+
body: refusalBody({
|
|
321
330
|
errorCode: "approval_stale",
|
|
322
|
-
|
|
323
|
-
|
|
331
|
+
message: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
|
|
332
|
+
facts: {
|
|
333
|
+
...(s57CurrentPending ? { currentPending: s57CurrentPending } : {}),
|
|
334
|
+
},
|
|
335
|
+
}),
|
|
324
336
|
};
|
|
325
337
|
}
|
|
326
338
|
const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
|
|
@@ -407,14 +419,48 @@ export function createResumeLegs(ctx) {
|
|
|
407
419
|
const plan = planWorkflowParkedDecide(origin, parkedReq);
|
|
408
420
|
if (!plan.ok)
|
|
409
421
|
return plan.deny;
|
|
410
|
-
const
|
|
422
|
+
const decisions = deps.workflowParkDecisions;
|
|
423
|
+
const runStore = deps.runStore;
|
|
424
|
+
if (decisions === undefined || runStore === undefined) {
|
|
425
|
+
return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
|
|
426
|
+
}
|
|
427
|
+
const fingerprint = parkDecisionFingerprint(plan.outcome);
|
|
428
|
+
const liveReceiptOf = async (held) => {
|
|
429
|
+
if (held === null || held.fingerprint !== fingerprint)
|
|
430
|
+
return undefined;
|
|
431
|
+
const run = await runStore.getRun(held.deliveredTaskId);
|
|
432
|
+
return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId) : undefined;
|
|
433
|
+
};
|
|
434
|
+
const replay = await liveReceiptOf(await decisions.get(token));
|
|
435
|
+
if (replay !== undefined)
|
|
436
|
+
return replay;
|
|
437
|
+
let recordFailure;
|
|
438
|
+
const delivery = {
|
|
439
|
+
carried: [{ runId: plan.runId, token, outcome: plan.outcome }],
|
|
440
|
+
commitBeforeLaunch: async (deliveredTaskId) => {
|
|
441
|
+
try {
|
|
442
|
+
const recorded = await decisions.record({ token, runId: plan.runId, hostSessionId: plan.hostSessionId, outcome: plan.outcome, fingerprint, deliveredTaskId, acceptedAtMs: Date.now() });
|
|
443
|
+
if (!recorded)
|
|
444
|
+
throw new Error(DELIVERY_NOT_RECORDED);
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
recordFailure = { err };
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
};
|
|
411
452
|
const hostToken = await cs
|
|
412
453
|
.findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
|
|
413
454
|
.catch(() => undefined);
|
|
414
455
|
const admittedPayer = decodeCheckpointScope(cp.scope);
|
|
415
|
-
|
|
416
|
-
? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly,
|
|
417
|
-
: await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req,
|
|
456
|
+
const out = hostToken
|
|
457
|
+
? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, delivery, admittedPayer)
|
|
458
|
+
: await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, delivery, plan.runId);
|
|
459
|
+
if (recordFailure !== undefined)
|
|
460
|
+
throw recordFailure.err;
|
|
461
|
+
if (acceptedTaskIdOf(out) === undefined)
|
|
462
|
+
return (await liveReceiptOf(await decisions.get(token))) ?? out;
|
|
463
|
+
return out;
|
|
418
464
|
}
|
|
419
465
|
}
|
|
420
466
|
const ctxRead0 = await readResumeCtx(ctxPromise);
|
|
@@ -422,7 +468,7 @@ export function createResumeLegs(ctx) {
|
|
|
422
468
|
return resumeCtxCorruptAnswer(ctxRead0.where);
|
|
423
469
|
const ctx = ctxRead0.kind === "ok" ? ctxRead0.ctx : null;
|
|
424
470
|
if (!ctx)
|
|
425
|
-
return { status: 409, body: {
|
|
471
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
|
|
426
472
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
427
473
|
const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
|
|
428
474
|
if (!resolved.ok)
|
|
@@ -430,10 +476,10 @@ export function createResumeLegs(ctx) {
|
|
|
430
476
|
const { spec, engineNotices } = resolved;
|
|
431
477
|
const pendingTool = cp.pendingAction.toolName;
|
|
432
478
|
if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
|
|
433
|
-
return { status: 400, body: {
|
|
479
|
+
return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", facts: { ...fieldConflictFacts("ask_answer", ["decision", "answer"]) } }) };
|
|
434
480
|
}
|
|
435
481
|
if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
|
|
436
|
-
return { status: 400, body: {
|
|
482
|
+
return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, facts: { ...fieldConflictFacts("ask_answer", ["answer", "decision"]) } }) };
|
|
437
483
|
}
|
|
438
484
|
const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
|
|
439
485
|
const pa = cp.pendingAction;
|
|
@@ -533,6 +579,7 @@ export function createResumeLegs(ctx) {
|
|
|
533
579
|
const ownerLeaseDenial = args.callerInitiated === true && args.ownerLeaseAdmitted !== true ? await ownerFleetLeaseDenied(principal) : undefined;
|
|
534
580
|
if (ownerLeaseDenial)
|
|
535
581
|
return ownerLeaseDenial;
|
|
582
|
+
const heldDecisions = await heldWorkflowParkDecisions(deps, sessionId, args.workflowDecision?.carried);
|
|
536
583
|
let taskId = await deps.runStore?.getActiveTaskId(sessionId);
|
|
537
584
|
let freshRunClaimed = false;
|
|
538
585
|
if (args.mintFreshRun && taskId && deps.runStore) {
|
|
@@ -654,6 +701,14 @@ export function createResumeLegs(ctx) {
|
|
|
654
701
|
deps.modelUsage?.register(taskId);
|
|
655
702
|
resumeManifestLease = deps.promptManifests?.register(taskId) ?? 0;
|
|
656
703
|
}
|
|
704
|
+
if (args.workflowDecision !== undefined && taskId === undefined) {
|
|
705
|
+
throw new Error("workflow decision delivery: no run row was claimed for the delivering leg — refusing to start a delivery whose decision could not be recorded against it");
|
|
706
|
+
}
|
|
707
|
+
const workflowDecision = args.workflowDecision;
|
|
708
|
+
const launchInternals = {
|
|
709
|
+
...heldDecisions,
|
|
710
|
+
...(workflowDecision !== undefined && taskId !== undefined ? { afterCheckpointResolve: () => workflowDecision.commitBeforeLaunch(taskId) } : {}),
|
|
711
|
+
};
|
|
657
712
|
commitSessionInheritEnv(deps, sessionId, resumeCtx.ctx.body);
|
|
658
713
|
if (args.onResumeCommitted)
|
|
659
714
|
await args.onResumeCommitted();
|
|
@@ -689,6 +744,7 @@ export function createResumeLegs(ctx) {
|
|
|
689
744
|
cancelLedgerAppend = vAppend;
|
|
690
745
|
const verifyTailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
691
746
|
const verifyInternals = {
|
|
747
|
+
...launchInternals,
|
|
692
748
|
...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
|
|
693
749
|
onForwardEvent: (e) => {
|
|
694
750
|
fleetPub?.onForwardEvent(e);
|
|
@@ -817,7 +873,7 @@ export function createResumeLegs(ctx) {
|
|
|
817
873
|
resumeLegLive = true;
|
|
818
874
|
const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
819
875
|
const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
|
|
820
|
-
...
|
|
876
|
+
...launchInternals,
|
|
821
877
|
onForwardEvent: (e) => {
|
|
822
878
|
fleetPub?.onForwardEvent(e);
|
|
823
879
|
{
|
|
@@ -1045,7 +1101,6 @@ export function createResumeLegs(ctx) {
|
|
|
1045
1101
|
? deps.toolApproval.runWithContext({
|
|
1046
1102
|
taskId,
|
|
1047
1103
|
owner: principal ?? null,
|
|
1048
|
-
emit: approvalEmit,
|
|
1049
1104
|
abortSignal: cancelCtrl.signal,
|
|
1050
1105
|
sessionId,
|
|
1051
1106
|
emitCard: createApprovalCardEmitter({ appendDurable: (f) => approvalEmit(f) }),
|
|
@@ -1181,7 +1236,7 @@ export function createResumeLegs(ctx) {
|
|
|
1181
1236
|
const RESUMABLE_GATE_KIND = "resource_limit";
|
|
1182
1237
|
const preemptBinding = { gateKinds: [RESUMABLE_GATE_KIND] };
|
|
1183
1238
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, preemptBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
1184
|
-
const noSuspension = { status: 404, body: {
|
|
1239
|
+
const noSuspension = { status: 404, body: refusalBody({ errorCode: "not_found.suspension", message: "no resumable suspension for this task (already running, resumed, or expired)" }) };
|
|
1185
1240
|
if (!token)
|
|
1186
1241
|
return noSuspension;
|
|
1187
1242
|
if (decider !== undefined && !decider.explicitOperator) {
|
|
@@ -1204,12 +1259,12 @@ export function createResumeLegs(ctx) {
|
|
|
1204
1259
|
deps.logger?.warn?.("assistant_resume_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
|
|
1205
1260
|
return noSuspension;
|
|
1206
1261
|
}
|
|
1207
|
-
return { status: 409, body: {
|
|
1262
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) };
|
|
1208
1263
|
}
|
|
1209
1264
|
if (!cp) {
|
|
1210
1265
|
if (!(await mayDiscloseRowError(cs, token, decider)))
|
|
1211
1266
|
return noSuspension;
|
|
1212
|
-
return { status: 404, body: {
|
|
1267
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) };
|
|
1213
1268
|
}
|
|
1214
1269
|
if (decider !== undefined && !decider.explicitOperator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== decider.principal) {
|
|
1215
1270
|
deps.logger?.warn?.("assistant_resume_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
|
|
@@ -1217,13 +1272,13 @@ export function createResumeLegs(ctx) {
|
|
|
1217
1272
|
}
|
|
1218
1273
|
const gateKind = cp.gate?.kind;
|
|
1219
1274
|
if (gateKind !== RESUMABLE_GATE_KIND) {
|
|
1220
|
-
return { status: 409, body: {
|
|
1275
|
+
return { status: 409, body: refusalBody({ errorCode: "gate_not_resumable", message: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a resumable resource/preempt suspension — an approval gate must be decided via POST /v1/approvals/:id/decide` }) };
|
|
1221
1276
|
}
|
|
1222
1277
|
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1223
1278
|
if (ctxRead.kind === "corrupt")
|
|
1224
1279
|
return resumeCtxCorruptAnswer(ctxRead.where);
|
|
1225
1280
|
if (ctxRead.kind === "absent")
|
|
1226
|
-
return { status: 409, body: {
|
|
1281
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
|
|
1227
1282
|
const ctx = ctxRead.ctx;
|
|
1228
1283
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1229
1284
|
const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
|
|
@@ -1234,10 +1289,10 @@ export function createResumeLegs(ctx) {
|
|
|
1234
1289
|
const verifyRounds = verifyRoundsFromBody(ctx.body);
|
|
1235
1290
|
return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
1236
1291
|
}
|
|
1237
|
-
async function startFreshHostTurn(sessionId, objective, principal, req,
|
|
1292
|
+
async function startFreshHostTurn(sessionId, objective, principal, req, workflowDecision, runId) {
|
|
1238
1293
|
const cs = deps.checkpointStore;
|
|
1239
1294
|
if (!deps.runStore) {
|
|
1240
|
-
return { status: 501, body: {
|
|
1295
|
+
return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
|
|
1241
1296
|
}
|
|
1242
1297
|
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1243
1298
|
if (ctxRead.kind === "corrupt")
|
|
@@ -1246,12 +1301,14 @@ export function createResumeLegs(ctx) {
|
|
|
1246
1301
|
if (!ctx) {
|
|
1247
1302
|
return {
|
|
1248
1303
|
status: 409,
|
|
1249
|
-
body: {
|
|
1250
|
-
error: "this approval waited too long to be delivered: the session this run was started from no longer keeps what a new turn would need, because that state is dropped once the session has been idle past this deployment's retention window (REAP_RUN_STALE_SEC — its current value is staleAfterSec below). Nothing was decided and the approval is still pending: resume the run yourself with the runId below, or ask whoever operates this deployment to raise REAP_RUN_STALE_SEC so approvals can sit longer before someone decides them",
|
|
1304
|
+
body: refusalBody({
|
|
1251
1305
|
errorCode: "conflict.resume_context_unavailable",
|
|
1252
|
-
runId,
|
|
1253
|
-
|
|
1254
|
-
|
|
1306
|
+
message: "this approval waited too long to be delivered: the session this run was started from no longer keeps what a new turn would need, because that state is dropped once the session has been idle past this deployment's retention window (REAP_RUN_STALE_SEC — its current value is staleAfterSec below). Nothing was decided and the approval is still pending: resume the run yourself with the runId below, or ask whoever operates this deployment to raise REAP_RUN_STALE_SEC so approvals can sit longer before someone decides them",
|
|
1307
|
+
facts: {
|
|
1308
|
+
runId,
|
|
1309
|
+
staleAfterSec: deps.config.runStaleSec,
|
|
1310
|
+
},
|
|
1311
|
+
}),
|
|
1255
1312
|
};
|
|
1256
1313
|
}
|
|
1257
1314
|
const windowDenial = await usageWindowDenialFor(principal || GLOBAL_USAGE_KEY);
|
|
@@ -1268,7 +1325,7 @@ export function createResumeLegs(ctx) {
|
|
|
1268
1325
|
fleetScope: resumeFleetScope(req, auth),
|
|
1269
1326
|
elicitOwner: principal ?? null,
|
|
1270
1327
|
source: "workflow_parked_decide",
|
|
1271
|
-
|
|
1328
|
+
workflowDecision,
|
|
1272
1329
|
});
|
|
1273
1330
|
if (out.existing) {
|
|
1274
1331
|
throw new Error(`decide host turn: createRun observed an existing row ${out.existing.taskId} for a freshly minted taskId (no idempotency key was sent)`);
|
|
@@ -1278,18 +1335,18 @@ export function createResumeLegs(ctx) {
|
|
|
1278
1335
|
const minted = out.body;
|
|
1279
1336
|
return acceptedResumingReceipt(minted.taskId, minted.sessionId);
|
|
1280
1337
|
}
|
|
1281
|
-
async function resumeWake(sessionId, message, caller, req, acceptEarly,
|
|
1338
|
+
async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowDecision, ownerLeaseAdmittedFor) {
|
|
1282
1339
|
const cs = deps.checkpointStore;
|
|
1283
1340
|
const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
|
|
1284
1341
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
1285
1342
|
if (!token)
|
|
1286
|
-
return { status: 404, body: {
|
|
1287
|
-
const operator = explicitOperatorOk(caller, deps.config
|
|
1343
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
|
|
1344
|
+
const operator = explicitOperatorOk(caller, deps.config);
|
|
1288
1345
|
if (!operator) {
|
|
1289
1346
|
const scope = await cs.peekPendingScope(sessionId, wakeBinding).then((s) => (s === undefined ? cs.peekPendingScope(sessionId) : s)).catch(() => undefined);
|
|
1290
1347
|
const decodedScope = scope != null ? decodeCheckpointScope(scope) : undefined;
|
|
1291
1348
|
if (decodedScope !== undefined && decodedScope !== caller) {
|
|
1292
|
-
return { status: 404, body: {
|
|
1349
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
|
|
1293
1350
|
}
|
|
1294
1351
|
}
|
|
1295
1352
|
let cp;
|
|
@@ -1299,22 +1356,22 @@ export function createResumeLegs(ctx) {
|
|
|
1299
1356
|
catch (e) {
|
|
1300
1357
|
if (!(await mayDiscloseRowError(cs, token, { principal: caller, explicitOperator: operator }))) {
|
|
1301
1358
|
deps.logger?.warn?.("wake_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
|
|
1302
|
-
return { status: 404, body: {
|
|
1359
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
|
|
1303
1360
|
}
|
|
1304
|
-
return { status: 409, body: {
|
|
1361
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) };
|
|
1305
1362
|
}
|
|
1306
1363
|
if (!cp) {
|
|
1307
1364
|
if (!(await mayDiscloseRowError(cs, token, { principal: caller, explicitOperator: operator }))) {
|
|
1308
|
-
return { status: 404, body: {
|
|
1365
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
|
|
1309
1366
|
}
|
|
1310
|
-
return { status: 404, body: {
|
|
1367
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) };
|
|
1311
1368
|
}
|
|
1312
1369
|
if (!operator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== caller) {
|
|
1313
|
-
return { status: 404, body: {
|
|
1370
|
+
return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
|
|
1314
1371
|
}
|
|
1315
1372
|
const gateKind = cp.gate?.kind;
|
|
1316
1373
|
if (gateKind !== "task_done") {
|
|
1317
|
-
return { status: 409, body: {
|
|
1374
|
+
return { status: 409, body: refusalBody({ errorCode: "wake.gate_pending", message: `cannot wake: this session is waiting on a pending gate decision (gate "${gateKind ?? "unknown"}") — resolve it through its own decide entry` }) };
|
|
1318
1375
|
}
|
|
1319
1376
|
const trusted = operator;
|
|
1320
1377
|
if (message !== undefined) {
|
|
@@ -1323,7 +1380,7 @@ export function createResumeLegs(ctx) {
|
|
|
1323
1380
|
}
|
|
1324
1381
|
catch (e) {
|
|
1325
1382
|
if (e instanceof CheckpointError && e.code === "steering.invalid_content") {
|
|
1326
|
-
return { status: 422, body: { errorCode: "steering.invalid_content",
|
|
1383
|
+
return { status: 422, body: refusalBody({ errorCode: "steering.invalid_content", message: e.message }) };
|
|
1327
1384
|
}
|
|
1328
1385
|
throw e;
|
|
1329
1386
|
}
|
|
@@ -1332,7 +1389,7 @@ export function createResumeLegs(ctx) {
|
|
|
1332
1389
|
if (ctxRead.kind === "corrupt")
|
|
1333
1390
|
return resumeCtxCorruptAnswer(ctxRead.where);
|
|
1334
1391
|
if (ctxRead.kind === "absent")
|
|
1335
|
-
return { status: 409, body: {
|
|
1392
|
+
return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
|
|
1336
1393
|
const ctx = ctxRead.ctx;
|
|
1337
1394
|
const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
|
|
1338
1395
|
const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
|
|
@@ -1352,10 +1409,10 @@ export function createResumeLegs(ctx) {
|
|
|
1352
1409
|
verifyRounds: undefined,
|
|
1353
1410
|
resumeCtx: { sessionId, ctx },
|
|
1354
1411
|
mintFreshRun: { source: "wake" },
|
|
1412
|
+
...(workflowDecision !== undefined ? { workflowDecision } : {}),
|
|
1355
1413
|
...(engineNotices.length > 0 ? { engineNotices } : {}),
|
|
1356
1414
|
...(acceptEarly === true ? { acceptEarly: true } : {}),
|
|
1357
1415
|
...(req !== undefined ? { callerInitiated: true } : {}),
|
|
1358
|
-
...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
|
|
1359
1416
|
...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
|
|
1360
1417
|
}));
|
|
1361
1418
|
}
|
|
@@ -1370,7 +1427,7 @@ export function createResumeLegs(ctx) {
|
|
|
1370
1427
|
const PLAN_REVIEW_GATE_KIND = "plan_review";
|
|
1371
1428
|
const planReviewBinding = { gateKinds: [PLAN_REVIEW_GATE_KIND] };
|
|
1372
1429
|
const token = (await cs.findPendingTokenBySession(sessionId, undefined, planReviewBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
|
|
1373
|
-
const noPending = { status: 404, body: {
|
|
1430
|
+
const noPending = { status: 404, body: refusalBody({ errorCode: "not_found.plan_review", message: "no pending plan_review for this session (already decided or expired)" }) };
|
|
1374
1431
|
if (!token) {
|
|
1375
1432
|
log404("no-pending-checkpoint");
|
|
1376
1433
|
return refuse(noPending);
|
|
@@ -1395,13 +1452,13 @@ export function createResumeLegs(ctx) {
|
|
|
1395
1452
|
deps.logger?.warn?.("plan_review_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
|
|
1396
1453
|
return refuse(noPending);
|
|
1397
1454
|
}
|
|
1398
|
-
return refuse({ status: 409, body: {
|
|
1455
|
+
return refuse({ status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) });
|
|
1399
1456
|
}
|
|
1400
1457
|
if (!cp) {
|
|
1401
1458
|
log404("checkpoint-missing");
|
|
1402
1459
|
if (!(await mayDiscloseRowError(cs, token, decider)))
|
|
1403
1460
|
return refuse(noPending);
|
|
1404
|
-
return refuse({ status: 404, body: {
|
|
1461
|
+
return refuse({ status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) });
|
|
1405
1462
|
}
|
|
1406
1463
|
if (decider !== undefined && !decider.explicitOperator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== decider.principal) {
|
|
1407
1464
|
deps.logger?.warn?.("plan_review_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
|
|
@@ -1411,17 +1468,17 @@ export function createResumeLegs(ctx) {
|
|
|
1411
1468
|
if (gateKind !== PLAN_REVIEW_GATE_KIND) {
|
|
1412
1469
|
return refuse({
|
|
1413
1470
|
status: 409,
|
|
1414
|
-
body: {
|
|
1415
|
-
error: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a plan_review — a tool approval must be decided via POST /v1/approvals/:id/decide`,
|
|
1471
|
+
body: refusalBody({
|
|
1416
1472
|
errorCode: "gate_not_plan_review",
|
|
1417
|
-
|
|
1473
|
+
message: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a plan_review — a tool approval must be decided via POST /v1/approvals/:id/decide`,
|
|
1474
|
+
}),
|
|
1418
1475
|
});
|
|
1419
1476
|
}
|
|
1420
1477
|
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1421
1478
|
if (ctxRead.kind === "corrupt")
|
|
1422
1479
|
return refuse(resumeCtxCorruptAnswer(ctxRead.where));
|
|
1423
1480
|
if (ctxRead.kind === "absent")
|
|
1424
|
-
return refuse({ status: 409, body: {
|
|
1481
|
+
return refuse({ status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) });
|
|
1425
1482
|
const replay = storedBodyReplayRefusal(ctxRead.ctx, deps.config);
|
|
1426
1483
|
if (replay !== undefined)
|
|
1427
1484
|
return refuse(refuseStoredBodyReplay(sessionId, replay));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 已退役的 `ServiceDeps` 座 —— 「嵌入方照传旧键」的**唯一判据**(闭集表 + 一只判官)。
|
|
3
|
+
*
|
|
4
|
+
* 病:一个座退役后,类型上它不在了(TypeScript 嵌入方 tsc 红即通告),但类型之外的嵌入方(JS / `as` 硬转)照传旧键 ⇒ 值被装配无声丢掉,
|
|
5
|
+
* 「传了没用」是无声失效 —— 而旧键背后的那道门(受理口的墙、blob 的帽)仍在别处判着,调用方以为自己的值在生效。
|
|
6
|
+
*
|
|
7
|
+
* 律:退役的座在 `createHttpServer` 装配期**响亮拒**,不静默忽略。座的名字只登记在 {@link RETIRED_SERVICE_DEPS} 这一张表里(键 = 旧座名,
|
|
8
|
+
* 值 = 退役版本 + 出路),判官 {@link retiredServiceDepsRefusal} 扫一次摊平后的装配入参(`flattenServiceDeps` 之后:顶层键与各组成员同在,
|
|
9
|
+
* 旧座曾住在 `knobs` 组里,分组形与平铺形同判),命中 ⇒ 交回一句拒话,点名「键 · 退役版本 · 出路」,一次把所有命中的键说全。
|
|
10
|
+
*
|
|
11
|
+
* 「传了」= 自有键在场且值不是 `undefined`(条件装配写 `k: cond ? v : undefined` 的嵌入方什么都没传,不拒)。
|
|
12
|
+
* 一个座新退役时,在表里加一行(同一提交里 CHANGELOG / MIGRATION 的升级条点名它);窗口过后的删表行是单独的一步,不在这里留别名。
|
|
13
|
+
*/
|
|
14
|
+
/** 一个退役座的登记:哪一版退的、嵌入方现在该怎么做。 */
|
|
15
|
+
export interface RetiredServiceDep {
|
|
16
|
+
/** 退役所在的版本(拒句里原样说出)。 */
|
|
17
|
+
readonly retiredIn: string;
|
|
18
|
+
/** 出路,接在「was retired in <版本> — 」之后的半句(英文,与起服拒的其余句子同口径)。 */
|
|
19
|
+
readonly wayOut: string;
|
|
20
|
+
}
|
|
21
|
+
/** 闭集:`ServiceDeps` 上已退役的座名。 */
|
|
22
|
+
export declare const RETIRED_SERVICE_DEPS: {
|
|
23
|
+
readonly sqlEngineFacts: {
|
|
24
|
+
readonly retiredIn: "7.132.0";
|
|
25
|
+
readonly wayOut: "replaced by sqlEngine — pass the SQL pool's engine reading as sqlEngine: `{ dialect: \"tidb\" | \"pg\", facts }` where `facts` is read live (a getter) and is null while the pool's connection setup has not succeeded, or leave both unset when there is no SQL store";
|
|
26
|
+
};
|
|
27
|
+
readonly snapshotBlobSqlCapBytes: {
|
|
28
|
+
readonly retiredIn: "7.132.0";
|
|
29
|
+
readonly wayOut: "the HTTP surface no longer holds a blob cap of its own — the SQL snapshot store judges the cap on the connection that executes the write; set SNAPSHOT_BLOB_SQL_MAX_BYTES for a fixed cap, and a self-built file-history store answers an oversized putBlob with `{ ok: false, error: { code \"blob_too_large_for_sql\", message, wallSource } }`";
|
|
30
|
+
};
|
|
31
|
+
readonly snapshotBlobSqlCap: {
|
|
32
|
+
readonly retiredIn: "7.133.0";
|
|
33
|
+
readonly wayOut: "the HTTP surface no longer holds a blob cap of its own — the SQL snapshot store judges the cap on the connection that executes the write; set SNAPSHOT_BLOB_SQL_MAX_BYTES for a fixed cap, and a self-built file-history store answers an oversized putBlob with `{ ok: false, error: { code \"blob_too_large_for_sql\", message, wallSource } }`";
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/** 退役座名的闭集。 */
|
|
37
|
+
export type RetiredServiceDepName = keyof typeof RETIRED_SERVICE_DEPS;
|
|
38
|
+
/**
|
|
39
|
+
* 装配入参(已摊平)里有没有传已退役的座。没有 ⇒ `undefined`;有 ⇒ 一句拒话(按表序把所有命中的键说全:键、退役版本、出路)。
|
|
40
|
+
* 调用方原样抛出 —— 判官只判不抛,抛的位置(装配期)在 `createHttpServer`。
|
|
41
|
+
*/
|
|
42
|
+
export declare function retiredServiceDepsRefusal(flatDeps: object): string | undefined;
|
|
43
|
+
//# sourceMappingURL=retired-service-deps.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const BLOB_CAP_WAY_OUT = "the HTTP surface no longer holds a blob cap of its own — the SQL snapshot store judges the cap on the connection that executes the write; set SNAPSHOT_BLOB_SQL_MAX_BYTES for a fixed cap, and a self-built file-history store answers an oversized putBlob with `{ ok: false, error: { code \"blob_too_large_for_sql\", message, wallSource } }`";
|
|
2
|
+
export const RETIRED_SERVICE_DEPS = {
|
|
3
|
+
sqlEngineFacts: {
|
|
4
|
+
retiredIn: "7.132.0",
|
|
5
|
+
wayOut: "replaced by sqlEngine — pass the SQL pool's engine reading as sqlEngine: `{ dialect: \"tidb\" | \"pg\", facts }` where `facts` is read live (a getter) and is null while the pool's connection setup has not succeeded, or leave both unset when there is no SQL store",
|
|
6
|
+
},
|
|
7
|
+
snapshotBlobSqlCapBytes: { retiredIn: "7.132.0", wayOut: BLOB_CAP_WAY_OUT },
|
|
8
|
+
snapshotBlobSqlCap: { retiredIn: "7.133.0", wayOut: BLOB_CAP_WAY_OUT },
|
|
9
|
+
};
|
|
10
|
+
const RETIRED_NAMES = Object.keys(RETIRED_SERVICE_DEPS);
|
|
11
|
+
export function retiredServiceDepsRefusal(flatDeps) {
|
|
12
|
+
const hits = RETIRED_NAMES.filter((name) => Object.hasOwn(flatDeps, name) && flatDeps[name] !== undefined);
|
|
13
|
+
if (hits.length === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
const said = hits.map((name) => `${name} was retired in ${RETIRED_SERVICE_DEPS[name].retiredIn} — ${RETIRED_SERVICE_DEPS[name].wayOut}`).join("; ");
|
|
16
|
+
return `createHttpServer: ${said}; refusing to start rather than silently ignore a retired ServiceDeps seat (the value would no longer do anything)`;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=retired-service-deps.js.map
|