@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
package/dist/runs.js
CHANGED
|
@@ -6,6 +6,44 @@ import { createLedgerSink } from "./trace/ledger-sink.js";
|
|
|
6
6
|
import { fleetRunResiduals } from "./fleet/fleet-bus.js";
|
|
7
7
|
import { defaultSubagentTailBus, projectTailFrame } from "./fleet/subagent-tail-bus.js";
|
|
8
8
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
|
|
9
|
+
/**
|
|
10
|
+
* stoppedBy (core 1.252): a service cancel is a USER stop — mark the run's still-running background
|
|
11
|
+
* children BEFORE core's teardown reaps them (bare abort = attribution falls back to "system"; core's own
|
|
12
|
+
* reap marks "parent", but first-marker-wins means our earlier "user" is the one that lands). Register this
|
|
13
|
+
* FIRST on the cancel signal so it runs ahead of any later-registered core listener (listener-order
|
|
14
|
+
* guarantee); one registration covers every abort route on that signal (same-replica fast path + the
|
|
15
|
+
* cross-replica flag poll). Advisory only — the cancel itself must never fail on attribution.
|
|
16
|
+
*
|
|
17
|
+
* Access = TASK-scoped children ONLY: owner = the parent taskId (on a RESUME leg: the sessionId — core's
|
|
18
|
+
* canonical taskId there), scope = `principal ?? "default"` (core registers children under
|
|
19
|
+
* `spec.principal ?? "default"`, so an anonymous run's children live in scope "default" and an access
|
|
20
|
+
* WITHOUT scope matched nothing). Session-scoped children are DELIBERATELY not marked: a cancelled parent
|
|
21
|
+
* does NOT take them down (CC Backgrounded semantics — core's teardown reaps with skipSessionScoped), so
|
|
22
|
+
* pre-marking them "user" would misattribute a stop that isn't happening.
|
|
23
|
+
*
|
|
24
|
+
* core 1.256: `markStopSourceForOwner` replaces the old `list()+markStopSource`
|
|
25
|
+
* per-row loop, closing BOTH documented holes — ① the EXPLICIT `sessionScoped` flag
|
|
26
|
+
* (`skipSessionScoped:true`) makes the resume leg safe (its owner key == sessionId COINCIDES with
|
|
27
|
+
* session-scoped children's; the old code couldn't tell them apart and took an honest degrade instead),
|
|
28
|
+
* ② a direct handle-map walk (no `list()` 500-row display cap under-marking >500-children runs). Covers
|
|
29
|
+
* core's full markable set (background_agent / background_bash / monitor); running-only +
|
|
30
|
+
* first-marker-wins guards live inside the seam.
|
|
31
|
+
*/
|
|
32
|
+
/** [1488]③(b) + codex R2: the bg agent-handle OUTPUT read, kind-gated BEFORE any poll. `pollTask` is the
|
|
33
|
+
* generic registry face and polling is NOT side-effect-free for other kinds — a terminal workflow poll
|
|
34
|
+
* fires `onServedTerminal` (acknowledges the completion as served → suppresses its notification) and a
|
|
35
|
+
* bash poll advances the output cursor. `getAccessibleTask` is a pure lookup: resolve + access-check +
|
|
36
|
+
* type-check FIRST; only a `background_agent` row is ever polled (non-blocking). Everything else returns
|
|
37
|
+
* the registry's own not_found shape, indistinguishable from an unknown handle (the route 404s it).
|
|
38
|
+
*
|
|
39
|
+
* 1.250 durable arm (core 1.364, [1511] follow-up delivered): a FULL in-process miss (`!row`) falls
|
|
40
|
+
* through to `pollTask(…, { agentStore })` — core's durable arm only fires for `a*`-shaped handles
|
|
41
|
+
* (DURABLE_AGENT_HANDLE_RE) behind `canAccessAgentRecord`, and with NO in-process handle the live
|
|
42
|
+
* workflow/bash branches are unreachable, so the kind-gate's reason (onServedTerminal / cursor advance)
|
|
43
|
+
* cannot trigger. Terminal rows serve the durable snapshot cross-instance/post-restart; a foreign-writer
|
|
44
|
+
* running row answers the honest "outcome unknown here"; anything else is core's own not_found (byte-same
|
|
45
|
+
* route 404 as today). A row that IS in-process but of the wrong type stays a hard 404 — the process
|
|
46
|
+
* knows the truth, falling back would sidestep the kind gate. */
|
|
9
47
|
export async function backgroundAgentOutput(registry, handle, access, agentStore) {
|
|
10
48
|
const row = registry.getAccessibleTask(handle, access);
|
|
11
49
|
if (!row || row.type !== "background_agent") {
|
|
@@ -15,20 +53,54 @@ export async function backgroundAgentOutput(registry, handle, access, agentStore
|
|
|
15
53
|
}
|
|
16
54
|
return registry.pollTask(handle, access);
|
|
17
55
|
}
|
|
56
|
+
/** [1499] CC TaskOutput 人侧对位 — the GENERIC task-handle read face (b* bash stdout — NB whether a read
|
|
57
|
+
* consumes the output cursor depends on the handle's shape: spooled handles re-read in full [GET-idempotent],
|
|
58
|
+
* cursor-only handles return new bytes per read; the projection's flags are authoritative; m* monitor
|
|
59
|
+
* batches; a* agent final report). No filter option: caller-supplied regexes are refused at the route (a
|
|
60
|
+
* backtracking engine on the shared event loop must not run wire-supplied patterns; client-side filtering
|
|
61
|
+
* sees only the clipped projection — full-fidelity filtered reads stay an in-engine tool capability).
|
|
62
|
+
* Addressing is exact task_id only (the tools' name/legacy-id resolution stays off the wire). Kind-gate
|
|
63
|
+
* FIRST on the pure getAccessibleTask (codex-R2 shape — pollTask is not side-effect-free): `workflow` is
|
|
64
|
+
* refused — its HTTP read face is the workflow journal ([1491] contract), and a pollTask here would fire
|
|
65
|
+
* onServedTerminal (ack the completion as served) and suppress the workflow completion push. Refusals
|
|
66
|
+
* return the registry's own not_found shape, indistinguishable from an unknown handle (the route 404s it). */
|
|
18
67
|
const TASK_HANDLE_KINDS = new Set(["background_bash", "monitor", "background_agent"]);
|
|
68
|
+
/** 1.250 复审 F1(HIGH):durable 回落的**形判门**——`getAccessibleTask` 是精确键(不 trim、不查 legacy
|
|
69
|
+
* 别名),而 core `pollTask/stopTask` 是通用解析面(先 trim、`resolveInternal` 含 legacyToTaskId、
|
|
70
|
+
* stopTask 还有 byName 臂)。裸 `!row` 回落会让垫充空白句柄(" "+wfId)/legacy shellId/agent 名字从
|
|
71
|
+
* 回落缝穿进 live 分支——kind 门要防的 onServedTerminal/游标消费/按名 stop 全部复活(复审实测复现)。
|
|
72
|
+
* 只有 **a\* 精确形**才回落:`DURABLE_AGENT_HANDLE_RE` = core 公共导出(core 1.369,[1518] ask 即答
|
|
73
|
+
* ——两边判词同源,镜像私拷已删)。残余(documented):恰为 a\* 形的 agent **名字**
|
|
74
|
+
* 在无 durable 行时仍会进 core stopTask 的 byName 臂——同 access 元组内、名字域与句柄域同形的病态窄面,
|
|
75
|
+
* 接受。 */
|
|
19
76
|
const DURABLE_AGENT_HANDLE_SHAPE = DURABLE_AGENT_HANDLE_RE;
|
|
20
77
|
export async function taskHandleOutput(registry, handle, access, agentStore) {
|
|
21
78
|
const row = registry.getAccessibleTask(handle, access);
|
|
22
79
|
if (!row || !TASK_HANDLE_KINDS.has(row.type)) {
|
|
80
|
+
// 1.250 durable arm — full in-process miss + a* 精确形才回落(F1 形判门,DURABLE_AGENT_HANDLE_SHAPE
|
|
81
|
+
// 顶注;b*/m* 无 durable 面照旧 not_found;进程内命中但型错 = 真相已知,不回落)。
|
|
23
82
|
if (!row && agentStore && DURABLE_AGENT_HANDLE_SHAPE.test(handle))
|
|
24
83
|
return registry.pollTask(handle, access, { agentStore });
|
|
25
84
|
return { content: "", details: { task_id: handle, retrieval_status: "not_ready", error: "not_found" } };
|
|
26
85
|
}
|
|
27
86
|
return registry.pollTask(handle, access);
|
|
28
87
|
}
|
|
88
|
+
/** [1499] CC TaskStop 人侧对位 — stop a background task handle (bash kill / monitor stop / agent abort).
|
|
89
|
+
* Same kind gate as the read face (a workflow stops via its own cancel face, never this verb). stopTask is
|
|
90
|
+
* idempotent on a terminal handle (the registry answers honestly); the stop is attributed "user" via the
|
|
91
|
+
* registry's existing markStopSource machinery (an HTTP stop is the human's hand — parity with the shell's
|
|
92
|
+
* TaskStop attribution). The pre-mark is first-write-wins and MUST come first (core's own stop path marks
|
|
93
|
+
* "parent" internally — marking after would lose the attribution race on success, the common path); the
|
|
94
|
+
* trade (codex R3) is that on a kill-that-didn't-land the pending "user" mark is not clearable through the
|
|
95
|
+
* public registry face — the route surfaces that arm as 409 stop.not_landed, and the atomic fix (a `source`
|
|
96
|
+
* option on stopTask) is a core ask. */
|
|
29
97
|
export async function taskHandleStop(registry, handle, access, agentStore) {
|
|
30
98
|
const row = registry.getAccessibleTask(handle, access);
|
|
31
99
|
if (!row || !TASK_HANDLE_KINDS.has(row.type)) {
|
|
100
|
+
// 1.250 durable arm — core stopTask's agentStore branch answers honestly (terminal row: "already
|
|
101
|
+
// <status> — nothing to stop"; foreign-writer running row: not_local, no kill is attempted). NO
|
|
102
|
+
// markStopSource here: there is no in-process kill to attribute, and the durable answer never reads
|
|
103
|
+
// as a landed stop. F1 形判门同 output 面(stopTask 还有 byName 臂——非 a* 形一律不进通用解析面)。
|
|
32
104
|
if (!row && agentStore && DURABLE_AGENT_HANDLE_SHAPE.test(handle))
|
|
33
105
|
return registry.stopTask(handle, access, { agentStore });
|
|
34
106
|
return { content: "", details: { task_id: handle, retrieval_status: "not_ready", error: "not_found" } };
|
|
@@ -42,9 +114,15 @@ export function markChildrenStoppedByUserOnAbort(signal, taskId, principal) {
|
|
|
42
114
|
defaultTaskRegistry.markStopSourceForOwner({ owner: taskId, scope: principal ?? "default" }, "user", { skipSessionScoped: true });
|
|
43
115
|
}
|
|
44
116
|
catch {
|
|
117
|
+
/* attribution is advisory — the cancel itself must never fail on it */
|
|
45
118
|
}
|
|
46
119
|
}, { once: true });
|
|
47
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* A session CAS conflict is swallowed by core into a failed `TaskResult` (it is not thrown from
|
|
123
|
+
* `runTaskStream`), so detect it from the result. Prefer the structured `errorCode` (core ≥1.8); fall
|
|
124
|
+
* back to the conflict wording only for older results without one.
|
|
125
|
+
*/
|
|
48
126
|
export function isSessionConflictResult(result) {
|
|
49
127
|
if (result.status !== "failed")
|
|
50
128
|
return false;
|
|
@@ -52,6 +130,23 @@ export function isSessionConflictResult(result) {
|
|
|
52
130
|
return result.errorCode === "conflict";
|
|
53
131
|
return /stale write|concurrent write|conflict/i.test(result.errorMessage ?? "");
|
|
54
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* E18 resume-at CALLER error → an HTTP 4xx. A bad/stale resume-at target (the resolved entryId is gone or is not a
|
|
135
|
+
* settled message boundary) surfaces as a FAILED TaskResult carrying a `resume_at.*` errorCode (core swallows the
|
|
136
|
+
* prepare-throw into the result, exactly like a session conflict — runtask catch → status:"failed"+errorCode). The
|
|
137
|
+
* sync path otherwise 200s a failed result; map these to 4xx so a caller mistake reads as one. `not_found` (the
|
|
138
|
+
* entry is genuinely absent) → 404; everything else (not_a_message / conflicts_resume / no_session) → 422.
|
|
139
|
+
*
|
|
140
|
+
* design/114 Phase3: `requireExistingSession` on a genuinely-missing/purged session ALSO surfaces as a failed result
|
|
141
|
+
* (core prepare-task maps the store's `not_found` → `resume.session_not_found`); map it to 404 (the session is gone),
|
|
142
|
+
* the same "caller mistake reads as a 4xx, not a silent 200 fresh run" discipline.
|
|
143
|
+
*
|
|
144
|
+
* [833] rewind exclusive mode (core 1.292 resumeAtMode:"before"): its two edge rejections ride the same `resume_at.*`
|
|
145
|
+
* prefix (before_target_not_user / before_root_unsupported → 422 via the existing branch, errorCode passed through
|
|
146
|
+
* UNCHANGED for the shell). `rewind_snapshot.unresolvable` (rewindFiles + "before": no snapshot at/above the branch
|
|
147
|
+
* point) is core's third rejection with a DIFFERENT prefix — same caller-mistake shape (prepare-throw, nothing
|
|
148
|
+
* billed), map it to 422 explicitly so it doesn't fall through to a 200-with-failed body.
|
|
149
|
+
*/
|
|
55
150
|
export function resumeAtHttpStatus(result) {
|
|
56
151
|
if (result.status !== "failed")
|
|
57
152
|
return undefined;
|
|
@@ -63,31 +158,71 @@ export function resumeAtHttpStatus(result) {
|
|
|
63
158
|
return undefined;
|
|
64
159
|
return result.errorCode === "resume_at.not_found" ? 404 : 422;
|
|
65
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* E18 resume-at — per-turn anchor capture, shared by runInBackground AND the resume-leg driveResume so the (single,
|
|
163
|
+
* tricky) capture rule can't drift between the two stream-drain loops.
|
|
164
|
+
*
|
|
165
|
+
* A "turn" in core = ONE assistant message + its tool batch. resume-at is valid ONLY at a SETTLED rewindable boundary
|
|
166
|
+
* — core's prepare-task accepts "user messages or finished assistant-text turns" and REJECTS an assistant message that
|
|
167
|
+
* ends mid-tool-call (resume_at.not_a_message). So we capture an anchor ONLY for a turn that produced text AND ran NO
|
|
168
|
+
* tools: then the turn's leaf at turn_end IS that assistant-text message (the rewindable target). For a tool-using
|
|
169
|
+
* turn neither candidate works — at turn_end the leaf is the turn's LAST tool-result (core appends tool results before
|
|
170
|
+
* emitting turn_end; a tool-result is stored as a `message` so core would SILENTLY branch AFTER the tools ran), and
|
|
171
|
+
* the assistant message itself ends mid-tool-call so core would reject it. The shell still gets a per-message eventId
|
|
172
|
+
* handle on every text event (E2 identity); a rewind to a non-captured (tool-using) message simply 404s — honest,
|
|
173
|
+
* never a silent wrong-point branch. (The adversarial-review HIGH: capturing at turn_end unconditionally mis-anchored
|
|
174
|
+
* every tool-using turn — the common agentic shape — to the tool-result leaf.)
|
|
175
|
+
*
|
|
176
|
+
* Best-effort: a capture failure must NEVER fail the run (it only makes that one turn non-rewindable). `onCaptureFail`
|
|
177
|
+
* surfaces the degrade (a metric) so a SYSTEMATIC failure — e.g. an un-migrated DB missing `resume_anchor` — is
|
|
178
|
+
* observable instead of a silent feature outage.
|
|
179
|
+
*/
|
|
66
180
|
export class TurnAnchorCapture {
|
|
67
181
|
capture;
|
|
68
182
|
onCaptureFail;
|
|
69
183
|
captureUser;
|
|
184
|
+
/** The turn's FIRST text_delta eventId = the message's stable resume handle (also stamped on the durable text event). */
|
|
70
185
|
firstTextEventId;
|
|
71
186
|
turnHadTool = false;
|
|
72
|
-
constructor(capture, onCaptureFail,
|
|
187
|
+
constructor(capture, onCaptureFail,
|
|
188
|
+
/** R8 (CC-parity rewind): capture a USER-message anchor straight from the
|
|
189
|
+
* `message_committed{role:"user"}` event — its `entryId` is the persisted user-message `SessionTreeEntry.id`,
|
|
190
|
+
* ALWAYS a valid `resumeAt` target (core accepts a user message; it rejects a tool-result / mid-tool-call
|
|
191
|
+
* assistant). This is the CC rewind target ("rewind to the prompt" = the code-restore parity path — a code
|
|
192
|
+
* change happens in a tool turn WITHIN a prompt's run, so rewinding to the prompt + rewindFiles undoes it).
|
|
193
|
+
* Keyed by the run's taskId (the handle the shell already holds from the POST response — no eventId→entryId
|
|
194
|
+
* side map / no `getLeafId` inference needed). The assistant-turn capture above stays as an additive bonus. */
|
|
195
|
+
captureUser) {
|
|
73
196
|
this.capture = capture;
|
|
74
197
|
this.onCaptureFail = onCaptureFail;
|
|
75
198
|
this.captureUser = captureUser;
|
|
76
199
|
}
|
|
200
|
+
/** A `message_committed` arrived — capture a USER-message rewind anchor (CC parity). Best-effort (a failure only
|
|
201
|
+
* makes that one prompt non-rewindable). Only `role==="user"`; assistant/toolResult committed entries are handled
|
|
202
|
+
* by the turn-text path (assistant) or deliberately not anchored (toolResult — `resumeAt` rejects them). */
|
|
77
203
|
async onMessageCommitted(role, entryId) {
|
|
78
204
|
if (role === "user" && entryId && this.captureUser) {
|
|
79
205
|
await this.captureUser(entryId).catch(() => this.onCaptureFail?.());
|
|
80
206
|
}
|
|
81
207
|
}
|
|
208
|
+
/** A text_delta arrived — latch the first DEFINED eventId as the message handle (eventId is optional on the stream). */
|
|
82
209
|
onText(eventId) {
|
|
83
210
|
if (this.firstTextEventId === undefined) {
|
|
211
|
+
// First text of THIS turn. Any tool seen before now belonged to a PRIOR turn: on the resume leg, core streams
|
|
212
|
+
// the suspended turn's resolution tool_start/tool_end (the approved tool) with NO intervening turn_end before the
|
|
213
|
+
// continuation turn (runtask.js applyResumeDecision). Clearing turnHadTool here keeps a pure-text continuation
|
|
214
|
+
// turn anchorable. SAFE for normal turns: within a turn the assistant streams its text BEFORE its own tool calls,
|
|
215
|
+
// so a real same-turn tool always arrives AFTER this first text and re-sets the flag (guarded by `=== undefined`,
|
|
216
|
+
// so a later text part after a tool — [text, toolCall, text] — does NOT wrongly clear it).
|
|
84
217
|
this.turnHadTool = false;
|
|
85
218
|
}
|
|
86
219
|
this.firstTextEventId ??= eventId;
|
|
87
220
|
}
|
|
221
|
+
/** A tool ran this turn — mark it non-rewindable (its leaf will be a tool-result, not a settled assistant-text message). */
|
|
88
222
|
onTool() {
|
|
89
223
|
this.turnHadTool = true;
|
|
90
224
|
}
|
|
225
|
+
/** turn_end — capture the anchor iff this was a settled assistant-TEXT turn, then reset for the next turn. */
|
|
91
226
|
async onTurnEnd() {
|
|
92
227
|
if (this.firstTextEventId && !this.turnHadTool && this.capture) {
|
|
93
228
|
await this.capture(this.firstTextEventId).catch(() => this.onCaptureFail?.());
|
|
@@ -96,25 +231,109 @@ export class TurnAnchorCapture {
|
|
|
96
231
|
this.turnHadTool = false;
|
|
97
232
|
}
|
|
98
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Strip the durable-resume capability token from a result before it is persisted to the replayable event
|
|
236
|
+
* log / run row or returned to the submitter. The token IS the resume credential (token-as-auth, search
|
|
237
|
+
* [18] Q1b) and must never leave the service. Safe on any result (no-op when absent) — notably core maps a
|
|
238
|
+
* NESTED suspend under verify/cascade to status:"failed" + errorCode "unexpected.suspended" while still
|
|
239
|
+
* spreading `checkpointToken`, so that "failed" path (unlike the stream path's explicit suspend handling)
|
|
240
|
+
* would otherwise leak it into task_event / task_run.result / the sync 200 response. `checkpointGate` is
|
|
241
|
+
* non-secret and intentionally kept.
|
|
242
|
+
*/
|
|
99
243
|
export function stripCheckpointToken(result) {
|
|
244
|
+
// Narrow to a plain object (reject null / arrays) and an OWN `checkpointToken` key, so a non-plain or
|
|
245
|
+
// array input is returned unchanged rather than reshaped.
|
|
100
246
|
if (result !== null && typeof result === "object" && !Array.isArray(result) && Object.hasOwn(result, "checkpointToken")) {
|
|
101
247
|
const { checkpointToken: _drop, ...rest } = result;
|
|
102
248
|
return rest;
|
|
103
249
|
}
|
|
104
250
|
return result;
|
|
105
251
|
}
|
|
252
|
+
/** Evict a stale warm-cache entry after a cross-instance write conflict (broken-affinity backstop). */
|
|
106
253
|
export function evictIfConflict(runner, sessionId, result) {
|
|
107
254
|
if (sessionId && isSessionConflictResult(result)) {
|
|
108
255
|
runner.sessions.evict?.(sessionId);
|
|
109
256
|
}
|
|
110
257
|
}
|
|
111
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Drive a task to completion in the background, persisting its events to the durable run log (S2).
|
|
260
|
+
*
|
|
261
|
+
* Text deltas are coalesced into one `text` event per turn (the durable stream is turn-grained for
|
|
262
|
+
* text); tool/lifecycle events are recorded individually. Per-token live streaming stays on the
|
|
263
|
+
* synchronous `/v1/tasks/stream` endpoint. Never throws — failures are recorded on the run.
|
|
264
|
+
*
|
|
265
|
+
* Cancellation (POST /v1/runs/:id/cancel): the run registers an AbortController in `inflight` (same-replica
|
|
266
|
+
* fast path — the cancel handler aborts it directly) and its heartbeat tick polls the durable
|
|
267
|
+
* `cancel_requested` flag (cross-replica, ≤HEARTBEAT_MS lag). Either way the run settles to
|
|
268
|
+
* status "failed" + errorCode "cancelled" (metric label "cancelled"); the entry is removed in `finally`.
|
|
269
|
+
*
|
|
270
|
+
* Preemption (POST /v1/assistant/tasks/:id/preempt, design/80 seam #2): a SECOND, INDEPENDENT controller
|
|
271
|
+
* (`preemptCtrl`, registered in `preemptable`) carries the scheduler's graceful "yield this task" — when raised,
|
|
272
|
+
* core durably SUSPENDS the task at the next clean turn boundary (gate resource_limit, reason "preempt") so it is
|
|
273
|
+
* RESUMABLE, the opposite of cancel's kill. It is wired ONLY on the plain-stream path: the verify/cascade
|
|
274
|
+
* branches finalize with `setTerminal` unconditionally (no `setSuspended`), so a preempt-suspend there would write
|
|
275
|
+
* a non-terminal "suspended" as terminal and release the session lock — and preempt's contract is graceful, not a
|
|
276
|
+
* failure. Cross-replica via the `preempt_requested` flag (same heartbeat tick). Eligibility (resourceSuspend +
|
|
277
|
+
* durable store + durable tool-results + remote env) is core's call: an ineligible task's preemptSignal no-ops.
|
|
278
|
+
*
|
|
279
|
+
* Steering (POST /v1/runs/:id/steer, design/47): the live core `TaskStream` is registered in `steerable` so the
|
|
280
|
+
* HTTP route can inject a mid-task message via `stream.steer()` (drained at the next turn boundary). Same-replica
|
|
281
|
+
* only — a suspended run is steered durably via the checkpoint's `setPendingSteer` instead, and only the
|
|
282
|
+
* plain-stream path holds a steerable stream (the verify/cascade branches return a result, not a stream).
|
|
283
|
+
*/
|
|
284
|
+
export async function runInBackground(runner, spec, runStore, taskId, metrics, principal, verify, // 快审 F1:number 参升整对象——cost 顶随传
|
|
285
|
+
cascade, instrumentDegenerate, planCacheProbe, persistThinking = false, inflight, preemptable, steerable, modelUsage, elicitation,
|
|
286
|
+
/** E23: the VERIFIED principal (gatedPrincipal) that may answer this run's elicitations — matches the respond
|
|
287
|
+
* owner-gate's identity source (NOT the cost-attribution `principal`, which on a direct door is the spoofable
|
|
288
|
+
* header → can be null while a JWT principal exists). Falls back to the run owner when not supplied. */
|
|
289
|
+
elicitOwner,
|
|
290
|
+
/** E18 resume-at: capture the turn's (firstTextEventId → leaf entryId) anchor at each turn_end. Built by the caller
|
|
291
|
+
* closing over {sessionId, owner, sessionStore.getLeafId, resumeAnchorStore}; undefined ⇒ no anchor store wired. */
|
|
292
|
+
captureTurnAnchor,
|
|
293
|
+
/** MF-Fleet (shell-host contract): the run-scoped fleet-row publisher. `onStart` adds the live row; `onEvent` accrues
|
|
294
|
+
* tokens (turn_end) + subagent child rows (task_progress); `onTerminal` flips/removes it. Fired for ALL legs
|
|
295
|
+
* (verify/cascade/plain-stream) so a background run TRANSITIONS its `GET /v1/fleet/stream` row exactly like the
|
|
296
|
+
* sync leg. No-op when no fleet bus is wired (the publisher is a null-object). */
|
|
297
|
+
fleetPublisher,
|
|
298
|
+
/** R8 (CC-parity rewind): capture a USER-message anchor (keyed by this run's taskId) from `message_committed{role:"user"}`
|
|
299
|
+
* — the CC "rewind to the prompt" target. undefined ⇒ no anchor store wired. */
|
|
300
|
+
captureUserMessageAnchor,
|
|
301
|
+
/** §4④ : AskUserQuestion LIVE coordinator — establishes a per-run onQuestion ALS context (like `elicitation`)
|
|
302
|
+
* so an AskUserQuestion tool call routes to this run's stream + owner-gated respond. undefined ⇒ headless default. */
|
|
303
|
+
question,
|
|
304
|
+
/** P1 ①② follow-on (core): the async-workflow completion inbox, drained at the START of this background
|
|
305
|
+
* leg (parity with the sync stream-open drain) into durable `workflow_complete` events a tailing client replays.
|
|
306
|
+
* Owner gate = `elicitOwner ?? owner` (the same verified-principal-with-run-owner-fallback the elicit/question
|
|
307
|
+
* contexts use). undefined ⇒ no push half wired (the WorkflowStatus poll floor stands alone). */
|
|
308
|
+
workflowCompletionInbox,
|
|
309
|
+
/** C2 (core 1.219): the replica-local Task-subagent steer-handle registry. When present, this leg
|
|
310
|
+
* opts into core's `onSubagentSpawn` sink — each SYNC delegation's steer handle registers under THIS run's
|
|
311
|
+
* taskId (auto-evicted on child settle) so POST /v1/runs/:id/subagents/:target/steer can route into it. */
|
|
312
|
+
subagentSteer,
|
|
313
|
+
/** ask①: emit-target diagnostic sink for the completion-inbox drain below (route/connection/
|
|
314
|
+
* deliveryLagMs per delivered `workflow_complete`). runs.ts has no logger of its own — the caller closes over
|
|
315
|
+
* deps.logger. undefined ⇒ silent (behavior unchanged). */
|
|
316
|
+
completionDiagLog,
|
|
317
|
+
/** SendUserFile(切片2):per-run file_link 帧发射 ALS(question/elicitation 同款)。emit=durable append。 */
|
|
318
|
+
sendUserFile,
|
|
319
|
+
/** [998]② the shared prompt-manifest accumulator (tracer records at prepare; this leg drains the pending
|
|
320
|
+
* record into a durable `prompt_assembled` event — turns/stream read it). undefined ⇒ not wired. */
|
|
321
|
+
promptManifests) {
|
|
112
322
|
const startedAt = Date.now();
|
|
113
323
|
metrics?.addGauge("runs_active", 1);
|
|
114
|
-
fleetPublisher?.onStart();
|
|
324
|
+
fleetPublisher?.onStart(); // MF-Fleet: the row goes live (running) the moment the background run starts
|
|
325
|
+
// Cancel (POST /v1/runs/:id/cancel): the controller aborts the run two ways — (a) same-replica fast path,
|
|
326
|
+
// the cancel handler looks this up in `inflight` and aborts now; (b) cross-replica, the heartbeat tick below
|
|
327
|
+
// polls the durable `cancel_requested` flag and aborts. Either way an aborted run finalizes to
|
|
328
|
+
// "failed" + errorCode "cancelled" (we do NOT extend TaskStatus — errorCode distinguishes user-cancel).
|
|
115
329
|
const cancelCtrl = new AbortController();
|
|
116
330
|
inflight?.set(taskId, cancelCtrl);
|
|
331
|
+
// stoppedBy (core 1.252): a service cancel is a USER stop — see markChildrenStoppedByUserOnAbort.
|
|
117
332
|
markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, principal);
|
|
333
|
+
// design/80 seam #2 (preemption): only the plain-stream path can durably suspend-and-resume on a preempt (the
|
|
334
|
+
// verify/cascade branches setTerminal unconditionally). Register the preempt controller — and poll the durable
|
|
335
|
+
// flag — ONLY for that path, so the endpoint that looks a task up in `preemptable` can honestly tell a
|
|
336
|
+
// verify/cascade task apart (not preemptible) from a stream task.
|
|
118
337
|
const preemptEligible = verify === undefined && cascade === undefined;
|
|
119
338
|
const preemptCtrl = new AbortController();
|
|
120
339
|
if (preemptEligible)
|
|
@@ -128,69 +347,157 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
128
347
|
metrics?.inc("task_tokens_total", {}, result.stats.tokens);
|
|
129
348
|
if (result?.stats.cacheHitRate !== undefined)
|
|
130
349
|
metrics?.observe("task_cache_hit_rate", result.stats.cacheHitRate);
|
|
350
|
+
// Degenerate-output a/b instrument (1.59) — same as the sync path; no-op unless output.degenerate.
|
|
131
351
|
if (result)
|
|
132
352
|
instrumentDegenerate?.(result);
|
|
353
|
+
// Plan-cache recurrence probe (design/42): objective recurrence per scope. status "completed" feeds
|
|
354
|
+
// the history ring (mirrors the sync path); other terminal states still count toward the denominator.
|
|
133
355
|
planCacheProbe?.record(principal, spec.objective, status === "completed");
|
|
134
356
|
};
|
|
357
|
+
// Liveness heartbeat (independent of events): keeps a long-but-alive run — e.g. one blocked in a
|
|
358
|
+
// multi-minute subagent that streams no parent events — from being mistaken for a dead instance.
|
|
359
|
+
// The owning principal is this run's owner — thread it as the single-DB-fleet null-safe owner guard on every
|
|
360
|
+
// run-store mutation/poll below (the run row was created with owner = principal ?? null).
|
|
135
361
|
const owner = principal ?? null;
|
|
136
362
|
const heartbeat = setInterval(() => {
|
|
137
363
|
void runStore.heartbeat(taskId, owner).catch(() => undefined);
|
|
364
|
+
// Cross-replica cancel: the cancel may have landed on another instance, which only set the durable flag.
|
|
365
|
+
// Poll it here so the OWNING instance aborts its in-flight run (bounded by HEARTBEAT_MS).
|
|
138
366
|
if (!cancelCtrl.signal.aborted) {
|
|
139
367
|
void runStore.isCancelRequested(taskId, owner).then((c) => { if (c)
|
|
140
368
|
cancelCtrl.abort(); }).catch(() => undefined);
|
|
141
369
|
}
|
|
370
|
+
// Cross-replica preempt (design/80 seam #2): a preempt may have landed on another instance (durable flag
|
|
371
|
+
// only). Poll it so the OWNING instance raises its preemptSignal → core suspends at the next clean turn
|
|
372
|
+
// boundary (resumable). Only the stream path wires the signal, so only poll there.
|
|
142
373
|
if (preemptEligible && !preemptCtrl.signal.aborted) {
|
|
143
374
|
void runStore.isPreemptRequested(taskId, owner).then((p) => { if (p)
|
|
144
375
|
preemptCtrl.abort(); }).catch(() => undefined);
|
|
145
376
|
}
|
|
146
377
|
}, HEARTBEAT_MS);
|
|
147
378
|
heartbeat.unref?.();
|
|
379
|
+
// E18 resume-at: per-turn anchor capture (the turn's first text_delta eventId = the stable per-message handle, which
|
|
380
|
+
// is also stamped on the durable `text` event so the shell sees ONE resume handle per message). The shared
|
|
381
|
+
// TurnAnchorCapture only anchors a SETTLED assistant-text turn (text + no tools) — see its doc; the metric surfaces
|
|
382
|
+
// a systematic capture failure (un-migrated DB) instead of a silent feature outage.
|
|
148
383
|
const anchor = new TurnAnchorCapture(captureTurnAnchor, () => metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
|
|
384
|
+
// Once a terminal outcome's event is DURABLY appended, the run has reached that state — record it so a later
|
|
385
|
+
// failure (e.g. setTerminal throws after the `done` event is logged) re-attempts the SAME terminal write
|
|
386
|
+
// rather than clobbering a completed run with `failed` (which would log both `done` and `failed` and report a
|
|
387
|
+
// succeeded task as failed).
|
|
149
388
|
let reached;
|
|
389
|
+
// 1.78 (review LOW): the plain-stream leg's live TaskStream — held so the finally can identity-guard the
|
|
390
|
+
// `steerable` deregistration (see the finally).
|
|
150
391
|
let steerableStreamRef;
|
|
392
|
+
// false once this leg settles — a background subagent/bash completion arriving AFTER that has no live
|
|
393
|
+
// harness for core's in-process injection (the idle gap), so the observer below parks it durably instead.
|
|
151
394
|
let legLive = true;
|
|
395
|
+
// option A: taskNotificationStreamKey (域:task_id:status[#seq], core [784]④a + codex F2 域二分) keys
|
|
396
|
+
// already carried by THIS leg (start-of-leg inbox drain, event-stream branch OR observer) — each arm skips
|
|
397
|
+
// keys another arm already delivered, so a notification isn't double-appended to the durable log (the
|
|
398
|
+
// turn-TAIL reap notification, which never reaches the stream, is exactly the one the observer catches;
|
|
399
|
+
// core's pend-drain replay of a double-parked idle arrival is exactly the one the drain-registration
|
|
400
|
+
// catches). The shell's dedup stays the outer belt. codex F1: value = the claiming write's outcome promise
|
|
401
|
+
// (the observer arm appends fire-and-forget; a failed claim must NOT eat the stream replay — see NotifiedKeys).
|
|
152
402
|
const notifiedKeys = new NotifiedKeys();
|
|
403
|
+
// design/122 ③: THIS leg's subagent-handle unregister thunks (identity-guarded by the registry) — run at
|
|
404
|
+
// leg end (finally), NOT on child settle, so a settled child stays resumable while the parent run lives.
|
|
153
405
|
const subagentHandleEvictions = [];
|
|
406
|
+
// design/80 D-B: PARK a `needs_review` result (a dry-run diff review OR a pre-action `plan_review` pause; core
|
|
407
|
+
// assemble-result slot 8.6) like a suspend — KEEP task_active + flip the row non-terminal so the resume
|
|
408
|
+
// (getActiveTaskId → markResuming) can claim it. Without this every finalize site setTerminal'd it (deleting the
|
|
409
|
+
// session lock), and the plan_review resume then got taskId=undefined → drove the model UNPROTECTED on an
|
|
410
|
+
// unlocked session with the row stuck `needs_review` (the adversarial-review HIGH finding). Mirrors the suspended
|
|
411
|
+
// branch (append only the non-secret gate, never the capability token).
|
|
154
412
|
const parkNeedsReview = async (res) => {
|
|
155
413
|
await sink.appendParked("needs_review", res);
|
|
156
|
-
await flushModelUsage();
|
|
414
|
+
await flushModelUsage(); // E8: persist pre-park usage before the review pause (resume leg accumulates fresh)
|
|
157
415
|
reached = { kind: "needs_review" };
|
|
158
416
|
await runStore.setNeedsReview(taskId);
|
|
159
417
|
metrics?.addGauge("runs_active", -1);
|
|
160
418
|
metrics?.inc("tasks_total", { status: "needs_review" });
|
|
161
419
|
};
|
|
420
|
+
// [900]①/[906]③: the event→ledger projection (text/reasoning coalescing + redaction + seq + the type/data
|
|
421
|
+
// whitelist shapes) lives in the SHARED LedgerSink — the sync /v1/tasks/stream detach lane consumes the same
|
|
422
|
+
// single switch, so the two ledgers can't drift. This leg keeps its non-ledger side-legs (anchor/metrics/
|
|
423
|
+
// model-usage/park-terminal/fleet) in place and drives the sink; `append`/`flush` below stay the leg-local
|
|
424
|
+
// names for every out-of-band row (elicit/question frames, model_usage deltas, suggestions, failed) so they
|
|
425
|
+
// share the run's single seq counter. `notifiedKeys` is the same per-leg instance the observer/drain arms use.
|
|
162
426
|
const sink = createLedgerSink({ appendEvent: (seq, type, data) => runStore.appendEvent(taskId, seq, type, data), persistThinking, notifiedKeys });
|
|
163
427
|
const append = (type, data) => sink.append(type, data);
|
|
164
428
|
const flush = () => sink.flush();
|
|
429
|
+
// E8 (shell-host contract): drain the per-task model-usage accumulator into an append-only `model_usage` DELTA event, so
|
|
430
|
+
// the durable log is the resume-safe source of `TaskStats.modelUsage`. Called at every turn boundary AND before a
|
|
431
|
+
// park (suspend/needs_review) — a run that suspends has its pre-suspend usage persisted before the resume leg
|
|
432
|
+
// continues accumulating; the append-only deltas SUM across all legs (no overwrite).
|
|
165
433
|
const flushModelUsage = () => appendModelUsageDelta(append, modelUsage, taskId);
|
|
434
|
+
// At a TERMINAL done: flush the final delta + SUM all `model_usage` events into result.stats.modelUsage (shared
|
|
435
|
+
// with the resume leg's terminal so a suspended→resumed run's total is summed across both legs).
|
|
166
436
|
const withModelUsage = (result) => attachModelUsage(result, { append, getEvents: (id, after) => runStore.getEvents(id, after), modelUsage, taskId });
|
|
167
|
-
modelUsage?.register(taskId);
|
|
437
|
+
modelUsage?.register(taskId); // E8 leak fence: only this top-level run's brain.calls are tracked (sub-tasks excluded)
|
|
438
|
+
// [998]②: same fence — sub-task manifests are never registered, never leak. The returned LEASE guards the
|
|
439
|
+
// finally's clear (B-3 window: a fast sibling resume re-registers this taskId before our finally runs —
|
|
440
|
+
// an unconditional clear would delete the NEW leg's registration/pending manifest).
|
|
168
441
|
const manifestLease = promptManifests?.register(taskId) ?? 0;
|
|
442
|
+
// [998]②: no-op when nothing is pending (one Map.get) — fired per stream event so the manifest lands at the
|
|
443
|
+
// top of the log right after core's prepare records it, and before the terminal on the no-stream legs.
|
|
169
444
|
const flushPromptManifest = () => appendPromptManifest(append, promptManifests, taskId);
|
|
445
|
+
// E23 (shell-host contract): an inbound MCP elicitation fired deep inside core's callTool surfaces via onElicit, which has
|
|
446
|
+
// no taskId — so establish a per-run elicitation context (ALS) carrying this run's emit (durable append → delivered
|
|
447
|
+
// on the events tail), owner (respond owner-gate) + cancel signal (a run abort cancels a parked elicitation). The
|
|
448
|
+
// elicitation append interleaves safely: onElicit is awaited SYNCHRONOUSLY inside callTool, so the loop is parked
|
|
449
|
+
// (not appending) while it runs. No-op when elicitation is off. `type` is the SSE event name; the rest is the data.
|
|
170
450
|
const elicitEmit = (frame) => {
|
|
171
451
|
const { type, ...rest } = frame;
|
|
172
452
|
return append(type, rest);
|
|
173
453
|
};
|
|
454
|
+
// §4④: same durable-append emit for AskUserQuestion frames (delivered on the events tail; the shell renders `question`
|
|
455
|
+
// + dismisses on `question_complete`). onQuestion is awaited SYNCHRONOUSLY inside core's callTool, so the loop parks
|
|
456
|
+
// (not appending) while it runs — the open frame's append is ordered ahead of the answer/terminal appends.
|
|
174
457
|
const questionEmit = (frame) => {
|
|
175
458
|
const { type, ...rest } = frame;
|
|
176
459
|
return append(type, rest);
|
|
177
460
|
};
|
|
178
461
|
try {
|
|
462
|
+
// P1 ①② follow-on (core): drain this session's async-workflow completion inbox at the START of the
|
|
463
|
+
// background leg (before ANY branch — verify/cascade/plain-stream all get it; parity with the sync leg's
|
|
464
|
+
// stream-open drain) into durable `workflow_complete` events the tailing client (GET /v1/runs/:id/events)
|
|
465
|
+
// replays as <task-notification>s. Best-effort inside the helper (it never throws); inside the try so the
|
|
466
|
+
// heartbeat/gauge finally-cleanup would cover even a defensive miss. Owner gate mirrors the elicit/question owner.
|
|
179
467
|
await emitPendingWorkflowCompletions(workflowCompletionInbox, spec.sessionId, elicitOwner ?? owner, (frame) => {
|
|
180
468
|
const { type, ...rest } = frame;
|
|
469
|
+
// core [784]④b: register the drained frame's key on THIS leg — the same idle-arrival was ALSO parked
|
|
470
|
+
// core-side (pendingSessionNotifications); its pend-drain replay rides this run's stream as a
|
|
471
|
+
// task_notification event, and the loop's key check folds that second durable append (sync twin's note).
|
|
472
|
+
// 三路复审修2(MED,推翻旧 codex F1 注释的 resolved(true) 登记):emitPendingWorkflowCompletions
|
|
473
|
+
// 虽 await 这次 append,但它 CATCH 失败(留 row 不 ack)——键却已被 Promise.resolve(true) 认领;
|
|
474
|
+
// core 若把同一 pending 经 pend-drain 重放到本腿 stream 臂,stream 臂见「真成功」claim 抑制重写,
|
|
475
|
+
// 通知就延到下一次开腿(可能无限延)。登记真实 append 结果:失败 resolve false,stream 臂
|
|
476
|
+
// await 到 false 照写(咬合 NotifiedKeys 的 F1 语义);row 未 ack 的下腿重投仍是兜底。
|
|
477
|
+
// codex F2: task_type rides into the key(drained frames restore it from the entry extras)——
|
|
478
|
+
// external 帧独立键域,不与内部任务帧互吞.
|
|
181
479
|
const f = frame;
|
|
182
480
|
const write = append(type, rest);
|
|
183
481
|
if (f.type === "task_notification" && f.task_id)
|
|
184
482
|
notifiedKeys.set(taskNotificationStreamKey({ task_type: f.task_type, task_id: f.task_id, status: String(f.status), seq: f.seq }), write.then(() => true, () => false));
|
|
185
483
|
return write;
|
|
186
|
-
},
|
|
484
|
+
},
|
|
485
|
+
// ask①: emit-target diagnostics — this drain appends to THIS background run's durable event log.
|
|
486
|
+
completionDiagLog ? { route: "bg-run-start", connection: "durable-append", log: completionDiagLog } : undefined);
|
|
487
|
+
// Thread the owning principal so the cost tracer attributes this run's spend (incl. council/team
|
|
488
|
+
// sub-tasks) to the principal's cumulative quota — mirrors the synchronous path.
|
|
187
489
|
const runBody = async () => {
|
|
188
490
|
if (verify !== undefined) {
|
|
491
|
+
// Developer-mode verification gate (1.44): multi-round (impl → adversarial verify → fix loop), not
|
|
492
|
+
// a stream — await the result and persist it terminal. The durable event log gets just the `done`
|
|
493
|
+
// (the verifier subtask runs on its own session). `result.verification` carries the verdict.
|
|
189
494
|
const result = await runWithVerification(runner, { ...spec, taskId, signal: cancelCtrl.signal }, verify);
|
|
190
495
|
await flush();
|
|
191
496
|
evictIfConflict(runner, spec.sessionId, result);
|
|
192
|
-
|
|
193
|
-
|
|
497
|
+
// A nested suspend under the verify gate is mapped by core to status:"failed" carrying the capability
|
|
498
|
+
// token — strip it before it touches the replayable log / run row (search [18] Q1b).
|
|
499
|
+
await flushPromptManifest(); // [998]②: the verify leg has no stream — persist the manifest before the terminal
|
|
500
|
+
const safe = await withModelUsage(stripCheckpointToken(result)); // E8: attach stats.modelUsage at terminal
|
|
194
501
|
await append("done", { result: safe });
|
|
195
502
|
reached = { kind: "done", result: safe };
|
|
196
503
|
await runStore.setTerminal(taskId, safe.status, safe, safe.errorMessage ?? null);
|
|
@@ -198,21 +505,39 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
198
505
|
return;
|
|
199
506
|
}
|
|
200
507
|
if (cascade !== undefined) {
|
|
508
|
+
// Cascade (1.45): cheap→strong ladder; multi-attempt cold re-runs, not a stream. Persist terminal.
|
|
201
509
|
const result = await runCascade(runner, { ...spec, taskId, signal: cancelCtrl.signal }, cascade);
|
|
202
510
|
await flush();
|
|
203
511
|
evictIfConflict(runner, spec.sessionId, result);
|
|
204
|
-
await flushPromptManifest();
|
|
205
|
-
const safe = await withModelUsage(stripCheckpointToken(result));
|
|
512
|
+
await flushPromptManifest(); // [998]②: same no-stream posture as the verify leg above
|
|
513
|
+
const safe = await withModelUsage(stripCheckpointToken(result)); // see the verify branch; E8 modelUsage at terminal
|
|
206
514
|
await append("done", { result: safe });
|
|
207
515
|
reached = { kind: "done", result: safe };
|
|
208
516
|
await runStore.setTerminal(taskId, safe.status, safe, safe.errorMessage ?? null);
|
|
209
517
|
recordDone(safe, safe.status);
|
|
210
518
|
return;
|
|
211
519
|
}
|
|
520
|
+
// design/47 (mid-task steering): hold the live stream handle so POST /v1/runs/:id/steer can inject a steer via
|
|
521
|
+
// stream.steer() (drained at the next turn boundary). Only this plain-stream path is steerable — the verify/
|
|
522
|
+
// cascade branches above return a result, not a stream (parity with the preemptable registration). Deregistered
|
|
523
|
+
// in `finally`. Registering it AFTER the verify/cascade early-returns means those legs never enter the map.
|
|
524
|
+
// core 1.154 nested-subagent live tree: opt into the forward sink — a subagent's task_progress lives in the
|
|
525
|
+
// child run's ISOLATED stream (core's only emitter is parentToolCallId-gated and pushes onto the CHILD queue;
|
|
526
|
+
// the top stream NEVER carries it), so this sink is the ONLY path a tick reaches the top-run consumers. 1.78
|
|
527
|
+
// (review HIGH): fan it out to the DURABLE event log too (via the shared whitelist+redact builder, fire-and-
|
|
528
|
+
// forget — `++seq` is a sync increment so sink-vs-loop appends get unique seqs), not just the fleet bus —
|
|
529
|
+
// previously no fleet bus wired ⇒ ticks dropped entirely, and the stream-switch task_progress case below is
|
|
530
|
+
// forward-compat only (dead against core ≤1.208). core swallow-guards a throwing sink.
|
|
212
531
|
const fwdInternals = {
|
|
213
532
|
onForwardEvent: (e) => {
|
|
214
533
|
fleetPublisher?.onForwardEvent(e);
|
|
534
|
+
// C1 (core 1.219): a delegated child's tool lifecycle + task_progress ticks persist through the SHARED
|
|
535
|
+
// sink (same whitelist+redact builders as the top stream — §E1, forwarded events are UNTRUSTED RAW;
|
|
536
|
+
// fire-and-forget with sync seq allocation). Rationale + the deliberate per-chunk-delta omission live
|
|
537
|
+
// on LedgerSink.onForwardEvent.
|
|
215
538
|
sink.onForwardEvent(e);
|
|
539
|
+
// S2 live tail(core 1.370 bgAgentId,[1520]{core}):bg 子代帧按句柄分发给 per-agent 订阅者
|
|
540
|
+
// (sync 腿 twin 在 http/server.ts)。hasSubscribers 快门=没人听不投影;投影一次 N 订阅者共享。
|
|
216
541
|
const bg = e.bgAgentId;
|
|
217
542
|
if (bg !== undefined && defaultSubagentTailBus.hasSubscribers(bg)) {
|
|
218
543
|
const f = projectTailFrame(e);
|
|
@@ -220,25 +545,60 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
220
545
|
defaultSubagentTailBus.publish(bg, f);
|
|
221
546
|
}
|
|
222
547
|
},
|
|
548
|
+
// (durable half is the deployment's job): observe EVERY background
|
|
549
|
+
// completion notification. (1) flip the child's fleet row (task_progress never emits a terminal tick —
|
|
550
|
+
// without this a settled bg agent showed "running" until parent settle); (2) when the LEG already ended
|
|
551
|
+
// (idle — core's steer/followUp had nowhere to go), park it in the durable inbox; the next stream-open
|
|
552
|
+
// drain emits it as the same `task_notification` frame family. Workflow-type notifications are SKIPPED —
|
|
553
|
+
// they have their own durable chain (notify gate + inbox + serve fences); double-enqueueing would re-open
|
|
554
|
+
// the double-push. While the leg is LIVE, core injects in-process + the stream carries the display frame —
|
|
555
|
+
// no durable copy (a boundary-race notification that misses both is covered by the TaskOutput poll floor).
|
|
556
|
+
// core 1.283 `seq` (per-stop-cycle counter) rides into the dedup key + parked entry — core [784]④a.
|
|
557
|
+
// 白名单排查(clay 令,2026-07-24,「彻查 as {} 连环坑」):此前手写窄类型(仅 10/19 键),3 处
|
|
558
|
+
// (此处+http/server.ts 两处)复制粘贴、随 core payload 增field 逐渐过时——n 本身按引用直转
|
|
559
|
+
// taskNotificationEventData({notification:n}),运行时不丢字段(该函数自己的白名单是完整的),
|
|
560
|
+
// 但窄类型会让未来在这些回调体内新增读字段的人被 TS 误导「这个字段到不了这里」。改用 core
|
|
561
|
+
// 真实导出类型,杜绝副本漂移。
|
|
223
562
|
onTaskNotification: (n) => {
|
|
224
563
|
if (n.task_type === "workflow")
|
|
225
564
|
return;
|
|
565
|
+
// S2 复审 #3(sync 腿同注):中途终态信号。
|
|
226
566
|
if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
|
|
227
567
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
228
568
|
}
|
|
569
|
+
// id-domain alias (core): fleet child rows key by the TICK's taskId = the child's sessionId
|
|
570
|
+
// (uuid domain); the notification's task_id is the a* domain — payload.sessionId IS the tick-domain
|
|
571
|
+
// alias, so flip by it (fallback task_id for pre-1.238 payloads / bash lane).
|
|
229
572
|
const hadRow = fleetPublisher?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false;
|
|
230
573
|
const parked = !legLive && Boolean(workflowCompletionInbox && spec.sessionId);
|
|
574
|
+
// diagnosability: one line per observed bg completion. hadFleetRow=false = the terminal frame was
|
|
575
|
+
// FLIP-THROUGH synthesized (the row was gone — parent settle removed it, or a bg BASH never ticked).
|
|
231
576
|
completionDiagLog?.("task_notification_observed", { route: "bg-run-start", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive, parkedDurable: parked });
|
|
232
577
|
if (parked) {
|
|
578
|
+
// 对抗评审 2026-07-11(MED):log the enqueue failure — the diag line above already printed
|
|
579
|
+
// parkedDurable:true, so a swallow made that a false park✓ fingerprint. Never faults the callback.
|
|
233
580
|
void workflowCompletionInbox.enqueue(taskNotificationInboxEntry(spec.sessionId, elicitOwner ?? owner, n, Date.now(), taskId)).catch((err) => completionDiagLog?.("park_enqueue_failed", { route: "bg-run-start", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
|
|
234
581
|
}
|
|
235
582
|
else if (legLive) {
|
|
583
|
+
// option A (the turn-TAIL killed receipt must be IMMEDIATE): a teardown-time reap
|
|
584
|
+
// notification fires while the leg is still live but past the stream's delivery window — core's
|
|
585
|
+
// in-process steer is swallowed and the event never reaches the log. Append the display frame HERE,
|
|
586
|
+
// skipping keys the stream already delivered (mid-turn dedup); the tailing client renders the receipt
|
|
587
|
+
// this turn, not one turn late. core [784]④a: seq-folded shared key — the seq-less
|
|
588
|
+
// `${task_id}:${status}` shape reverse-swallowed a second stop-cycle's same-status frame here.
|
|
589
|
+
// codex F1 (HIGH): this append is fire-and-forget — claim the key with its REAL outcome, not
|
|
590
|
+
// unconditionally. A failed append (SQL blip) resolves the claim false, and the stream arm's
|
|
591
|
+
// await-then-write below re-delivers the same key instead of being swallowed by a dead claim.
|
|
236
592
|
const key = taskNotificationStreamKey(n);
|
|
237
593
|
if (notifiedKeys.get(key) === undefined) {
|
|
238
594
|
notifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n })).then(() => true, () => false));
|
|
239
595
|
}
|
|
240
596
|
}
|
|
241
597
|
},
|
|
598
|
+
// C2 (core 1.219) + design/122 (core 1.225 ③): register each SYNC child's steer handle under THIS
|
|
599
|
+
// run's taskId. NOT evicted on settle — `resume` is only legal AFTER the child settled (a settle-evict
|
|
600
|
+
// would make revival structurally unreachable). Evicted at LEG END via the collected identity-guarded
|
|
601
|
+
// thunks (finally below); a resume re-emits a FRESH handle under the same id (registry newest-wins).
|
|
242
602
|
...(subagentSteer
|
|
243
603
|
? {
|
|
244
604
|
onSubagentSpawn: (handle) => {
|
|
@@ -248,30 +608,53 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
248
608
|
: {}),
|
|
249
609
|
};
|
|
250
610
|
const stream = runner.runTaskStream({ ...spec, taskId, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, undefined, fwdInternals);
|
|
251
|
-
steerableStreamRef = stream;
|
|
611
|
+
steerableStreamRef = stream; // held for the finally's identity-guarded delete (1.78 review LOW)
|
|
252
612
|
steerable?.set(taskId, stream);
|
|
253
613
|
for await (const ev of stream) {
|
|
614
|
+
// MF-Fleet: accrue this run's fleet-row tokens (turn_end usage). Fired before the switch so every event
|
|
615
|
+
// reaches it. No-op when no bus is wired. (Subagent task_progress arrives via the FORWARD sink above, not
|
|
616
|
+
// this top stream — the case below is forward-compat only; a stale comment here claimed otherwise.)
|
|
254
617
|
fleetPublisher?.onEvent(ev);
|
|
618
|
+
// [998]② release-review round 2: drain the pending prompt manifest at the TOP of every iteration —
|
|
619
|
+
// BEFORE the done/suspend branches below. A done-first stream (post-prepare failure/abort, or an
|
|
620
|
+
// immediate suspend) would otherwise skip the flush entirely: the manifest recorded at prepare would
|
|
621
|
+
// be cleared by the finally (or, worse, survive a lease-guarded no-op clear into a fast sibling
|
|
622
|
+
// resume's queue and land AFTER the suspension event). Here it always precedes its own terminal.
|
|
255
623
|
await flushPromptManifest();
|
|
624
|
+
// Non-ledger side-legs stay HERE (E18 anchor capture / S4 metrics) — the event→ledger projection itself
|
|
625
|
+
// is the shared sink below (single switch; the sync detach lane consumes the same one, so the persisted
|
|
626
|
+
// type/data vocabulary has ONE source). Ordering parity: anchor.onTool precedes the sink's tool_start
|
|
627
|
+
// flush (as before); the metric fires before the brain_status row lands (metric ≠ ledger, no row moves).
|
|
256
628
|
switch (ev.type) {
|
|
257
629
|
case "text_delta":
|
|
258
|
-
anchor.onText(ev.eventId);
|
|
630
|
+
anchor.onText(ev.eventId); // E18: latch the turn's first-text eventId = the message's resume handle
|
|
259
631
|
break;
|
|
260
632
|
case "tool_start":
|
|
261
|
-
anchor.onTool();
|
|
633
|
+
anchor.onTool(); // E18: a tool ran this turn → not a settled assistant-text boundary (don't anchor it)
|
|
262
634
|
break;
|
|
263
635
|
case "status":
|
|
636
|
+
// S4 (SILENT-FALLBACK P0-a): count the brain-layer liveness observation (the sink persists the row —
|
|
637
|
+
// previously: sync SSE only, async runs completely dark).
|
|
264
638
|
metrics?.inc("brain_retry_total", { phase: String(ev.phase) });
|
|
265
639
|
break;
|
|
266
640
|
case "message_committed":
|
|
641
|
+
// R8 (CC-parity rewind): a persisted session entry was committed. Capture a USER-message rewind anchor
|
|
642
|
+
// (keyed by taskId) straight from its `entryId` — the CC "rewind to the prompt" target. assistant entries
|
|
643
|
+
// are anchored by the turn-text path; tool-results are deliberately not anchored (resumeAt rejects them).
|
|
644
|
+
// NOT forwarded to the durable trace / shell stream (core口径: replay walks the persisted session tree —
|
|
645
|
+
// the sink deliberately ignores this type).
|
|
267
646
|
await anchor.onMessageCommitted(ev.role, ev.entryId);
|
|
268
647
|
break;
|
|
269
648
|
}
|
|
270
649
|
if (ev.type === "done") {
|
|
271
650
|
await flush();
|
|
272
651
|
if (ev.result.status === "suspended") {
|
|
652
|
+
// Durable F4 pause (design/45): PARK the run (non-terminal), NOT a `done`. Never persist the
|
|
653
|
+
// capability `checkpointToken` to the replayable event log / run row (search [18] Q1b) — only the
|
|
654
|
+
// non-secret gate (sink.appendParked). The submitter polling GET /v1/runs/:id sees status:"suspended";
|
|
655
|
+
// an operator resumes via /v1/approvals (token looked up internally). The worker is freed (runs_active -1).
|
|
273
656
|
await sink.appendParked("suspended", ev.result);
|
|
274
|
-
await flushModelUsage();
|
|
657
|
+
await flushModelUsage(); // E8: persist pre-suspend usage before parking (resume leg accumulates fresh)
|
|
275
658
|
reached = { kind: "suspended" };
|
|
276
659
|
await runStore.setSuspended(taskId);
|
|
277
660
|
metrics?.addGauge("runs_active", -1);
|
|
@@ -279,12 +662,18 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
279
662
|
continue;
|
|
280
663
|
}
|
|
281
664
|
if (ev.result.status === "needs_review") {
|
|
665
|
+
// design/80 D-B: a review pause (plan_review / dry_run_review) — PARK it like a suspend (keep the lock)
|
|
666
|
+
// so the /v1/assistant/tasks/:id/plan_review resume can claim the row. NEVER persist the token.
|
|
282
667
|
await parkNeedsReview(ev.result);
|
|
283
668
|
continue;
|
|
284
669
|
}
|
|
670
|
+
// A session CAS conflict arrives here as a failed result (core swallows it) — evict the
|
|
671
|
+
// now-stale warm-cache entry so the next turn re-wakes from TiDB instead of re-conflicting.
|
|
285
672
|
evictIfConflict(runner, spec.sessionId, ev.result);
|
|
673
|
+
// Structural token fence (no-op today; this plain-stream path's only token-bearing result is the
|
|
674
|
+
// suspended branch above, handled separately) — symmetric with the verify/cascade + resume paths.
|
|
286
675
|
{
|
|
287
|
-
const safe = await withModelUsage(stripCheckpointToken(ev.result));
|
|
676
|
+
const safe = await withModelUsage(stripCheckpointToken(ev.result)); // E8: attach stats.modelUsage at terminal
|
|
288
677
|
await sink.appendDone(safe);
|
|
289
678
|
reached = { kind: "done", result: safe };
|
|
290
679
|
await runStore.setTerminal(taskId, safe.status, safe, safe.errorMessage ?? null);
|
|
@@ -292,17 +681,37 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
292
681
|
}
|
|
293
682
|
continue;
|
|
294
683
|
}
|
|
684
|
+
// The shared event→ledger projection (text/reasoning coalescing, whitelist+redact builders, flush-before
|
|
685
|
+
// discipline, task_notification dedup against this leg's `notifiedKeys`) — see LedgerSink.onEvent.
|
|
686
|
+
// (The prompt-manifest drain runs at the loop TOP — ahead of done/suspend too, see above.)
|
|
295
687
|
await sink.onEvent(ev);
|
|
296
688
|
if (ev.type === "turn_end") {
|
|
297
|
-
await flushModelUsage();
|
|
689
|
+
await flushModelUsage(); // E8: persist this turn's per-model usage delta (resume-safe)
|
|
690
|
+
// E18 resume-at: anchor (this turn's message eventId → its leaf SessionTreeEntry.id), but ONLY for a settled
|
|
691
|
+
// assistant-TEXT turn (text + no tools) — for a tool-using turn the leaf here is the last tool-result, NOT
|
|
692
|
+
// the message the eventId names (see TurnAnchorCapture). Best-effort + reset for the next turn.
|
|
298
693
|
await anchor.onTurnEnd();
|
|
299
694
|
}
|
|
300
695
|
}
|
|
696
|
+
// E12 (shell-host contract): after a COMPLETED run, pull core's opt-in post-completion prompt suggestions (a fire-and-forget
|
|
697
|
+
// pass in core; suggestions() never rejects + resolves to [] when off / not completed / empty). UNTRUSTED model
|
|
698
|
+
// text for the shell UI ONLY → redact + persist as a `suggestions` event so the shell reads it off the events tail
|
|
699
|
+
// (like model_usage); NEVER re-fed to a model. Gated on a COMPLETED terminal — a suspended/parked/failed leg has
|
|
700
|
+
// none (core also returns [] for a non-completed task; the explicit status gate avoids the needless call + pins the
|
|
701
|
+
// completed-only contract rather than depending on core's guard).
|
|
301
702
|
if (spec.suggestNextPrompts && reached?.kind === "done" && reached.result.status === "completed") {
|
|
302
703
|
const suggestions = await stream.suggestions().catch(() => []);
|
|
704
|
+
// 🔴 **空结果也要落事件**(2026-07-26):这个事件是「已结算」的唯一信号。此前 `if (suggestions.length)`
|
|
705
|
+
// 才 append ⇒ 「跑完了、确实一条都没有」在轮询面上与「还没跑完」完全同形(都是 `suggestions: undefined`)
|
|
706
|
+
// ⇒ 轮询方无从判断该不该停。而 `setTerminal`(上方)在本段**之前**执行,所以确实存在一个真实的
|
|
707
|
+
// pending 窗口 —— 这正是本仓刚在后台 shell `poll()` 上修过的**同一类**:消费方拿终态当停止条件,
|
|
708
|
+
// 而数据在终态之后才到。⇒ 事件在 = 已结算(哪怕是空);事件不在 = 仍在跑。
|
|
303
709
|
await append("suggestions", { suggestions: suggestions.map((s) => redactSecrets(s)) });
|
|
304
710
|
}
|
|
305
711
|
};
|
|
712
|
+
// E23 + §4④: nest the elicitation AND AskUserQuestion per-run contexts (each its own ALS) around the run body — a
|
|
713
|
+
// background run's inbound elicitation AND the agent's AskUserQuestion both route to this run's durable events tail
|
|
714
|
+
// + owner-gated respond. Same id/owner/signal; the nesting order is immaterial (disjoint ALS).
|
|
306
715
|
await withPrincipal(principal, () => {
|
|
307
716
|
const withElicit = elicitation
|
|
308
717
|
? () => elicitation.runWithContext({ taskId, owner: elicitOwner ?? owner, emit: elicitEmit, abortSignal: cancelCtrl.signal }, runBody)
|
|
@@ -310,10 +719,13 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
310
719
|
const withQuestion = () => question
|
|
311
720
|
? question.runWithContext({ taskId, owner: elicitOwner ?? owner, emit: questionEmit, abortSignal: cancelCtrl.signal }, withElicit)
|
|
312
721
|
: withElicit();
|
|
722
|
+
// SendUserFile 帧与 question 帧同投递面(durable events tail,web 渲染文件卡);嵌套序无关(ALS 互不相交)。
|
|
313
723
|
return sendUserFile
|
|
314
724
|
? sendUserFile.runWithContext({ taskId, emit: (frame) => { const { type, ...rest } = frame; return append(type, rest); } }, withQuestion)
|
|
315
725
|
: withQuestion();
|
|
316
726
|
});
|
|
727
|
+
// Cancelled mid-stream and the generator ended WITHOUT a terminal event (abort can end the stream cleanly
|
|
728
|
+
// rather than throwing) — finalize cancelled here so the run isn't left "running" for the reaper to catch.
|
|
317
729
|
if (!reached && cancelCtrl.signal.aborted) {
|
|
318
730
|
await flush();
|
|
319
731
|
const c = cancelledResult();
|
|
@@ -326,29 +738,41 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
326
738
|
catch (err) {
|
|
327
739
|
const message = err instanceof Error ? err.message : String(err);
|
|
328
740
|
try {
|
|
329
|
-
await flush();
|
|
741
|
+
await flush(); // persist any buffered turn text before recording the failure (don't drop it)
|
|
330
742
|
if (reached?.kind === "done") {
|
|
743
|
+
// The task already produced a terminal result (its `done` event is durably logged); only the run-row
|
|
744
|
+
// write failed. Re-attempt THAT write — never write `failed` over a completed run.
|
|
331
745
|
await runStore.setTerminal(taskId, reached.result.status, reached.result, reached.result.errorMessage ?? null);
|
|
332
746
|
}
|
|
333
747
|
else if (reached?.kind === "suspended") {
|
|
334
|
-
await runStore.setSuspended(taskId);
|
|
748
|
+
await runStore.setSuspended(taskId); // parked, not failed — re-attempt the suspend row write
|
|
335
749
|
}
|
|
336
750
|
else if (reached?.kind === "needs_review") {
|
|
337
|
-
await runStore.setNeedsReview(taskId);
|
|
751
|
+
await runStore.setNeedsReview(taskId); // D-B: parked on a review gate — re-attempt the review-park row write
|
|
338
752
|
}
|
|
339
753
|
else if (cancelCtrl.signal.aborted) {
|
|
754
|
+
// Cancelled: the abort threw out of the stream/verify/cascade. Record a cancelled terminal (queryable
|
|
755
|
+
// errorCode) instead of a generic failure, so the portal can show "cancelled by user" not an error.
|
|
340
756
|
const c = cancelledResult();
|
|
341
757
|
await append("failed", { errorMessage: c.errorMessage ?? "cancelled by user", errorCode: "cancelled" });
|
|
342
758
|
await runStore.setTerminal(taskId, "failed", c, c.errorMessage ?? null);
|
|
343
759
|
}
|
|
344
760
|
else {
|
|
761
|
+
// BL-12: redact the raw exception message before it lands in the durable event log / run row — a tool
|
|
762
|
+
// or transport error can carry a secret (a credentialed URL, a token) and the trace surface is served
|
|
763
|
+
// to TRACE_TOKEN holders. redactSecrets is best-effort + idempotent (the read side redacts again, BL-13).
|
|
345
764
|
const safeMsg = redactSecrets(message);
|
|
346
765
|
await append("failed", { errorMessage: safeMsg });
|
|
347
766
|
await runStore.setTerminal(taskId, "failed", null, safeMsg);
|
|
348
767
|
}
|
|
349
768
|
}
|
|
350
769
|
catch {
|
|
770
|
+
/* best-effort: the stale reaper will catch a run we couldn't finalize */
|
|
351
771
|
}
|
|
772
|
+
// recordDone runs exactly once: the success paths call it inline (and return/break before the throw); we
|
|
773
|
+
// reach here only if the terminal-row write threw AFTER the event was logged, or on a genuine failure.
|
|
774
|
+
// A cancelled terminal keeps its distinct metric label even on this retry path (council B2: the result's
|
|
775
|
+
// STATUS is "failed" — labeling by status would count a user cancel as a failure).
|
|
352
776
|
if (reached?.kind === "done")
|
|
353
777
|
recordDone(reached.result, reached.result.errorCode === "cancelled" ? "cancelled" : reached.result.status);
|
|
354
778
|
else if (reached?.kind === "suspended") {
|
|
@@ -356,15 +780,22 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
356
780
|
metrics?.inc("tasks_total", { status: "suspended" });
|
|
357
781
|
}
|
|
358
782
|
else if (cancelCtrl.signal.aborted) {
|
|
359
|
-
recordDone(null, "cancelled");
|
|
783
|
+
recordDone(null, "cancelled"); // distinct metric label; the run row is "failed"+errorCode:"cancelled"
|
|
360
784
|
}
|
|
361
785
|
else {
|
|
362
786
|
recordDone(null, "failed");
|
|
363
787
|
}
|
|
364
788
|
}
|
|
365
789
|
finally {
|
|
366
|
-
legLive = false;
|
|
790
|
+
legLive = false; // notifications from here on take the durable-inbox path
|
|
791
|
+
// MF-Fleet: flip/remove the fleet row at the SINGLE finalization point — runs once for EVERY exit path
|
|
792
|
+
// (success / cancel / error / suspend / needs_review). A truly-terminal status leaves the fleet; a PARKED
|
|
793
|
+
// status (suspended→waiting / needs_review→awaiting approval) STAYS (the run is still active). A cancelled
|
|
794
|
+
// run carries status:"failed"+errorCode:"cancelled" — map it to "killed" so the row reads as a kill, not a
|
|
795
|
+
// failure. `reached` undefined ⇒ errored/aborted before any terminal event (catch recorded failed/cancelled).
|
|
367
796
|
if (fleetPublisher) {
|
|
797
|
+
// [1839]①/(a) 案:真终态帧带残局键(usage=stats 映射 / cancel⇒stoppedBy:"user" / transcriptId=sessionId);
|
|
798
|
+
// parked(suspended/needs_review)不带(publisher 侧也再挡一层)。
|
|
368
799
|
if (reached?.kind === "done")
|
|
369
800
|
fleetPublisher.onTerminal(reached.result.errorCode === "cancelled" ? "cancelled" : reached.result.status, fleetRunResiduals(reached.result));
|
|
370
801
|
else if (reached?.kind === "suspended")
|
|
@@ -373,22 +804,32 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
373
804
|
fleetPublisher.onTerminal("needs_review");
|
|
374
805
|
else
|
|
375
806
|
fleetPublisher.onTerminal(cancelCtrl.signal.aborted ? "cancelled" : "failed", {
|
|
807
|
+
// errored/aborted before any terminal event — no TaskResult.stats to map; the transcript still exists.
|
|
376
808
|
...(cancelCtrl.signal.aborted ? { stoppedBy: "user" } : {}),
|
|
377
809
|
...(spec.sessionId ? { transcriptId: spec.sessionId } : {}),
|
|
378
810
|
});
|
|
379
811
|
}
|
|
812
|
+
// 对抗复查 B-3(HIGH):identity-guard these two like `steerable` below — the run row flips suspended
|
|
813
|
+
// BEFORE this finally runs, so a fast resume can re-register the SAME taskId (a fresh cancelCtrl /
|
|
814
|
+
// preemptCtrl) in the window; an unconditional delete then evicted the NEW leg's entry, and drain
|
|
815
|
+
// under-counted the resuming leg (its cancel/preempt handles also went unreachable).
|
|
380
816
|
if (inflight?.get(taskId) === cancelCtrl)
|
|
381
817
|
inflight.delete(taskId);
|
|
382
818
|
if (preemptable?.get(taskId) === preemptCtrl)
|
|
383
819
|
preemptable.delete(taskId);
|
|
820
|
+
// Identity-guarded (1.78 review LOW, parity with the sync/resume legs): only delete the handle THIS leg
|
|
821
|
+
// registered and only if it is still ours — a fast sibling leg re-registering the same taskId must not lose
|
|
822
|
+
// its fresh handle to our teardown; never registered ⇒ nothing of ours to delete.
|
|
384
823
|
if (steerableStreamRef !== undefined && steerable?.get(taskId) === steerableStreamRef)
|
|
385
824
|
steerable.delete(taskId);
|
|
386
825
|
for (const evict of subagentHandleEvictions)
|
|
387
|
-
evict();
|
|
388
|
-
modelUsage?.clear(taskId);
|
|
389
|
-
promptManifests?.clear(taskId, manifestLease);
|
|
826
|
+
evict(); // design/122 ③: leg-end handle eviction (identity-guarded — a fast sibling leg's fresh handles survive)
|
|
827
|
+
modelUsage?.clear(taskId); // E8: drop any un-drained accumulation (abandoned/errored leg) — no memory leak
|
|
828
|
+
promptManifests?.clear(taskId, manifestLease); // [998]②: lease-guarded — a stale leg never tears down the new leg's slot
|
|
390
829
|
clearInterval(heartbeat);
|
|
391
830
|
}
|
|
392
831
|
}
|
|
832
|
+
/** How often a running instance refreshes its run's updated_at (liveness, independent of events).
|
|
833
|
+
* Must stay strictly below `runStaleSec` (asserted at startup) or the reaper would race live runs. */
|
|
393
834
|
export const HEARTBEAT_MS = 30_000;
|
|
394
835
|
//# sourceMappingURL=runs.js.map
|