@sema-agent/server 1.323.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 +58 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
- package/dist/plugins/workflow-run-store-sql.js +84 -6
- package/dist/plugins/worktree-isolation.d.ts +45 -0
- package/dist/plugins/worktree-isolation.js +105 -6
- package/dist/plugins/write-behind-counter.d.ts +56 -1
- package/dist/plugins/write-behind-counter.js +53 -14
- package/dist/principal-jwt.d.ts +24 -0
- package/dist/principal-jwt.js +60 -5
- package/dist/project-memory.d.ts +22 -0
- package/dist/project-memory.js +144 -15
- package/dist/prompts-domain-validate.d.ts +28 -0
- package/dist/prompts-domain-validate.js +42 -1
- package/dist/question.d.ts +33 -0
- package/dist/question.js +69 -2
- package/dist/resource-suspend.d.ts +21 -0
- package/dist/resource-suspend.js +16 -0
- package/dist/router/route-orchestration.d.ts +78 -0
- package/dist/router/route-orchestration.js +77 -0
- package/dist/run-local.d.ts +32 -0
- package/dist/run-local.js +172 -8
- package/dist/runs.d.ts +189 -4
- package/dist/runs.js +465 -24
- package/dist/runtime-caps-resolver.d.ts +106 -0
- package/dist/runtime-caps-resolver.js +89 -4
- package/dist/runtime-governance.d.ts +84 -0
- package/dist/runtime-governance.js +127 -1
- package/dist/sandbox-pkg-source.d.ts +23 -0
- package/dist/sandbox-pkg-source.js +37 -0
- package/dist/sealed-key.d.ts +54 -0
- package/dist/sealed-key.js +68 -3
- package/dist/security.d.ts +108 -0
- package/dist/security.js +145 -6
- package/dist/sema-registry.d.ts +21 -0
- package/dist/sema-registry.js +21 -0
- package/dist/session-leaf-bus.d.ts +31 -0
- package/dist/session-leaf-bus.js +32 -0
- package/dist/session-sync-content.d.ts +46 -0
- package/dist/session-sync-content.js +47 -1
- package/dist/session-sync-kernel.d.ts +89 -0
- package/dist/session-sync-kernel.js +48 -3
- package/dist/session-sync.d.ts +125 -0
- package/dist/session-sync.js +181 -8
- package/dist/session-titler.d.ts +26 -0
- package/dist/session-titler.js +41 -7
- package/dist/session-watch.d.ts +53 -1
- package/dist/session-watch.js +73 -9
- package/dist/sighup-idle.d.ts +30 -0
- package/dist/sighup-idle.js +12 -1
- package/dist/spec-fields.d.ts +91 -3
- package/dist/spec-fields.js +116 -4
- package/dist/task-cwd.d.ts +50 -0
- package/dist/task-cwd.js +68 -1
- package/dist/task-mcp.d.ts +47 -0
- package/dist/task-mcp.js +29 -0
- package/dist/task-settings.d.ts +175 -0
- package/dist/task-settings.js +226 -11
- package/dist/task-workflow.d.ts +67 -0
- package/dist/task-workflow.js +75 -8
- package/dist/tool-approval.d.ts +91 -0
- package/dist/tool-approval.js +215 -0
- package/dist/trace/artifacts.d.ts +9 -0
- package/dist/trace/artifacts.js +30 -5
- package/dist/trace/core-keyset-guard.d.ts +18 -0
- package/dist/trace/ledger-sink.d.ts +43 -0
- package/dist/trace/ledger-sink.js +64 -5
- package/dist/trace/project.d.ts +133 -0
- package/dist/trace/project.js +189 -2
- package/dist/trace/redact.d.ts +19 -0
- package/dist/trace/redact.js +66 -9
- package/dist/usage-analytics.d.ts +26 -0
- package/dist/usage-analytics.js +26 -3
- package/package.json +1 -1
|
@@ -1,39 +1,84 @@
|
|
|
1
|
+
/** The staging-id format: a real session id with a `#stg-${token}` suffix. The `#` is illegal in a uuidv7, so a
|
|
2
|
+
* staging id can never collide with a real session id (the orphan invariant) and is recognizable for the reaper. */
|
|
1
3
|
export function stagingIdFor(realSessionId, token) {
|
|
2
4
|
return `${realSessionId}#stg-${token}`;
|
|
3
5
|
}
|
|
6
|
+
/** The substring marker every staging id carries (the reaper's `LIKE '%#stg-%'` selector). */
|
|
4
7
|
export const STAGING_ID_MARKER = "#stg-";
|
|
8
|
+
/** Recover the real session id from a staging id (everything before the `#stg-` marker), or null if not a staging id. */
|
|
5
9
|
export function realIdOfStaging(stagingId) {
|
|
6
10
|
const i = stagingId.indexOf(STAGING_ID_MARKER);
|
|
7
11
|
return i < 0 ? null : stagingId.slice(0, i);
|
|
8
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* §7 — classify how importing `srcEntries` into a destination holding `dstEntries` would relate the two logs. PURE
|
|
15
|
+
* (no I/O); the §7 fork detector. The comparison is over the ENTRY-ID SETS, not leaf_id:
|
|
16
|
+
*
|
|
17
|
+
* Entry ids are copied VERBATIM across backends (fork/import re-seq only `seq`; id/parentId/payload are byte-identical
|
|
18
|
+
* — see tidb-session-store fork/importEntries), so a set comparison of ids is the SOUND divergence test §7 demands. It
|
|
19
|
+
* fixes v1's leaf_id-only check, which was unsound BOTH ways:
|
|
20
|
+
* - FALSE NEGATIVE (the dangerous one): a cross-end rewind moves `LeafEntry.targetId` back onto a shared OLD entry id,
|
|
21
|
+
* so two genuinely-forked logs report the SAME leaf_id → v1 would silently overwrite = data loss (exactly the 2c
|
|
22
|
+
* use case). Set comparison sees each side's exclusive ids and reports `fork`.
|
|
23
|
+
* - FALSE POSITIVE: a clean fast-forward ends on a NEW leaf id ≠ the dst's leaf → v1 flags a (non-)conflict. Set
|
|
24
|
+
* comparison sees dst ⊊ src and reports `fast_forward` (safe).
|
|
25
|
+
*
|
|
26
|
+
* `commonAncestor` for a fork = the entry id present in BOTH logs that appears LAST in `srcEntries` oldest-first order
|
|
27
|
+
* (the deepest shared appended entry = the divergence point), or `null` if the logs share no entry at all.
|
|
28
|
+
*/
|
|
9
29
|
export function classifySyncRelationship(srcEntries, dstEntries) {
|
|
30
|
+
// Map entries → their ids (the only thing the comparison reads) and delegate to the id-set classifier — ONE source
|
|
31
|
+
// of the §7 relation logic. (P1d's HTTP plan route, where one peer is REMOTE and sends only an entry-id LIST, calls
|
|
32
|
+
// classifySyncRelationshipByIds DIRECTLY; this entries-typed entry point is the same logic with a nicer call shape.)
|
|
10
33
|
return classifySyncRelationshipByIds(srcEntries.map((e) => e.id), dstEntries === null ? null : dstEntries.map((e) => e.id));
|
|
11
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* §7 — the SAME relation classifier as {@link classifySyncRelationship}, over entry-id LISTS (oldest-first) instead of
|
|
37
|
+
* full entries. This is the canonical implementation the entries-typed wrapper delegates to. It exists in its own right
|
|
38
|
+
* for the P1d HTTP `/sync/plan` route: the two peers are different PROCESSES (the local shell + the cloud), so the
|
|
39
|
+
* planning peer holds only the OTHER end's entry-id list (not its `SessionTreeEntry[]`) — the relation is decidable from
|
|
40
|
+
* the ids alone (ids are copied VERBATIM across backends, so a set comparison of ids is the sound divergence test §7
|
|
41
|
+
* demands; see the wrapper's doc-comment for why leaf_id was unsound). `srcIds`/`dstIds` are OLDEST-FIRST so the
|
|
42
|
+
* fork `commonAncestor` (the deepest shared id) and the ordered exclusive/new lists are derived correctly.
|
|
43
|
+
*/
|
|
12
44
|
export function classifySyncRelationshipByIds(srcIds, dstIds) {
|
|
45
|
+
// dst absent OR empty ⇒ nothing at the destination to relate to / lose → an unconditional fresh import.
|
|
13
46
|
if (dstIds === null || dstIds.length === 0)
|
|
14
47
|
return { relation: "fresh" };
|
|
15
48
|
const srcSet = new Set(srcIds);
|
|
16
49
|
const dstSet = new Set(dstIds);
|
|
50
|
+
// identical: same cardinality AND every src id is in dst (⟹ the sets are equal) → already present & equal (no-op).
|
|
17
51
|
if (srcSet.size === dstSet.size && [...srcSet].every((id) => dstSet.has(id)))
|
|
18
52
|
return { relation: "identical" };
|
|
19
|
-
const allDstInSrc = [...dstSet].every((id) => srcSet.has(id));
|
|
20
|
-
const allSrcInDst = [...srcSet].every((id) => dstSet.has(id));
|
|
53
|
+
const allDstInSrc = [...dstSet].every((id) => srcSet.has(id)); // dst ⊆ src
|
|
54
|
+
const allSrcInDst = [...srcSet].every((id) => dstSet.has(id)); // src ⊆ dst
|
|
55
|
+
// fast_forward: dst ⊊ src (src strictly ahead — a clean append) → safe to apply. newEntryIds = the src ids the dst
|
|
56
|
+
// lacks, in SRC oldest-first order (the appended tail), so the caller can surface "what would be added".
|
|
21
57
|
if (allDstInSrc && !allSrcInDst) {
|
|
22
58
|
const newEntryIds = srcIds.filter((id) => !dstSet.has(id));
|
|
23
59
|
return { relation: "fast_forward", newEntryIds };
|
|
24
60
|
}
|
|
61
|
+
// stale: src ⊊ dst (src strictly behind) → applying would DROP the dst entries src never saw = data loss. dstAheadBy
|
|
62
|
+
// = the dst ids src lacks (dst order). Refused as a conflict unless the caller explicitly resolves overwrite-dst.
|
|
25
63
|
if (allSrcInDst && !allDstInSrc) {
|
|
26
64
|
const dstAheadBy = dstIds.filter((id) => !srcSet.has(id));
|
|
27
65
|
return { relation: "stale", dstAheadBy };
|
|
28
66
|
}
|
|
67
|
+
// fork: neither set contains the other ⇒ each side has ≥1 exclusive entry (true divergence). The common ancestor is
|
|
68
|
+
// the deepest shared entry = the LAST id in src oldest-first order that the dst also holds (null if no shared id).
|
|
29
69
|
const srcExclusive = srcIds.filter((id) => !dstSet.has(id));
|
|
30
70
|
const dstExclusive = dstIds.filter((id) => !srcSet.has(id));
|
|
31
71
|
let commonAncestor = null;
|
|
32
72
|
for (const id of srcIds)
|
|
33
73
|
if (dstSet.has(id))
|
|
34
|
-
commonAncestor = id;
|
|
74
|
+
commonAncestor = id; // last shared id in src oldest-first order
|
|
35
75
|
return { relation: "fork", commonAncestor, srcExclusive, dstExclusive };
|
|
36
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* §7 — a typed conflict the route maps to HTTP 409: an import that WOULD lose destination history (a `fork` or a
|
|
79
|
+
* `stale` source) and the caller did NOT pass an explicit `{ resolution: "overwrite-dst" }`. Carries the classified
|
|
80
|
+
* relation so the route/shell can surface the exclusive entry sets to the user for a keep-local / keep-cloud decision.
|
|
81
|
+
*/
|
|
37
82
|
export class SyncConflictError extends Error {
|
|
38
83
|
sessionId;
|
|
39
84
|
relation;
|
package/dist/session-sync.d.ts
CHANGED
|
@@ -1,47 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 2c session-sync — P1b ORCHESTRATION: export a session's WHOLE portable state from one StoreBackend and import it
|
|
3
|
+
* verbatim into another (the seamless local↔cloud "save state" move; docs/DESIGN-session-sync.md §5–§10). All the
|
|
4
|
+
* per-store PRIMITIVES already exist (P1a entries + P1b snapshots/policy/anchor); this module is the COORDINATOR that
|
|
5
|
+
* (a) reads them into a {@link SessionBundle} and (b) replays them across backends in the §8 fail-closed atomic order.
|
|
6
|
+
*
|
|
7
|
+
* This is a SERVICE function — the caller (the future HTTP route, P1d) owns the `sessionOwnerScope` gate (§9: export
|
|
8
|
+
* 404s a non-owner, import re-stamps owner to the authenticated principal). exportSession/importSession themselves do
|
|
9
|
+
* NOT authenticate; they assume the caller proved ownership.
|
|
10
|
+
*
|
|
11
|
+
* Scope (P1b): a one-shot whole-session move with FAIL-CLOSED validation (§10) + cross-store atomic ordering (§8) +
|
|
12
|
+
* owner re-stamp (§9). NOT in scope here: sync-watermark / fork detection (§7) and HTTP/streaming blob negotiation
|
|
13
|
+
* (§5) — those are P1c/P1d. Blobs are NOT held in memory (§5 OOM guard): the bundle carries only the manifest
|
|
14
|
+
* (relPath→blobHash); bytes flow through a `getBlob(hash)` getter the importer pulls content-addressed (deduped).
|
|
15
|
+
*/
|
|
1
16
|
import type { StoreBackend } from "./plugins/store-backend.js";
|
|
2
17
|
import { type SyncRelation } from "./session-sync-kernel.js";
|
|
3
18
|
import type { SessionTreeEntry, SessionRulesRecord } from "@sema-agent/core";
|
|
19
|
+
/** A blob getter: content-addressed bytes for a sha256 hash, or `undefined` if the source can't supply them.
|
|
20
|
+
* Returned by {@link exportSession} (closes over the source store), consumed by {@link importSession}. */
|
|
4
21
|
export type BlobGetter = (hash: string) => Promise<Uint8Array | undefined>;
|
|
5
22
|
export { stagingIdFor, STAGING_ID_MARKER, realIdOfStaging, classifySyncRelationship, classifySyncRelationshipByIds, SyncConflictError } from "./session-sync-kernel.js";
|
|
6
23
|
export type { StagingHandle, ImportStagingStore, SyncRelation } from "./session-sync-kernel.js";
|
|
24
|
+
/**
|
|
25
|
+
* The portable state of ONE session, ready to replay into another backend. Blobs are deliberately NOT inlined
|
|
26
|
+
* (§5: a turn's working-tree blobs can be tens of MiB → OOM/413 if buffered); each snapshot carries only its
|
|
27
|
+
* `manifest` (relPath → blobHash) and the bytes are streamed through the paired {@link BlobGetter}.
|
|
28
|
+
*/
|
|
7
29
|
export interface SessionBundle {
|
|
8
30
|
sessionId: string;
|
|
31
|
+
/** The FULL durable conversation log (compaction floor bypassed), verbatim ids/parents/payload — `exportEntries`. */
|
|
9
32
|
entries: SessionTreeEntry[];
|
|
33
|
+
/** One per E19 file snapshot (keyed by `SessionTreeEntry.id` = `key`); manifest = [relPath, blobHash] tuples. */
|
|
10
34
|
snapshots: Array<{
|
|
11
35
|
key: string;
|
|
12
36
|
manifest: Array<[string, string]>;
|
|
13
37
|
}>;
|
|
38
|
+
/** Every (principal, rules) record for the session, across ALL principals (E6 `listBySession`). */
|
|
14
39
|
policy: SessionRulesRecord[];
|
|
40
|
+
/** Every E18 resume-at anchor (eventId→entryId + its source owner; owner is RE-KEYED on import, §9). */
|
|
15
41
|
anchors: Array<{
|
|
16
42
|
eventId: string;
|
|
17
43
|
entryId: string;
|
|
18
44
|
owner: string | null;
|
|
19
45
|
}>;
|
|
20
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* P1d-α (PULL streaming, §1.5/§5/§15) — a {@link SessionBundle} with the ENTRIES LIFTED OFF THE WIRE: the HTTP PULL
|
|
49
|
+
* `GET /sync/manifest` returns THIS (small, bounded), and the entries flow separately through the NDJSON
|
|
50
|
+
* `GET /sync/entries` stream (bounded memory regardless of session size — the §1.5 ruling: entries are small text
|
|
51
|
+
* rows, so streaming is about ROW COUNT, not blob size). It carries the entries' IDS (`entryIds`, oldest-first — the
|
|
52
|
+
* local peer feeds these to {@link classifySyncRelationshipByIds} to decide fast-forward / fork BEFORE pulling the
|
|
53
|
+
* stream) + the count + the current leaf, and the snapshots/policy/anchors EXACTLY as {@link SessionBundle} does.
|
|
54
|
+
*/
|
|
21
55
|
export interface SessionManifest {
|
|
22
56
|
sessionId: string;
|
|
57
|
+
/** The full durable log's entry IDS, oldest-first (verbatim, cross-backend stable) — NOT the entry payloads. */
|
|
23
58
|
entryIds: string[];
|
|
59
|
+
/** `entryIds.length` — the number of entries the paired `/sync/entries` stream will yield (the trailer's count). */
|
|
24
60
|
entryCount: number;
|
|
61
|
+
/** The session's current leaf `SessionTreeEntry.id` (session_meta.leaf_id), or null if it has none. */
|
|
25
62
|
leafId: string | null;
|
|
63
|
+
/** One per E19 file snapshot (keyed by entry id); manifest = [relPath, blobHash] tuples. EXACTLY as the bundle. */
|
|
26
64
|
snapshots: Array<{
|
|
27
65
|
key: string;
|
|
28
66
|
manifest: Array<[string, string]>;
|
|
29
67
|
}>;
|
|
68
|
+
/** Every (principal, rules) record across ALL principals (E6 `listBySession`). EXACTLY as the bundle. */
|
|
30
69
|
policy: SessionRulesRecord[];
|
|
70
|
+
/** Every E18 resume-at anchor (eventId→entryId + source owner; RE-KEYED on import, §9). EXACTLY as the bundle. */
|
|
31
71
|
anchors: Array<{
|
|
32
72
|
eventId: string;
|
|
33
73
|
entryId: string;
|
|
34
74
|
owner: string | null;
|
|
35
75
|
}>;
|
|
36
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* EXPORT a session's whole portable state from `srcBackend` into a {@link SessionBundle} + a {@link BlobGetter}.
|
|
79
|
+
*
|
|
80
|
+
* - entries: `exportEntries(sessionId)` — `null` ⇒ the session does not exist ⇒ this returns `null` (no partial bundle).
|
|
81
|
+
* - snapshots: `listKeys(sessionId)` → for each key `exportManifest(scope,key)` → `{key, [...manifest]}`. A key whose
|
|
82
|
+
* manifest reads back `null` (a concurrent reap between listKeys and exportManifest) is dropped, not exported empty.
|
|
83
|
+
* - policy: `listBySession(sessionId)` (empty `[]` if the store lacks the optional seam — honest degrade).
|
|
84
|
+
* - anchors: `listBySession(sessionId)` (empty `[]` if the store has no resumeAnchor seam).
|
|
85
|
+
* - getBlob: bound to the source `fileSnapshot().getBlob` so the importer can pull content-addressed bytes lazily.
|
|
86
|
+
*
|
|
87
|
+
* The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
|
|
88
|
+
* `null` ONLY for "session does not exist", never as an authz signal.
|
|
89
|
+
*
|
|
90
|
+
* NB (P1d-α): the HTTP PULL no longer uses this — it uses {@link exportSessionManifest} (entries lifted off the wire)
|
|
91
|
+
* + the keyset-paged `exportEntriesStream` NDJSON route so memory is bounded by ROW COUNT, not the whole-log array.
|
|
92
|
+
* exportSession (entries-inline) STAYS for the local same-process import path, P1d-β (PUSH), and the tests.
|
|
93
|
+
*/
|
|
37
94
|
export declare function exportSession(sessionId: string, srcBackend: StoreBackend): Promise<{
|
|
38
95
|
bundle: SessionBundle;
|
|
39
96
|
getBlob: BlobGetter;
|
|
40
97
|
} | null>;
|
|
98
|
+
/**
|
|
99
|
+
* P1d-α (PULL streaming, §15) — EXPORT a session's portable state as a {@link SessionManifest} (entries LIFTED OFF
|
|
100
|
+
* the wire) + a {@link BlobGetter}. Like {@link exportSession} but it does NOT load entry payloads: it reads the
|
|
101
|
+
* IDS-only projection via `listEntryIds` (`null` ⇒ no such session ⇒ this returns `null`), counts them, reads the
|
|
102
|
+
* current `leafId`, and builds snapshots/policy/anchors EXACTLY as exportSession does. The paired NDJSON
|
|
103
|
+
* `GET /sync/entries` route streams the entry payloads separately (keyset-paged → bounded memory), so a 1M-entry
|
|
104
|
+
* session's manifest stays small (ids + counts + the snapshot/policy/anchor metadata).
|
|
105
|
+
*
|
|
106
|
+
* The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
|
|
107
|
+
* `null` ONLY for "session does not exist". (Named exportSessionManifest, NOT exportManifest, to avoid the collision
|
|
108
|
+
* with `FileSnapshotStore.exportManifest`.)
|
|
109
|
+
*/
|
|
41
110
|
export declare function exportSessionManifest(sessionId: string, srcBackend: StoreBackend): Promise<{
|
|
42
111
|
manifest: SessionManifest;
|
|
43
112
|
getBlob: BlobGetter;
|
|
44
113
|
} | null>;
|
|
114
|
+
/**
|
|
115
|
+
* IMPORT a {@link SessionBundle} (+ its {@link BlobGetter}) into `dstBackend` under `bundle.sessionId`, re-stamping
|
|
116
|
+
* ownership to `importingPrincipal`. Returns the snapshot tally (imported vs honestly-skipped on a destination whose
|
|
117
|
+
* FileSnapshotStore lacks the importManifest seam — pre-1.141.0; core's InMemory/File NOW carry it, so a local dst
|
|
118
|
+
* imports snapshots for real).
|
|
119
|
+
*
|
|
120
|
+
* §10 FAIL-CLOSED VALIDATION runs FIRST — BEFORE any write:
|
|
121
|
+
* 1. `validateEntriesForImport(bundle.entries)` (the single core invariant gate: unique ids / parent-before-child /
|
|
122
|
+
* one root / leaf-resolvable). Throws on violation; NOTHING is written.
|
|
123
|
+
* 2. Every blobHash referenced by every snapshot manifest MUST be supplied by `getBlob` — a missing blob would
|
|
124
|
+
* restore a PARTIAL tree (files half-written, conversation whole → breaks the entry↔file lockstep). A lightweight
|
|
125
|
+
* PRESENCE pre-check (fetch each distinct hash once, no hashing) fails FAST here; the AUTHORITATIVE content-address
|
|
126
|
+
* integrity check (`sha256(bytes)===hash`) is done ONCE inside `importManifest` (core 1.141.0), so we don't double-
|
|
127
|
+
* hash. (Runs only when the dst supports snapshot import; otherwise there is nothing to half-write.)
|
|
128
|
+
*
|
|
129
|
+
* §7 CLASSIFICATION GATE — BEFORE any write, the destination's CURRENT entry log is read and compared to the bundle
|
|
130
|
+
* via {@link classifySyncRelationship} (a sound entry-id SET comparison, not leaf_id). A `fork` (true divergence) or a
|
|
131
|
+
* `stale` source (the dst is strictly ahead) WOULD lose destination history → it throws {@link SyncConflictError}
|
|
132
|
+
* (route → 409) UNLESS the caller passes `{ resolution: "overwrite-dst" }`. An `identical` relation SKIPS the entries
|
|
133
|
+
* write entirely (the log is already present & equal; the idempotent snapshot/policy/anchor replay below still runs to
|
|
134
|
+
* heal anything missing). `fresh` / `fast_forward` (and an overwrite-resolved fork/stale) write the entries via the
|
|
135
|
+
* IDEMPOTENT {@link OwnerAwareSessionStore.replaceEntries} (purge-then-import) — NOT `importEntries`, whose plain
|
|
136
|
+
* INSERT crashes on a duplicate PK when the session already exists at the destination.
|
|
137
|
+
*
|
|
138
|
+
* §8 CROSS-STORE ATOMIC ORDER + IDEMPOTENCY — session_meta (written LAST inside `replaceEntries`) is the sole
|
|
139
|
+
* commit point, so a crash mid-import leaves orphan blobs/manifests/anchors with NO session_meta = invisible to
|
|
140
|
+
* wake (never a half-session, only collectable orphans). Order:
|
|
141
|
+
* ① snapshots (manifests + content-addressed blobs) — content-addressed + create-once = idempotent retry-to-completion.
|
|
142
|
+
* `importManifest` returns a {@link FileSnapshotResult} (NEVER throws — core 1.141.0); a `{ok:false}` (a
|
|
143
|
+
* rejecting/missing/hash-mismatched blob the §10 pre-check didn't catch — e.g. a blob corrupted in transit) is
|
|
144
|
+
* re-raised as a fail-closed THROW so the import aborts BEFORE the entries commit (no entry↔file split).
|
|
145
|
+
* ② entries (`replaceEntries` runs the core gate AGAIN and writes session_meta LAST = the commit point; idempotent
|
|
146
|
+
* over an existing session — skipped entirely when the relation is `identical`)
|
|
147
|
+
* ③ policy (replay each record via putRules; `opts.operatorOk` defaults FALSE → the E6 tighten-only gate APPLIES, so
|
|
148
|
+
* a replay that would LOOSEN an operator-tightened dst policy throws `SessionPolicyError("loosen_forbidden")` —
|
|
149
|
+
* `operator:true` is set ONLY when the authenticated importer is itself a verified operator)
|
|
150
|
+
* ④ anchors (owner RE-KEYED to `importingPrincipal`, §9)
|
|
151
|
+
* Steps ③/④ are post-commit; re-running them is idempotent — ③ SKIPS a put whose content already equals the dst row
|
|
152
|
+
* (a "no-op rev bump" is NOT idempotent: rev is the operator's optimistic-lock token — the A6 fix below), ④ is a
|
|
153
|
+
* same-value upsert — so a crash between ②–④ is recovered by a retry that converges to the same bytes.
|
|
154
|
+
*
|
|
155
|
+
* @param opts.resolution — `"overwrite-dst"` consents to a `fork`/`stale` import that overwrites destination history
|
|
156
|
+
* (the user's keep-source decision); omitted ⇒ such an import is refused with {@link SyncConflictError}.
|
|
157
|
+
* @param opts.operatorOk — when `true`, the policy replay (step ③) is an OPERATOR write (may loosen the dst rules); set
|
|
158
|
+
* ONLY when the AUTHENTICATED importer is a verified operator. DEFAULT false → the E6 tighten-only gate applies, so a
|
|
159
|
+
* non-operator import is TIGHTEN-ONLY (design §4): a replay that would loosen an operator-tightened dst session policy
|
|
160
|
+
* throws `SessionPolicyError("loosen_forbidden")`, which propagates (NOT swallowed) so the route can refuse the loosen.
|
|
161
|
+
*/
|
|
45
162
|
export declare function importSession(bundle: SessionBundle, getBlob: BlobGetter, dstBackend: StoreBackend, importingPrincipal: string | null, opts?: {
|
|
46
163
|
resolution?: "overwrite-dst";
|
|
47
164
|
operatorOk?: boolean;
|
|
@@ -50,5 +167,13 @@ export declare function importSession(bundle: SessionBundle, getBlob: BlobGetter
|
|
|
50
167
|
snapshotsSkipped: number;
|
|
51
168
|
relation: SyncRelation["relation"];
|
|
52
169
|
}>;
|
|
170
|
+
/**
|
|
171
|
+
* §7 DRY-RUN — classify what importing `sessionId` from `srcBackend` into `dstBackend` WOULD do, WITHOUT writing
|
|
172
|
+
* anything. The route (P1d) calls this to surface a conflict (fork/stale) to the user BEFORE committing an
|
|
173
|
+
* {@link importSession} (so the shell can present a keep-local / keep-cloud / fork-new choice up front).
|
|
174
|
+
*
|
|
175
|
+
* Returns `null` when the SOURCE session does not exist (no bundle to plan); otherwise the {@link SyncRelation}
|
|
176
|
+
* between the source's full durable log and the destination's current log (a `null` dst log ⇒ `fresh`). No writes.
|
|
177
|
+
*/
|
|
53
178
|
export declare function planSync(sessionId: string, srcBackend: StoreBackend, dstBackend: StoreBackend): Promise<SyncRelation | null>;
|
|
54
179
|
//# sourceMappingURL=session-sync.d.ts.map
|
package/dist/session-sync.js
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
1
|
import { identicalIdsAlsoIdenticalContent } from "./session-sync-content.js";
|
|
2
2
|
import { classifySyncRelationship, SyncConflictError } from "./session-sync-kernel.js";
|
|
3
3
|
import { validateEntriesForImport, normalizeRules, stripRev } from "@sema-agent/core";
|
|
4
|
+
// ── PURE KERNEL moved to ./session-sync-kernel.ts (design/158 A2) ─────────────────────────────────────────────
|
|
5
|
+
// The §7 relation classifier (classifySyncRelationship[ByIds]), the staging-id scheme (stagingIdFor /
|
|
6
|
+
// STAGING_ID_MARKER / realIdOfStaging), the StagingHandle / ImportStagingStore contracts and SyncConflictError
|
|
7
|
+
// lived here (lines ~26–187) but depend on NOTHING in this repo — while THIS module value-imports StoreBackend and
|
|
8
|
+
// type-imports security's OwnerAwareSessionStore. The three DB session adapters (local/pg/tidb) and security.ts
|
|
9
|
+
// need only those pure symbols, and security.ts importing them from HERE formed the repo's only true type cycle
|
|
10
|
+
// (security ↔ session-sync; lens2 §B/§E). They now live in the leaf module session-sync-kernel.ts (same shape as
|
|
11
|
+
// session-sync-content.ts) and are RE-EXPORTED verbatim below, so existing importers of this module keep working.
|
|
4
12
|
export { stagingIdFor, STAGING_ID_MARKER, realIdOfStaging, classifySyncRelationship, classifySyncRelationshipByIds, SyncConflictError } from "./session-sync-kernel.js";
|
|
13
|
+
/** Narrow `backend.session()` to the optional ownership/export seam (the concrete TiDB/PG/Local stores carry it;
|
|
14
|
+
* the bare in-memory dev store doesn't → the methods are optional, exactly like the fork route narrows). */
|
|
5
15
|
function ownerAware(backend) {
|
|
6
16
|
return backend.session();
|
|
7
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* EXPORT a session's whole portable state from `srcBackend` into a {@link SessionBundle} + a {@link BlobGetter}.
|
|
20
|
+
*
|
|
21
|
+
* - entries: `exportEntries(sessionId)` — `null` ⇒ the session does not exist ⇒ this returns `null` (no partial bundle).
|
|
22
|
+
* - snapshots: `listKeys(sessionId)` → for each key `exportManifest(scope,key)` → `{key, [...manifest]}`. A key whose
|
|
23
|
+
* manifest reads back `null` (a concurrent reap between listKeys and exportManifest) is dropped, not exported empty.
|
|
24
|
+
* - policy: `listBySession(sessionId)` (empty `[]` if the store lacks the optional seam — honest degrade).
|
|
25
|
+
* - anchors: `listBySession(sessionId)` (empty `[]` if the store has no resumeAnchor seam).
|
|
26
|
+
* - getBlob: bound to the source `fileSnapshot().getBlob` so the importer can pull content-addressed bytes lazily.
|
|
27
|
+
*
|
|
28
|
+
* The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
|
|
29
|
+
* `null` ONLY for "session does not exist", never as an authz signal.
|
|
30
|
+
*
|
|
31
|
+
* NB (P1d-α): the HTTP PULL no longer uses this — it uses {@link exportSessionManifest} (entries lifted off the wire)
|
|
32
|
+
* + the keyset-paged `exportEntriesStream` NDJSON route so memory is bounded by ROW COUNT, not the whole-log array.
|
|
33
|
+
* exportSession (entries-inline) STAYS for the local same-process import path, P1d-β (PUSH), and the tests.
|
|
34
|
+
*/
|
|
8
35
|
export async function exportSession(sessionId, srcBackend) {
|
|
9
36
|
const session = ownerAware(srcBackend);
|
|
37
|
+
// entries FIRST — null ⇒ no such session ⇒ no bundle at all (the route maps this to 404).
|
|
10
38
|
const exportEntries = session.exportEntries?.bind(session);
|
|
11
39
|
if (!exportEntries) {
|
|
40
|
+
// A backend whose session store lacks the export seam can't be a sync SOURCE (older/in-memory dev store). Treat as
|
|
41
|
+
// "nothing to export" rather than crash — the route already gates on the durable backend being present.
|
|
12
42
|
return null;
|
|
13
43
|
}
|
|
14
44
|
const entries = await exportEntries(sessionId);
|
|
15
45
|
if (entries === null)
|
|
16
|
-
return null;
|
|
46
|
+
return null; // session does not exist
|
|
47
|
+
// snapshots — listKeys → per-key manifest. exportManifest may be absent (no snapshot export seam) → no snapshots.
|
|
17
48
|
const fs = srcBackend.fileSnapshot();
|
|
18
49
|
const snapshots = [];
|
|
19
50
|
if (typeof fs.listKeys === "function" && typeof fs.exportManifest === "function") {
|
|
@@ -21,32 +52,53 @@ export async function exportSession(sessionId, srcBackend) {
|
|
|
21
52
|
for (const key of keys) {
|
|
22
53
|
const manifest = await fs.exportManifest(sessionId, key);
|
|
23
54
|
if (manifest === null)
|
|
24
|
-
continue;
|
|
55
|
+
continue; // raced reap between listKeys and exportManifest — skip (never an empty snapshot)
|
|
25
56
|
snapshots.push({ key, manifest: [...manifest] });
|
|
26
57
|
}
|
|
27
58
|
}
|
|
59
|
+
// policy — every (principal, rules) record across ALL principals (E6 listBySession; optional → []).
|
|
28
60
|
const policyStore = srcBackend.sessionPolicy();
|
|
29
61
|
const policy = typeof policyStore.listBySession === "function"
|
|
30
62
|
? await policyStore.listBySession(sessionId)
|
|
31
63
|
: [];
|
|
64
|
+
// anchors — every E18 resume anchor (eventId→entryId + source owner). resumeAnchor is REQUIRED on all backends.
|
|
32
65
|
const anchorStore = srcBackend.resumeAnchor();
|
|
33
66
|
const anchors = typeof anchorStore.listBySession === "function"
|
|
34
67
|
? await anchorStore.listBySession(sessionId)
|
|
35
68
|
: [];
|
|
69
|
+
// getBlob — bound to the SOURCE snapshot store so the importer pulls bytes content-addressed (deduped), lazily.
|
|
70
|
+
// A source without getBlob (no blobs to move) supplies a getter that always returns undefined → snapshots with a
|
|
71
|
+
// missing blob fail the §10 validation below before any write (never a partial restore).
|
|
36
72
|
const getBlobFn = fs.getBlob?.bind(fs);
|
|
37
73
|
const getBlob = getBlobFn ? (hash) => getBlobFn(hash) : async () => undefined;
|
|
38
74
|
return { bundle: { sessionId, entries, snapshots, policy, anchors }, getBlob };
|
|
39
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* P1d-α (PULL streaming, §15) — EXPORT a session's portable state as a {@link SessionManifest} (entries LIFTED OFF
|
|
78
|
+
* the wire) + a {@link BlobGetter}. Like {@link exportSession} but it does NOT load entry payloads: it reads the
|
|
79
|
+
* IDS-only projection via `listEntryIds` (`null` ⇒ no such session ⇒ this returns `null`), counts them, reads the
|
|
80
|
+
* current `leafId`, and builds snapshots/policy/anchors EXACTLY as exportSession does. The paired NDJSON
|
|
81
|
+
* `GET /sync/entries` route streams the entry payloads separately (keyset-paged → bounded memory), so a 1M-entry
|
|
82
|
+
* session's manifest stays small (ids + counts + the snapshot/policy/anchor metadata).
|
|
83
|
+
*
|
|
84
|
+
* The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
|
|
85
|
+
* `null` ONLY for "session does not exist". (Named exportSessionManifest, NOT exportManifest, to avoid the collision
|
|
86
|
+
* with `FileSnapshotStore.exportManifest`.)
|
|
87
|
+
*/
|
|
40
88
|
export async function exportSessionManifest(sessionId, srcBackend) {
|
|
41
89
|
const session = ownerAware(srcBackend);
|
|
90
|
+
// entry IDS FIRST — null ⇒ no such session ⇒ no manifest (the route maps this to 404). A backend whose session store
|
|
91
|
+
// lacks the seam can't be a sync source (older/in-memory dev store) → treat as nothing to export.
|
|
42
92
|
const listEntryIds = session.listEntryIds?.bind(session);
|
|
43
93
|
if (!listEntryIds)
|
|
44
94
|
return null;
|
|
45
95
|
const entryIds = await listEntryIds(sessionId);
|
|
46
96
|
if (entryIds === null)
|
|
47
|
-
return null;
|
|
97
|
+
return null; // session does not exist
|
|
98
|
+
// leafId — the session's current leaf (cache-bypassing single read). Absent seam / no leaf ⇒ null.
|
|
48
99
|
const getLeafId = session.getLeafId?.bind(session);
|
|
49
100
|
const leafId = getLeafId ? await getLeafId(sessionId) : null;
|
|
101
|
+
// snapshots / policy / anchors — IDENTICAL to exportSession (no entry payloads loaded anywhere here).
|
|
50
102
|
const fs = srcBackend.fileSnapshot();
|
|
51
103
|
const snapshots = [];
|
|
52
104
|
if (typeof fs.listKeys === "function" && typeof fs.exportManifest === "function") {
|
|
@@ -54,7 +106,7 @@ export async function exportSessionManifest(sessionId, srcBackend) {
|
|
|
54
106
|
for (const key of keys) {
|
|
55
107
|
const manifest = await fs.exportManifest(sessionId, key);
|
|
56
108
|
if (manifest === null)
|
|
57
|
-
continue;
|
|
109
|
+
continue; // raced reap between listKeys and exportManifest — skip (never an empty snapshot)
|
|
58
110
|
snapshots.push({ key, manifest: [...manifest] });
|
|
59
111
|
}
|
|
60
112
|
}
|
|
@@ -66,6 +118,7 @@ export async function exportSessionManifest(sessionId, srcBackend) {
|
|
|
66
118
|
const anchors = typeof anchorStore.listBySession === "function"
|
|
67
119
|
? await anchorStore.listBySession(sessionId)
|
|
68
120
|
: [];
|
|
121
|
+
// getBlob — bound to the SOURCE snapshot store (content-addressed, lazy), exactly like exportSession.
|
|
69
122
|
const getBlobFn = fs.getBlob?.bind(fs);
|
|
70
123
|
const getBlob = getBlobFn ? (hash) => getBlobFn(hash) : async () => undefined;
|
|
71
124
|
return {
|
|
@@ -73,16 +126,78 @@ export async function exportSessionManifest(sessionId, srcBackend) {
|
|
|
73
126
|
getBlob,
|
|
74
127
|
};
|
|
75
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* IMPORT a {@link SessionBundle} (+ its {@link BlobGetter}) into `dstBackend` under `bundle.sessionId`, re-stamping
|
|
131
|
+
* ownership to `importingPrincipal`. Returns the snapshot tally (imported vs honestly-skipped on a destination whose
|
|
132
|
+
* FileSnapshotStore lacks the importManifest seam — pre-1.141.0; core's InMemory/File NOW carry it, so a local dst
|
|
133
|
+
* imports snapshots for real).
|
|
134
|
+
*
|
|
135
|
+
* §10 FAIL-CLOSED VALIDATION runs FIRST — BEFORE any write:
|
|
136
|
+
* 1. `validateEntriesForImport(bundle.entries)` (the single core invariant gate: unique ids / parent-before-child /
|
|
137
|
+
* one root / leaf-resolvable). Throws on violation; NOTHING is written.
|
|
138
|
+
* 2. Every blobHash referenced by every snapshot manifest MUST be supplied by `getBlob` — a missing blob would
|
|
139
|
+
* restore a PARTIAL tree (files half-written, conversation whole → breaks the entry↔file lockstep). A lightweight
|
|
140
|
+
* PRESENCE pre-check (fetch each distinct hash once, no hashing) fails FAST here; the AUTHORITATIVE content-address
|
|
141
|
+
* integrity check (`sha256(bytes)===hash`) is done ONCE inside `importManifest` (core 1.141.0), so we don't double-
|
|
142
|
+
* hash. (Runs only when the dst supports snapshot import; otherwise there is nothing to half-write.)
|
|
143
|
+
*
|
|
144
|
+
* §7 CLASSIFICATION GATE — BEFORE any write, the destination's CURRENT entry log is read and compared to the bundle
|
|
145
|
+
* via {@link classifySyncRelationship} (a sound entry-id SET comparison, not leaf_id). A `fork` (true divergence) or a
|
|
146
|
+
* `stale` source (the dst is strictly ahead) WOULD lose destination history → it throws {@link SyncConflictError}
|
|
147
|
+
* (route → 409) UNLESS the caller passes `{ resolution: "overwrite-dst" }`. An `identical` relation SKIPS the entries
|
|
148
|
+
* write entirely (the log is already present & equal; the idempotent snapshot/policy/anchor replay below still runs to
|
|
149
|
+
* heal anything missing). `fresh` / `fast_forward` (and an overwrite-resolved fork/stale) write the entries via the
|
|
150
|
+
* IDEMPOTENT {@link OwnerAwareSessionStore.replaceEntries} (purge-then-import) — NOT `importEntries`, whose plain
|
|
151
|
+
* INSERT crashes on a duplicate PK when the session already exists at the destination.
|
|
152
|
+
*
|
|
153
|
+
* §8 CROSS-STORE ATOMIC ORDER + IDEMPOTENCY — session_meta (written LAST inside `replaceEntries`) is the sole
|
|
154
|
+
* commit point, so a crash mid-import leaves orphan blobs/manifests/anchors with NO session_meta = invisible to
|
|
155
|
+
* wake (never a half-session, only collectable orphans). Order:
|
|
156
|
+
* ① snapshots (manifests + content-addressed blobs) — content-addressed + create-once = idempotent retry-to-completion.
|
|
157
|
+
* `importManifest` returns a {@link FileSnapshotResult} (NEVER throws — core 1.141.0); a `{ok:false}` (a
|
|
158
|
+
* rejecting/missing/hash-mismatched blob the §10 pre-check didn't catch — e.g. a blob corrupted in transit) is
|
|
159
|
+
* re-raised as a fail-closed THROW so the import aborts BEFORE the entries commit (no entry↔file split).
|
|
160
|
+
* ② entries (`replaceEntries` runs the core gate AGAIN and writes session_meta LAST = the commit point; idempotent
|
|
161
|
+
* over an existing session — skipped entirely when the relation is `identical`)
|
|
162
|
+
* ③ policy (replay each record via putRules; `opts.operatorOk` defaults FALSE → the E6 tighten-only gate APPLIES, so
|
|
163
|
+
* a replay that would LOOSEN an operator-tightened dst policy throws `SessionPolicyError("loosen_forbidden")` —
|
|
164
|
+
* `operator:true` is set ONLY when the authenticated importer is itself a verified operator)
|
|
165
|
+
* ④ anchors (owner RE-KEYED to `importingPrincipal`, §9)
|
|
166
|
+
* Steps ③/④ are post-commit; re-running them is idempotent — ③ SKIPS a put whose content already equals the dst row
|
|
167
|
+
* (a "no-op rev bump" is NOT idempotent: rev is the operator's optimistic-lock token — the A6 fix below), ④ is a
|
|
168
|
+
* same-value upsert — so a crash between ②–④ is recovered by a retry that converges to the same bytes.
|
|
169
|
+
*
|
|
170
|
+
* @param opts.resolution — `"overwrite-dst"` consents to a `fork`/`stale` import that overwrites destination history
|
|
171
|
+
* (the user's keep-source decision); omitted ⇒ such an import is refused with {@link SyncConflictError}.
|
|
172
|
+
* @param opts.operatorOk — when `true`, the policy replay (step ③) is an OPERATOR write (may loosen the dst rules); set
|
|
173
|
+
* ONLY when the AUTHENTICATED importer is a verified operator. DEFAULT false → the E6 tighten-only gate applies, so a
|
|
174
|
+
* non-operator import is TIGHTEN-ONLY (design §4): a replay that would loosen an operator-tightened dst session policy
|
|
175
|
+
* throws `SessionPolicyError("loosen_forbidden")`, which propagates (NOT swallowed) so the route can refuse the loosen.
|
|
176
|
+
*/
|
|
76
177
|
export async function importSession(bundle, getBlob, dstBackend, importingPrincipal, opts) {
|
|
77
178
|
const sessionId = bundle.sessionId;
|
|
78
179
|
const fs = dstBackend.fileSnapshot();
|
|
79
180
|
const canImportSnapshots = typeof fs.importManifest === "function";
|
|
181
|
+
// ── §7 classification gate, BEFORE any write ───────────────────────────────────────────────────────────────
|
|
182
|
+
// Read the destination's CURRENT entry log and classify the relationship (sound entry-id set comparison). A fork or
|
|
183
|
+
// a stale source would lose dst history → refuse with a typed 409 unless the caller explicitly resolved overwrite-dst.
|
|
80
184
|
const dstEntries = (await ownerAware(dstBackend).exportEntries?.(sessionId)) ?? null;
|
|
81
185
|
const rel = classifySyncRelationship(bundle.entries, dstEntries);
|
|
82
186
|
if ((rel.relation === "fork" || rel.relation === "stale") && opts?.resolution !== "overwrite-dst") {
|
|
83
|
-
throw new SyncConflictError(sessionId, rel);
|
|
187
|
+
throw new SyncConflictError(sessionId, rel); // 409 — surface the exclusive sets so the user picks keep-local/cloud
|
|
84
188
|
}
|
|
189
|
+
// ── §10 fail-closed validation, BEFORE any write ────────────────────────────────────────────────────────────
|
|
190
|
+
// (1) entries invariants (the core gate). validateEntriesForImport throws on any violation; we discard its result
|
|
191
|
+
// here (importEntries re-runs it as the authoritative write-time gate) — this is the early fail-closed check so
|
|
192
|
+
// we never start writing snapshots for a session whose entries the gate will reject.
|
|
85
193
|
validateEntriesForImport(bundle.entries);
|
|
194
|
+
// (2) every referenced blob must be fetchable — ONLY when the destination can actually import snapshots (otherwise
|
|
195
|
+
// there is nothing to half-write, so a missing blob is moot; the snapshot is honestly skipped below). Fetch each
|
|
196
|
+
// DISTINCT hash once (content-addressed) so a large manifest doesn't refetch. This is a lightweight PRESENCE
|
|
197
|
+
// pre-check (fail-FAST before we write any entries) — the AUTHORITATIVE sha256 content-address integrity check is
|
|
198
|
+
// done ONCE inside importManifest (core 1.141.0); we deliberately don't re-hash here to avoid double-hashing
|
|
199
|
+
// a possibly-large blob set. The importManifest {ok:false} below is the backstop for anything the presence check
|
|
200
|
+
// can't see (a blob present-but-corrupted-in-transit, a getBlob that rejects only on a later call).
|
|
86
201
|
if (canImportSnapshots) {
|
|
87
202
|
const distinct = new Set();
|
|
88
203
|
for (const snap of bundle.snapshots)
|
|
@@ -95,6 +210,19 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
|
|
|
95
210
|
}
|
|
96
211
|
}
|
|
97
212
|
}
|
|
213
|
+
// ── ⓪ overwrite-dst subsystem WIPE (fork/stale with explicit consent ONLY) ─────────────────────────────────
|
|
214
|
+
// "overwrite-dst" means the user chose KEEP-SOURCE — the result must be the SOURCE's state, not "the source's
|
|
215
|
+
// conversation + the destination's leftover policy/anchors/snapshots" (a MIXED state is worse than lost history:
|
|
216
|
+
// dangling anchors resolve onto purged entries, an abandoned branch's policy rows keep gating, and a shared-key
|
|
217
|
+
// snapshot keeps the dst's bytes because importManifest is create-once). So before replaying ①/③/④ we wipe the
|
|
218
|
+
// dst's per-session subsystem state. Crash-safety: the user consented to discarding the dst's state, and every
|
|
219
|
+
// wipe+replay leg is idempotent → a crash mid-wipe is recovered by the same retry-to-completion as §8's ②–④.
|
|
220
|
+
// · snapshots: prefer the purge seam `deleteBySession` (SQL twins: manifests drop now, blobs go to the async
|
|
221
|
+
// orphan GC); fall back to the core-REQUIRED `reap(scope, [])` (in-memory/file stores GC unreferenced blobs
|
|
222
|
+
// inline — content-addressed, so blobs still referenced by other scopes survive).
|
|
223
|
+
// · anchors / policy: `deleteBySession` where the store carries it — since core 1.423 that is EVERY in-tree
|
|
224
|
+
// store (the seam is optional on core's SessionPolicyStore interface and implemented by its File/InMemory
|
|
225
|
+
// stores too — [1796]§三 → [1801]; the feature-detect stays for third-party stores that predate the seam).
|
|
98
226
|
const overwriting = (rel.relation === "fork" || rel.relation === "stale") && opts?.resolution === "overwrite-dst";
|
|
99
227
|
if (overwriting) {
|
|
100
228
|
const fsWipe = fs;
|
|
@@ -109,6 +237,14 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
|
|
|
109
237
|
if (typeof policyWipe.deleteBySession === "function")
|
|
110
238
|
await policyWipe.deleteBySession(sessionId);
|
|
111
239
|
}
|
|
240
|
+
// ── §8 atomic order ─────────────────────────────────────────────────────────────────────────────────────────
|
|
241
|
+
// ① snapshots — content-addressed blobs + manifest, BEFORE session_meta. Feature-detect importManifest: ALL backends
|
|
242
|
+
// now carry it (durable TiDB/PG + core's File/InMemory since 1.141.0) → a local dst imports snapshots for
|
|
243
|
+
// real. The only remaining honest-degrade is a backend whose FileSnapshotStore predates the seam (none in-tree).
|
|
244
|
+
// importManifest NEVER throws — it returns a FileSnapshotResult; a {ok:false} (rejecting/missing/hash-mismatched
|
|
245
|
+
// blob — e.g. corrupted in transit) is re-raised here as a fail-closed THROW so we abort BEFORE the entries commit
|
|
246
|
+
// (no entry↔file split). The whole import is the caller's transaction: a throw here means no session_meta is ever
|
|
247
|
+
// written (the commit point is inside importEntries, step ②), so the dst stays clean (only collectable orphans).
|
|
112
248
|
let snapshotsImported = 0;
|
|
113
249
|
let snapshotsSkipped = 0;
|
|
114
250
|
if (canImportSnapshots) {
|
|
@@ -122,8 +258,16 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
|
|
|
122
258
|
}
|
|
123
259
|
}
|
|
124
260
|
else {
|
|
125
|
-
snapshotsSkipped = bundle.snapshots.length;
|
|
261
|
+
snapshotsSkipped = bundle.snapshots.length; // pre-1.141.0 dst — files pend the import seam; honest degrade (NOT a silent drop)
|
|
126
262
|
}
|
|
263
|
+
// ② entries — core gate (again, authoritative) + session_meta LAST = the commit point. Owner = importingPrincipal
|
|
264
|
+
// (NEVER the bundle's, §9). Written via the IDEMPOTENT replaceEntries (purge-then-import) — NOT importEntries,
|
|
265
|
+
// whose plain INSERT crashes on a duplicate PK when the session already exists (fast-forward / overwrite-resolved
|
|
266
|
+
// / a §8 retry). When the relation is `identical` the log is already present & equal → SKIP the entries write
|
|
267
|
+
// entirely (the snapshot/policy/anchor replay below is idempotent and still runs to heal anything missing). A
|
|
268
|
+
// destination without the replace seam can't accept a session at all → throw.
|
|
269
|
+
// 🔴 同上(`session-sync-content.ts` 顶注):`identical` 只说明 id 集合相等,不说明内容相同。这条路上
|
|
270
|
+
// `bundle.entries` 与目的端日志都在手上 ⇒ 直接比内容摘要;不符就照常改写(不抛错、不 409)。
|
|
127
271
|
const dstEntriesForContent = await ownerAware(dstBackend).exportEntries?.(sessionId).catch(() => null) ?? null;
|
|
128
272
|
const contentEqual = rel.relation === "identical" && identicalIdsAlsoIdenticalContent(bundle.entries, dstEntriesForContent);
|
|
129
273
|
if (rel.relation !== "identical" || !contentEqual) {
|
|
@@ -134,24 +278,53 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
|
|
|
134
278
|
}
|
|
135
279
|
await replaceEntries(sessionId, importingPrincipal, bundle.entries);
|
|
136
280
|
}
|
|
281
|
+
// ③ policy — replay each (principal, rules) record VERBATIM. The E6 tighten-only gate is the security boundary here:
|
|
282
|
+
// we do NOT pass operator:true unconditionally (a non-operator pushing a client-controlled bundle could otherwise
|
|
283
|
+
// LOOSEN an operator-tightened dst session policy — the loosen the E6 route refuses). `opts.operatorOk` defaults
|
|
284
|
+
// FALSE → the tighten gate applies, so a TIGHTEN-ONLY import succeeds (design §4 "merge tighten-only") and a
|
|
285
|
+
// loosening replay throws SessionPolicyError("loosen_forbidden") — which we do NOT swallow; it propagates to the
|
|
286
|
+
// route so the malicious loosen is refused (entries may already have committed: the conversation syncs, only the
|
|
287
|
+
// loosen is refused = the correct fail-closed outcome). operator:true ONLY when the AUTHENTICATED importer is a
|
|
288
|
+
// verified operator. principal is carried VERBATIM (NOT re-keyed to importingPrincipal): the policy `principal` is
|
|
289
|
+
// the per-TASK rule-owner key (`spec.principal`), not the session owner, so re-keying would collapse distinct
|
|
290
|
+
// per-principal rows and could clobber. The session-wide row (principal=undefined) stays session-wide and still
|
|
291
|
+
// applies to the new owner.
|
|
292
|
+
// IDEMPOTENT REPLAY (the A6 fix): a put whose content (rev stripped, normalized) already equals the dst row is
|
|
293
|
+
// SKIPPED — putRules bumps `rev` on every write, and `rev` is the optimistic-lock token operators hold for
|
|
294
|
+
// read-modify-write. Without the skip, every §8 crash-retry (and every harmless duplicate sync) advances every
|
|
295
|
+
// row's rev while changing nothing ⇒ the operator's next putRules({expectedRev}) throws `conflict` on rules whose
|
|
296
|
+
// content never moved. With it, retry-after-crash converges to the same BYTES as a single clean run, which is
|
|
297
|
+
// what "steps ③/④ are idempotent" (§8 above) actually has to mean. normalizeRules gives both sides the same
|
|
298
|
+
// canonical field order/shape, so JSON.stringify is a sound equality here (array ORDER is significant on purpose:
|
|
299
|
+
// a reordered list is a different stored byte-state and must be written).
|
|
137
300
|
const policyStore = dstBackend.sessionPolicy();
|
|
138
301
|
const canonRules = (r) => JSON.stringify(normalizeRules(stripRev(r)));
|
|
139
302
|
for (const rec of bundle.policy) {
|
|
140
303
|
const cur = await policyStore.getRules(sessionId, rec.principal);
|
|
141
304
|
if (cur !== null && canonRules(cur) === canonRules(rec.rules))
|
|
142
|
-
continue;
|
|
305
|
+
continue; // content already identical — don't touch rev
|
|
143
306
|
await policyStore.putRules(sessionId, rec.principal, rec.rules, { operator: opts?.operatorOk === true });
|
|
144
307
|
}
|
|
308
|
+
// ④ anchors — owner RE-KEYED to importingPrincipal (§9): the E18 owner-guard resolves under the NEW owner so
|
|
309
|
+
// rewind-to-message keeps working post-move (the source owner, e.g. local null, would never resolve in the cloud).
|
|
145
310
|
const anchorStore = dstBackend.resumeAnchor();
|
|
146
311
|
for (const a of bundle.anchors) {
|
|
147
312
|
await anchorStore.put(sessionId, a.eventId, a.entryId, importingPrincipal);
|
|
148
313
|
}
|
|
149
314
|
return { snapshotsImported, snapshotsSkipped, relation: rel.relation };
|
|
150
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* §7 DRY-RUN — classify what importing `sessionId` from `srcBackend` into `dstBackend` WOULD do, WITHOUT writing
|
|
318
|
+
* anything. The route (P1d) calls this to surface a conflict (fork/stale) to the user BEFORE committing an
|
|
319
|
+
* {@link importSession} (so the shell can present a keep-local / keep-cloud / fork-new choice up front).
|
|
320
|
+
*
|
|
321
|
+
* Returns `null` when the SOURCE session does not exist (no bundle to plan); otherwise the {@link SyncRelation}
|
|
322
|
+
* between the source's full durable log and the destination's current log (a `null` dst log ⇒ `fresh`). No writes.
|
|
323
|
+
*/
|
|
151
324
|
export async function planSync(sessionId, srcBackend, dstBackend) {
|
|
152
325
|
const srcEntries = (await ownerAware(srcBackend).exportEntries?.(sessionId)) ?? null;
|
|
153
326
|
if (srcEntries === null)
|
|
154
|
-
return null;
|
|
327
|
+
return null; // no such source session — nothing to plan (the route maps this to 404)
|
|
155
328
|
const dstEntries = (await ownerAware(dstBackend).exportEntries?.(sessionId)) ?? null;
|
|
156
329
|
return classifySyncRelationship(srcEntries, dstEntries);
|
|
157
330
|
}
|
package/dist/session-titler.d.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated session titles.
|
|
3
|
+
*
|
|
4
|
+
* SHAPE: fire-and-forget at FIRST task submit for a session (the objective is known then — no need to wait
|
|
5
|
+
* for the run to finish, and the web Recents gets the title sooner). Write-once (`setTitleIfNull`, store-side
|
|
6
|
+
* `WHERE title IS NULL`) — the USER-facing rename lives BFF-side as a label overlay (user label wins
|
|
7
|
+
* over engine title, two layers never fight). Generation = one cheap-model one-liner via the deployment's
|
|
8
|
+
* hookLlm face (summarize role → MODEL_CHEAP_ID when configured, else the main model; bounded + timed out).
|
|
9
|
+
*
|
|
10
|
+
* FAILURE POSTURE: everything is best-effort — a generation/store failure logs and leaves title null (the
|
|
11
|
+
* list falls back to objectivePreview). An in-process pending set dedupes concurrent submits on the same
|
|
12
|
+
* session; the store's IS NULL guard closes the cross-replica race (double generation costs one extra cheap
|
|
13
|
+
* call at worst, and exactly one title lands).
|
|
14
|
+
*/
|
|
1
15
|
export interface SessionTitlerDeps {
|
|
16
|
+
/** Secret scrub applied to the RAW llm text before sanitize/persist (workflow audit 2026-07-13: the
|
|
17
|
+
* objective may carry a pasted secret and the model can echo it into the title — titles are persisted,
|
|
18
|
+
* listed over HTTP, and logged, all outside the trace redact pipeline; wire src/trace/redact.ts here). */
|
|
2
19
|
redact?: (text: string) => string;
|
|
20
|
+
/** Write-once store leg (SQL twins / local sidecar). Resolves false when a title already existed. */
|
|
3
21
|
setTitleIfNull(sessionId: string, title: string): Promise<boolean>;
|
|
22
|
+
/** Cheap pre-LLM probe: "titled" = skip (a restart/evicted-seen/another replica
|
|
23
|
+
* already paid — don't re-spend an LLM call); "untitled" = generate; "none" = the session_meta row hasn't
|
|
24
|
+
* been REGISTERED yet (createRun fires before the Runner's session acquire) — retry after a grace window,
|
|
25
|
+
* and if still absent, release the seen slot so the NEXT submit retries (never a permanently-untitled
|
|
26
|
+
* session via a swallowed early UPDATE-0). Local lane has no row-existence dimension → never "none". */
|
|
4
27
|
probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
|
|
28
|
+
/** The deployment's cheap one-shot LLM face (main.ts hookLlm — summarize role default). */
|
|
5
29
|
llm(opts: {
|
|
6
30
|
prompt: string;
|
|
7
31
|
timeoutMs: number;
|
|
@@ -22,9 +46,11 @@ export interface SessionTitlerDeps {
|
|
|
22
46
|
};
|
|
23
47
|
}
|
|
24
48
|
export interface SessionTitler {
|
|
49
|
+
/** Fire-and-forget: maybe generate + persist a title for this session. Never throws, never blocks the caller. */
|
|
25
50
|
maybeTitle(sessionId: string, objective: string): void;
|
|
26
51
|
}
|
|
27
52
|
export declare const TITLE_MAX_CHARS = 80;
|
|
53
|
+
/** Single line, control chars stripped, hard cap; surrounding quotes (a common LLM tic) trimmed. */
|
|
28
54
|
export declare function sanitizeTitle(raw: string): string;
|
|
29
55
|
export declare function buildTitlePrompt(objective: string): string;
|
|
30
56
|
export declare function createSessionTitler(deps: SessionTitlerDeps): SessionTitler;
|