@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
|
@@ -3,21 +3,27 @@ import { escapeLike } from "./sql-escape.js";
|
|
|
3
3
|
import { pgSafeJsonStringify, pgSanitizeText } from "./pg-safe-json.js";
|
|
4
4
|
import { parseJsonLenient as parseJson, toIso as iso } from "./sql-row-helpers.js";
|
|
5
5
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
6
|
+
/** Dual-dialect async run registry + event log. See the file header for the dialect-delta ledger. */
|
|
6
7
|
export class SqlRunStore {
|
|
7
8
|
db;
|
|
8
9
|
constructor(db) {
|
|
9
10
|
this.db = db;
|
|
10
11
|
}
|
|
12
|
+
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
11
13
|
q(tidb, pg) {
|
|
12
14
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
13
15
|
}
|
|
16
|
+
/** mysql2 names the dup-key `ER_DUP_ENTRY`; PG raises SQLSTATE `23505`. */
|
|
14
17
|
isDupKey(err) {
|
|
15
18
|
const code = err?.code;
|
|
16
19
|
return Boolean(err) && (this.db.dialect === "tidb" ? code === "ER_DUP_ENTRY" : code === "23505");
|
|
17
20
|
}
|
|
21
|
+
/** JSON column binding: TiDB stores the string verbatim; PG goes through the protocol-safe envelope
|
|
22
|
+
* (a raw NUL byte in an event payload would otherwise throw 22P05 mid-write). */
|
|
18
23
|
json(value) {
|
|
19
24
|
return this.db.dialect === "tidb" ? JSON.stringify(value) : pgSafeJsonStringify(value);
|
|
20
25
|
}
|
|
26
|
+
/** Run statements atomically; rolls back on failure. */
|
|
21
27
|
async tx(fn) {
|
|
22
28
|
const conn = await this.db.connect();
|
|
23
29
|
try {
|
|
@@ -30,6 +36,7 @@ export class SqlRunStore {
|
|
|
30
36
|
await conn.rollback();
|
|
31
37
|
}
|
|
32
38
|
catch {
|
|
39
|
+
/* ignore */
|
|
33
40
|
}
|
|
34
41
|
throw err;
|
|
35
42
|
}
|
|
@@ -37,6 +44,14 @@ export class SqlRunStore {
|
|
|
37
44
|
conn.release();
|
|
38
45
|
}
|
|
39
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a run, but only if the session has no other active run (double-text guard).
|
|
49
|
+
*
|
|
50
|
+
* Mutual exclusion is a unique-key claim on `task_active(session_id)` — NOT a SELECT…FOR UPDATE,
|
|
51
|
+
* which on TiDB (no gap locks) would fail to serialize two concurrent createRuns when the session
|
|
52
|
+
* has no prior row. A plain INSERT either claims the session or throws a duplicate-key error (TiDB
|
|
53
|
+
* does NOT report a reliable affectedRows for `ON DUPLICATE KEY UPDATE …=…`, so we catch dup-key).
|
|
54
|
+
*/
|
|
40
55
|
async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
|
|
41
56
|
const conn = await this.db.connect();
|
|
42
57
|
try {
|
|
@@ -62,6 +77,8 @@ export class SqlRunStore {
|
|
|
62
77
|
instanceId,
|
|
63
78
|
meta.jobId ?? null,
|
|
64
79
|
meta.source ?? null,
|
|
80
|
+
// PG-only (R12 同族): objective 预览=模型可控标量,走 pgSanitizeText(NUL/protocol-byte 防护)。
|
|
81
|
+
// TiDB 存 verbatim(无该字节类)。
|
|
65
82
|
this.db.dialect === "tidb"
|
|
66
83
|
? meta.objectivePreview ?? null
|
|
67
84
|
: meta.objectivePreview == null
|
|
@@ -78,6 +95,7 @@ export class SqlRunStore {
|
|
|
78
95
|
await conn.rollback();
|
|
79
96
|
}
|
|
80
97
|
catch {
|
|
98
|
+
/* ignore */
|
|
81
99
|
}
|
|
82
100
|
throw err;
|
|
83
101
|
}
|
|
@@ -85,39 +103,79 @@ export class SqlRunStore {
|
|
|
85
103
|
conn.release();
|
|
86
104
|
}
|
|
87
105
|
}
|
|
106
|
+
/** Request cancel of a RUNNING run (durable, cross-replica). Sets the flag the owning instance's heartbeat
|
|
107
|
+
* tick polls; a same-replica caller additionally aborts the in-flight controller for instant effect. Only
|
|
108
|
+
* flags a `running` row (terminal/suspended → 0 affected = no-op). Returns whether a running row was flagged.
|
|
109
|
+
*
|
|
110
|
+
* `owner` is a single-DB-fleet defense-in-depth guard: the WHERE pins the row to the authenticated owner
|
|
111
|
+
* (null-safe compare so a legacy null-owner run matches owner=null), so even if the HTTP owner-gate has a bug
|
|
112
|
+
* a caller can never flip another tenant's run. Pass the run's owner (from the looked-up RunRecord). */
|
|
88
113
|
async requestCancel(taskId, owner) {
|
|
89
114
|
const { affected } = await this.db.query(this.q("UPDATE task_run SET cancel_requested = 1 WHERE task_id = ? AND owner <=> ? AND status = 'running'", "UPDATE task_run SET cancel_requested = 1 WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2 AND status = 'running'"), [taskId, owner]);
|
|
90
115
|
return affected > 0;
|
|
91
116
|
}
|
|
117
|
+
/** Whether a cancel has been requested for this run (polled by the owning instance's heartbeat tick).
|
|
118
|
+
* `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}). */
|
|
92
119
|
async isCancelRequested(taskId, owner) {
|
|
93
120
|
const { rows } = await this.db.query(this.q("SELECT cancel_requested FROM task_run WHERE task_id = ? AND owner <=> ?", "SELECT cancel_requested FROM task_run WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2"), [taskId, owner]);
|
|
94
121
|
return Boolean(rows[0]?.cancel_requested);
|
|
95
122
|
}
|
|
123
|
+
/** design/80 seam #2: request PREEMPT (graceful durable yield, ≠ cancel's kill) of a RUNNING run — durable +
|
|
124
|
+
* cross-replica. Sets the flag the owning instance's heartbeat tick polls (it then aborts its preemptSignal →
|
|
125
|
+
* the task suspends at the next clean turn boundary, gate resource_limit/reason preempt); a same-replica caller
|
|
126
|
+
* additionally aborts the in-flight preempt controller for instant effect. Only flags a `running` row
|
|
127
|
+
* (terminal/suspended → 0 affected = no-op). Returns whether a running row was flagged.
|
|
128
|
+
* `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}). */
|
|
96
129
|
async requestPreempt(taskId, owner) {
|
|
97
130
|
const { affected } = await this.db.query(this.q("UPDATE task_run SET preempt_requested = 1 WHERE task_id = ? AND owner <=> ? AND status = 'running'", "UPDATE task_run SET preempt_requested = 1 WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2 AND status = 'running'"), [taskId, owner]);
|
|
98
131
|
return affected > 0;
|
|
99
132
|
}
|
|
133
|
+
/** Whether a preempt has been requested for this run (polled by the owning instance's heartbeat tick).
|
|
134
|
+
* `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}). */
|
|
100
135
|
async isPreemptRequested(taskId, owner) {
|
|
101
136
|
const { rows } = await this.db.query(this.q("SELECT preempt_requested FROM task_run WHERE task_id = ? AND owner <=> ?", "SELECT preempt_requested FROM task_run WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2"), [taskId, owner]);
|
|
102
137
|
return Boolean(rows[0]?.preempt_requested);
|
|
103
138
|
}
|
|
139
|
+
/** Liveness heartbeat: refresh updated_at for a still-running run, independent of events. The
|
|
140
|
+
* running instance calls this on a timer so a long-but-alive turn (e.g. a subagent that emits no
|
|
141
|
+
* parent events for minutes) is not mistaken for a dead instance.
|
|
142
|
+
* `owner` is the single-DB-fleet null-safe owner guard (see {@link requestCancel}); the heartbeat is
|
|
143
|
+
* always issued by the owning instance with the run's own owner, so this only narrows, never blocks. */
|
|
104
144
|
async heartbeat(taskId, owner) {
|
|
105
145
|
await this.db.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ? AND owner <=> ? AND status = 'running'", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2 AND owner IS NOT DISTINCT FROM $3 AND status = 'running'"), [new Date(), taskId, owner]);
|
|
106
146
|
}
|
|
147
|
+
/** Append one event. A single INSERT — `updated_at` (liveness) is owned by `heartbeat`, so there
|
|
148
|
+
* is no second statement to keep atomic here. */
|
|
107
149
|
async appendEvent(taskId, seq, type, data) {
|
|
108
150
|
await this.db.query(this.q("INSERT INTO task_event (task_id, seq, type, data, ts) VALUES (?,?,?,?,?)", "INSERT INTO task_event (task_id, seq, type, data, ts) VALUES ($1,$2,$3,$4::jsonb,$5)"), [taskId, seq, type, data == null ? null : this.json(data), new Date()]);
|
|
109
151
|
}
|
|
152
|
+
/** Highest event seq for a task (0 if none). Lets a resumed leg CONTINUE the durable event log past the
|
|
153
|
+
* suspend (core 1.70 `resumeStream`) — the suspend wrote events 1..K, the resume appends from K+1 without
|
|
154
|
+
* colliding on the (task_id, seq) PK. */
|
|
110
155
|
async maxSeq(taskId) {
|
|
111
156
|
const { rows } = await this.db.query(this.q("SELECT MAX(seq) AS m FROM task_event WHERE task_id = ?", "SELECT MAX(seq) AS m FROM task_event WHERE task_id = $1"), [taskId]);
|
|
112
157
|
const m = rows[0]?.m;
|
|
113
158
|
return m == null ? 0 : Number(m);
|
|
114
159
|
}
|
|
160
|
+
/** Mark terminal AND release the single-active-run claim atomically — if the DELETE were a separate
|
|
161
|
+
* statement that failed, the session would stay permanently locked. [888] FIRST terminal writer wins:
|
|
162
|
+
* the UPDATE is CAS'd POSITIVELY on the live statuses (running/suspended/needs_review) — every call site
|
|
163
|
+
* is a settle-once flow whose backstops may double-fire (cancel × leg-throw × reaper), and an
|
|
164
|
+
* unconditional write let the LAST racer overwrite errorCode (a `cancelled` row flipping to a generic
|
|
165
|
+
* abort text, or vice versa). Positive form ([1.207 codex M1]): the negative NOT IN missed the
|
|
166
|
+
* blocked/timeout terminals — a first write of those could still be overwritten. The claim DELETE stays
|
|
167
|
+
* unconditional (idempotent; the first writer already released it). */
|
|
115
168
|
async setTerminal(taskId, status, result, error) {
|
|
169
|
+
// Denormalize the structured code into its own column so operators can `WHERE error_code LIKE
|
|
170
|
+
// 'budget.%'` without parsing every result JSON. Source of truth stays the result blob.
|
|
116
171
|
const errorCode = result?.errorCode ?? null;
|
|
117
172
|
await this.tx(async (conn) => {
|
|
118
173
|
await conn.query(this.q("UPDATE task_run SET status = ?, result = ?, error = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','suspended','needs_review')", "UPDATE task_run SET status = $1, result = $2::jsonb, error = $3, error_code = $4, updated_at = $5 WHERE task_id = $6 AND status IN ('running','suspended','needs_review')"), [
|
|
119
174
|
status,
|
|
120
175
|
result == null ? null : this.json(result),
|
|
176
|
+
// codex R12 (PG only): error/errorCode = 裸 TEXT 参数 — 真 NUL 直炸 22P05,UPDATE 抛 → 事务回滚 →
|
|
177
|
+
// task_active 保持 → run 卡非终态到 reaper(错误文案带脏字节的形:tool 报错内嵌 raw 输出)。观测标量
|
|
178
|
+
// 同族有损 sanitize(与 result jsonb 同臂同纪律)。TiDB 存 verbatim。
|
|
121
179
|
this.db.dialect === "tidb" ? error : error == null ? null : pgSanitizeText(error),
|
|
122
180
|
this.db.dialect === "tidb" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
|
|
123
181
|
new Date(),
|
|
@@ -126,20 +184,64 @@ export class SqlRunStore {
|
|
|
126
184
|
await conn.query(this.q("DELETE FROM task_active WHERE task_id = ?", "DELETE FROM task_active WHERE task_id = $1"), [taskId]);
|
|
127
185
|
});
|
|
128
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Durable F4 (design/45): a task suspended on a policy `ask`. Sets the run NON-terminal `suspended` and
|
|
189
|
+
* KEEPS the `task_active` claim — the session stays locked (no new run) until an operator resumes it to a
|
|
190
|
+
* terminal state or the reaper expires it. The token-bearing result is NOT stored (the submitter polling
|
|
191
|
+
* this row must never receive the capability token); only the status flips.
|
|
192
|
+
*/
|
|
129
193
|
async setSuspended(taskId) {
|
|
194
|
+
// CAS on `running` (idempotent on an already-suspended row): without it, a heartbeat-stall race where
|
|
195
|
+
// `reapStale` already flipped the row to `failed` and DELETED its `task_active` claim would be reverted
|
|
196
|
+
// here to `suspended` WITHOUT re-claiming `task_active` — leaving a suspended run with no session lock, so
|
|
197
|
+
// a new run could start on the same session while the checkpoint is still pending. The CAS makes the
|
|
198
|
+
// reaped→suspended revert a no-op, so the reaper's decision stands and the invariant holds.
|
|
130
199
|
await this.db.query(this.q("UPDATE task_run SET status = 'suspended', updated_at = ? WHERE task_id = ? AND status IN ('running','suspended')", "UPDATE task_run SET status = 'suspended', updated_at = $1 WHERE task_id = $2 AND status IN ('running','suspended')"), [new Date(), taskId]);
|
|
131
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* design/80 D-B: PARK a run that paused on a REVIEW gate — a `needs_review` result (a dry-run diff review OR a
|
|
203
|
+
* pre-action `plan_review`; core assemble-result slot 8.6). Like {@link setSuspended} this is a NON-terminal park
|
|
204
|
+
* that KEEPS the `task_active` session claim (it does NOT delete it — only {@link setTerminal} does), so the
|
|
205
|
+
* resume path (`getActiveTaskId` → `markResuming`) can find + claim the row. core treats a review pause "like a
|
|
206
|
+
* suspend, an INTENTIONAL durable pause" (design/76 §2.5) — the service MUST park it the same way, else the run is
|
|
207
|
+
* terminalized + its lock released and the plan_review/dry_run_review resume can never run (it gets
|
|
208
|
+
* taskId=undefined and drives the model unprotected on an unlocked session). CAS on `running`/`needs_review`
|
|
209
|
+
* (reaper-revert guard, mirrors setSuspended). 🔴 The store SQL here is exercised by mocked unit tests only — a
|
|
210
|
+
* real-TiDB integration test of the park→resume→claim invariant is a required-before-GA TODO (the lesson from the
|
|
211
|
+
* P2 lease-SQL + this needs_review-park bug: mocked tests hide store-SQL defects).
|
|
212
|
+
*/
|
|
132
213
|
async setNeedsReview(taskId) {
|
|
133
214
|
await this.db.query(this.q("UPDATE task_run SET status = 'needs_review', updated_at = ? WHERE task_id = ? AND status IN ('running','needs_review')", "UPDATE task_run SET status = 'needs_review', updated_at = $1 WHERE task_id = $2 AND status IN ('running','needs_review')"), [new Date(), taskId]);
|
|
134
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Durable F4 resume: a suspended run is being actively resumed. Flip it back to `running` (CAS — only if
|
|
218
|
+
* still suspended) and refresh liveness, so the in-flight resume is protected by the running-run heartbeat
|
|
219
|
+
* and is NOT killed by `reapSuspended` (which targets `status='suspended'` past the approval TTL). Without
|
|
220
|
+
* this the row sits at `suspended`/updated_at=suspend-time for the WHOLE resume execution, so a resume that
|
|
221
|
+
* crosses the TTL is marked `failed` mid-run and its session lock (`task_active`) is released. Returns true
|
|
222
|
+
* if it claimed the flip (false ⇒ the reaper already expired it, or it isn't suspended).
|
|
223
|
+
*/
|
|
135
224
|
async markResuming(taskId) {
|
|
136
|
-
const { affected } = await this.db.query(
|
|
225
|
+
const { affected } = await this.db.query(
|
|
226
|
+
// cancel_requested is RESET on resume: a cancel that raced the suspend (flag set but never honored —
|
|
227
|
+
// the run parked first) must not silently kill an operator-APPROVED resume later. Cancelling a suspended
|
|
228
|
+
// run is the deny-approval path; a cancel of the resumed leg sets the flag afresh. preempt_requested is
|
|
229
|
+
// RESET for the same reason (design/80 seam #2): a preempt flag that survived into the resumed leg would
|
|
230
|
+
// make the heartbeat re-preempt it immediately (you resume to make PROGRESS) — the durable twin of core's
|
|
231
|
+
// pre-aborted-signal strip; a genuine re-preempt of the resumed leg sets the flag afresh.
|
|
232
|
+
// design/80 D-B: a `needs_review` (plan_review / dry_run_review) park is resumable too — the CAS accepts it
|
|
233
|
+
// alongside `suspended` so the plan_review resume can claim the parked row. The flag-reset rationale is
|
|
234
|
+
// identical for both park kinds.
|
|
235
|
+
this.q("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = ? WHERE task_id = ? AND status IN ('suspended','needs_review')", "UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = $1 WHERE task_id = $2 AND status IN ('suspended','needs_review')"), [new Date(), taskId]);
|
|
137
236
|
return affected === 1;
|
|
138
237
|
}
|
|
238
|
+
/** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
|
|
139
239
|
async getActiveTaskId(sessionId) {
|
|
140
240
|
const { rows } = await this.db.query(this.q("SELECT task_id FROM task_active WHERE session_id = ?", "SELECT task_id FROM task_active WHERE session_id = $1"), [sessionId]);
|
|
141
241
|
return rows[0] ? String(rows[0].task_id) : undefined;
|
|
142
242
|
}
|
|
243
|
+
/** Shared row→record mapper — byte-identical between dialects (both drivers hand back the same column
|
|
244
|
+
* names + comparable JS types after SqlDriver's result normalization). */
|
|
143
245
|
rowToRun(r) {
|
|
144
246
|
return {
|
|
145
247
|
taskId: String(r.task_id),
|
|
@@ -161,7 +263,18 @@ export class SqlRunStore {
|
|
|
161
263
|
const { rows } = await this.db.query(this.q("SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, instance_id, objective_preview, created_at, updated_at FROM task_run WHERE task_id = ?", "SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, instance_id, objective_preview, created_at, updated_at FROM task_run WHERE task_id = $1"), [taskId]);
|
|
162
264
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
163
265
|
}
|
|
266
|
+
/** Task Trace list (S1): runs newest-first, keyset-paginated on (created_at, task_id). Optional status +
|
|
267
|
+
* jobId filters (jobId = the work-view group, served by idx_job). Caller passes `limit+1` to detect a next
|
|
268
|
+
* page. Cursor fields are bound as params (no SQL injection).
|
|
269
|
+
*
|
|
270
|
+
* 🔴 Genuinely-divergent BUILDER (not just SQL text): TiDB's `?` is position-agnostic, so the WHERE array is
|
|
271
|
+
* built with plain string pushes in call order. PG's `$n` needs a NUMBERED placeholder per param, so that arm
|
|
272
|
+
* keeps its own positional-counter closure (`p()`) — collapsing the two into one shared counter would hide
|
|
273
|
+
* the very placeholder-numbering divergence this file exists to keep visible. */
|
|
164
274
|
async listRuns(opts) {
|
|
275
|
+
// 夜测 N1(2026-07-09,LOW):a non-date cursor.createdAt made the four backends diverge (PG threw a raw
|
|
276
|
+
// DB error; the other three returned an empty page). HTTP's decodeCursor pre-validates, so this is
|
|
277
|
+
// defence-in-depth for direct store callers — normalize ALL backends to the empty page.
|
|
165
278
|
if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
|
|
166
279
|
return [];
|
|
167
280
|
if (this.db.dialect === "tidb") {
|
|
@@ -180,11 +293,11 @@ export class SqlRunStore {
|
|
|
180
293
|
params.push(opts.source);
|
|
181
294
|
}
|
|
182
295
|
if (opts.owner) {
|
|
183
|
-
where.push("owner = ?");
|
|
296
|
+
where.push("owner = ?"); // idx_owner — the per-user list view (portal scopes a normal user to their own)
|
|
184
297
|
params.push(opts.owner);
|
|
185
298
|
}
|
|
186
299
|
if (opts.cursor) {
|
|
187
|
-
where.push("(created_at < ? OR (created_at = ? AND task_id < ?))");
|
|
300
|
+
where.push("(created_at < ? OR (created_at = ? AND task_id < ?))"); // keyset: strictly older, ties by task_id
|
|
188
301
|
params.push(new Date(opts.cursor.createdAt), new Date(opts.cursor.createdAt), opts.cursor.taskId);
|
|
189
302
|
}
|
|
190
303
|
const sql = "SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, objective_preview, created_at, updated_at FROM task_run" +
|
|
@@ -220,6 +333,12 @@ export class SqlRunStore {
|
|
|
220
333
|
const { rows } = await this.db.query(sql, params);
|
|
221
334
|
return rows.map((r) => this.rowToRun(r));
|
|
222
335
|
}
|
|
336
|
+
/** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
|
|
337
|
+
* (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
|
|
338
|
+
* session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
|
|
339
|
+
* Bounded by per-tenant data today; a denormalized session table is the optimization if it ever goes hot.
|
|
340
|
+
*
|
|
341
|
+
* 🔴 Same builder divergence as {@link listRuns} — see its header note. */
|
|
223
342
|
async listSessions(opts) {
|
|
224
343
|
if (this.db.dialect === "tidb") {
|
|
225
344
|
const params = [];
|
|
@@ -230,15 +349,18 @@ export class SqlRunStore {
|
|
|
230
349
|
}
|
|
231
350
|
const outer = ["rn = 1"];
|
|
232
351
|
if (opts.q) {
|
|
352
|
+
// `?q=` — ANY-run preview match, session-granular (an outer LIKE on the rn=1 row alone would
|
|
353
|
+
// miss sessions whose match lives in an older run). LOWER-LIKE = collation-proof ci; escapeLike pins wildcards.
|
|
354
|
+
// Owner/sql_mode rationale: see tidb-session-store.ts listSessions.
|
|
233
355
|
outer.push("EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = t.session_id AND LOWER(tr.objective_preview) LIKE LOWER(?) ESCAPE '\\\\')");
|
|
234
356
|
params.push(`%${escapeLike(opts.q)}%`);
|
|
235
357
|
}
|
|
236
358
|
if (opts.cursor) {
|
|
237
|
-
outer.push("(last_activity < ? OR (last_activity = ? AND session_id < ?))");
|
|
359
|
+
outer.push("(last_activity < ? OR (last_activity = ? AND session_id < ?))"); // keyset: strictly older, ties by session_id
|
|
238
360
|
params.push(new Date(opts.cursor.lastActivityAt), new Date(opts.cursor.lastActivityAt), opts.cursor.sessionId);
|
|
239
361
|
}
|
|
240
362
|
const sql = "SELECT session_id, owner, task_id, last_activity, first_activity, run_count, objective_preview, status FROM (" +
|
|
241
|
-
"SELECT session_id, owner, task_id, objective_preview, status," +
|
|
363
|
+
"SELECT session_id, owner, task_id, objective_preview, status," + // K-5c: carry the rn=1 (latest) run's task_id
|
|
242
364
|
" ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at DESC, task_id DESC) AS rn," +
|
|
243
365
|
" MAX(created_at) OVER (PARTITION BY session_id) AS last_activity," +
|
|
244
366
|
" MIN(created_at) OVER (PARTITION BY session_id) AS first_activity," +
|
|
@@ -256,6 +378,7 @@ export class SqlRunStore {
|
|
|
256
378
|
};
|
|
257
379
|
const innerWhere = opts.owner ? ` WHERE owner = ${p(opts.owner)}` : "";
|
|
258
380
|
const outer = ["rn = 1"];
|
|
381
|
+
// `?q=` — the PG twin of the TiDB EXISTS predicate above.
|
|
259
382
|
if (opts.q)
|
|
260
383
|
outer.push(`EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = t.session_id AND LOWER(tr.objective_preview) LIKE LOWER(${p(`%${escapeLike(opts.q)}%`)}) ESCAPE '\\')`);
|
|
261
384
|
if (opts.cursor) {
|
|
@@ -265,7 +388,7 @@ export class SqlRunStore {
|
|
|
265
388
|
outer.push(`(last_activity < ${a} OR (last_activity = ${b} AND session_id < ${sid}))`);
|
|
266
389
|
}
|
|
267
390
|
const sql = "SELECT session_id, owner, task_id, last_activity, first_activity, run_count, objective_preview, status FROM (" +
|
|
268
|
-
"SELECT session_id, owner, task_id, objective_preview, status," +
|
|
391
|
+
"SELECT session_id, owner, task_id, objective_preview, status," + // K-5c: carry the rn=1 (latest) run's task_id
|
|
269
392
|
" ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at DESC, task_id DESC) AS rn," +
|
|
270
393
|
" MAX(created_at) OVER (PARTITION BY session_id) AS last_activity," +
|
|
271
394
|
" MIN(created_at) OVER (PARTITION BY session_id) AS first_activity," +
|
|
@@ -275,6 +398,7 @@ export class SqlRunStore {
|
|
|
275
398
|
const { rows } = await this.db.query(sql, params);
|
|
276
399
|
return rows.map((r) => this.rowToSession(r));
|
|
277
400
|
}
|
|
401
|
+
/** Shared row→summary mapper for listSessions (byte-identical between dialects). */
|
|
278
402
|
rowToSession(r) {
|
|
279
403
|
return {
|
|
280
404
|
sessionId: String(r.session_id),
|
|
@@ -284,18 +408,41 @@ export class SqlRunStore {
|
|
|
284
408
|
runCount: Number(r.run_count),
|
|
285
409
|
objectivePreview: r.objective_preview ?? null,
|
|
286
410
|
lastStatus: String(r.status),
|
|
287
|
-
lastRunId: r.task_id ?? null,
|
|
288
|
-
title: null,
|
|
411
|
+
lastRunId: r.task_id ?? null, // K-5c: the latest run's id (the live-tail re-attach anchor)
|
|
412
|
+
title: null, // the legacy task_run aggregate has no session_meta — honest degrade
|
|
289
413
|
};
|
|
290
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* E21 (§0.5 session delete) — purge the service-owned RUNS LEDGER for one session: task_event (the replayable
|
|
417
|
+
* event log), task_active (the single-active-run claim), and task_run (the run rows). Owner-guarded (null-safe
|
|
418
|
+
* compare, single-DB defense-in-depth, mirrors {@link requestCancel}) so even a buggy HTTP gate can't purge
|
|
419
|
+
* another tenant's runs. Idempotent — returns `{ removed }` (the count of task_run rows removed; 0 = nothing to
|
|
420
|
+
* purge). The conversation HISTORY (session_meta/session_event) is purged separately by the session store; the
|
|
421
|
+
* checkpoint/tool-result rows by their stores — this method owns only the runs-ledger tables. Done in one TX so a
|
|
422
|
+
* partial purge can't leak a task_active claim with no run row (a permanent session lock).
|
|
423
|
+
*
|
|
424
|
+
* 🔴 Active-run re-assert INSIDE the TX (adversarial-review LOW — TOCTOU): the route's pre-purge `getActiveTaskId`
|
|
425
|
+
* 409 check is a non-transactional SELECT — a concurrent createRun can claim `task_active(session_id)` in the
|
|
426
|
+
* window between that check and this purge, then have its LIVE rows deleted here. So we re-take the no-active-run
|
|
427
|
+
* invariant transactionally: `SELECT task_id FROM task_active WHERE session_id=? FOR UPDATE` at the TOP of the TX.
|
|
428
|
+
* `FOR UPDATE` serializes against createRun's unique-key INSERT on the SAME session_id (createRun either already
|
|
429
|
+
* holds the row — we see it and abort — or blocks on our lock until commit, then fails its own unique-key claim
|
|
430
|
+
* against the now-absent row). If a claim exists we return `{ active }` (the route maps it to 409) and delete
|
|
431
|
+
* NOTHING. Returns `{ removed }` on a clean purge.
|
|
432
|
+
*
|
|
433
|
+
* 🔴 The multi-table DELETEs are dialect-divergent SQL FORMS (not just placeholders): TiDB's
|
|
434
|
+
* `DELETE te FROM te JOIN tr ON …` vs PG's `DELETE FROM te USING tr WHERE …`.
|
|
435
|
+
*/
|
|
291
436
|
async deleteBySession(sessionId, owner) {
|
|
292
437
|
let result = { removed: 0 };
|
|
293
438
|
await this.tx(async (conn) => {
|
|
439
|
+
// Transactional no-active-run gate (FOR UPDATE serializes against createRun's task_active claim).
|
|
294
440
|
const { rows: active } = await conn.query(this.q("SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE", "SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE"), [sessionId]);
|
|
295
441
|
if (active[0]) {
|
|
296
442
|
result = { active: String(active[0].task_id) };
|
|
297
|
-
return;
|
|
443
|
+
return; // abort — delete nothing; the route 409s (don't purge a live run's rows)
|
|
298
444
|
}
|
|
445
|
+
// task_event has no owner column → scope it through the owner-guarded task_run set for this session.
|
|
299
446
|
await conn.query(this.q("DELETE te FROM task_event te JOIN task_run tr ON te.task_id = tr.task_id WHERE tr.session_id = ? AND tr.owner <=> ?", "DELETE FROM task_event te USING task_run tr WHERE te.task_id = tr.task_id AND tr.session_id = $1 AND tr.owner IS NOT DISTINCT FROM $2"), [sessionId, owner]);
|
|
300
447
|
await conn.query(this.q("DELETE ta FROM task_active ta JOIN task_run tr ON ta.task_id = tr.task_id WHERE tr.session_id = ? AND tr.owner <=> ?", "DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.session_id = $1 AND tr.owner IS NOT DISTINCT FROM $2"), [sessionId, owner]);
|
|
301
448
|
const { affected } = await conn.query(this.q("DELETE FROM task_run WHERE session_id = ? AND owner <=> ?", "DELETE FROM task_run WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2"), [sessionId, owner]);
|
|
@@ -303,10 +450,15 @@ export class SqlRunStore {
|
|
|
303
450
|
});
|
|
304
451
|
return result;
|
|
305
452
|
}
|
|
453
|
+
/** Per-door usage rollup (center systems-UI input): COUNT by (source,status) since a cutoff. Reads the
|
|
454
|
+
* denormalized columns only — no JSON parsing; served by full scan bounded by created_at (small windows). */
|
|
306
455
|
async sourceSummary(sinceMs) {
|
|
307
456
|
const { rows } = await this.db.query(this.q("SELECT source, status, COUNT(*) AS c FROM task_run WHERE created_at >= ? GROUP BY source, status", "SELECT source, status, COUNT(*) AS c FROM task_run WHERE created_at >= $1 GROUP BY source, status"), [new Date(sinceMs)]);
|
|
308
457
|
return rows.map((r) => ({ source: r.source ?? null, status: String(r.status), count: Number(r.c) }));
|
|
309
458
|
}
|
|
459
|
+
/** 用量扫窗(usage-analytics):按时间窗取 {owner,created_at,status,result} 四列,行内投影
|
|
460
|
+
* stats(JSON 提取在 JS,免 SQL JSON 方言分叉);LIMIT+1 探测截断(触顶=truncated:true 诚实上抛)。
|
|
461
|
+
* 聚合在 src/usage-analytics.ts 纯函数(三后端同代码)。可选 owner 过滤=JWT 主体只看自己。 */
|
|
310
462
|
async usageScan(fromMs, toMs, opts) {
|
|
311
463
|
const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
|
|
312
464
|
const where = [this.q("created_at >= ?", "created_at >= $1"), this.q("created_at < ?", "created_at < $2")];
|
|
@@ -328,11 +480,14 @@ export class SqlRunStore {
|
|
|
328
480
|
})),
|
|
329
481
|
};
|
|
330
482
|
}
|
|
483
|
+
/** Earliest retained event seq for a task (0 if none) → `retainedFrom` for /turns and the 416 boundary for
|
|
484
|
+
* /stream (a resume point below this has been evicted past retention). */
|
|
331
485
|
async retainedFrom(taskId) {
|
|
332
486
|
const { rows } = await this.db.query(this.q("SELECT MIN(seq) AS m FROM task_event WHERE task_id = ?", "SELECT MIN(seq) AS m FROM task_event WHERE task_id = $1"), [taskId]);
|
|
333
487
|
const m = rows[0]?.m;
|
|
334
488
|
return m == null ? 0 : Number(m);
|
|
335
489
|
}
|
|
490
|
+
/** Events with seq strictly greater than `afterSeq` (use 0 for the whole stream). */
|
|
336
491
|
async getEvents(taskId, afterSeq) {
|
|
337
492
|
const { rows } = await this.db.query(this.q("SELECT seq, type, data, ts FROM task_event WHERE task_id = ? AND seq > ? ORDER BY seq ASC", "SELECT seq, type, data, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC"), [taskId, afterSeq]);
|
|
338
493
|
return rows.map((r) => ({
|
|
@@ -342,6 +497,12 @@ export class SqlRunStore {
|
|
|
342
497
|
ts: iso(r.ts),
|
|
343
498
|
}));
|
|
344
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Mark stale `running` rows as failed (best-effort recovery for runs whose instance died), then
|
|
502
|
+
* free any active-run claim whose run is no longer running (reaped here, or a terminal whose
|
|
503
|
+
* DELETE was lost). `olderThanMs` must exceed the longest expected gap between a run's events
|
|
504
|
+
* (each event refreshes `updated_at`), else a healthy slow run is wrongly reaped.
|
|
505
|
+
*/
|
|
345
506
|
async reapStale(olderThanMs) {
|
|
346
507
|
const cutoff = new Date(Date.now() - olderThanMs);
|
|
347
508
|
let reaped = 0;
|
|
@@ -350,10 +511,32 @@ export class SqlRunStore {
|
|
|
350
511
|
"WHERE status = 'running' AND updated_at < ?", "UPDATE task_run SET status = 'failed', error = 'run stalled (instance lost?)', updated_at = $1 " +
|
|
351
512
|
"WHERE status = 'running' AND updated_at < $2"), [new Date(), cutoff]);
|
|
352
513
|
reaped = affected;
|
|
514
|
+
// Release task_active for TERMINAL rows only. A 'suspended' row (durable F4) intentionally KEEPS its
|
|
515
|
+
// claim — the session stays locked until an operator resumes it or reapSuspended expires it. (Was
|
|
516
|
+
// `<> 'running'`, which would have prematurely unlocked a parked suspended session.)
|
|
353
517
|
await conn.query(this.q("DELETE ta FROM task_active ta JOIN task_run tr ON ta.task_id = tr.task_id WHERE tr.status NOT IN ('running','suspended','needs_review')", "DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.status NOT IN ('running','suspended','needs_review')"));
|
|
354
518
|
});
|
|
355
519
|
return reaped;
|
|
356
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Durable F4 expiry: an approval parked beyond `olderThanMs` (the approval TTL) was never answered → fail
|
|
523
|
+
* the run + RELEASE its task_active (unlock the session). Mirrors the checkpoint reaper's CAS-expire (≈ deny);
|
|
524
|
+
* idempotent across replicas. Returns count.
|
|
525
|
+
*
|
|
526
|
+
* 🔴 design/80 D-D (adversarial blocker fix): SKIPS any row whose checkpoint is STILL pending — that row is
|
|
527
|
+
* owned by a checkpoint-state-driven sweep (the SLA deny-sweep for human/irreversible_ask, or
|
|
528
|
+
* failSuspendedWithExpiredCheckpoint once the checkpoint is expired). Without this guard, this time-based
|
|
529
|
+
* UPDATE fired at the SAME instant as the deny-sweep (deadline == createdAt + ttl, both keyed on the TTL),
|
|
530
|
+
* almost always committed FIRST (it is a single UPDATE), and deleted task_active + failed the run BEFORE the
|
|
531
|
+
* deny-sweep's resumeCheckpoint ran — so resumeCheckpoint saw no task row (markResuming skipped) yet STILL ran
|
|
532
|
+
* the model on a now-unlocked session (the pending checkpoint's resolve-CAS wins), orphaning a model leg and
|
|
533
|
+
* opening a two-runs-one-session window. The NOT-EXISTS-pending guard (same shape as
|
|
534
|
+
* failSuspendedWithExpiredCheckpoint) means this only ever reaps a row whose checkpoint is already gone/expired.
|
|
535
|
+
*
|
|
536
|
+
* 🔴 Alias-qualification divergence (kept EXPLICIT, never "fixed" to match): TiDB's SET clause QUALIFIES with
|
|
537
|
+
* `tr.` (`SET tr.status = …`); PG's SET clause must NOT be alias-qualified (`SET status = …` — PG rejects
|
|
538
|
+
* `SET a.col`). Both WHERE clauses use the `tr.` alias.
|
|
539
|
+
*/
|
|
357
540
|
async reapSuspended(olderThanMs) {
|
|
358
541
|
const cutoff = new Date(Date.now() - olderThanMs);
|
|
359
542
|
let reaped = 0;
|
|
@@ -368,10 +551,26 @@ export class SqlRunStore {
|
|
|
368
551
|
});
|
|
369
552
|
return reaped;
|
|
370
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* design/80 §3 inv#3 (crash-safe backstop — the run-row half of the checkpoint `terminal_at` sweep): fail a
|
|
556
|
+
* suspended run whose durable checkpoint was ALREADY EXPIRED by `reapExpired` (past its `deadline` or its
|
|
557
|
+
* absolute `terminal_at`), and release its task_active (unlock the session). Runs UNCONDITIONALLY (no
|
|
558
|
+
* approval-TTL gate), CHECKPOINT-STATE-driven not time-driven, so it aligns EXACTLY with the per-row
|
|
559
|
+
* `terminal_at` (which never falls before an operator's >30d deadline) — unlike a uniform timer, which would
|
|
560
|
+
* either be inert (gated off at APPROVAL_TIMEOUT_SEC=0) or prematurely kill a long gate. Safe against the two
|
|
561
|
+
* windows a naive predicate mis-fires on: (a) the transient suspend-write window — a run suspended before its
|
|
562
|
+
* checkpoint row exists has NO expired checkpoint, so it is not matched; (b) a re-suspended session that minted
|
|
563
|
+
* a NEW pending checkpoint — the `NOT EXISTS pending` clause excludes it. Idempotent across replicas. Returns count.
|
|
564
|
+
*
|
|
565
|
+
* Same alias-qualification divergence as {@link reapSuspended} — see its header note.
|
|
566
|
+
*/
|
|
371
567
|
async failSuspendedWithExpiredCheckpoint() {
|
|
372
568
|
let reaped = 0;
|
|
373
569
|
await this.tx(async (conn) => {
|
|
374
570
|
const { affected } = await conn.query(this.q("UPDATE task_run tr SET tr.status = 'failed', tr.error = 'approval expired before decision', tr.error_code = 'approval.expired', tr.updated_at = ? " +
|
|
571
|
+
// wq64gmm5e HIGH: a needs_review park is ALSO a non-terminal claim-keeping state; an abandoned review
|
|
572
|
+
// park whose checkpoint reapExpired already expired must be failed + its task_active released, exactly
|
|
573
|
+
// like a suspended park — else the run row + session lock leak forever (no other reaper targets it).
|
|
375
574
|
"WHERE tr.status IN ('suspended','needs_review') " +
|
|
376
575
|
"AND EXISTS (SELECT 1 FROM checkpoint ce WHERE ce.session_id = tr.session_id AND ce.status = 'expired') " +
|
|
377
576
|
"AND NOT EXISTS (SELECT 1 FROM checkpoint cp WHERE cp.session_id = tr.session_id AND cp.status = 'pending')", "UPDATE task_run AS tr SET status = 'failed', error = 'approval expired before decision', error_code = 'approval.expired', updated_at = $1 " +
|
|
@@ -384,11 +583,13 @@ export class SqlRunStore {
|
|
|
384
583
|
return reaped;
|
|
385
584
|
}
|
|
386
585
|
}
|
|
586
|
+
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
387
587
|
export class TiDBRunStore extends SqlRunStore {
|
|
388
588
|
constructor(pool) {
|
|
389
589
|
super(mysqlDriver(pool));
|
|
390
590
|
}
|
|
391
591
|
}
|
|
592
|
+
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
392
593
|
export class PgRunStore extends SqlRunStore {
|
|
393
594
|
constructor(pool) {
|
|
394
595
|
super(pgDriver(pool));
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
export interface S3PresignParams {
|
|
2
|
+
/** Base endpoint, e.g. `http://minio.minio.svc.cluster.local:9000` (the host the SANDBOX will reach). */
|
|
2
3
|
endpoint: string;
|
|
3
4
|
bucket: string;
|
|
5
|
+
/** Object key (path within the bucket); each "/" segment is encoded, "/" preserved. */
|
|
4
6
|
key: string;
|
|
5
7
|
method: "GET" | "PUT" | "HEAD" | "DELETE";
|
|
6
8
|
accessKey: string;
|
|
7
9
|
secretKey: string;
|
|
8
10
|
region?: string;
|
|
9
11
|
expiresSec?: number;
|
|
12
|
+
/** Override the signing clock (tests). Defaults to now. */
|
|
10
13
|
now?: Date;
|
|
11
14
|
}
|
|
15
|
+
/** Build a SigV4 presigned **single-object** URL. Pure string math — no network. */
|
|
12
16
|
export declare function presignS3Url(p: S3PresignParams): string;
|
|
13
17
|
export interface S3BucketPresignParams {
|
|
14
18
|
endpoint: string;
|
|
15
19
|
bucket: string;
|
|
20
|
+
/** Bucket-level verbs only: PUT = CreateBucket, DELETE = DeleteBucket (MinIO path-style, live-verified). */
|
|
16
21
|
method: "PUT" | "DELETE";
|
|
17
22
|
accessKey: string;
|
|
18
23
|
secretKey: string;
|
|
19
24
|
region?: string;
|
|
20
25
|
expiresSec?: number;
|
|
26
|
+
/** Override the signing clock (tests). Defaults to now. */
|
|
21
27
|
now?: Date;
|
|
22
28
|
}
|
|
29
|
+
/** Build a SigV4 presigned **bucket-level** URL (`/<bucket>/`, no key) — the explicit face for
|
|
30
|
+
* CreateBucket/DeleteBucket (codex 1.189 F8: previously test fixtures smuggled this through
|
|
31
|
+
* presignS3Url with key "", which the single-object contract now rejects). Same signing core. */
|
|
23
32
|
export declare function presignS3BucketUrl(p: S3BucketPresignParams): string;
|
|
33
|
+
/** ListObjectsV2 presign(D-1 孤儿对象 GC,clay 拍 2026-07-28):GET `/<bucket>/?list-type=2&prefix=…`。
|
|
34
|
+
* bucket 级 GET 是**列举能力**——只给 server 端 sweep 用,URL 绝不外发(与 copy 面同纪律)。 */
|
|
24
35
|
export declare function presignS3ListUrl(p: {
|
|
25
36
|
endpoint: string;
|
|
26
37
|
bucket: string;
|
|
@@ -34,9 +45,12 @@ export declare function presignS3ListUrl(p: {
|
|
|
34
45
|
now?: Date;
|
|
35
46
|
}): string;
|
|
36
47
|
export interface S3CopyPresignParams {
|
|
48
|
+
/** INTERNAL endpoint — CopyObject is a SERVER-side request with adapter creds; never hand this URL out. */
|
|
37
49
|
endpoint: string;
|
|
50
|
+
/** Destination coordinates (the PUT target). */
|
|
38
51
|
bucket: string;
|
|
39
52
|
key: string;
|
|
53
|
+
/** Source coordinates (ride the signed `x-amz-copy-source` header). */
|
|
40
54
|
sourceBucket: string;
|
|
41
55
|
sourceKey: string;
|
|
42
56
|
accessKey: string;
|
|
@@ -45,6 +59,12 @@ export interface S3CopyPresignParams {
|
|
|
45
59
|
expiresSec?: number;
|
|
46
60
|
now?: Date;
|
|
47
61
|
}
|
|
62
|
+
/** Build a SigV4 presigned **CopyObject** request (修3 staging-key two-phase): S3 CopyObject = `PUT
|
|
63
|
+
* /<dstBucket>/<dstKey>` with `x-amz-copy-source: /<srcBucket>/<srcKey>`. Query auth requires every
|
|
64
|
+
* `x-amz-*` request header to be SIGNED, so the copy-source header goes through the signing core's
|
|
65
|
+
* signedHeaders seam; the caller MUST send `headers` verbatim on the fetch (the source key is
|
|
66
|
+
* RFC-3986-encoded — MinIO/S3 both accept the encoded copy-source form, and header values must be
|
|
67
|
+
* ASCII anyway). Same single-object guards as presignS3Url on BOTH sides. */
|
|
48
68
|
export declare function presignS3CopyUrl(p: S3CopyPresignParams): {
|
|
49
69
|
url: string;
|
|
50
70
|
headers: Record<string, string>;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal AWS SigV4 **presigned URL** generator for S3-compatible object stores (MinIO) — zero dependencies
|
|
3
|
+
* (crypto only), matching the repo's no-SDK posture (OTLP exporter, the k8s adapter). We only need presign:
|
|
4
|
+
* the k8s sandbox adapter uploads/downloads a workspace snapshot by handing the SANDBOX a short-lived,
|
|
5
|
+
* single-object URL so the adapter's MinIO credentials never enter the sandbox (design/53 lethal-trifecta
|
|
6
|
+
* red line — "adapter-held, sandbox sees only a presigned URL").
|
|
7
|
+
*
|
|
8
|
+
* Path-style addressing (`<endpoint>/<bucket>/<key>`) — MinIO's default and what an IP/cluster-DNS endpoint
|
|
9
|
+
* needs (no virtual-host bucket subdomain). Payload is UNSIGNED-PAYLOAD so a streamed `curl -T -` body
|
|
10
|
+
* (chunked, unknown length) verifies — the signature binds method+path+headers+expiry, not the bytes.
|
|
11
|
+
*/
|
|
1
12
|
import { createHash, createHmac } from "node:crypto";
|
|
13
|
+
/** URI-encode per RFC 3986 (S3 canonicalization): unreserved chars stay literal, everything else %XX. */
|
|
2
14
|
function uriEncode(s, encodeSlash) {
|
|
3
15
|
let out = "";
|
|
4
16
|
for (const ch of Buffer.from(s, "utf8")) {
|
|
@@ -18,22 +30,40 @@ function uriEncode(s, encodeSlash) {
|
|
|
18
30
|
function hmac(key, data) {
|
|
19
31
|
return createHmac("sha256", key).update(data, "utf8").digest();
|
|
20
32
|
}
|
|
33
|
+
/** The shared SigV4 signing core: everything below the canonical-URI choice is identical for an
|
|
34
|
+
* object URL and a bucket URL — one implementation so the two public faces can never drift.
|
|
35
|
+
*
|
|
36
|
+
* Endpoint PATH PREFIX (三路复审修1): an endpoint like `https://files.example.com/minio` (reverse-proxy
|
|
37
|
+
* sub-path in front of MinIO) contributes its path to BOTH the canonical URI (what SigV4 signs) and the
|
|
38
|
+
* returned URL — previously only `url.origin` was used, so the signed canonical URI (`/bucket/key`) never
|
|
39
|
+
* matched the request path the store actually saw (`/minio/bucket/key`), and the presigned track silently
|
|
40
|
+
* diverged from the permanent track's plain string concat (which DID keep the prefix). One rstrip'd
|
|
41
|
+
* pathname, prepended to both, keeps signature and request path congruent for any endpoint shape.
|
|
42
|
+
*
|
|
43
|
+
* `signedHeaders` (修3 CopyObject support): extra headers to SIGN beyond `host` — presigned-URL (query)
|
|
44
|
+
* auth requires every `x-amz-*` header the request carries to be in X-Amz-SignedHeaders, so a server-side
|
|
45
|
+
* CopyObject (`x-amz-copy-source`) must sign it. Names are lowercased; canonical headers are sorted;
|
|
46
|
+
* the CALLER must send exactly these header values on the request. */
|
|
21
47
|
function presignCanonical(p) {
|
|
22
48
|
const region = p.region ?? "us-east-1";
|
|
23
49
|
const expires = p.expiresSec ?? 600;
|
|
24
50
|
const now = p.now ?? new Date();
|
|
25
|
-
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
51
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, ""); // YYYYMMDDTHHMMSSZ
|
|
26
52
|
const dateStamp = amzDate.slice(0, 8);
|
|
27
53
|
const url = new URL(p.endpoint);
|
|
28
|
-
const host = url.host;
|
|
54
|
+
const host = url.host; // includes port
|
|
55
|
+
// Endpoint path prefix (修1): "" for a root endpoint; "/minio" for a sub-path endpoint (trailing "/"
|
|
56
|
+
// stripped; `new URL().pathname` is already percent-encoded, which is what the canonical URI wants).
|
|
29
57
|
const basePath = url.pathname.replace(/\/+$/, "");
|
|
30
58
|
const canonicalUri = `${basePath}${p.canonicalUri}`;
|
|
31
59
|
const scope = `${dateStamp}/${region}/s3/aws4_request`;
|
|
60
|
+
// Signed headers: `host` always; extras (lowercased, value-trimmed) merged in; canonical order = sorted.
|
|
32
61
|
const headerMap = new Map([["host", host]]);
|
|
33
62
|
for (const [k, v] of Object.entries(p.signedHeaders ?? {}))
|
|
34
63
|
headerMap.set(k.toLowerCase(), v.trim());
|
|
35
64
|
const headerNames = [...headerMap.keys()].sort();
|
|
36
65
|
const signedHeaderList = headerNames.join(";");
|
|
66
|
+
// Query params must be sorted; values URI-encoded (the credential's "/" too).
|
|
37
67
|
const params = {
|
|
38
68
|
...(p.extraQuery ?? {}),
|
|
39
69
|
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
|
|
@@ -56,18 +86,29 @@ function presignCanonical(p) {
|
|
|
56
86
|
const signature = createHmac("sha256", kSigning).update(stringToSign, "utf8").digest("hex");
|
|
57
87
|
return `${url.origin}${canonicalUri}?${canonicalQuery}&X-Amz-Signature=${signature}`;
|
|
58
88
|
}
|
|
89
|
+
/** Build a SigV4 presigned **single-object** URL. Pure string math — no network. */
|
|
59
90
|
export function presignS3Url(p) {
|
|
91
|
+
// codex 1.189 F8: the contract here is SINGLE-OBJECT — an empty key would silently sign the BUCKET URI
|
|
92
|
+
// (`/<bucket>/` = CreateBucket/DeleteBucket/ListObjects depending on method), a far broader capability
|
|
93
|
+
// than any caller of this face intends. Refuse; bucket-level operations go through presignS3BucketUrl.
|
|
60
94
|
if (p.key === "") {
|
|
61
95
|
throw new Error("presignS3Url: refusing to sign an empty key (single-object contract) — use presignS3BucketUrl for bucket-level operations");
|
|
62
96
|
}
|
|
97
|
+
// Backstop (defense in depth): callers MUST pass a pre-sanitized key, but a path-traversal segment here would
|
|
98
|
+
// sign a URL that escapes its intended prefix once the store normalizes the path — refuse rather than sign it.
|
|
63
99
|
if (p.key.startsWith("/") || p.key.split("/").some((seg) => seg === "." || seg === "..")) {
|
|
64
100
|
throw new Error(`presignS3Url: refusing to sign a key with a traversal segment: ${p.key}`);
|
|
65
101
|
}
|
|
66
102
|
return presignCanonical({ ...p, canonicalUri: `/${uriEncode(p.bucket, true)}/${uriEncode(p.key, false)}` });
|
|
67
103
|
}
|
|
104
|
+
/** Build a SigV4 presigned **bucket-level** URL (`/<bucket>/`, no key) — the explicit face for
|
|
105
|
+
* CreateBucket/DeleteBucket (codex 1.189 F8: previously test fixtures smuggled this through
|
|
106
|
+
* presignS3Url with key "", which the single-object contract now rejects). Same signing core. */
|
|
68
107
|
export function presignS3BucketUrl(p) {
|
|
69
108
|
return presignCanonical({ ...p, canonicalUri: `/${uriEncode(p.bucket, true)}/` });
|
|
70
109
|
}
|
|
110
|
+
/** ListObjectsV2 presign(D-1 孤儿对象 GC,clay 拍 2026-07-28):GET `/<bucket>/?list-type=2&prefix=…`。
|
|
111
|
+
* bucket 级 GET 是**列举能力**——只给 server 端 sweep 用,URL 绝不外发(与 copy 面同纪律)。 */
|
|
71
112
|
export function presignS3ListUrl(p) {
|
|
72
113
|
return presignCanonical({
|
|
73
114
|
endpoint: p.endpoint,
|
|
@@ -86,6 +127,12 @@ export function presignS3ListUrl(p) {
|
|
|
86
127
|
},
|
|
87
128
|
});
|
|
88
129
|
}
|
|
130
|
+
/** Build a SigV4 presigned **CopyObject** request (修3 staging-key two-phase): S3 CopyObject = `PUT
|
|
131
|
+
* /<dstBucket>/<dstKey>` with `x-amz-copy-source: /<srcBucket>/<srcKey>`. Query auth requires every
|
|
132
|
+
* `x-amz-*` request header to be SIGNED, so the copy-source header goes through the signing core's
|
|
133
|
+
* signedHeaders seam; the caller MUST send `headers` verbatim on the fetch (the source key is
|
|
134
|
+
* RFC-3986-encoded — MinIO/S3 both accept the encoded copy-source form, and header values must be
|
|
135
|
+
* ASCII anyway). Same single-object guards as presignS3Url on BOTH sides. */
|
|
89
136
|
export function presignS3CopyUrl(p) {
|
|
90
137
|
for (const [label, key] of [
|
|
91
138
|
["destination", p.key],
|