@sema-agent/server 1.323.0 → 2.0.1
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/dist/approval-hmac.d.ts +40 -0
- package/dist/approval-hmac.js +67 -0
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +72 -4
- package/dist/audit.d.ts +26 -0
- package/dist/audit.js +40 -1
- package/dist/auth-bridge.d.ts +10 -0
- package/dist/auth-bridge.js +47 -4
- package/dist/auth-keys.d.ts +19 -0
- package/dist/auth-keys.js +13 -0
- package/dist/bake-runner/main.d.ts +5 -0
- package/dist/bake-runner/main.js +43 -4
- package/dist/bake-runner/protocol.d.ts +57 -0
- package/dist/bake-runner/protocol.js +58 -3
- package/dist/bake-runner/runner.d.ts +54 -0
- package/dist/bake-runner/runner.js +87 -5
- package/dist/bench/l8/artifact.d.ts +63 -0
- package/dist/bench/l8/artifact.js +61 -2
- package/dist/bench/l8/escape.d.ts +36 -0
- package/dist/bench/l8/escape.js +25 -0
- package/dist/bench/l8/index.d.ts +14 -0
- package/dist/bench/l8/index.js +14 -0
- package/dist/bench/l8/probes.d.ts +74 -0
- package/dist/bench/l8/probes.js +60 -0
- package/dist/bench/l8/run-probes.d.ts +43 -0
- package/dist/bench/l8/run-probes.js +45 -2
- package/dist/bench/s1/arms.d.ts +126 -0
- package/dist/bench/s1/arms.js +188 -13
- package/dist/bench/s1/live-deps.d.ts +66 -0
- package/dist/bench/s1/live-deps.js +249 -11
- package/dist/bench/s1/oracle.d.ts +49 -0
- package/dist/bench/s1/oracle.js +36 -1
- package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
- package/dist/bench/s1/repair-oracle-adapter.js +30 -0
- package/dist/bench/s1/reviewer.d.ts +38 -0
- package/dist/bench/s1/reviewer.js +34 -0
- package/dist/bench/s1/row.d.ts +77 -0
- package/dist/bench/s1/row.js +62 -0
- package/dist/bench/s1/run-firm.d.ts +47 -0
- package/dist/bench/s1/run-firm.js +70 -4
- package/dist/bench/s1/runner-ctx.d.ts +71 -0
- package/dist/bench/s1/runner-ctx.js +36 -0
- package/dist/bench/s1/tasks.d.ts +108 -0
- package/dist/bench/s1/tasks.js +115 -0
- package/dist/boot/budget-tracing.d.ts +48 -0
- package/dist/boot/budget-tracing.js +86 -0
- package/dist/boot/config-center.d.ts +62 -0
- package/dist/boot/config-center.js +995 -0
- package/dist/boot/coordinators.d.ts +33 -0
- package/dist/boot/coordinators.js +97 -0
- package/dist/boot/execution-env.d.ts +26 -0
- package/dist/boot/execution-env.js +370 -0
- package/dist/boot/leader.d.ts +27 -0
- package/dist/boot/leader.js +81 -0
- package/dist/boot/reapers.d.ts +53 -0
- package/dist/boot/reapers.js +252 -0
- package/dist/boot/resolve-spec.d.ts +70 -0
- package/dist/boot/resolve-spec.js +1072 -0
- package/dist/boot/runner-deps.d.ts +101 -0
- package/dist/boot/runner-deps.js +343 -0
- package/dist/boot/runtime-caps.d.ts +21 -0
- package/dist/boot/runtime-caps.js +62 -0
- package/dist/boot/session-faces.d.ts +57 -0
- package/dist/boot/session-faces.js +157 -0
- package/dist/boot/shutdown.d.ts +50 -0
- package/dist/boot/shutdown.js +129 -0
- package/dist/boot/stores.d.ts +32 -0
- package/dist/boot/stores.js +361 -0
- package/dist/boot/workflow-orchestration.d.ts +46 -0
- package/dist/boot/workflow-orchestration.js +150 -0
- package/dist/boot-reclaim.d.ts +19 -0
- package/dist/boot-reclaim.js +20 -0
- package/dist/brain.d.ts +25 -0
- package/dist/brain.js +61 -1
- package/dist/budget.d.ts +66 -0
- package/dist/budget.js +128 -4
- package/dist/capabilities/builtin-tools.d.ts +3 -0
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/center-plugins.d.ts +17 -1
- package/dist/capabilities/center-plugins.js +45 -4
- package/dist/capabilities/center-prompts.d.ts +46 -0
- package/dist/capabilities/center-prompts.js +57 -3
- package/dist/capabilities/code-review-council.d.ts +28 -0
- package/dist/capabilities/code-review-council.js +40 -7
- package/dist/capabilities/collab-wire.d.ts +2 -0
- package/dist/capabilities/collab-workflows.d.ts +7 -0
- package/dist/capabilities/collab-workflows.js +44 -1
- package/dist/capabilities/oa-tools.d.ts +6 -0
- package/dist/capabilities/oa-tools.js +10 -0
- package/dist/capabilities/prompt.d.ts +14 -0
- package/dist/capabilities/prompt.js +14 -0
- package/dist/capabilities/prompts/code-review.d.ts +14 -0
- package/dist/capabilities/prompts/code-review.js +14 -0
- package/dist/capabilities/prompts/identity.d.ts +6 -0
- package/dist/capabilities/prompts/identity.js +6 -0
- package/dist/capabilities/prompts/team.d.ts +4 -0
- package/dist/capabilities/prompts/team.js +4 -0
- package/dist/capabilities/repo-tools.d.ts +11 -0
- package/dist/capabilities/repo-tools.js +8 -0
- package/dist/capabilities/sandbox-file-send.d.ts +95 -0
- package/dist/capabilities/sandbox-file-send.js +91 -3
- package/dist/capabilities/scenarios.d.ts +130 -0
- package/dist/capabilities/scenarios.js +180 -7
- package/dist/capabilities/select-environment-tool.d.ts +7 -0
- package/dist/capabilities/select-environment-tool.js +30 -0
- package/dist/capabilities/send-user-file-tool.d.ts +33 -0
- package/dist/capabilities/send-user-file-tool.js +65 -1
- package/dist/capabilities/skills.d.ts +29 -0
- package/dist/capabilities/skills.js +13 -2
- package/dist/capabilities/team.d.ts +22 -0
- package/dist/capabilities/team.js +22 -2
- package/dist/capabilities/tool-defer.d.ts +26 -0
- package/dist/capabilities/tool-defer.js +3 -0
- package/dist/config-center/apply-effective.d.ts +65 -0
- package/dist/config-center/apply-effective.js +204 -12
- package/dist/config-center/http-client.d.ts +34 -0
- package/dist/config-center/http-client.js +68 -0
- package/dist/config-center/restart-signal.d.ts +22 -0
- package/dist/config-center/restart-signal.js +39 -0
- package/dist/config-center/skills-mcp.d.ts +20 -1
- package/dist/config-center/skills-mcp.js +42 -7
- package/dist/config-center/types.d.ts +92 -1
- package/dist/config-lkg.d.ts +12 -0
- package/dist/config-lkg.js +41 -0
- package/dist/config-provider.d.ts +78 -0
- package/dist/config-provider.js +165 -2
- package/dist/config-types.d.ts +585 -1
- package/dist/config.d.ts +78 -2
- package/dist/config.js +874 -301
- package/dist/degenerate-instrument.d.ts +38 -0
- package/dist/degenerate-instrument.js +55 -3
- package/dist/elicitation.d.ts +39 -0
- package/dist/elicitation.js +87 -3
- package/dist/env-facts.d.ts +61 -0
- package/dist/env-facts.js +73 -7
- package/dist/fleet/fleet-bus.d.ts +185 -0
- package/dist/fleet/fleet-bus.js +333 -27
- package/dist/fleet/subagent-tail-bus.d.ts +11 -0
- package/dist/fleet/subagent-tail-bus.js +33 -2
- package/dist/fleet-client.d.ts +38 -0
- package/dist/fleet-client.js +44 -9
- package/dist/fleet-lease.d.ts +46 -0
- package/dist/fleet-lease.js +73 -5
- package/dist/hooks/branch-transcript.d.ts +8 -0
- package/dist/hooks/branch-transcript.js +33 -0
- package/dist/hooks/cc-stop-prompt.d.ts +42 -0
- package/dist/hooks/cc-stop-prompt.js +43 -1
- package/dist/hooks/hook-llm.d.ts +15 -0
- package/dist/hooks/hook-llm.js +47 -0
- package/dist/hooks/hook-runner.d.ts +89 -0
- package/dist/hooks/hook-runner.js +327 -18
- package/dist/http/idempotency.d.ts +31 -0
- package/dist/http/idempotency.js +34 -0
- package/dist/http/principal-gate.d.ts +17 -0
- package/dist/http/principal-gate.js +35 -3
- package/dist/http/route-ctx.d.ts +36 -0
- package/dist/http/routes/approvals-assistant.d.ts +25 -0
- package/dist/http/routes/approvals-assistant.js +253 -23
- package/dist/http/routes/attachments.js +22 -4
- package/dist/http/routes/capabilities.d.ts +7 -0
- package/dist/http/routes/capabilities.js +202 -1
- package/dist/http/routes/fleet.d.ts +6 -0
- package/dist/http/routes/fleet.js +94 -5
- package/dist/http/routes/images.d.ts +17 -0
- package/dist/http/routes/images.js +222 -14
- package/dist/http/routes/leader.d.ts +7 -0
- package/dist/http/routes/leader.js +10 -0
- package/dist/http/routes/memory-policy.d.ts +6 -0
- package/dist/http/routes/memory-policy.js +38 -6
- package/dist/http/routes/notify-wake.d.ts +10 -0
- package/dist/http/routes/notify-wake.js +19 -3
- package/dist/http/routes/observability.d.ts +6 -0
- package/dist/http/routes/observability.js +19 -2
- package/dist/http/routes/runs.d.ts +15 -0
- package/dist/http/routes/runs.js +464 -41
- package/dist/http/routes/session-sync.d.ts +15 -0
- package/dist/http/routes/session-sync.js +229 -19
- package/dist/http/routes/sessions-list.d.ts +6 -0
- package/dist/http/routes/sessions-list.js +35 -4
- package/dist/http/routes/sessions.d.ts +14 -0
- package/dist/http/routes/sessions.js +176 -31
- package/dist/http/routes/side-query.d.ts +8 -0
- package/dist/http/routes/side-query.js +26 -1
- package/dist/http/routes/tasks.d.ts +18 -0
- package/dist/http/routes/tasks.js +501 -38
- package/dist/http/routes/trace-usage.d.ts +10 -0
- package/dist/http/routes/trace-usage.js +66 -14
- package/dist/http/routes/workflows.d.ts +12 -0
- package/dist/http/routes/workflows.js +180 -19
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +6 -0
- package/dist/http/send.d.ts +19 -0
- package/dist/http/send.js +11 -0
- package/dist/http/server.d.ts +255 -0
- package/dist/http/server.js +921 -75
- package/dist/http/sse-log.d.ts +15 -0
- package/dist/http/sse-log.js +16 -0
- package/dist/http/tar.d.ts +9 -0
- package/dist/http/tar.js +21 -5
- package/dist/http/wire-gate.d.ts +9 -0
- package/dist/http/wire-gate.js +9 -0
- package/dist/http/wire-types.d.ts +187 -0
- package/dist/http/workspace-content.d.ts +8 -0
- package/dist/http/workspace-content.js +10 -0
- package/dist/images/bake-validate.d.ts +63 -0
- package/dist/images/bake-validate.js +71 -1
- package/dist/images/manifest.d.ts +19 -0
- package/dist/images/manifest.js +6 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.js +10 -5
- package/dist/key-resolver.d.ts +23 -0
- package/dist/key-resolver.js +27 -2
- package/dist/leader/diffout.d.ts +23 -0
- package/dist/leader/diffout.js +8 -1
- package/dist/leader/diffup.d.ts +32 -0
- package/dist/leader/diffup.js +43 -0
- package/dist/leader/endpoint.d.ts +20 -0
- package/dist/leader/endpoint.js +25 -2
- package/dist/leader/fanout.d.ts +60 -0
- package/dist/leader/fanout.js +29 -3
- package/dist/leader/grader-env-factory.d.ts +74 -0
- package/dist/leader/grader-env-factory.js +62 -3
- package/dist/leader/leader.d.ts +97 -0
- package/dist/leader/leader.js +151 -4
- package/dist/leader/merge.d.ts +94 -0
- package/dist/leader/merge.js +112 -9
- package/dist/leader/planner.d.ts +52 -0
- package/dist/leader/planner.js +47 -3
- package/dist/leader/repair-oracle.d.ts +61 -0
- package/dist/leader/repair-oracle.js +59 -1
- package/dist/leader/repair-wire.d.ts +78 -0
- package/dist/leader/repair-wire.js +79 -2
- package/dist/leader/wire.d.ts +79 -0
- package/dist/leader/wire.js +255 -8
- package/dist/lsp/e2b-bridge.d.ts +16 -0
- package/dist/lsp/e2b-bridge.js +32 -4
- package/dist/lsp/e2b-manager.d.ts +10 -0
- package/dist/lsp/e2b-manager.js +18 -2
- package/dist/lsp/lsp-frames.d.ts +12 -0
- package/dist/lsp/lsp-frames.js +12 -0
- package/dist/lsp/manager.d.ts +12 -0
- package/dist/lsp/manager.js +35 -2
- package/dist/lsp/types.d.ts +10 -0
- package/dist/lsp/ws-transport.d.ts +12 -0
- package/dist/lsp/ws-transport.js +53 -5
- package/dist/lsp-evict.d.ts +14 -0
- package/dist/lsp-evict.js +15 -1
- package/dist/main.js +530 -2129
- package/dist/memory-export.d.ts +1 -0
- package/dist/memory-export.js +4 -0
- package/dist/memory-scope.d.ts +49 -0
- package/dist/memory-scope.js +87 -4
- package/dist/memory-sync-client.d.ts +24 -0
- package/dist/memory-sync-client.js +41 -2
- package/dist/memory-sync.d.ts +23 -0
- package/dist/memory-sync.js +70 -1
- package/dist/model-select.d.ts +39 -0
- package/dist/model-select.js +42 -1
- package/dist/observability/cost-quota.d.ts +20 -0
- package/dist/observability/cost-quota.js +4 -0
- package/dist/observability/cost-taxonomy.d.ts +45 -0
- package/dist/observability/cost-taxonomy.js +34 -0
- package/dist/observability/logger.d.ts +1 -0
- package/dist/observability/logger.js +6 -0
- package/dist/observability/metrics.d.ts +13 -0
- package/dist/observability/metrics.js +63 -0
- package/dist/observability/otel-exporter.d.ts +9 -0
- package/dist/observability/otel-exporter.js +11 -3
- package/dist/observability/principal-context.d.ts +9 -0
- package/dist/observability/principal-context.js +9 -0
- package/dist/observability/prompt-manifest.d.ts +37 -0
- package/dist/observability/prompt-manifest.js +35 -1
- package/dist/observability/rate-limit.d.ts +10 -0
- package/dist/observability/rate-limit.js +1 -0
- package/dist/observability/tool-trace.d.ts +24 -0
- package/dist/observability/tool-trace.js +41 -1
- package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
- package/dist/orchestration/hardened-vm-runner.js +119 -4
- package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
- package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
- package/dist/orchestration/hardened-vm-worker.js +27 -0
- package/dist/orchestration/subagent-steer.d.ts +44 -0
- package/dist/orchestration/subagent-steer.js +45 -1
- package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
- package/dist/orchestration/workflow-agent-steer.js +80 -1
- package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
- package/dist/orchestration/workflow-completion-inbox.js +283 -32
- package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
- package/dist/orchestration/workflow-notify-journal.js +187 -13
- package/dist/parked-decide.d.ts +61 -0
- package/dist/parked-decide.js +58 -1
- package/dist/per-task-image.d.ts +54 -0
- package/dist/per-task-image.js +41 -0
- package/dist/plan-cache-probe.d.ts +35 -0
- package/dist/plan-cache-probe.js +18 -3
- package/dist/plugins/approval-exemption-store.d.ts +7 -0
- package/dist/plugins/approval-exemption-store.js +28 -2
- package/dist/plugins/approval-store-sql.d.ts +64 -0
- package/dist/plugins/approval-store-sql.js +32 -0
- package/dist/plugins/background-agent-store-sql.d.ts +55 -0
- package/dist/plugins/background-agent-store-sql.js +91 -4
- package/dist/plugins/background-shell-support.d.ts +98 -0
- package/dist/plugins/background-shell-support.js +117 -13
- package/dist/plugins/blob-backend.d.ts +104 -0
- package/dist/plugins/blob-backend.js +163 -6
- package/dist/plugins/breaker-state-sql.d.ts +65 -1
- package/dist/plugins/breaker-state-sql.js +46 -8
- package/dist/plugins/caching-session-store.d.ts +42 -0
- package/dist/plugins/caching-session-store.js +63 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
- package/dist/plugins/checkpoint-store-sql.js +239 -11
- package/dist/plugins/file-outcome-sink.d.ts +4 -0
- package/dist/plugins/file-outcome-sink.js +9 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
- package/dist/plugins/file-resume-anchor-store.js +43 -4
- package/dist/plugins/file-run-store.d.ts +90 -0
- package/dist/plugins/file-run-store.js +145 -13
- package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
- package/dist/plugins/file-snapshot-store-sql.js +178 -10
- package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
- package/dist/plugins/file-workflow-journal-store.js +10 -0
- package/dist/plugins/fork-routing-session-store.d.ts +47 -0
- package/dist/plugins/fork-routing-session-store.js +51 -3
- package/dist/plugins/host-platform.d.ts +69 -0
- package/dist/plugins/host-platform.js +81 -2
- package/dist/plugins/image-bake-store-sql.d.ts +136 -1
- package/dist/plugins/image-bake-store-sql.js +241 -7
- package/dist/plugins/image-index-sql.d.ts +46 -0
- package/dist/plugins/image-index-sql.js +113 -4
- package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
- package/dist/plugins/k8s-bg-scripts.js +88 -4
- package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
- package/dist/plugins/k8s-exec-protocol.js +41 -0
- package/dist/plugins/local-checkpoint-store.d.ts +40 -0
- package/dist/plugins/local-checkpoint-store.js +86 -7
- package/dist/plugins/local-session-store.d.ts +157 -0
- package/dist/plugins/local-session-store.js +289 -34
- package/dist/plugins/local-task-attachment-store.js +14 -2
- package/dist/plugins/mailbox-store-sql.d.ts +38 -0
- package/dist/plugins/mailbox-store-sql.js +39 -6
- package/dist/plugins/memory-engine-pg.d.ts +30 -0
- package/dist/plugins/memory-engine-pg.js +122 -4
- package/dist/plugins/memory-engine-tidb.d.ts +26 -0
- package/dist/plugins/memory-engine-tidb.js +115 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
- package/dist/plugins/memory-engine-vector-util.js +5 -0
- package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
- package/dist/plugins/memory-resume-anchor-store.js +17 -0
- package/dist/plugins/memory-run-store.d.ts +63 -0
- package/dist/plugins/memory-run-store.js +73 -8
- package/dist/plugins/memory-session-policy-store.d.ts +17 -0
- package/dist/plugins/memory-session-policy-store.js +17 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
- package/dist/plugins/memory-sync-store-pg.js +45 -2
- package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
- package/dist/plugins/memory-sync-store-tidb.js +31 -1
- package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
- package/dist/plugins/outcome-ledger-sql.js +97 -3
- package/dist/plugins/pg-approval-store.d.ts +7 -0
- package/dist/plugins/pg-approval-store.js +7 -0
- package/dist/plugins/pg-breaker-state.d.ts +6 -0
- package/dist/plugins/pg-breaker-state.js +6 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
- package/dist/plugins/pg-checkpoint-store.js +8 -0
- package/dist/plugins/pg-cost-quota.d.ts +17 -0
- package/dist/plugins/pg-cost-quota.js +6 -0
- package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/pg-file-snapshot-store.js +6 -0
- package/dist/plugins/pg-image-bake.d.ts +9 -0
- package/dist/plugins/pg-image-bake.js +9 -0
- package/dist/plugins/pg-image-index.d.ts +9 -0
- package/dist/plugins/pg-image-index.js +9 -0
- package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
- package/dist/plugins/pg-outcome-ledger.js +9 -0
- package/dist/plugins/pg-pool.d.ts +50 -0
- package/dist/plugins/pg-pool.js +78 -0
- package/dist/plugins/pg-query.d.ts +11 -0
- package/dist/plugins/pg-rate-limiter.d.ts +23 -0
- package/dist/plugins/pg-rate-limiter.js +12 -2
- package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/pg-resume-anchor-store.js +5 -0
- package/dist/plugins/pg-run-store.d.ts +7 -0
- package/dist/plugins/pg-run-store.js +7 -0
- package/dist/plugins/pg-safe-json.d.ts +13 -0
- package/dist/plugins/pg-safe-json.js +40 -4
- package/dist/plugins/pg-session-policy-store.d.ts +5 -0
- package/dist/plugins/pg-session-policy-store.js +5 -0
- package/dist/plugins/pg-session-storage.d.ts +72 -0
- package/dist/plugins/pg-session-storage.js +152 -20
- package/dist/plugins/pg-session-store.d.ts +10 -0
- package/dist/plugins/pg-session-store.js +10 -0
- package/dist/plugins/pg-tool-result-store.d.ts +7 -0
- package/dist/plugins/pg-tool-result-store.js +7 -0
- package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-journal-store.js +7 -0
- package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-run-store.js +7 -0
- package/dist/plugins/posix-shell-fs.d.ts +3 -0
- package/dist/plugins/posix-shell-fs.js +31 -1
- package/dist/plugins/remote-env-adb.d.ts +25 -0
- package/dist/plugins/remote-env-adb.js +95 -12
- package/dist/plugins/remote-env-e2b.d.ts +146 -0
- package/dist/plugins/remote-env-e2b.js +371 -40
- package/dist/plugins/remote-env-file-error.d.ts +34 -0
- package/dist/plugins/remote-env-file-error.js +37 -0
- package/dist/plugins/remote-env-host.d.ts +151 -0
- package/dist/plugins/remote-env-host.js +468 -70
- package/dist/plugins/remote-env-k8s.d.ts +135 -0
- package/dist/plugins/remote-env-k8s.js +318 -32
- package/dist/plugins/remote-env-local-docker.d.ts +65 -0
- package/dist/plugins/remote-env-local-docker.js +149 -24
- package/dist/plugins/remote-env-ssh.d.ts +35 -0
- package/dist/plugins/remote-env-ssh.js +117 -20
- package/dist/plugins/remote-scratchpad.d.ts +38 -0
- package/dist/plugins/remote-scratchpad.js +32 -2
- package/dist/plugins/remote-shell.d.ts +22 -0
- package/dist/plugins/remote-shell.js +27 -1
- package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
- package/dist/plugins/resume-anchor-store-sql.js +11 -0
- package/dist/plugins/roster-store-sql.d.ts +27 -0
- package/dist/plugins/roster-store-sql.js +54 -0
- package/dist/plugins/run-store-sql.d.ts +194 -0
- package/dist/plugins/run-store-sql.js +210 -9
- package/dist/plugins/s3-presign.d.ts +20 -0
- package/dist/plugins/s3-presign.js +49 -2
- package/dist/plugins/scheduler-support.d.ts +12 -0
- package/dist/plugins/scheduler-support.js +75 -3
- package/dist/plugins/send-file-ledger.d.ts +15 -0
- package/dist/plugins/send-file-ledger.js +53 -4
- package/dist/plugins/send-user-file.d.ts +77 -0
- package/dist/plugins/send-user-file.js +92 -4
- package/dist/plugins/session-policy-store-sql.d.ts +15 -0
- package/dist/plugins/session-policy-store-sql.js +75 -3
- package/dist/plugins/session-store.d.ts +45 -0
- package/dist/plugins/session-store.js +51 -0
- package/dist/plugins/sql-driver.d.ts +77 -0
- package/dist/plugins/sql-driver.js +4 -0
- package/dist/plugins/sql-escape.d.ts +8 -0
- package/dist/plugins/sql-escape.js +8 -0
- package/dist/plugins/sql-row-helpers.d.ts +25 -0
- package/dist/plugins/sql-row-helpers.js +25 -0
- package/dist/plugins/store-backend.d.ts +103 -0
- package/dist/plugins/store-backend.js +120 -27
- package/dist/plugins/store-contracts.d.ts +62 -0
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +60 -0
- package/dist/plugins/task-attachment-store.js +49 -3
- package/dist/plugins/task-list-store-sql.d.ts +25 -0
- package/dist/plugins/task-list-store-sql.js +21 -0
- package/dist/plugins/tidb-approval-store.d.ts +6 -0
- package/dist/plugins/tidb-approval-store.js +6 -0
- package/dist/plugins/tidb-breaker-state.d.ts +5 -0
- package/dist/plugins/tidb-breaker-state.js +5 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
- package/dist/plugins/tidb-checkpoint-store.js +7 -0
- package/dist/plugins/tidb-cost-quota.d.ts +6 -0
- package/dist/plugins/tidb-cost-quota.js +1 -0
- package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/tidb-file-snapshot-store.js +6 -0
- package/dist/plugins/tidb-image-bake.d.ts +8 -0
- package/dist/plugins/tidb-image-bake.js +8 -0
- package/dist/plugins/tidb-image-index.d.ts +8 -0
- package/dist/plugins/tidb-image-index.js +8 -0
- package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
- package/dist/plugins/tidb-outcome-ledger.js +10 -0
- package/dist/plugins/tidb-pool.d.ts +56 -0
- package/dist/plugins/tidb-pool.js +181 -1
- package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
- package/dist/plugins/tidb-rate-limiter.js +5 -2
- package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/tidb-resume-anchor-store.js +5 -0
- package/dist/plugins/tidb-run-store.d.ts +7 -0
- package/dist/plugins/tidb-run-store.js +7 -0
- package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
- package/dist/plugins/tidb-session-policy-store.js +5 -0
- package/dist/plugins/tidb-session-storage.d.ts +43 -0
- package/dist/plugins/tidb-session-storage.js +62 -3
- package/dist/plugins/tidb-session-store.d.ts +142 -0
- package/dist/plugins/tidb-session-store.js +264 -16
- package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
- package/dist/plugins/tidb-tool-result-store.js +7 -0
- package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/tidb-workflow-journal-store.js +7 -0
- package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
- package/dist/plugins/tidb-workflow-run-store.js +8 -0
- package/dist/plugins/tool-result-store-sql.d.ts +58 -0
- package/dist/plugins/tool-result-store-sql.js +38 -2
- package/dist/plugins/web-search.d.ts +35 -0
- package/dist/plugins/web-search.js +45 -9
- package/dist/plugins/workflow-journal-limits.d.ts +10 -0
- package/dist/plugins/workflow-journal-limits.js +10 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
- package/dist/plugins/workflow-journal-store-sql.js +58 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
- package/dist/plugins/workflow-run-store-sql.js +84 -6
- package/dist/plugins/worktree-isolation.d.ts +45 -0
- package/dist/plugins/worktree-isolation.js +105 -6
- package/dist/plugins/write-behind-counter.d.ts +56 -1
- package/dist/plugins/write-behind-counter.js +53 -14
- package/dist/principal-jwt.d.ts +24 -0
- package/dist/principal-jwt.js +60 -5
- package/dist/project-memory.d.ts +22 -0
- package/dist/project-memory.js +144 -15
- package/dist/prompts-domain-validate.d.ts +28 -0
- package/dist/prompts-domain-validate.js +42 -1
- package/dist/question.d.ts +33 -0
- package/dist/question.js +69 -2
- package/dist/resource-suspend.d.ts +21 -0
- package/dist/resource-suspend.js +16 -0
- package/dist/router/route-orchestration.d.ts +78 -0
- package/dist/router/route-orchestration.js +77 -0
- package/dist/run-local.d.ts +32 -0
- package/dist/run-local.js +172 -8
- package/dist/runs.d.ts +189 -4
- package/dist/runs.js +465 -24
- package/dist/runtime-caps-resolver.d.ts +106 -0
- package/dist/runtime-caps-resolver.js +89 -4
- package/dist/runtime-governance.d.ts +84 -0
- package/dist/runtime-governance.js +127 -1
- package/dist/sandbox-pkg-source.d.ts +23 -0
- package/dist/sandbox-pkg-source.js +37 -0
- package/dist/sealed-key.d.ts +54 -0
- package/dist/sealed-key.js +68 -3
- package/dist/security.d.ts +108 -0
- package/dist/security.js +145 -6
- package/dist/sema-registry.d.ts +21 -0
- package/dist/sema-registry.js +21 -0
- package/dist/session-leaf-bus.d.ts +31 -0
- package/dist/session-leaf-bus.js +32 -0
- package/dist/session-sync-content.d.ts +46 -0
- package/dist/session-sync-content.js +47 -1
- package/dist/session-sync-kernel.d.ts +89 -0
- package/dist/session-sync-kernel.js +48 -3
- package/dist/session-sync.d.ts +125 -0
- package/dist/session-sync.js +181 -8
- package/dist/session-titler.d.ts +26 -0
- package/dist/session-titler.js +41 -7
- package/dist/session-watch.d.ts +53 -1
- package/dist/session-watch.js +73 -9
- package/dist/sighup-idle.d.ts +30 -0
- package/dist/sighup-idle.js +12 -1
- package/dist/spec-fields.d.ts +91 -3
- package/dist/spec-fields.js +116 -4
- package/dist/task-cwd.d.ts +50 -0
- package/dist/task-cwd.js +68 -1
- package/dist/task-mcp.d.ts +47 -0
- package/dist/task-mcp.js +29 -0
- package/dist/task-settings.d.ts +175 -0
- package/dist/task-settings.js +226 -11
- package/dist/task-workflow.d.ts +67 -0
- package/dist/task-workflow.js +75 -8
- package/dist/tool-approval.d.ts +91 -0
- package/dist/tool-approval.js +215 -0
- package/dist/trace/artifacts.d.ts +9 -0
- package/dist/trace/artifacts.js +30 -5
- package/dist/trace/core-keyset-guard.d.ts +18 -0
- package/dist/trace/ledger-sink.d.ts +43 -0
- package/dist/trace/ledger-sink.js +64 -5
- package/dist/trace/project.d.ts +133 -0
- package/dist/trace/project.js +189 -2
- package/dist/trace/redact.d.ts +19 -0
- package/dist/trace/redact.js +66 -9
- package/dist/usage-analytics.d.ts +26 -0
- package/dist/usage-analytics.js +26 -3
- package/package.json +1 -1
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-backed async run registry + replayable event log — the DURABLE local twin of {@link MemoryRunStore} (and the
|
|
3
|
+
* file-backed sibling of {@link TiDBRunStore}/{@link PgRunStore}). P0.5 variant-1 (clay 2026-06-26): the file-backed
|
|
4
|
+
* follow-on the Memory twin's header promised — a locally-run HTTP service now keeps its runs ledger + trace event log
|
|
5
|
+
* across a process restart, not just in memory. Behaviour is byte-identical to MemoryRunStore (the shared parity oracle
|
|
6
|
+
* — same single-active claim, the suspend/needs_review parks that KEEP the claim, the markResuming CAS that resets
|
|
7
|
+
* cancel/preempt, the owner null-safe guard, the keyset list order, the (taskId,seq) event dedup, and the structuredClone
|
|
8
|
+
* write/read isolation); the only difference is durability.
|
|
9
|
+
*
|
|
10
|
+
* THREE on-disk data planes (all under `<root>/runs/`), each using core's crash-safe fs primitives — never hand-rolled
|
|
11
|
+
* fs (zero drift, no zero-length/torn corruption):
|
|
12
|
+
* 1. run registry — `<root>/runs/<taskId>/run.json` = the RunRow, replaced WHOLE on every status change via
|
|
13
|
+
* `atomicWriteFile` (tmp→fsync→rename→fsync-dir, the 5-step atomic replace).
|
|
14
|
+
* 2. single-active claim — `<root>/runs/active/<sanitizePathComponent(sessionId)>` carrying the active taskId, created
|
|
15
|
+
* with `writeThenLink` (atomic create; EEXIST ⇒ the session already has an active run → the
|
|
16
|
+
* createRun loses the claim, returns `{ok:false, activeTaskId}`). The file version of task_active's
|
|
17
|
+
* unique key.
|
|
18
|
+
* 3. per-run event log — `<root>/runs/<taskId>/events.jsonl` = an `AppendLog` (one line per event, fsync:false — events
|
|
19
|
+
* are high-frequency, the torn-tail-drop on replay covers a crash tail; only the run.json status
|
|
20
|
+
* commit + the active-claim create use fsync). (taskId,seq) is deduped on replay.
|
|
21
|
+
*
|
|
22
|
+
* An in-memory INDEX (the same Maps MemoryRunStore holds) is hydrated on construction by scanning each runs/<taskId>/
|
|
23
|
+
* run.json + the active claim files — so listRuns/listSessions/getActiveTaskId are O(in-mem) with byte-identical keyset sort to the SQL/Memory
|
|
24
|
+
* twins. Every write touches BOTH the disk and the index on one path. Single-process (the FileStorageBackend boot lock
|
|
25
|
+
* guarantees one writer per data dir), so there is no per-op lock. The checkpoint-coupled suspended-run reapers run
|
|
26
|
+
* off an injected {@link RunStoreCheckpointProbe} ([868]; wired by the local backend where checkpoint + run store
|
|
27
|
+
* share one root) and degrade to honest no-ops when the probe is absent — exactly like MemoryRunStore.
|
|
28
|
+
*/
|
|
1
29
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
30
|
import type { TaskResult, TaskStatus } from "@sema-agent/core";
|
|
3
31
|
import type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
|
|
@@ -6,10 +34,16 @@ export declare class FileRunStore {
|
|
|
6
34
|
private readonly runsDir;
|
|
7
35
|
private readonly activeDir;
|
|
8
36
|
private readonly tmpDir;
|
|
37
|
+
/** taskId → RunRow (the run registry index). */
|
|
9
38
|
private readonly runs;
|
|
39
|
+
/** sessionId → taskId — the single-active-run claim (the task_active table; index of active/*). */
|
|
10
40
|
private readonly active;
|
|
41
|
+
/** taskId → events (kept seq-sorted on read), the event-log index. */
|
|
11
42
|
private readonly events;
|
|
43
|
+
/** taskId → the open append log (one O_APPEND fd per run, lazily opened, held for lifetime). */
|
|
12
44
|
private readonly eventLogs;
|
|
45
|
+
/** [868] the checkpoint-table stand-in (see memory-run-store.ts {@link RunStoreCheckpointProbe} — the full
|
|
46
|
+
* rationale lives on the interface); absent ⇒ the suspended-run reapers stay honest NO-OPs. */
|
|
13
47
|
private checkpointProbe?;
|
|
14
48
|
constructor(root: string);
|
|
15
49
|
private runDir;
|
|
@@ -19,9 +53,16 @@ export declare class FileRunStore {
|
|
|
19
53
|
private toRow;
|
|
20
54
|
private toDisk;
|
|
21
55
|
private toRecord;
|
|
56
|
+
/** Replay the on-disk registry/claims/event-logs into the in-memory index on boot. Each plane is torn-tail-/
|
|
57
|
+
* partial-write-safe (run.json via atomicWriteFile can never be half-written; the active file via writeThenLink is
|
|
58
|
+
* all-or-nothing; events.jsonl via readJsonlRecords drops a crash tail). A corrupt run.json (failed parse) is
|
|
59
|
+
* skipped — that run is lost, not a poison that fails the boot. */
|
|
22
60
|
private hydrate;
|
|
61
|
+
/** Commit a RunRow to disk (whole-file atomic replace) AND the index — the single write path for every status change. */
|
|
23
62
|
private persist;
|
|
24
63
|
private eventLogFor;
|
|
64
|
+
/** Create a run iff the session has no active run (the task_active unique-key claim). The claim file is created with
|
|
65
|
+
* writeThenLink (atomic): if it already exists (EEXIST) the session has an active run → lose, return the holder. */
|
|
25
66
|
createRun(taskId: string, sessionId: string, owner: string | null, instanceId: string, meta?: {
|
|
26
67
|
jobId?: string | null;
|
|
27
68
|
source?: string | null;
|
|
@@ -41,12 +82,18 @@ export declare class FileRunStore {
|
|
|
41
82
|
maxSeq(taskId: string): Promise<number>;
|
|
42
83
|
retainedFrom(taskId: string): Promise<number>;
|
|
43
84
|
getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
|
|
85
|
+
/** [888] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
|
|
86
|
+
* [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
|
|
44
87
|
setTerminal(taskId: string, status: TaskStatus, result: TaskResult | null, error: string | null): Promise<void>;
|
|
88
|
+
/** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
|
|
45
89
|
setSuspended(taskId: string): Promise<void>;
|
|
90
|
+
/** design/80 D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
|
|
46
91
|
setNeedsReview(taskId: string): Promise<void>;
|
|
92
|
+
/** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags. */
|
|
47
93
|
markResuming(taskId: string): Promise<boolean>;
|
|
48
94
|
getActiveTaskId(sessionId: string): Promise<string | undefined>;
|
|
49
95
|
getRun(taskId: string): Promise<RunRecord | undefined>;
|
|
96
|
+
/** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
|
|
50
97
|
listRuns(opts: {
|
|
51
98
|
status?: string;
|
|
52
99
|
jobId?: string;
|
|
@@ -58,6 +105,7 @@ export declare class FileRunStore {
|
|
|
58
105
|
};
|
|
59
106
|
limit: number;
|
|
60
107
|
}): Promise<RunRecord[]>;
|
|
108
|
+
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
61
109
|
listSessions(opts: {
|
|
62
110
|
owner?: string;
|
|
63
111
|
cursor?: {
|
|
@@ -67,11 +115,13 @@ export declare class FileRunStore {
|
|
|
67
115
|
limit: number;
|
|
68
116
|
q?: string;
|
|
69
117
|
}): Promise<SessionSummary[]>;
|
|
118
|
+
/** E21 (§0.5 delete) — purge the runs ledger for one session, owner-guarded; aborts (returns {active}) if a run is live. */
|
|
70
119
|
deleteBySession(sessionId: string, owner: string | null): Promise<{
|
|
71
120
|
removed: number;
|
|
72
121
|
} | {
|
|
73
122
|
active: string;
|
|
74
123
|
}>;
|
|
124
|
+
/** 用量扫窗——in-memory 腿:遍历窗内行,投影同 usage-analytics 纯函数(SQL 孪生 parity)。 */
|
|
75
125
|
usageScan(fromMs: number, toMs: number, opts?: {
|
|
76
126
|
owner?: string;
|
|
77
127
|
limit?: number;
|
|
@@ -84,16 +134,56 @@ export declare class FileRunStore {
|
|
|
84
134
|
status: string;
|
|
85
135
|
count: number;
|
|
86
136
|
}>>;
|
|
137
|
+
/** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
|
|
138
|
+
/** Release any claim whose row is now terminal; KEEP suspended/needs_review (resume re-finds them). One place for
|
|
139
|
+
* the claim-release invariant, shared by reapRunning + reclaimOrphanedAtBoot. */
|
|
87
140
|
private sweepClaims;
|
|
141
|
+
/** Flip every `running` row matching `pred` → failed (with `reason`), then sweep terminal claims. */
|
|
88
142
|
private reapRunning;
|
|
89
143
|
reapStale(olderThanMs: number): Promise<number>;
|
|
144
|
+
/** TOC integration ask③ (INTEGRATION-DESIGN §11): a host-lane engine restart leaves the PREVIOUS process's
|
|
145
|
+
* `running` rows orphaned — their in-process driver (runInBackground, in the dead process's memory) is gone, but the
|
|
146
|
+
* `active/<sessionId>` claim persists on disk and EEXIST-blocks a re-submit (the shell reads the session as stuck).
|
|
147
|
+
* The boot flock (store-backend `LOCK`) already proves no concurrent live owner reached here: a still-alive old
|
|
148
|
+
* engine holds it so the new one FAILS FAST; a dead one had it kernel-released. So a `running` row here is orphaned
|
|
149
|
+
* → flip failed + release its claim UNCONDITIONALLY (no TTL wait, unlike reapStale's steady-state poll).
|
|
150
|
+
* 🔴 EXCEPT a resume-in-progress row: `markResuming` flips a suspended run (suspended→running) while its pending
|
|
151
|
+
* checkpoint still exists, and the resume picker will re-drive it — reclaiming it would STRAND a resumable session
|
|
152
|
+
* as failed. `isResumable(sessionId)` (wired by main.ts boot, which holds the checkpointStore) excludes those; with
|
|
153
|
+
* no callback every running row is treated as orphaned (conservative default when no checkpoint plane is wired).
|
|
154
|
+
* HOST/FILE LANE ONLY — the cross-replica tidb/pg store uses heartbeat→reapStale (a running row there may be alive
|
|
155
|
+
* on another replica; an unconditional reclaim would kill a live fleet run). */
|
|
90
156
|
reclaimOrphanedAtBoot(isResumable?: (sessionId: string) => Promise<boolean>): Promise<number>;
|
|
157
|
+
/** [868] wire the checkpoint probe (LocalBackend.checkpoint(), where checkpoint + run store share one root). */
|
|
91
158
|
setCheckpointProbe(probe: RunStoreCheckpointProbe): void;
|
|
159
|
+
/** [868] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired') and
|
|
160
|
+
* PERSIST it — the file twin of the SQL UPDATE (`error`/`error_code` set directly, no result blob). */
|
|
92
161
|
private failExpiredPark;
|
|
162
|
+
/**
|
|
163
|
+
* [868] Durable F4 expiry — the file twin of TiDBRunStore.reapSuspended, driven by the injected checkpoint
|
|
164
|
+
* probe instead of a SQL JOIN (formerly a NO-OP — one of the five closed doors in the "suspended run locks the
|
|
165
|
+
* session forever" incident). Semantics verbatim from the SQL twin: suspended ∧ updatedAt < cutoff ∧ NOT
|
|
166
|
+
* probe.hasPending(session) → failed 'approval expired before decision' / errorCode 'approval.expired' +
|
|
167
|
+
* release the task_active claim. The NOT-pending clause is the design/80 D-D adversarial-blocker guard
|
|
168
|
+
* (tidb-run-store.ts:577-583): a row whose checkpoint is STILL pending belongs to the checkpoint-state sweeps —
|
|
169
|
+
* time-reaping it would orphan a model leg on a now-unlocked session. Probe absent ⇒ honest NO-OP.
|
|
170
|
+
* MemoryRunStore is the parity oracle (byte-identical predicate; only the persist differs).
|
|
171
|
+
*/
|
|
93
172
|
reapSuspended(olderThanMs: number): Promise<number>;
|
|
173
|
+
/**
|
|
174
|
+
* [868] design/80 §3 inv#3 crash-safe backstop — the file twin of TiDBRunStore.failSuspendedWithExpiredCheckpoint
|
|
175
|
+
* (checkpoint-STATE-driven, not time-driven; scheduled UNCONDITIONALLY by the main.ts reaper, no
|
|
176
|
+
* APPROVAL_TIMEOUT_SEC gate). Semantics verbatim from the SQL twin: (suspended ∨ needs_review) ∧
|
|
177
|
+
* probe.hasExpired(session) ∧ NOT probe.hasPending(session) → same fail + claim release (wq64gmm5e: an
|
|
178
|
+
* abandoned needs_review park leaks exactly like a suspended one; a re-suspended session that minted a NEW
|
|
179
|
+
* pending gate is excluded by the NOT-pending clause).
|
|
180
|
+
*/
|
|
94
181
|
failSuspendedWithExpiredCheckpoint(): Promise<number>;
|
|
182
|
+
/** Release a session's single-active claim: unlink the claim file + drop the index entry (idempotent). */
|
|
95
183
|
private releaseClaim;
|
|
184
|
+
/** Drop a run entirely (registry + event log + open fd + on-disk dir) — used by deleteBySession. */
|
|
96
185
|
private dropRun;
|
|
186
|
+
/** Release every open event-log fd (called by LocalBackend.close so a graceful restart can re-open the data dir). */
|
|
97
187
|
dispose(): void;
|
|
98
188
|
}
|
|
99
189
|
//# sourceMappingURL=file-run-store.d.ts.map
|
|
@@ -1,26 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-backed async run registry + replayable event log — the DURABLE local twin of {@link MemoryRunStore} (and the
|
|
3
|
+
* file-backed sibling of {@link TiDBRunStore}/{@link PgRunStore}). P0.5 variant-1 (clay 2026-06-26): the file-backed
|
|
4
|
+
* follow-on the Memory twin's header promised — a locally-run HTTP service now keeps its runs ledger + trace event log
|
|
5
|
+
* across a process restart, not just in memory. Behaviour is byte-identical to MemoryRunStore (the shared parity oracle
|
|
6
|
+
* — same single-active claim, the suspend/needs_review parks that KEEP the claim, the markResuming CAS that resets
|
|
7
|
+
* cancel/preempt, the owner null-safe guard, the keyset list order, the (taskId,seq) event dedup, and the structuredClone
|
|
8
|
+
* write/read isolation); the only difference is durability.
|
|
9
|
+
*
|
|
10
|
+
* THREE on-disk data planes (all under `<root>/runs/`), each using core's crash-safe fs primitives — never hand-rolled
|
|
11
|
+
* fs (zero drift, no zero-length/torn corruption):
|
|
12
|
+
* 1. run registry — `<root>/runs/<taskId>/run.json` = the RunRow, replaced WHOLE on every status change via
|
|
13
|
+
* `atomicWriteFile` (tmp→fsync→rename→fsync-dir, the 5-step atomic replace).
|
|
14
|
+
* 2. single-active claim — `<root>/runs/active/<sanitizePathComponent(sessionId)>` carrying the active taskId, created
|
|
15
|
+
* with `writeThenLink` (atomic create; EEXIST ⇒ the session already has an active run → the
|
|
16
|
+
* createRun loses the claim, returns `{ok:false, activeTaskId}`). The file version of task_active's
|
|
17
|
+
* unique key.
|
|
18
|
+
* 3. per-run event log — `<root>/runs/<taskId>/events.jsonl` = an `AppendLog` (one line per event, fsync:false — events
|
|
19
|
+
* are high-frequency, the torn-tail-drop on replay covers a crash tail; only the run.json status
|
|
20
|
+
* commit + the active-claim create use fsync). (taskId,seq) is deduped on replay.
|
|
21
|
+
*
|
|
22
|
+
* An in-memory INDEX (the same Maps MemoryRunStore holds) is hydrated on construction by scanning each runs/<taskId>/
|
|
23
|
+
* run.json + the active claim files — so listRuns/listSessions/getActiveTaskId are O(in-mem) with byte-identical keyset sort to the SQL/Memory
|
|
24
|
+
* twins. Every write touches BOTH the disk and the index on one path. Single-process (the FileStorageBackend boot lock
|
|
25
|
+
* guarantees one writer per data dir), so there is no per-op lock. The checkpoint-coupled suspended-run reapers run
|
|
26
|
+
* off an injected {@link RunStoreCheckpointProbe} ([868]; wired by the local backend where checkpoint + run store
|
|
27
|
+
* share one root) and degrade to honest no-ops when the probe is absent — exactly like MemoryRunStore.
|
|
28
|
+
*/
|
|
1
29
|
import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
|
|
2
30
|
import { join } from "node:path";
|
|
3
31
|
import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
4
32
|
import { AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizePathComponent, writeThenLink } from "@sema-agent/core";
|
|
33
|
+
/** Null-safe owner equality — the JS twin of the SQL `owner <=> ?` guard (a legacy null-owner run matches owner=null). */
|
|
5
34
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
6
35
|
export class FileRunStore {
|
|
7
36
|
runsDir;
|
|
8
37
|
activeDir;
|
|
9
38
|
tmpDir;
|
|
39
|
+
/** taskId → RunRow (the run registry index). */
|
|
10
40
|
runs = new Map();
|
|
41
|
+
/** sessionId → taskId — the single-active-run claim (the task_active table; index of active/*). */
|
|
11
42
|
active = new Map();
|
|
43
|
+
/** taskId → events (kept seq-sorted on read), the event-log index. */
|
|
12
44
|
events = new Map();
|
|
45
|
+
/** taskId → the open append log (one O_APPEND fd per run, lazily opened, held for lifetime). */
|
|
13
46
|
eventLogs = new Map();
|
|
47
|
+
/** [868] the checkpoint-table stand-in (see memory-run-store.ts {@link RunStoreCheckpointProbe} — the full
|
|
48
|
+
* rationale lives on the interface); absent ⇒ the suspended-run reapers stay honest NO-OPs. */
|
|
14
49
|
checkpointProbe;
|
|
15
50
|
constructor(root) {
|
|
16
51
|
this.runsDir = join(root, "runs");
|
|
17
52
|
this.activeDir = join(root, "runs", "active");
|
|
18
|
-
this.tmpDir = join(root, "tmp");
|
|
53
|
+
this.tmpDir = join(root, "tmp"); // atomicWriteFile temp dir (same FS as runs → rename is atomic)
|
|
19
54
|
ensureDir(this.runsDir);
|
|
20
55
|
ensureDir(this.activeDir);
|
|
21
56
|
ensureDir(this.tmpDir);
|
|
22
57
|
this.hydrate();
|
|
58
|
+
// NB: orphan reclaim is NOT auto-run here — main.ts boot drives it AFTER the checkpointStore is wired, so a
|
|
59
|
+
// resume-in-progress (markResuming) row with a pending checkpoint is excluded (see reclaimOrphanedAtBoot).
|
|
23
60
|
}
|
|
61
|
+
// ── disk paths ──
|
|
24
62
|
runDir(taskId) {
|
|
25
63
|
return join(this.runsDir, sanitizePathComponent(taskId));
|
|
26
64
|
}
|
|
@@ -56,6 +94,10 @@ export class FileRunStore {
|
|
|
56
94
|
updatedAt: r.updatedAt.toISOString(),
|
|
57
95
|
};
|
|
58
96
|
}
|
|
97
|
+
/** Replay the on-disk registry/claims/event-logs into the in-memory index on boot. Each plane is torn-tail-/
|
|
98
|
+
* partial-write-safe (run.json via atomicWriteFile can never be half-written; the active file via writeThenLink is
|
|
99
|
+
* all-or-nothing; events.jsonl via readJsonlRecords drops a crash tail). A corrupt run.json (failed parse) is
|
|
100
|
+
* skipped — that run is lost, not a poison that fails the boot. */
|
|
59
101
|
hydrate() {
|
|
60
102
|
let entries;
|
|
61
103
|
try {
|
|
@@ -66,19 +108,24 @@ export class FileRunStore {
|
|
|
66
108
|
}
|
|
67
109
|
for (const name of entries) {
|
|
68
110
|
if (name === "active")
|
|
69
|
-
continue;
|
|
111
|
+
continue; // the claims dir, scanned separately
|
|
70
112
|
const dir = join(this.runsDir, name);
|
|
71
113
|
const jsonPath = join(dir, "run.json");
|
|
72
114
|
if (!existsSync(jsonPath))
|
|
73
115
|
continue;
|
|
74
|
-
const rows = readJsonlRecords(jsonPath);
|
|
75
|
-
const disk = rows[rows.length - 1];
|
|
116
|
+
const rows = readJsonlRecords(jsonPath); // run.json is a single atomic record; tolerate a torn/empty file
|
|
117
|
+
const disk = rows[rows.length - 1]; // last complete record (atomicWriteFile writes the whole file each time)
|
|
76
118
|
if (!disk || typeof disk.taskId !== "string")
|
|
77
119
|
continue;
|
|
120
|
+
// R8 修:atomicWriteFile 防的是半写,防不了 schema 半态(旧版残留/手工改动的 run.json 缺时间戳字段)。
|
|
121
|
+
// 不挡在这里的话,坏行会在**读路径**上毒发:`toRecord` 对 Invalid Date 调 toISOString ⇒ RangeError ⇒
|
|
122
|
+
// 一条坏文件打死整个 listRuns(GET /v1/runs 全 500)。按顶注自述的隔离承诺同款处置:跳过该行,
|
|
123
|
+
// 一条坏行最多只影响它自己。
|
|
78
124
|
if (typeof disk.createdAt !== "string" || Number.isNaN(Date.parse(disk.createdAt)) ||
|
|
79
125
|
typeof disk.updatedAt !== "string" || Number.isNaN(Date.parse(disk.updatedAt)))
|
|
80
126
|
continue;
|
|
81
127
|
this.runs.set(disk.taskId, this.toRow(disk));
|
|
128
|
+
// event log
|
|
82
129
|
const eventsPath = join(dir, "events.jsonl");
|
|
83
130
|
const recs = readJsonlRecords(eventsPath);
|
|
84
131
|
if (recs.length) {
|
|
@@ -86,13 +133,14 @@ export class FileRunStore {
|
|
|
86
133
|
const seen = new Set();
|
|
87
134
|
for (const e of recs) {
|
|
88
135
|
if (!e || typeof e.seq !== "number" || seen.has(e.seq))
|
|
89
|
-
continue;
|
|
136
|
+
continue; // (taskId,seq) dedup-on-replay (last write wins by skip-first? no: first wins, matching append-ignore-dup)
|
|
90
137
|
seen.add(e.seq);
|
|
91
138
|
list.push({ seq: e.seq, type: String(e.type), data: e.data ?? null, ts: String(e.ts) });
|
|
92
139
|
}
|
|
93
140
|
this.events.set(disk.taskId, list);
|
|
94
141
|
}
|
|
95
142
|
}
|
|
143
|
+
// active claims
|
|
96
144
|
let claims;
|
|
97
145
|
try {
|
|
98
146
|
claims = readdirSync(this.activeDir);
|
|
@@ -108,6 +156,7 @@ export class FileRunStore {
|
|
|
108
156
|
}
|
|
109
157
|
}
|
|
110
158
|
}
|
|
159
|
+
/** Commit a RunRow to disk (whole-file atomic replace) AND the index — the single write path for every status change. */
|
|
111
160
|
persist(r) {
|
|
112
161
|
mkdirSync(this.runDir(r.taskId), { recursive: true });
|
|
113
162
|
atomicWriteFile(this.tmpDir, this.runJsonPath(r.taskId), `${JSON.stringify(this.toDisk(r))}\n`);
|
|
@@ -122,12 +171,18 @@ export class FileRunStore {
|
|
|
122
171
|
}
|
|
123
172
|
return log;
|
|
124
173
|
}
|
|
174
|
+
/** Create a run iff the session has no active run (the task_active unique-key claim). The claim file is created with
|
|
175
|
+
* writeThenLink (atomic): if it already exists (EEXIST) the session has an active run → lose, return the holder. */
|
|
125
176
|
async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
|
|
177
|
+
// Atomic single-active claim: writeThenLink throws EEXIST if the claim already exists (the file twin of the
|
|
178
|
+
// task_active unique key). The index is the fast-path read; the file is the authority on a concurrent create.
|
|
126
179
|
try {
|
|
127
180
|
writeThenLink(this.activePath(sessionId), `${JSON.stringify({ sessionId, taskId })}\n`);
|
|
128
181
|
}
|
|
129
182
|
catch (e) {
|
|
130
183
|
if (e.code === "EEXIST") {
|
|
184
|
+
// Recover the current holder from the claim file (the index may lag a concurrent winner, but in this
|
|
185
|
+
// single-process model the index is authoritative; read disk for robustness anyway).
|
|
131
186
|
const recs = readJsonlRecords(this.activePath(sessionId));
|
|
132
187
|
const holder = recs[recs.length - 1]?.taskId ?? this.active.get(sessionId) ?? "";
|
|
133
188
|
return { ok: false, activeTaskId: holder };
|
|
@@ -156,11 +211,15 @@ export class FileRunStore {
|
|
|
156
211
|
});
|
|
157
212
|
}
|
|
158
213
|
catch (e) {
|
|
214
|
+
// R9 修:claim 与 run 行是一体的 —— 行没落下(如超长 taskId 的 ENAMETOOLONG)就把刚落的 claim 撤掉再
|
|
215
|
+
// rethrow。否则 session 被一个 getRun=undefined 的幽灵 taskId 占用:起不了新 run、也取消不了"活跃 run",
|
|
216
|
+
// 直到进程重启 sweepClaims 才解(SQL 孪生天然免疫:createRun 整体在一个事务里)。
|
|
159
217
|
this.active.delete(sessionId);
|
|
160
218
|
try {
|
|
161
219
|
rmSync(this.activePath(sessionId), { force: true });
|
|
162
220
|
}
|
|
163
221
|
catch {
|
|
222
|
+
/* best-effort:claim 文件撤不掉时至少 index 已撤;重启 sweep 兜底 */
|
|
164
223
|
}
|
|
165
224
|
throw e;
|
|
166
225
|
}
|
|
@@ -199,8 +258,14 @@ export class FileRunStore {
|
|
|
199
258
|
}
|
|
200
259
|
async appendEvent(taskId, seq, type, data) {
|
|
201
260
|
const list = this.events.get(taskId) ?? [];
|
|
261
|
+
// Mirror the (task_id, seq) PK: a duplicate seq is a caller bug (callers use ++seq); ignore a re-append of the
|
|
262
|
+
// same seq rather than corrupt the log (the SQL twin throws ER_DUP_ENTRY — callers never hit it).
|
|
202
263
|
if (list.some((e) => e.seq === seq))
|
|
203
264
|
return;
|
|
265
|
+
// Deep-clone `data` on WRITE so the stored event is isolated from later caller mutation — the SQL twin
|
|
266
|
+
// JSON.stringify's it into the column, giving the same write-isolation (parity oracle). Persist BEFORE indexing
|
|
267
|
+
// so a crash after the append still replays the event (fsync:false — events are high-frequency, the torn-tail
|
|
268
|
+
// drop on replay covers a crash tail; only run.json status + the active claim need fsync).
|
|
204
269
|
const clone = data == null ? null : structuredClone(data);
|
|
205
270
|
const ts = new Date().toISOString();
|
|
206
271
|
this.eventLogFor(taskId).append({ seq, type, data: clone, ts }, false);
|
|
@@ -217,11 +282,15 @@ export class FileRunStore {
|
|
|
217
282
|
}
|
|
218
283
|
async getEvents(taskId, afterSeq) {
|
|
219
284
|
const list = this.events.get(taskId) ?? [];
|
|
285
|
+
// Deep-clone `data` on READ so each call yields a fresh, mutation-isolated object — the SQL twin re-parses the
|
|
286
|
+
// JSON column per read (parity oracle); a shallow `{...e}` would alias `data` into the stored list (review footgun).
|
|
220
287
|
return list
|
|
221
288
|
.filter((e) => e.seq > afterSeq)
|
|
222
289
|
.sort((a, b) => a.seq - b.seq)
|
|
223
290
|
.map((e) => ({ ...e, data: e.data == null ? null : structuredClone(e.data) }));
|
|
224
291
|
}
|
|
292
|
+
/** [888] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
|
|
293
|
+
* [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
|
|
225
294
|
async setTerminal(taskId, status, result, error) {
|
|
226
295
|
const r = this.runs.get(taskId);
|
|
227
296
|
if (!r)
|
|
@@ -234,9 +303,11 @@ export class FileRunStore {
|
|
|
234
303
|
r.updatedAt = new Date();
|
|
235
304
|
this.persist(r);
|
|
236
305
|
}
|
|
306
|
+
// Release the single-active-run claim (DELETE FROM task_active WHERE task_id = ?) — unlink the claim file + index.
|
|
237
307
|
if (this.active.get(r.sessionId) === taskId)
|
|
238
308
|
this.releaseClaim(r.sessionId);
|
|
239
309
|
}
|
|
310
|
+
/** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
|
|
240
311
|
async setSuspended(taskId) {
|
|
241
312
|
const r = this.runs.get(taskId);
|
|
242
313
|
if (r && (r.status === "running" || r.status === "suspended")) {
|
|
@@ -245,6 +316,7 @@ export class FileRunStore {
|
|
|
245
316
|
this.persist(r);
|
|
246
317
|
}
|
|
247
318
|
}
|
|
319
|
+
/** design/80 D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
|
|
248
320
|
async setNeedsReview(taskId) {
|
|
249
321
|
const r = this.runs.get(taskId);
|
|
250
322
|
if (r && (r.status === "running" || r.status === "needs_review")) {
|
|
@@ -253,6 +325,7 @@ export class FileRunStore {
|
|
|
253
325
|
this.persist(r);
|
|
254
326
|
}
|
|
255
327
|
}
|
|
328
|
+
/** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags. */
|
|
256
329
|
async markResuming(taskId) {
|
|
257
330
|
const r = this.runs.get(taskId);
|
|
258
331
|
if (!r || (r.status !== "suspended" && r.status !== "needs_review"))
|
|
@@ -271,7 +344,11 @@ export class FileRunStore {
|
|
|
271
344
|
const r = this.runs.get(taskId);
|
|
272
345
|
return r ? this.toRecord(r) : undefined;
|
|
273
346
|
}
|
|
347
|
+
/** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
|
|
274
348
|
async listRuns(opts) {
|
|
349
|
+
// 夜测 N1(2026-07-09,LOW):a non-date cursor.createdAt made the four backends diverge (PG threw a raw
|
|
350
|
+
// DB error; the other three returned an empty page). HTTP's decodeCursor pre-validates, so this is
|
|
351
|
+
// defence-in-depth for direct store callers — normalize ALL backends to the empty page.
|
|
275
352
|
if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
|
|
276
353
|
return [];
|
|
277
354
|
const cursorAt = opts.cursor ? Date.parse(opts.cursor.createdAt) : undefined;
|
|
@@ -279,31 +356,35 @@ export class FileRunStore {
|
|
|
279
356
|
.filter((r) => (opts.status ? r.status === opts.status : true))
|
|
280
357
|
.filter((r) => (opts.jobId ? r.jobId === opts.jobId : true))
|
|
281
358
|
.filter((r) => (opts.source ? r.source === opts.source : true))
|
|
282
|
-
.filter((r) => (opts.owner ? r.owner === opts.owner : true))
|
|
359
|
+
.filter((r) => (opts.owner ? r.owner === opts.owner : true)) // exact (SQL `owner = ?`): a set owner excludes null-owner rows
|
|
283
360
|
.filter((r) => {
|
|
284
361
|
if (cursorAt === undefined)
|
|
285
362
|
return true;
|
|
286
363
|
const t = r.createdAt.getTime();
|
|
287
|
-
return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId);
|
|
364
|
+
return t < cursorAt || (t === cursorAt && r.taskId < opts.cursor.taskId); // keyset: strictly older, ties by taskId
|
|
288
365
|
})
|
|
289
366
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
290
367
|
return rows.slice(0, opts.limit).map((r) => this.toRecord(r));
|
|
291
368
|
}
|
|
369
|
+
/** DISTINCT sessions newest-first by last activity (the CC /resume picker): latest run's preview/status + first/last/count. */
|
|
292
370
|
async listSessions(opts) {
|
|
293
371
|
const bySession = new Map();
|
|
294
372
|
for (const r of this.runs.values()) {
|
|
295
373
|
if (opts.owner && r.owner !== opts.owner)
|
|
296
|
-
continue;
|
|
374
|
+
continue; // exact owner filter (SQL `owner = ?`)
|
|
297
375
|
const arr = bySession.get(r.sessionId) ?? [];
|
|
298
376
|
arr.push(r);
|
|
299
377
|
bySession.set(r.sessionId, arr);
|
|
300
378
|
}
|
|
301
379
|
const cursorAt = opts.cursor ? Date.parse(opts.cursor.lastActivityAt) : undefined;
|
|
302
|
-
const qlc = opts.q?.toLowerCase();
|
|
380
|
+
const qlc = opts.q?.toLowerCase(); // `?q=`: any-run preview match — signature parity with the memory twin
|
|
381
|
+
// (this lister is UNREACHABLE via GET /v1/sessions today [local backend's sessionStorage lister wins], but a
|
|
382
|
+
// carried lister must not silently ignore a filter key — the workflow-audit hygiene item, 2026-07-13).
|
|
303
383
|
const summaries = [];
|
|
304
384
|
for (const [sessionId, arr] of bySession) {
|
|
305
385
|
if (qlc && !arr.some((r) => r.objectivePreview?.toLowerCase().includes(qlc)))
|
|
306
386
|
continue;
|
|
387
|
+
// rn=1 = latest run (createdAt DESC, taskId DESC) → preview/status/owner.
|
|
307
388
|
const sorted = [...arr].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
308
389
|
const latest = sorted[0];
|
|
309
390
|
const times = arr.map((r) => r.createdAt.getTime());
|
|
@@ -317,8 +398,8 @@ export class FileRunStore {
|
|
|
317
398
|
runCount: arr.length,
|
|
318
399
|
objectivePreview: latest.objectivePreview,
|
|
319
400
|
lastStatus: latest.status,
|
|
320
|
-
lastRunId: latest.taskId,
|
|
321
|
-
title: null,
|
|
401
|
+
lastRunId: latest.taskId, // K-5c: latest run id = the live-tail re-attach anchor
|
|
402
|
+
title: null, // honest degrade (no session_meta on the file runs-ledger twin)
|
|
322
403
|
});
|
|
323
404
|
}
|
|
324
405
|
return summaries
|
|
@@ -331,6 +412,7 @@ export class FileRunStore {
|
|
|
331
412
|
.sort((a, b) => Date.parse(b.lastActivityAt) - Date.parse(a.lastActivityAt) || (a.sessionId < b.sessionId ? 1 : a.sessionId > b.sessionId ? -1 : 0))
|
|
332
413
|
.slice(0, opts.limit);
|
|
333
414
|
}
|
|
415
|
+
/** E21 (§0.5 delete) — purge the runs ledger for one session, owner-guarded; aborts (returns {active}) if a run is live. */
|
|
334
416
|
async deleteBySession(sessionId, owner) {
|
|
335
417
|
const claimed = this.active.get(sessionId);
|
|
336
418
|
if (claimed !== undefined)
|
|
@@ -344,6 +426,7 @@ export class FileRunStore {
|
|
|
344
426
|
}
|
|
345
427
|
return { removed };
|
|
346
428
|
}
|
|
429
|
+
/** 用量扫窗——in-memory 腿:遍历窗内行,投影同 usage-analytics 纯函数(SQL 孪生 parity)。 */
|
|
347
430
|
async usageScan(fromMs, toMs, opts) {
|
|
348
431
|
const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
|
|
349
432
|
const all = [];
|
|
@@ -364,13 +447,16 @@ export class FileRunStore {
|
|
|
364
447
|
for (const r of this.runs.values()) {
|
|
365
448
|
if (r.createdAt.getTime() < sinceMs)
|
|
366
449
|
continue;
|
|
367
|
-
const key = JSON.stringify([r.source, r.status]);
|
|
450
|
+
const key = JSON.stringify([r.source, r.status]); // collision-proof + keeps NULL distinct from "" (SQL GROUP BY parity)
|
|
368
451
|
const cur = counts.get(key) ?? { source: r.source, status: r.status, count: 0 };
|
|
369
452
|
cur.count += 1;
|
|
370
453
|
counts.set(key, cur);
|
|
371
454
|
}
|
|
372
455
|
return [...counts.values()];
|
|
373
456
|
}
|
|
457
|
+
/** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
|
|
458
|
+
/** Release any claim whose row is now terminal; KEEP suspended/needs_review (resume re-finds them). One place for
|
|
459
|
+
* the claim-release invariant, shared by reapRunning + reclaimOrphanedAtBoot. */
|
|
374
460
|
sweepClaims() {
|
|
375
461
|
for (const [sessionId, taskId] of [...this.active]) {
|
|
376
462
|
const r = this.runs.get(taskId);
|
|
@@ -378,6 +464,7 @@ export class FileRunStore {
|
|
|
378
464
|
this.releaseClaim(sessionId);
|
|
379
465
|
}
|
|
380
466
|
}
|
|
467
|
+
/** Flip every `running` row matching `pred` → failed (with `reason`), then sweep terminal claims. */
|
|
381
468
|
reapRunning(pred, reason) {
|
|
382
469
|
let reaped = 0;
|
|
383
470
|
for (const r of this.runs.values()) {
|
|
@@ -396,6 +483,18 @@ export class FileRunStore {
|
|
|
396
483
|
const cutoff = Date.now() - olderThanMs;
|
|
397
484
|
return this.reapRunning((r) => r.updatedAt.getTime() < cutoff, "run stalled (instance lost?)");
|
|
398
485
|
}
|
|
486
|
+
/** TOC integration ask③ (INTEGRATION-DESIGN §11): a host-lane engine restart leaves the PREVIOUS process's
|
|
487
|
+
* `running` rows orphaned — their in-process driver (runInBackground, in the dead process's memory) is gone, but the
|
|
488
|
+
* `active/<sessionId>` claim persists on disk and EEXIST-blocks a re-submit (the shell reads the session as stuck).
|
|
489
|
+
* The boot flock (store-backend `LOCK`) already proves no concurrent live owner reached here: a still-alive old
|
|
490
|
+
* engine holds it so the new one FAILS FAST; a dead one had it kernel-released. So a `running` row here is orphaned
|
|
491
|
+
* → flip failed + release its claim UNCONDITIONALLY (no TTL wait, unlike reapStale's steady-state poll).
|
|
492
|
+
* 🔴 EXCEPT a resume-in-progress row: `markResuming` flips a suspended run (suspended→running) while its pending
|
|
493
|
+
* checkpoint still exists, and the resume picker will re-drive it — reclaiming it would STRAND a resumable session
|
|
494
|
+
* as failed. `isResumable(sessionId)` (wired by main.ts boot, which holds the checkpointStore) excludes those; with
|
|
495
|
+
* no callback every running row is treated as orphaned (conservative default when no checkpoint plane is wired).
|
|
496
|
+
* HOST/FILE LANE ONLY — the cross-replica tidb/pg store uses heartbeat→reapStale (a running row there may be alive
|
|
497
|
+
* on another replica; an unconditional reclaim would kill a live fleet run). */
|
|
399
498
|
async reclaimOrphanedAtBoot(isResumable) {
|
|
400
499
|
const orphans = [];
|
|
401
500
|
for (const r of this.runs.values()) {
|
|
@@ -411,9 +510,12 @@ export class FileRunStore {
|
|
|
411
510
|
this.sweepClaims();
|
|
412
511
|
return orphans.length;
|
|
413
512
|
}
|
|
513
|
+
/** [868] wire the checkpoint probe (LocalBackend.checkpoint(), where checkpoint + run store share one root). */
|
|
414
514
|
setCheckpointProbe(probe) {
|
|
415
515
|
this.checkpointProbe = probe;
|
|
416
516
|
}
|
|
517
|
+
/** [868] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired') and
|
|
518
|
+
* PERSIST it — the file twin of the SQL UPDATE (`error`/`error_code` set directly, no result blob). */
|
|
417
519
|
failExpiredPark(r) {
|
|
418
520
|
r.status = "failed";
|
|
419
521
|
r.error = "approval expired before decision";
|
|
@@ -421,6 +523,16 @@ export class FileRunStore {
|
|
|
421
523
|
r.updatedAt = new Date();
|
|
422
524
|
this.persist(r);
|
|
423
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* [868] Durable F4 expiry — the file twin of TiDBRunStore.reapSuspended, driven by the injected checkpoint
|
|
528
|
+
* probe instead of a SQL JOIN (formerly a NO-OP — one of the five closed doors in the "suspended run locks the
|
|
529
|
+
* session forever" incident). Semantics verbatim from the SQL twin: suspended ∧ updatedAt < cutoff ∧ NOT
|
|
530
|
+
* probe.hasPending(session) → failed 'approval expired before decision' / errorCode 'approval.expired' +
|
|
531
|
+
* release the task_active claim. The NOT-pending clause is the design/80 D-D adversarial-blocker guard
|
|
532
|
+
* (tidb-run-store.ts:577-583): a row whose checkpoint is STILL pending belongs to the checkpoint-state sweeps —
|
|
533
|
+
* time-reaping it would orphan a model leg on a now-unlocked session. Probe absent ⇒ honest NO-OP.
|
|
534
|
+
* MemoryRunStore is the parity oracle (byte-identical predicate; only the persist differs).
|
|
535
|
+
*/
|
|
424
536
|
async reapSuspended(olderThanMs) {
|
|
425
537
|
const probe = this.checkpointProbe;
|
|
426
538
|
if (!probe)
|
|
@@ -432,7 +544,10 @@ export class FileRunStore {
|
|
|
432
544
|
continue;
|
|
433
545
|
const snap = r.updatedAt.getTime();
|
|
434
546
|
if (await probe.hasPending(r.sessionId))
|
|
435
|
-
continue;
|
|
547
|
+
continue; // D-D guard: a still-pending checkpoint owns this row
|
|
548
|
+
// codex H2:probe 的 await 让出事件循环——markResuming(approve 恢复)/cancel/新一轮 park 可能已改行。
|
|
549
|
+
// 复核与改写之间无 await(Node 单线程 ⇒ 原子);状态或 updatedAt 变了就放手,绝不覆盖一条已被别腿
|
|
550
|
+
// 认领的行(SQL 孪生靠单条条件 UPDATE 天然原子,这里的 snap 复核就是它的本地等价)。
|
|
436
551
|
if (r.status !== "suspended" || r.updatedAt.getTime() !== snap)
|
|
437
552
|
continue;
|
|
438
553
|
this.failExpiredPark(r);
|
|
@@ -441,6 +556,14 @@ export class FileRunStore {
|
|
|
441
556
|
this.sweepClaims();
|
|
442
557
|
return reaped;
|
|
443
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* [868] design/80 §3 inv#3 crash-safe backstop — the file twin of TiDBRunStore.failSuspendedWithExpiredCheckpoint
|
|
561
|
+
* (checkpoint-STATE-driven, not time-driven; scheduled UNCONDITIONALLY by the main.ts reaper, no
|
|
562
|
+
* APPROVAL_TIMEOUT_SEC gate). Semantics verbatim from the SQL twin: (suspended ∨ needs_review) ∧
|
|
563
|
+
* probe.hasExpired(session) ∧ NOT probe.hasPending(session) → same fail + claim release (wq64gmm5e: an
|
|
564
|
+
* abandoned needs_review park leaks exactly like a suspended one; a re-suspended session that minted a NEW
|
|
565
|
+
* pending gate is excluded by the NOT-pending clause).
|
|
566
|
+
*/
|
|
444
567
|
async failSuspendedWithExpiredCheckpoint() {
|
|
445
568
|
const probe = this.checkpointProbe;
|
|
446
569
|
if (!probe)
|
|
@@ -455,6 +578,7 @@ export class FileRunStore {
|
|
|
455
578
|
continue;
|
|
456
579
|
if (await probe.hasPending(r.sessionId))
|
|
457
580
|
continue;
|
|
581
|
+
// codex H2 同款 snap 复核(两个 await 窗都盖住):行被别腿改过就放手。
|
|
458
582
|
if (r.status !== parkedAs || r.updatedAt.getTime() !== snap)
|
|
459
583
|
continue;
|
|
460
584
|
this.failExpiredPark(r);
|
|
@@ -463,6 +587,8 @@ export class FileRunStore {
|
|
|
463
587
|
this.sweepClaims();
|
|
464
588
|
return reaped;
|
|
465
589
|
}
|
|
590
|
+
// ── claim / run file lifecycle helpers ──
|
|
591
|
+
/** Release a session's single-active claim: unlink the claim file + drop the index entry (idempotent). */
|
|
466
592
|
releaseClaim(sessionId) {
|
|
467
593
|
this.active.delete(sessionId);
|
|
468
594
|
const path = this.activePath(sessionId);
|
|
@@ -471,9 +597,11 @@ export class FileRunStore {
|
|
|
471
597
|
rmSync(path, { force: true });
|
|
472
598
|
}
|
|
473
599
|
catch {
|
|
600
|
+
/* best-effort — a stale claim file is harmless (the index entry is already gone) */
|
|
474
601
|
}
|
|
475
602
|
}
|
|
476
603
|
}
|
|
604
|
+
/** Drop a run entirely (registry + event log + open fd + on-disk dir) — used by deleteBySession. */
|
|
477
605
|
dropRun(taskId) {
|
|
478
606
|
const log = this.eventLogs.get(taskId);
|
|
479
607
|
if (log) {
|
|
@@ -481,6 +609,7 @@ export class FileRunStore {
|
|
|
481
609
|
log.close();
|
|
482
610
|
}
|
|
483
611
|
catch {
|
|
612
|
+
/* best-effort */
|
|
484
613
|
}
|
|
485
614
|
this.eventLogs.delete(taskId);
|
|
486
615
|
}
|
|
@@ -492,15 +621,18 @@ export class FileRunStore {
|
|
|
492
621
|
rmSync(dir, { recursive: true, force: true });
|
|
493
622
|
}
|
|
494
623
|
catch {
|
|
624
|
+
/* best-effort */
|
|
495
625
|
}
|
|
496
626
|
}
|
|
497
627
|
}
|
|
628
|
+
/** Release every open event-log fd (called by LocalBackend.close so a graceful restart can re-open the data dir). */
|
|
498
629
|
dispose() {
|
|
499
630
|
for (const log of this.eventLogs.values()) {
|
|
500
631
|
try {
|
|
501
632
|
log.close();
|
|
502
633
|
}
|
|
503
634
|
catch {
|
|
635
|
+
/* best-effort */
|
|
504
636
|
}
|
|
505
637
|
}
|
|
506
638
|
this.eventLogs.clear();
|