@zhushanwen/subagent-core 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
- package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
- package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
- package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
- package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
- package/dist/execution/engine/engines/zcode/constants.js +17 -27
- 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 +2 -2
- package/dist/index.cjs +17100 -14082
- package/dist/index.d.cts +5336 -4033
- package/dist/index.d.ts +5336 -4033
- package/dist/index.js +17020 -14055
- package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
- package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
- package/dist.bundle/index.cjs +34933 -0
- package/package.json +3 -3
- 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 +4 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +10 -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/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 +4 -4
- 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 +2 -2
- package/src/execution/__tests__/alive-store.test.ts +1 -1
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/collect-budget.test.ts +291 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
- package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
- package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
- package/src/execution/__tests__/config.test.ts +1 -1
- package/src/execution/__tests__/delivery-methods.test.ts +75 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
- package/src/execution/__tests__/dialog-queue.test.ts +2 -2
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +29 -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 +5 -5
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
- package/src/execution/__tests__/finalize-record.test.ts +173 -17
- package/src/execution/__tests__/finalized-marker.test.ts +1 -1
- package/src/execution/__tests__/gc-timer.test.ts +56 -3
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
- 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 +50 -148
- package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
- 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 +1 -1
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
- 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-batch.test.ts +500 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +36 -4
- package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
- package/src/execution/__tests__/record-store.test.ts +10 -9
- 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__/relay-env.test.ts +12 -1
- 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 +15 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
- package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
- 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 +4 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +36 -1
- package/src/execution/__tests__/session-pending.test.ts +257 -14
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
- package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
- 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 +282 -47
- 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 +3 -3
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +2 -1
- package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
- 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 +48 -22
- package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
- 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__/sync-collect-recovery.test.ts +1117 -0
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +6 -5
- package/src/execution/__tests__/tombstone-store.test.ts +1 -1
- 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 +12 -6
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
- 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 +2 -2
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
- package/src/execution/__tests__/worktree-registry.test.ts +1 -1
- package/src/execution/agent-registry.ts +74 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/collect-coordinator.ts +200 -0
- package/src/execution/config.ts +77 -5
- 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/event-journal.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 +213 -53
- 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 +22 -112
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
- 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 +4 -2
- 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/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} +1 -1
- 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/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
- 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 +54 -27
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
- 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 +20 -8
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
- 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 +48 -148
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +24 -11
- package/src/execution/engine/engines/zcode/constants.ts +75 -71
- package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +12 -143
- package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
- package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
- 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 +24 -6
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +229 -144
- package/src/execution/finalize-record.ts +141 -99
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/notifier.ts +234 -25
- package/src/execution/notify-host.ts +232 -0
- package/src/execution/record-entry.ts +16 -0
- package/src/execution/record-store.ts +280 -103
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +117 -47
- package/src/execution/settled-watchdog.ts +264 -52
- package/src/execution/subagent-actions-core.ts +84 -28
- package/src/execution/subagent-service.ts +1225 -964
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/sync-rebuild.ts +87 -0
- package/src/execution/types.ts +72 -0
- package/src/execution/ui-request-handler-factory.ts +102 -74
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/index.ts +442 -316
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/config-loader.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
- package/src/orchestration/__tests__/script-generate.test.ts +5 -5
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
- 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} +27 -19
- 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 +1 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +1 -1
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +19 -73
- package/src/orchestration/launcher.ts +128 -81
- package/src/orchestration/lifecycle.ts +43 -45
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +2 -2
- package/src/orchestration/models/trace.ts +1 -1
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +1 -1
- package/src/orchestration/script-lint.ts +91 -58
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
- package/src/shared/__tests__/atomic-write.test.ts +1 -1
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
- package/src/shared/__tests__/resource-discovery.test.ts +258 -215
- package/src/shared/atomic-write.ts +12 -9
- package/src/shared/meta-parser.ts +158 -84
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +9 -183
- package/src/shared/schema-jsonify.ts +1 -1
- package/workflows/review-fix-loop-utils.cjs +151 -89
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- 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__/preparer-appserver.test.ts +0 -387
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
- package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
- package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- 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,7 +23,7 @@ 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(),
|
|
@@ -34,7 +35,7 @@ vi.mock("../session-runner.ts", () => {
|
|
|
34
35
|
};
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
import { runSpawn, spawnedChildren } from "../session-runner.ts";
|
|
38
|
+
import { runSpawn, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
|
|
38
39
|
import * as lifecycle from "../lifecycle-manager.ts";
|
|
39
40
|
import { createRecord } from "../execution-record.ts";
|
|
40
41
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
@@ -50,11 +51,15 @@ function makeTmpAgentDir(): string {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
function makePi(): PiLike & {
|
|
53
|
-
appendEntry: ReturnType<typeof vi.fn
|
|
54
|
-
events: { emit: ReturnType<typeof vi.fn
|
|
55
|
-
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>>;
|
|
56
57
|
} {
|
|
57
|
-
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
|
+
};
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
function makeResult(success: boolean): AgentResult {
|
|
@@ -110,17 +115,17 @@ function makeNormalChild(): ChildProcess {
|
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
// ============================================================
|
|
113
|
-
//
|
|
118
|
+
// deliverChatMessage EPIPE 兜底
|
|
114
119
|
// ============================================================
|
|
115
120
|
|
|
116
|
-
describe("
|
|
121
|
+
describe("deliverChatMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径续轮)", () => {
|
|
117
122
|
let agentDir: string;
|
|
118
123
|
let service: SubagentService;
|
|
119
124
|
let record: ExecutionRecord;
|
|
120
125
|
|
|
121
126
|
beforeEach(() => {
|
|
122
127
|
agentDir = makeTmpAgentDir();
|
|
123
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
128
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
124
129
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
125
130
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
126
131
|
record = makeIdleRecord();
|
|
@@ -135,7 +140,7 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
135
140
|
service.dispose();
|
|
136
141
|
spawnedChildren.clear();
|
|
137
142
|
lifecycle._resetLifecycleState();
|
|
138
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
143
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
139
144
|
});
|
|
140
145
|
|
|
141
146
|
it("首次 EPIPE → 自动转冷路径 resumeRound + 原消息重放", async () => {
|
|
@@ -143,13 +148,13 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
143
148
|
// 注册 EPIPE child(热路径会命中但 stdin.write 抛 EPIPE)
|
|
144
149
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
145
150
|
|
|
146
|
-
//
|
|
147
|
-
await expect(service.
|
|
151
|
+
// 投递不 throw(首次 EPIPE 走续轮兜底)
|
|
152
|
+
await expect(service.chatActions.deliverChatMessage(record, "msg after epipe", false)).resolves.toBeUndefined();
|
|
148
153
|
|
|
149
154
|
// spawnedChildren 中的死进程条目已清理(EPIPE catch 块 delete)
|
|
150
155
|
expect(spawnedChildren.has(record.id)).toBe(false);
|
|
151
156
|
|
|
152
|
-
//
|
|
157
|
+
// 冷路径续轮被触发,runSpawn 收到 resume 参数 + 原消息
|
|
153
158
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
154
159
|
const call = mockRunSpawn.mock.calls[0]!;
|
|
155
160
|
expect(call[1]).toBe("msg after epipe");
|
|
@@ -174,7 +179,7 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
174
179
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
175
180
|
|
|
176
181
|
// 第 1 次:EPIPE → resume(不 throw)
|
|
177
|
-
await service.
|
|
182
|
+
await service.chatActions.deliverChatMessage(record, "first epipe", false);
|
|
178
183
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
179
184
|
|
|
180
185
|
// resume 完成后 record 回 running(v4 B-1:finalizeRoundToIdle 设 running,旧 idle 折入)
|
|
@@ -184,10 +189,10 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
184
189
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
185
190
|
|
|
186
191
|
// 第 2 次 EPIPE → throw 含恢复指引(连续 2 次触发 exhaustion)
|
|
187
|
-
// 注意:第二 EPIPE 时 count 已为 2,catch 块立即 throw
|
|
192
|
+
// 注意:第二 EPIPE 时 count 已为 2,catch 块立即 throw(不经过续轮)。
|
|
188
193
|
let thrown: unknown;
|
|
189
194
|
try {
|
|
190
|
-
await service.
|
|
195
|
+
await service.chatActions.deliverChatMessage(record, "second epipe", false);
|
|
191
196
|
} catch (err) {
|
|
192
197
|
thrown = err;
|
|
193
198
|
}
|
|
@@ -203,13 +208,13 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
203
208
|
|
|
204
209
|
// 第 1 轮:EPIPE → resume
|
|
205
210
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
206
|
-
await service.
|
|
211
|
+
await service.chatActions.deliverChatMessage(record, "epipe msg", false);
|
|
207
212
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
208
213
|
await vi.waitFor(() => expect(record.status).toBe("running"));
|
|
209
214
|
|
|
210
215
|
// 第 2 轮:正常 child,成功写入(清零 EPIPE 计数)
|
|
211
216
|
spawnedChildren.set(record.id, makeNormalChild());
|
|
212
|
-
await service.
|
|
217
|
+
await service.chatActions.deliverChatMessage(record, "normal msg", true);
|
|
213
218
|
// 不 throw,status=running(热路径成功)
|
|
214
219
|
expect(record.status).toBe("running");
|
|
215
220
|
|
|
@@ -217,7 +222,7 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
217
222
|
// 需要先让 record 回 running(等待续聊态)
|
|
218
223
|
record.status = "running";
|
|
219
224
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
220
|
-
await expect(service.
|
|
225
|
+
await expect(service.chatActions.deliverChatMessage(record, "after reset", false)).resolves.toBeUndefined();
|
|
221
226
|
});
|
|
222
227
|
|
|
223
228
|
it("EPIPE 兜底与冷路径共用 in-flight 守卫:resume 在途时 EPIPE 兜底不再二次 spawn(review round2 MF1)", async () => {
|
|
@@ -226,14 +231,14 @@ describe("deliverMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径 resu
|
|
|
226
231
|
mockRunSpawn.mockImplementationOnce(
|
|
227
232
|
() => new Promise<AgentResult>((res) => { releaseFirst = res; }),
|
|
228
233
|
);
|
|
229
|
-
await service.
|
|
234
|
+
await service.chatActions.deliverChatMessage(record, "cold path first", false);
|
|
230
235
|
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
231
236
|
|
|
232
|
-
// 注册 EPIPE child →
|
|
237
|
+
// 注册 EPIPE child → 投递走热路径 → stdin EPIPE → 兜底调冷路径续轮
|
|
233
238
|
// → in-flight 命中 → throw(不再走第二次 spawn。EPIPE 计数仍 +1 但未达阈值 2,
|
|
234
239
|
// exhaustion 分支不触发;throw 由兜底路径的 resumeRound 守卫抛出)
|
|
235
240
|
spawnedChildren.set(record.id, makeEpipeChild());
|
|
236
|
-
await expect(service.
|
|
241
|
+
await expect(service.chatActions.deliverChatMessage(record, "epipe during resume", false)).rejects.toThrow(
|
|
237
242
|
/already starting a new round/,
|
|
238
243
|
);
|
|
239
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
|
]);
|