@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
|
@@ -2,38 +2,75 @@ import { BaseSessionStorage, type PromptEpochDescriptor, type AcquiredSession, t
|
|
|
2
2
|
import type { Pool, PoolClient } from "pg";
|
|
3
3
|
import type { SessionSummary } from "./store-contracts.js";
|
|
4
4
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
5
|
+
/**
|
|
6
|
+
* PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
|
|
7
|
+
* JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), inline `KEY`/`UNIQUE KEY`→separate CREATE INDEX (UNIQUE for uk_entry).
|
|
8
|
+
*
|
|
9
|
+
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are
|
|
10
|
+
* drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead.
|
|
11
|
+
*/
|
|
5
12
|
export declare const PG_SESSION_SCHEMA: string[];
|
|
13
|
+
/** Idempotent schema apply for this store's tables (the test calls this; production aggregation is separate). */
|
|
6
14
|
export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* PostgreSQL-backed {@link BaseSessionStorage} — the durable L1 event log for one session.
|
|
17
|
+
*
|
|
18
|
+
* Behaviourally identical to {@link TiDBSessionStorage}; see that file's class doc for the F2/F3 seams. The only
|
|
19
|
+
* differences are dialectal (placeholders, `IS NOT DISTINCT FROM`, jsonb, rowCount, transaction handle).
|
|
20
|
+
*/
|
|
7
21
|
export declare class PgSessionStorage extends BaseSessionStorage {
|
|
8
22
|
private readonly pool;
|
|
9
23
|
private readonly sessionId;
|
|
10
24
|
private readonly floorSeq;
|
|
11
25
|
constructor(pool: Pool, sessionId: string, metadata: SessionMetadata, entries: SessionTreeEntry[], leafId: string | null, floorEntryId?: string | null, floorSeq?: number);
|
|
26
|
+
/** @see TiDBSessionStorage.getEpochAnchor — [976] S3 RB-14 pull seam, PG dialect twin
|
|
27
|
+
* (keyset-paged to exhaustion: a bounded scan over restatement-less pre-epoch compactions would
|
|
28
|
+
* misreport an epoch-bearing session as pre-epoch — codex HIGH). */
|
|
12
29
|
getEpochAnchor(): Promise<PromptEpochDescriptor | undefined>;
|
|
30
|
+
/** @see TiDBSessionStorage.wake — F3 bounded `[floor..leaf]` rebuild; null if the session does not exist. */
|
|
13
31
|
static wake(pool: Pool, sessionId: string): Promise<PgSessionStorage | null>;
|
|
32
|
+
/** The bounded-window floor = last compaction's firstKeptEntryId (and its seq). @see TiDBSessionStorage.computeFloor */
|
|
14
33
|
private static computeFloor;
|
|
34
|
+
/** Sub-floor lookups (e.g. a label target) aren't in the loaded tail — fall back to the durable log. */
|
|
15
35
|
getEntry(id: string): Promise<SessionTreeEntry | undefined>;
|
|
16
36
|
appendEntry(entry: SessionTreeEntry, opts?: SessionWriteOptions): Promise<void>;
|
|
17
37
|
setLeafId(leafId: string | null, opts?: SessionWriteOptions): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Atomically: CAS the meta leaf (+bump leaf_seq), then append the event at the new seq.
|
|
40
|
+
* The `(session_id, seq)` primary key is the backstop — a racing writer that grabbed the same
|
|
41
|
+
* seq makes our INSERT fail with a duplicate-key (23505), which we translate to a conflict too.
|
|
42
|
+
*/
|
|
18
43
|
private persist;
|
|
19
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* PostgreSQL-backed {@link SessionStore} — the PG twin of {@link TiDBSessionStore}. Maps the
|
|
47
|
+
* `SessionStore` surface (acquire / register / ownerOf / touch / release) onto {@link PgSessionStorage}.
|
|
48
|
+
*/
|
|
20
49
|
export declare class PgSessionStore implements SessionStore {
|
|
21
50
|
private readonly pool;
|
|
51
|
+
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
|
|
22
52
|
private readonly pending;
|
|
23
53
|
constructor(pool: Pool);
|
|
24
54
|
acquire(sessionId?: string, opts?: {
|
|
25
55
|
requireExisting?: boolean;
|
|
26
56
|
}): Promise<AcquiredSession>;
|
|
27
57
|
private load;
|
|
58
|
+
/** Idempotently create a session_meta row with an owner. Existing owner is never overwritten. */
|
|
28
59
|
register(sessionId: string, owner: string | null): Promise<void>;
|
|
60
|
+
/** Owner principal of a session, `null` if registered ownerless, `undefined` if no such session. */
|
|
29
61
|
ownerOf(sessionId: string): Promise<string | null | undefined>;
|
|
30
62
|
touch(sessionId: string): Promise<void>;
|
|
63
|
+
/** E18 — the session's current leaf SessionTreeEntry.id (session_meta.leaf_id). @see TiDBSessionStore.getLeafId
|
|
64
|
+
* (single SELECT, no acquire/lock, cache-bypassing — safe to read mid-run for the resume-at turn-capture). */
|
|
31
65
|
getLeafId(sessionId: string): Promise<string | null>;
|
|
66
|
+
/** [1196] 六轮复审:owner+leafId 单行原子快照(@see TiDBSessionStore.getHead——混世代组合防)。 */
|
|
32
67
|
getHead(sessionId: string): Promise<{
|
|
33
68
|
owner: string | null;
|
|
34
69
|
leafId: string | null;
|
|
35
70
|
} | undefined>;
|
|
71
|
+
/** Durable history is retained; just drop any in-flight cache entry. */
|
|
36
72
|
release(sessionId: string): Promise<void>;
|
|
73
|
+
/** @see TiDBSessionStore.listSessions — session identity from session_meta (owner-filtered), task_run LEFT JOIN for preview. */
|
|
37
74
|
listSessions(opts: {
|
|
38
75
|
owner?: string;
|
|
39
76
|
cursor?: {
|
|
@@ -43,21 +80,56 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
43
80
|
limit: number;
|
|
44
81
|
q?: string;
|
|
45
82
|
}): Promise<SessionSummary[]>;
|
|
83
|
+
/** @see TiDBSessionStore.probeTitle — the PG twin. */
|
|
46
84
|
probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
|
|
85
|
+
/** @see TiDBSessionStore.setTitleIfNull — the PG twin (write-once auto-title). */
|
|
47
86
|
setTitleIfNull(sessionId: string, title: string): Promise<boolean>;
|
|
87
|
+
/** @see TiDBSessionStore.fork — copy the WHOLE durable history (full session_event log, compaction floor bypassed,
|
|
88
|
+
* NOT wake's bounded tail) to a NEW uuidv7 session, re-seq dense from 0, ids/parents verbatim, owner=caller. */
|
|
48
89
|
fork(sourceId: string, owner: string | null): Promise<string | null>;
|
|
90
|
+
/** @see TiDBSessionStore.exportEntries — 2c session-sync EXPORT: the FULL durable session_event log (compaction floor
|
|
91
|
+
* bypassed, NOT wake's tail) as `SessionTreeEntry[]`, oldest-first, verbatim. null if the source is unknown. */
|
|
49
92
|
exportEntries(sessionId: string): Promise<SessionTreeEntry[] | null>;
|
|
93
|
+
/** @see TiDBSessionStore.listEntryIds — 2c P1d-α (PULL streaming): the IDS-ONLY projection of the full durable log,
|
|
94
|
+
* oldest-first (`SELECT entry_id … ORDER BY seq ASC`), no payloads. null if the source is unknown. */
|
|
50
95
|
listEntryIds(sessionId: string): Promise<string[] | null>;
|
|
96
|
+
/** @see TiDBSessionStore.exportEntriesStream — 2c P1d-α (PULL streaming): KEYSET-paged async generator over the full
|
|
97
|
+
* durable log (node-pg also buffers a plain SELECT → page it). `… WHERE seq > cursor ORDER BY seq ASC LIMIT K`
|
|
98
|
+
* (K = batchSize ?? 500), advancing the cursor to the last seq each batch, ONE pooled `pool.query` per batch (no
|
|
99
|
+
* held connection across the stream → a slow consumer can't pin one). Start cursor = afterSeq ?? -1 (0-based dense).
|
|
100
|
+
* Resolves to null (NOT an empty iterable) for a missing session — eager `session_meta` probe (BIGINT seq comes
|
|
101
|
+
* back as a STRING in node-pg → always Number(...) the cursor advance). */
|
|
51
102
|
exportEntriesStream(sessionId: string, opts?: {
|
|
52
103
|
afterSeq?: number;
|
|
53
104
|
batchSize?: number;
|
|
54
105
|
}): Promise<AsyncIterable<SessionTreeEntry> | null>;
|
|
106
|
+
/** @see TiDBSessionStore.importEntries — 2c session-sync IMPORT: core `validateEntriesForImport` gate FIRST (fail-closed),
|
|
107
|
+
* then persist its verbatim output (re-seq dense from 0, ids/parents/payload/ts unchanged) in ONE txn with
|
|
108
|
+
* session_meta LAST as the commit point; owner = the RE-STAMPED authenticated principal (never the bundle's). */
|
|
55
109
|
importEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
110
|
+
/** @see TiDBSessionStore.replaceEntries — 2c (§7/§8) IDEMPOTENT REPLACE: core `validateEntriesForImport` gate FIRST
|
|
111
|
+
* (fail-closed), then DELETE events + the owner-guarded meta delete (`owner IS NOT DISTINCT FROM $n` = the PG `<=>`,
|
|
112
|
+
* defense-in-depth), then re-insert via the SHARED body (entries + session_meta LAST as the commit point), ALL in
|
|
113
|
+
* ONE txn. Safe to run OVER an existing session (fast-forward / §8 retry) — unlike importEntries' fresh-only INSERT. */
|
|
56
114
|
replaceEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
115
|
+
/** @see TiDBSessionStore.insertValidatedLog — shared insert half of importEntries / replaceEntries (ONE source, no
|
|
116
|
+
* drift): entries re-seq dense from 0 (ids/parents/payload/ts verbatim), THEN session_meta LAST as the commit point.
|
|
117
|
+
* Runs inside the caller's open txn (`conn` already in BEGIN); the caller owns COMMIT/ROLLBACK + the validate gate. */
|
|
57
118
|
private insertValidatedLog;
|
|
119
|
+
/** @see TiDBSessionStore.beginImportStaging — 2c P1d-β PUSH staging: stream batches into the shadow id
|
|
120
|
+
* `${realSessionId}#stg-${token}` (route-minted token), then commit (atomic swap) / abort. */
|
|
58
121
|
beginImportStaging(realSessionId: string, token: string): StagingHandle;
|
|
122
|
+
/** @see TiDBSessionStore.readStagedEntries — 2c P1d-β staged-row inspection: the staged rows under `stagingId`, oldest-
|
|
123
|
+
* first, no session_meta probe (a staging id has no meta row). No longer the import gate (the per-line
|
|
124
|
+
* StreamingImportValidator validates during the stream); a tests/diagnostics seam. Returns [] for an empty/unknown id. */
|
|
59
125
|
readStagedEntries(stagingId: string): Promise<SessionTreeEntry[]>;
|
|
126
|
+
/** @see TiDBSessionStore.sweepStagingSessions — reaper for abandoned staging sessions: DELETE session_event rows
|
|
127
|
+
* under a `%#stg-%` id with NO session_meta whose oldest row is older than the grace window (in-flight stays fresh).
|
|
128
|
+
* Returns the affected row count. Driven by main.ts's reaper. */
|
|
60
129
|
sweepStagingSessions(): Promise<number>;
|
|
130
|
+
/** @see TiDBSessionStore.deleteSession — purge session_meta + session_event; idempotent (false if absent).
|
|
131
|
+
* Owner-guarded on session_meta.owner (`IS NOT DISTINCT FROM` = the PG `<=>`); the event purge is gated on the
|
|
132
|
+
* owner-matched meta delete (session_event has no owner column). */
|
|
61
133
|
deleteSession(sessionId: string, owner: string | null): Promise<boolean>;
|
|
62
134
|
get size(): number;
|
|
63
135
|
dispose(): void;
|
|
@@ -1,16 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL-backed {@link BaseSessionStorage} — the PG twin of {@link TiDBSessionStorage}
|
|
3
|
+
* (src/plugins/tidb-session-storage.ts; clay 2026-06-21, the second session backend).
|
|
4
|
+
*
|
|
5
|
+
* A behavioural TWIN: identical class/method surface + identical semantics (the F2 optimistic-lock CAS on
|
|
6
|
+
* `leaf_id`/`leaf_seq`, the F3 bounded `[floor..leaf]` wake, the sub-floor durable getEntry fallback), only the
|
|
7
|
+
* SQL dialect differs. Kept in lock-step with the TiDB store by ONE shared real-DB integration suite
|
|
8
|
+
* (test/pg-session-storage-integration.test.ts) that drives the SAME scenarios against BOTH engines. Dialect deltas:
|
|
9
|
+
* - placeholders `$1..$n` (not `?`) - affected rows = `result.rowCount` (not affectedRows)
|
|
10
|
+
* - `leaf_id IS NOT DISTINCT FROM $n` (not `<=>`) - dup-key SQLSTATE `23505` (not ER_DUP_ENTRY)
|
|
11
|
+
* - `payload` is `jsonb`, bound `$n::jsonb`; node-pg PARSES jsonb → object on read (parseJson passes objects through)
|
|
12
|
+
* - BIGINT (`seq`/`leaf_seq`) comes back as a STRING → always `Number(...)`
|
|
13
|
+
* - transaction via `pool.connect()` + BEGIN/COMMIT/ROLLBACK/release (not getConnection/beginTransaction)
|
|
14
|
+
*
|
|
15
|
+
* Unlike the run/checkpoint PG stores, this store carries its OWN schema (PG_SESSION_SCHEMA + ensureSchema): the
|
|
16
|
+
* session_meta/session_event tables are DISJOINT from the centrally-owned run-store/checkpoint tables, so a
|
|
17
|
+
* self-contained CREATE IF NOT EXISTS cannot shadow another store's table. (Central aggregation into pg-pool is
|
|
18
|
+
* done separately afterward.)
|
|
19
|
+
*/
|
|
1
20
|
import { emitLeafAdvance } from "../session-leaf-bus.js";
|
|
21
|
+
// 🔴 core 1.403 迁移(2026-07-25):runtime 构造用法,`Session` → `StoredSession`(见 audit.ts 同款注释)。
|
|
2
22
|
import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, normalizePromptEpoch, } from "@sema-agent/core";
|
|
3
23
|
import { escapeLike } from "./sql-escape.js";
|
|
4
24
|
import { pgSafeJsonStringify } from "./pg-safe-json.js";
|
|
5
25
|
import { toIso } from "./sql-row-helpers.js";
|
|
6
26
|
import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, STAGING_ID_MARKER, } from "../session-sync-kernel.js";
|
|
27
|
+
/** Staging-row reaper grace window (twin of the TiDB store's STAGING_GC_GRACE_MS) — see that store for the rationale. */
|
|
7
28
|
const STAGING_GC_GRACE_MS = 3_600_000;
|
|
8
29
|
function isDupKey(err) {
|
|
9
|
-
return Boolean(err) && err.code === "23505";
|
|
30
|
+
return Boolean(err) && err.code === "23505"; // PG unique_violation
|
|
10
31
|
}
|
|
32
|
+
/** node-pg parses jsonb → object already; only strings need JSON.parse, everything else passes through. */
|
|
11
33
|
function parseEntry(p) {
|
|
12
34
|
return (typeof p === "string" ? JSON.parse(p) : p);
|
|
13
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
|
|
38
|
+
* JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), inline `KEY`/`UNIQUE KEY`→separate CREATE INDEX (UNIQUE for uk_entry).
|
|
39
|
+
*
|
|
40
|
+
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are
|
|
41
|
+
* drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead.
|
|
42
|
+
*/
|
|
14
43
|
export const PG_SESSION_SCHEMA = [
|
|
15
44
|
`CREATE TABLE IF NOT EXISTS session_meta (
|
|
16
45
|
session_id VARCHAR(64) NOT NULL,
|
|
@@ -37,10 +66,17 @@ export const PG_SESSION_SCHEMA = [
|
|
|
37
66
|
)`,
|
|
38
67
|
`CREATE UNIQUE INDEX IF NOT EXISTS uk_session_event_entry ON session_event (session_id, entry_id)`,
|
|
39
68
|
];
|
|
69
|
+
/** Idempotent schema apply for this store's tables (the test calls this; production aggregation is separate). */
|
|
40
70
|
export async function ensureSchema(pool) {
|
|
41
71
|
for (const stmt of PG_SESSION_SCHEMA)
|
|
42
72
|
await pool.query(stmt);
|
|
43
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* PostgreSQL-backed {@link BaseSessionStorage} — the durable L1 event log for one session.
|
|
76
|
+
*
|
|
77
|
+
* Behaviourally identical to {@link TiDBSessionStorage}; see that file's class doc for the F2/F3 seams. The only
|
|
78
|
+
* differences are dialectal (placeholders, `IS NOT DISTINCT FROM`, jsonb, rowCount, transaction handle).
|
|
79
|
+
*/
|
|
44
80
|
export class PgSessionStorage extends BaseSessionStorage {
|
|
45
81
|
pool;
|
|
46
82
|
sessionId;
|
|
@@ -51,14 +87,17 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
51
87
|
this.sessionId = sessionId;
|
|
52
88
|
this.floorSeq = floorSeq;
|
|
53
89
|
}
|
|
90
|
+
/** @see TiDBSessionStorage.getEpochAnchor — [976] S3 RB-14 pull seam, PG dialect twin
|
|
91
|
+
* (keyset-paged to exhaustion: a bounded scan over restatement-less pre-epoch compactions would
|
|
92
|
+
* misreport an epoch-bearing session as pre-epoch — codex HIGH). */
|
|
54
93
|
async getEpochAnchor() {
|
|
55
94
|
if (this.floorSeq <= 0)
|
|
56
|
-
return undefined;
|
|
95
|
+
return undefined; // full load — the walk already reaches the root pin
|
|
57
96
|
let cursor = this.floorSeq;
|
|
58
97
|
for (;;) {
|
|
59
98
|
const res = await this.pool.query("SELECT seq, payload FROM session_event WHERE session_id = $1 AND seq < $2 AND type IN ('prompt_epoch','compaction') ORDER BY seq DESC LIMIT 100", [this.sessionId, cursor]);
|
|
60
99
|
if (res.rows.length === 0)
|
|
61
|
-
return undefined;
|
|
100
|
+
return undefined; // exhausted — a genuine pre-epoch session
|
|
62
101
|
for (const r of res.rows) {
|
|
63
102
|
const e = parseEntry(r.payload);
|
|
64
103
|
const d = e.type === "prompt_epoch" ? normalizePromptEpoch(e) : normalizePromptEpoch(e.details?.promptEpoch);
|
|
@@ -68,6 +107,7 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
68
107
|
cursor = Number(res.rows[res.rows.length - 1].seq);
|
|
69
108
|
}
|
|
70
109
|
}
|
|
110
|
+
/** @see TiDBSessionStorage.wake — F3 bounded `[floor..leaf]` rebuild; null if the session does not exist. */
|
|
71
111
|
static async wake(pool, sessionId) {
|
|
72
112
|
const metaRes = await pool.query("SELECT created_at, leaf_id, leaf_seq FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
73
113
|
const meta = metaRes.rows[0];
|
|
@@ -79,6 +119,7 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
79
119
|
const metadata = { id: sessionId, createdAt: toIso(meta.created_at) };
|
|
80
120
|
return new PgSessionStorage(pool, sessionId, metadata, entries, meta.leaf_id ?? null, floorEntryId, floorSeq);
|
|
81
121
|
}
|
|
122
|
+
/** The bounded-window floor = last compaction's firstKeptEntryId (and its seq). @see TiDBSessionStorage.computeFloor */
|
|
82
123
|
static async computeFloor(pool, sessionId) {
|
|
83
124
|
const compRes = await pool.query("SELECT payload FROM session_event WHERE session_id = $1 AND type = 'compaction' ORDER BY seq DESC LIMIT 1", [sessionId]);
|
|
84
125
|
if (!compRes.rows[0])
|
|
@@ -88,10 +129,12 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
88
129
|
if (!fk)
|
|
89
130
|
return { floorEntryId: null, floorSeq: 0 };
|
|
90
131
|
const seqRes = await pool.query("SELECT seq FROM session_event WHERE session_id = $1 AND entry_id = $2", [sessionId, fk]);
|
|
132
|
+
// firstKeptEntryId missing from the log (shouldn't happen) → degrade to full load.
|
|
91
133
|
if (!seqRes.rows[0])
|
|
92
134
|
return { floorEntryId: null, floorSeq: 0 };
|
|
93
135
|
return { floorEntryId: fk, floorSeq: Number(seqRes.rows[0].seq) };
|
|
94
136
|
}
|
|
137
|
+
/** Sub-floor lookups (e.g. a label target) aren't in the loaded tail — fall back to the durable log. */
|
|
95
138
|
async getEntry(id) {
|
|
96
139
|
const inMem = await super.getEntry(id);
|
|
97
140
|
if (inMem)
|
|
@@ -107,10 +150,16 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
107
150
|
this.recordEntry(entry);
|
|
108
151
|
}
|
|
109
152
|
async setLeafId(leafId, opts) {
|
|
153
|
+
// A leaf move is itself a persisted `leaf` entry whose parent is the current leaf.
|
|
110
154
|
const leafEntry = this.createLeafEntry(leafId);
|
|
111
155
|
await this.persist(leafEntry, opts);
|
|
112
156
|
this.recordEntry(leafEntry);
|
|
113
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Atomically: CAS the meta leaf (+bump leaf_seq), then append the event at the new seq.
|
|
160
|
+
* The `(session_id, seq)` primary key is the backstop — a racing writer that grabbed the same
|
|
161
|
+
* seq makes our INSERT fail with a duplicate-key (23505), which we translate to a conflict too.
|
|
162
|
+
*/
|
|
114
163
|
async persist(entry, opts) {
|
|
115
164
|
const newLeaf = leafIdAfterEntry(entry);
|
|
116
165
|
const conditional = opts !== undefined && opts.expectedLeafId !== undefined;
|
|
@@ -127,16 +176,17 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
127
176
|
}
|
|
128
177
|
const seqRes = await conn.query("SELECT leaf_seq, owner FROM session_meta WHERE session_id = $1", [this.sessionId]);
|
|
129
178
|
const seq = Number(seqRes.rows[0].leaf_seq);
|
|
130
|
-
const commitOwner = seqRes.rows[0].owner ?? null;
|
|
179
|
+
const commitOwner = seqRes.rows[0].owner ?? null; // codex-2:S2 发射 owner=本事务当前行(tidb twin 同注)
|
|
131
180
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)", [this.sessionId, seq, entry.id, entry.parentId ?? null, entry.type, pgSafeJsonStringify(entry), new Date(entry.timestamp)]);
|
|
132
181
|
await conn.query("COMMIT");
|
|
133
|
-
emitLeafAdvance(this.sessionId, newLeaf, commitOwner, seq);
|
|
182
|
+
emitLeafAdvance(this.sessionId, newLeaf, commitOwner, seq); // S2 fast path — post-commit only, fire-and-forget (session-leaf-bus 契约;tidb twin 同点)
|
|
134
183
|
}
|
|
135
184
|
catch (err) {
|
|
136
185
|
try {
|
|
137
186
|
await conn.query("ROLLBACK");
|
|
138
187
|
}
|
|
139
188
|
catch {
|
|
189
|
+
/* ignore rollback error */
|
|
140
190
|
}
|
|
141
191
|
if (err instanceof SessionError)
|
|
142
192
|
throw err;
|
|
@@ -150,8 +200,14 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
150
200
|
}
|
|
151
201
|
}
|
|
152
202
|
}
|
|
203
|
+
// ────────────────────────────────── PG twin of TiDBSessionStore ──────────────────────────────────
|
|
204
|
+
/**
|
|
205
|
+
* PostgreSQL-backed {@link SessionStore} — the PG twin of {@link TiDBSessionStore}. Maps the
|
|
206
|
+
* `SessionStore` surface (acquire / register / ownerOf / touch / release) onto {@link PgSessionStorage}.
|
|
207
|
+
*/
|
|
153
208
|
export class PgSessionStore {
|
|
154
209
|
pool;
|
|
210
|
+
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
|
|
155
211
|
pending = new Map();
|
|
156
212
|
constructor(pool) {
|
|
157
213
|
this.pool = pool;
|
|
@@ -162,6 +218,9 @@ export class PgSessionStore {
|
|
|
162
218
|
await this.register(id, null);
|
|
163
219
|
return this.load(id);
|
|
164
220
|
}
|
|
221
|
+
// design/114 Phase3 (F1): a `requireExisting` acquire NEVER creates and can REJECT (not_found) — it MUST bypass
|
|
222
|
+
// the shared create-dedup `pending`, else a concurrent create-capable waiter awaiting that same promise would
|
|
223
|
+
// inherit the not_found rejection instead of creating-on-miss. (Twin of TiDBSessionStore.acquire.)
|
|
165
224
|
if (opts?.requireExisting)
|
|
166
225
|
return this.load(sessionId, true);
|
|
167
226
|
const inflight = this.pending.get(sessionId);
|
|
@@ -174,6 +233,8 @@ export class PgSessionStore {
|
|
|
174
233
|
async load(sessionId, requireExisting = false) {
|
|
175
234
|
let storage = await PgSessionStorage.wake(this.pool, sessionId);
|
|
176
235
|
if (!storage) {
|
|
236
|
+
// design/114 Phase3: `requireExisting` ⇒ FAIL LOUD on a genuinely missing session (no session_meta row),
|
|
237
|
+
// never the silent create-on-miss. core's prepare-task maps this `not_found` to `resume.session_not_found`.
|
|
177
238
|
if (requireExisting)
|
|
178
239
|
throw new SessionError("not_found", `session ${sessionId} not found`);
|
|
179
240
|
await this.register(sessionId, null);
|
|
@@ -181,11 +242,13 @@ export class PgSessionStore {
|
|
|
181
242
|
}
|
|
182
243
|
return { session: new StoredSession(storage), sessionId };
|
|
183
244
|
}
|
|
245
|
+
/** Idempotently create a session_meta row with an owner. Existing owner is never overwritten. */
|
|
184
246
|
async register(sessionId, owner) {
|
|
185
247
|
const now = new Date();
|
|
186
248
|
await this.pool.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq) " +
|
|
187
249
|
"VALUES ($1,$2,$3,$4,NULL,0) ON CONFLICT (session_id) DO NOTHING", [sessionId, owner, now, now]);
|
|
188
250
|
}
|
|
251
|
+
/** Owner principal of a session, `null` if registered ownerless, `undefined` if no such session. */
|
|
189
252
|
async ownerOf(sessionId) {
|
|
190
253
|
const res = await this.pool.query("SELECT owner FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
191
254
|
if (res.rows.length === 0)
|
|
@@ -198,19 +261,27 @@ export class PgSessionStore {
|
|
|
198
261
|
sessionId,
|
|
199
262
|
]);
|
|
200
263
|
}
|
|
264
|
+
/** E18 — the session's current leaf SessionTreeEntry.id (session_meta.leaf_id). @see TiDBSessionStore.getLeafId
|
|
265
|
+
* (single SELECT, no acquire/lock, cache-bypassing — safe to read mid-run for the resume-at turn-capture). */
|
|
201
266
|
async getLeafId(sessionId) {
|
|
202
267
|
const r = await this.pool.query("SELECT leaf_id FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
203
268
|
return r.rows[0] ? (r.rows[0].leaf_id ?? null) : null;
|
|
204
269
|
}
|
|
270
|
+
/** [1196] 六轮复审:owner+leafId 单行原子快照(@see TiDBSessionStore.getHead——混世代组合防)。 */
|
|
205
271
|
async getHead(sessionId) {
|
|
206
272
|
const r = await this.pool.query("SELECT owner, leaf_id FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
207
273
|
if (r.rows.length === 0)
|
|
208
274
|
return undefined;
|
|
209
275
|
return { owner: r.rows[0].owner ?? null, leafId: r.rows[0].leaf_id ?? null };
|
|
210
276
|
}
|
|
277
|
+
/** Durable history is retained; just drop any in-flight cache entry. */
|
|
211
278
|
async release(sessionId) {
|
|
212
279
|
this.pending.delete(sessionId);
|
|
213
280
|
}
|
|
281
|
+
// ───────────────────────── SessionRepo seam (§0.5) — PG twin of TiDBSessionStore ─────────────────────────
|
|
282
|
+
// Behaviourally identical to {@link TiDBSessionStore.listSessions/fork/deleteSession}; only the SQL dialect
|
|
283
|
+
// differs ($n placeholders, jsonb, rowCount, BEGIN/COMMIT handle). Kept in lock-step by the shared real-DB suite.
|
|
284
|
+
/** @see TiDBSessionStore.listSessions — session identity from session_meta (owner-filtered), task_run LEFT JOIN for preview. */
|
|
214
285
|
async listSessions(opts) {
|
|
215
286
|
const params = [];
|
|
216
287
|
const ph = (v) => {
|
|
@@ -220,6 +291,7 @@ export class PgSessionStore {
|
|
|
220
291
|
const where = [];
|
|
221
292
|
if (opts.owner)
|
|
222
293
|
where.push(`sm.owner = ${ph(opts.owner)}`);
|
|
294
|
+
// `?q=` — the PG twin of the TiDB EXISTS predicate (LOWER-LIKE + ESCAPE, dialect-mirrored).
|
|
223
295
|
if (opts.q)
|
|
224
296
|
where.push(`EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = sm.session_id AND LOWER(tr.objective_preview) LIKE LOWER(${ph(`%${escapeLike(opts.q)}%`)}) ESCAPE '\\')`);
|
|
225
297
|
if (opts.cursor) {
|
|
@@ -232,7 +304,7 @@ export class PgSessionStore {
|
|
|
232
304
|
" COALESCE(r.run_count, 0) AS run_count, latest.objective_preview, latest.status, latest.task_id AS last_run_id " +
|
|
233
305
|
"FROM session_meta sm " +
|
|
234
306
|
"LEFT JOIN (SELECT session_id, COUNT(*) AS run_count FROM task_run GROUP BY session_id) r ON r.session_id = sm.session_id " +
|
|
235
|
-
"LEFT JOIN (SELECT DISTINCT ON (session_id) session_id, objective_preview, status, task_id FROM task_run " +
|
|
307
|
+
"LEFT JOIN (SELECT DISTINCT ON (session_id) session_id, objective_preview, status, task_id FROM task_run " + // K-5c: latest run's task_id
|
|
236
308
|
" ORDER BY session_id, created_at DESC, task_id DESC) latest ON latest.session_id = sm.session_id " +
|
|
237
309
|
(where.length ? "WHERE " + where.join(" AND ") + " " : "") +
|
|
238
310
|
`ORDER BY sm.updated_at DESC, sm.session_id DESC LIMIT ${ph(opts.limit)}`;
|
|
@@ -245,10 +317,11 @@ export class PgSessionStore {
|
|
|
245
317
|
runCount: Number(r.run_count),
|
|
246
318
|
objectivePreview: r.objective_preview ?? null,
|
|
247
319
|
lastStatus: r.status == null ? "" : String(r.status),
|
|
248
|
-
lastRunId: r.last_run_id ?? null,
|
|
320
|
+
lastRunId: r.last_run_id ?? null, // K-5c: latest run id = the live-tail re-attach anchor (null if no runs)
|
|
249
321
|
title: r.title ?? null,
|
|
250
322
|
}));
|
|
251
323
|
}
|
|
324
|
+
/** @see TiDBSessionStore.probeTitle — the PG twin. */
|
|
252
325
|
async probeTitle(sessionId) {
|
|
253
326
|
const res = await this.pool.query("SELECT title FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
254
327
|
const row = res.rows[0];
|
|
@@ -256,15 +329,19 @@ export class PgSessionStore {
|
|
|
256
329
|
return "none";
|
|
257
330
|
return row.title == null ? "untitled" : "titled";
|
|
258
331
|
}
|
|
332
|
+
/** @see TiDBSessionStore.setTitleIfNull — the PG twin (write-once auto-title). */
|
|
259
333
|
async setTitleIfNull(sessionId, title) {
|
|
260
334
|
const res = await this.pool.query("UPDATE session_meta SET title = $1 WHERE session_id = $2 AND title IS NULL", [title, sessionId]);
|
|
261
335
|
return (res.rowCount ?? 0) > 0;
|
|
262
336
|
}
|
|
337
|
+
/** @see TiDBSessionStore.fork — copy the WHOLE durable history (full session_event log, compaction floor bypassed,
|
|
338
|
+
* NOT wake's bounded tail) to a NEW uuidv7 session, re-seq dense from 0, ids/parents verbatim, owner=caller. */
|
|
263
339
|
async fork(sourceId, owner) {
|
|
264
340
|
const metaRes = await this.pool.query("SELECT leaf_id FROM session_meta WHERE session_id = $1", [sourceId]);
|
|
265
341
|
if (!metaRes.rows[0])
|
|
266
342
|
return null;
|
|
267
343
|
const leafId = metaRes.rows[0].leaf_id ?? null;
|
|
344
|
+
// FULL durable history (compaction floor bypassed) — every entry, oldest first. node-pg parses jsonb → object.
|
|
268
345
|
const eventRes = await this.pool.query("SELECT entry_id, parent_id, type, payload, ts FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [sourceId]);
|
|
269
346
|
const rows = eventRes.rows;
|
|
270
347
|
const newId = uuidv7();
|
|
@@ -274,6 +351,8 @@ export class PgSessionStore {
|
|
|
274
351
|
await conn.query("BEGIN");
|
|
275
352
|
for (let seq = 0; seq < rows.length; seq++) {
|
|
276
353
|
const r = rows[seq];
|
|
354
|
+
// Copy the payload blob VERBATIM; re-stringify only an object so $6::jsonb binds a JSON string (node-pg
|
|
355
|
+
// returns jsonb already parsed, so a string here means it was stored stringly — rare; pass either through).
|
|
277
356
|
const payload = typeof r.payload === "string" ? r.payload : pgSafeJsonStringify(r.payload);
|
|
278
357
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
|
|
279
358
|
}
|
|
@@ -286,6 +365,7 @@ export class PgSessionStore {
|
|
|
286
365
|
await conn.query("ROLLBACK");
|
|
287
366
|
}
|
|
288
367
|
catch {
|
|
368
|
+
/* ignore */
|
|
289
369
|
}
|
|
290
370
|
throw err instanceof SessionError ? err : new SessionError("storage", `fork of session ${sourceId} failed`, err instanceof Error ? err : undefined);
|
|
291
371
|
}
|
|
@@ -293,44 +373,60 @@ export class PgSessionStore {
|
|
|
293
373
|
conn.release();
|
|
294
374
|
}
|
|
295
375
|
}
|
|
376
|
+
/** @see TiDBSessionStore.exportEntries — 2c session-sync EXPORT: the FULL durable session_event log (compaction floor
|
|
377
|
+
* bypassed, NOT wake's tail) as `SessionTreeEntry[]`, oldest-first, verbatim. null if the source is unknown. */
|
|
296
378
|
async exportEntries(sessionId) {
|
|
297
379
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
298
380
|
if (metaRes.rows.length === 0)
|
|
299
381
|
return null;
|
|
300
382
|
const eventRes = await this.pool.query("SELECT payload FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [sessionId]);
|
|
383
|
+
// node-pg parses jsonb → object already (parseEntry passes objects through; a string would be rare/legacy).
|
|
301
384
|
return eventRes.rows.map((r) => parseEntry(r.payload));
|
|
302
385
|
}
|
|
386
|
+
/** @see TiDBSessionStore.listEntryIds — 2c P1d-α (PULL streaming): the IDS-ONLY projection of the full durable log,
|
|
387
|
+
* oldest-first (`SELECT entry_id … ORDER BY seq ASC`), no payloads. null if the source is unknown. */
|
|
303
388
|
async listEntryIds(sessionId) {
|
|
304
389
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
305
390
|
if (metaRes.rows.length === 0)
|
|
306
|
-
return null;
|
|
391
|
+
return null; // no such session (mirror exportEntries' null)
|
|
307
392
|
const res = await this.pool.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [sessionId]);
|
|
308
393
|
return res.rows.map((r) => String(r.entry_id));
|
|
309
394
|
}
|
|
395
|
+
/** @see TiDBSessionStore.exportEntriesStream — 2c P1d-α (PULL streaming): KEYSET-paged async generator over the full
|
|
396
|
+
* durable log (node-pg also buffers a plain SELECT → page it). `… WHERE seq > cursor ORDER BY seq ASC LIMIT K`
|
|
397
|
+
* (K = batchSize ?? 500), advancing the cursor to the last seq each batch, ONE pooled `pool.query` per batch (no
|
|
398
|
+
* held connection across the stream → a slow consumer can't pin one). Start cursor = afterSeq ?? -1 (0-based dense).
|
|
399
|
+
* Resolves to null (NOT an empty iterable) for a missing session — eager `session_meta` probe (BIGINT seq comes
|
|
400
|
+
* back as a STRING in node-pg → always Number(...) the cursor advance). */
|
|
310
401
|
async exportEntriesStream(sessionId, opts) {
|
|
311
402
|
const metaRes = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = $1", [sessionId]);
|
|
312
403
|
if (metaRes.rows.length === 0)
|
|
313
|
-
return null;
|
|
404
|
+
return null; // no such session — NOT an empty iterable
|
|
314
405
|
const pool = this.pool;
|
|
315
406
|
const batchSize = opts?.batchSize ?? 500;
|
|
316
407
|
async function* gen() {
|
|
317
|
-
let cursor = opts?.afterSeq ?? -1;
|
|
408
|
+
let cursor = opts?.afterSeq ?? -1; // seq is 0-based dense → > -1 starts at seq 0
|
|
318
409
|
for (;;) {
|
|
319
410
|
const res = await pool.query("SELECT seq, payload FROM session_event WHERE session_id = $1 AND seq > $2 ORDER BY seq ASC LIMIT $3", [sessionId, cursor, batchSize]);
|
|
320
411
|
for (const r of res.rows)
|
|
321
412
|
yield parseEntry(r.payload);
|
|
322
413
|
if (res.rows.length < batchSize)
|
|
323
|
-
return;
|
|
324
|
-
cursor = Number(res.rows[res.rows.length - 1].seq);
|
|
414
|
+
return; // short page = end of log
|
|
415
|
+
cursor = Number(res.rows[res.rows.length - 1].seq); // BIGINT seq → STRING in node-pg → Number(...)
|
|
325
416
|
}
|
|
326
417
|
}
|
|
327
418
|
return gen();
|
|
328
419
|
}
|
|
420
|
+
/** @see TiDBSessionStore.importEntries — 2c session-sync IMPORT: core `validateEntriesForImport` gate FIRST (fail-closed),
|
|
421
|
+
* then persist its verbatim output (re-seq dense from 0, ids/parents/payload/ts unchanged) in ONE txn with
|
|
422
|
+
* session_meta LAST as the commit point; owner = the RE-STAMPED authenticated principal (never the bundle's). */
|
|
329
423
|
async importEntries(sessionId, owner, entries) {
|
|
330
|
-
const validated = validateEntriesForImport(entries);
|
|
424
|
+
const validated = validateEntriesForImport(entries); // fail-closed invariant gate BEFORE any write
|
|
331
425
|
const conn = await this.pool.connect();
|
|
332
426
|
try {
|
|
333
427
|
await conn.query("BEGIN");
|
|
428
|
+
// Shared insert body (entries re-seq dense from 0 + session_meta LAST = commit point). Plain INSERT (fresh-only);
|
|
429
|
+
// replaceEntries is the idempotent-over-existing path (DELETEs first inside the same txn). @see TiDB twin.
|
|
334
430
|
await this.insertValidatedLog(conn, sessionId, owner, validated);
|
|
335
431
|
await conn.query("COMMIT");
|
|
336
432
|
}
|
|
@@ -339,22 +435,29 @@ export class PgSessionStore {
|
|
|
339
435
|
await conn.query("ROLLBACK");
|
|
340
436
|
}
|
|
341
437
|
catch {
|
|
438
|
+
/* ignore */
|
|
342
439
|
}
|
|
343
440
|
if (err instanceof SessionError)
|
|
344
|
-
throw err;
|
|
441
|
+
throw err; // invalid_session (gate) / conflict — pass through
|
|
345
442
|
throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
346
443
|
}
|
|
347
444
|
finally {
|
|
348
445
|
conn.release();
|
|
349
446
|
}
|
|
350
447
|
}
|
|
448
|
+
/** @see TiDBSessionStore.replaceEntries — 2c (§7/§8) IDEMPOTENT REPLACE: core `validateEntriesForImport` gate FIRST
|
|
449
|
+
* (fail-closed), then DELETE events + the owner-guarded meta delete (`owner IS NOT DISTINCT FROM $n` = the PG `<=>`,
|
|
450
|
+
* defense-in-depth), then re-insert via the SHARED body (entries + session_meta LAST as the commit point), ALL in
|
|
451
|
+
* ONE txn. Safe to run OVER an existing session (fast-forward / §8 retry) — unlike importEntries' fresh-only INSERT. */
|
|
351
452
|
async replaceEntries(sessionId, owner, entries) {
|
|
352
|
-
const validated = validateEntriesForImport(entries);
|
|
453
|
+
const validated = validateEntriesForImport(entries); // fail-closed gate BEFORE any write (mirrors importEntries)
|
|
353
454
|
const conn = await this.pool.connect();
|
|
354
455
|
try {
|
|
355
456
|
await conn.query("BEGIN");
|
|
457
|
+
// Purge first (idempotent: a missing session deletes 0 rows). Events, then the owner-guarded meta delete.
|
|
356
458
|
await conn.query("DELETE FROM session_event WHERE session_id = $1", [sessionId]);
|
|
357
459
|
await conn.query("DELETE FROM session_meta WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2", [sessionId, owner]);
|
|
460
|
+
// …then re-insert the validated log (entries + meta-as-commit-point) via the SHARED body.
|
|
358
461
|
await this.insertValidatedLog(conn, sessionId, owner, validated);
|
|
359
462
|
await conn.query("COMMIT");
|
|
360
463
|
}
|
|
@@ -363,19 +466,23 @@ export class PgSessionStore {
|
|
|
363
466
|
await conn.query("ROLLBACK");
|
|
364
467
|
}
|
|
365
468
|
catch {
|
|
469
|
+
/* ignore */
|
|
366
470
|
}
|
|
367
471
|
if (err instanceof SessionError)
|
|
368
|
-
throw err;
|
|
472
|
+
throw err; // invalid_session (gate) / conflict — pass through
|
|
369
473
|
throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
370
474
|
}
|
|
371
475
|
finally {
|
|
372
476
|
conn.release();
|
|
373
477
|
}
|
|
374
478
|
}
|
|
479
|
+
/** @see TiDBSessionStore.insertValidatedLog — shared insert half of importEntries / replaceEntries (ONE source, no
|
|
480
|
+
* drift): entries re-seq dense from 0 (ids/parents/payload/ts verbatim), THEN session_meta LAST as the commit point.
|
|
481
|
+
* Runs inside the caller's open txn (`conn` already in BEGIN); the caller owns COMMIT/ROLLBACK + the validate gate. */
|
|
375
482
|
async insertValidatedLog(conn, sessionId, owner, validated) {
|
|
376
483
|
let leafId = null;
|
|
377
484
|
for (const e of validated)
|
|
378
|
-
leafId = leafIdAfterEntry(e);
|
|
485
|
+
leafId = leafIdAfterEntry(e); // replayed leaf — core's exported helper (no service copy)
|
|
379
486
|
const now = new Date();
|
|
380
487
|
for (let seq = 0; seq < validated.length; seq++) {
|
|
381
488
|
const e = validated[seq];
|
|
@@ -383,13 +490,21 @@ export class PgSessionStore {
|
|
|
383
490
|
}
|
|
384
491
|
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq) VALUES ($1,$2,$3,$4,$5,$6)", [sessionId, owner, now, now, leafId, validated.length]);
|
|
385
492
|
}
|
|
493
|
+
/** @see TiDBSessionStore.beginImportStaging — 2c P1d-β PUSH staging: stream batches into the shadow id
|
|
494
|
+
* `${realSessionId}#stg-${token}` (route-minted token), then commit (atomic swap) / abort. */
|
|
386
495
|
beginImportStaging(realSessionId, token) {
|
|
387
496
|
return new PgStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
|
|
388
497
|
}
|
|
498
|
+
/** @see TiDBSessionStore.readStagedEntries — 2c P1d-β staged-row inspection: the staged rows under `stagingId`, oldest-
|
|
499
|
+
* first, no session_meta probe (a staging id has no meta row). No longer the import gate (the per-line
|
|
500
|
+
* StreamingImportValidator validates during the stream); a tests/diagnostics seam. Returns [] for an empty/unknown id. */
|
|
389
501
|
async readStagedEntries(stagingId) {
|
|
390
502
|
const res = await this.pool.query("SELECT payload FROM session_event WHERE session_id = $1 ORDER BY seq ASC", [stagingId]);
|
|
391
503
|
return res.rows.map((r) => parseEntry(r.payload));
|
|
392
504
|
}
|
|
505
|
+
/** @see TiDBSessionStore.sweepStagingSessions — reaper for abandoned staging sessions: DELETE session_event rows
|
|
506
|
+
* under a `%#stg-%` id with NO session_meta whose oldest row is older than the grace window (in-flight stays fresh).
|
|
507
|
+
* Returns the affected row count. Driven by main.ts's reaper. */
|
|
393
508
|
async sweepStagingSessions() {
|
|
394
509
|
const cutoff = new Date(Date.now() - STAGING_GC_GRACE_MS);
|
|
395
510
|
const cands = await this.pool.query("SELECT se.session_id FROM session_event se LEFT JOIN session_meta sm ON sm.session_id = se.session_id " +
|
|
@@ -402,6 +517,9 @@ export class PgSessionStore {
|
|
|
402
517
|
}
|
|
403
518
|
return removed;
|
|
404
519
|
}
|
|
520
|
+
/** @see TiDBSessionStore.deleteSession — purge session_meta + session_event; idempotent (false if absent).
|
|
521
|
+
* Owner-guarded on session_meta.owner (`IS NOT DISTINCT FROM` = the PG `<=>`); the event purge is gated on the
|
|
522
|
+
* owner-matched meta delete (session_event has no owner column). */
|
|
405
523
|
async deleteSession(sessionId, owner) {
|
|
406
524
|
const conn = await this.pool.connect();
|
|
407
525
|
try {
|
|
@@ -417,6 +535,7 @@ export class PgSessionStore {
|
|
|
417
535
|
await conn.query("ROLLBACK");
|
|
418
536
|
}
|
|
419
537
|
catch {
|
|
538
|
+
/* ignore */
|
|
420
539
|
}
|
|
421
540
|
throw err;
|
|
422
541
|
}
|
|
@@ -431,18 +550,28 @@ export class PgSessionStore {
|
|
|
431
550
|
this.pending.clear();
|
|
432
551
|
}
|
|
433
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* 2c session-sync P1d-β — the PG twin of {@link TiDBStagingHandle} (behaviourally identical; only the SQL dialect
|
|
555
|
+
* differs: `$n` placeholders, `$n::jsonb`, `ON CONFLICT (session_id, entry_id) DO NOTHING`, rowCount, FOR UPDATE,
|
|
556
|
+
* BEGIN/COMMIT handle). Streams entry batches into a shadow session_event partition, then atomically RENAMEs them
|
|
557
|
+
* onto the real session id in ONE txn with session_meta LAST = the commit point.
|
|
558
|
+
*/
|
|
434
559
|
class PgStagingHandle {
|
|
435
560
|
pool;
|
|
436
561
|
realSessionId;
|
|
437
562
|
stagingId;
|
|
438
563
|
nextSeq = 0;
|
|
439
|
-
seen = new Set();
|
|
564
|
+
seen = new Set(); // staged ids (resume dedup → dense seqs); @see TiDBStagingHandle
|
|
440
565
|
constructor(pool, realSessionId, stagingId) {
|
|
441
566
|
this.pool = pool;
|
|
442
567
|
this.realSessionId = realSessionId;
|
|
443
568
|
this.stagingId = stagingId;
|
|
444
569
|
}
|
|
570
|
+
/** @see TiDBStagingHandle.appendBatch — DENSE re-seq (skip already-staged ids), idempotent ON CONFLICT (session_id,
|
|
571
|
+
* entry_id) DO NOTHING, one txn/batch. */
|
|
445
572
|
async appendBatch(entries) {
|
|
573
|
+
// ONE multi-row INSERT per batch (bounded) — @see TiDBStagingHandle.appendBatch. Build $-placeholders for the NEW
|
|
574
|
+
// rows (skip already-staged ids → dense seqs), ON CONFLICT (session_id, entry_id) DO NOTHING as the durable backstop.
|
|
446
575
|
const params = [];
|
|
447
576
|
const tuples = [];
|
|
448
577
|
for (const e of entries) {
|
|
@@ -450,7 +579,7 @@ class PgStagingHandle {
|
|
|
450
579
|
continue;
|
|
451
580
|
const b = params.length;
|
|
452
581
|
tuples.push(`($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6}::jsonb,$${b + 7})`);
|
|
453
|
-
params.push(this.stagingId, this.nextSeq, e.id, e.parentId ?? null, e.type, pgSafeJsonStringify(e), new Date());
|
|
582
|
+
params.push(this.stagingId, this.nextSeq, e.id, e.parentId ?? null, e.type, pgSafeJsonStringify(e), new Date() /* A-1: row-insert time, not source-history — reaper liveness; payload keeps e.timestamp */);
|
|
454
583
|
this.seen.add(e.id);
|
|
455
584
|
this.nextSeq++;
|
|
456
585
|
}
|
|
@@ -464,6 +593,8 @@ class PgStagingHandle {
|
|
|
464
593
|
throw err instanceof SessionError ? err : new SessionError("storage", `staged append for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
465
594
|
}
|
|
466
595
|
}
|
|
596
|
+
/** @see TiDBStagingHandle.commit — the atomic swap: in-commit re-classify (FOR UPDATE) → purge dst → rename
|
|
597
|
+
* staging→real → session_meta LAST. Throws SyncConflictError on an unconsented fork/stale flip. */
|
|
467
598
|
async commit(owner, opts) {
|
|
468
599
|
const conn = await this.pool.connect();
|
|
469
600
|
try {
|
|
@@ -494,7 +625,7 @@ class PgStagingHandle {
|
|
|
494
625
|
try {
|
|
495
626
|
await conn.query("ROLLBACK");
|
|
496
627
|
}
|
|
497
|
-
catch { }
|
|
628
|
+
catch { /* ignore */ }
|
|
498
629
|
if (err instanceof SyncConflictError || err instanceof SessionError)
|
|
499
630
|
throw err;
|
|
500
631
|
throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
@@ -503,6 +634,7 @@ class PgStagingHandle {
|
|
|
503
634
|
conn.release();
|
|
504
635
|
}
|
|
505
636
|
}
|
|
637
|
+
/** @see TiDBStagingHandle.abort — DELETE the staging rows. Idempotent. */
|
|
506
638
|
async abort() {
|
|
507
639
|
await this.pool.query("DELETE FROM session_event WHERE session_id = $1", [this.stagingId]);
|
|
508
640
|
}
|