@zhushanwen/subagent-core 0.2.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-43ONBFZX.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-V3VHZ2VX.js +59 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
- package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
- package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
- package/dist/execution/engine/engines/zcode/constants.js +39 -7
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +18234 -1277
- package/dist/index.d.cts +5646 -673
- package/dist/index.d.ts +5646 -673
- package/dist/index.js +17828 -1277
- package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
- package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
- package/package.json +7 -4
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +93 -3
- package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +77 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +278 -16
- package/src/execution/__tests__/gc-timer.test.ts +57 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +89 -5
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
- package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
- package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +471 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +155 -11
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +13 -12
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +298 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +94 -3
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +598 -0
- package/src/execution/engine/engines/zcode/constants.ts +147 -14
- package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +22 -150
- package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +65 -7
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +181 -85
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +148 -88
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +127 -102
- package/src/execution/settled-watchdog.ts +311 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +1106 -907
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +126 -75
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +542 -9
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +8 -8
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +25 -79
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +160 -104
- package/src/orchestration/lifecycle.ts +371 -97
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +15 -15
- package/src/orchestration/models/trace.ts +9 -9
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +28 -28
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +123 -90
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +261 -217
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +323 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +182 -68
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +97 -204
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/schema-jsonify.ts +1 -1
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +165 -92
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/execution/session-runner.ts +0 -1789
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow Extension — error-recovery
|
|
2
|
+
* Workflow Extension — worker-message-pump(原 error-recovery,D5-① 更名)
|
|
3
3
|
*
|
|
4
|
-
* Worker
|
|
4
|
+
* Worker 消息泵 + 失败恢复 free functions(D-12)。承载四类职责:
|
|
5
|
+
* 1. 消息路由:handleWorkerMessage 分发 agent-call / workflow-call / return / error
|
|
6
|
+
* 2. IPC 序列化防御:postMessage 的 DataCloneError 拦截 + fallback 回发(W2)
|
|
7
|
+
* 3. retry/重建:worker/script 错误的指数退避重试 + rebuildRuntime(G3-001)
|
|
8
|
+
* 4. 终态化:finalizeRun ——「transition → closeOut in-flight → save →
|
|
9
|
+
* pending:unregister → onRunDone」终态序列的唯一定义点(D5-② 单点化,收敛原
|
|
10
|
+
* 8 处逐字复制;OR-8 收口步骤与 OR-4/B-4 双围栏内化于本函数)
|
|
5
11
|
*
|
|
6
12
|
* 4 个导出函数(domain-models.md §失败处理矩阵):
|
|
7
|
-
* - handleWorkerMessage(run, raw, deps, handlers) — 路由 agent_call/return/error
|
|
13
|
+
* - handleWorkerMessage(run, raw, deps, handlers) — 路由 agent_call/return/error/log
|
|
8
14
|
* - handleWorkerError(run, err, deps, handlers) — worker uncaught error
|
|
9
15
|
* - handleWorkerExit(run, code, handle, deps, handlers) — worker exit
|
|
10
16
|
* - handleScriptError(run, msg, deps, handlers) — type:"error" from worker
|
|
@@ -13,6 +19,11 @@
|
|
|
13
19
|
* - worker error/exit(非零)→ 3 次重试 + 指数退避 1s/2s/4s;超限 failed
|
|
14
20
|
* - script error → 3 次重试 + 指数退避;超限 failed
|
|
15
21
|
* - 重试前 rebuildRuntime(G3-001:整个 RunRuntime 重建:worker+controller)
|
|
22
|
+
* - [OR-2] 重建动作本身失败(workerHost.start 抛错)回灌本矩阵:计入
|
|
23
|
+
* workerErrorCount,未超限再走退避+重建,超限收敛 done,failed(见
|
|
24
|
+
* scheduleRebuild / handleRebuildStartFailure)——恢复机制不得在它自己的
|
|
25
|
+
* 恢复路径上开口(旧实现裸调 rebuildRuntime → run 永久 running +
|
|
26
|
+
* rejection 经 void 变 unhandledRejection)
|
|
16
27
|
*
|
|
17
28
|
* 关键不变式:
|
|
18
29
|
* - 重试前必须 rebuildRuntime(worker+controller 整体重建,避免孤儿资源)。
|
|
@@ -28,7 +39,7 @@
|
|
|
28
39
|
|
|
29
40
|
import { getLogger } from "../core/logger.ts";
|
|
30
41
|
|
|
31
|
-
import { SLUG_MAX_LENGTH } from "
|
|
42
|
+
import { SLUG_MAX_LENGTH } from "./models/types.ts";
|
|
32
43
|
import { createRecord, updateFromEvent } from "../execution/execution-record.ts";
|
|
33
44
|
import { SubagentStream } from "../execution/stream-sink.ts";
|
|
34
45
|
import type { AgentEvent } from "../shared/agent-event.ts";
|
|
@@ -38,9 +49,10 @@ import { AgentCall } from "./models/agent-call.ts";
|
|
|
38
49
|
import type { LifecycleDeps, WorkerHandlers } from "./models/ports.ts";
|
|
39
50
|
import { RunRuntime } from "./models/run-runtime.ts";
|
|
40
51
|
import type { WorkerLogEntry } from "./models/types.ts";
|
|
41
|
-
import type { AgentCallOpts, AgentResult, ExecutionTraceNode } from "./models/types.ts";
|
|
52
|
+
import type { AgentCallOpts, AgentResult, DoneReason, ExecutionTraceNode } from "./models/types.ts";
|
|
42
53
|
import type { WorkflowRun } from "./models/workflow-run.ts";
|
|
43
54
|
import type { WorkerHandle } from "./worker-handle.ts";
|
|
55
|
+
import { toErrorMessage } from "../core/error-message.ts";
|
|
44
56
|
|
|
45
57
|
const logger = getLogger("subagents");
|
|
46
58
|
|
|
@@ -66,6 +78,22 @@ const MAX_ERROR_LOGS = 500;
|
|
|
66
78
|
/** malformed agent-call 日志中 opts JSON 的预览截断长度(字符)。 */
|
|
67
79
|
const MALFORMED_MSG_LOG_PREVIEW_CHARS = 200;
|
|
68
80
|
|
|
81
|
+
/**
|
|
82
|
+
* [OR-8] run 到达 done 终态时残留 in-flight call 的收口文案。
|
|
83
|
+
*
|
|
84
|
+
* trace/call 状态枚举封闭(无 "cancelled" 态),以 failed + 本固定文案表达
|
|
85
|
+
* 「run 终态前被收口」——GUI/快照侧不再出现 done run 含 running 节点的不一致。
|
|
86
|
+
*/
|
|
87
|
+
const IN_FLIGHT_CALL_CANCELLED_MSG =
|
|
88
|
+
"Cancelled: run reached terminal state while this call was in flight";
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* [P-SD/S-D] 测试钩子 env(设计 §7.3 P-SD):设为正整数 N 时 rebuildRuntime
|
|
92
|
+
* 第 N 次及以后的每次调用抛错,供 S-D「worker 崩溃后重建失败」验收注入。
|
|
93
|
+
* 安全约束:仅显式设置时激活 + 激活即 warn 留痕(见 resolveRebuildFailureInjectionThreshold)。
|
|
94
|
+
*/
|
|
95
|
+
const REBUILD_FAILURE_INJECT_ENV = "XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE";
|
|
96
|
+
|
|
69
97
|
/**
|
|
70
98
|
* [F1] worker 交付前退出(无终态消息)的归因文案。
|
|
71
99
|
*
|
|
@@ -114,7 +142,14 @@ interface WorkflowCallMsg {
|
|
|
114
142
|
args: Record<string, unknown>;
|
|
115
143
|
}
|
|
116
144
|
|
|
117
|
-
|
|
145
|
+
/** 脚本 log() 全局发出的独立诊断消息(协议见 worker-script-builder 头注释,OR-6)。 */
|
|
146
|
+
interface LogMsg {
|
|
147
|
+
type: "log";
|
|
148
|
+
phase?: string;
|
|
149
|
+
message: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
type WorkerMsg = AgentCallMsg | WorkflowCallMsg | ReturnMsg | ErrorMsg | LogMsg;
|
|
118
153
|
|
|
119
154
|
// ── 内部 helper ──────────────────────────────────────────────
|
|
120
155
|
|
|
@@ -163,7 +198,7 @@ async function saveRunBestEffort(
|
|
|
163
198
|
try {
|
|
164
199
|
await deps.store.save(run);
|
|
165
200
|
} catch (err) {
|
|
166
|
-
const m =
|
|
201
|
+
const m = toErrorMessage(err);
|
|
167
202
|
logger.error(
|
|
168
203
|
`[workflow] store.save failed (${context}, runId=${run.runId}): ${m}. ` +
|
|
169
204
|
"Continuing state-machine finalization (in-memory state already terminal).",
|
|
@@ -171,6 +206,101 @@ async function saveRunBestEffort(
|
|
|
171
206
|
}
|
|
172
207
|
}
|
|
173
208
|
|
|
209
|
+
// ── finalizeRun(D5-② 终态 coda 单写点) ──────────────────────
|
|
210
|
+
|
|
211
|
+
/** finalizeRun 的可调项。 */
|
|
212
|
+
export interface FinalizeRunOptions {
|
|
213
|
+
/** store.save 失败日志的上下文标记(OB3 排障定位,如 "handleReturn (done,completed)")。 */
|
|
214
|
+
context: string;
|
|
215
|
+
/**
|
|
216
|
+
* 是否调 deps.onRunDone(Interface 层完成通知)。缺省 true。
|
|
217
|
+
* terminateRunningRuns 传 false——session 切换/关闭语境下主 agent 已离开本
|
|
218
|
+
* session,注入完成通知只会把消息发给已离开的 session(对齐 session_start
|
|
219
|
+
* 恢复先例:只发 unregister、不发 onRunDone)。
|
|
220
|
+
*/
|
|
221
|
+
notifyDone?: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Run 终态五步 coda 的唯一定义点(D5-② + OR-8 收口内化):
|
|
226
|
+
* transition(done) → closeOutInFlightCalls → save(best-effort)→
|
|
227
|
+
* pending:unregister → onRunDone。
|
|
228
|
+
*
|
|
229
|
+
* 收敛前 8 处逐字复制(本文件 6 处 + lifecycle 2 处)已全部改走本函数。原各副本
|
|
230
|
+
* 的三处微差统一为规范形态(收敛裁决,非行为回归):
|
|
231
|
+
* - transition 失败(M12):一律吞掉并中止后续步骤——并发 abort/terminate 抢先
|
|
232
|
+
* 终态化时 illegal-transition 是预期事件,本路径的 unregister/onRunDone 语义已
|
|
233
|
+
* 由抢先方兑现,重复执行只会造成重复注销/重复通知。
|
|
234
|
+
* - save 失败(SW-DATA-3):一律 best-effort——save 抛错若向上抛,handle* 的
|
|
235
|
+
* 调用方(worker-host 绑定处 `void handlers.onXxx(...)`)无人接 →
|
|
236
|
+
* unhandledRejection + pending:unregister / onRunDone 不执行 → pending 通知
|
|
237
|
+
* 幽灵注销(列表残留永不清理的 running 条目)。
|
|
238
|
+
* - unregister reason:`run.state.reason ?? doneReason`——transition 成功后
|
|
239
|
+
* reason 恒有值(不变式 I2),兜底仅防御异常形态,取本路径的 doneReason 语义
|
|
240
|
+
* 最贴近(原各副本 `?? "completed"` / 固定 "failed" / `?? "time_limited"` 三种
|
|
241
|
+
* 死兜底等价收敛)。
|
|
242
|
+
*
|
|
243
|
+
* [OR-8] transition 成功后、save 之前 closeOutInFlightCalls——终态收口残留
|
|
244
|
+
* in-flight call(fire-and-forget agent() 未 await / worker 死亡时已 dispatch 的
|
|
245
|
+
* call),先收口再落盘,内存态与持久化快照同一时点收敛(快照不再含 running 节点)。
|
|
246
|
+
*
|
|
247
|
+
* [OR-4][B-4] unregister 与 onRunDone 各自独立 try 围栏(不共用一个 try——emit
|
|
248
|
+
* 抛错会跳过 onRunDone):这两个是真实副作用,任一同步抛错不得经 worker-host 的
|
|
249
|
+
* `void handlers.onXxx(...)` 变 unhandledRejection 崩宿主;通知总线故障也不得吞掉
|
|
250
|
+
* Interface 层完成回调(runAndWait 轮询依赖 onRunDone 语义收口,被跳过即悬挂)。
|
|
251
|
+
*
|
|
252
|
+
* @returns 是否成功 transition(false = 转移前已被并发终态化,后续步骤未执行)
|
|
253
|
+
*/
|
|
254
|
+
export async function finalizeRun(
|
|
255
|
+
run: WorkflowRun,
|
|
256
|
+
deps: LifecycleDeps,
|
|
257
|
+
doneReason: DoneReason,
|
|
258
|
+
options: FinalizeRunOptions,
|
|
259
|
+
): Promise<boolean> {
|
|
260
|
+
try {
|
|
261
|
+
run.transition("done", doneReason);
|
|
262
|
+
} catch (te: unknown) {
|
|
263
|
+
// M12:并发 abort/terminate 导致 illegal-transition 是预期的,可忽略——
|
|
264
|
+
// 抢先方已兑现 unregister/onRunDone,本路径让位(debug 日志留痕)。
|
|
265
|
+
void te;
|
|
266
|
+
deps.log?.("debug", "workflow:worker-message-pump", "finalize skipped: run already terminal", {
|
|
267
|
+
runId: run.runId,
|
|
268
|
+
doneReason,
|
|
269
|
+
context: options.context,
|
|
270
|
+
});
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
// [OR-8] 终态收口残留 in-flight call(先收口再落盘——快照不再含 running 节点)
|
|
274
|
+
closeOutInFlightCalls(run);
|
|
275
|
+
await saveRunBestEffort(run, deps, options.context);
|
|
276
|
+
deps.log?.("debug", "workflow:worker-message-pump", "run finalized", {
|
|
277
|
+
runId: run.runId,
|
|
278
|
+
reason: run.state.reason,
|
|
279
|
+
context: options.context,
|
|
280
|
+
});
|
|
281
|
+
// [OR-4] emit pending:unregister 独立围栏(M12 同款)——listener 同步抛错 error
|
|
282
|
+
// 留痕后继续,不崩宿主、不跳过 onRunDone
|
|
283
|
+
try {
|
|
284
|
+
deps.eventBus?.emit("pending:unregister", {
|
|
285
|
+
id: run.runId,
|
|
286
|
+
reason: run.state.reason ?? doneReason,
|
|
287
|
+
});
|
|
288
|
+
} catch (err) {
|
|
289
|
+
const m = toErrorMessage(err);
|
|
290
|
+
logger.error(`[workflow] pending:unregister emit failed (${options.context}): ${m}`);
|
|
291
|
+
}
|
|
292
|
+
// [OR-4][B-4] onRunDone 独立围栏(与 emit 拆分——emit 抛错不得吞掉完成回调)
|
|
293
|
+
if (options.notifyDone !== false) {
|
|
294
|
+
try {
|
|
295
|
+
deps.onRunDone?.(run);
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const m = toErrorMessage(err);
|
|
298
|
+
logger.error(`[workflow] onRunDone failed (${options.context}): ${m}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
174
304
|
function delay(ms: number): Promise<void> {
|
|
175
305
|
return new Promise((resolve) => {
|
|
176
306
|
const timer = setTimeout(resolve, ms);
|
|
@@ -180,6 +310,59 @@ function delay(ms: number): Promise<void> {
|
|
|
180
310
|
|
|
181
311
|
// ── rebuildRuntime(G3-001 整重建) ─────────────────────────
|
|
182
312
|
|
|
313
|
+
/** [P-SD] rebuildRuntime 进程级调用计数(注入阈值「第 N 次」的判定基准)。 */
|
|
314
|
+
let rebuildRuntimeInvocationCount = 0;
|
|
315
|
+
/** [P-SD] 钩子激活/非法值 warn 是否已发(多轮 rebuild 只留痕一次,防刷屏)。 */
|
|
316
|
+
let rebuildFailureHookWarned = false;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* [P-SD/S-D] 读取重建失败注入阈值:env XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE=<N>
|
|
320
|
+
* 使 rebuildRuntime 第 N 次及以后的每次调用抛错(配合脚本内 process.exit 制造
|
|
321
|
+
* 「worker 崩溃后重建失败」的 S-D 验收场景)。
|
|
322
|
+
*
|
|
323
|
+
* 安全约束(设计 §7.3 P-SD,对齐 T7① 可见性原则):
|
|
324
|
+
* - 仅显式设置时激活;未设置/空串 = 钩子完全不激活(零行为差);
|
|
325
|
+
* - 首次读取到该 env(无论合法非法)即 logger.warn 留痕一次,杜绝静默生效;
|
|
326
|
+
* - 非法值(非正整数)不激活且 warn 指明原值,杜绝「以为注入了、实际没有」的
|
|
327
|
+
* 静默失效(LC-7 同族教训)。
|
|
328
|
+
*
|
|
329
|
+
* 语义取「第 N 次及以后每次」而非「仅第 N 次」:S-D 验收要求 run 收敛 done,failed——
|
|
330
|
+
* 仅注入一次会在重试预算(MAX_WORKER_RETRIES)耗尽前放行后续重建,run 可能正常完成,
|
|
331
|
+
* 验收不可证伪。连续注入让重试矩阵确定性走完:耗尽后经 handleRebuildStartFailure
|
|
332
|
+
* 收敛 done,failed。
|
|
333
|
+
*
|
|
334
|
+
* @returns 注入阈值(调用序数 >= 阈值的 rebuild 抛错);undefined = 未激活
|
|
335
|
+
*/
|
|
336
|
+
function resolveRebuildFailureInjectionThreshold(): number | undefined {
|
|
337
|
+
const raw = process.env[REBUILD_FAILURE_INJECT_ENV];
|
|
338
|
+
if (raw === undefined || raw === "") return undefined;
|
|
339
|
+
const parsed = Number(raw);
|
|
340
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
341
|
+
if (!rebuildFailureHookWarned) {
|
|
342
|
+
rebuildFailureHookWarned = true;
|
|
343
|
+
logger.warn(
|
|
344
|
+
`[workflow] ${REBUILD_FAILURE_INJECT_ENV}="${raw}" is not a positive integer — ` +
|
|
345
|
+
"test hook INACTIVE, no rebuild failure will be injected",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
if (!rebuildFailureHookWarned) {
|
|
351
|
+
rebuildFailureHookWarned = true;
|
|
352
|
+
logger.warn(
|
|
353
|
+
`[workflow] ${REBUILD_FAILURE_INJECT_ENV}=${raw} ACTIVE — rebuildRuntime invocations ` +
|
|
354
|
+
`#${parsed} and later will throw (S-D test hook; NEVER set in production)`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return parsed;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** 测试辅助:重置注入计数与 warn 状态(仅 __tests__ 导入,生产勿用)。 */
|
|
361
|
+
export function resetRebuildFailureInjectionForTest(): void {
|
|
362
|
+
rebuildRuntimeInvocationCount = 0;
|
|
363
|
+
rebuildFailureHookWarned = false;
|
|
364
|
+
}
|
|
365
|
+
|
|
183
366
|
/**
|
|
184
367
|
* 移除 run 中未真正完成的在飞 call(status !== "done")及其 trace 节点。
|
|
185
368
|
*
|
|
@@ -203,6 +386,50 @@ function discardInFlightCalls(run: WorkflowRun): number[] {
|
|
|
203
386
|
return inFlight.sort((a, b) => a - b);
|
|
204
387
|
}
|
|
205
388
|
|
|
389
|
+
/**
|
|
390
|
+
* [OR-8] run 到达 done 终态时,把 calls Map 残留的 in-flight call(status !== "done")
|
|
391
|
+
* 收口为取消终态(不删除条目——保留调用痕迹,快照/GUI 不再出现 done run 含
|
|
392
|
+
* running 节点的不一致)。
|
|
393
|
+
*
|
|
394
|
+
* 场景:脚本 fire-and-forget agent()(不 await)后 return;或 worker 死亡/abort 时
|
|
395
|
+
* 已 dispatch 未完成的 call。旧实现 trace 节点永久 "running" 并原样落盘
|
|
396
|
+
* (run-snapshot 序列化不做状态修正)。
|
|
397
|
+
*
|
|
398
|
+
* 收口语义(trace/call 状态枚举封闭,无 "cancelled" 态):
|
|
399
|
+
* - AgentCall 补齐 pending→running→done 状态机(markDone 要求 running 前置),
|
|
400
|
+
* result 以 IN_FLIGHT_CALL_CANCELLED_MSG 承载取消原因;
|
|
401
|
+
* - trace 节点置 failed + 固定取消文案 + completedAt(「failed + Cancelled 文案」
|
|
402
|
+
* 即取消的既有表达形态,不新增状态枚举);
|
|
403
|
+
* - node.live 清除(终态 run 无 TUI 轮询,防 ExecutionRecord 滞留)。
|
|
404
|
+
*
|
|
405
|
+
* 调用点约定:每个 transition("done") 成功后、store.save 之前——先收口再落盘,
|
|
406
|
+
* 内存态与持久化快照在同一时点收敛(「run-snapshot 落盘前」的实现形态)。
|
|
407
|
+
* 返回被收口的 callId 数组(升序)供调用方记日志。
|
|
408
|
+
*/
|
|
409
|
+
export function closeOutInFlightCalls(run: WorkflowRun): number[] {
|
|
410
|
+
const inFlight: number[] = [];
|
|
411
|
+
for (const [callId, call] of run.state.calls) {
|
|
412
|
+
if (call.status !== "done") inFlight.push(callId);
|
|
413
|
+
}
|
|
414
|
+
const completedAt = new Date().toISOString();
|
|
415
|
+
for (const callId of inFlight) {
|
|
416
|
+
const call = run.state.calls.get(callId);
|
|
417
|
+
if (!call) continue; // 防御:迭代后被删(正常路径不可达)
|
|
418
|
+
if (call.status === "pending") call.markRunning();
|
|
419
|
+
if (call.status === "running") {
|
|
420
|
+
call.markDone({ content: "", error: IN_FLIGHT_CALL_CANCELLED_MSG });
|
|
421
|
+
}
|
|
422
|
+
call.traceNode.live = undefined;
|
|
423
|
+
run.state.trace.update(callId, {
|
|
424
|
+
status: "failed",
|
|
425
|
+
result: { content: "", error: IN_FLIGHT_CALL_CANCELLED_MSG },
|
|
426
|
+
error: IN_FLIGHT_CALL_CANCELLED_MSG,
|
|
427
|
+
completedAt,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return inFlight.sort((a, b) => a - b);
|
|
431
|
+
}
|
|
432
|
+
|
|
206
433
|
/**
|
|
207
434
|
* 计算 run 的剩余时间预算(ms)[race-F3]。
|
|
208
435
|
*
|
|
@@ -226,30 +453,15 @@ function remainingTimeBudgetMs(run: WorkflowRun): number | undefined {
|
|
|
226
453
|
* 重试前发现时间预算已耗尽的收尾:不 rebuild,直接 done,time_limited 终态。
|
|
227
454
|
*
|
|
228
455
|
* 副作用与 handleWorkerError 超限路径对齐:transition + 持久化 + 注销
|
|
229
|
-
* pending-notification + onRunDone
|
|
230
|
-
* illegal-transition 是预期的,可忽略。
|
|
456
|
+
* pending-notification + onRunDone(D5-② 收敛为 finalizeRun 单写点)。
|
|
231
457
|
*/
|
|
232
458
|
async function finalizeTimeBudgetExhausted(run: WorkflowRun, deps: LifecycleDeps): Promise<void> {
|
|
233
|
-
deps.log?.("debug", "workflow:
|
|
459
|
+
deps.log?.("debug", "workflow:worker-message-pump", "time budget exhausted on rebuild, transition done", {
|
|
234
460
|
runId: run.runId,
|
|
235
461
|
budgetTimeMs: run.spec.budgetTimeMs,
|
|
236
462
|
});
|
|
237
463
|
run.state.error = run.state.error ?? `Time budget exhausted (${run.spec.budgetTimeMs} ms wall clock) before retry rebuild`;
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
run.transition("done", "time_limited");
|
|
241
|
-
transitioned = true;
|
|
242
|
-
} catch (te: unknown) {
|
|
243
|
-
// run 可能在检查后、transition 前被并发 abort——预期,不记错
|
|
244
|
-
void te;
|
|
245
|
-
}
|
|
246
|
-
if (!transitioned) return;
|
|
247
|
-
await deps.store.save(run).catch((e: unknown) => {
|
|
248
|
-
const m = e instanceof Error ? e.message : String(e);
|
|
249
|
-
logger.error(`[workflow] store.save failed (time budget exhausted): ${m}`);
|
|
250
|
-
});
|
|
251
|
-
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "time_limited" });
|
|
252
|
-
deps.onRunDone?.(run);
|
|
464
|
+
await finalizeRun(run, deps, "time_limited", { context: "time budget exhausted on rebuild" });
|
|
253
465
|
}
|
|
254
466
|
|
|
255
467
|
/**
|
|
@@ -275,58 +487,68 @@ export function rebuildRuntime(
|
|
|
275
487
|
deps: LifecycleDeps,
|
|
276
488
|
handlers: WorkerHandlers,
|
|
277
489
|
): void {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
490
|
+
// [P-SD] 测试钩子注入点:先于任何副作用(模拟 workerHost.start 抛错)。抛错由
|
|
491
|
+
// scheduleRebuild 的 catch 接住回灌重试矩阵([OR-2]),不再裸抛。
|
|
492
|
+
rebuildRuntimeInvocationCount += 1;
|
|
493
|
+
const injectThreshold = resolveRebuildFailureInjectionThreshold();
|
|
494
|
+
if (injectThreshold !== undefined && rebuildRuntimeInvocationCount >= injectThreshold) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`[S-D test hook] injected rebuildRuntime failure ` +
|
|
497
|
+
`(invocation #${rebuildRuntimeInvocationCount}, ${REBUILD_FAILURE_INJECT_ENV}>=${injectThreshold})`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
// OB3(可观察性):rebuild 关键节点 debug 日志——此前函数体 0 处 deps.log,
|
|
501
|
+
// 崩溃自愈只能靠行为证据诊断(L1 入口 / L2 重排 / L3 discard / L4 完成)。
|
|
502
|
+
deps.log?.("debug", "workflow:worker-message-pump", "runtime rebuild start", {
|
|
281
503
|
runId: run.runId,
|
|
282
504
|
budgetTimeMs: run.spec.budgetTimeMs,
|
|
283
505
|
});
|
|
284
506
|
const controller = new AbortController();
|
|
285
507
|
const worker = deps.workerHost.start(run.spec, run.spec.args, handlers);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
508
|
+
// D-12 regression fix (round-2 #2):重新调度 run 级墙钟预算计时器。
|
|
509
|
+
// replaceRuntime 释放旧 runtime 时 clearTimeout 了旧计时器(run-runtime.release),
|
|
510
|
+
// 新 runtime 必须重排,否则带 budgetTimeMs 的 run 命中一次 worker/script 错误重试后
|
|
511
|
+
// 时间预算静默失效(直到 rebuildRuntime 才重排——本函数即唯一重排点)。
|
|
512
|
+
// deps.scheduleTimeBudget 由 Interface 层注入;未注入时(旧测试)跳过重排(兼容,
|
|
513
|
+
// 不影响无时间预算的 run)。
|
|
514
|
+
// 重排分支改为 if——语义与原三元一致(同一条件调 scheduleTimeBudget),仅为在
|
|
515
|
+
// 分支内记 L2 日志,控制流/异常语义零变化。
|
|
516
|
+
// [race-F3] 重排值改为剩余墙钟(remainingTimeBudgetMs)而非满额——重试不重置预算;
|
|
517
|
+
// L2 日志 payload 同步报实际重排值(排障时与 setTimeout 对得上)。remaining > 0
|
|
518
|
+
// 由调用方 scheduleRebuild 保证(耗尽在那里转 time_limited,不进本函数);本处
|
|
519
|
+
// remaining <= 0 时不挂 timer(防御直调,宁可不挂也不能挂出 0ms 立即触发)。
|
|
298
520
|
let timeBudgetTimer: ReturnType<typeof setTimeout> | undefined;
|
|
299
521
|
const remainingBudgetMs = remainingTimeBudgetMs(run);
|
|
300
522
|
if (remainingBudgetMs !== undefined && remainingBudgetMs > 0 && deps.scheduleTimeBudget) {
|
|
301
523
|
timeBudgetTimer = deps.scheduleTimeBudget(run.runId, remainingBudgetMs);
|
|
302
|
-
deps.log?.("debug", "workflow:
|
|
524
|
+
deps.log?.("debug", "workflow:worker-message-pump", "time budget rescheduled", {
|
|
303
525
|
runId: run.runId,
|
|
304
526
|
budgetTimeMs: remainingBudgetMs,
|
|
305
527
|
});
|
|
306
528
|
}
|
|
307
529
|
run.replaceRuntime(new RunRuntime(worker, controller, timeBudgetTimer));
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
530
|
+
// 清除被旧 runtime abort 的在飞 call——必须在 replaceRuntime 之后同步执行(无
|
|
531
|
+
// await 间隔):replaceRuntime 同步 abort 旧 controller + terminate 旧 worker,
|
|
532
|
+
// 在飞 executeAgentCall 的 finalize 发生在 `await runner.run` resolve 后的
|
|
533
|
+
// microtask,此刻在飞 call 仍为 "running"/"pending"(status !== "done")可精确
|
|
534
|
+
// 清理;genuinely-done 的 call 保留(重跑 replay)。放 delay 退避之前会误删退避
|
|
535
|
+
// 期间自然完成的真结果(重跑重复耗 token);放任何 await 之后,假失败已 finalize
|
|
536
|
+
// 为 "done" 挡不住——重跑 replay 会把 abort 错误当真结果回放,静默污染输出。
|
|
537
|
+
// 注意:discard 只清 Map/trace 条目,旧 executeAgentCall 的 promise 链仍会醒来
|
|
538
|
+
// finalize。markDone 在孤儿实例上无害,但后续投递并非 no-op——postAgentResult
|
|
539
|
+
// 会投给 run.runtime(已是新 worker)的同 callId pending,劫持重跑调用(实测
|
|
540
|
+
// S7-second 竞态:旧失败结果被 worker 侧 resolve 为空串 → 脚本假成功);
|
|
541
|
+
// finalizeCall 的 trace.update 在重跑已 append 同 stepIndex 新节点时命中新节点
|
|
542
|
+
// (瞬时污染,由重跑完成时的 update 覆盖)。该投递由 dispatchAgentCall 的
|
|
543
|
+
// 孤儿守卫(isOrphanedCall)拦截,trace.update 的瞬时污染由 executeAgentCall
|
|
544
|
+
// 的 isOrphaned 谓词(OB2)拦截,此处不重复设防。
|
|
323
545
|
const discardedCallIds = discardInFlightCalls(run);
|
|
324
|
-
deps.log?.("debug", "workflow:
|
|
546
|
+
deps.log?.("debug", "workflow:worker-message-pump", "in-flight calls discarded", {
|
|
325
547
|
runId: run.runId,
|
|
326
548
|
callIds: discardedCallIds,
|
|
327
549
|
count: discardedCallIds.length,
|
|
328
550
|
});
|
|
329
|
-
deps.log?.("debug", "workflow:
|
|
551
|
+
deps.log?.("debug", "workflow:worker-message-pump", "runtime rebuild complete", {
|
|
330
552
|
runId: run.runId,
|
|
331
553
|
});
|
|
332
554
|
}
|
|
@@ -339,6 +561,11 @@ export function rebuildRuntime(
|
|
|
339
561
|
* agent_call → 派发 executeAgentCall(异步,不 await——立即返回让 worker 继续发消息)
|
|
340
562
|
* return → transition done,completed(脚本正常返回)
|
|
341
563
|
* error → handleScriptError(脚本主动抛错)
|
|
564
|
+
* log → 计入 run.state.errorLogs + debug 留痕([OR-6/T7④] 主线程半边——worker 侧
|
|
565
|
+
* log() 双通路的独立消息面消费点;u-m0a 已接 workerLogs 随 return/error 带回,
|
|
566
|
+
* 本 case 消费独立 {type:"log"} 消息,与 worker-script-builder 协议注释对齐)
|
|
567
|
+
* default → warn 留痕后丢弃([OR-6/T7④] 协议漂移防线——协议文档与实现漂移零
|
|
568
|
+
* 可观测的反面;未知类型静默丢弃会让协议单方面演化不可发现)
|
|
342
569
|
*
|
|
343
570
|
* 终态(done)下的 stale 消息丢弃(P0-1)。
|
|
344
571
|
*/
|
|
@@ -348,10 +575,10 @@ export async function handleWorkerMessage(
|
|
|
348
575
|
deps: LifecycleDeps,
|
|
349
576
|
handlers: WorkerHandlers,
|
|
350
577
|
): Promise<void> {
|
|
351
|
-
|
|
578
|
+
// 终态(done)丢弃 stale 消息(P0-1)
|
|
352
579
|
if (isTerminal(run)) return;
|
|
353
580
|
|
|
354
|
-
|
|
581
|
+
// M7: 形状校验——防畸形 IPC 消息(worker 崩溃/发非对象)导致下游 TypeError
|
|
355
582
|
if (typeof raw !== "object" || raw === null) return;
|
|
356
583
|
const msg = raw as WorkerMsg;
|
|
357
584
|
switch (msg.type) {
|
|
@@ -369,7 +596,7 @@ export async function handleWorkerMessage(
|
|
|
369
596
|
await handleReturn(run, msg, deps);
|
|
370
597
|
return;
|
|
371
598
|
case "error":
|
|
372
|
-
|
|
599
|
+
// M1: 传 handlers(rebuildRuntime 需要)
|
|
373
600
|
// [F1] 同 return——error 也是终态消息,标记本代际已交付(同上防误判)。
|
|
374
601
|
if (run.runtime) run.runtime.receivedTerminalMessage = true;
|
|
375
602
|
await handleScriptError(
|
|
@@ -380,9 +607,46 @@ export async function handleWorkerMessage(
|
|
|
380
607
|
handlers,
|
|
381
608
|
);
|
|
382
609
|
return;
|
|
610
|
+
case "log":
|
|
611
|
+
handleWorkerLog(run, msg, deps);
|
|
612
|
+
return;
|
|
613
|
+
default:
|
|
614
|
+
// [OR-6/T7④] 协议漂移防线:未知消息类型 warn 留痕后丢弃。畸形消息(M7 形状
|
|
615
|
+
// 校验之上、已知类型之外的 type)此前静默穿过 switch——协议注释新增消息类型
|
|
616
|
+
// 而主线程未接线时,这里提供可观测信号(而非零痕迹丢弃)。
|
|
617
|
+
logger.warn(
|
|
618
|
+
`[workflow] unknown worker message type dropped (runId=${run.runId}): ` +
|
|
619
|
+
`${JSON.stringify((msg as { type?: unknown }).type)}`,
|
|
620
|
+
);
|
|
621
|
+
deps.log?.("warn", "workflow:worker-message-pump", "unknown worker message type", {
|
|
622
|
+
runId: run.runId,
|
|
623
|
+
type: (msg as { type?: unknown }).type,
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
383
626
|
}
|
|
384
627
|
}
|
|
385
628
|
|
|
629
|
+
/**
|
|
630
|
+
* 消费 worker 的独立 log 消息([OR-6/T7④] 主线程半边)。
|
|
631
|
+
*
|
|
632
|
+
* 计入 run.state.errorLogs(与 workerLogs 通路的 L9 追加/上限语义一致——该容器
|
|
633
|
+
* 本就承载全级别 worker 日志,"log" 级条目已在其中)+ deps.log debug 留痕
|
|
634
|
+
* (含 phase,协议字段不落 WorkerLogEntry 但排查时可见)。
|
|
635
|
+
* 终态守卫(isTerminal)已由 handleWorkerMessage 前置——此处只管写入。
|
|
636
|
+
*/
|
|
637
|
+
function handleWorkerLog(run: WorkflowRun, msg: LogMsg, deps: LifecycleDeps): void {
|
|
638
|
+
const message = typeof msg.message === "string" ? msg.message : String(msg.message);
|
|
639
|
+
run.state.errorLogs.push({ level: "log", message });
|
|
640
|
+
if (run.state.errorLogs.length > MAX_ERROR_LOGS) {
|
|
641
|
+
run.state.errorLogs = run.state.errorLogs.slice(-MAX_ERROR_LOGS);
|
|
642
|
+
}
|
|
643
|
+
deps.log?.("debug", "workflow:worker-message-pump", "worker log", {
|
|
644
|
+
runId: run.runId,
|
|
645
|
+
phase: msg.phase,
|
|
646
|
+
message,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
386
650
|
/**
|
|
387
651
|
* 派发 agent 调用:构建 AgentCall + trace 节点,异步触发 executeAgentCall。
|
|
388
652
|
*
|
|
@@ -403,6 +667,16 @@ export async function handleWorkerMessage(
|
|
|
403
667
|
* completion 不投递——rebuild 不改 status,第 1 层拦不住跨 runtime 代际的迟到
|
|
404
668
|
* 结果(S7-second 竞态:旧失败结果投给新 worker 劫持重跑 pending → 假成功)。
|
|
405
669
|
*/
|
|
670
|
+
/**
|
|
671
|
+
* M4: agent-call 消息 IPC 字段校验谓词——畸形(opts 非对象/缺失、callId 非数字、
|
|
672
|
+
* prompt 缺失)= true。提取为谓词保持 dispatchAgentCall 主流程可读(圈复杂度门禁)。
|
|
673
|
+
*/
|
|
674
|
+
function isMalformedAgentCallMsg(msg: AgentCallMsg): boolean {
|
|
675
|
+
return typeof msg.callId !== "number" || !Number.isFinite(msg.callId) ||
|
|
676
|
+
typeof msg.opts !== "object" || msg.opts === null ||
|
|
677
|
+
typeof msg.opts.prompt !== "string";
|
|
678
|
+
}
|
|
679
|
+
|
|
406
680
|
function dispatchAgentCall(
|
|
407
681
|
run: WorkflowRun,
|
|
408
682
|
msg: AgentCallMsg,
|
|
@@ -411,14 +685,12 @@ function dispatchAgentCall(
|
|
|
411
685
|
// M4: IPC 字段校验——畸形 agent-call 消息(opts 非对象/缺失、callId 非数字、prompt 缺失)
|
|
412
686
|
// 不写 trace / 不 postAgentResult——这类消息通常意味着 worker 模块版本不匹配或内存损坏,
|
|
413
687
|
// 回发结果给 worker 也没意义(worker 可能已崩)。仅记日志,让 worker timeout/exit 路径接管。
|
|
414
|
-
if (
|
|
415
|
-
typeof msg.opts !== "object" || msg.opts === null ||
|
|
416
|
-
typeof msg.opts.prompt !== "string") {
|
|
688
|
+
if (isMalformedAgentCallMsg(msg)) {
|
|
417
689
|
logger.error(`[workflow] malformed agent-call message: callId=${JSON.stringify(msg.callId)}, opts=${JSON.stringify(msg.opts)?.slice(0, MALFORMED_MSG_LOG_PREVIEW_CHARS)}`);
|
|
418
690
|
return;
|
|
419
691
|
}
|
|
420
692
|
|
|
421
|
-
|
|
693
|
+
// 已缓存的调用直接 replay(跨 rebuild——崩溃重建后重跑脚本,已完成调用按 callId 命中缓存)
|
|
422
694
|
const cached = run.state.calls.get(msg.callId);
|
|
423
695
|
if (cached && cached.status === "done") {
|
|
424
696
|
postAgentResult(run, msg.callId, cached.result!, true);
|
|
@@ -430,12 +702,14 @@ function dispatchAgentCall(
|
|
|
430
702
|
// slug 复用 agentName(超长截断),live record 的 slug 仅用于 TUI 展示。
|
|
431
703
|
const liveSlug = agentName.length > SLUG_MAX_LENGTH ? agentName.slice(0, SLUG_MAX_LENGTH) : agentName;
|
|
432
704
|
const now = new Date().toISOString();
|
|
705
|
+
// 未显式指定 model 的展示口径(live record 与 trace node 共用,两处一致)。
|
|
706
|
+
const model = msg.opts.model ?? "default";
|
|
433
707
|
// live record:收口 agent 执行过程中的 text/thinking/toolCalls/usage,
|
|
434
708
|
// 供 TUI 在 agent 运行期间显示进度(getEventLog/getCurrentActivity)。
|
|
435
709
|
// 完成时由下方 .then 清除(终态由 node.result 承载)。
|
|
436
710
|
const liveRecord = createRecord(String(msg.callId), {
|
|
437
711
|
agent: agentName,
|
|
438
|
-
model
|
|
712
|
+
model,
|
|
439
713
|
mode: "background",
|
|
440
714
|
task: msg.opts.prompt,
|
|
441
715
|
slug: liveSlug,
|
|
@@ -445,7 +719,7 @@ function dispatchAgentCall(
|
|
|
445
719
|
stepIndex: msg.callId,
|
|
446
720
|
agent: agentName,
|
|
447
721
|
task: msg.opts.prompt,
|
|
448
|
-
model
|
|
722
|
+
model,
|
|
449
723
|
status: "running" as const,
|
|
450
724
|
phase: msg.phase,
|
|
451
725
|
startedAt: now,
|
|
@@ -453,8 +727,8 @@ function dispatchAgentCall(
|
|
|
453
727
|
};
|
|
454
728
|
run.state.trace.append(node);
|
|
455
729
|
|
|
456
|
-
|
|
457
|
-
|
|
730
|
+
// 构建 AgentCall(opts 形状对齐 AgentCallOpts;schema: unknown → Record)
|
|
731
|
+
// 跨进程 IPC 边界的 schema 为 unknown,窄化前加 typeof guard 兜底。
|
|
458
732
|
const rawSchema = msg.opts.schema;
|
|
459
733
|
const opts: AgentCallOpts = {
|
|
460
734
|
...msg.opts,
|
|
@@ -464,11 +738,11 @@ function dispatchAgentCall(
|
|
|
464
738
|
: undefined,
|
|
465
739
|
};
|
|
466
740
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
741
|
+
// BL-1:解析 skill/schema → skillPath / schemaEnv / appendSystemPrompt。
|
|
742
|
+
// M2 修正后 resolveAgentOpts 单参数,只处理 schema SO 指令(内容直传)+ skill。
|
|
743
|
+
// agent ref 处理(systemPrompt/model/thinkingLevel)交 resolveIdentity(经
|
|
744
|
+
// getAgentConfig + resolveModel 完整覆盖),消除双重注入与 model 层级混乱。
|
|
745
|
+
// 解析失败(skill 未找到)走 error 路径,不发 slot、不 spawn。
|
|
472
746
|
const resolved = resolveAgentOpts(opts);
|
|
473
747
|
if (resolved.error) {
|
|
474
748
|
const call = new AgentCall(msg.callId, opts, node);
|
|
@@ -485,7 +759,7 @@ function dispatchAgentCall(
|
|
|
485
759
|
});
|
|
486
760
|
postAgentResult(run, msg.callId, errorResult, false);
|
|
487
761
|
deps.store.save(run).catch((e: unknown) => {
|
|
488
|
-
logger.error(`[workflow] store.save failed (resolveAgentOpts): ${
|
|
762
|
+
logger.error(`[workflow] store.save failed (resolveAgentOpts): ${toErrorMessage(e)}`);
|
|
489
763
|
});
|
|
490
764
|
return;
|
|
491
765
|
}
|
|
@@ -493,14 +767,14 @@ function dispatchAgentCall(
|
|
|
493
767
|
const call = new AgentCall(msg.callId, resolved.opts, node);
|
|
494
768
|
run.state.calls.set(msg.callId, call);
|
|
495
769
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
770
|
+
// C-3:agent call 执行入口。
|
|
771
|
+
// (原经 gate.withSlot 包装,并发门闩 gate 已删——no-op 抽象,实际并发由
|
|
772
|
+
// SubagentService ConcurrencyPool 管理;仅保留其 pre-abort 检查语义,见下方
|
|
773
|
+
// dispatchCall 内 signal.aborted 分支。)executeAgentCall 管 retry/budget/stale-context;
|
|
774
|
+
// runner(runner.run)管 spawn pi 子进程。
|
|
775
|
+
// assignRuntime/replaceRuntime 保证 status==="running" ⟺ runtime defined,
|
|
776
|
+
// 故 run.runtime 在此必存在(dispatchAgentCall 仅从 handleWorkerMessage 调用,
|
|
777
|
+
// 后者已守 terminal(isTerminal)早期 return)。fallback new AbortController 已移除。
|
|
504
778
|
const runtime = run.runtime!;
|
|
505
779
|
const signal = runtime.controller.signal;
|
|
506
780
|
// D-005: onEvent 签名升级——executeAndAwait 直接出 AgentEvent(强类型,
|
|
@@ -516,8 +790,8 @@ function dispatchAgentCall(
|
|
|
516
790
|
const stream = deps.streamSink
|
|
517
791
|
? new SubagentStream(`${run.runId}-${msg.callId}`, deps.streamSink)
|
|
518
792
|
: undefined;
|
|
519
|
-
|
|
520
|
-
|
|
793
|
+
// 原 gate.withSlot(fn, signal) 语义内联:pre-aborted 时 reject AbortError(
|
|
794
|
+
// 下方 .catch 依赖此约定不记错),否则直接执行——并发调度归 ConcurrencyPool。
|
|
521
795
|
const dispatchCall = async (): Promise<void> => {
|
|
522
796
|
if (signal.aborted) {
|
|
523
797
|
const abortErr = new Error("Operation aborted before start");
|
|
@@ -525,9 +799,9 @@ function dispatchAgentCall(
|
|
|
525
799
|
throw abortErr;
|
|
526
800
|
}
|
|
527
801
|
try {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
802
|
+
// OB2(S7 残留):isOrphaned 谓词注入——旧代际 finalize 在 trace.update 前被
|
|
803
|
+
// 拦截(判定语义与下方 .then/.catch 守卫同一 isOrphanedCall,详见
|
|
804
|
+
// execute-agent-call.ts finalizeCall 文档注释)。
|
|
531
805
|
await executeAgentCall(call, deps.runner, run.state.budget, signal, run.state.trace, onEvent, stream, () => isOrphanedCall(run, msg.callId, call));
|
|
532
806
|
} finally {
|
|
533
807
|
stream?.dispose();
|
|
@@ -535,97 +809,72 @@ function dispatchAgentCall(
|
|
|
535
809
|
};
|
|
536
810
|
void dispatchCall()
|
|
537
811
|
.then(() => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
812
|
+
// 清除 live record:终态已由 executeAgentCall → finalizeCall 写入 node.result,
|
|
813
|
+
// live 不再需要(且含可变状态,不保留)。无论 stale 与否都清,避免内存泄漏。
|
|
814
|
+
// M4: 必须在 stale guard 之前清,否则跨 rebuild 的迟到 completion 会累积 live record。
|
|
541
815
|
node.live = undefined;
|
|
542
|
-
|
|
816
|
+
// run 终止(终态)后到达的 stale completion 不写 state
|
|
543
817
|
if (run.state.status !== "running") return;
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
818
|
+
// 孤儿 call 守卫(S7-second 竞态):rebuild 的 discardInFlightCalls 已移除本
|
|
819
|
+
// call、或重跑 dispatch 已用新实例替换同 callId 条目时,本 completion 属于旧
|
|
820
|
+
// runtime 代际。postAgentResult 的投递目标是 run.runtime(已是新 worker),
|
|
821
|
+
// 迟到结果会劫持新 worker 内重跑 agent() 的 pending Promise——跳过投递 /
|
|
822
|
+
// budget 同步 / 持久化,仅留日志。executeAgentCall 内 finalizeCall 的
|
|
823
|
+
// trace.update 若已命中重跑新节点(瞬时污染),由重跑完成时的 update 覆盖。
|
|
550
824
|
if (isOrphanedCall(run, msg.callId, call)) {
|
|
551
|
-
deps.log?.("debug", "workflow:
|
|
825
|
+
deps.log?.("debug", "workflow:worker-message-pump", "orphan agent call completion dropped", { runId: run.runId, callId: msg.callId });
|
|
552
826
|
return;
|
|
553
827
|
}
|
|
554
828
|
if (call.result) postAgentResult(run, msg.callId, call.result, false);
|
|
555
|
-
|
|
556
|
-
|
|
829
|
+
// D-12 regression fix (round-2 #1):executeAgentCall 内 consume/incrementCallCount
|
|
830
|
+
// 后同步 worker $BUDGET(否则 $BUDGET.spent()/remaining() 恒为 0)
|
|
557
831
|
postBudgetUpdate(run);
|
|
558
832
|
deps.store.save(run).catch((e: unknown) => {
|
|
559
|
-
const m =
|
|
833
|
+
const m = toErrorMessage(e);
|
|
560
834
|
logger.error(`[workflow] store.save failed (agent call ${msg.callId}): ${m}`);
|
|
561
835
|
});
|
|
562
836
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
837
|
+
// C-2:budget 超限 → 终止整个 run(避免继续 spawn 烧预算)
|
|
838
|
+
// 内联 terminate(不调 lifecycle.abortRun 避免 engine 内循环依赖):
|
|
839
|
+
// 若 run 仍非终态,transition done,budget_limited + 持久化。
|
|
840
|
+
// 上方 status !== "running" 已保证此处非 done(且 finalizeRun 内含 done 让位守卫)。
|
|
567
841
|
if (run.state.budget.isExceeded()) {
|
|
568
842
|
run.state.error = run.state.error ?? "Budget exceeded";
|
|
569
|
-
deps.log?.("debug", "workflow:
|
|
570
|
-
|
|
571
|
-
let transitioned = false;
|
|
572
|
-
try {
|
|
573
|
-
run.transition("done", "budget_limited");
|
|
574
|
-
transitioned = true;
|
|
575
|
-
} catch (te: unknown) {
|
|
576
|
-
// run 可能在 budget 检查后、transition 前被并发 abort——预期,不记错
|
|
577
|
-
void te;
|
|
578
|
-
}
|
|
579
|
-
if (transitioned) {
|
|
580
|
-
deps.store.save(run).catch((e: unknown) => {
|
|
581
|
-
const m = e instanceof Error ? e.message : String(e);
|
|
582
|
-
logger.error(`[workflow] store.save failed (budget done): ${m}`);
|
|
583
|
-
});
|
|
584
|
-
deps.log?.("debug", "workflow:error-recovery", "run saved after budget done", { runId: run.runId, reason: run.state.reason });
|
|
585
|
-
// M12: onRunDone/emit 单独 try——这些是真实副作用,错误不应被静默吞掉
|
|
586
|
-
try {
|
|
587
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
588
|
-
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
589
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
590
|
-
deps.onRunDone?.(run);
|
|
591
|
-
} catch (err) {
|
|
592
|
-
const m = err instanceof Error ? err.message : String(err);
|
|
593
|
-
logger.error(`[workflow] onRunDone/emit failed (budget done): ${m}`);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
843
|
+
deps.log?.("debug", "workflow:worker-message-pump", "budget exceeded, transition done", { runId: run.runId });
|
|
844
|
+
void finalizeRun(run, deps, "budget_limited", { context: "agent call budget done" });
|
|
596
845
|
}
|
|
597
846
|
})
|
|
598
847
|
.catch((err: unknown) => {
|
|
599
|
-
|
|
848
|
+
// pre-abort 检查(原 gate.withSlot 语义)在 dispatchCall 入口 reject AbortError——预期,不记错。
|
|
600
849
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
601
|
-
const message =
|
|
850
|
+
const message = toErrorMessage(err);
|
|
602
851
|
logger.error(`[workflow] agent call ${msg.callId} failed: ${message}`);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
852
|
+
// 兜底回发:executeAgentCall 抛非 Abort 异常时(如 runner undefined 的 TypeError、
|
|
853
|
+
// dispatchCall 内部 bug)原 catch 仅 console.error,worker 内对 callId 的 pending
|
|
854
|
+
// Promise 永不 resolve → agent() 永久 await → worker 脚本挂死。构造 failed AgentResult
|
|
855
|
+
//(与 resolveAgentOpts 失败路径 L262-275 一致的模式)postAgentResult 回 worker,
|
|
856
|
+
// 让 pending Promise resolve(结果为 error),脚本可继续或失败退出。
|
|
857
|
+
// 孤儿 call 守卫(与 .then 对称,S7-second 竞态):rebuild 后本 call 已被 discard
|
|
858
|
+
// 移除/替换——markDone 虽在孤儿实例上无害,但 trace.update 会污染重跑新建的同
|
|
859
|
+
// stepIndex 节点、postAgentResult 会劫持新 worker 的同 callId pending。孤儿时只
|
|
860
|
+
// 留日志,全部跳过。node.live 无条件先清(旧节点已脱离 trace,防御性统一)。
|
|
612
861
|
node.live = undefined;
|
|
613
862
|
if (isOrphanedCall(run, msg.callId, call)) {
|
|
614
|
-
deps.log?.("debug", "workflow:
|
|
863
|
+
deps.log?.("debug", "workflow:worker-message-pump", "orphan agent call failure dropped", { runId: run.runId, callId: msg.callId });
|
|
615
864
|
return;
|
|
616
865
|
}
|
|
617
866
|
const errorResult: AgentResult = { content: "", error: message };
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
867
|
+
// call 已 done(executeAgentCall 内 finalizeCall 已 markDone)时跳过,避免重复 markDone。
|
|
868
|
+
// status 理论上必为 running(executeAgentCall L130 markRunning 先于 reject),pending
|
|
869
|
+
// 分支为防御性保护。非 running/done 意外态:跳过 markDone(markDone 要求 running)。
|
|
621
870
|
if (call.status !== "done") {
|
|
622
871
|
if (call.status === "pending") call.markRunning();
|
|
623
872
|
call.markDone(errorResult);
|
|
624
873
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
874
|
+
// state 一致性三件套(与 resolveAgentOpts 失败 L268-276 / .then L319-325 对等):
|
|
875
|
+
// trace 标 failed + 清 live record(防泄漏)+ 持久化(catch 恰是最需留证的场景)。
|
|
876
|
+
// stale 终态(run 已 done)时 run.runtime 为 undefined,postAgentResult 用
|
|
877
|
+
// optional chaining 跳过 worker 回发;trace/state 写入仍执行(无害,终态快照已存)。
|
|
629
878
|
run.state.trace.update(msg.callId, {
|
|
630
879
|
status: "failed",
|
|
631
880
|
result: errorResult,
|
|
@@ -635,7 +884,7 @@ function dispatchAgentCall(
|
|
|
635
884
|
// S2: 与 .then 对称——catch 路径也同步 worker $BUDGET(幂等)
|
|
636
885
|
postBudgetUpdate(run);
|
|
637
886
|
deps.store.save(run).catch((e: unknown) => {
|
|
638
|
-
logger.error(`[workflow] store.save failed (catch fallback): ${
|
|
887
|
+
logger.error(`[workflow] store.save failed (catch fallback): ${toErrorMessage(e)}`);
|
|
639
888
|
});
|
|
640
889
|
});
|
|
641
890
|
}
|
|
@@ -646,7 +895,7 @@ function dispatchAgentCall(
|
|
|
646
895
|
* postResult(workflow-call)与 postAgentResult(agent-call)各自前缀不同,故 prefix 参数化,
|
|
647
896
|
* 共享返回类型与构造逻辑,避免字面量重复导致形状漂移。
|
|
648
897
|
*
|
|
649
|
-
* W2 防御关键纯函数——export 供独立单测(
|
|
898
|
+
* W2 防御关键纯函数——export 供独立单测(worker-message-pump-serialize-failed-result.test.ts)验证
|
|
650
899
|
* 返回 shape `{content:"", error:"<prefix>: <errMsg>"}`,确保两条 fallback 路径(workflow-call /
|
|
651
900
|
* agent-call)共享同一构造逻辑不漂移。
|
|
652
901
|
*/
|
|
@@ -690,7 +939,7 @@ function dispatchWorkflowCall(
|
|
|
690
939
|
result,
|
|
691
940
|
});
|
|
692
941
|
} catch (err) {
|
|
693
|
-
const errMsg =
|
|
942
|
+
const errMsg = toErrorMessage(err);
|
|
694
943
|
logger.error(`[workflow] postResult (workflow-call callId=${msg.callId}) failed: ${errMsg}. Sending error fallback.`);
|
|
695
944
|
// 回发纯字符串 fallback result(必可克隆),让 worker pending resolve
|
|
696
945
|
try {
|
|
@@ -720,7 +969,7 @@ function dispatchWorkflowCall(
|
|
|
720
969
|
.catch((err: unknown) => {
|
|
721
970
|
postResult({
|
|
722
971
|
content: "",
|
|
723
|
-
error:
|
|
972
|
+
error: toErrorMessage(err),
|
|
724
973
|
});
|
|
725
974
|
});
|
|
726
975
|
}
|
|
@@ -743,7 +992,7 @@ function postAgentResult(
|
|
|
743
992
|
try {
|
|
744
993
|
run.runtime?.worker.postMessage({ type: "agent-result", callId, result, cached });
|
|
745
994
|
} catch (err) {
|
|
746
|
-
const msg =
|
|
995
|
+
const msg = toErrorMessage(err);
|
|
747
996
|
logger.error(`[workflow] postAgentResult failed (callId=${callId}): ${msg}. Result likely contains non-cloneable value.`);
|
|
748
997
|
// 回发纯字符串 fallback result(必可克隆),让 worker pending resolve(避免永久挂起)
|
|
749
998
|
try {
|
|
@@ -766,7 +1015,7 @@ function postAgentResult(
|
|
|
766
1015
|
* 更新 spent()/remaining())。每次 agent 调用消费 usage 后发送,保持 worker 内 $BUDGET
|
|
767
1016
|
* 与主线程 Budget 值对象同步。
|
|
768
1017
|
*
|
|
769
|
-
* D-12 regression fix (round-2 #1):重建 budget-update 发送方。被
|
|
1018
|
+
* D-12 regression fix (round-2 #1):重建 budget-update 发送方。被 worker-message-pump 主路径调用
|
|
770
1019
|
* (dispatch 后同步 worker $BUDGET)——单一实现,避免消息形状漂移。
|
|
771
1020
|
*/
|
|
772
1021
|
export function postBudgetUpdate(run: WorkflowRun): void {
|
|
@@ -779,7 +1028,7 @@ export function postBudgetUpdate(run: WorkflowRun): void {
|
|
|
779
1028
|
},
|
|
780
1029
|
});
|
|
781
1030
|
} catch (err) {
|
|
782
|
-
const msg =
|
|
1031
|
+
const msg = toErrorMessage(err);
|
|
783
1032
|
// budget 是纯 number 不太可能失败,但防御性兜底——budget 同步非关键(worker 仍可
|
|
784
1033
|
// 基于 $BUDGET.spent() 自行累计),失败仅记日志,不中断调用方流程。
|
|
785
1034
|
logger.error(`[workflow] postBudgetUpdate failed: ${msg}. Budget sync to worker skipped (non-critical).`);
|
|
@@ -794,8 +1043,8 @@ async function handleReturn(
|
|
|
794
1043
|
msg: ReturnMsg,
|
|
795
1044
|
deps: LifecycleDeps,
|
|
796
1045
|
): Promise<void> {
|
|
797
|
-
deps.log?.("debug", "workflow:
|
|
798
|
-
|
|
1046
|
+
deps.log?.("debug", "workflow:worker-message-pump", "handleReturn", { runId: run.runId, status: run.state.status });
|
|
1047
|
+
// 捕获 worker 诊断日志(P2-2)
|
|
799
1048
|
// L9: 追加而非覆盖——保留重试历史的诊断日志(各 worker 实例的 console 输出)
|
|
800
1049
|
if (msg.workerLogs && msg.workerLogs.length > 0) {
|
|
801
1050
|
run.state.errorLogs.push(...msg.workerLogs);
|
|
@@ -804,15 +1053,9 @@ async function handleReturn(
|
|
|
804
1053
|
}
|
|
805
1054
|
}
|
|
806
1055
|
run.state.scriptResult = msg.result;
|
|
807
|
-
run
|
|
808
|
-
//
|
|
809
|
-
await
|
|
810
|
-
deps.log?.("debug", "workflow:error-recovery", "run saved after return", { runId: run.runId, reason: run.state.reason });
|
|
811
|
-
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
812
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
813
|
-
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
814
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
815
|
-
deps.onRunDone?.(run);
|
|
1056
|
+
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
1057
|
+
// (D5-② 四步 coda 收敛为 finalizeRun 单写点,含 SW-DATA-3 save 兜底)
|
|
1058
|
+
await finalizeRun(run, deps, "completed", { context: "handleReturn (done,completed)" });
|
|
816
1059
|
}
|
|
817
1060
|
|
|
818
1061
|
// ── handleWorkerError ────────────────────────────────────────
|
|
@@ -835,18 +1078,18 @@ export async function handleWorkerError(
|
|
|
835
1078
|
deps: LifecycleDeps,
|
|
836
1079
|
handlers: WorkerHandlers,
|
|
837
1080
|
): Promise<void> {
|
|
838
|
-
|
|
839
|
-
|
|
1081
|
+
// 与 handleWorkerMessage 对称——终态(done)丢弃 stale error。
|
|
1082
|
+
// 否则终态后到达的 worker error 仍会 workerErrorCount++(污染跨 runtime 计数)。
|
|
840
1083
|
if (isTerminal(run)) return;
|
|
841
1084
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1085
|
+
// [R4-F1] 同代际幂等守卫:worker 崩溃时 error + exit(1) 双事件各派发一次
|
|
1086
|
+
// handleWorkerError(onError 先到,exit 非 0 经 handleWorkerExit 委托二次到达)——
|
|
1087
|
+
// 旧实现单次崩溃 workerErrorCount +2、两个 scheduleRebuild 并行交错(双 rebuild
|
|
1088
|
+
// 各自 new Worker,旧 handle 的 terminate/exit 事件与新 handle 的生命周期互相踩踏)。
|
|
1089
|
+
// 复用 R4 的 receivedTerminalMessage 代际标志(RunRuntime 字段,rebuild 自然重置):
|
|
1090
|
+
// 进入处理前置 true 标记「本代际已有 error/terminal 处理」,第二个事件(无论
|
|
1091
|
+
// onError 直达还是 exit(1) 委托)命中标志直接跳过。新代际的 handleWorkerError
|
|
1092
|
+
// 不受影响(新 RunRuntime 的标志为 false)。
|
|
850
1093
|
if (run.runtime?.receivedTerminalMessage) return;
|
|
851
1094
|
if (run.runtime) run.runtime.receivedTerminalMessage = true;
|
|
852
1095
|
|
|
@@ -858,18 +1101,12 @@ export async function handleWorkerError(
|
|
|
858
1101
|
return;
|
|
859
1102
|
}
|
|
860
1103
|
|
|
861
|
-
|
|
1104
|
+
// 超限 → failed
|
|
862
1105
|
run.state.error = err.message;
|
|
863
|
-
deps.log?.("debug", "workflow:
|
|
864
|
-
run
|
|
865
|
-
//
|
|
866
|
-
await
|
|
867
|
-
deps.log?.("debug", "workflow:error-recovery", "run saved after worker error", { runId: run.runId, reason: run.state.reason });
|
|
868
|
-
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
869
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
870
|
-
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
871
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
872
|
-
deps.onRunDone?.(run);
|
|
1106
|
+
deps.log?.("debug", "workflow:worker-message-pump", "handleWorkerError retries exceeded, transition done", { runId: run.runId, count });
|
|
1107
|
+
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
1108
|
+
// (D5-② 四步 coda 收敛为 finalizeRun 单写点)
|
|
1109
|
+
await finalizeRun(run, deps, "failed", { context: "handleWorkerError (done,failed)" });
|
|
873
1110
|
}
|
|
874
1111
|
|
|
875
1112
|
// ── handleWorkerExit ─────────────────────────────────────────
|
|
@@ -896,7 +1133,7 @@ export async function handleWorkerExit(
|
|
|
896
1133
|
deps: LifecycleDeps,
|
|
897
1134
|
handlers: WorkerHandlers,
|
|
898
1135
|
): Promise<void> {
|
|
899
|
-
|
|
1136
|
+
// G-025: stale exit 事件丢弃(handle 已不是当前 runtime 的 worker)
|
|
900
1137
|
if (!handle.isCurrent) return;
|
|
901
1138
|
if (isTerminal(run)) return;
|
|
902
1139
|
|
|
@@ -907,20 +1144,15 @@ export async function handleWorkerExit(
|
|
|
907
1144
|
// [F1] 无终态消息的 exit(0) = worker 静默退出(不可克隆 return 被吞 / 脚本直调
|
|
908
1145
|
// process.exit(0) 等)。置 failed 保证 runAndWait 必有终态。不重试:rebuild 重跑
|
|
909
1146
|
// 脚本对确定性根因(不可克隆 return)无意义,且 belt 路径优先给用户明确归因。
|
|
910
|
-
deps.log?.("debug", "workflow:
|
|
1147
|
+
deps.log?.("debug", "workflow:worker-message-pump", "worker exited without terminal message, transition done", { runId: run.runId });
|
|
911
1148
|
run.state.error = WORKER_EXITED_WITHOUT_RESULT_MSG;
|
|
912
|
-
run.transition("done", "failed");
|
|
913
|
-
await saveRunBestEffort(run, deps, "handleWorkerExit (done,failed, no terminal message)");
|
|
914
|
-
deps.log?.("debug", "workflow:error-recovery", "run saved after exit without result", { runId: run.runId, reason: run.state.reason });
|
|
915
1149
|
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
916
|
-
|
|
917
|
-
deps
|
|
918
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
919
|
-
deps.onRunDone?.(run);
|
|
1150
|
+
// (D5-② 四步 coda 收敛为 finalizeRun 单写点)
|
|
1151
|
+
await finalizeRun(run, deps, "failed", { context: "handleWorkerExit (done,failed, no terminal message)" });
|
|
920
1152
|
return;
|
|
921
1153
|
}
|
|
922
1154
|
|
|
923
|
-
|
|
1155
|
+
// 非零 exit → 委托 handleWorkerError(C.3: onExit 传 handle 用于竞态防护)
|
|
924
1156
|
await handleWorkerError(
|
|
925
1157
|
run,
|
|
926
1158
|
new Error(`Worker exited with code ${code}`),
|
|
@@ -947,10 +1179,10 @@ export async function handleScriptError(
|
|
|
947
1179
|
deps: LifecycleDeps,
|
|
948
1180
|
handlers: WorkerHandlers,
|
|
949
1181
|
): Promise<void> {
|
|
950
|
-
|
|
1182
|
+
// 与 handleWorkerMessage/handleWorkerError 对称——终态守卫前置。
|
|
951
1183
|
if (isTerminal(run)) return;
|
|
952
1184
|
|
|
953
|
-
|
|
1185
|
+
// P2-2: 捕获 worker 诊断日志
|
|
954
1186
|
// L9: 追加而非覆盖
|
|
955
1187
|
if (workerLogs.length > 0) {
|
|
956
1188
|
run.state.errorLogs.push(...workerLogs);
|
|
@@ -967,18 +1199,12 @@ export async function handleScriptError(
|
|
|
967
1199
|
return;
|
|
968
1200
|
}
|
|
969
1201
|
|
|
970
|
-
|
|
1202
|
+
// 超限 → failed
|
|
971
1203
|
run.state.error = `Workflow failed after ${MAX_WORKER_RETRIES} retries: ${errorMsg}`;
|
|
972
|
-
deps.log?.("debug", "workflow:
|
|
973
|
-
run
|
|
974
|
-
//
|
|
975
|
-
await
|
|
976
|
-
deps.log?.("debug", "workflow:error-recovery", "run saved after script error", { runId: run.runId, reason: run.state.reason });
|
|
977
|
-
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
978
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
979
|
-
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
980
|
-
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
981
|
-
deps.onRunDone?.(run);
|
|
1204
|
+
deps.log?.("debug", "workflow:worker-message-pump", "handleScriptError retries exceeded, transition done", { runId: run.runId, count });
|
|
1205
|
+
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
1206
|
+
// (D5-② 四步 coda 收敛为 finalizeRun 单写点)
|
|
1207
|
+
await finalizeRun(run, deps, "failed", { context: "handleScriptError (done,failed)" });
|
|
982
1208
|
}
|
|
983
1209
|
|
|
984
1210
|
// ── scheduleRebuild(退避 + 重建) ──────────────────────────
|
|
@@ -988,31 +1214,82 @@ export async function handleScriptError(
|
|
|
988
1214
|
*
|
|
989
1215
|
* 退避期间 run 可能被 abort(转终态 done)——rebuildRuntime 前重检状态,终态时
|
|
990
1216
|
* 跳过重建(避免给已终止的 run 启新 worker)。
|
|
1217
|
+
*
|
|
1218
|
+
* [OR-2] rebuildRuntime 抛错(workerHost.start 失败:线程/内存耗尽、eval 编译失败等)
|
|
1219
|
+
* 不再裸抛——裸抛会沿 handleWorkerError/handleScriptError 的 await 链冒泡到
|
|
1220
|
+
* worker-host 的 `void handlers.onXxx(...)` 变 unhandledRejection,且 run 卡 running
|
|
1221
|
+
* (旧 worker 已死、新 worker 未建,再无任何事件可达)。本函数 catch 后回灌重试矩阵
|
|
1222
|
+
* (handleRebuildStartFailure):计入 workerErrorCount,未超限再退避重建,超限收敛
|
|
1223
|
+
* done,failed——恢复机制在它自己的恢复路径上不再开口。
|
|
991
1224
|
*/
|
|
992
1225
|
async function scheduleRebuild(
|
|
993
1226
|
run: WorkflowRun,
|
|
994
1227
|
deps: LifecycleDeps,
|
|
995
1228
|
handlers: WorkerHandlers,
|
|
996
1229
|
): Promise<void> {
|
|
997
|
-
|
|
1230
|
+
// 用当前重试计数算退避(workerErrorCount 或 scriptErrorCount 已递增)
|
|
998
1231
|
const retryIndex = Math.max(
|
|
999
1232
|
run.meta.workerErrorCount ?? 0,
|
|
1000
1233
|
run.meta.scriptErrorCount ?? 0,
|
|
1001
1234
|
);
|
|
1002
1235
|
await delay(backoffDelay(retryIndex));
|
|
1003
1236
|
|
|
1004
|
-
|
|
1237
|
+
// 退避期间状态可能变化——重检
|
|
1005
1238
|
if (isTerminal(run)) return;
|
|
1006
1239
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1240
|
+
// [race-F3] 时间预算折算后已耗尽 → 不再 rebuild 重试,直接 time_limited 终态。
|
|
1241
|
+
// 必须在退避 delay 之后、rebuildRuntime 之前检查:检查前移会在「退避期间耗尽」的
|
|
1242
|
+
// 窗口漏判(rebuild 挂不出 timer,run 预算静默失效);检查点与 rebuildRuntime 的
|
|
1243
|
+
// 计时器挂载之间无 await,remaining > 0 判定不会失效。
|
|
1011
1244
|
const remainingMs = remainingTimeBudgetMs(run);
|
|
1012
1245
|
if (remainingMs !== undefined && remainingMs <= 0) {
|
|
1013
1246
|
await finalizeTimeBudgetExhausted(run, deps);
|
|
1014
1247
|
return;
|
|
1015
1248
|
}
|
|
1016
1249
|
|
|
1017
|
-
|
|
1250
|
+
try {
|
|
1251
|
+
rebuildRuntime(run, deps, handlers);
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
await handleRebuildStartFailure(run, err, deps, handlers);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* [OR-2] rebuildRuntime 抛错回灌重试矩阵。
|
|
1259
|
+
*
|
|
1260
|
+
* 重建动作本身失败按 worker 家族计数(重建的就是 worker)——计入 run.meta.workerErrorCount
|
|
1261
|
+
* (跨 runtime 存活的重试计数载体),与既有 handleWorkerError 共用同一上限
|
|
1262
|
+
* MAX_WORKER_RETRIES 与退避序列:
|
|
1263
|
+
* - count <= MAX → 递归 scheduleRebuild(天然复用退避 / isTerminal 重检 / 预算折算守卫;
|
|
1264
|
+
* 每轮计数 +1,递归深度有界 ≤ MAX_WORKER_RETRIES);
|
|
1265
|
+
* - count > MAX → 收敛 done,failed(transition + 收口 in-flight + 持久化 + 围栏副作用),
|
|
1266
|
+
* run 不再卡 running。
|
|
1267
|
+
*
|
|
1268
|
+
* 终态路径顺序与其余 handle* 对齐:transition → closeOut → save → 围栏 emit/onRunDone。
|
|
1269
|
+
*/
|
|
1270
|
+
async function handleRebuildStartFailure(
|
|
1271
|
+
run: WorkflowRun,
|
|
1272
|
+
err: unknown,
|
|
1273
|
+
deps: LifecycleDeps,
|
|
1274
|
+
handlers: WorkerHandlers,
|
|
1275
|
+
): Promise<void> {
|
|
1276
|
+
if (isTerminal(run)) return;
|
|
1277
|
+
const message = toErrorMessage(err);
|
|
1278
|
+
const count = (run.meta.workerErrorCount ?? 0) + 1;
|
|
1279
|
+
run.meta.workerErrorCount = count;
|
|
1280
|
+
logger.error(
|
|
1281
|
+
`[workflow] rebuildRuntime failed (runId=${run.runId}, attempt ${count}/${MAX_WORKER_RETRIES}): ${message}`,
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
if (count <= MAX_WORKER_RETRIES) {
|
|
1285
|
+
await scheduleRebuild(run, deps, handlers);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// 耗尽 → 收敛 done,failed(不卡 running)
|
|
1290
|
+
run.state.error = `Runtime rebuild failed after ${MAX_WORKER_RETRIES} retries: ${message}`;
|
|
1291
|
+
deps.log?.("debug", "workflow:worker-message-pump", "rebuild retries exhausted, transition done", { runId: run.runId, count });
|
|
1292
|
+
// 终态序列与其余 handle* 对齐:transition → closeOut → save → 围栏 emit/onRunDone
|
|
1293
|
+
// (D5-② 收敛为 finalizeRun 单写点)
|
|
1294
|
+
await finalizeRun(run, deps, "failed", { context: "handleRebuildStartFailure (done,failed)" });
|
|
1018
1295
|
}
|