@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,9 +1,10 @@
|
|
|
1
1
|
// src/execution/__tests__/epipe-fallback.test.ts
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// deliverChatMessage EPIPE 兜底集成测试——热路径 stdin 写入 EPIPE 时自动转冷路径续轮 + 消息重放
|
|
4
|
+
//(D2 后投递协议知识在 PiEngine.deliverPrompt,编排层经 engine.interactRecord 进入)。
|
|
4
5
|
//
|
|
5
6
|
// 场景覆盖:
|
|
6
|
-
// 1. 首次 EPIPE →
|
|
7
|
+
// 1. 首次 EPIPE → 自动转冷路径续轮(runSpawn 收到 resume 参数 + 原消息)
|
|
7
8
|
// 2. 连续 2 次 EPIPE → throw 含恢复指引(防无限循环)
|
|
8
9
|
// 3. 热路径成功写入后清零 EPIPE 计数(正常路径不受影响)
|
|
9
10
|
|
|
@@ -22,17 +23,19 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
|
22
23
|
|
|
23
24
|
// mock session-runner:runSpawn 受控 + killAllSpawnedChildren 空实现;
|
|
24
25
|
// getChildByRecord/spawnedChildren 提供真实 Map 语义。
|
|
25
|
-
vi.mock("../session-runner.ts", () => {
|
|
26
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => {
|
|
26
27
|
const spawnedChildren = new Map<string, unknown>();
|
|
27
28
|
return {
|
|
28
29
|
runSpawn: vi.fn(),
|
|
29
30
|
killAllSpawnedChildren: vi.fn(),
|
|
31
|
+
registerSpawnedChildForRecord: vi.fn(),
|
|
32
|
+
killRecordChildWithEscalation: vi.fn(),
|
|
30
33
|
spawnedChildren,
|
|
31
34
|
getChildByRecord: (id: string): unknown => spawnedChildren.get(id),
|
|
32
35
|
};
|
|
33
36
|
});
|
|
34
37
|
|
|
35
|
-
import { runSpawn, spawnedChildren } from "../session-runner.ts";
|
|
38
|
+
import { runSpawn, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
|
|
36
39
|
import * as lifecycle from "../lifecycle-manager.ts";
|
|
37
40
|
import { createRecord } from "../execution-record.ts";
|
|
38
41
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
@@ -48,11 +51,15 @@ function makeTmpAgentDir(): string {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
function makePi(): PiLike & {
|
|
51
|
-
appendEntry: ReturnType<typeof vi.fn
|
|
52
|
-
events: { emit: ReturnType<typeof vi.fn
|
|
53
|
-
sendMessage: ReturnType<typeof vi.fn
|
|
54
|
+
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
55
|
+
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
56
|
+
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
54
57
|
} {
|
|
55
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
60
|
+
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
61
|
+
sendMessage: vi.fn(() => {}),
|
|
62
|
+
};
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
function makeResult(success: boolean): AgentResult {
|
|
@@ -108,17 +115,17 @@ function makeNormalChild(): ChildProcess {
|
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
// ============================================================
|
|
111
|
-
//
|
|
118
|
+
// deliverChatMessage EPIPE 兜底
|
|
112
119
|
// ============================================================
|
|
113
120
|
|
|
114
|
-
describe("
|
|
121
|
+
describe("deliverChatMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径续轮)", () => {
|
|
115
122
|
let agentDir: string;
|
|
116
123
|
let service: SubagentService;
|
|
117
124
|
let record: ExecutionRecord;
|
|
118
125
|
|
|
119
126
|
beforeEach(() => {
|
|
120
127
|
agentDir = makeTmpAgentDir();
|
|
121
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
128
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
122
129
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
123
130
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
124
131
|
record = makeIdleRecord();
|
|
@@ -133,7 +140,7 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
133
140
|
service.dispose();
|
|
134
141
|
spawnedChildren.clear();
|
|
135
142
|
lifecycle._resetLifecycleState();
|
|
136
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
143
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
137
144
|
});
|
|
138
145
|
|
|
139
146
|
it("首次 EPIPE → 自动转冷路径 resumeRound + 原消息重放", async () => {
|
|
@@ -141,13 +148,13 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
141
148
|
// 注册 EPIPE child(热路径会命中但 stdin.write 抛 EPIPE)
|
|
142
149
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
143
150
|
|
|
144
|
-
//
|
|
145
|
-
await expect(service.
|
|
151
|
+
// 投递不 throw(首次 EPIPE 走续轮兜底)
|
|
152
|
+
await expect(service.chatActions.deliverChatMessage(record, "msg after epipe", false)).resolves.toBeUndefined();
|
|
146
153
|
|
|
147
154
|
// spawnedChildren 中的死进程条目已清理(EPIPE catch 块 delete)
|
|
148
155
|
expect(spawnedChildren.has(record.id)).toBe(false);
|
|
149
156
|
|
|
150
|
-
//
|
|
157
|
+
// 冷路径续轮被触发,runSpawn 收到 resume 参数 + 原消息
|
|
151
158
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
152
159
|
const call = mockRunSpawn.mock.calls[0]!;
|
|
153
160
|
expect(call[1]).toBe("msg after epipe");
|
|
@@ -172,7 +179,7 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
172
179
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
173
180
|
|
|
174
181
|
// 第 1 次:EPIPE → resume(不 throw)
|
|
175
|
-
await service.
|
|
182
|
+
await service.chatActions.deliverChatMessage(record, "first epipe", false);
|
|
176
183
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
177
184
|
|
|
178
185
|
// resume 完成后 record 回 running(v4 B-1:finalizeRoundToIdle 设 running,旧 idle 折入)
|
|
@@ -182,10 +189,10 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
182
189
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
183
190
|
|
|
184
191
|
// 第 2 次 EPIPE → throw 含恢复指引(连续 2 次触发 exhaustion)
|
|
185
|
-
// 注意:第二 EPIPE 时 count 已为 2,catch 块立即 throw
|
|
192
|
+
// 注意:第二 EPIPE 时 count 已为 2,catch 块立即 throw(不经过续轮)。
|
|
186
193
|
let thrown: unknown;
|
|
187
194
|
try {
|
|
188
|
-
await service.
|
|
195
|
+
await service.chatActions.deliverChatMessage(record, "second epipe", false);
|
|
189
196
|
} catch (err) {
|
|
190
197
|
thrown = err;
|
|
191
198
|
}
|
|
@@ -201,13 +208,13 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
201
208
|
|
|
202
209
|
// 第 1 轮:EPIPE → resume
|
|
203
210
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
204
|
-
await service.
|
|
211
|
+
await service.chatActions.deliverChatMessage(record, "epipe msg", false);
|
|
205
212
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
206
213
|
await vi.waitFor(() => expect(record.status).toBe("running"));
|
|
207
214
|
|
|
208
215
|
// 第 2 轮:正常 child,成功写入(清零 EPIPE 计数)
|
|
209
216
|
spawnedChildren.set(record.id, makeNormalChild());
|
|
210
|
-
await service.
|
|
217
|
+
await service.chatActions.deliverChatMessage(record, "normal msg", true);
|
|
211
218
|
// 不 throw,status=running(热路径成功)
|
|
212
219
|
expect(record.status).toBe("running");
|
|
213
220
|
|
|
@@ -215,7 +222,7 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
215
222
|
// 需要先让 record 回 running(等待续聊态)
|
|
216
223
|
record.status = "running";
|
|
217
224
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
218
|
-
await expect(service.
|
|
225
|
+
await expect(service.chatActions.deliverChatMessage(record, "after reset", false)).resolves.toBeUndefined();
|
|
219
226
|
});
|
|
220
227
|
|
|
221
228
|
it("EPIPE 兜底与冷路径共用 in-flight 守卫:resume 在途时 EPIPE 兜底不再二次 spawn(review round2 MF1)", async () => {
|
|
@@ -224,14 +231,14 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
224
231
|
mockRunSpawn.mockImplementationOnce(
|
|
225
232
|
() => new Promise<AgentResult>((res) => { releaseFirst = res; }),
|
|
226
233
|
);
|
|
227
|
-
await service.
|
|
234
|
+
await service.chatActions.deliverChatMessage(record, "cold path first", false);
|
|
228
235
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
229
236
|
|
|
230
|
-
// 注册 EPIPE child →
|
|
237
|
+
// 注册 EPIPE child → 投递走热路径 → stdin EPIPE → 兜底调冷路径续轮
|
|
231
238
|
// → in-flight 命中 → throw(不再走第二次 spawn。EPIPE 计数仍 +1 但未达阈值 2,
|
|
232
239
|
// exhaustion 分支不触发;throw 由兜底路径的 resumeRound 守卫抛出)
|
|
233
240
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
234
|
-
await expect(service.
|
|
241
|
+
await expect(service.chatActions.deliverChatMessage(record, "epipe during resume", false)).rejects.toThrow(
|
|
235
242
|
/already starting a new round/,
|
|
236
243
|
);
|
|
237
244
|
expect(mockRunSpawn).toHaveBeenCalledTimes(1);
|
|
@@ -8,101 +8,31 @@
|
|
|
8
8
|
// ── mock 策略 ──
|
|
9
9
|
//
|
|
10
10
|
// 复用 execute-nesting.test.ts 的 spawn / node:fs / manifest-store / temp-prompt / alive-store
|
|
11
|
-
// / finalized-marker mock
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// / finalized-marker mock 范式,已收敛到 ./helpers/subagent-service-mocks.ts 单源(四文件
|
|
12
|
+
// 共享)。本文件 **不驱动 FakeChild 完成**——被测的两个分支都在 runSpawn 之前抛/收尾
|
|
13
|
+
// (worktree create 在步骤 2.5,runSpawn 在步骤 5),因此 spawn 即使被调也无人驱动,
|
|
14
|
+
// 测试在抛错后立即断言即可结束。
|
|
14
15
|
//
|
|
15
16
|
// worktreeManager 是 SubagentService 构造时 new 出的私有字段(WorktreeManager 实例,非模块)。
|
|
16
17
|
// 测试 2 用 vi.spyOn(Reflect.get(service, "worktreeManager"), "create") 注入抛错,无需模块级 mock。
|
|
17
18
|
|
|
18
|
-
import { PassThrough } from "node:stream";
|
|
19
|
-
|
|
20
19
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
spawn: vi.fn(() => new FakeChild()),
|
|
43
|
-
// buildEnvBlock 的 git branch 调用(execFile 异步):默认 err-first 兜底 → catch → branch=""
|
|
44
|
-
execFile: vi.fn(
|
|
45
|
-
(
|
|
46
|
-
_cmd: string,
|
|
47
|
-
_args: readonly string[],
|
|
48
|
-
_opts: unknown,
|
|
49
|
-
cb: (err: Error | null, stdout?: string, stderr?: string) => void,
|
|
50
|
-
) => cb(new Error("execFile not configured in this test")),
|
|
51
|
-
),
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
vi.mock("node:fs", async () => {
|
|
56
|
-
const actual = await import("node:fs");
|
|
57
|
-
return {
|
|
58
|
-
default: {
|
|
59
|
-
...actual,
|
|
60
|
-
mkdirSync: vi.fn(),
|
|
61
|
-
existsSync: vi.fn(() => false),
|
|
62
|
-
appendFileSync: vi.fn(),
|
|
63
|
-
writeFileSync: vi.fn(),
|
|
64
|
-
readdirSync: vi.fn(() => []),
|
|
65
|
-
},
|
|
66
|
-
mkdirSync: vi.fn(),
|
|
67
|
-
existsSync: vi.fn(() => false),
|
|
68
|
-
appendFileSync: vi.fn(),
|
|
69
|
-
writeFileSync: vi.fn(),
|
|
70
|
-
readdirSync: vi.fn(() => []),
|
|
71
|
-
promises: actual.promises,
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
vi.mock("../alive-store.ts", async (importOriginal) => {
|
|
76
|
-
const actual = await importOriginal<typeof import("../alive-store.ts")>();
|
|
77
|
-
return {
|
|
78
|
-
...actual,
|
|
79
|
-
writeAliveMarker: vi.fn(),
|
|
80
|
-
removeAliveMarker: vi.fn(),
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
vi.mock("../finalized-marker.ts", () => ({
|
|
85
|
-
writeFinalized: vi.fn(),
|
|
86
|
-
readFinalized: vi.fn(() => false),
|
|
87
|
-
}));
|
|
88
|
-
|
|
89
|
-
vi.mock("../manifest-store.ts", () => {
|
|
90
|
-
class FakeManifestStore {
|
|
91
|
-
writeManifest = vi.fn(async () => {});
|
|
92
|
-
readManifest = vi.fn(async () => null);
|
|
93
|
-
listAllSync = vi.fn(() => []);
|
|
94
|
-
recoverTmpFiles = vi.fn(async () => []);
|
|
95
|
-
}
|
|
96
|
-
return { ManifestStore: FakeManifestStore };
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
100
|
-
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
101
|
-
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
102
|
-
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
103
|
-
}),
|
|
104
|
-
cleanupTempPrompt: vi.fn(async () => {}),
|
|
105
|
-
}));
|
|
21
|
+
import {
|
|
22
|
+
aliveStoreModule,
|
|
23
|
+
childProcessModule,
|
|
24
|
+
finalizedMarkerModule,
|
|
25
|
+
fsSyncModule,
|
|
26
|
+
manifestStoreModule,
|
|
27
|
+
tempPromptModule,
|
|
28
|
+
} from "./helpers/subagent-service-mocks.ts";
|
|
29
|
+
|
|
30
|
+
vi.mock("node:child_process", () => childProcessModule());
|
|
31
|
+
vi.mock("node:fs", async (importOriginal) => fsSyncModule(await importOriginal<typeof import("node:fs")>()));
|
|
32
|
+
vi.mock("../alive-store.ts", async (importOriginal) => aliveStoreModule(await importOriginal<typeof import("../alive-store.ts")>()));
|
|
33
|
+
vi.mock("../finalized-marker.ts", () => finalizedMarkerModule());
|
|
34
|
+
vi.mock("../manifest-store.ts", () => manifestStoreModule());
|
|
35
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => tempPromptModule());
|
|
106
36
|
|
|
107
37
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
108
38
|
import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
|
|
@@ -127,7 +57,7 @@ interface SetupResult {
|
|
|
127
57
|
|
|
128
58
|
function setup(): SetupResult {
|
|
129
59
|
const agentDir = "/tmp/exec-await-worktree-it"; // fs 已 mock,路径不需真实存在
|
|
130
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
60
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
131
61
|
modelService.initModel({
|
|
132
62
|
modelRegistry: makeEmptyRegistry(),
|
|
133
63
|
sessionId: "exec-await-worktree-it",
|
|
@@ -219,7 +149,7 @@ describe("executeAndAwait worktree 失败收尾", () => {
|
|
|
219
149
|
}) as Parameters<WorktreeManager["cleanup"]>[0];
|
|
220
150
|
let resolveCreate!: (h: unknown) => void;
|
|
221
151
|
vi.spyOn(worktreeManager, "create").mockImplementation(
|
|
222
|
-
() => new Promise((r) => { resolveCreate = r; }) as ReturnType<WorktreeManager["create"]>,
|
|
152
|
+
() => new Promise<unknown>((r) => { resolveCreate = r; }) as ReturnType<WorktreeManager["create"]>,
|
|
223
153
|
);
|
|
224
154
|
const cleanupSpy = vi.spyOn(worktreeManager, "cleanup").mockResolvedValue(undefined);
|
|
225
155
|
|
|
@@ -15,134 +15,36 @@
|
|
|
15
15
|
// 因此本文件 mock 的是 node:child_process.spawn(返回 FakeChild),而非 getSdk/fakeSession
|
|
16
16
|
// (那是对 in-process run() 的旧 mock,在 spawn 改造后是死代码)。
|
|
17
17
|
//
|
|
18
|
-
// mock
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// - node:child_process.execFile → err-first callback 默认兜底(buildEnvBlock 的 git
|
|
22
|
-
// branch 调用失败 → catch → branch="",避免副作用)。
|
|
23
|
-
// - node:fs 同步方法 → mock(mkdirSync/existsSync/appendFileSync/writeFileSync/readdirSync),
|
|
24
|
-
// 避免 sessionDir/sessionFile 触碰真实文件系统。
|
|
25
|
-
// - fs.promises.* → 保留真实实现(temp-prompt 整体被 mock,不触发真实 I/O)。
|
|
26
|
-
// - temp-prompt → mock(writePromptToTempFile 返回固定路径,消除 fake-timers flaky)。
|
|
27
|
-
// - alive-store.writeAliveMarker → mock(避免写 .alive sidecar)。
|
|
18
|
+
// mock 模块工厂已收敛 ./helpers/subagent-service-mocks.ts(四文件共享单源,含
|
|
19
|
+
// spawn → FakeChild / fs 同步方法 / temp-prompt / alive-store / finalized-marker /
|
|
20
|
+
// manifest-store 的完整桩形与动机注释)。
|
|
28
21
|
//
|
|
29
22
|
// 所有断言语义不变:它们测的是 SubagentService 的 **编排逻辑**
|
|
30
23
|
// (pool.acquire / execCtxAls 深度),这些逻辑
|
|
31
24
|
// 无论事件来自 fakeSession.subscribe 还是 FakeChild.stdout 都一致。
|
|
32
25
|
|
|
33
|
-
import type { PassThrough } from "node:stream";
|
|
34
|
-
|
|
35
26
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
killed = false;
|
|
54
|
-
killSignal: string | undefined;
|
|
55
|
-
kill(sig?: string): boolean {
|
|
56
|
-
this.killed = true;
|
|
57
|
-
this.killSignal = sig;
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
spawn: vi.fn(() => new FakeChild()),
|
|
64
|
-
// buildEnvBlock 的 git branch 调用(execFile 异步):默认 err-first 兜底 → catch → branch=""
|
|
65
|
-
execFile: vi.fn(
|
|
66
|
-
(
|
|
67
|
-
_cmd: string,
|
|
68
|
-
_args: readonly string[],
|
|
69
|
-
_opts: unknown,
|
|
70
|
-
cb: (err: Error | null, stdout?: string, stderr?: string) => void,
|
|
71
|
-
) => cb(new Error("execFile not configured in this test")),
|
|
72
|
-
),
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// node:fs:同步方法 mock(runSpawn 用到的全部),promises 保留真实实现(temp-prompt 用)。
|
|
77
|
-
vi.mock("node:fs", async () => {
|
|
78
|
-
const actual = await import("node:fs");
|
|
79
|
-
return {
|
|
80
|
-
default: {
|
|
81
|
-
...actual,
|
|
82
|
-
mkdirSync: vi.fn(),
|
|
83
|
-
existsSync: vi.fn(() => false),
|
|
84
|
-
appendFileSync: vi.fn(),
|
|
85
|
-
writeFileSync: vi.fn(),
|
|
86
|
-
readdirSync: vi.fn(() => []),
|
|
87
|
-
},
|
|
88
|
-
// 具名导出与 default 保持一致
|
|
89
|
-
mkdirSync: vi.fn(),
|
|
90
|
-
existsSync: vi.fn(() => false),
|
|
91
|
-
appendFileSync: vi.fn(),
|
|
92
|
-
writeFileSync: vi.fn(),
|
|
93
|
-
readdirSync: vi.fn(() => []),
|
|
94
|
-
// promises 保留真实实现——temp-prompt 已被 mock(见下方 vi.mock),不再触发真实 I/O
|
|
95
|
-
promises: actual.promises,
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// alive-store:mock writeAliveMarker(runSpawn 写 .alive sidecar)+ removeAliveMarker
|
|
100
|
-
// (finalizeRecord 收尾删 .alive)。其余导出(readAliveMarker/isProcessAlive)保留真实实现
|
|
101
|
-
// (worktree-manager/record-store 用,本组用例不涉及但保留以避免间接报错)。
|
|
102
|
-
vi.mock("../alive-store.ts", async (importOriginal) => {
|
|
103
|
-
const actual = await importOriginal<typeof import("../alive-store.ts")>();
|
|
104
|
-
return {
|
|
105
|
-
...actual,
|
|
106
|
-
writeAliveMarker: vi.fn(),
|
|
107
|
-
removeAliveMarker: vi.fn(),
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// finalized-marker mock:避免真实 fs 写 sidecar(测试不关心 finalized 行为)
|
|
112
|
-
vi.mock("../finalized-marker.ts", () => ({
|
|
113
|
-
writeFinalized: vi.fn(),
|
|
114
|
-
readFinalized: vi.fn(() => false),
|
|
115
|
-
}));
|
|
116
|
-
|
|
117
|
-
// manifest-store mock:writeManifest 用真实 fs.promises 写盘,但本文件 fs 同步方法已 mock
|
|
118
|
-
// (目录从不真实创建)→ open/rename/unlink 全 ENOENT → bestEffort 异步 console.debug。
|
|
119
|
-
// 这些延迟 console 通过 worker RPC(onUserConsoleLog)回流,与 vitest teardown 形成 race:
|
|
120
|
-
// "Closing rpc while onUserConsoleLog was pending" → unhandled rejection / exit 1(flaky)。
|
|
121
|
-
// 本组用例测编排逻辑(pool / depth / throttle),不测 manifest 持久化(有 manifest-store.test.ts
|
|
122
|
-
// 独立覆盖)。故 mock 成 no-op,消除 teardown race 的根因——异步 console 输出。
|
|
123
|
-
vi.mock("../manifest-store.ts", () => {
|
|
124
|
-
class FakeManifestStore {
|
|
125
|
-
writeManifest = vi.fn(async () => {});
|
|
126
|
-
readManifest = vi.fn(async () => null);
|
|
127
|
-
listAllSync = vi.fn(() => []);
|
|
128
|
-
recoverTmpFiles = vi.fn(async () => []);
|
|
129
|
-
}
|
|
130
|
-
return { ManifestStore: FakeManifestStore };
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// temp-prompt:mock 掉真实 fs.promises I/O,消除 fake-timers 下的 flaky 竞态
|
|
134
|
-
// (详见 run-spawn-integration.test.ts 同名 mock 的注释)。
|
|
135
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
136
|
-
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
137
|
-
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
138
|
-
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
139
|
-
}),
|
|
140
|
-
cleanupTempPrompt: vi.fn(async () => {}),
|
|
141
|
-
}));
|
|
28
|
+
import {
|
|
29
|
+
aliveStoreModule,
|
|
30
|
+
childProcessModule,
|
|
31
|
+
driveChildToCompletion,
|
|
32
|
+
finalizedMarkerModule,
|
|
33
|
+
fsSyncModule,
|
|
34
|
+
manifestStoreModule,
|
|
35
|
+
tempPromptModule,
|
|
36
|
+
} from "./helpers/subagent-service-mocks.ts";
|
|
37
|
+
|
|
38
|
+
vi.mock("node:child_process", () => childProcessModule());
|
|
39
|
+
vi.mock("node:fs", async (importOriginal) => fsSyncModule(await importOriginal<typeof import("node:fs")>()));
|
|
40
|
+
vi.mock("../alive-store.ts", async (importOriginal) => aliveStoreModule(await importOriginal<typeof import("../alive-store.ts")>()));
|
|
41
|
+
vi.mock("../finalized-marker.ts", () => finalizedMarkerModule());
|
|
42
|
+
vi.mock("../manifest-store.ts", () => manifestStoreModule());
|
|
43
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => tempPromptModule());
|
|
142
44
|
|
|
143
45
|
import { spawn } from "node:child_process";
|
|
144
46
|
|
|
145
|
-
import { waitForSpawn } from "./helpers/spawn-mock.ts";
|
|
47
|
+
import { waitForSpawn, lastSpawnedChild } from "./helpers/spawn-mock.ts";
|
|
146
48
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
147
49
|
import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
|
|
148
50
|
import { MAX_FORK_DEPTH } from "../session-context-resolver.ts";
|
|
@@ -150,63 +52,6 @@ import { SubagentService } from "../subagent-service.ts";
|
|
|
150
52
|
|
|
151
53
|
const mockSpawn = vi.mocked(spawn);
|
|
152
54
|
|
|
153
|
-
/**
|
|
154
|
-
* spawn mock 返回的 fake child 类型。
|
|
155
|
-
* 由于 FakeChild 定义在 vi.mock 工厂内部(作用域隔离),此处用结构子集类型描述,
|
|
156
|
-
* 测试代码通过此类型访问 stdout/stderr/kill 等成员。
|
|
157
|
-
*/
|
|
158
|
-
interface FakeChild {
|
|
159
|
-
pid: number;
|
|
160
|
-
stdout: PassThrough;
|
|
161
|
-
stderr: PassThrough;
|
|
162
|
-
killed: boolean;
|
|
163
|
-
killSignal: string | undefined;
|
|
164
|
-
kill(sig?: string): boolean;
|
|
165
|
-
emit(event: string, ...args: unknown[]): boolean;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** 从最近一次 spawn 调用取回返回的 FakeChild(测试控制器)。 */
|
|
169
|
-
function lastSpawnedChild(): FakeChild {
|
|
170
|
-
const result = mockSpawn.mock.results.at(-1);
|
|
171
|
-
if (!result) throw new Error("spawn was not called yet");
|
|
172
|
-
return result.value as FakeChild;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ============================================================
|
|
176
|
-
// 辅助:FakeChild stdout 驱动(替代旧 fakeSession.subscribe 的事件注入)
|
|
177
|
-
// ============================================================
|
|
178
|
-
|
|
179
|
-
/** 构造 session header 行(stdout 首行,runSpawn 据此回填 record.sessionFile)。 */
|
|
180
|
-
function sessionHeader(id = "nest-session"): Record<string, unknown> {
|
|
181
|
-
return {
|
|
182
|
-
type: "session",
|
|
183
|
-
id,
|
|
184
|
-
timestamp: "2026-07-03T12-00-00-000Z",
|
|
185
|
-
cwd: "/tmp/test",
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** 向 stdout 写一行 JSON(自动补换行,runSpawn 按 \n split 行)。 */
|
|
190
|
-
function emitStdoutLine(child: FakeChild, obj: Record<string, unknown>): void {
|
|
191
|
-
child.stdout.write(`${JSON.stringify(obj)}\n`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* 驱动 FakeChild 完成 session:写 header + 可选事件 + close(0)。
|
|
196
|
-
*
|
|
197
|
-
* 这是「让 runSpawn 自然 resolve」的标准收尾路径。runSpawn 在 close 后判定 success
|
|
198
|
-
* (exitCode=0 → success=true),并跑 identity 补写 + finalizeRecord。
|
|
199
|
-
*
|
|
200
|
-
* @param events header 之后、close 之前 emit 的 SdkEvent 行(tool/message/turn 等)
|
|
201
|
-
*/
|
|
202
|
-
async function driveChildToCompletion(child: FakeChild, events: Record<string, unknown>[] = []): Promise<void> {
|
|
203
|
-
emitStdoutLine(child, sessionHeader());
|
|
204
|
-
for (const e of events) emitStdoutLine(child, e);
|
|
205
|
-
child.stdout.end();
|
|
206
|
-
child.stderr.end();
|
|
207
|
-
child.emit("close", 0);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
55
|
// ============================================================
|
|
211
56
|
// 辅助:service 构造(与旧 setup 等价,但不再装配 fakeSdk)
|
|
212
57
|
// ============================================================
|
|
@@ -225,7 +70,7 @@ interface SetupResult {
|
|
|
225
70
|
|
|
226
71
|
function setup(): SetupResult {
|
|
227
72
|
const agentDir = "/tmp/nest-it"; // fs 已 mock,路径不需真实存在
|
|
228
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
73
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
229
74
|
modelService.initModel({
|
|
230
75
|
modelRegistry: makeEmptyRegistry(),
|
|
231
76
|
sessionId: "nest-it",
|
|
@@ -242,7 +87,8 @@ function setup(): SetupResult {
|
|
|
242
87
|
|
|
243
88
|
const ctxModel: ModelInfo = { id: "m", name: "M", provider: "p", reasoning: false };
|
|
244
89
|
|
|
245
|
-
/**
|
|
90
|
+
/** [D3-⑤] execNesting(公共层 ExecutionNestingContext).run 的 duck-type(绕过
|
|
91
|
+
* import AsyncLocalStorage,足够本组用例——私字段经 Reflect 取,机制与旧 execCtxAls 同构)。 */
|
|
246
92
|
interface ExecCtxAls {
|
|
247
93
|
run: <T>(store: { recordId: string | undefined; depth: number }, cb: () => T) => T;
|
|
248
94
|
}
|
|
@@ -262,12 +108,12 @@ describe("嵌套护栏 / 并发池 / 节流(D-030~D-033 回归锁)", () => {
|
|
|
262
108
|
const pool = Reflect.get(service, "pool") as { acquire: ReturnType<typeof vi.fn>; release: ReturnType<typeof vi.fn> };
|
|
263
109
|
const acquireSpy = vi.spyOn(pool, "acquire");
|
|
264
110
|
|
|
265
|
-
const execPromise = service.execute({ task: "bg in pool", ctxModel });
|
|
111
|
+
const execPromise = service.execute({ task: "bg in pool", slug: "test", ctxModel });
|
|
266
112
|
// detached runAndFinalize → acquire。等 spawn 拿到 child 再驱动完成。
|
|
267
113
|
await waitForSpawn(mockSpawn);
|
|
268
|
-
await driveChildToCompletion(lastSpawnedChild());
|
|
114
|
+
await driveChildToCompletion(lastSpawnedChild(mockSpawn));
|
|
269
115
|
|
|
270
|
-
// 等 detached promise 链跑完(
|
|
116
|
+
// 等 detached promise 链跑完(kickOffChatRound 的 .then notify)
|
|
271
117
|
await new Promise<void>((r) => setTimeout(r, 10));
|
|
272
118
|
|
|
273
119
|
expect(acquireSpy).toHaveBeenCalled();
|
|
@@ -283,16 +129,16 @@ describe("嵌套护栏 / 并发池 / 节流(D-030~D-033 回归锁)", () => {
|
|
|
283
129
|
it("[D-033] execCtxAls depth=MAX 时 execute 抛错(nestingDepth=MAX+1 被拒)", async () => {
|
|
284
130
|
const { service } = setup();
|
|
285
131
|
|
|
286
|
-
const
|
|
132
|
+
const execNesting = Reflect.get(service, "execNesting") as ExecCtxAls;
|
|
287
133
|
|
|
288
134
|
await expect(
|
|
289
|
-
|
|
290
|
-
service.execute({ task: "too deep", ctxModel }),
|
|
135
|
+
execNesting.run({ recordId: "parent", depth: MAX_FORK_DEPTH }, () =>
|
|
136
|
+
service.execute({ task: "too deep", slug: "test", ctxModel }),
|
|
291
137
|
),
|
|
292
138
|
).rejects.toThrow(/nesting depth/);
|
|
293
139
|
|
|
294
140
|
// 无副作用:guard 在 createRecordForMode 之前,record 未创建
|
|
295
|
-
expect(service.collectRecords(10)).toHaveLength(0);
|
|
141
|
+
expect(service.queries.collectRecords(10)).toHaveLength(0);
|
|
296
142
|
// guard 在 spawn 之前——不应 spawn 任何子进程
|
|
297
143
|
expect(mockSpawn).not.toHaveBeenCalled();
|
|
298
144
|
});
|
|
@@ -300,13 +146,13 @@ describe("嵌套护栏 / 并发池 / 节流(D-030~D-033 回归锁)", () => {
|
|
|
300
146
|
it("[D-033] execCtxAls depth=MAX-1 时 execute 不抛(nestingDepth=MAX 允许)", async () => {
|
|
301
147
|
const { service } = setup();
|
|
302
148
|
|
|
303
|
-
const
|
|
149
|
+
const execNesting = Reflect.get(service, "execNesting") as ExecCtxAls;
|
|
304
150
|
|
|
305
|
-
const execPromise =
|
|
306
|
-
service.execute({ task: "at limit", ctxModel }),
|
|
151
|
+
const execPromise = execNesting.run({ recordId: "parent", depth: MAX_FORK_DEPTH - 1 }, () =>
|
|
152
|
+
service.execute({ task: "at limit", slug: "test", ctxModel }),
|
|
307
153
|
);
|
|
308
154
|
await waitForSpawn(mockSpawn);
|
|
309
|
-
await driveChildToCompletion(lastSpawnedChild(), [
|
|
155
|
+
await driveChildToCompletion(lastSpawnedChild(mockSpawn), [
|
|
310
156
|
{ type: "turn_end" },
|
|
311
157
|
{ type: "message_end", message: { usage: { input: 1 } } },
|
|
312
158
|
]);
|