@sema-agent/server 1.322.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/approval-hmac.d.ts +40 -0
- package/dist/approval-hmac.js +67 -0
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +72 -4
- package/dist/audit.d.ts +26 -0
- package/dist/audit.js +40 -1
- package/dist/auth-bridge.d.ts +10 -0
- package/dist/auth-bridge.js +47 -4
- package/dist/auth-keys.d.ts +19 -0
- package/dist/auth-keys.js +13 -0
- package/dist/bake-runner/main.d.ts +5 -0
- package/dist/bake-runner/main.js +43 -4
- package/dist/bake-runner/protocol.d.ts +57 -0
- package/dist/bake-runner/protocol.js +58 -3
- package/dist/bake-runner/runner.d.ts +54 -0
- package/dist/bake-runner/runner.js +87 -5
- package/dist/bench/l8/artifact.d.ts +63 -0
- package/dist/bench/l8/artifact.js +61 -2
- package/dist/bench/l8/escape.d.ts +36 -0
- package/dist/bench/l8/escape.js +25 -0
- package/dist/bench/l8/index.d.ts +14 -0
- package/dist/bench/l8/index.js +14 -0
- package/dist/bench/l8/probes.d.ts +74 -0
- package/dist/bench/l8/probes.js +60 -0
- package/dist/bench/l8/run-probes.d.ts +43 -0
- package/dist/bench/l8/run-probes.js +45 -2
- package/dist/bench/s1/arms.d.ts +126 -0
- package/dist/bench/s1/arms.js +188 -13
- package/dist/bench/s1/live-deps.d.ts +66 -0
- package/dist/bench/s1/live-deps.js +249 -11
- package/dist/bench/s1/oracle.d.ts +49 -0
- package/dist/bench/s1/oracle.js +36 -1
- package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
- package/dist/bench/s1/repair-oracle-adapter.js +30 -0
- package/dist/bench/s1/reviewer.d.ts +38 -0
- package/dist/bench/s1/reviewer.js +34 -0
- package/dist/bench/s1/row.d.ts +77 -0
- package/dist/bench/s1/row.js +62 -0
- package/dist/bench/s1/run-firm.d.ts +47 -0
- package/dist/bench/s1/run-firm.js +70 -4
- package/dist/bench/s1/runner-ctx.d.ts +71 -0
- package/dist/bench/s1/runner-ctx.js +36 -0
- package/dist/bench/s1/tasks.d.ts +108 -0
- package/dist/bench/s1/tasks.js +115 -0
- package/dist/boot-reclaim.d.ts +19 -0
- package/dist/boot-reclaim.js +20 -0
- package/dist/brain.d.ts +25 -0
- package/dist/brain.js +61 -1
- package/dist/budget.d.ts +66 -0
- package/dist/budget.js +128 -4
- package/dist/capabilities/builtin-tools.d.ts +3 -0
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/center-plugins.d.ts +17 -1
- package/dist/capabilities/center-plugins.js +45 -4
- package/dist/capabilities/center-prompts.d.ts +46 -0
- package/dist/capabilities/center-prompts.js +57 -3
- package/dist/capabilities/code-review-council.d.ts +28 -0
- package/dist/capabilities/code-review-council.js +40 -7
- package/dist/capabilities/collab-wire.d.ts +2 -0
- package/dist/capabilities/collab-workflows.d.ts +7 -0
- package/dist/capabilities/collab-workflows.js +44 -1
- package/dist/capabilities/oa-tools.d.ts +6 -0
- package/dist/capabilities/oa-tools.js +10 -0
- package/dist/capabilities/prompt.d.ts +14 -0
- package/dist/capabilities/prompt.js +14 -0
- package/dist/capabilities/prompts/code-review.d.ts +14 -0
- package/dist/capabilities/prompts/code-review.js +14 -0
- package/dist/capabilities/prompts/identity.d.ts +6 -0
- package/dist/capabilities/prompts/identity.js +6 -0
- package/dist/capabilities/prompts/team.d.ts +4 -0
- package/dist/capabilities/prompts/team.js +4 -0
- package/dist/capabilities/repo-tools.d.ts +11 -0
- package/dist/capabilities/repo-tools.js +8 -0
- package/dist/capabilities/sandbox-file-send.d.ts +95 -0
- package/dist/capabilities/sandbox-file-send.js +91 -3
- package/dist/capabilities/scenarios.d.ts +130 -0
- package/dist/capabilities/scenarios.js +180 -7
- package/dist/capabilities/select-environment-tool.d.ts +7 -0
- package/dist/capabilities/select-environment-tool.js +30 -0
- package/dist/capabilities/send-user-file-tool.d.ts +33 -0
- package/dist/capabilities/send-user-file-tool.js +65 -1
- package/dist/capabilities/skills.d.ts +29 -0
- package/dist/capabilities/skills.js +13 -2
- package/dist/capabilities/team.d.ts +22 -0
- package/dist/capabilities/team.js +22 -2
- package/dist/capabilities/tool-defer.d.ts +26 -0
- package/dist/capabilities/tool-defer.js +3 -0
- package/dist/config-center/apply-effective.d.ts +65 -0
- package/dist/config-center/apply-effective.js +204 -12
- package/dist/config-center/http-client.d.ts +34 -0
- package/dist/config-center/http-client.js +68 -0
- package/dist/config-center/restart-signal.d.ts +22 -0
- package/dist/config-center/restart-signal.js +39 -0
- package/dist/config-center/skills-mcp.d.ts +20 -1
- package/dist/config-center/skills-mcp.js +42 -7
- package/dist/config-center/types.d.ts +92 -1
- package/dist/config-lkg.d.ts +12 -0
- package/dist/config-lkg.js +41 -0
- package/dist/config-provider.d.ts +78 -0
- package/dist/config-provider.js +165 -2
- package/dist/config-types.d.ts +546 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.js +455 -60
- package/dist/degenerate-instrument.d.ts +38 -0
- package/dist/degenerate-instrument.js +55 -3
- package/dist/elicitation.d.ts +39 -0
- package/dist/elicitation.js +87 -3
- package/dist/env-facts.d.ts +61 -0
- package/dist/env-facts.js +73 -7
- package/dist/fleet/fleet-bus.d.ts +185 -0
- package/dist/fleet/fleet-bus.js +333 -27
- package/dist/fleet/subagent-tail-bus.d.ts +11 -0
- package/dist/fleet/subagent-tail-bus.js +33 -2
- package/dist/fleet-client.d.ts +38 -0
- package/dist/fleet-client.js +44 -9
- package/dist/fleet-lease.d.ts +46 -0
- package/dist/fleet-lease.js +73 -5
- package/dist/hooks/branch-transcript.d.ts +8 -0
- package/dist/hooks/branch-transcript.js +33 -0
- package/dist/hooks/cc-stop-prompt.d.ts +42 -0
- package/dist/hooks/cc-stop-prompt.js +43 -1
- package/dist/hooks/hook-llm.d.ts +15 -0
- package/dist/hooks/hook-llm.js +47 -0
- package/dist/hooks/hook-runner.d.ts +89 -0
- package/dist/hooks/hook-runner.js +327 -18
- package/dist/http/idempotency.d.ts +31 -0
- package/dist/http/idempotency.js +34 -0
- package/dist/http/principal-gate.d.ts +17 -0
- package/dist/http/principal-gate.js +35 -3
- package/dist/http/route-ctx.d.ts +36 -0
- package/dist/http/routes/approvals-assistant.d.ts +25 -0
- package/dist/http/routes/approvals-assistant.js +253 -23
- package/dist/http/routes/attachments.js +22 -4
- package/dist/http/routes/capabilities.d.ts +7 -0
- package/dist/http/routes/capabilities.js +202 -1
- package/dist/http/routes/fleet.d.ts +6 -0
- package/dist/http/routes/fleet.js +94 -5
- package/dist/http/routes/images.d.ts +17 -0
- package/dist/http/routes/images.js +222 -14
- package/dist/http/routes/leader.d.ts +7 -0
- package/dist/http/routes/leader.js +10 -0
- package/dist/http/routes/memory-policy.d.ts +6 -0
- package/dist/http/routes/memory-policy.js +38 -6
- package/dist/http/routes/notify-wake.d.ts +10 -0
- package/dist/http/routes/notify-wake.js +19 -3
- package/dist/http/routes/observability.d.ts +6 -0
- package/dist/http/routes/observability.js +19 -2
- package/dist/http/routes/runs.d.ts +15 -0
- package/dist/http/routes/runs.js +464 -41
- package/dist/http/routes/session-sync.d.ts +15 -0
- package/dist/http/routes/session-sync.js +229 -19
- package/dist/http/routes/sessions-list.d.ts +6 -0
- package/dist/http/routes/sessions-list.js +35 -4
- package/dist/http/routes/sessions.d.ts +14 -0
- package/dist/http/routes/sessions.js +176 -31
- package/dist/http/routes/side-query.d.ts +8 -0
- package/dist/http/routes/side-query.js +26 -1
- package/dist/http/routes/tasks.d.ts +18 -0
- package/dist/http/routes/tasks.js +501 -38
- package/dist/http/routes/trace-usage.d.ts +10 -0
- package/dist/http/routes/trace-usage.js +66 -14
- package/dist/http/routes/workflows.d.ts +12 -0
- package/dist/http/routes/workflows.js +180 -19
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +6 -0
- package/dist/http/send.d.ts +19 -0
- package/dist/http/send.js +11 -0
- package/dist/http/server.d.ts +255 -0
- package/dist/http/server.js +921 -75
- package/dist/http/sse-log.d.ts +15 -0
- package/dist/http/sse-log.js +16 -0
- package/dist/http/tar.d.ts +9 -0
- package/dist/http/tar.js +21 -5
- package/dist/http/wire-gate.d.ts +9 -0
- package/dist/http/wire-gate.js +9 -0
- package/dist/http/wire-types.d.ts +187 -0
- package/dist/http/workspace-content.d.ts +8 -0
- package/dist/http/workspace-content.js +10 -0
- package/dist/images/bake-validate.d.ts +63 -0
- package/dist/images/bake-validate.js +71 -1
- package/dist/images/manifest.d.ts +19 -0
- package/dist/images/manifest.js +6 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.js +10 -5
- package/dist/key-resolver.d.ts +23 -0
- package/dist/key-resolver.js +27 -2
- package/dist/leader/diffout.d.ts +23 -0
- package/dist/leader/diffout.js +8 -1
- package/dist/leader/diffup.d.ts +32 -0
- package/dist/leader/diffup.js +43 -0
- package/dist/leader/endpoint.d.ts +20 -0
- package/dist/leader/endpoint.js +25 -2
- package/dist/leader/fanout.d.ts +60 -0
- package/dist/leader/fanout.js +29 -3
- package/dist/leader/grader-env-factory.d.ts +74 -0
- package/dist/leader/grader-env-factory.js +62 -3
- package/dist/leader/leader.d.ts +97 -0
- package/dist/leader/leader.js +151 -4
- package/dist/leader/merge.d.ts +94 -0
- package/dist/leader/merge.js +112 -9
- package/dist/leader/planner.d.ts +52 -0
- package/dist/leader/planner.js +47 -3
- package/dist/leader/repair-oracle.d.ts +61 -0
- package/dist/leader/repair-oracle.js +59 -1
- package/dist/leader/repair-wire.d.ts +78 -0
- package/dist/leader/repair-wire.js +79 -2
- package/dist/leader/wire.d.ts +79 -0
- package/dist/leader/wire.js +255 -8
- package/dist/lsp/e2b-bridge.d.ts +16 -0
- package/dist/lsp/e2b-bridge.js +32 -4
- package/dist/lsp/e2b-manager.d.ts +10 -0
- package/dist/lsp/e2b-manager.js +18 -2
- package/dist/lsp/lsp-frames.d.ts +12 -0
- package/dist/lsp/lsp-frames.js +12 -0
- package/dist/lsp/manager.d.ts +12 -0
- package/dist/lsp/manager.js +35 -2
- package/dist/lsp/types.d.ts +10 -0
- package/dist/lsp/ws-transport.d.ts +12 -0
- package/dist/lsp/ws-transport.js +53 -5
- package/dist/lsp-evict.d.ts +14 -0
- package/dist/lsp-evict.js +15 -1
- package/dist/main.js +2132 -96
- package/dist/memory-export.d.ts +1 -0
- package/dist/memory-export.js +4 -0
- package/dist/memory-scope.d.ts +49 -0
- package/dist/memory-scope.js +87 -4
- package/dist/memory-sync-client.d.ts +24 -0
- package/dist/memory-sync-client.js +41 -2
- package/dist/memory-sync.d.ts +23 -0
- package/dist/memory-sync.js +70 -1
- package/dist/model-select.d.ts +39 -0
- package/dist/model-select.js +42 -1
- package/dist/observability/cost-quota.d.ts +20 -0
- package/dist/observability/cost-quota.js +4 -0
- package/dist/observability/cost-taxonomy.d.ts +45 -0
- package/dist/observability/cost-taxonomy.js +34 -0
- package/dist/observability/logger.d.ts +1 -0
- package/dist/observability/logger.js +6 -0
- package/dist/observability/metrics.d.ts +13 -0
- package/dist/observability/metrics.js +63 -0
- package/dist/observability/otel-exporter.d.ts +9 -0
- package/dist/observability/otel-exporter.js +11 -3
- package/dist/observability/principal-context.d.ts +9 -0
- package/dist/observability/principal-context.js +9 -0
- package/dist/observability/prompt-manifest.d.ts +37 -0
- package/dist/observability/prompt-manifest.js +35 -1
- package/dist/observability/rate-limit.d.ts +10 -0
- package/dist/observability/rate-limit.js +1 -0
- package/dist/observability/tool-trace.d.ts +24 -0
- package/dist/observability/tool-trace.js +41 -1
- package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
- package/dist/orchestration/hardened-vm-runner.js +119 -4
- package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
- package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
- package/dist/orchestration/hardened-vm-worker.js +27 -0
- package/dist/orchestration/subagent-steer.d.ts +44 -0
- package/dist/orchestration/subagent-steer.js +45 -1
- package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
- package/dist/orchestration/workflow-agent-steer.js +80 -1
- package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
- package/dist/orchestration/workflow-completion-inbox.js +283 -32
- package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
- package/dist/orchestration/workflow-notify-journal.js +187 -13
- package/dist/parked-decide.d.ts +61 -0
- package/dist/parked-decide.js +58 -1
- package/dist/per-task-image.d.ts +54 -0
- package/dist/per-task-image.js +41 -0
- package/dist/plan-cache-probe.d.ts +35 -0
- package/dist/plan-cache-probe.js +18 -3
- package/dist/plugins/approval-exemption-store.d.ts +7 -0
- package/dist/plugins/approval-exemption-store.js +28 -2
- package/dist/plugins/approval-store-sql.d.ts +64 -0
- package/dist/plugins/approval-store-sql.js +32 -0
- package/dist/plugins/background-agent-store-sql.d.ts +55 -0
- package/dist/plugins/background-agent-store-sql.js +91 -4
- package/dist/plugins/background-shell-support.d.ts +98 -0
- package/dist/plugins/background-shell-support.js +117 -13
- package/dist/plugins/blob-backend.d.ts +104 -0
- package/dist/plugins/blob-backend.js +163 -6
- package/dist/plugins/breaker-state-sql.d.ts +65 -1
- package/dist/plugins/breaker-state-sql.js +46 -8
- package/dist/plugins/caching-session-store.d.ts +42 -0
- package/dist/plugins/caching-session-store.js +63 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
- package/dist/plugins/checkpoint-store-sql.js +239 -11
- package/dist/plugins/file-outcome-sink.d.ts +4 -0
- package/dist/plugins/file-outcome-sink.js +9 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
- package/dist/plugins/file-resume-anchor-store.js +43 -4
- package/dist/plugins/file-run-store.d.ts +90 -0
- package/dist/plugins/file-run-store.js +145 -13
- package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
- package/dist/plugins/file-snapshot-store-sql.js +178 -10
- package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
- package/dist/plugins/file-workflow-journal-store.js +10 -0
- package/dist/plugins/fork-routing-session-store.d.ts +47 -0
- package/dist/plugins/fork-routing-session-store.js +51 -3
- package/dist/plugins/host-platform.d.ts +69 -0
- package/dist/plugins/host-platform.js +81 -2
- package/dist/plugins/image-bake-store-sql.d.ts +136 -1
- package/dist/plugins/image-bake-store-sql.js +241 -7
- package/dist/plugins/image-index-sql.d.ts +46 -0
- package/dist/plugins/image-index-sql.js +113 -4
- package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
- package/dist/plugins/k8s-bg-scripts.js +88 -4
- package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
- package/dist/plugins/k8s-exec-protocol.js +41 -0
- package/dist/plugins/local-checkpoint-store.d.ts +40 -0
- package/dist/plugins/local-checkpoint-store.js +86 -7
- package/dist/plugins/local-session-store.d.ts +157 -0
- package/dist/plugins/local-session-store.js +289 -34
- package/dist/plugins/local-task-attachment-store.js +14 -2
- package/dist/plugins/mailbox-store-sql.d.ts +38 -0
- package/dist/plugins/mailbox-store-sql.js +39 -6
- package/dist/plugins/memory-engine-pg.d.ts +30 -0
- package/dist/plugins/memory-engine-pg.js +122 -4
- package/dist/plugins/memory-engine-tidb.d.ts +26 -0
- package/dist/plugins/memory-engine-tidb.js +115 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
- package/dist/plugins/memory-engine-vector-util.js +5 -0
- package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
- package/dist/plugins/memory-resume-anchor-store.js +17 -0
- package/dist/plugins/memory-run-store.d.ts +63 -0
- package/dist/plugins/memory-run-store.js +73 -8
- package/dist/plugins/memory-session-policy-store.d.ts +17 -0
- package/dist/plugins/memory-session-policy-store.js +17 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
- package/dist/plugins/memory-sync-store-pg.js +45 -2
- package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
- package/dist/plugins/memory-sync-store-tidb.js +31 -1
- package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
- package/dist/plugins/outcome-ledger-sql.js +97 -3
- package/dist/plugins/pg-approval-store.d.ts +7 -0
- package/dist/plugins/pg-approval-store.js +7 -0
- package/dist/plugins/pg-breaker-state.d.ts +6 -0
- package/dist/plugins/pg-breaker-state.js +6 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
- package/dist/plugins/pg-checkpoint-store.js +8 -0
- package/dist/plugins/pg-cost-quota.d.ts +17 -0
- package/dist/plugins/pg-cost-quota.js +6 -0
- package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/pg-file-snapshot-store.js +6 -0
- package/dist/plugins/pg-image-bake.d.ts +9 -0
- package/dist/plugins/pg-image-bake.js +9 -0
- package/dist/plugins/pg-image-index.d.ts +9 -0
- package/dist/plugins/pg-image-index.js +9 -0
- package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
- package/dist/plugins/pg-outcome-ledger.js +9 -0
- package/dist/plugins/pg-pool.d.ts +50 -0
- package/dist/plugins/pg-pool.js +78 -0
- package/dist/plugins/pg-query.d.ts +11 -0
- package/dist/plugins/pg-rate-limiter.d.ts +23 -0
- package/dist/plugins/pg-rate-limiter.js +12 -2
- package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/pg-resume-anchor-store.js +5 -0
- package/dist/plugins/pg-run-store.d.ts +7 -0
- package/dist/plugins/pg-run-store.js +7 -0
- package/dist/plugins/pg-safe-json.d.ts +13 -0
- package/dist/plugins/pg-safe-json.js +40 -4
- package/dist/plugins/pg-session-policy-store.d.ts +5 -0
- package/dist/plugins/pg-session-policy-store.js +5 -0
- package/dist/plugins/pg-session-storage.d.ts +72 -0
- package/dist/plugins/pg-session-storage.js +152 -20
- package/dist/plugins/pg-session-store.d.ts +10 -0
- package/dist/plugins/pg-session-store.js +10 -0
- package/dist/plugins/pg-tool-result-store.d.ts +7 -0
- package/dist/plugins/pg-tool-result-store.js +7 -0
- package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-journal-store.js +7 -0
- package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-run-store.js +7 -0
- package/dist/plugins/posix-shell-fs.d.ts +3 -0
- package/dist/plugins/posix-shell-fs.js +31 -1
- package/dist/plugins/remote-env-adb.d.ts +25 -0
- package/dist/plugins/remote-env-adb.js +95 -12
- package/dist/plugins/remote-env-e2b.d.ts +146 -0
- package/dist/plugins/remote-env-e2b.js +371 -40
- package/dist/plugins/remote-env-file-error.d.ts +34 -0
- package/dist/plugins/remote-env-file-error.js +37 -0
- package/dist/plugins/remote-env-host.d.ts +151 -0
- package/dist/plugins/remote-env-host.js +468 -70
- package/dist/plugins/remote-env-k8s.d.ts +135 -0
- package/dist/plugins/remote-env-k8s.js +318 -32
- package/dist/plugins/remote-env-local-docker.d.ts +65 -0
- package/dist/plugins/remote-env-local-docker.js +149 -24
- package/dist/plugins/remote-env-ssh.d.ts +35 -0
- package/dist/plugins/remote-env-ssh.js +117 -20
- package/dist/plugins/remote-scratchpad.d.ts +38 -0
- package/dist/plugins/remote-scratchpad.js +32 -2
- package/dist/plugins/remote-shell.d.ts +22 -0
- package/dist/plugins/remote-shell.js +27 -1
- package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
- package/dist/plugins/resume-anchor-store-sql.js +11 -0
- package/dist/plugins/roster-store-sql.d.ts +27 -0
- package/dist/plugins/roster-store-sql.js +54 -0
- package/dist/plugins/run-store-sql.d.ts +194 -0
- package/dist/plugins/run-store-sql.js +210 -9
- package/dist/plugins/s3-presign.d.ts +20 -0
- package/dist/plugins/s3-presign.js +49 -2
- package/dist/plugins/scheduler-support.d.ts +12 -0
- package/dist/plugins/scheduler-support.js +75 -3
- package/dist/plugins/send-file-ledger.d.ts +15 -0
- package/dist/plugins/send-file-ledger.js +53 -4
- package/dist/plugins/send-user-file.d.ts +77 -0
- package/dist/plugins/send-user-file.js +92 -4
- package/dist/plugins/session-policy-store-sql.d.ts +15 -0
- package/dist/plugins/session-policy-store-sql.js +75 -3
- package/dist/plugins/session-store.d.ts +45 -0
- package/dist/plugins/session-store.js +51 -0
- package/dist/plugins/sql-driver.d.ts +77 -0
- package/dist/plugins/sql-driver.js +4 -0
- package/dist/plugins/sql-escape.d.ts +8 -0
- package/dist/plugins/sql-escape.js +8 -0
- package/dist/plugins/sql-row-helpers.d.ts +25 -0
- package/dist/plugins/sql-row-helpers.js +25 -0
- package/dist/plugins/store-backend.d.ts +103 -0
- package/dist/plugins/store-backend.js +120 -27
- package/dist/plugins/store-contracts.d.ts +62 -0
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +60 -0
- package/dist/plugins/task-attachment-store.js +49 -3
- package/dist/plugins/task-list-store-sql.d.ts +25 -0
- package/dist/plugins/task-list-store-sql.js +21 -0
- package/dist/plugins/tidb-approval-store.d.ts +6 -0
- package/dist/plugins/tidb-approval-store.js +6 -0
- package/dist/plugins/tidb-breaker-state.d.ts +5 -0
- package/dist/plugins/tidb-breaker-state.js +5 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
- package/dist/plugins/tidb-checkpoint-store.js +7 -0
- package/dist/plugins/tidb-cost-quota.d.ts +6 -0
- package/dist/plugins/tidb-cost-quota.js +1 -0
- package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/tidb-file-snapshot-store.js +6 -0
- package/dist/plugins/tidb-image-bake.d.ts +8 -0
- package/dist/plugins/tidb-image-bake.js +8 -0
- package/dist/plugins/tidb-image-index.d.ts +8 -0
- package/dist/plugins/tidb-image-index.js +8 -0
- package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
- package/dist/plugins/tidb-outcome-ledger.js +10 -0
- package/dist/plugins/tidb-pool.d.ts +56 -0
- package/dist/plugins/tidb-pool.js +181 -1
- package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
- package/dist/plugins/tidb-rate-limiter.js +5 -2
- package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/tidb-resume-anchor-store.js +5 -0
- package/dist/plugins/tidb-run-store.d.ts +7 -0
- package/dist/plugins/tidb-run-store.js +7 -0
- package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
- package/dist/plugins/tidb-session-policy-store.js +5 -0
- package/dist/plugins/tidb-session-storage.d.ts +43 -0
- package/dist/plugins/tidb-session-storage.js +62 -3
- package/dist/plugins/tidb-session-store.d.ts +142 -0
- package/dist/plugins/tidb-session-store.js +264 -16
- package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
- package/dist/plugins/tidb-tool-result-store.js +7 -0
- package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/tidb-workflow-journal-store.js +7 -0
- package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
- package/dist/plugins/tidb-workflow-run-store.js +8 -0
- package/dist/plugins/tool-result-store-sql.d.ts +58 -0
- package/dist/plugins/tool-result-store-sql.js +38 -2
- package/dist/plugins/web-search.d.ts +35 -0
- package/dist/plugins/web-search.js +45 -9
- package/dist/plugins/workflow-journal-limits.d.ts +10 -0
- package/dist/plugins/workflow-journal-limits.js +10 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
- package/dist/plugins/workflow-journal-store-sql.js +59 -3
- package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
- package/dist/plugins/workflow-run-store-sql.js +84 -6
- package/dist/plugins/worktree-isolation.d.ts +45 -0
- package/dist/plugins/worktree-isolation.js +105 -6
- package/dist/plugins/write-behind-counter.d.ts +56 -1
- package/dist/plugins/write-behind-counter.js +53 -14
- package/dist/principal-jwt.d.ts +24 -0
- package/dist/principal-jwt.js +60 -5
- package/dist/project-memory.d.ts +22 -0
- package/dist/project-memory.js +144 -15
- package/dist/prompts-domain-validate.d.ts +28 -0
- package/dist/prompts-domain-validate.js +42 -1
- package/dist/question.d.ts +33 -0
- package/dist/question.js +69 -2
- package/dist/resource-suspend.d.ts +21 -0
- package/dist/resource-suspend.js +16 -0
- package/dist/router/route-orchestration.d.ts +78 -0
- package/dist/router/route-orchestration.js +77 -0
- package/dist/run-local.d.ts +32 -0
- package/dist/run-local.js +172 -8
- package/dist/runs.d.ts +189 -4
- package/dist/runs.js +465 -24
- package/dist/runtime-caps-resolver.d.ts +106 -0
- package/dist/runtime-caps-resolver.js +89 -4
- package/dist/runtime-governance.d.ts +84 -0
- package/dist/runtime-governance.js +127 -1
- package/dist/sandbox-pkg-source.d.ts +23 -0
- package/dist/sandbox-pkg-source.js +37 -0
- package/dist/sealed-key.d.ts +54 -0
- package/dist/sealed-key.js +68 -3
- package/dist/security.d.ts +108 -0
- package/dist/security.js +145 -6
- package/dist/sema-registry.d.ts +21 -0
- package/dist/sema-registry.js +21 -0
- package/dist/session-leaf-bus.d.ts +31 -0
- package/dist/session-leaf-bus.js +32 -0
- package/dist/session-sync-content.d.ts +46 -0
- package/dist/session-sync-content.js +47 -1
- package/dist/session-sync-kernel.d.ts +89 -0
- package/dist/session-sync-kernel.js +48 -3
- package/dist/session-sync.d.ts +125 -0
- package/dist/session-sync.js +181 -8
- package/dist/session-titler.d.ts +26 -0
- package/dist/session-titler.js +41 -7
- package/dist/session-watch.d.ts +53 -1
- package/dist/session-watch.js +73 -9
- package/dist/sighup-idle.d.ts +30 -0
- package/dist/sighup-idle.js +12 -1
- package/dist/spec-fields.d.ts +91 -3
- package/dist/spec-fields.js +116 -4
- package/dist/task-cwd.d.ts +50 -0
- package/dist/task-cwd.js +68 -1
- package/dist/task-mcp.d.ts +47 -0
- package/dist/task-mcp.js +29 -0
- package/dist/task-settings.d.ts +175 -0
- package/dist/task-settings.js +226 -11
- package/dist/task-workflow.d.ts +67 -0
- package/dist/task-workflow.js +75 -8
- package/dist/tool-approval.d.ts +91 -0
- package/dist/tool-approval.js +215 -0
- package/dist/trace/artifacts.d.ts +9 -0
- package/dist/trace/artifacts.js +30 -5
- package/dist/trace/core-keyset-guard.d.ts +18 -0
- package/dist/trace/ledger-sink.d.ts +43 -0
- package/dist/trace/ledger-sink.js +64 -5
- package/dist/trace/project.d.ts +133 -0
- package/dist/trace/project.js +189 -2
- package/dist/trace/redact.d.ts +19 -0
- package/dist/trace/redact.js +66 -9
- package/dist/usage-analytics.d.ts +26 -0
- package/dist/usage-analytics.js +26 -3
- package/package.json +1 -1
|
@@ -1,24 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P1 ①② — the async-workflow COMPLETION INBOX (the deployment half of "route a finished
|
|
3
|
+
* background workflow back to the session that started it").
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
* A self-orchestration / background workflow (started via core's `RunWorkflow`) runs to terminal OUTSIDE the
|
|
7
|
+
* originating session's request loop — often minutes later, possibly after that turn's SSE stream already
|
|
8
|
+
* closed. core's notifier seam + the SVC-1 {@link WorkflowNotifyGate} deliver its terminal payload to the
|
|
9
|
+
* deployment's `deliverWorkflowCompletion` AT-LEAST-ONCE, but that was a log-only shell: the model that
|
|
10
|
+
* launched the workflow never learned it finished (it had only the deterministic `WorkflowStatus` poll floor).
|
|
11
|
+
*
|
|
12
|
+
* This inbox is the PUSH half. `deliverWorkflowCompletion` resolves the originating **session + owner** (from
|
|
13
|
+
* the payload's `sourceTaskId`) and `enqueue`s the completion here; when that session next OPENS a stream (a
|
|
14
|
+
* new `POST /v1/runs` turn), the HTTP layer `drain`s the inbox and — for entries whose `owner` matches the
|
|
15
|
+
* draining caller's VERIFIED principal — emits a `workflow_complete` out-of-band SSE frame (mirroring the
|
|
16
|
+
* question/elicit frame pattern) that the shell turns into a `<task-notification>` (the shell's
|
|
17
|
+
* `enqueuePendingNotification`, deduped by runId). Delivery is thus decoupled from whether a stream was open
|
|
18
|
+
* at completion time — a disconnected-then-reconnected shell still gets it — WITHOUT a cross-loop append into
|
|
19
|
+
* the originating run's own event log (its `seq` counter is owned by that run's loop; appending from outside
|
|
20
|
+
* would race it — so completions live in this SEPARATE store).
|
|
21
|
+
*
|
|
22
|
+
* ## Tenancy
|
|
23
|
+
* Each entry carries the originating run's `owner`. The drain-side owner check (in the HTTP layer) is a
|
|
24
|
+
* defence-in-depth SECOND gate on top of the authorizer's sessionId→owner binding: a session that is deleted
|
|
25
|
+
* then re-claimed by a different tenant rotates that binding, so the stamped `owner` is what actually gates
|
|
26
|
+
* whether a completion is EMITTED. `purge(sessionId)` is called on session delete so orphaned completions
|
|
27
|
+
* don't linger. (`owner: null` = the single-user turnkey posture, no tenancy boundary.)
|
|
28
|
+
*
|
|
29
|
+
* ## Durability / delivery model
|
|
30
|
+
* - **At-least-once, shell-deduped**: the shell dedups by `runId` (`markTaskNotified`), so a completion frame
|
|
31
|
+
* re-delivered across a crash is harmless. `drain` therefore removes on read; the small window where a crash
|
|
32
|
+
* after `drain` but before the frame is flushed loses a push is covered by the deterministic `WorkflowStatus`
|
|
33
|
+
* poll floor (core 1.197) — never a silent hang.
|
|
34
|
+
* - **Crash-safe File ledger** (default): an append-only `(op) → record` journal, replayed into an in-memory
|
|
35
|
+
* `sessionId → (runId → entry)` map. A torn trailing line (crash mid-append) is dropped on replay
|
|
36
|
+
* (uncommitted). **Compacted** — rewritten from the live in-memory state on boot AND once the ledger grows
|
|
37
|
+
* past {@link COMPACT_EVERY} appends — so tombstones/evicted/drained lines don't accumulate without bound.
|
|
38
|
+
* Owner-only perms (identity data).
|
|
39
|
+
* - **Bounded**: at most {@link MAX_PENDING_PER_SESSION} newest completions are retained per session; the
|
|
40
|
+
* oldest is dropped (with a warn on LIVE enqueue — suppressed during replay to avoid log noise).
|
|
41
|
+
*/
|
|
1
42
|
import { mkdirSync, readFileSync, existsSync, openSync, writeSync, fsyncSync, closeSync, renameSync } from "node:fs";
|
|
2
43
|
import { join } from "node:path";
|
|
3
44
|
import { redactSecrets } from "../trace/redact.js";
|
|
45
|
+
/** Cap on retained pending completions per session (drop-oldest beyond it). A session with more than this many
|
|
46
|
+
* undelivered workflow completions is pathological; the poll floor still covers the dropped ones. */
|
|
4
47
|
export const MAX_PENDING_PER_SESSION = 100;
|
|
48
|
+
/** Compact the File ledger (rewrite from live state) once this many records have been appended since the last
|
|
49
|
+
* compaction — bounds on-disk size + boot replay cost for a long-lived process with high workflow churn. */
|
|
5
50
|
export const COMPACT_EVERY = 512;
|
|
51
|
+
/** How long after a `purge` an `enqueue` for the same session is refused (the purge-vs-enqueue fence): a
|
|
52
|
+
* workflow completion landing AFTER its session was deleted must not resurrect as a live entry the next
|
|
53
|
+
* claimant of that sessionId would see. In-memory only (a restart forgets the fence — the cross-restart race
|
|
54
|
+
* window is negligible and the drain-side owner gate still stands as the real tenancy boundary). */
|
|
6
55
|
export const PURGE_FENCE_MS = 10 * 60 * 1000;
|
|
56
|
+
/** How long after {@link WorkflowCompletionInbox.markTerminalServed} an `enqueue` for the SAME (session,
|
|
57
|
+
* runId) is refused — covers the notify path lagging the model's poll (the notify gate is async +
|
|
58
|
+
* at-least-once, so the completion can land in the inbox AFTER the poll already told the model). Long enough
|
|
59
|
+
* to swallow any realistic notify lag incl. a boot-recovery re-fire racing a fresh poll; per-runId scoped, so
|
|
60
|
+
* it can never starve a DIFFERENT workflow's push. In-memory only (see {@link PURGE_FENCE_MS} rationale). */
|
|
7
61
|
export const SERVED_FENCE_MS = 30 * 60 * 1000;
|
|
62
|
+
/** design/144 §4 server 折叠键:`JSON([域, taskId, RAW status])[#seq]`。域=external 帧独立 lane(task_id 是
|
|
63
|
+
* 调用方自由串,不分域可预占内部帧键把真实子代 settle 帧吞掉)/其余内部帧统一 "task" 域。
|
|
64
|
+
*
|
|
65
|
+
* 三路复审修1(MED):status 保留 RAW,不再归一 completed/failed —— 归一让同 id 无 seq 的两条 external
|
|
66
|
+
* 通知(先 `event` 后 `cancelled`)折叠成同一把 `…:failed` 键,inbox map 里第二条 REPLACE 第一条,drain
|
|
67
|
+
* 只吐一条(entry 的存储 status 列仍归一,那是账本的双终态列,不动;RAW status 本就随 payload extras
|
|
68
|
+
* 透传)。这与 {@link taskNotificationStreamKey} 的 RAW 语义对齐(stream 侧早已保 RAW)。
|
|
69
|
+
*
|
|
70
|
+
* 编码=防碰撞元组:域/taskId/status 走 JSON 数组(调用方自由串伪造不了分隔符);`#seq` 尾折在 JSON 之外
|
|
71
|
+
* ——JSON 段恒以 `"]` 结尾,`#digits` 尾无歧义,于是 {@link foldKeyFamily} 的剥尾语义 AND SQL 双生子
|
|
72
|
+
* (tidb/pg-workflow-run-store 的 `regexp_replace(run_id, '#[0-9]+$', '')` 家族比对)原样成立——把 seq 折进
|
|
73
|
+
* JSON 元组会让 SQL 侧家族 ack/fence 永 miss(那是另一批的真库改动面,本修有意不碰)。
|
|
74
|
+
* seq 折入=逐停机周期各自成键(1.283 起 background_agent 四腿 notify 铸 cycleSeq);无 seq=单周期旧键
|
|
75
|
+
* (bash/monitor terminal/retain 降级,诚实语义)。serve-fence 侧必须用同一函数(或其家族前缀)构造,
|
|
76
|
+
* 否则键形漂移=fence 永 miss(server.ts fleet 腿的 `taskNotificationFoldKey({task_id, status})` 无 seq 形
|
|
77
|
+
* 即家族前缀,两侧同为 RAW status,依旧咬合)。 */
|
|
8
78
|
export function taskNotificationFoldKey(n) {
|
|
9
79
|
const domain = n.task_type === "external" ? "external" : "task";
|
|
10
80
|
return `${JSON.stringify([domain, n.task_id, n.status])}${n.seq !== undefined ? `#${n.seq}` : ""}`;
|
|
11
81
|
}
|
|
82
|
+
/** design/144 stream 侧去重键(core [784]④a + codex 1.189 前双审 F2)= `域:taskId:status[#seq]`。
|
|
83
|
+
* 与 {@link taskNotificationFoldKey} 同族,域二分也一致(codex F2 HIGH:external 通知的 task_id 是
|
|
84
|
+
* POST /v1/sessions/:id/notify 调用方自由串——同一条腿上,drain 出的 external 帧可与内部任务帧同形
|
|
85
|
+
* `task_id:status:seq` 撞键互吞,per-leg 键集并不豁免这个面,旧注释「没有跨调用方预占面」判错);
|
|
86
|
+
* 仍与 fold key 有意不同的一点:status 保留 RAW(killed/cancelled/event 不归一 failed)——归一会让同
|
|
87
|
+
* task 的 Monitor event 批与 failed 终态在一条腿上互吞;wire 去重要的是逐帧身份,不是 inbox 的双终态列。
|
|
88
|
+
* `seq` 折入 = 停→唤→停的第二周期同 status 帧各自成键(core 1.283 起 background_agent 四腿 notify 铸
|
|
89
|
+
* cycleSeq;缺 seq 时旧键曾把二周期 completed 帧在 observer 臂反向吞掉——core [784]④a);无 seq 的帧
|
|
90
|
+
* (bash/monitor terminal/retain 降级)保持单周期旧键=同键第二帧吞,诚实语义。task_type 缺席(旧 core
|
|
91
|
+
* 内部帧)归 "task" 域——external 帧的 task_type 三来源齐备(notify 端点 core 铸 / inbox entry extras 存
|
|
92
|
+
* / drain 帧 spread 复原),不会漏域。observer 臂 / stream 臂 / turn-open drain 登记三处必须用本函数
|
|
93
|
+
* 构键且都传 task_type,键形漂移=去重永 miss。 */
|
|
12
94
|
export function taskNotificationStreamKey(n) {
|
|
13
95
|
const domain = n.task_type === "external" ? "external" : "task";
|
|
14
96
|
return `${domain}:${n.task_id}:${n.status}${n.seq !== undefined ? `#${n.seq}` : ""}`;
|
|
15
97
|
}
|
|
98
|
+
/** Per-leg notified-key ledger cap (codex 1.189 前双审 F3):一条超长腿(海量互异 bg 通知)不许把键集
|
|
99
|
+
* 撑到无界——超出删最老插入(Map 迭代序=插入序)。远高于任何真实 per-leg 通知量;被逐键的老通知
|
|
100
|
+
* 最坏情形=同键晚到帧多写一次(at-least-once,shell 按键 dedup 兜底),绝不吞新帧。 */
|
|
16
101
|
export const NOTIFIED_KEYS_MAX = 4096;
|
|
102
|
+
/**
|
|
103
|
+
* Per-leg delivered-notification ledger(codex 1.189 前双审 F1 HIGH 的修位)。
|
|
104
|
+
* 键 = {@link taskNotificationStreamKey};值 = 「认领这把键的那次写是否真落盘」的 promise(true=成功)。
|
|
105
|
+
* 为什么是 Map<string, Promise<boolean>> 而不是 Set:bg/resume 腿的 observer 臂是 fire-and-forget append
|
|
106
|
+
* (`void append(...)`)——旧 Set 下 append 失败(SQL blip)时键已被占而盘上无帧,core pend-drain 把同
|
|
107
|
+
* payload 重放到 stream 臂时被键吞 = 通知永丢。现在 stream 臂对已见键 `await` 认领方的真实结果,false 则
|
|
108
|
+
* 照写并把键值升为 true。sync 腿的写是同步 res.write(无异步失败窗口)、drain 臂的写被
|
|
109
|
+
* emitPendingWorkflowCompletions await(失败→不 ack→下腿重投)——两者登记 resolved(true) 即可;三腿统一
|
|
110
|
+
* 用本 Map 形状,让三对臂保持逐行孪生。
|
|
111
|
+
*/
|
|
17
112
|
export class NotifiedKeys {
|
|
18
113
|
byKey = new Map();
|
|
114
|
+
/** The pending/settled outcome of the write that claimed `key`, or undefined when unclaimed. */
|
|
19
115
|
get(key) {
|
|
20
116
|
return this.byKey.get(key);
|
|
21
117
|
}
|
|
118
|
+
/** Claim (or update) `key` with the outcome of its write. Bounded: past {@link NOTIFIED_KEYS_MAX} the
|
|
119
|
+
* oldest-inserted key is dropped (F3; an update keeps the original insertion slot — good enough for a
|
|
120
|
+
* pathology cap). */
|
|
22
121
|
set(key, written) {
|
|
23
122
|
this.byKey.set(key, written);
|
|
24
123
|
while (this.byKey.size > NOTIFIED_KEYS_MAX) {
|
|
@@ -27,15 +126,27 @@ export class NotifiedKeys {
|
|
|
27
126
|
}
|
|
28
127
|
}
|
|
29
128
|
}
|
|
129
|
+
/** 家族前缀 = 折叠键只剥 `#seq` 分量(仅当尾是纯数字才剥)。新元组键的 JSON 段恒以 `"]` 结尾、taskId/
|
|
130
|
+
* status 里天然的 # 被 JSON 引号包住,`#digits` 尾无歧义=必是 seq;旧格式遗留行(`域:task:status#seq`)
|
|
131
|
+
* 与 workflow_complete 的裸 runId 键同一语义原样兼容。 */
|
|
30
132
|
export function foldKeyFamily(runId) {
|
|
31
133
|
const i = runId.lastIndexOf("#");
|
|
32
134
|
return i > 0 && /^\d+$/.test(runId.slice(i + 1)) ? runId.slice(0, i) : runId;
|
|
33
135
|
}
|
|
136
|
+
/** Collision-safe (session, runId) fence key — ids are caller-influenced strings, so a plain separator could
|
|
137
|
+
* be forged into a cross-pair collision; JSON tuple encoding cannot. */
|
|
34
138
|
const servedKey = (sessionId, runId) => JSON.stringify([sessionId, runId]);
|
|
139
|
+
/** In-memory inbox — the parity oracle + the ephemeral opt-out (WORKFLOW_RUN_STORE=memory). No persistence:
|
|
140
|
+
* a restart forgets undelivered completions (the poll floor covers them). */
|
|
35
141
|
export class InMemoryWorkflowCompletionInbox {
|
|
36
142
|
onWarn;
|
|
37
143
|
bySession = new Map();
|
|
144
|
+
/** sessionId → {at, owner}: purge stamps; enqueues within {@link PURGE_FENCE_MS} for the SAME owner are
|
|
145
|
+
* refused. Swept on BOTH enqueue and purge (a purge-heavy, enqueue-quiet process must not accumulate
|
|
146
|
+
* stamps unboundedly — review). */
|
|
38
147
|
purgedAt = new Map();
|
|
148
|
+
/** servedKey(sessionId, runId) → stamp: terminal-poll-served fences; an enqueue for the pair within
|
|
149
|
+
* {@link SERVED_FENCE_MS} is refused (the poll already told the model). Swept alongside the purge fence. */
|
|
39
150
|
servedAt = new Map();
|
|
40
151
|
constructor(onWarn) {
|
|
41
152
|
this.onWarn = onWarn;
|
|
@@ -48,9 +159,16 @@ export class InMemoryWorkflowCompletionInbox {
|
|
|
48
159
|
if (now - at > SERVED_FENCE_MS)
|
|
49
160
|
this.servedAt.delete(key);
|
|
50
161
|
}
|
|
162
|
+
/** True (and warns) when `entry`'s session was purged within the fence window AND the fence applies to its
|
|
163
|
+
* owner: same owner as the purged session (a late completion of the DELETED session) — or an owner-less
|
|
164
|
+
* fence (caller couldn't say), conservative. A DIFFERENT owner = a new legitimate claimant of a reused
|
|
165
|
+
* sessionId → not fenced (their push must not be starved; the drain-side owner gate stays the real
|
|
166
|
+
* tenancy boundary either way). */
|
|
51
167
|
purgeFenced(entry) {
|
|
52
168
|
const now = Date.now();
|
|
53
169
|
this.sweepFence(now);
|
|
170
|
+
// Served fence FIRST (per-runId, so it can't shadow the purge fence's owner semantics): this exact
|
|
171
|
+
// completion already reached the model through its own poll — enqueueing it would be the double delivery.
|
|
54
172
|
if (this.servedAt.has(servedKey(entry.sessionId, entry.runId)) || this.servedAt.has(servedKey(entry.sessionId, foldKeyFamily(entry.runId)))) {
|
|
55
173
|
this.onWarn?.("workflow_completion_enqueue_after_poll_served", { sessionId: entry.sessionId, runId: entry.runId });
|
|
56
174
|
return true;
|
|
@@ -59,14 +177,20 @@ export class InMemoryWorkflowCompletionInbox {
|
|
|
59
177
|
if (st === undefined)
|
|
60
178
|
return false;
|
|
61
179
|
if (st.owner !== undefined && entry.owner !== st.owner)
|
|
62
|
-
return false;
|
|
180
|
+
return false; // new claimant, different owner → pass
|
|
63
181
|
this.onWarn?.("workflow_completion_enqueue_after_purge", { sessionId: entry.sessionId, runId: entry.runId });
|
|
64
182
|
return true;
|
|
65
183
|
}
|
|
184
|
+
/** Fence FIRST (an enqueue racing the poll is refused even mid-call), then drop any already-pending
|
|
185
|
+
* entry via `ack` — the durable subclass inherits this and gets a durable ack for free. */
|
|
66
186
|
async markTerminalServed(sessionId, runId) {
|
|
67
187
|
this.sweepFence(Date.now());
|
|
68
188
|
this.servedAt.set(servedKey(sessionId, runId), Date.now());
|
|
69
189
|
await this.ack(sessionId, runId);
|
|
190
|
+
// 家族 ack(design/144 消费批):fleet 常开流帧不带 seq,而 1.283 起终态 park 是 seq 键——精确 ack
|
|
191
|
+
// 必 miss ⇒ 下次开流 drain 二次注入模型。传入 runId 视作家族前缀,清掉同 (域,taskId,status) 的全部
|
|
192
|
+
// seq 键成员。取舍如实记:同家族的更早 Monitor 批可能被连带清(终态已送达后丢进度行,低害),
|
|
193
|
+
// 换取每个 bg 终态必然双投的消除;workflow_complete 键无 # 尾,家族=自身,行为不变。
|
|
70
194
|
const q = this.bySession.get(sessionId);
|
|
71
195
|
if (q) {
|
|
72
196
|
for (const key of [...q.keys()]) {
|
|
@@ -76,14 +200,16 @@ export class InMemoryWorkflowCompletionInbox {
|
|
|
76
200
|
}
|
|
77
201
|
}
|
|
78
202
|
stampPurge(sessionId, owner) {
|
|
79
|
-
this.sweepFence(Date.now());
|
|
203
|
+
this.sweepFence(Date.now()); // bound the map on the purge side too (enqueue-quiet processes)
|
|
80
204
|
this.purgedAt.set(sessionId, { at: Date.now(), owner });
|
|
81
205
|
}
|
|
206
|
+
/** Fold one entry into the map. `warn` is false during ledger replay (a historical overflow must not re-log
|
|
207
|
+
* on every boot) and true on a live enqueue. */
|
|
82
208
|
applyEnqueue(entry, warn) {
|
|
83
209
|
let q = this.bySession.get(entry.sessionId);
|
|
84
210
|
if (!q)
|
|
85
211
|
this.bySession.set(entry.sessionId, (q = new Map()));
|
|
86
|
-
q.delete(entry.runId);
|
|
212
|
+
q.delete(entry.runId); // idempotent + re-insert at the tail (Map preserves insertion order → oldest-first)
|
|
87
213
|
q.set(entry.runId, entry);
|
|
88
214
|
while (q.size > MAX_PENDING_PER_SESSION) {
|
|
89
215
|
const oldest = q.keys().next().value;
|
|
@@ -94,13 +220,16 @@ export class InMemoryWorkflowCompletionInbox {
|
|
|
94
220
|
}
|
|
95
221
|
async enqueue(entry) {
|
|
96
222
|
if (this.purgeFenced(entry))
|
|
97
|
-
return;
|
|
223
|
+
return; // late completion of a DELETED session → drop (poll floor covers)
|
|
98
224
|
this.applyEnqueue(entry, true);
|
|
99
225
|
}
|
|
100
226
|
async pending(sessionId) {
|
|
101
227
|
const q = this.bySession.get(sessionId);
|
|
102
228
|
if (!q || q.size === 0)
|
|
103
229
|
return [];
|
|
230
|
+
// The served fence is armed SYNCHRONOUSLY in markTerminalServed (before its async ack
|
|
231
|
+
// lands), so filtering here closes the same-process race where a stream-open drain reads a still-pending
|
|
232
|
+
// entry while the ack write is in flight — the drain must never re-deliver what the fence says was served.
|
|
104
233
|
return [...q.values()].filter((e) => !this.servedAt.has(servedKey(e.sessionId, e.runId)) && !this.servedAt.has(servedKey(e.sessionId, foldKeyFamily(e.runId))));
|
|
105
234
|
}
|
|
106
235
|
async ack(sessionId, runId) {
|
|
@@ -116,6 +245,8 @@ export class InMemoryWorkflowCompletionInbox {
|
|
|
116
245
|
this.stampPurge(sessionId, purgedOwner);
|
|
117
246
|
}
|
|
118
247
|
}
|
|
248
|
+
/** File-backed crash-safe inbox (the durable default — a background workflow's completion survives a replica
|
|
249
|
+
* restart, the prerequisite for pushing it to a session that reconnects after the crash). */
|
|
119
250
|
export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox {
|
|
120
251
|
fsyncEnabled;
|
|
121
252
|
dir;
|
|
@@ -126,10 +257,12 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
126
257
|
super(onWarn);
|
|
127
258
|
this.fsyncEnabled = fsyncEnabled;
|
|
128
259
|
this.dir = join(root, "workflow-completion-inbox");
|
|
260
|
+
// 0o700 dir + 0o600 ledger: entries carry the completion summary (may reference tenant work) + sessionId +
|
|
261
|
+
// owner — owner-only, parity with the notify-journal's identity-data posture.
|
|
129
262
|
mkdirSync(this.dir, { recursive: true, mode: 0o700 });
|
|
130
263
|
this.ledgerPath = join(this.dir, "inbox.jsonl");
|
|
131
264
|
this.replay();
|
|
132
|
-
this.compact();
|
|
265
|
+
this.compact(); // rewrite the ledger to just the live pending set → tombstones/evicted lines don't survive restarts
|
|
133
266
|
}
|
|
134
267
|
replay() {
|
|
135
268
|
if (!existsSync(this.ledgerPath))
|
|
@@ -143,7 +276,7 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
143
276
|
rec = JSON.parse(line);
|
|
144
277
|
}
|
|
145
278
|
catch {
|
|
146
|
-
continue;
|
|
279
|
+
continue; // torn/partial trailing line (crash mid-append) — uncommitted, skip
|
|
147
280
|
}
|
|
148
281
|
if (rec.op === "enqueue" && rec.entry?.sessionId && rec.entry.runId)
|
|
149
282
|
this.applyEnqueue(rec.entry, false);
|
|
@@ -151,29 +284,40 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
151
284
|
const q = this.bySession.get(rec.sessionId);
|
|
152
285
|
q?.delete(rec.runId);
|
|
153
286
|
if (q && q.size === 0)
|
|
154
|
-
this.bySession.delete(rec.sessionId);
|
|
287
|
+
this.bySession.delete(rec.sessionId); // mirror the live ack — replay is a strict fold
|
|
155
288
|
}
|
|
156
289
|
else if (rec.op === "drain" && rec.sessionId)
|
|
157
|
-
this.bySession.delete(rec.sessionId);
|
|
290
|
+
this.bySession.delete(rec.sessionId); // purge today; pre-1.80 drain lines too
|
|
158
291
|
}
|
|
159
292
|
}
|
|
160
293
|
commit(rec) {
|
|
161
294
|
if (this.fd === undefined)
|
|
162
|
-
this.fd = openSync(this.ledgerPath, "a", 0o600);
|
|
295
|
+
this.fd = openSync(this.ledgerPath, "a", 0o600); // owner-only (identity/work data)
|
|
163
296
|
writeSync(this.fd, `${JSON.stringify(rec)}\n`);
|
|
164
297
|
if (this.fsyncEnabled)
|
|
165
|
-
fsyncSync(this.fd);
|
|
298
|
+
fsyncSync(this.fd); // durable BEFORE the in-memory state the caller observes
|
|
166
299
|
this.appendsSinceCompact++;
|
|
167
300
|
}
|
|
301
|
+
/** Compact iff we've appended enough since the last one. MUST be called AFTER the in-memory mutation the
|
|
302
|
+
* triggering commit records — compact() rewrites from `bySession`, so a compact fired BEFORE the caller
|
|
303
|
+
* applied its enqueue/delete would rewrite a stale snapshot and lose that record. */
|
|
168
304
|
maybeCompact() {
|
|
169
305
|
if (this.appendsSinceCompact >= COMPACT_EVERY)
|
|
170
306
|
this.compact();
|
|
171
307
|
}
|
|
308
|
+
/** Rewrite the ledger from the authoritative in-memory state — one `enqueue` record per LIVE pending entry —
|
|
309
|
+
* dropping every accumulated drain-tombstone + evicted/drained line. Atomic (temp + rename) so a crash
|
|
310
|
+
* mid-compact leaves the old ledger intact. */
|
|
172
311
|
compact() {
|
|
173
312
|
if (this.fd !== undefined) {
|
|
174
313
|
closeSync(this.fd);
|
|
175
314
|
this.fd = undefined;
|
|
176
315
|
}
|
|
316
|
+
// tmp 名带 pid(2026-07-28 核查修):两个进程共用同一数据根时(例:两个 memory 形引擎共用一个
|
|
317
|
+
// HOME——multi-instance 共享目录本不受支持,但 memory 形用户合理预期「无盘无锁」,却默默共享
|
|
318
|
+
// `~/.ai-agent`),固定 `.tmp` 名会互相 rename 走对方的 staging ⇒ 对方 renameSync ENOENT,而 compact
|
|
319
|
+
// 在 constructor 里同步跑 = **boot fatal**(真机复现:两只引擎并发 boot 恢复期)。带 pid 后各进程
|
|
320
|
+
// staging 独立,rename 原子替换回落到 last-writer-wins(账本内容级共享仍不支持,但不再拒启)。
|
|
177
321
|
const tmp = `${this.ledgerPath}.tmp.${process.pid}`;
|
|
178
322
|
const lines = [];
|
|
179
323
|
for (const q of this.bySession.values())
|
|
@@ -189,7 +333,9 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
189
333
|
finally {
|
|
190
334
|
closeSync(tfd);
|
|
191
335
|
}
|
|
192
|
-
renameSync(tmp, this.ledgerPath);
|
|
336
|
+
renameSync(tmp, this.ledgerPath); // atomic replace of the ledger
|
|
337
|
+
// fsync the DIRECTORY so the rename (a namespace change) is itself crash-durable on POSIX/macOS, not just
|
|
338
|
+
// the file contents. Best-effort: a dir that can't be opened for fsync (rare) is skipped.
|
|
193
339
|
if (this.fsyncEnabled) {
|
|
194
340
|
try {
|
|
195
341
|
const dfd = openSync(this.dir, "r");
|
|
@@ -201,35 +347,40 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
201
347
|
}
|
|
202
348
|
}
|
|
203
349
|
catch {
|
|
350
|
+
/* dir fsync unsupported/failed — the rename is still namespace-atomic; skip */
|
|
204
351
|
}
|
|
205
352
|
}
|
|
206
353
|
this.appendsSinceCompact = 0;
|
|
207
354
|
}
|
|
208
355
|
async enqueue(entry) {
|
|
209
356
|
if (this.purgeFenced(entry))
|
|
210
|
-
return;
|
|
357
|
+
return; // late completion of a DELETED session → never re-committed to the ledger
|
|
211
358
|
this.commit({ op: "enqueue", entry });
|
|
212
359
|
this.applyEnqueue(entry, true);
|
|
213
|
-
this.maybeCompact();
|
|
360
|
+
this.maybeCompact(); // AFTER applyEnqueue → compact's rewrite includes this entry
|
|
214
361
|
}
|
|
362
|
+
// `pending` is the inherited non-destructive read — deliberately NO ledger write (phase 1 of two-phase
|
|
363
|
+
// delivery leaves the durable state untouched; only the per-entry `ack` mutates it, so a crash/failed
|
|
364
|
+
// frame-write between the two re-delivers on the next leg instead of silently eating the completion).
|
|
215
365
|
async ack(sessionId, runId) {
|
|
216
366
|
const q = this.bySession.get(sessionId);
|
|
217
367
|
if (!q?.has(runId))
|
|
218
|
-
return;
|
|
219
|
-
this.commit({ op: "ack", sessionId, runId });
|
|
368
|
+
return; // idempotent — a raced double-ack (two legs) writes ONE tombstone
|
|
369
|
+
this.commit({ op: "ack", sessionId, runId }); // durable BEFORE the in-memory delete (deliver-then-ack)
|
|
220
370
|
q.delete(runId);
|
|
221
371
|
if (q.size === 0)
|
|
222
372
|
this.bySession.delete(sessionId);
|
|
223
|
-
this.maybeCompact();
|
|
373
|
+
this.maybeCompact(); // AFTER delete → compact's rewrite reflects the ack (no resurrection)
|
|
224
374
|
}
|
|
225
375
|
async purge(sessionId, purgedOwner) {
|
|
226
|
-
this.stampPurge(sessionId, purgedOwner);
|
|
376
|
+
this.stampPurge(sessionId, purgedOwner); // fence FIRST — an enqueue racing the purge is refused even mid-purge
|
|
227
377
|
if (!this.bySession.has(sessionId))
|
|
228
378
|
return;
|
|
229
|
-
this.commit({ op: "drain", sessionId });
|
|
379
|
+
this.commit({ op: "drain", sessionId }); // a purge clears the whole session (session-level tombstone)
|
|
230
380
|
this.bySession.delete(sessionId);
|
|
231
|
-
this.maybeCompact();
|
|
381
|
+
this.maybeCompact(); // AFTER delete → compact's rewrite reflects the purge
|
|
232
382
|
}
|
|
383
|
+
/** Close the ledger fd (shutdown). */
|
|
233
384
|
close() {
|
|
234
385
|
if (this.fd !== undefined) {
|
|
235
386
|
closeSync(this.fd);
|
|
@@ -237,14 +388,41 @@ export class FileWorkflowCompletionInbox extends InMemoryWorkflowCompletionInbox
|
|
|
237
388
|
}
|
|
238
389
|
}
|
|
239
390
|
}
|
|
240
|
-
|
|
391
|
+
/**
|
|
392
|
+
* P1 ①② delivery half, TWO-PHASE since 1.80: read the session's pending completions, emit
|
|
393
|
+
* one `workflow_complete` out-of-band frame per entry, and `ack` (remove) each entry only AFTER its emit
|
|
394
|
+
* SUCCEEDED — a failed frame write (dead socket / durable-append error) leaves the entry pending, so the next
|
|
395
|
+
* leg re-delivers instead of silently eating it (the 1.77-review "drained-but-append-failed" window, closed).
|
|
396
|
+
* Called at STREAM-OPEN of EVERY run leg (the sync `POST /v1/runs` SSE leg emits a live frame; the bg + resume
|
|
397
|
+
* legs append a durable `workflow_complete` event their tailing client replays). Best-effort throughout (the
|
|
398
|
+
* deterministic `WorkflowStatus` poll is the floor); at-least-once — the shell dedups by runId (two legs racing
|
|
399
|
+
* the same session may double-emit; same contract).
|
|
400
|
+
*
|
|
401
|
+
* 🔒 Tenancy (defence-in-depth over the authorizer's sessionId→owner gate): only entries whose stamped
|
|
402
|
+
* `owner` matches `callerPrincipal` are EMITTED — a session deleted-then-reclaimed by a different tenant
|
|
403
|
+
* (which rotates the sessionId→owner binding) can't surface the prior owner's completions. `owner: null`
|
|
404
|
+
* (single-user turnkey) has no tenancy boundary → always emitted. A non-matching entry is a DELIBERATE drop —
|
|
405
|
+
* acked (the session is gone/reclaimed for its original owner; the poll floor is their fallback), so it never
|
|
406
|
+
* wedges the queue.
|
|
407
|
+
*/
|
|
408
|
+
export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPrincipal, emit,
|
|
409
|
+
/** Emit-TARGET diagnostics — WHICH leg drained (route) onto WHAT kind of connection
|
|
410
|
+
* (live SSE write vs durable event-log append), plus the enqueue→emit lag, so a "workflow completed but the
|
|
411
|
+
* shell only saw it on the next manual turn" report can be pinned to the leg that finally carried the frame
|
|
412
|
+
* (the push is stream-open-driven by design — a session with NO open leg has no push path until its next
|
|
413
|
+
* leg; the lag makes that visible instead of a mystery). Info-level, one line per delivered frame. */
|
|
414
|
+
diag) {
|
|
241
415
|
if (!inbox || !sessionId)
|
|
242
416
|
return;
|
|
417
|
+
// Diagnostics must never influence delivery (1.108 review): a throwing log sink inside the try would
|
|
418
|
+
// misroute an already-delivered-and-acked frame into the emit_failed line — a lie in the very diagnostics
|
|
419
|
+
// this sink exists to provide. Swallow here; the drain semantics stay byte-identical with diag absent.
|
|
243
420
|
const dlog = (msg, meta) => {
|
|
244
421
|
try {
|
|
245
422
|
diag?.log(msg, meta);
|
|
246
423
|
}
|
|
247
424
|
catch {
|
|
425
|
+
/* never let diagnostics fault the drain */
|
|
248
426
|
}
|
|
249
427
|
};
|
|
250
428
|
let entries;
|
|
@@ -252,28 +430,34 @@ export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPri
|
|
|
252
430
|
entries = await inbox.pending(sessionId);
|
|
253
431
|
}
|
|
254
432
|
catch {
|
|
255
|
-
return;
|
|
433
|
+
return; // best-effort push — the model can always fall back to the WorkflowStatus poll
|
|
256
434
|
}
|
|
257
435
|
for (const e of entries) {
|
|
258
436
|
try {
|
|
259
437
|
if (e.owner !== null && e.owner !== callerPrincipal) {
|
|
260
|
-
await inbox.ack(sessionId, e.runId);
|
|
438
|
+
await inbox.ack(sessionId, e.runId); // 🔒 not this caller's completion → deliberate drop (ack, never emit)
|
|
261
439
|
dlog("workflow_complete_dropped_owner_mismatch", { route: diag?.route, sessionId, runId: e.runId });
|
|
262
440
|
continue;
|
|
263
441
|
}
|
|
264
442
|
if (e.kind === "task_notification") {
|
|
443
|
+
// A background subagent/bash completion parked while the session was idle — drain it as the
|
|
444
|
+
// SAME frame family the live legs emit (`task_notification`, task_id keyed), so the shell's existing
|
|
445
|
+
// renderer + dedup consume it unchanged. Extras (task_type/result/…) were bounded+redacted at enqueue.
|
|
265
446
|
let extras = {};
|
|
266
447
|
try {
|
|
267
448
|
extras = e.payload ? JSON.parse(e.payload) : {};
|
|
268
449
|
}
|
|
269
450
|
catch {
|
|
451
|
+
/* a torn payload degrades to the summary-only frame */
|
|
270
452
|
}
|
|
453
|
+
// `injected: false` AFTER the extras spread:恒量判别位([1818]§四 (b)案,与 project.ts live 投影
|
|
454
|
+
// 同义)——parked 通知更如实:park 发生恰因 leg 已死、core 的注入无处可去。
|
|
271
455
|
await emit({ type: "task_notification", task_id: e.runId, status: e.status, summary: e.summary, ...extras, injected: false });
|
|
272
456
|
}
|
|
273
457
|
else {
|
|
274
458
|
await emit({ type: "workflow_complete", runId: e.runId, status: e.status, summary: e.summary });
|
|
275
459
|
}
|
|
276
|
-
await inbox.ack(sessionId, e.runId);
|
|
460
|
+
await inbox.ack(sessionId, e.runId); // phase 2: the frame durably landed → remove
|
|
277
461
|
dlog("workflow_complete_emitted", {
|
|
278
462
|
route: diag?.route,
|
|
279
463
|
connection: diag?.connection,
|
|
@@ -284,22 +468,51 @@ export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPri
|
|
|
284
468
|
});
|
|
285
469
|
}
|
|
286
470
|
catch (err) {
|
|
471
|
+
// emit failed → NOT acked → re-delivered on the next leg (at-least-once; a dead socket / failed append
|
|
472
|
+
// must not fault the turn). An ack failure after a successful emit re-delivers too — same contract.
|
|
287
473
|
dlog("workflow_complete_emit_failed", { route: diag?.route, connection: diag?.connection, sessionId, runId: e.runId, err: err instanceof Error ? err.message : String(err) });
|
|
288
474
|
}
|
|
289
475
|
}
|
|
290
476
|
}
|
|
291
|
-
|
|
477
|
+
/**
|
|
478
|
+
* The durable half is the deployment's job: map a core `TaskNotificationPayload`
|
|
479
|
+
* (background subagent / background bash completion) onto an inbox entry, for the IDLE gap — core's in-process
|
|
480
|
+
* injection covers a LIVE harness; a completion that lands after the leg ended had NO durable path (idle no-show,
|
|
481
|
+
* lost across restart). Bounded + redacted HERE (the store carries only egress-safe bytes; summary/result are
|
|
482
|
+
* UNTRUSTED free text). `status` collapses onto the inbox's completed|failed column; the ORIGINAL status rides
|
|
483
|
+
* the payload extras and overrides on drain (killed/cancelled reach the shell verbatim). Workflow-type
|
|
484
|
+
* notifications are the CALLER's job to filter (they have their own durable chain — double-enqueueing here would
|
|
485
|
+
* re-open the poll-served double-push).
|
|
486
|
+
*/
|
|
487
|
+
export function taskNotificationInboxEntry(sessionId, owner, n, enqueuedAt,
|
|
488
|
+
/** lead-b (dual-ID audit): the PARENT run's durable taskId (the uuid the shell's footer rows
|
|
489
|
+
* key on) — rides the drained frame so a consumer can bind the a*-keyed child notification to its uuid-keyed
|
|
490
|
+
* parent row without a side lookup. */
|
|
491
|
+
parentTaskId) {
|
|
292
492
|
const cap = (s, max) => (s.length > max ? `${s.slice(0, max)}…[+${s.length - max} chars]` : s);
|
|
293
493
|
const extras = {
|
|
294
494
|
task_type: n.task_type,
|
|
295
495
|
...(parentTaskId ? { parentTaskId } : {}),
|
|
496
|
+
// core 1.238: the payload's own sessionId — the stable a*↔local-uuid cross-system anchor
|
|
497
|
+
// (same handle TaskResult.sessionId/fleet rows carry). Optional (bash lane has none).
|
|
296
498
|
...(n.sessionId ? { sessionId: n.sessionId } : {}),
|
|
297
499
|
...(n.toolUseId ? { toolUseId: n.toolUseId } : {}),
|
|
298
|
-
...(n.status !== "completed" && n.status !== "failed" ? { status: n.status } : {}),
|
|
500
|
+
...(n.status !== "completed" && n.status !== "failed" ? { status: n.status } : {}), // original rides through
|
|
501
|
+
// core drain 案: the Monitor/stoppedBy fields must survive the PARK too, or a drained event batch
|
|
502
|
+
// arrives with no dedup discriminator (`seq` — core's taskNotificationDedupKey folds it in), no line text,
|
|
503
|
+
// and no stop attribution. Same trust posture as the live projection (project.ts): seq/stoppedBy verbatim
|
|
504
|
+
// (core counter / open enum), lines = watched-process stdout → redact + cap each line.
|
|
299
505
|
...(n.seq !== undefined ? { seq: n.seq } : {}),
|
|
506
|
+
// 对抗评审 2026-07-11:redact BEFORE cap — capping first could bisect a secret so its residue no longer
|
|
507
|
+
// matches any redact pattern (a half-token is still a usable prefix). Order: redact, then bound.
|
|
300
508
|
...(n.lines !== undefined ? { lines: n.lines.map((l) => cap(redactSecrets(l), 1024)) } : {}),
|
|
301
509
|
...(n.stoppedBy !== undefined ? { stoppedBy: n.stoppedBy } : {}),
|
|
302
|
-
...(n.diagnostics !== undefined ? { diagnostics: cap(n.diagnostics, 4_000) } : {}),
|
|
510
|
+
...(n.diagnostics !== undefined ? { diagnostics: cap(n.diagnostics, 4_000) } : {}), // [1412] 诊断槽(bounded;引擎值单源,park 原样过)
|
|
511
|
+
// (core 1.262.0 残局字段的 PARK 半场——live 腿 1.154.0 已放行,但「用户杀 bg 子代」的
|
|
512
|
+
// 主动线恰是 between-turns,通知落这里):与 project.ts 同一 trust posture——recentSteps.tool verbatim,
|
|
513
|
+
// target/outcome UNTRUSTED(命令行/工具输出首行可为 secret 形)→ redact+cap;editedFiles.path → redact+cap
|
|
514
|
+
// (attachedFiles 姿势),edits 数字 verbatim;resumable boolean verbatim。字段挑取绝不 spread;数组防御
|
|
515
|
+
// 截断(core 上游本就 bound ≤10 步——slice 是入库前的双保险)。
|
|
303
516
|
...(n.recentSteps !== undefined
|
|
304
517
|
? { recentSteps: n.recentSteps.slice(0, 10).map((s) => ({ tool: s.tool, target: cap(redactSecrets(s.target), 512), outcome: cap(redactSecrets(s.outcome), 512) })) }
|
|
305
518
|
: {}),
|
|
@@ -313,14 +526,43 @@ export function taskNotificationInboxEntry(sessionId, owner, n, enqueuedAt, pare
|
|
|
313
526
|
return {
|
|
314
527
|
sessionId,
|
|
315
528
|
owner,
|
|
529
|
+
// 对抗评审 2026-07-11(MED):the inbox key is REPLACE-on-runId (the workflow_complete contract) — keying a
|
|
530
|
+
// task_notification by the bare task_id COLLAPSES multiple between-turns notifications of the same bg task
|
|
531
|
+
// (a Monitor event batch, then the killed terminal: the terminal REPLACED the batch, drain emitted 1 of 2).
|
|
532
|
+
// Fold `seq` into the key (core's own taskNotificationDedupKey folds seq for exactly this reason); the frame's
|
|
533
|
+
// task_id comes from the extras override below, so the wire shape is unchanged. Same-key retries still dedup.
|
|
534
|
+
// design/144 §4 键升级:(域,taskId,status,seq?)——status 进键且保 RAW(修1:归一曾让同 id 的
|
|
535
|
+
// event/cancelled 两条 external 通知互吞),external 分域([769]①),seq 折入(逐周期)。
|
|
536
|
+
// wire 形状不变(task_id 从 extras override 恢复);存储 status 列(下一行)仍归一双终态,不受键影响。
|
|
316
537
|
runId: taskNotificationFoldKey(n),
|
|
317
538
|
status: n.status === "completed" ? "completed" : "failed",
|
|
318
539
|
summary: cap(redactSecrets(n.summary ?? ""), 4000),
|
|
319
540
|
enqueuedAt,
|
|
320
541
|
kind: "task_notification",
|
|
321
|
-
payload: JSON.stringify({ ...extras, task_id: n.task_id }),
|
|
542
|
+
payload: JSON.stringify({ ...extras, task_id: n.task_id }), // task_id rides extras → drain's spread restores it over the keyed runId
|
|
322
543
|
};
|
|
323
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Resolve WHERE a workflow completion routes (core 1.208): the SESSION comes straight off
|
|
547
|
+
* `originatingSessionId` (the Runner closes over it at RunWorkflow mount — lookup-free). The OWNER comes from,
|
|
548
|
+
* in order:
|
|
549
|
+
* 1. the originating RUN ROW's owner (`getRun(sourceTaskId)`, 1.75) — immutable, stamped at submit. Only the
|
|
550
|
+
* BG leg has a run-row `sourceTaskId` though: the sync + resume legs deliberately leave `spec.taskId` unset
|
|
551
|
+
* (subagent nesting), so core derives `sourceTaskId = sessionId` and this lookup MISSES for them (review
|
|
552
|
+
* review, 1.78) — which under the old rule silently killed the multi-tenant push for every sync-leg workflow;
|
|
553
|
+
* 2. the payload's `principal` — core threads `spec.principal`, which in THIS service is ALWAYS the VERIFIED
|
|
554
|
+
* identity (the 1.55 F-fix invariant, main.ts: gated door = BFF-verified header, direct door = JWT `sub`,
|
|
555
|
+
* never the spoofable header; core 1.187 hardened the `TaskSpec.principal` contract to exactly this).
|
|
556
|
+
* Captured at SPAWN time → immutable, no delete-then-reclaim window. This is also the same value core's own
|
|
557
|
+
* workflow scope guard (`canAccessWorkflowRun`) keys off;
|
|
558
|
+
* 3. neither → single-user stamps `owner: null` (no tenancy boundary); multi-tenant (`requirePrincipal`)
|
|
559
|
+
* returns undefined = DROP fail-closed (an unverifiable owner must not ride the reclaim gate; the
|
|
560
|
+
* `WorkflowStatus` poll floor covers). Returns undefined when there is no route at all.
|
|
561
|
+
* The `getRun` lookup also carries the session FALLBACK for a crash-RECOVERY notify payload that lacks
|
|
562
|
+
* `originatingSessionId` — since core 1.210 the durable `WorkflowRun` records it and the recovery
|
|
563
|
+
* re-derivation threads it through (workflow-notify-journal), so this fallback covers only pre-1.210 rows
|
|
564
|
+
* (or a writer that omitted it), not the recovery path wholesale.
|
|
565
|
+
*/
|
|
324
566
|
export async function resolveCompletionRoute(p, getRun, requirePrincipal) {
|
|
325
567
|
let sessionId = p.originatingSessionId;
|
|
326
568
|
let owner;
|
|
@@ -329,23 +571,31 @@ export async function resolveCompletionRoute(p, getRun, requirePrincipal) {
|
|
|
329
571
|
const originating = await getRun(p.sourceTaskId);
|
|
330
572
|
if (originating) {
|
|
331
573
|
sessionId ??= originating.sessionId ?? undefined;
|
|
332
|
-
owner = originating.owner ?? null;
|
|
574
|
+
owner = originating.owner ?? null; // the run row's owner — stamped at submit, immutable
|
|
333
575
|
}
|
|
334
576
|
}
|
|
335
577
|
catch {
|
|
578
|
+
// lookup outage: fall through — the session may still route via the payload; owner resolves below
|
|
336
579
|
}
|
|
337
580
|
}
|
|
338
581
|
if (!sessionId)
|
|
339
|
-
return undefined;
|
|
582
|
+
return undefined; // no route → poll floor
|
|
340
583
|
if (owner === undefined && p.principal !== undefined)
|
|
341
|
-
owner = p.principal;
|
|
584
|
+
owner = p.principal; // VERIFIED spec.principal (see doc #2)
|
|
342
585
|
if (owner === undefined) {
|
|
343
586
|
if (requirePrincipal)
|
|
344
|
-
return undefined;
|
|
345
|
-
owner = null;
|
|
587
|
+
return undefined; // multi-tenant + no verifiable owner → fail-closed (poll floor)
|
|
588
|
+
owner = null; // single-user turnkey: no tenancy boundary
|
|
346
589
|
}
|
|
347
590
|
return { sessionId, owner };
|
|
348
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* core 1.232 — resolve WHICH session's inbox entry a poll-served ack targets: the payload's
|
|
594
|
+
* `originatingSessionId` directly (core threads it on `ackServed`), else the originating run row via
|
|
595
|
+
* `sourceTaskId` (mirrors {@link resolveCompletionRoute}'s session half — same fallback, same reason:
|
|
596
|
+
* older runs may predate the lookup-free field). Returns undefined when there is no attribution — the
|
|
597
|
+
* entry stays pending (at-least-once; the shell's own dedup covers a rare duplicate).
|
|
598
|
+
*/
|
|
349
599
|
export async function resolveServedSession(p, getRun) {
|
|
350
600
|
if (p.originatingSessionId)
|
|
351
601
|
return p.originatingSessionId;
|
|
@@ -354,6 +604,7 @@ export async function resolveServedSession(p, getRun) {
|
|
|
354
604
|
return (await getRun(p.sourceTaskId))?.sessionId ?? undefined;
|
|
355
605
|
}
|
|
356
606
|
catch {
|
|
607
|
+
/* lookup outage → no attribution; the poll floor + shell dedup cover */
|
|
357
608
|
}
|
|
358
609
|
}
|
|
359
610
|
return undefined;
|