@sema-agent/server 1.322.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-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 +546 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.js +455 -60
- 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 +2132 -96
- 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 +59 -3
- 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,7 +1,7 @@
|
|
|
1
1
|
import { callKeyOrdinal } from "@sema-agent/core";
|
|
2
2
|
import { oversizeJournalResult } from "./workflow-journal-limits.js";
|
|
3
3
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
4
|
-
const DEFAULT_RESUME_CLAIM_TTL_MS =
|
|
4
|
+
const DEFAULT_RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
|
|
5
5
|
export class SqlWorkflowJournalStore {
|
|
6
6
|
db;
|
|
7
7
|
resumeClaimTtlMs;
|
|
@@ -9,38 +9,83 @@ export class SqlWorkflowJournalStore {
|
|
|
9
9
|
this.db = db;
|
|
10
10
|
this.resumeClaimTtlMs = opts?.resumeClaimTtlMs ?? DEFAULT_RESUME_CLAIM_TTL_MS;
|
|
11
11
|
}
|
|
12
|
+
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
12
13
|
q(tidb, pg) {
|
|
13
14
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
14
15
|
}
|
|
16
|
+
/** Record one agent's result. Idempotent per (run_id, ordinal): the PK upsert OVERWRITES (last-write-wins), so a
|
|
17
|
+
* resumed run re-appending its replayed prefix verbatim leaves no duplicate. callKeyOrdinal is the SOLE ordinal
|
|
18
|
+
* source (core's parser — a malformed key collapses to 0, matching InMemory). Throws on a REAL failure
|
|
19
|
+
* (LOAD-BEARING), EXCEPT an oversize result is skip-journaled (see below) rather than throwing the workflow dead. */
|
|
15
20
|
async append(runId, scope, entry) {
|
|
21
|
+
// codex R10 定谳:result 列=TEXT(PG)/MEDIUMTEXT(TiDB),存的是 stringify 输出——NUL/lone surrogate
|
|
22
|
+
// 在输出里恒为 \uXXXX 六字符**转义形**(纯 ASCII),TEXT 列可精确存取;22P05 只咬 ::jsonb cast。
|
|
23
|
+
// 故 journal 无需任何 NUL 守卫:直存=逐位回放(R8 skip/R9 invalidate 均为过度修,已撤——那会把
|
|
24
|
+
// 可精确回放的完成结果作废成不必要的活跑重执行,外部副作用可能重复)。
|
|
16
25
|
const serialized = JSON.stringify(entry.result);
|
|
26
|
+
// TiDB rejects a row over the ~6 MiB txn-entry-size-limit ("entry too large"); a large agent TaskResult would
|
|
27
|
+
// make this LOAD-BEARING append THROW + kill the workflow — and the PG twin (no such limit) would NOT, a
|
|
28
|
+
// dialect divergence. DEGRADE identically on both: skip-journal an oversize result (no-op, never insert/throw)
|
|
29
|
+
// → its ordinal isn't cached, so a resume re-runs that one agent live (resume is an optimization).
|
|
17
30
|
if (oversizeJournalResult(serialized))
|
|
18
31
|
return;
|
|
19
32
|
await this.db.query(this.q("INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at) VALUES (?,?,?,?,?,?) " +
|
|
33
|
+
// scope NOT in the UPDATE: a re-append keeps the FIRST writer's scope (fail-closed — a re-append must never
|
|
34
|
+
// reassign tenancy). The owner is the sole writer + runId is unique per scope, so this never fires
|
|
35
|
+
// cross-scope in practice; the secure choice over matching InMemory's last-write-wins on scope.
|
|
20
36
|
"ON DUPLICATE KEY UPDATE call_key = VALUES(call_key), result = VALUES(result)", "INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at) VALUES ($1,$2,$3,$4,$5,$6) " +
|
|
21
37
|
"ON CONFLICT (run_id, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result"), [runId, callKeyOrdinal(entry.callKey), scope, entry.callKey, serialized, Date.now()]);
|
|
22
38
|
}
|
|
39
|
+
/** Entries for `runId` IF its recorded scope === `scope`, ASCENDING by ordinal; otherwise EMPTY. CORE-9 audit
|
|
40
|
+
* BLOCKER: scope in the WHERE (twin of WorkflowRunStore) — the LLM controls `resumeFromRunId`, so a cross-scope
|
|
41
|
+
* runId resolves to an empty journal (resume safely diverges to live, never discloses another tenant's results).
|
|
42
|
+
* Each row's `result` is the persisted JSON.stringify(TaskResult) → JSON.parse back. Throws on failure (LOAD-BEARING). */
|
|
23
43
|
async load(runId, scope) {
|
|
24
44
|
const { rows } = await this.db.query(this.q("SELECT call_key, result FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC", "SELECT call_key, result FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC"), [runId, scope]);
|
|
25
45
|
return rows.map((r) => ({ callKey: String(r.call_key), result: JSON.parse(String(r.result)) }));
|
|
26
46
|
}
|
|
47
|
+
/** [1412] locator 云形(core 1.353 WorkflowJournalStore.locator additive):SQL 店的 journal 坐标=
|
|
48
|
+
* [1402] HTTP 读面路由(file 店返 jsonl 路径=CC 本地形;此处返云形,core 铸 diagnostics 教句时单源引用)。 */
|
|
27
49
|
locator(runId, _scope) {
|
|
28
50
|
return `/v1/workflows/${runId}/journal`;
|
|
29
51
|
}
|
|
52
|
+
/** codex R1-H2:分页投影读——LIMIT/OFFSET 界行数,字节门界单行字节(超限行不取 result 全文,只回字节数;
|
|
53
|
+
* 读面标 truncated)。ORDER BY ordinal 与 load 同轴。
|
|
54
|
+
* 🔴 param ORDER differs by dialect (see file header): TiDB's `?`s bind in text order (maxResultBytes first,
|
|
55
|
+
* inside the CASE); PG's `$n` are positional, so its array keeps runId/scope first. Never unify — a swap
|
|
56
|
+
* silently mis-binds the OTHER dialect's placeholders. */
|
|
30
57
|
async loadPage(runId, scope, opts) {
|
|
31
58
|
const params = this.db.dialect === "tidb"
|
|
32
59
|
? [opts.maxResultBytes, runId, scope, opts.limit, opts.offset]
|
|
33
60
|
: [runId, scope, opts.maxResultBytes, opts.limit, opts.offset];
|
|
34
|
-
const { rows } = await this.db.query(this.q("SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE result END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?",
|
|
61
|
+
const { rows } = await this.db.query(this.q("SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE result END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?",
|
|
62
|
+
// codex R3: PG's LENGTH(text) is CHARACTER count (TiDB's LENGTH is byte count) — a multi-byte UTF-8 row
|
|
63
|
+
// would slip past the byte-budget door on plain LENGTH; OCTET_LENGTH keeps this the byte count PG-side
|
|
64
|
+
// too (real dual-DB-suite-confirmed anti-drift).
|
|
65
|
+
"SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE result END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5"), params);
|
|
35
66
|
return rows.map((r) => ({ callKey: String(r.call_key), resultJson: r.result_json === null ? null : String(r.result_json), resultBytes: Number(r.result_bytes) }));
|
|
36
67
|
}
|
|
68
|
+
/** GC — purge ALL journal entries for a run (the run reaper calls this once the run is terminal + retained). Scoped
|
|
69
|
+
* by run_id alone; idempotent (affected-row count). Mirrors the resume-anchor store's deleteBySession. */
|
|
37
70
|
async deleteByRun(runId) {
|
|
38
71
|
const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE run_id = ?", "DELETE FROM workflow_journal WHERE run_id = $1"), [runId]);
|
|
39
72
|
return res.affected;
|
|
40
73
|
}
|
|
74
|
+
/** RB-242 / WF2([1981] core 拍板 a 形,签名逐字)—— cross-replica resume admission lease。
|
|
75
|
+
*
|
|
76
|
+
* 语义(bake-store `idem_key UNIQUE` 先例):PK (source_run_id, scope) 上的原子赢或观察。四步,每步
|
|
77
|
+
* 单语句原子,并发交叉在任一步都收敛到「恰一个持有者」:
|
|
78
|
+
* ① 抢空位:INSERT..DO NOTHING / ON DUP KEY 无操作 —— affected=1 即赢;
|
|
79
|
+
* ② 同持有者幂等重入(engine 重试同一 resume):按 (键, new_run_id) 守卫的 claimed_at 刷新;
|
|
80
|
+
* ③ TTL 崩溃兜底接管:claimed_at < now-ttl 守卫下的原子改持有者(engine 终态会显式释放,
|
|
81
|
+
* 走到这步=上一持有 engine 崩了没释放;两层分工见 SqlWorkflowJournalStoreOptions 注);
|
|
82
|
+
* ④ 都没赢 ⇒ 读在位者返 {granted:false, holder}(holder 进 engine 的拒绝文案供归因)。 */
|
|
41
83
|
async resumeClaim(input) {
|
|
42
84
|
const now = Date.now();
|
|
43
|
-
const ins = await this.db.query(this.q(
|
|
85
|
+
const ins = await this.db.query(this.q(
|
|
86
|
+
// INSERT IGNORE(非 ON DUP KEY 无操作形):TiDB 对「无变化的 DUP KEY UPDATE」affected 报 1
|
|
87
|
+
// (MySQL 报 0)——真双库跑出来的方言差;IGNORE 形两家都在冲突时报 0。
|
|
88
|
+
"INSERT IGNORE INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES (?, ?, ?, ?)", "INSERT INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES ($1, $2, $3, $4) ON CONFLICT (source_run_id, scope) DO NOTHING"), [input.sourceRunId, input.scope, input.newRunId, now]);
|
|
44
89
|
if (ins.affected === 1)
|
|
45
90
|
return { granted: true };
|
|
46
91
|
const refresh = await this.db.query(this.q("UPDATE workflow_resume_claim SET claimed_at = ? WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "UPDATE workflow_resume_claim SET claimed_at = $1 WHERE source_run_id = $2 AND scope = $3 AND new_run_id = $4"), [now, input.sourceRunId, input.scope, input.newRunId]);
|
|
@@ -50,25 +95,36 @@ export class SqlWorkflowJournalStore {
|
|
|
50
95
|
if (takeover.affected >= 1)
|
|
51
96
|
return { granted: true };
|
|
52
97
|
const holder = await this.db.query(this.q("SELECT new_run_id FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ?", "SELECT new_run_id FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2"), [input.sourceRunId, input.scope]);
|
|
98
|
+
// 行在①-③间被释放的窄窗:holder 读空 ⇒ 如实返 denied 无 holder(engine 下一次重试会在①赢)。
|
|
53
99
|
const row = holder.rows[0];
|
|
54
100
|
const holderId = row?.new_run_id === undefined ? undefined : String(row.new_run_id);
|
|
101
|
+
// ②的 UPDATE 在 MySQL 缺省协议下只数**被改变**的行——同毫秒重入(claimed_at 未变)会 affected=0
|
|
102
|
+
// 掉到这里;持有者==自己仍是 granted(幂等重入语义不依赖 affected 的方言细节)。
|
|
55
103
|
if (holderId === input.newRunId)
|
|
56
104
|
return { granted: true };
|
|
57
105
|
return { granted: false, holder: holderId };
|
|
58
106
|
}
|
|
107
|
+
/** 只有 holder 能释放(new_run_id 守卫)—— engine 在 newRunId 终态/放弃时调;非 holder 调用=no-op。 */
|
|
59
108
|
async releaseResumeClaim(input) {
|
|
60
109
|
await this.db.query(this.q("DELETE FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "DELETE FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2 AND new_run_id = $3"), [input.sourceRunId, input.scope, input.newRunId]);
|
|
61
110
|
}
|
|
111
|
+
/** GC — time-based sweep: purge journal entries older than `maxAgeMs` (created_at < now - maxAgeMs). The
|
|
112
|
+
* per-run deleteByRun has no production caller (a run-store reap gives no per-run hook), so this bounded sweep
|
|
113
|
+
* — wired into the service reaper — is what keeps the heaviest table (TaskResult-bearing) from growing without
|
|
114
|
+
* bound. A resume of a journal older than the retention window simply re-runs live (resume is an optimization).
|
|
115
|
+
* Idempotent; returns the rows purged. */
|
|
62
116
|
async reapExpired(now, maxAgeMs) {
|
|
63
117
|
const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE created_at < ?", "DELETE FROM workflow_journal WHERE created_at < $1"), [now - maxAgeMs]);
|
|
64
118
|
return res.affected;
|
|
65
119
|
}
|
|
66
120
|
}
|
|
121
|
+
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
67
122
|
export class TiDBWorkflowJournalStore extends SqlWorkflowJournalStore {
|
|
68
123
|
constructor(pool, opts) {
|
|
69
124
|
super(mysqlDriver(pool), opts);
|
|
70
125
|
}
|
|
71
126
|
}
|
|
127
|
+
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
72
128
|
export class PgWorkflowJournalStore extends SqlWorkflowJournalStore {
|
|
73
129
|
constructor(pool, opts) {
|
|
74
130
|
super(pgDriver(pool), opts);
|
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P1 (fleet failover correctness, clay 2026-07-05) — SINGLE-FILE DUAL-DIALECT (design/158 A12 定型半场).
|
|
3
|
+
* ONE implementation, TWO dialects; the historical `TiDBWorkflowRunStore`/`PgWorkflowRunStore`,
|
|
4
|
+
* `TiDBWorkflowCompletionInbox`/`PgWorkflowCompletionInbox`, `TiDBWorkflowNotifyJournalStore`/
|
|
5
|
+
* `PgWorkflowNotifyJournalStore` class names survive as thin ctor subclasses so every consumer
|
|
6
|
+
* (store-backend.ts, the DB-integration + slim-oversize-run suites) is untouched.
|
|
7
|
+
*
|
|
8
|
+
* These are the TiDB/PG twins of core's `WorkflowRunStore` and the service `WorkflowCompletionInbox`,
|
|
9
|
+
* closing the LAST replica-local gap in the fleet story: dispatch failover (taskId idempotency +
|
|
10
|
+
* announce/lease, 1.98–1.103) can land a session on ANOTHER instance, where a File-backed workflow
|
|
11
|
+
* record / completion inbox strands the run's history and its pending push.
|
|
12
|
+
*
|
|
13
|
+
* ## WorkflowRunStore twin
|
|
14
|
+
* One row per run: the full `WorkflowRun` as a JSON blob + EXTRACTED columns for everything SQL needs to
|
|
15
|
+
* index/filter (scope, status, created_at, ended_at) + an authoritative `rev` column (the OCC key — the
|
|
16
|
+
* blob's own `rev` is OVERLAID from the column on every read, so writes never have to know the bumped value
|
|
17
|
+
* up front). `update` is a single-statement CAS (`WHERE id AND scope [AND rev]`, `SET rev = rev + 1`);
|
|
18
|
+
* `listByScope` projects через core's SHARED `summarizeWorkflowRun` (anti-drift — identical to InMemory/File).
|
|
19
|
+
*
|
|
20
|
+
* ## Completion inbox twin
|
|
21
|
+
* The File inbox's fences (purge / poll-served) are in-memory BY DESIGN there (single box); here they are
|
|
22
|
+
* ROWS (`workflow_inbox_fence`), which is the actual point of the port — a poll served on replica A must
|
|
23
|
+
* fence the late notify landing on replica B, or the double-push returns the moment a fleet has two
|
|
24
|
+
* replicas. Fences are swept opportunistically on every write verb. Entry order rides an auto-incrementing
|
|
25
|
+
* `seq` (re-enqueue = atomic move-to-tail → fresh seq = tail, matching the File/InMemory Map semantics; 1.108
|
|
26
|
+
* review hardening: post-insert fence RE-CHECK closes the cross-replica check-then-insert TOCTOU).
|
|
27
|
+
*
|
|
28
|
+
* ## Notify-journal twin
|
|
29
|
+
* 1.108 review fix (lens③ HIGH): with a SQL run store + inbox, a replica-local File journal was the last
|
|
30
|
+
* replica-local piece of the at-least-once completion-notify chain (a replica that died holding an un-acked
|
|
31
|
+
* entry stranded the notify forever). Semantics mirror `FileWorkflowNotifyJournalStore` verb-for-verb:
|
|
32
|
+
* `record` is idempotent and NEVER resets an acked row; `ack` flips pending→acked once; `listPending` is the
|
|
33
|
+
* recovery backlog (any replica may sweep it — the receiver is idempotent on runId, so a double recovery
|
|
34
|
+
* across replicas is at-least-once, not N-times).
|
|
35
|
+
*
|
|
36
|
+
* ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
|
|
37
|
+
* - `?` placeholders vs `$n` (and the OPTIONAL `AND rev = ?` / `AND rev = $6` OCC tail on `update`)
|
|
38
|
+
* - create-once dup-key: errno-string `ER_DUP_ENTRY` vs SQLSTATE `23505`
|
|
39
|
+
* - `listByScope`'s optional-session filter: `JSON_UNQUOTE(JSON_EXTRACT(run, '$.originatingSessionId'))`
|
|
40
|
+
* vs `run::json->>'originatingSessionId'` — AND the placeholder-numbering shape that follows (PG's `$n`
|
|
41
|
+
* is POSITIONAL and must track `params.length` as each optional clause is appended; TiDB's `?` is not),
|
|
42
|
+
* so this method keeps an explicit tidb/pg branch rather than a single `q()` pair.
|
|
43
|
+
* - completion-inbox move-to-tail: TiDB `REPLACE INTO …` (atomic delete-conflicting-row + insert, omitted
|
|
44
|
+
* `seq` mints a fresh AUTO_INCREMENT = tail) vs PG `INSERT … ON CONFLICT … DO UPDATE SET seq = nextval(…)`
|
|
45
|
+
* (re-mint `seq` off the BIGSERIAL's own sequence) — same outcome, structurally different statements.
|
|
46
|
+
* - the inbox overflow-scan needs `LIMIT 100000 OFFSET ?` on TiDB (MySQL-protocol requires a LIMIT
|
|
47
|
+
* alongside OFFSET) but plain `OFFSET $2` on PG.
|
|
48
|
+
* - bulk delete-by-id-set: `id IN (?,?,…)` (dynamic placeholder count) vs `id = ANY($n::text[])` /
|
|
49
|
+
* `seq = ANY($n::bigint[])` (array bind) — both twins do this for `reap` and the inbox overflow drop.
|
|
50
|
+
* - `run` is TEXT (not jsonb) on PG — matches TiDB's plain string column, so JSON binding is
|
|
51
|
+
* `JSON.stringify` verbatim on BOTH dialects (no `pgProtocolJsonStringify` envelope needed here; that
|
|
52
|
+
* concern belongs to stores whose column IS jsonb, e.g. checkpoint-store-sql.ts).
|
|
53
|
+
* - affectedRows vs rowCount (via SqlDriver's normalized `affected`).
|
|
54
|
+
* - schema ownership: TiDB DDL in tidb-pool.ts, PG DDL centrally in pg-pool.ts (neither store creates
|
|
55
|
+
* tables).
|
|
56
|
+
*/
|
|
1
57
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
2
58
|
import type { Pool as PgPool } from "pg";
|
|
3
59
|
import { summarizeWorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
|
|
@@ -5,16 +61,29 @@ import { type WorkflowCompletionInbox, type WorkflowCompletionInboxEntry } from
|
|
|
5
61
|
import type { WorkflowNotifyJournalStore, WorkflowNotifyJournalEntry } from "../orchestration/workflow-notify-journal.js";
|
|
6
62
|
import { type SqlDriver } from "./sql-driver.js";
|
|
7
63
|
type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
|
|
64
|
+
/** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
|
|
65
|
+
* ~6 MiB txn-entry limit would otherwise throw the (best-effort) progress write into the workflow loop.
|
|
66
|
+
* `put` (create, small) still throws on a real failure. Mirrors the journal's oversize skip-degrade. */
|
|
8
67
|
export declare const MAX_RUN_BLOB_BYTES: number;
|
|
9
68
|
declare const onWarnType: (msg: string, meta: Record<string, unknown>) => void;
|
|
10
69
|
export type InboxWarn = typeof onWarnType;
|
|
70
|
+
/** Review fix (1.108, lens④ HIGH): an oversize blob must NOT fail the update outright — core's persist chain
|
|
71
|
+
* treats `false` as a CAS loss and does NOT advance its tracked rev, so once a run outgrows the cap EVERY later
|
|
72
|
+
* write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
|
|
73
|
+
* orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write:
|
|
74
|
+
* step 1 truncate the two unbounded strings (result/error); step 2 drop the heavy arrays (phases/agents/groups)
|
|
75
|
+
* with an explicit marker. Returns null only when even the skeleton is oversize (physics — caller keeps the
|
|
76
|
+
* prior revision, matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared
|
|
77
|
+
* verbatim by both twins. */
|
|
11
78
|
export declare function slimOversizeRun(run: WorkflowRun & {
|
|
12
79
|
id: string;
|
|
13
80
|
scope: string;
|
|
14
81
|
}, maxBytes: number): string | null;
|
|
82
|
+
/** Dual-dialect durable `WorkflowRunStore`. See the file header for the dialect-delta ledger. */
|
|
15
83
|
export declare class SqlWorkflowRunStore implements WorkflowRunStore {
|
|
16
84
|
protected readonly db: SqlDriver;
|
|
17
85
|
constructor(db: SqlDriver);
|
|
86
|
+
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
18
87
|
private q;
|
|
19
88
|
put(id: string, run: WorkflowRun): Promise<void>;
|
|
20
89
|
get(id: string): Promise<WorkflowRun | null>;
|
|
@@ -26,6 +95,11 @@ export declare class SqlWorkflowRunStore implements WorkflowRunStore {
|
|
|
26
95
|
limit?: number;
|
|
27
96
|
session?: string;
|
|
28
97
|
}): Promise<ReturnType<typeof summarizeWorkflowRun>[]>;
|
|
98
|
+
/** Retention (the store never auto-purges; reap is explicit): the contract's `reap` is scope-keyed and the
|
|
99
|
+
* contract deliberately has NO cross-scope enumeration — but the SQL twin CAN enumerate (DISTINCT on the
|
|
100
|
+
* extracted scope column), which is exactly what a periodic deployment sweep needs. Walks every scope through
|
|
101
|
+
* the contract-faithful `reap` (terminal-only; running rows are the orphan-grace sweep's business). Service
|
|
102
|
+
* extension, not a contract member. Byte-identical text on both dialects (no params) — no q() needed. */
|
|
29
103
|
reapAllScopes(now: number, opts: {
|
|
30
104
|
maxAgeMs?: number;
|
|
31
105
|
keep?: number;
|
|
@@ -35,6 +109,7 @@ export declare class SqlWorkflowRunStore implements WorkflowRunStore {
|
|
|
35
109
|
keep?: number;
|
|
36
110
|
}): Promise<number>;
|
|
37
111
|
}
|
|
112
|
+
/** Dual-dialect `WorkflowCompletionInbox`. See the file header for the dialect-delta ledger. */
|
|
38
113
|
export declare class SqlWorkflowCompletionInbox implements WorkflowCompletionInbox {
|
|
39
114
|
protected readonly db: SqlDriver;
|
|
40
115
|
private readonly onWarn?;
|
|
@@ -47,6 +122,10 @@ export declare class SqlWorkflowCompletionInbox implements WorkflowCompletionInb
|
|
|
47
122
|
purge(sessionId: string, purgedOwner?: string | null): Promise<void>;
|
|
48
123
|
markTerminalServed(sessionId: string, runId: string): Promise<void>;
|
|
49
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Dual-dialect `WorkflowNotifyJournalStore` — the crash-safe recovery backlog for at-least-once completion
|
|
127
|
+
* notify. See the file header for the dialect-delta ledger.
|
|
128
|
+
*/
|
|
50
129
|
export declare class SqlWorkflowNotifyJournalStore implements WorkflowNotifyJournalStore {
|
|
51
130
|
protected readonly db: SqlDriver;
|
|
52
131
|
constructor(db: SqlDriver);
|
|
@@ -60,9 +139,13 @@ export declare class SqlWorkflowNotifyJournalStore implements WorkflowNotifyJour
|
|
|
60
139
|
}): Promise<void>;
|
|
61
140
|
ack(runId: string, ackedAt: number): Promise<void>;
|
|
62
141
|
listPending(): Promise<WorkflowNotifyJournalEntry[]>;
|
|
142
|
+
/** Retention (same sweep as reapAllScopes): ACKED rows are pure history — without this the twin re-opens
|
|
143
|
+
* the unbounded-growth hole the same release closed for workflow_run. Pending rows are NEVER reaped (they
|
|
144
|
+
* are the recovery backlog; the orphan-grace sweep is what retires a stuck pending run). */
|
|
63
145
|
reapAcked(before: number): Promise<number>;
|
|
64
146
|
get(runId: string): Promise<WorkflowNotifyJournalEntry | null>;
|
|
65
147
|
}
|
|
148
|
+
/** MySQL-protocol (TiDB) bindings — historical class names + ctor shapes preserved. */
|
|
66
149
|
export declare class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
|
|
67
150
|
constructor(pool: MySqlPool);
|
|
68
151
|
}
|
|
@@ -72,6 +155,7 @@ export declare class TiDBWorkflowCompletionInbox extends SqlWorkflowCompletionIn
|
|
|
72
155
|
export declare class TiDBWorkflowNotifyJournalStore extends SqlWorkflowNotifyJournalStore {
|
|
73
156
|
constructor(pool: MySqlPool);
|
|
74
157
|
}
|
|
158
|
+
/** PostgreSQL bindings — historical class names + ctor shapes preserved. */
|
|
75
159
|
export declare class PgWorkflowRunStore extends SqlWorkflowRunStore {
|
|
76
160
|
constructor(pool: PgPool);
|
|
77
161
|
}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
|
|
2
2
|
import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
|
|
3
3
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
4
|
+
/** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
|
|
5
|
+
* ~6 MiB txn-entry limit would otherwise throw the (best-effort) progress write into the workflow loop.
|
|
6
|
+
* `put` (create, small) still throws on a real failure. Mirrors the journal's oversize skip-degrade. */
|
|
4
7
|
export const MAX_RUN_BLOB_BYTES = 4 * 1024 * 1024;
|
|
5
8
|
const onWarnType = (msg, meta) => void [msg, meta];
|
|
9
|
+
/** Review fix (1.108, lens④ HIGH): an oversize blob must NOT fail the update outright — core's persist chain
|
|
10
|
+
* treats `false` as a CAS loss and does NOT advance its tracked rev, so once a run outgrows the cap EVERY later
|
|
11
|
+
* write (including the TERMINAL one) keeps failing → the durable row is stuck `running` forever and the 24h
|
|
12
|
+
* orphan sweep finalizes a genuinely-completed run as `abandoned`. Instead degrade the PAYLOAD, not the write:
|
|
13
|
+
* step 1 truncate the two unbounded strings (result/error); step 2 drop the heavy arrays (phases/agents/groups)
|
|
14
|
+
* with an explicit marker. Returns null only when even the skeleton is oversize (physics — caller keeps the
|
|
15
|
+
* prior revision, matching the old behavior for that corner). Dialect-neutral (pure JS, no SQL) — shared
|
|
16
|
+
* verbatim by both twins. */
|
|
6
17
|
export function slimOversizeRun(run, maxBytes) {
|
|
7
18
|
const MARK = "…[truncated: run blob exceeded the store cap]";
|
|
8
19
|
const cut = (s) => s.slice(0, 4000) + MARK;
|
|
@@ -18,20 +29,24 @@ export function slimOversizeRun(run, maxBytes) {
|
|
|
18
29
|
blob = JSON.stringify(step2);
|
|
19
30
|
return Buffer.byteLength(blob) <= maxBytes ? blob : null;
|
|
20
31
|
}
|
|
32
|
+
/** Dual-dialect durable `WorkflowRunStore`. See the file header for the dialect-delta ledger. */
|
|
21
33
|
export class SqlWorkflowRunStore {
|
|
22
34
|
db;
|
|
23
35
|
constructor(db) {
|
|
24
36
|
this.db = db;
|
|
25
37
|
}
|
|
38
|
+
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
26
39
|
q(tidb, pg) {
|
|
27
40
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
28
41
|
}
|
|
29
42
|
async put(id, run) {
|
|
30
|
-
const stored = { ...run, id, rev: run.rev ?? 0 };
|
|
43
|
+
const stored = { ...run, id, rev: run.rev ?? 0 }; // key authoritative + observed-rev base (InMemory parity)
|
|
31
44
|
try {
|
|
32
45
|
await this.db.query(this.q("INSERT INTO workflow_run (id, scope, status, run, rev, created_at, ended_at) VALUES (?,?,?,?,?,?,?)", "INSERT INTO workflow_run (id, scope, status, run, rev, created_at, ended_at) VALUES ($1,$2,$3,$4,$5,$6,$7)"), [id, run.scope, run.status, JSON.stringify(stored), stored.rev, run.createdAt, run.endedAt ?? null]);
|
|
33
46
|
}
|
|
34
47
|
catch (e) {
|
|
48
|
+
// create-once error every backend throws (contract) — dup-key classification is dialect-specific:
|
|
49
|
+
// TiDB's errno-string ER_DUP_ENTRY vs PG's SQLSTATE 23505.
|
|
35
50
|
const isDupKey = this.db.dialect === "tidb" ? e.code === "ER_DUP_ENTRY" : e.code === "23505";
|
|
36
51
|
if (isDupKey) {
|
|
37
52
|
throw new WorkflowRunStoreError("workflow_run.already_exists", "workflow run id already exists");
|
|
@@ -45,23 +60,32 @@ export class SqlWorkflowRunStore {
|
|
|
45
60
|
if (!r)
|
|
46
61
|
return null;
|
|
47
62
|
const run = JSON.parse(String(r.run));
|
|
48
|
-
run.rev = Number(r.rev);
|
|
63
|
+
run.rev = Number(r.rev); // the COLUMN is the OCC authority — overlay it (the blob's rev may predate a CAS bump)
|
|
49
64
|
return run;
|
|
50
65
|
}
|
|
51
66
|
async update(id, scope, run, expect) {
|
|
67
|
+
// id/scope immutable in the stored payload (InMemory parity: key + partition are authoritative). The blob's
|
|
68
|
+
// rev is NOT trusted (see get's overlay) — write it as-is; the column bump below is the real OCC state.
|
|
52
69
|
let blob = JSON.stringify({ ...run, id, scope });
|
|
53
70
|
if (Buffer.byteLength(blob) > MAX_RUN_BLOB_BYTES) {
|
|
71
|
+
// Oversize degrade (1.108 review fix): slim the payload so the write — critically the TERMINAL one —
|
|
72
|
+
// still lands (a `false` here would never be retried with a smaller payload; see slimOversizeRun).
|
|
54
73
|
const slim = slimOversizeRun({ ...run, id, scope }, MAX_RUN_BLOB_BYTES);
|
|
55
74
|
if (slim === null)
|
|
56
|
-
return false;
|
|
75
|
+
return false; // even the skeleton is oversize — keep the prior revision
|
|
57
76
|
blob = slim;
|
|
58
77
|
}
|
|
59
78
|
const res = await this.db.query(this.q(`UPDATE workflow_run SET run = ?, rev = rev + 1, status = ?, ended_at = ? WHERE id = ? AND scope = ?${expect !== undefined ? " AND rev = ?" : ""}`, `UPDATE workflow_run SET run = $1, rev = rev + 1, status = $2, ended_at = $3 WHERE id = $4 AND scope = $5${expect !== undefined ? " AND rev = $6" : ""}`), expect !== undefined
|
|
60
79
|
? [blob, run.status, run.endedAt ?? null, id, scope, expect.rev]
|
|
61
80
|
: [blob, run.status, run.endedAt ?? null, id, scope]);
|
|
62
|
-
return res.affected === 1;
|
|
81
|
+
return res.affected === 1; // rev always bumps → affected is a faithful CAS verdict
|
|
63
82
|
}
|
|
64
83
|
async listByScope(scope, opts) {
|
|
84
|
+
// 🔴 genuinely-divergent CONSTRUCTION (not just SQL text): PG's `$n` placeholders are POSITIONAL and must
|
|
85
|
+
// track `params.length` as each optional clause is appended, while TiDB's `?` is positionless — a single
|
|
86
|
+
// `q()` pair can't express that, so the where/limit assembly forks per dialect. The FILTER logic itself
|
|
87
|
+
// (status → session → limit, session dropping unmatched/absent-key rows via JSON extraction, limit applied
|
|
88
|
+
// last) is identical; only the placeholder bookkeeping differs.
|
|
65
89
|
const params = [scope];
|
|
66
90
|
let where;
|
|
67
91
|
let limit = "";
|
|
@@ -72,6 +96,9 @@ export class SqlWorkflowRunStore {
|
|
|
72
96
|
params.push(opts.status);
|
|
73
97
|
}
|
|
74
98
|
if (opts?.session !== undefined) {
|
|
99
|
+
// core 1.368([1516]②)session 下推臂:limit 前应用、严格等值、无字段行剔除(JSON path 缺席 →
|
|
100
|
+
// NULL → `= ?` 恒假 ✓)。JSON 提取而非投影列:对存量行零迁移直接生效;行量有 retention 界,
|
|
101
|
+
// 与本函数固有的全行 JSON.parse 同量级。
|
|
75
102
|
where += " AND JSON_UNQUOTE(JSON_EXTRACT(run, '$.originatingSessionId')) = ?";
|
|
76
103
|
params.push(opts.session);
|
|
77
104
|
}
|
|
@@ -87,6 +114,7 @@ export class SqlWorkflowRunStore {
|
|
|
87
114
|
where += ` AND status = $${params.length}`;
|
|
88
115
|
}
|
|
89
116
|
if (opts?.session !== undefined) {
|
|
117
|
+
// core 1.368([1516]②)session 下推臂(TiDB twin 同案注释):JSON 提取、limit 前、缺席键 → NULL → 拒 ✓
|
|
90
118
|
params.push(opts.session);
|
|
91
119
|
where += ` AND run::json->>'originatingSessionId' = $${params.length}`;
|
|
92
120
|
}
|
|
@@ -95,6 +123,7 @@ export class SqlWorkflowRunStore {
|
|
|
95
123
|
limit = ` LIMIT $${params.length}`;
|
|
96
124
|
}
|
|
97
125
|
}
|
|
126
|
+
// Sort in SQL (newest first), summarize via core's SHARED projection (anti-drift).
|
|
98
127
|
const { rows } = await this.db.query(`SELECT run, rev FROM workflow_run WHERE ${where} ORDER BY created_at DESC, id DESC${limit}`, params);
|
|
99
128
|
return rows.map((r) => {
|
|
100
129
|
const run = JSON.parse(String(r.run));
|
|
@@ -102,6 +131,11 @@ export class SqlWorkflowRunStore {
|
|
|
102
131
|
return summarizeWorkflowRun(run);
|
|
103
132
|
});
|
|
104
133
|
}
|
|
134
|
+
/** Retention (the store never auto-purges; reap is explicit): the contract's `reap` is scope-keyed and the
|
|
135
|
+
* contract deliberately has NO cross-scope enumeration — but the SQL twin CAN enumerate (DISTINCT on the
|
|
136
|
+
* extracted scope column), which is exactly what a periodic deployment sweep needs. Walks every scope through
|
|
137
|
+
* the contract-faithful `reap` (terminal-only; running rows are the orphan-grace sweep's business). Service
|
|
138
|
+
* extension, not a contract member. Byte-identical text on both dialects (no params) — no q() needed. */
|
|
105
139
|
async reapAllScopes(now, opts) {
|
|
106
140
|
const { rows } = await this.db.query("SELECT DISTINCT scope FROM workflow_run");
|
|
107
141
|
let total = 0;
|
|
@@ -111,7 +145,9 @@ export class SqlWorkflowRunStore {
|
|
|
111
145
|
}
|
|
112
146
|
async reap(scope, now, opts) {
|
|
113
147
|
if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
|
|
114
|
-
return 0;
|
|
148
|
+
return 0; // retention is always explicit
|
|
149
|
+
// Cheap columns only; terminal-set + keep-N semantics computed in JS to match InMemory EXACTLY
|
|
150
|
+
// (isTerminalWorkflowStatus is core's — no status list duplicated into SQL).
|
|
115
151
|
const { rows } = await this.db.query(this.q("SELECT id, status, created_at, ended_at FROM workflow_run WHERE scope = ? ORDER BY created_at DESC, id DESC", "SELECT id, status, created_at, ended_at FROM workflow_run WHERE scope = $1 ORDER BY created_at DESC, id DESC"), [scope]);
|
|
116
152
|
const terminal = rows.filter((r) => isTerminalWorkflowStatus(String(r.status)));
|
|
117
153
|
const doomed = [];
|
|
@@ -125,12 +161,16 @@ export class SqlWorkflowRunStore {
|
|
|
125
161
|
}
|
|
126
162
|
if (doomed.length === 0)
|
|
127
163
|
return 0;
|
|
164
|
+
// Bulk delete-by-id-set: TiDB expands a dynamic `IN (?,?,…)` (one placeholder per id, spread params);
|
|
165
|
+
// PG binds the whole set as one array via `= ANY($2::text[])`. Same result set, different statement shape.
|
|
128
166
|
const res = await this.db.query(this.q(`DELETE FROM workflow_run WHERE scope = ? AND id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])"), this.db.dialect === "tidb" ? [scope, ...doomed] : [scope, doomed]);
|
|
129
167
|
return res.affected;
|
|
130
168
|
}
|
|
131
169
|
}
|
|
170
|
+
/** The SQL fence row kinds. `run_id = ''` on a purge fence (session-wide). */
|
|
132
171
|
const KIND_SERVED = "served";
|
|
133
172
|
const KIND_PURGE = "purge";
|
|
173
|
+
/** Dual-dialect `WorkflowCompletionInbox`. See the file header for the dialect-delta ledger. */
|
|
134
174
|
export class SqlWorkflowCompletionInbox {
|
|
135
175
|
db;
|
|
136
176
|
onWarn;
|
|
@@ -147,26 +187,43 @@ export class SqlWorkflowCompletionInbox {
|
|
|
147
187
|
async enqueue(entry) {
|
|
148
188
|
const now = Date.now();
|
|
149
189
|
await this.sweepFences(now);
|
|
190
|
+
// Served fence FIRST (per-runId — cross-replica: a poll served on ANOTHER replica fences us here).
|
|
150
191
|
const served = await this.db.query(this.q("SELECT 1 FROM workflow_inbox_fence WHERE session_id = ? AND kind = ? AND run_id IN (?, ?) AND until_ms >= ?", "SELECT 1 FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id IN ($3, $4) AND until_ms >= $5"), [entry.sessionId, KIND_SERVED, entry.runId, foldKeyFamily(entry.runId), now]);
|
|
151
192
|
if (served.rows.length > 0) {
|
|
152
193
|
this.onWarn?.("workflow_completion_enqueue_after_poll_served", { sessionId: entry.sessionId, runId: entry.runId });
|
|
153
194
|
return;
|
|
154
195
|
}
|
|
196
|
+
// Purge fence: owner-scoped exactly like the File impl (owner_known=0 → conservative fence-all;
|
|
197
|
+
// owner_known=1 → fences only the SAME owner; a different owner = a new legitimate claimant, passes).
|
|
155
198
|
const purge = await this.db.query(this.q("SELECT owner, owner_known FROM workflow_inbox_fence WHERE session_id = ? AND kind = ? AND run_id = '' AND until_ms >= ?", "SELECT owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id = '' AND until_ms >= $3"), [entry.sessionId, KIND_PURGE, now]);
|
|
156
199
|
const pf = purge.rows[0];
|
|
157
200
|
if (pf && (Number(pf.owner_known) === 0 || (pf.owner === null ? entry.owner === null : String(pf.owner) === entry.owner))) {
|
|
158
201
|
this.onWarn?.("workflow_completion_enqueue_after_purge", { sessionId: entry.sessionId, runId: entry.runId });
|
|
159
202
|
return;
|
|
160
203
|
}
|
|
204
|
+
// Atomic move-to-tail (1.108 review fix — the prior DELETE-then-INSERT had a crash window that dropped the
|
|
205
|
+
// old entry; self-healing via the notify journal, but atomic is free here). Genuinely divergent statement:
|
|
206
|
+
// TiDB `REPLACE` = delete-conflicting-row + insert atomically, and omitting `seq` mints a fresh
|
|
207
|
+
// AUTO_INCREMENT = tail. PG has no REPLACE, so the twin is an upsert that explicitly re-mints `seq` off the
|
|
208
|
+
// BIGSERIAL's own sequence — same outcome (idempotent-on-runId + move-to-tail in ONE statement).
|
|
161
209
|
await this.db.query(this.q("REPLACE INTO workflow_completion_inbox (session_id, run_id, owner, status, summary, enqueued_at, kind, payload) VALUES (?,?,?,?,?,?,?,?)", "INSERT INTO workflow_completion_inbox (session_id, run_id, owner, status, summary, enqueued_at, kind, payload) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) " +
|
|
162
210
|
"ON CONFLICT (session_id, run_id) DO UPDATE SET seq = nextval(pg_get_serial_sequence('workflow_completion_inbox','seq')), " +
|
|
163
211
|
"owner = EXCLUDED.owner, status = EXCLUDED.status, summary = EXCLUDED.summary, enqueued_at = EXCLUDED.enqueued_at, kind = EXCLUDED.kind, payload = EXCLUDED.payload"), [entry.sessionId, entry.runId, entry.owner, entry.status, entry.summary, entry.enqueuedAt, entry.kind ?? null, entry.payload ?? null]);
|
|
212
|
+
// 1.108 review (lens① MAJOR ×2): the File impl's check-then-set is atomic on one event loop; here the
|
|
213
|
+
// fence check and the INSERT are two network statements, so a fence written IN BETWEEN (a poll served on
|
|
214
|
+
// another replica, or a racing purge) would leave fence + pending row coexisting → the next drain re-pushes
|
|
215
|
+
// an already-served/purged completion. Close the window by RE-CHECKING after the insert and self-revoking —
|
|
216
|
+
// ordering guarantees the loser is always visible to someone: markTerminalServed/purge write their fence
|
|
217
|
+
// BEFORE their delete, so either their delete kills our row, or our re-check sees their fence.
|
|
218
|
+
// (Residual, documented: a purge racing a NEW claimant's enqueue between fence-write and session-DELETE can
|
|
219
|
+
// still drop that one push — ms window + sessionId reclaim in it is contrived; the WorkflowStatus poll floor
|
|
220
|
+
// + notify-journal recovery cover it. File parity: purge drops the whole session queue there too.)
|
|
164
221
|
const confirm = await this.db.query(this.q("SELECT kind, owner, owner_known FROM workflow_inbox_fence WHERE session_id = ? AND until_ms >= ? AND (run_id IN (?, ?) OR (kind = ? AND run_id = ''))", "SELECT kind, owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND until_ms >= $2 AND (run_id IN ($3, $4) OR (kind = $5 AND run_id = ''))"), [entry.sessionId, now, entry.runId, foldKeyFamily(entry.runId), KIND_PURGE]);
|
|
165
222
|
for (const f of confirm.rows) {
|
|
166
223
|
const isServed = String(f.kind) === KIND_SERVED;
|
|
167
224
|
const purgeFencesUs = !isServed && (Number(f.owner_known) === 0 || (f.owner === null ? entry.owner === null : String(f.owner) === entry.owner));
|
|
168
225
|
if (isServed || purgeFencesUs) {
|
|
169
|
-
await this.ack(entry.sessionId, entry.runId);
|
|
226
|
+
await this.ack(entry.sessionId, entry.runId); // self-revoke: the fence won the race
|
|
170
227
|
this.onWarn?.(isServed ? "workflow_completion_enqueue_after_poll_served" : "workflow_completion_enqueue_after_purge", {
|
|
171
228
|
sessionId: entry.sessionId,
|
|
172
229
|
runId: entry.runId,
|
|
@@ -174,14 +231,21 @@ export class SqlWorkflowCompletionInbox {
|
|
|
174
231
|
return;
|
|
175
232
|
}
|
|
176
233
|
}
|
|
234
|
+
// Cap: drop-oldest beyond MAX_PENDING_PER_SESSION (warn per drop, File parity). TiDB requires a LIMIT
|
|
235
|
+
// alongside OFFSET (MySQL-protocol syntax); PG's OFFSET stands alone.
|
|
177
236
|
const over = await this.db.query(this.q("SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = ? ORDER BY seq DESC LIMIT 100000 OFFSET ?", "SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = $1 ORDER BY seq DESC OFFSET $2"), [entry.sessionId, MAX_PENDING_PER_SESSION]);
|
|
178
237
|
if (over.rows.length > 0) {
|
|
238
|
+
// Bulk delete-by-seq-set: dynamic `IN (?,?,…)` on TiDB vs array-bound `= ANY($2::bigint[])` on PG.
|
|
179
239
|
await this.db.query(this.q(`DELETE FROM workflow_completion_inbox WHERE session_id = ? AND seq IN (${over.rows.map(() => "?").join(",")})`, "DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND seq = ANY($2::bigint[])"), this.db.dialect === "tidb" ? [entry.sessionId, ...over.rows.map((r) => r.seq)] : [entry.sessionId, over.rows.map((r) => r.seq)]);
|
|
180
240
|
for (const r of over.rows)
|
|
181
241
|
this.onWarn?.("workflow_completion_inbox_overflow", { sessionId: entry.sessionId, droppedRunId: String(r.run_id) });
|
|
182
242
|
}
|
|
183
243
|
}
|
|
184
244
|
async pending(sessionId) {
|
|
245
|
+
// 2026-07-11(同一缺陷的 SQL 半场):filter SERVED-fenced rows like the in-memory
|
|
246
|
+
// inbox does — markTerminalServed's fence-INSERT and ack-DELETE are two statements, and a drain on
|
|
247
|
+
// another replica between them re-emitted an already-served completion (the in-memory pending()
|
|
248
|
+
// filters by servedAt; this brings the SQL twin to the same semantics).
|
|
185
249
|
const { rows } = await this.db.query(this.q("SELECT i.run_id, i.owner, i.status, i.summary, i.enqueued_at, i.kind, i.payload FROM workflow_completion_inbox i " +
|
|
186
250
|
"WHERE i.session_id = ? AND NOT EXISTS (SELECT 1 FROM workflow_inbox_fence f WHERE f.session_id = i.session_id AND f.kind = ? AND f.run_id IN (i.run_id, REGEXP_REPLACE(i.run_id, '#[0-9]+$', '')) AND f.until_ms >= ?) " +
|
|
187
251
|
"ORDER BY i.seq ASC", "SELECT i.run_id, i.owner, i.status, i.summary, i.enqueued_at, i.kind, i.payload FROM workflow_completion_inbox i " +
|
|
@@ -204,6 +268,7 @@ export class SqlWorkflowCompletionInbox {
|
|
|
204
268
|
async purge(sessionId, purgedOwner) {
|
|
205
269
|
const now = Date.now();
|
|
206
270
|
await this.sweepFences(now);
|
|
271
|
+
// Fence FIRST (an enqueue racing the purge must lose), then clear. Upsert = a re-purge refreshes the window.
|
|
207
272
|
await this.db.query(this.q("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES (?,?,?,?,?,?) " +
|
|
208
273
|
"ON DUPLICATE KEY UPDATE owner = VALUES(owner), owner_known = VALUES(owner_known), until_ms = VALUES(until_ms)", "INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,'',$3,$4,$5) " +
|
|
209
274
|
"ON CONFLICT (session_id, kind, run_id) DO UPDATE SET owner = EXCLUDED.owner, owner_known = EXCLUDED.owner_known, until_ms = EXCLUDED.until_ms"), this.db.dialect === "tidb"
|
|
@@ -218,9 +283,15 @@ export class SqlWorkflowCompletionInbox {
|
|
|
218
283
|
"ON DUPLICATE KEY UPDATE until_ms = VALUES(until_ms)", "INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,$3,NULL,0,$4) " +
|
|
219
284
|
"ON CONFLICT (session_id, kind, run_id) DO UPDATE SET until_ms = EXCLUDED.until_ms"), [sessionId, KIND_SERVED, runId, now + SERVED_FENCE_MS]);
|
|
220
285
|
await this.ack(sessionId, runId);
|
|
286
|
+
// design/144 家族 ack(与 in-memory 语义对齐):fleet 帧无 seq 传家族前缀,park 行是 seq 键——
|
|
287
|
+
// 精确 DELETE 必 miss。剥行键尾部 #digits 与传入前缀比对(REGEXP_REPLACE/regexp_replace 语义=foldKeyFamily)。
|
|
221
288
|
await this.db.query(this.q("DELETE FROM workflow_completion_inbox WHERE session_id = ? AND run_id <> ? AND REGEXP_REPLACE(run_id, '#[0-9]+$', '') = ?", "DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND run_id <> $2 AND regexp_replace(run_id, '#[0-9]+$', '') = $3"), [sessionId, runId, runId]);
|
|
222
289
|
}
|
|
223
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Dual-dialect `WorkflowNotifyJournalStore` — the crash-safe recovery backlog for at-least-once completion
|
|
293
|
+
* notify. See the file header for the dialect-delta ledger.
|
|
294
|
+
*/
|
|
224
295
|
export class SqlWorkflowNotifyJournalStore {
|
|
225
296
|
db;
|
|
226
297
|
constructor(db) {
|
|
@@ -230,6 +301,8 @@ export class SqlWorkflowNotifyJournalStore {
|
|
|
230
301
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
231
302
|
}
|
|
232
303
|
async record(input) {
|
|
304
|
+
// Idempotent-on-runId (a second record for the same run — incl. an acked one — is a no-op): TiDB
|
|
305
|
+
// `INSERT IGNORE` vs PG `ON CONFLICT (run_id) DO NOTHING`.
|
|
233
306
|
await this.db.query(this.q("INSERT IGNORE INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at) VALUES (?,?,0,?,?,?)", "INSERT INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at) VALUES ($1,$2,0,$3,$4,$5) ON CONFLICT (run_id) DO NOTHING"), [input.runId, input.scope, input.sourceTaskId ?? null, input.principal ?? null, input.createdAt]);
|
|
234
307
|
}
|
|
235
308
|
async ack(runId, ackedAt) {
|
|
@@ -246,6 +319,9 @@ export class SqlWorkflowNotifyJournalStore {
|
|
|
246
319
|
createdAt: Number(r.created_at),
|
|
247
320
|
}));
|
|
248
321
|
}
|
|
322
|
+
/** Retention (same sweep as reapAllScopes): ACKED rows are pure history — without this the twin re-opens
|
|
323
|
+
* the unbounded-growth hole the same release closed for workflow_run. Pending rows are NEVER reaped (they
|
|
324
|
+
* are the recovery backlog; the orphan-grace sweep is what retires a stuck pending run). */
|
|
249
325
|
async reapAcked(before) {
|
|
250
326
|
const res = await this.db.query(this.q("DELETE FROM workflow_notify_journal WHERE acked = 1 AND acked_at < ?", "DELETE FROM workflow_notify_journal WHERE acked = 1 AND acked_at < $1"), [before]);
|
|
251
327
|
return res.affected;
|
|
@@ -266,6 +342,7 @@ export class SqlWorkflowNotifyJournalStore {
|
|
|
266
342
|
};
|
|
267
343
|
}
|
|
268
344
|
}
|
|
345
|
+
/** MySQL-protocol (TiDB) bindings — historical class names + ctor shapes preserved. */
|
|
269
346
|
export class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
|
|
270
347
|
constructor(pool) {
|
|
271
348
|
super(mysqlDriver(pool));
|
|
@@ -281,6 +358,7 @@ export class TiDBWorkflowNotifyJournalStore extends SqlWorkflowNotifyJournalStor
|
|
|
281
358
|
super(mysqlDriver(pool));
|
|
282
359
|
}
|
|
283
360
|
}
|
|
361
|
+
/** PostgreSQL bindings — historical class names + ctor shapes preserved. */
|
|
284
362
|
export class PgWorkflowRunStore extends SqlWorkflowRunStore {
|
|
285
363
|
constructor(pool) {
|
|
286
364
|
super(pgDriver(pool));
|
|
@@ -1,16 +1,61 @@
|
|
|
1
1
|
import { type ExecutionEnv, type ExecutionEnvFactory } from "@sema-agent/core";
|
|
2
|
+
/** Structured logger surface this wrapper uses (matches the service `logger`; all calls optional-chained). */
|
|
2
3
|
export interface WorktreeIsolationLogger {
|
|
3
4
|
info?(event: string, fields?: Record<string, unknown>): void;
|
|
4
5
|
warn?(event: string, fields?: Record<string, unknown>): void;
|
|
5
6
|
}
|
|
7
|
+
/** Options for {@link withWorktreeIsolation}. ALL fields are operator-supplied (config/closure) — NONE come
|
|
8
|
+
* from a `TaskSpec` or the factory `ctx` (that is the trust boundary; see the file header). */
|
|
6
9
|
export interface WorktreeIsolationOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The operator-trusted base repository root — where `git worktree add/remove/prune` run (the base env's cwd
|
|
12
|
+
* for the git command). MUST be an absolute path to a git repo. This is a CLOSURE value (config), never a
|
|
13
|
+
* task input, so an untrusted caller can never redirect worktree creation to another path.
|
|
14
|
+
*/
|
|
7
15
|
repoRoot: string;
|
|
16
|
+
/**
|
|
17
|
+
* Operator-trusted allowlist of permitted repo roots. {@link repoRoot} MUST be contained within (or equal
|
|
18
|
+
* to) one of these — checked at wrap time, fails LOUD otherwise. Defence in depth against a mis-wired
|
|
19
|
+
* deployment (e.g. a `REPO_ROOT` env accidentally pointed outside the intended tree). Default: `[repoRoot]`
|
|
20
|
+
* (repoRoot trivially permits itself) — pass a broader set only when the operator intends multiple roots.
|
|
21
|
+
*/
|
|
8
22
|
allowedRoots?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* The SHARED base {@link ExecutionEnv} used to run the git worktree commands (`add`/`remove`/`prune`). Its
|
|
25
|
+
* own cwd is irrelevant (core passes `cwd: repoRoot` explicitly on every git call) — it only needs a working
|
|
26
|
+
* `exec` with `git` on PATH. For the host lane this is a host env; we keep it as a single long-lived env per
|
|
27
|
+
* deployment (git ops are cheap and serial-safe). The reaper reuses it for {@link pruneWorktrees}.
|
|
28
|
+
*/
|
|
9
29
|
baseEnvForGit: ExecutionEnv;
|
|
30
|
+
/**
|
|
31
|
+
* Env-SPECIFIC constructor of a worktree-ROOTED {@link ExecutionEnv} (the only lane-specific bit). Host lane:
|
|
32
|
+
* `(dir) => new RemoteHostExecutionEnv({ workspaceDir: dir })` — a host env whose fs/shell operate relative
|
|
33
|
+
* to `dir` AND whose own `destroy()` does NOT delete `dir` (persistent-dir mode), so core's destroy wrapper
|
|
34
|
+
* owns removal via `git worktree remove`. A single-E2B/k8s deployment would pass a remote env rooted at the
|
|
35
|
+
* worktree dir (follow-on — see file header re: suspend incompatibility on the host lane).
|
|
36
|
+
*/
|
|
10
37
|
rootEnvAt: (worktreeDir: string) => ExecutionEnv | Promise<ExecutionEnv>;
|
|
38
|
+
/** Base commit/ref for every worktree (default core's `HEAD` at creation time). Operator-supplied. */
|
|
11
39
|
commit?: string;
|
|
12
40
|
logger?: WorktreeIsolationLogger;
|
|
13
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Wrap a deployment's `executionEnvFactory` so an agent with `ctx.isolation === "worktree"` runs in its own
|
|
44
|
+
* detached git worktree. See the file header for the full contract + trust gate + caveats.
|
|
45
|
+
*
|
|
46
|
+
* Throws at WRAP time (fail-loud, not faked) if `repoRoot` is not contained within `allowedRoots` — a
|
|
47
|
+
* mis-wired deployment must not silently git-worktree an arbitrary path.
|
|
48
|
+
*/
|
|
14
49
|
export declare function withWorktreeIsolation(baseFactory: ExecutionEnvFactory, opts: WorktreeIsolationOptions): ExecutionEnvFactory;
|
|
50
|
+
/**
|
|
51
|
+
* Best-effort reaper hook: deregister worktrees orphaned by a process crash (the Runner never reached
|
|
52
|
+
* `destroy`). Core's {@link pruneWorktrees} runs `git worktree prune` against the base repo — it cleans up
|
|
53
|
+
* registrations whose worktree dirs are already gone. Wired into the service reaper (userland; core ships no
|
|
54
|
+
* post-kill Runner hook). NEVER throws (a prune failure is logged, never fatal).
|
|
55
|
+
*
|
|
56
|
+
* NOTE: `git worktree prune` only deregisters worktrees whose DIRECTORY is missing. A crashed run that left
|
|
57
|
+
* the dir intact is NOT reaped by prune alone — that is acceptable for v1 (the worktree lives under the known
|
|
58
|
+
* `.sema-worktrees/` parent and is human-/script-reapable; a stricter age-based sweep is a follow-on).
|
|
59
|
+
*/
|
|
15
60
|
export declare function reapOrphanWorktrees(baseEnvForGit: ExecutionEnv, repoRoot: string, logger?: WorktreeIsolationLogger): Promise<void>;
|
|
16
61
|
//# sourceMappingURL=worktree-isolation.d.ts.map
|