@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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// src/execution/__tests__/create-concurrency-pool.test.ts
|
|
2
|
+
//
|
|
3
|
+
// U4(subagent-core-sink-design D7):并发池工厂 createConcurrencyPool({maxConcurrent, queuePolicy})。
|
|
4
|
+
// 排队策略差异是宿主声明的有意决策(pi=priority / zsw=strict-fifo),保留为参数而非消灭。
|
|
5
|
+
// 锚定:
|
|
6
|
+
// ①缺省策略与既有 DefaultConcurrencyPool 行为逐点等值(pi 零回归);
|
|
7
|
+
// ②priority / strict-fifo 两策略排队顺序语义互斥可辨。
|
|
8
|
+
//
|
|
9
|
+
// pool 的排队/放行是纯 Promise resolve(microtask),无 timer 参与——microtask flush
|
|
10
|
+
// 推进,不引入真实 setTimeout(TEST-STRATEGY 禁真实等待),无需 fake timers。
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { createConcurrencyPool, DefaultConcurrencyPool } from "../concurrency-pool.ts";
|
|
14
|
+
|
|
15
|
+
/** microtask flush:让排队条目的 resolve/.then 链跑完(pool 无 timer 语义)。 */
|
|
16
|
+
async function flushMicrotasks(): Promise<void> {
|
|
17
|
+
for (let i = 0; i < 10; i++) {
|
|
18
|
+
await Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("createConcurrencyPool 工厂(U4/D7)", () => {
|
|
23
|
+
it("缺省 queuePolicy 与既有 DefaultConcurrencyPool 行为逐点等值(active/排队/release 对照)", async () => {
|
|
24
|
+
const factory = createConcurrencyPool({ maxConcurrent: 2 });
|
|
25
|
+
const classic = new DefaultConcurrencyPool(2);
|
|
26
|
+
|
|
27
|
+
// 槽位放行与 active 计数一致
|
|
28
|
+
await factory.acquire(0);
|
|
29
|
+
await factory.acquire(0);
|
|
30
|
+
await classic.acquire(0);
|
|
31
|
+
await classic.acquire(0);
|
|
32
|
+
expect(factory.active).toBe(classic.active);
|
|
33
|
+
expect(factory.active).toBe(2);
|
|
34
|
+
|
|
35
|
+
// 超额 acquire 同样排队(不放行)
|
|
36
|
+
let factoryQueued = false;
|
|
37
|
+
let classicQueued = false;
|
|
38
|
+
const fq = factory.acquire(0).then(() => {
|
|
39
|
+
factoryQueued = true;
|
|
40
|
+
});
|
|
41
|
+
const cq = classic.acquire(0).then(() => {
|
|
42
|
+
classicQueued = true;
|
|
43
|
+
});
|
|
44
|
+
await flushMicrotasks();
|
|
45
|
+
expect(factoryQueued).toBe(false);
|
|
46
|
+
expect(classicQueued).toBe(false);
|
|
47
|
+
|
|
48
|
+
// release 后排队条目同样获得槽位,active 语义一致
|
|
49
|
+
factory.release();
|
|
50
|
+
classic.release();
|
|
51
|
+
await flushMicrotasks();
|
|
52
|
+
expect(factoryQueued).toBe(true);
|
|
53
|
+
expect(classicQueued).toBe(true);
|
|
54
|
+
expect(factory.active).toBe(classic.active);
|
|
55
|
+
|
|
56
|
+
// 清理后归零一致
|
|
57
|
+
factory.release();
|
|
58
|
+
classic.release();
|
|
59
|
+
factory.release();
|
|
60
|
+
classic.release();
|
|
61
|
+
await flushMicrotasks();
|
|
62
|
+
expect(factory.active).toBe(0);
|
|
63
|
+
expect(classic.active).toBe(0);
|
|
64
|
+
await Promise.allSettled([fq, cq]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("queuePolicy 缺省值等值:不传 queuePolicy 与显式 'priority' 产出同序放行", async () => {
|
|
68
|
+
const implicit = createConcurrencyPool({ maxConcurrent: 1 });
|
|
69
|
+
const explicit = createConcurrencyPool({ maxConcurrent: 1, queuePolicy: "priority" });
|
|
70
|
+
|
|
71
|
+
const implicitOrder: string[] = [];
|
|
72
|
+
const explicitOrder: string[] = [];
|
|
73
|
+
|
|
74
|
+
// 同一序列:低优先级先入队、高优先级后入队 → priority 策略下高优先级插队
|
|
75
|
+
for (const [pool, order] of [
|
|
76
|
+
[implicit, implicitOrder],
|
|
77
|
+
[explicit, explicitOrder],
|
|
78
|
+
] as const) {
|
|
79
|
+
await pool.acquire(0);
|
|
80
|
+
const low = pool.acquire(1000).then(() => {
|
|
81
|
+
order.push("low");
|
|
82
|
+
pool.release();
|
|
83
|
+
});
|
|
84
|
+
const high = pool.acquire(0).then(() => {
|
|
85
|
+
order.push("high");
|
|
86
|
+
pool.release();
|
|
87
|
+
});
|
|
88
|
+
pool.release();
|
|
89
|
+
await Promise.all([low, high]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
expect(implicitOrder).toEqual(["high", "low"]);
|
|
93
|
+
expect(explicitOrder).toEqual(implicitOrder);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("queuePolicy:'priority' 高优先级先获得释放的槽位(pi 既有行为)", async () => {
|
|
97
|
+
const pool = createConcurrencyPool({ maxConcurrent: 1, queuePolicy: "priority" });
|
|
98
|
+
await pool.acquire(1000); // 低优先级占满
|
|
99
|
+
|
|
100
|
+
let lowAcquired = false;
|
|
101
|
+
let highAcquired = false;
|
|
102
|
+
const low = pool.acquire(1000).then(() => {
|
|
103
|
+
lowAcquired = true;
|
|
104
|
+
});
|
|
105
|
+
const high = pool.acquire(0).then(() => {
|
|
106
|
+
highAcquired = true;
|
|
107
|
+
});
|
|
108
|
+
await flushMicrotasks();
|
|
109
|
+
expect(lowAcquired).toBe(false);
|
|
110
|
+
expect(highAcquired).toBe(false);
|
|
111
|
+
|
|
112
|
+
pool.release();
|
|
113
|
+
await flushMicrotasks();
|
|
114
|
+
expect(highAcquired).toBe(true);
|
|
115
|
+
expect(lowAcquired).toBe(false);
|
|
116
|
+
pool.release();
|
|
117
|
+
await Promise.allSettled([low, high]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("queuePolicy:'strict-fifo' 忽略 priority,纯入队顺序放行", async () => {
|
|
121
|
+
const pool = createConcurrencyPool({ maxConcurrent: 1, queuePolicy: "strict-fifo" });
|
|
122
|
+
await pool.acquire(0); // 占满
|
|
123
|
+
|
|
124
|
+
const order: number[] = [];
|
|
125
|
+
// 入队顺序 priority: 10 → 0 → 5。priority 策略会让 0 最先;strict-fifo 必须按 10 → 0 → 5
|
|
126
|
+
const a = pool.acquire(10).then(() => {
|
|
127
|
+
order.push(10);
|
|
128
|
+
pool.release();
|
|
129
|
+
});
|
|
130
|
+
const b = pool.acquire(0).then(() => {
|
|
131
|
+
order.push(0);
|
|
132
|
+
pool.release();
|
|
133
|
+
});
|
|
134
|
+
const c = pool.acquire(5).then(() => {
|
|
135
|
+
order.push(5);
|
|
136
|
+
pool.release();
|
|
137
|
+
});
|
|
138
|
+
pool.release(); // 放行队首(seq 最小 = a)
|
|
139
|
+
|
|
140
|
+
await Promise.all([a, b, c]);
|
|
141
|
+
expect(order).toEqual([10, 0, 5]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("queuePolicy:'strict-fifo' 高优先级不插队(与 priority 策略语义互斥可辨)", async () => {
|
|
145
|
+
const pool = createConcurrencyPool({ maxConcurrent: 1, queuePolicy: "strict-fifo" });
|
|
146
|
+
await pool.acquire(0); // 占满
|
|
147
|
+
|
|
148
|
+
let lowFirstAcquired = false;
|
|
149
|
+
let highSecondAcquired = false;
|
|
150
|
+
// 低优先级先入队,高优先级后入队——priority 策略会放行 high;strict-fifo 必须放行 low
|
|
151
|
+
const low = pool.acquire(1000).then(() => {
|
|
152
|
+
lowFirstAcquired = true;
|
|
153
|
+
});
|
|
154
|
+
const high = pool.acquire(0).then(() => {
|
|
155
|
+
highSecondAcquired = true;
|
|
156
|
+
});
|
|
157
|
+
await flushMicrotasks();
|
|
158
|
+
expect(lowFirstAcquired).toBe(false);
|
|
159
|
+
expect(highSecondAcquired).toBe(false);
|
|
160
|
+
|
|
161
|
+
pool.release();
|
|
162
|
+
await flushMicrotasks();
|
|
163
|
+
expect(lowFirstAcquired).toBe(true);
|
|
164
|
+
expect(highSecondAcquired).toBe(false);
|
|
165
|
+
|
|
166
|
+
pool.release();
|
|
167
|
+
await flushMicrotasks();
|
|
168
|
+
expect(highSecondAcquired).toBe(true);
|
|
169
|
+
await Promise.allSettled([low, high]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("策略不影响并发语义:strict-fifo 的 clamp/active 与 priority 一致", async () => {
|
|
173
|
+
const pool = createConcurrencyPool({ maxConcurrent: 0, queuePolicy: "strict-fifo" });
|
|
174
|
+
// clamp 到 1(C3 修复语义策略无关)
|
|
175
|
+
expect(pool.maxConcurrent).toBe(1);
|
|
176
|
+
await pool.acquire(0);
|
|
177
|
+
expect(pool.active).toBe(1);
|
|
178
|
+
pool.release();
|
|
179
|
+
expect(pool.active).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("strict-fifo 下排队条目 abort 仍 reject(AbortError,H2 语义策略无关)", async () => {
|
|
183
|
+
const pool = createConcurrencyPool({ maxConcurrent: 1, queuePolicy: "strict-fifo" });
|
|
184
|
+
await pool.acquire(0); // 占满
|
|
185
|
+
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
const queued = pool.acquire(0, undefined, controller.signal);
|
|
188
|
+
await flushMicrotasks();
|
|
189
|
+
expect(pool.active).toBe(1); // 仍在排队
|
|
190
|
+
|
|
191
|
+
controller.abort();
|
|
192
|
+
await expect(queued).rejects.toMatchObject({ name: "AbortError" });
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// src/execution/__tests__/delivery-methods.test.ts
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// 冷路径续轮 + chatMode 统一投递单元测试(M2-B1 投递基础设施;D2 单轨后口径)。
|
|
4
4
|
//
|
|
5
5
|
// mock session-runner(runSpawn 受控 + killAllSpawnedChildren 空实现 + getChildByRecord/spawnedChildren
|
|
6
|
-
// 真实 Map 语义),走 SubagentService
|
|
7
|
-
// -
|
|
8
|
-
//
|
|
9
|
-
// -
|
|
6
|
+
// 真实 Map 语义),走 SubagentService.deliverChatMessage → PiEngine.deliverPrompt 真实逻辑:
|
|
7
|
+
// - 冷路径(进程死):续轮 resume spawn(runSpawn 收到 resume 参数);chatMode+done 回 running/round+1;
|
|
8
|
+
// 终态 / 无 sessionFile / 无 controller throw 行动语言
|
|
9
|
+
// - 热路径(进程活):prompt + streamingBehavior(interrupt → steer/followUp)
|
|
10
10
|
//
|
|
11
|
-
// stdin-writer 不 mock(端到端验证
|
|
11
|
+
// stdin-writer 不 mock(端到端验证 PiEngine.deliverPrompt→sendPromptCommand→child.stdin 字节)。
|
|
12
12
|
// [review 修复] 已删除 deliverToRunning describe(busy follow_up/steer 投递)——随
|
|
13
13
|
// deliverToRunning 方法一并移除(无生产调用方,pendingMessages 消费确认制死机制)。
|
|
14
14
|
|
|
@@ -29,17 +29,19 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
|
29
29
|
|
|
30
30
|
// mock session-runner:runSpawn 受控 + killAllSpawnedChildren 空实现;
|
|
31
31
|
// getChildByRecord/spawnedChildren 提供真实 Map 语义(deliverToRunning 注册 mock child 用)。
|
|
32
|
-
vi.mock("../session-runner.ts", () => {
|
|
32
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => {
|
|
33
33
|
const spawnedChildren = new Map<string, unknown>();
|
|
34
34
|
return {
|
|
35
35
|
runSpawn: vi.fn(),
|
|
36
36
|
killAllSpawnedChildren: vi.fn(),
|
|
37
|
+
registerSpawnedChildForRecord: vi.fn(),
|
|
38
|
+
killRecordChildWithEscalation: vi.fn(),
|
|
37
39
|
spawnedChildren,
|
|
38
40
|
getChildByRecord: (id: string): unknown => spawnedChildren.get(id),
|
|
39
41
|
};
|
|
40
42
|
});
|
|
41
43
|
|
|
42
|
-
import { runSpawn, spawnedChildren, type SessionRunnerContext } from "../session-runner.ts";
|
|
44
|
+
import { runSpawn, spawnedChildren, type SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
|
|
43
45
|
import * as lifecycle from "../lifecycle-manager.ts";
|
|
44
46
|
import { createRecord } from "../execution-record.ts";
|
|
45
47
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
@@ -57,11 +59,15 @@ function makeTmpAgentDir(): string {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
function makePi(): PiLike & {
|
|
60
|
-
appendEntry: ReturnType<typeof vi.fn
|
|
61
|
-
events: { emit: ReturnType<typeof vi.fn
|
|
62
|
-
sendMessage: ReturnType<typeof vi.fn
|
|
62
|
+
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
63
|
+
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
64
|
+
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
63
65
|
} {
|
|
64
|
-
return {
|
|
66
|
+
return {
|
|
67
|
+
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
68
|
+
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
69
|
+
sendMessage: vi.fn(() => {}),
|
|
70
|
+
};
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
function makeResult(success: boolean): AgentResult {
|
|
@@ -98,7 +104,7 @@ function makeIdleRecord(id = "sa-chat"): ExecutionRecord {
|
|
|
98
104
|
|
|
99
105
|
/** PassThrough child(可读出 stdin 字节验证 deliverToRunning 写入)。 */
|
|
100
106
|
function makeStreamChild(): ChildProcess {
|
|
101
|
-
// exitCode/signalCode:真 ChildProcess 未退出时均为 null
|
|
107
|
+
// exitCode/signalCode:真 ChildProcess 未退出时均为 null(热路径投递 [race-F5]
|
|
102
108
|
// 写后死进程检测读这两个字段,缺省 undefined 会被误判为已死触发 warn)。
|
|
103
109
|
return { stdin: new PassThrough(), exitCode: null, signalCode: null } as unknown as ChildProcess;
|
|
104
110
|
}
|
|
@@ -106,7 +112,7 @@ function makeStreamChild(): ChildProcess {
|
|
|
106
112
|
function readStdinLines(child: ChildProcess): unknown[] {
|
|
107
113
|
const stream = child.stdin as unknown as PassThrough;
|
|
108
114
|
stream.pause();
|
|
109
|
-
const text = stream.read()?.toString() ?? "";
|
|
115
|
+
const text: string = stream.read()?.toString() ?? "";
|
|
110
116
|
return text
|
|
111
117
|
.split("\n")
|
|
112
118
|
.map((l) => l.trim())
|
|
@@ -114,14 +120,14 @@ function readStdinLines(child: ChildProcess): unknown[] {
|
|
|
114
120
|
.map((l) => JSON.parse(l));
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
describe("
|
|
123
|
+
describe("冷路径续轮(M2-B1 idle 投递;D2 后经 deliverChatMessage 无活进程到达)", () => {
|
|
118
124
|
let agentDir: string;
|
|
119
125
|
let service: SubagentService;
|
|
120
126
|
let record: ExecutionRecord;
|
|
121
127
|
|
|
122
128
|
beforeEach(() => {
|
|
123
129
|
agentDir = makeTmpAgentDir();
|
|
124
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
130
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
125
131
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
126
132
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
127
133
|
record = makeIdleRecord();
|
|
@@ -132,16 +138,16 @@ describe("resumeRound (M2-B1 idle 投递)", () => {
|
|
|
132
138
|
|
|
133
139
|
afterEach(() => {
|
|
134
140
|
service.dispose();
|
|
135
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
141
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
136
142
|
});
|
|
137
143
|
|
|
138
|
-
it("
|
|
144
|
+
it("冷路径续轮(running) → kickOff runSpawn 收到 resume 参数;chatMode+done 回 running/round+1", async () => {
|
|
139
145
|
mockRunSpawn.mockResolvedValueOnce(makeResult(true));
|
|
140
146
|
const beforeRound = record.round;
|
|
141
147
|
|
|
142
|
-
service.
|
|
148
|
+
await service.chatActions.deliverChatMessage(record, "next round msg", false);
|
|
143
149
|
|
|
144
|
-
//
|
|
150
|
+
// 冷路径守卫通过后:status 已手动设回 running(M2-A 边界,绕过 tryTransition)
|
|
145
151
|
expect(record.status).toBe("running");
|
|
146
152
|
|
|
147
153
|
// detached:等 runSpawn 被调
|
|
@@ -161,42 +167,42 @@ describe("resumeRound (M2-B1 idle 投递)", () => {
|
|
|
161
167
|
expect(record.round).toBe(beforeRound! + 1);
|
|
162
168
|
});
|
|
163
169
|
|
|
164
|
-
it("终态 closed record → throw 行动语言(MF-4,仅 running 可续聊),不触发 kickOff", () => {
|
|
170
|
+
it("终态 closed record → throw 行动语言(MF-4,仅 running 可续聊),不触发 kickOff", async () => {
|
|
165
171
|
record.status = "closed";
|
|
166
172
|
// MF-4:行动语言(spec §3.1),不暴露 resume/controller 内部词汇
|
|
167
|
-
expect(
|
|
173
|
+
await expect(service.chatActions.deliverChatMessage(record, "msg", false)).rejects.toThrow(/not ready for a new message/);
|
|
168
174
|
expect(mockRunSpawn).not.toHaveBeenCalled();
|
|
169
175
|
});
|
|
170
176
|
|
|
171
|
-
it("record 无 sessionFile → throw 行动语言(MF-4 canonical session unavailable),不触发 kickOff", () => {
|
|
177
|
+
it("record 无 sessionFile → throw 行动语言(MF-4 canonical session unavailable),不触发 kickOff", async () => {
|
|
172
178
|
record.sessionFile = undefined;
|
|
173
|
-
expect(
|
|
179
|
+
await expect(service.chatActions.deliverChatMessage(record, "msg", false)).rejects.toThrow(/session unavailable/);
|
|
174
180
|
expect(mockRunSpawn).not.toHaveBeenCalled();
|
|
175
181
|
});
|
|
176
182
|
|
|
177
|
-
it("record 无 controller → throw 行动语言(MF-4),不触发 kickOff", () => {
|
|
183
|
+
it("record 无 controller → throw 行动语言(MF-4),不触发 kickOff", async () => {
|
|
178
184
|
record.controller = undefined;
|
|
179
|
-
expect(
|
|
185
|
+
await expect(service.chatActions.deliverChatMessage(record, "msg", false)).rejects.toThrow(/not ready for a new message/);
|
|
180
186
|
expect(mockRunSpawn).not.toHaveBeenCalled();
|
|
181
187
|
});
|
|
182
188
|
});
|
|
183
189
|
|
|
184
190
|
// ============================================================
|
|
185
|
-
//
|
|
191
|
+
// deliverChatMessage(V2 决策 3 chatMode 统一投递:按进程死活分流;D2 后经 engine.interactRecord)
|
|
186
192
|
// ============================================================
|
|
187
193
|
|
|
188
|
-
describe("
|
|
194
|
+
describe("deliverChatMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
189
195
|
let agentDir: string;
|
|
190
196
|
let service: SubagentService;
|
|
191
197
|
let record: ExecutionRecord;
|
|
192
198
|
|
|
193
199
|
beforeEach(() => {
|
|
194
200
|
agentDir = makeTmpAgentDir();
|
|
195
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
201
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
196
202
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
197
203
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
198
204
|
record = makeIdleRecord(); // chatMode:true, idle, round=1
|
|
199
|
-
// sessionFile
|
|
205
|
+
// sessionFile:冷路径续轮需要(热路径不用,设了无害)
|
|
200
206
|
record.sessionFile = path.join(agentDir, "fake-session.jsonl");
|
|
201
207
|
spawnedChildren.clear();
|
|
202
208
|
mockRunSpawn.mockReset();
|
|
@@ -207,15 +213,15 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
207
213
|
service.dispose();
|
|
208
214
|
spawnedChildren.clear();
|
|
209
215
|
lifecycle._resetLifecycleState();
|
|
210
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
216
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
211
217
|
});
|
|
212
218
|
|
|
213
|
-
it("热路径 interrupt=false:进程活 → prompt streamingBehavior:followUp + status=running + pid 记录", () => {
|
|
219
|
+
it("热路径 interrupt=false:进程活 → prompt streamingBehavior:followUp + status=running + pid 记录", async () => {
|
|
214
220
|
const child = makeStreamChild();
|
|
215
221
|
Object.assign(child, { pid: 12345 });
|
|
216
222
|
spawnedChildren.set(record.id, child);
|
|
217
223
|
|
|
218
|
-
service.
|
|
224
|
+
await service.chatActions.deliverChatMessage(record, "after you finish", false);
|
|
219
225
|
|
|
220
226
|
const lines = readStdinLines(child);
|
|
221
227
|
expect(lines).toHaveLength(1);
|
|
@@ -225,11 +231,11 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
225
231
|
expect(record.pid).toBe(12345);
|
|
226
232
|
});
|
|
227
233
|
|
|
228
|
-
it("热路径 interrupt=true:进程活 → prompt streamingBehavior:steer", () => {
|
|
234
|
+
it("热路径 interrupt=true:进程活 → prompt streamingBehavior:steer", async () => {
|
|
229
235
|
const child = makeStreamChild();
|
|
230
236
|
spawnedChildren.set(record.id, child);
|
|
231
237
|
|
|
232
|
-
service.
|
|
238
|
+
await service.chatActions.deliverChatMessage(record, "stop now", true);
|
|
233
239
|
|
|
234
240
|
const lines = readStdinLines(child);
|
|
235
241
|
expect(lines[0]).toMatchObject({ type: "prompt", message: "stop now", streamingBehavior: "steer" });
|
|
@@ -239,13 +245,13 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
239
245
|
// [race-F5] 写后死进程检测:热路径 write 同步成功(数据进内核缓冲)但子进程在读取前
|
|
240
246
|
// 已死(gate/idle kill 竞速)→ 消息将随缓冲静默丢弃。修复:写后检查 exitCode/signalCode,
|
|
241
247
|
// 已死则 warn 留证(含 runId 与消息类型),不抛错不重试(终态已由 kill 路径保证)。
|
|
242
|
-
it("[race-F5] 热路径写后子进程已死 → logger.warn 被记录(含 runId 与消息类型),不抛错", () => {
|
|
248
|
+
it("[race-F5] 热路径写后子进程已死 → logger.warn 被记录(含 runId 与消息类型),不抛错", async () => {
|
|
243
249
|
const child = makeStreamChild();
|
|
244
250
|
// 模拟 gate/idle kill 竞速:写 stdin 成功但子进程已死(SIGTERM 终止形态)
|
|
245
251
|
Object.assign(child, { signalCode: "SIGTERM" });
|
|
246
252
|
spawnedChildren.set(record.id, child);
|
|
247
253
|
|
|
248
|
-
expect(
|
|
254
|
+
await expect(service.chatActions.deliverChatMessage(record, "lost msg", true)).resolves.toBeUndefined();
|
|
249
255
|
|
|
250
256
|
// 写入照常发生(热路径语义不变,不做二次分发)
|
|
251
257
|
const lines = readStdinLines(child);
|
|
@@ -257,13 +263,13 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
257
263
|
);
|
|
258
264
|
});
|
|
259
265
|
|
|
260
|
-
it("[race-F5] 活进程正常投递不触发死进程 warn(守卫不误报)", () => {
|
|
266
|
+
it("[race-F5] 活进程正常投递不触发死进程 warn(守卫不误报)", async () => {
|
|
261
267
|
// loggerMock 是模块级共享 mock:清历史调用后再断言(防前一用例的 warn 干扰)
|
|
262
268
|
loggerMock.warn.mockClear();
|
|
263
269
|
const child = makeStreamChild();
|
|
264
270
|
spawnedChildren.set(record.id, child);
|
|
265
271
|
|
|
266
|
-
service.
|
|
272
|
+
await service.chatActions.deliverChatMessage(record, "normal", false);
|
|
267
273
|
|
|
268
274
|
expect(loggerMock.warn).not.toHaveBeenCalledWith(
|
|
269
275
|
expect.stringContaining("died around stdin write"),
|
|
@@ -271,24 +277,24 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
271
277
|
);
|
|
272
278
|
});
|
|
273
279
|
|
|
274
|
-
it("热路径 disarm idle timer:arm
|
|
280
|
+
it("热路径 disarm idle timer:arm 后投递 → timer 清除(防 turn 期间误杀)", async () => {
|
|
275
281
|
const child = makeStreamChild();
|
|
276
282
|
spawnedChildren.set(record.id, child);
|
|
277
283
|
// 先 arm idle timer(模拟 Step 4a agent_settled 后 armed)
|
|
278
284
|
lifecycle.armIdleTimer(record.id, () => {}, 10000);
|
|
279
285
|
expect(lifecycle.hasIdleTimer(record.id)).toBe(true);
|
|
280
286
|
|
|
281
|
-
service.
|
|
287
|
+
await service.chatActions.deliverChatMessage(record, "msg", false);
|
|
282
288
|
|
|
283
289
|
// disarmIdleTimer 被调 → timer 清除(新 turn 不被 idle timer 误杀)
|
|
284
290
|
expect(lifecycle.hasIdleTimer(record.id)).toBe(false);
|
|
285
291
|
});
|
|
286
292
|
|
|
287
|
-
it("冷路径:进程死(无 child)→
|
|
293
|
+
it("冷路径:进程死(无 child)→ 续轮 resume spawn(runSpawn 收到 resume 参数)", async () => {
|
|
288
294
|
mockRunSpawn.mockResolvedValueOnce(makeResult(true));
|
|
289
295
|
// spawnedChildren 无该 record → getChildByRecord 返回 undefined → 冷路径
|
|
290
296
|
|
|
291
|
-
service.
|
|
297
|
+
await service.chatActions.deliverChatMessage(record, "resume msg", false);
|
|
292
298
|
|
|
293
299
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
294
300
|
const call = mockRunSpawn.mock.calls[0]!;
|
|
@@ -306,7 +312,7 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
306
312
|
Object.assign(child, { killed: true }); // 进程已 kill
|
|
307
313
|
spawnedChildren.set(record.id, child);
|
|
308
314
|
|
|
309
|
-
service.
|
|
315
|
+
await service.chatActions.deliverChatMessage(record, "after kill", false);
|
|
310
316
|
|
|
311
317
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
312
318
|
});
|
|
@@ -314,7 +320,7 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
314
320
|
it("续聊后 agent_settled → onRoundSettled 设 running + round+1(v4 B-1 idle 折入 running,复用 Step 4a 链路)", () => {
|
|
315
321
|
// buildSessionRunnerContext 是 private,类型断言访问(测试专用)
|
|
316
322
|
const ctx = (service as unknown as { buildSessionRunnerContext(): SessionRunnerContext }).buildSessionRunnerContext();
|
|
317
|
-
record.status = "running"; //
|
|
323
|
+
record.status = "running"; // 模拟热路径投递设的新 turn 状态
|
|
318
324
|
const beforeRound = record.round;
|
|
319
325
|
|
|
320
326
|
// 模拟 session-runner 在 agent_settled 时调本回调(Step 4a 接入点)
|
|
@@ -329,21 +335,21 @@ describe("deliverMessage (V2 决策 3 chatMode 统一投递)", () => {
|
|
|
329
335
|
// 冷路径并发守卫(review round2 MF1:同 turn 批量两条 message 双冷路径双 spawn)
|
|
330
336
|
// ============================================================
|
|
331
337
|
// 复现链(reviewer 探针实证):pi 对同一条 assistant message 的 tool calls 顺序执行
|
|
332
|
-
// (subagent tool sequential),tool1
|
|
338
|
+
// (subagent tool sequential),tool1 的投递在冷路径续轮返回即
|
|
333
339
|
// resolve——早于 runSpawn 完成 spawn 注册(session-runner spawnedChildren.set 前有
|
|
334
340
|
// pool.acquire await / writePromptToTempFile 等多个异步点);tool2 立即执行 →
|
|
335
|
-
// getChildByRecord 仍 undefined → 再次冷路径。v4
|
|
341
|
+
// getChildByRecord 仍 undefined → 再次冷路径。v4 两态收敛后续轮的
|
|
336
342
|
// `status !== "running"` 守卫对 idle-resumable record 恒放行(idle 本来就是 running)、
|
|
337
343
|
// `status = "running"` 是幂等写 → 两次 kickOff → runSpawn 被调 2 次 → 两个 pi 子进程
|
|
338
344
|
// 以 --session 同一 JSONL 双写 + 第一个进程脱离 kill 记账成孤儿。
|
|
339
|
-
describe("
|
|
345
|
+
describe("deliverChatMessage 冷路径并发守卫(review round2 MF1)", () => {
|
|
340
346
|
let agentDir: string;
|
|
341
347
|
let service: SubagentService;
|
|
342
348
|
let record: ExecutionRecord;
|
|
343
349
|
|
|
344
350
|
beforeEach(() => {
|
|
345
351
|
agentDir = makeTmpAgentDir();
|
|
346
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
352
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
347
353
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
348
354
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
349
355
|
record = makeIdleRecord();
|
|
@@ -357,7 +363,7 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
|
|
|
357
363
|
service.dispose();
|
|
358
364
|
spawnedChildren.clear();
|
|
359
365
|
lifecycle._resetLifecycleState();
|
|
360
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
366
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
361
367
|
});
|
|
362
368
|
|
|
363
369
|
it("同 record 连续两条 message 冷路径 → 第二条 throw 行动语言,runSpawn 仅 1 次", async () => {
|
|
@@ -369,13 +375,13 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
|
|
|
369
375
|
);
|
|
370
376
|
|
|
371
377
|
// 第一条:正常冷路径 resume
|
|
372
|
-
await expect(service.
|
|
378
|
+
await expect(service.chatActions.deliverChatMessage(record, "first msg", false)).resolves.toBeUndefined();
|
|
373
379
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
374
380
|
|
|
375
381
|
// 第二条:spawn 仍在途(getChildByRecord undefined)→ 再走冷路径。
|
|
376
|
-
//
|
|
382
|
+
// 修复前:续轮守卫恒放行 → 第二次 kickOff → runSpawn 2 次(双 spawn 双写 session)。
|
|
377
383
|
// 修复后:in-flight 守卫 throw 行动语言(MF-4)。
|
|
378
|
-
await expect(service.
|
|
384
|
+
await expect(service.chatActions.deliverChatMessage(record, "second msg", false)).rejects.toThrow(
|
|
379
385
|
/already starting a new round/,
|
|
380
386
|
);
|
|
381
387
|
expect(mockRunSpawn).toHaveBeenCalledTimes(1);
|
|
@@ -386,7 +392,7 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
|
|
|
386
392
|
expect((service as unknown as { resumesInFlight: Set<string> }).resumesInFlight.has(record.id)).toBe(false),
|
|
387
393
|
);
|
|
388
394
|
mockRunSpawn.mockResolvedValueOnce(makeResult(true));
|
|
389
|
-
await expect(service.
|
|
395
|
+
await expect(service.chatActions.deliverChatMessage(record, "third msg", false)).resolves.toBeUndefined();
|
|
390
396
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(2));
|
|
391
397
|
expect(mockRunSpawn.mock.calls[1]![1]).toBe("third msg");
|
|
392
398
|
});
|
|
@@ -399,25 +405,34 @@ describe("deliverMessage 冷路径并发守卫(review round2 MF1)", () => {
|
|
|
399
405
|
() => new Promise<AgentResult>((res) => { releaseA = res; }),
|
|
400
406
|
);
|
|
401
407
|
|
|
402
|
-
await service.
|
|
408
|
+
await service.chatActions.deliverChatMessage(record, "A msg", false);
|
|
403
409
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
404
410
|
|
|
405
411
|
// B 的冷路径不受 A 在途影响
|
|
406
|
-
await expect(service.
|
|
412
|
+
await expect(service.chatActions.deliverChatMessage(recordB, "B msg", false)).resolves.toBeUndefined();
|
|
407
413
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(2));
|
|
408
414
|
expect(mockRunSpawn.mock.calls[1]![0]).toBe(recordB);
|
|
409
415
|
|
|
410
416
|
releaseA(makeResult(true));
|
|
411
417
|
});
|
|
412
418
|
|
|
413
|
-
it("
|
|
414
|
-
//
|
|
419
|
+
it("冷路径续轮在途时 EPIPE 兜底重入同样被守卫拦截(不持锁路径,review round2 MF1)", async () => {
|
|
420
|
+
// D2 后续轮本体是编排层私有,不持锁的重入形态只剩 EPIPE 兜底(PiEngine.deliverPrompt
|
|
421
|
+
// catch 分支直调续轮)——此处经热路径 EPIPE 复现:活 child 的 stdin 已销毁 → 写入
|
|
422
|
+
// EPIPE → 兜底转冷路径。首条消息已占在途守卫时,第二条的 EPIPE 兜底被拒。
|
|
415
423
|
let release!: (r: AgentResult) => void;
|
|
416
|
-
|
|
417
|
-
|
|
424
|
+
mockRunSpawn.mockImplementationOnce(() => new Promise<AgentResult>((res) => { release = res; }));
|
|
425
|
+
// 第一条:冷路径(无 child)→ 续轮在途
|
|
426
|
+
await expect(service.chatActions.deliverChatMessage(record, "first", false)).resolves.toBeUndefined();
|
|
427
|
+
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
418
428
|
|
|
419
|
-
|
|
420
|
-
|
|
429
|
+
// 第二条:EPIPE 兜底(child 活但 stdin 断)→ 兜底转冷路径 → 在途守卫 throw 行动语言
|
|
430
|
+
const epipe = new Error("write EPIPE") as NodeJS.ErrnoException;
|
|
431
|
+
epipe.code = "EPIPE"; // writeStdinLine 的 R3 判据:err.code === 'EPIPE' 才转 throw
|
|
432
|
+
const child = { stdin: { destroyed: false, write: () => { throw epipe; } }, exitCode: null, signalCode: null, killed: false } as unknown as ChildProcess;
|
|
433
|
+
spawnedChildren.set(record.id, child);
|
|
434
|
+
await expect(service.chatActions.deliverChatMessage(record, "second", false)).rejects.toThrow(/already starting a new round/);
|
|
435
|
+
expect(mockRunSpawn).toHaveBeenCalledTimes(1);
|
|
421
436
|
|
|
422
437
|
release(makeResult(true));
|
|
423
438
|
});
|