@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
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// src/execution/__tests__/run-and-finalize-anchoring.test.ts
|
|
2
|
+
//
|
|
3
|
+
// runAndFinalize 特征锚定基线(第二批复杂度债务 U04:锚定测试先行)。
|
|
4
|
+
//
|
|
5
|
+
// 既有 run-and-finalize-chatmode.test.ts 覆盖 settle 阶段的 chatMode × closeAfterRound
|
|
6
|
+
// 分流;本文件补直接分支缺口,重构前后行为逐字节对照(错误文案 / 终态写盘 / 事件顺序):
|
|
7
|
+
// G1 pooled acquire 成功 → acquire → runSpawn → release 调用次序 + 轮次回退事件(running)
|
|
8
|
+
// G2 排队 acquire 被中止(signal.aborted)→ finalizeAborted("cancelled by user")
|
|
9
|
+
// G3 acquire 非中止失败 → finalizeFailed 合成 "aborted" 文案(原始错误被吞——现状怪癖锚定)
|
|
10
|
+
// G4 非 chatMode + runSpawn 抛错 → finalizeFailed:closed/gc + archive + manifest 落盘
|
|
11
|
+
// G5 chatMode + runSpawn 抛错 → MF-6 回退:record.result="round did not complete: <msg>"
|
|
12
|
+
// G6 chatMode + 抛错 + CAS 输(record 已 closed)→ 不回退、不 archive、原样返回合成 result
|
|
13
|
+
// G7 chatMode + idle timer armed → early return:settle 不执行(round/closeAfterRound 不动)
|
|
14
|
+
// G8 settle 阶段 CAS 输(record 已 closed)→ 跳过收尾、closeAfterRound 不消费
|
|
15
|
+
// G9 非 chatMode + 失败 result → else 分支:closed/gc + archive + manifest 落盘 + endedAt
|
|
16
|
+
// G10 运行中 abort → closedReason="cancelled" 终态化,result 原样返回(不改写为 cancelled 形态)
|
|
17
|
+
//
|
|
18
|
+
// harness 与 run-and-finalize-chatmode.test.ts 同形态:mock session-runner.runSpawn,
|
|
19
|
+
// 走 SubagentService 真实 runAndFinalize 分流逻辑,断言「最终可观察状态」。
|
|
20
|
+
|
|
21
|
+
import * as fs from "node:fs";
|
|
22
|
+
import * as os from "node:os";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
|
|
25
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
26
|
+
|
|
27
|
+
// mock logger(doFinalizeRecord 的 manifest 写入降级路径用 logger.error)
|
|
28
|
+
const { loggerMock } = vi.hoisted(() => ({
|
|
29
|
+
loggerMock: { debug: vi.fn(), error: vi.fn(), warn: vi.fn() },
|
|
30
|
+
}));
|
|
31
|
+
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
32
|
+
|
|
33
|
+
// mock session-runner:runSpawn 受控返回 result / 抛错,killAllSpawnedChildren 空实现。
|
|
34
|
+
// 注意:必须在 import SubagentService 之前 mock(vi.mock 提升到顶部)。
|
|
35
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
36
|
+
runSpawn: vi.fn(),
|
|
37
|
+
killAllSpawnedChildren: vi.fn(),
|
|
38
|
+
getChildByRecord: vi.fn(() => undefined),
|
|
39
|
+
registerSpawnedChildForRecord: vi.fn(),
|
|
40
|
+
killRecordChildWithEscalation: vi.fn(),
|
|
41
|
+
spawnedChildren: new Map(),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
45
|
+
import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
|
|
46
|
+
import { armIdleTimer, disarmIdleTimer } from "../lifecycle-manager.ts";
|
|
47
|
+
import { createRecord } from "../execution-record.ts";
|
|
48
|
+
import { ModelConfigService } from "../model-config-service.ts";
|
|
49
|
+
import type { ModelInfo } from "../model-resolver.ts";
|
|
50
|
+
import { getSubagentRecordsDir } from "../path-encoding.ts";
|
|
51
|
+
import { RecordStore } from "../record-store.ts";
|
|
52
|
+
import type { ConcurrencyPool } from "../concurrency-pool.ts";
|
|
53
|
+
import { SubagentService } from "../subagent-service.ts";
|
|
54
|
+
import type { PiLike } from "../subagent-service.ts";
|
|
55
|
+
import type {
|
|
56
|
+
AgentResult,
|
|
57
|
+
ExecutionRecord,
|
|
58
|
+
ExecuteOptions,
|
|
59
|
+
} from "../types.ts";
|
|
60
|
+
|
|
61
|
+
const mockRunSpawn = vi.mocked(runSpawn);
|
|
62
|
+
|
|
63
|
+
// ── 最小合法 ModelInfo(resolveModel 未注入 registry 会抛错,故 stub;runSpawn 被 mock 不消费)──
|
|
64
|
+
const STUB_MODEL: ModelInfo = {
|
|
65
|
+
id: "test-model",
|
|
66
|
+
name: "Test",
|
|
67
|
+
provider: "test",
|
|
68
|
+
reasoning: false,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function makeTmpAgentDir(): string {
|
|
72
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "raf-anchor-test-"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function makePi(): PiLike & {
|
|
76
|
+
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
77
|
+
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
78
|
+
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
79
|
+
} {
|
|
80
|
+
return {
|
|
81
|
+
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
82
|
+
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
83
|
+
sendMessage: vi.fn(() => {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function makeResult(success: boolean): AgentResult {
|
|
88
|
+
return {
|
|
89
|
+
text: success ? "done" : "err",
|
|
90
|
+
turns: 1,
|
|
91
|
+
durationMs: 100,
|
|
92
|
+
success,
|
|
93
|
+
error: success ? undefined : "boom",
|
|
94
|
+
sessionId: "sess-1",
|
|
95
|
+
toolCalls: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function makeRecord(chatMode: boolean, id: string): ExecutionRecord {
|
|
100
|
+
return createRecord(id, {
|
|
101
|
+
agent: "general-purpose",
|
|
102
|
+
model: "test-model",
|
|
103
|
+
mode: "background",
|
|
104
|
+
task: "do something",
|
|
105
|
+
slug: "test",
|
|
106
|
+
startedAt: 1000,
|
|
107
|
+
rootSessionId: "root-session",
|
|
108
|
+
chatMode,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 暴露 runAndFinalize + store + pool 的私有访问接口(测试专用 cast)。 */
|
|
113
|
+
interface ServiceInternals {
|
|
114
|
+
store: RecordStore;
|
|
115
|
+
pool: ConcurrencyPool;
|
|
116
|
+
runAndFinalize: (
|
|
117
|
+
record: ExecutionRecord,
|
|
118
|
+
opts: ExecuteOptions,
|
|
119
|
+
ctx: SessionRunnerContext,
|
|
120
|
+
identity: {
|
|
121
|
+
agent: string;
|
|
122
|
+
agentConfig: unknown;
|
|
123
|
+
resolved: { model: ModelInfo; thinkingLevel: string | undefined };
|
|
124
|
+
},
|
|
125
|
+
signal: AbortSignal | undefined,
|
|
126
|
+
priority: number,
|
|
127
|
+
) => Promise<AgentResult>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
describe("runAndFinalize 特征锚定基线(U04 重构前后行为对照)", () => {
|
|
131
|
+
let agentDir: string;
|
|
132
|
+
let modelService: ModelConfigService;
|
|
133
|
+
let service: SubagentService;
|
|
134
|
+
let internals: ServiceInternals;
|
|
135
|
+
let pi: ReturnType<typeof makePi>;
|
|
136
|
+
let armedTimerRecordIds: string[];
|
|
137
|
+
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
// rootCwd 推导基线:PI_SUBAGENT_ROOT_CWD 若被宿主 env 污染会改写 recordsDir 编码段,
|
|
140
|
+
// manifest 落盘断言(G4/G9)依赖「rootCwd = init.cwd」的未设基线。
|
|
141
|
+
delete process.env.PI_SUBAGENT_ROOT_CWD;
|
|
142
|
+
agentDir = makeTmpAgentDir();
|
|
143
|
+
modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
144
|
+
service = new SubagentService({ cwd: agentDir, modelService });
|
|
145
|
+
pi = makePi();
|
|
146
|
+
service.initSession({ pi, sessionId: "root-session" });
|
|
147
|
+
internals = service as unknown as ServiceInternals;
|
|
148
|
+
armedTimerRecordIds = [];
|
|
149
|
+
mockRunSpawn.mockReset();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
for (const id of armedTimerRecordIds) disarmIdleTimer(id);
|
|
154
|
+
service.dispose();
|
|
155
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/** 直接调私有 runAndFinalize(mock runSpawn 后走完整分流逻辑)。 */
|
|
159
|
+
async function callRunAndFinalize(
|
|
160
|
+
record: ExecutionRecord,
|
|
161
|
+
mockImpl: () => void,
|
|
162
|
+
signal?: AbortSignal,
|
|
163
|
+
): Promise<AgentResult> {
|
|
164
|
+
mockImpl();
|
|
165
|
+
const opts: ExecuteOptions = { task: "do something", slug: "test" };
|
|
166
|
+
const ctx: SessionRunnerContext = {
|
|
167
|
+
cwd: agentDir,
|
|
168
|
+
agentDir,
|
|
169
|
+
skillDirs: [],
|
|
170
|
+
mainCwd: agentDir,
|
|
171
|
+
sessionRootId: "s-root",
|
|
172
|
+
rootCwd: agentDir,
|
|
173
|
+
};
|
|
174
|
+
const identity = {
|
|
175
|
+
agent: "general-purpose",
|
|
176
|
+
agentConfig: undefined,
|
|
177
|
+
resolved: { model: STUB_MODEL, thinkingLevel: undefined },
|
|
178
|
+
};
|
|
179
|
+
return internals.runAndFinalize(record, opts, ctx, identity, signal, 0);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function manifestPath(recordId: string): string {
|
|
183
|
+
return path.join(getSubagentRecordsDir(agentDir, agentDir), `${recordId}.json`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
it("G1: pooled acquire 成功 → acquire → runSpawn → release 次序锚定 + 轮次回退事件(running)", async () => {
|
|
187
|
+
const record = makeRecord(false, "sa-anchor-g1");
|
|
188
|
+
internals.store.register(record);
|
|
189
|
+
const pool = internals.pool;
|
|
190
|
+
const acquireSpy = vi.spyOn(pool, "acquire");
|
|
191
|
+
const releaseSpy = vi.spyOn(pool, "release");
|
|
192
|
+
|
|
193
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
|
|
194
|
+
|
|
195
|
+
expect(result.text).toBe("done");
|
|
196
|
+
// 事件顺序:acquire 先于 runSpawn,release(finally 收口)最后。
|
|
197
|
+
expect(acquireSpy).toHaveBeenCalledTimes(1);
|
|
198
|
+
expect(mockRunSpawn).toHaveBeenCalledTimes(1);
|
|
199
|
+
expect(releaseSpy).toHaveBeenCalledTimes(1);
|
|
200
|
+
const order = acquireSpy.mock.invocationCallOrder[0];
|
|
201
|
+
const spawnOrder = mockRunSpawn.mock.invocationCallOrder[0];
|
|
202
|
+
const releaseOrder = releaseSpy.mock.invocationCallOrder[0];
|
|
203
|
+
expect(order).toBeLessThan(spawnOrder);
|
|
204
|
+
expect(spawnOrder).toBeLessThan(releaseOrder);
|
|
205
|
+
// one-shot 成功无 closeAfterRound → finalizeRoundToIdle:留内存 + 进程死注销事件(running)。
|
|
206
|
+
expect(record.status).toBe("running");
|
|
207
|
+
expect(internals.store.getMutable(record.id)).toBe(record);
|
|
208
|
+
expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "running" });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("G2: 排队 acquire 被中止(signal.aborted)→ finalizeAborted:'cancelled by user' + closed/cancelled + archive", async () => {
|
|
212
|
+
const record = makeRecord(false, "sa-anchor-g2");
|
|
213
|
+
internals.store.register(record);
|
|
214
|
+
const releaseSpy = vi.spyOn(internals.pool, "release");
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
controller.abort(); // pre-aborted:signal.aborted = true
|
|
217
|
+
// 真实 ConcurrencyPool.acquire 对 pre-aborted signal 并不保证 reject——
|
|
218
|
+
// 显式 mock 排队中止(acquire reject + signal.aborted)以触发 catch 的 S1 分支。
|
|
219
|
+
vi.spyOn(internals.pool, "acquire").mockRejectedValueOnce(new Error("AbortError: queued acquire aborted"));
|
|
220
|
+
|
|
221
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)), controller.signal);
|
|
222
|
+
|
|
223
|
+
// 错误路径文案逐字节:S1 对齐已运行被 abort 的 cancel 语义。
|
|
224
|
+
expect(result.error).toBe("cancelled by user");
|
|
225
|
+
expect(result.success).toBe(false);
|
|
226
|
+
// 终态:closed + cancelled + archive(写盘收尾走完),runSpawn 未触达。
|
|
227
|
+
expect(record.status).toBe("closed");
|
|
228
|
+
expect(record.closedReason).toBe("cancelled");
|
|
229
|
+
expect(record.endedAt).toBeDefined();
|
|
230
|
+
expect(internals.store.getMutable(record.id)).toBeUndefined();
|
|
231
|
+
expect(mockRunSpawn).not.toHaveBeenCalled();
|
|
232
|
+
// acquire 失败早退(try/finally 之前)→ 池槽不归还(acquired=false 语义)。
|
|
233
|
+
expect(releaseSpy).not.toHaveBeenCalled();
|
|
234
|
+
expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("G3: acquire 非中止失败 → finalizeFailed 合成 'aborted' 文案(原始错误被吞——现状怪癖锚定)", async () => {
|
|
238
|
+
const record = makeRecord(false, "sa-anchor-g3");
|
|
239
|
+
internals.store.register(record);
|
|
240
|
+
const releaseSpy = vi.spyOn(internals.pool, "release");
|
|
241
|
+
vi.spyOn(internals.pool, "acquire").mockRejectedValueOnce(new Error("pool exploded"));
|
|
242
|
+
|
|
243
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
|
|
244
|
+
|
|
245
|
+
// 现状:catch 吞掉原始错误,统一合成 new Error("aborted") → toErrorMessage = "aborted"。
|
|
246
|
+
// "pool exploded" 不透传——重构必须保持该文案(行为逐字节不变)。
|
|
247
|
+
expect(result.error).toBe("aborted");
|
|
248
|
+
expect(result.success).toBe(false);
|
|
249
|
+
expect(record.status).toBe("closed");
|
|
250
|
+
expect(record.closedReason).toBe("gc");
|
|
251
|
+
expect(internals.store.getMutable(record.id)).toBeUndefined();
|
|
252
|
+
expect(mockRunSpawn).not.toHaveBeenCalled();
|
|
253
|
+
expect(releaseSpy).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("G4: 非 chatMode + runSpawn 抛错 → finalizeFailed:error 透传 + closed/gc + archive + manifest 落盘", async () => {
|
|
257
|
+
const record = makeRecord(false, "sa-anchor-g4");
|
|
258
|
+
internals.store.register(record);
|
|
259
|
+
|
|
260
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockRejectedValueOnce(new Error("spawn exploded")));
|
|
261
|
+
|
|
262
|
+
// 错误文案:toErrorMessage(Error) → .message 逐字节透传。
|
|
263
|
+
expect(result.error).toBe("spawn exploded");
|
|
264
|
+
expect(result.success).toBe(false);
|
|
265
|
+
// 终态写盘:closed + gc + archive + manifest json 落盘。
|
|
266
|
+
expect(record.status).toBe("closed");
|
|
267
|
+
expect(record.closedReason).toBe("gc");
|
|
268
|
+
expect(record.endedAt).toBeDefined();
|
|
269
|
+
expect(internals.store.getMutable(record.id)).toBeUndefined();
|
|
270
|
+
expect(fs.existsSync(manifestPath(record.id))).toBe(true);
|
|
271
|
+
expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("G5: chatMode + runSpawn 抛错 → MF-6 回退:'round did not complete: <msg>' + running + round+1 + 留内存", async () => {
|
|
275
|
+
const record = makeRecord(true, "sa-anchor-g5");
|
|
276
|
+
internals.store.register(record);
|
|
277
|
+
|
|
278
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockRejectedValueOnce(new Error("spawn creation failed")));
|
|
279
|
+
|
|
280
|
+
// 合成 failed result 错误文案透传(swallow,不 re-throw)。
|
|
281
|
+
expect(result.error).toBe("spawn creation failed");
|
|
282
|
+
// MF-6:不销毁对话——finalizeRoundToIdle 回退 running-resumable(留内存可重试 message/close)。
|
|
283
|
+
expect(record.status).toBe("running");
|
|
284
|
+
expect(record.closedReason).toBeUndefined(); // [S10] 不残留 gc
|
|
285
|
+
expect(record.round).toBe(1);
|
|
286
|
+
expect(internals.store.getMutable(record.id)).toBe(record);
|
|
287
|
+
// MF-2 写点:失败轮 error 优先 → record.result 固定前缀文案(notify 正文逐字节)。
|
|
288
|
+
expect(record.result).toBe("round did not complete: spawn creation failed");
|
|
289
|
+
// 事件:进程死注销 reason=running(非 closed——record 留内存非终态)。
|
|
290
|
+
expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "running" });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("G6: chatMode + 抛错 + CAS 输(record 已 closed)→ 不回退不 archive,原样返回合成 result", async () => {
|
|
294
|
+
const record = makeRecord(true, "sa-anchor-g6");
|
|
295
|
+
record.status = "closed"; // cancel/收尾抢先:tryTransition(closed, gc) 必败
|
|
296
|
+
internals.store.register(record);
|
|
297
|
+
|
|
298
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockRejectedValueOnce(new Error("spawn creation failed")));
|
|
299
|
+
|
|
300
|
+
expect(result.error).toBe("spawn creation failed");
|
|
301
|
+
// CAS 输:无任何收尾副作用——状态/轮次不动、留内存、无注销事件。
|
|
302
|
+
expect(record.status).toBe("closed");
|
|
303
|
+
expect(record.round).toBe(0);
|
|
304
|
+
expect(internals.store.getMutable(record.id)).toBe(record);
|
|
305
|
+
expect(pi.events.emit).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("G7: chatMode + idle timer armed → early return:settle 不执行(round/closeAfterRound 不动)", async () => {
|
|
309
|
+
const record = makeRecord(true, "sa-anchor-g7");
|
|
310
|
+
record.status = "running";
|
|
311
|
+
record.round = 1;
|
|
312
|
+
internals.store.register(record);
|
|
313
|
+
// V2 决策 2/3 形态:onRoundSettled 已 arm idle timer(agent_settled 提前闭环)。
|
|
314
|
+
armIdleTimer(record.id, () => {}, 60_000);
|
|
315
|
+
armedTimerRecordIds.push(record.id);
|
|
316
|
+
|
|
317
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
|
|
318
|
+
|
|
319
|
+
// early return:原样返回 runSpawn result,settle 阶段未执行。
|
|
320
|
+
expect(result.text).toBe("done");
|
|
321
|
+
expect(record.status).toBe("running");
|
|
322
|
+
expect(record.round).toBe(1); // 未 +1(round+1 归 onRoundSettled,非本函数)
|
|
323
|
+
expect(record.closeAfterRound).toBeUndefined();
|
|
324
|
+
expect(internals.store.getMutable(record.id)).toBe(record);
|
|
325
|
+
expect(pi.events.emit).not.toHaveBeenCalled();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("G8: settle 阶段 CAS 输(record 已 closed)→ 跳过收尾、closeAfterRound 不消费", async () => {
|
|
329
|
+
const record = makeRecord(false, "sa-anchor-g8");
|
|
330
|
+
record.status = "closed"; // cancel 抢先(A2-1 形态)
|
|
331
|
+
record.closeAfterRound = true;
|
|
332
|
+
internals.store.register(record);
|
|
333
|
+
|
|
334
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(true)));
|
|
335
|
+
|
|
336
|
+
// 原样返回(不改写为 cancelled 形态);收尾全跳过。
|
|
337
|
+
expect(result.text).toBe("done");
|
|
338
|
+
expect(result.success).toBe(true);
|
|
339
|
+
expect(record.status).toBe("closed");
|
|
340
|
+
expect(record.closeAfterRound).toBe(true); // 标志不被本路径消费
|
|
341
|
+
expect(record.endedAt).toBeUndefined();
|
|
342
|
+
expect(internals.store.getMutable(record.id)).toBe(record);
|
|
343
|
+
expect(pi.events.emit).not.toHaveBeenCalled();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("G9: 非 chatMode + 失败 result → else 分支:closed/gc + archive + manifest 落盘 + endedAt", async () => {
|
|
347
|
+
const record = makeRecord(false, "sa-anchor-g9");
|
|
348
|
+
internals.store.register(record);
|
|
349
|
+
|
|
350
|
+
const result = await callRunAndFinalize(record, () => mockRunSpawn.mockResolvedValueOnce(makeResult(false)));
|
|
351
|
+
|
|
352
|
+
// 失败 result 原样返回(错误文案不经本函数改写)。
|
|
353
|
+
expect(result.success).toBe(false);
|
|
354
|
+
expect(result.error).toBe("boom");
|
|
355
|
+
// 非 chatMode 失败 = 一次性销毁(archive + manifest 落盘)。
|
|
356
|
+
expect(record.status).toBe("closed");
|
|
357
|
+
expect(record.closedReason).toBe("gc");
|
|
358
|
+
expect(record.endedAt).toBeDefined();
|
|
359
|
+
expect(internals.store.getMutable(record.id)).toBeUndefined();
|
|
360
|
+
expect(fs.existsSync(manifestPath(record.id))).toBe(true);
|
|
361
|
+
expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("G10: 运行中 abort → closedReason='cancelled' 终态化,result 原样返回(不改写)", async () => {
|
|
365
|
+
const record = makeRecord(false, "sa-anchor-g10");
|
|
366
|
+
internals.store.register(record);
|
|
367
|
+
const controller = new AbortController();
|
|
368
|
+
// acquire(signal 未中止 → 放行)后、runSpawn resolve 前中止。
|
|
369
|
+
mockRunSpawn.mockImplementationOnce(async () => {
|
|
370
|
+
await new Promise((r) => { setTimeout(r, 15); });
|
|
371
|
+
return makeResult(true);
|
|
372
|
+
});
|
|
373
|
+
setTimeout(() => controller.abort(), 5);
|
|
374
|
+
|
|
375
|
+
const result = await callRunAndFinalize(record, () => {}, controller.signal);
|
|
376
|
+
|
|
377
|
+
// result 原样返回(success true 不被改写为 cancelled 形态——现状锚定)。
|
|
378
|
+
expect(result.success).toBe(true);
|
|
379
|
+
expect(result.text).toBe("done");
|
|
380
|
+
// 终态:closed + cancelled(abort 折入 closed)+ archive。
|
|
381
|
+
expect(record.status).toBe("closed");
|
|
382
|
+
expect(record.closedReason).toBe("cancelled");
|
|
383
|
+
expect(record.endedAt).toBeDefined();
|
|
384
|
+
expect(internals.store.getMutable(record.id)).toBeUndefined();
|
|
385
|
+
expect(pi.events.emit).toHaveBeenCalledWith("pending:unregister", { id: record.id, reason: "closed" });
|
|
386
|
+
});
|
|
387
|
+
});
|
|
@@ -25,7 +25,7 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
|
25
25
|
|
|
26
26
|
// mock session-runner:runSpawn 受控返回 result,killAllSpawnedChildren 空实现。
|
|
27
27
|
// 注意:必须在 import SubagentService 之前 mock(vi.mock 提升到顶部)。
|
|
28
|
-
vi.mock("../session-runner.ts", () => ({
|
|
28
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
29
29
|
runSpawn: vi.fn(),
|
|
30
30
|
killAllSpawnedChildren: vi.fn(),
|
|
31
31
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -34,8 +34,8 @@ vi.mock("../session-runner.ts", () => ({
|
|
|
34
34
|
spawnedChildren: new Map(),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
|
-
import { runSpawn } from "../session-runner.ts";
|
|
38
|
-
import type { SessionRunnerContext } from "../session-runner.ts";
|
|
37
|
+
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
38
|
+
import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
|
|
39
39
|
import { createRecord } from "../execution-record.ts";
|
|
40
40
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
41
41
|
import type { ModelInfo } from "../model-resolver.ts";
|
|
@@ -63,11 +63,15 @@ function makeTmpAgentDir(): string {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function makePi(): PiLike & {
|
|
66
|
-
appendEntry: ReturnType<typeof vi.fn
|
|
67
|
-
events: { emit: ReturnType<typeof vi.fn
|
|
68
|
-
sendMessage: ReturnType<typeof vi.fn
|
|
66
|
+
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
67
|
+
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
68
|
+
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
69
69
|
} {
|
|
70
|
-
return {
|
|
70
|
+
return {
|
|
71
|
+
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
72
|
+
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
73
|
+
sendMessage: vi.fn(() => {}),
|
|
74
|
+
};
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
function makeResult(success: boolean): AgentResult {
|
|
@@ -120,7 +124,7 @@ describe("runAndFinalize chatMode idle 分流 (M2-A)", () => {
|
|
|
120
124
|
|
|
121
125
|
beforeEach(() => {
|
|
122
126
|
agentDir = makeTmpAgentDir();
|
|
123
|
-
modelService = new ModelConfigService({ agentDir });
|
|
127
|
+
modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
124
128
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
125
129
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
126
130
|
internals = service as unknown as ServiceInternals;
|
|
@@ -129,7 +133,7 @@ describe("runAndFinalize chatMode idle 分流 (M2-A)", () => {
|
|
|
129
133
|
|
|
130
134
|
afterEach(() => {
|
|
131
135
|
service.dispose();
|
|
132
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
136
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
133
137
|
});
|
|
134
138
|
|
|
135
139
|
/** 直接调私有 runAndFinalize(mock runSpawn 后走完整分流逻辑)。 */
|
|
@@ -141,6 +145,8 @@ describe("runAndFinalize chatMode idle 分流 (M2-A)", () => {
|
|
|
141
145
|
agentDir,
|
|
142
146
|
skillDirs: [],
|
|
143
147
|
mainCwd: agentDir,
|
|
148
|
+
sessionRootId: "s-root",
|
|
149
|
+
rootCwd: agentDir,
|
|
144
150
|
};
|
|
145
151
|
const identity = {
|
|
146
152
|
agent: "general-purpose",
|
|
@@ -63,7 +63,7 @@ vi.mock("../session-pending.ts", () => ({
|
|
|
63
63
|
prunePendingCursor: vi.fn(),
|
|
64
64
|
}));
|
|
65
65
|
|
|
66
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
66
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
|
|
67
67
|
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
68
68
|
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
69
69
|
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
@@ -71,8 +71,8 @@ vi.mock("../temp-prompt.ts", () => ({
|
|
|
71
71
|
cleanupTempPrompt: vi.fn(async () => {}),
|
|
72
72
|
}));
|
|
73
73
|
|
|
74
|
-
import { runSpawn } from "../session-runner.ts";
|
|
75
|
-
import type { SessionRunnerContext } from "../session-runner.ts";
|
|
74
|
+
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
75
|
+
import type { SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
|
|
76
76
|
import {
|
|
77
77
|
DEFAULT_IDLE_TIMEOUT_MS,
|
|
78
78
|
hasIdleTimer,
|
|
@@ -101,7 +101,7 @@ function makeChatModeRecord(id = "sa-chat"): ExecutionRecord {
|
|
|
101
101
|
return createRecord(id, {
|
|
102
102
|
agent: "general-purpose",
|
|
103
103
|
model: "test-model",
|
|
104
|
-
mode: "
|
|
104
|
+
mode: "background",
|
|
105
105
|
task: "chat task",
|
|
106
106
|
slug: "chat",
|
|
107
107
|
startedAt: 1_000_000,
|
|
@@ -78,7 +78,7 @@ vi.mock("../session-pending.ts", () => ({
|
|
|
78
78
|
prunePendingCursor: vi.fn(),
|
|
79
79
|
}));
|
|
80
80
|
|
|
81
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
81
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
|
|
82
82
|
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
83
83
|
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
84
84
|
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
@@ -86,7 +86,7 @@ vi.mock("../temp-prompt.ts", () => ({
|
|
|
86
86
|
cleanupTempPrompt: vi.fn(async () => {}),
|
|
87
87
|
}));
|
|
88
88
|
|
|
89
|
-
import { KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS, killAllSpawnedChildren, runSpawn, spawnedChildren, WAKEUP_GRACE_MS, maxTurnsToWatchdogMs, SPAWN_WATCHDOG_ENV } from "../session-runner.ts";
|
|
89
|
+
import { KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS, killAllSpawnedChildren, runSpawn, spawnedChildren, WAKEUP_GRACE_MS, maxTurnsToWatchdogMs, SPAWN_WATCHDOG_ENV } from "../engine/engines/pi/session-runner.ts";
|
|
90
90
|
import { writeAliveMarker } from "../alive-store.ts";
|
|
91
91
|
import { getSubagentSessionDir } from "../path-encoding.ts";
|
|
92
92
|
import { readActivePendingFromSessionFile } from "../session-pending.ts";
|
|
@@ -566,7 +566,7 @@ describe("runSpawn", () => {
|
|
|
566
566
|
it("MF-4: agent_end(count>0)→ 等待超时 = maxTurnsToWatchdogMs(maxTurns)(动态,非固定 2h)", async () => {
|
|
567
567
|
const maxTurns = 20;
|
|
568
568
|
const expected = maxTurnsToWatchdogMs(maxTurns);
|
|
569
|
-
mockPending.mockReturnValue({ count: 2 });
|
|
569
|
+
mockPending.mockReturnValue({ count: 2, recentUnregister: false });
|
|
570
570
|
const record = makeRecord();
|
|
571
571
|
const promise = runSpawn(record, "Task: slow-desc", makeOpts({ maxTurns }), makeCtx());
|
|
572
572
|
|
|
@@ -610,7 +610,7 @@ describe("runSpawn", () => {
|
|
|
610
610
|
// hermetic:确保兑底 env 未设(若外层 shell 误设会让「裸缺省」断言失效)
|
|
611
611
|
const prevWatchdogEnv = process.env[SPAWN_WATCHDOG_ENV];
|
|
612
612
|
delete process.env[SPAWN_WATCHDOG_ENV];
|
|
613
|
-
mockPending.mockReturnValue({ count: 2 });
|
|
613
|
+
mockPending.mockReturnValue({ count: 2, recentUnregister: false });
|
|
614
614
|
const record = makeRecord();
|
|
615
615
|
const promise = runSpawn(record, "Task: slow-desc-no-turns", makeOpts(), makeCtx());
|
|
616
616
|
|
|
@@ -670,7 +670,7 @@ describe("runSpawn", () => {
|
|
|
670
670
|
it("S-9: agent_end(count=0 + error)→ 保守不 kill + watchdog re-arm 到动态超时", async () => {
|
|
671
671
|
const maxTurns = 20;
|
|
672
672
|
const expected = maxTurnsToWatchdogMs(maxTurns);
|
|
673
|
-
mockPending.mockReturnValue({ count: 0, error: "session file unreadable: EACCES" });
|
|
673
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false, error: "session file unreadable: EACCES" });
|
|
674
674
|
const record = makeRecord();
|
|
675
675
|
const promise = runSpawn(record, "Task: unreadable", makeOpts({ maxTurns }), makeCtx());
|
|
676
676
|
|
|
@@ -706,7 +706,7 @@ describe("runSpawn", () => {
|
|
|
706
706
|
});
|
|
707
707
|
|
|
708
708
|
it("agent_end(willRetry=false,无活跃后代)→ child.kill(SIGTERM) 被调用,close 后 success=true", async () => {
|
|
709
|
-
mockPending.mockReturnValue({ count: 0 });
|
|
709
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false });
|
|
710
710
|
const record = makeRecord();
|
|
711
711
|
const promise = runSpawn(record, "Task: done", makeOpts(), makeCtx());
|
|
712
712
|
|
|
@@ -731,9 +731,9 @@ describe("runSpawn", () => {
|
|
|
731
731
|
|
|
732
732
|
it("agent_end(willRetry=false,有活跃后代)→ 不 kill,进程保持 idle 等 steer 唤醒;后代完成后 kill", async () => {
|
|
733
733
|
// 第一次 agent_end:有活跃后代(count=1)→ 不 kill
|
|
734
|
-
mockPending.mockReturnValueOnce({ count: 1 });
|
|
734
|
+
mockPending.mockReturnValueOnce({ count: 1, recentUnregister: false });
|
|
735
735
|
// 第二次 agent_end:后代已 unregister(count=0)→ kill
|
|
736
|
-
mockPending.mockReturnValueOnce({ count: 0 });
|
|
736
|
+
mockPending.mockReturnValueOnce({ count: 0, recentUnregister: false });
|
|
737
737
|
const record = makeRecord();
|
|
738
738
|
const promise = runSpawn(record, "Task: orchestrate", makeOpts(), makeCtx());
|
|
739
739
|
|
|
@@ -760,7 +760,7 @@ describe("runSpawn", () => {
|
|
|
760
760
|
});
|
|
761
761
|
|
|
762
762
|
it("agent_end(willRetry=true)→ child.kill 不被调用(agent 会重试,等下一个 agent_end)", async () => {
|
|
763
|
-
mockPending.mockReturnValue({ count: 0 });
|
|
763
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false });
|
|
764
764
|
const record = makeRecord();
|
|
765
765
|
const promise = runSpawn(record, "Task: retry", makeOpts(), makeCtx());
|
|
766
766
|
|
|
@@ -789,7 +789,7 @@ describe("runSpawn", () => {
|
|
|
789
789
|
});
|
|
790
790
|
|
|
791
791
|
it("agent_end 后的后续 event 仍被 handleSdkEvent 处理(kill 不阻塞 event pump)", async () => {
|
|
792
|
-
mockPending.mockReturnValue({ count: 0 });
|
|
792
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false });
|
|
793
793
|
const record = makeRecord();
|
|
794
794
|
const promise = runSpawn(record, "Task: flush", makeOpts(), makeCtx());
|
|
795
795
|
|
|
@@ -829,7 +829,9 @@ describe("runSpawn", () => {
|
|
|
829
829
|
describe("[T1] agent_end 惰性回补 + LC-4 收尾反查", () => {
|
|
830
830
|
const mockPending = vi.mocked(readActivePendingFromSessionFile);
|
|
831
831
|
const mockWriteAliveMarker = vi.mocked(writeAliveMarker);
|
|
832
|
-
|
|
832
|
+
// 被测链路(finalize-record 兜底反查)调用无 options 的 string[] 重载;
|
|
833
|
+
// vi.mocked 直接包 fs.readdirSync 会推导到 withFileTypes 重载(Dirent[]),显式绑定
|
|
834
|
+
const mockReaddirSync = vi.mocked(fs.readdirSync as (path: fs.PathLike) => string[]);
|
|
833
835
|
|
|
834
836
|
/** 统计 stdin 收到的 get_state 请求数(握手 1 次 + agent_end 惰性回补 1 次)。 */
|
|
835
837
|
function countGetStateRequests(child: FakeChild): { seen: number } {
|
|
@@ -892,7 +894,7 @@ describe("runSpawn", () => {
|
|
|
892
894
|
// ① 握手失败(sessionFile 空)→ agent_end 惰性 get_state 回填 → 正常三分支(final kill)
|
|
893
895
|
it("① 握手失败 sessionFile 缺失 → agent_end 惰性 get_state 回填 → 无后代走 final kill(非保守不杀)", async () => {
|
|
894
896
|
// 回补成功后判定:无活跃后代 → final kill 分支(若仍走保守分支则不会 kill,断言失败)
|
|
895
|
-
mockPending.mockReturnValue({ count: 0 });
|
|
897
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false });
|
|
896
898
|
const record = makeRecord();
|
|
897
899
|
const promise = runSpawn(record, "Task: lazy-backfill", makeOpts(), makeCtx());
|
|
898
900
|
|
|
@@ -935,7 +937,7 @@ describe("runSpawn", () => {
|
|
|
935
937
|
// ② 回补超时/失败 → 仍走保守分支(行为不劣化)
|
|
936
938
|
it("② 回补无响应超时 → record.sessionFile 仍缺失 → 保守不杀(不劣化为 final kill)", async () => {
|
|
937
939
|
// mock 判定:sessionFile 缺失形态返回 error(真实现语义,见 session-pending.ts:85)
|
|
938
|
-
mockPending.mockReturnValue({ count: 0, error: "no sessionFile (handshake not settled)" });
|
|
940
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false, error: "no sessionFile (handshake not settled)" });
|
|
939
941
|
const record = makeRecord();
|
|
940
942
|
const promise = runSpawn(record, "Task: lazy-timeout", makeOpts(), makeCtx());
|
|
941
943
|
|
|
@@ -982,7 +984,7 @@ describe("runSpawn", () => {
|
|
|
982
984
|
// 且顺带覆盖惰性回补的 sessionId 补入 handshakeResult 分支(close 收尾 lookupId 源)。
|
|
983
985
|
it("③ LC-4: 惰性回补只回 sessionId(sessionFile 仍无)→ close 收尾 sessionDir 反查修正 record.sessionFile", async () => {
|
|
984
986
|
// sessionFile 缺失 → 决策点惰性回补;回补判定保守(error → 不杀)
|
|
985
|
-
mockPending.mockReturnValue({ count: 0, error: "no sessionFile (handshake not settled)" });
|
|
987
|
+
mockPending.mockReturnValue({ count: 0, recentUnregister: false, error: "no sessionFile (handshake not settled)" });
|
|
986
988
|
const record = makeRecord();
|
|
987
989
|
const promise = runSpawn(record, "Task: lc4-lookup", makeOpts(), makeCtx());
|
|
988
990
|
|
|
@@ -1031,7 +1033,7 @@ describe("runSpawn", () => {
|
|
|
1031
1033
|
it("④ A1-3: 回补等待窗口内 kill → close → continuation 不触达三分支(不 touch marker / 不挂 timer)", async () => {
|
|
1032
1034
|
// 若守卫缺失(回归),continuation 会以 count=1 走 keep-alive 分支:
|
|
1033
1035
|
// touchAliveMarkerForHeartbeat 写 marker + 裸缺省 arm 无进展 timer(30min 后 kill)
|
|
1034
|
-
mockPending.mockReturnValue({ count: 1 });
|
|
1036
|
+
mockPending.mockReturnValue({ count: 1, recentUnregister: false });
|
|
1035
1037
|
const record = makeRecord();
|
|
1036
1038
|
const promise = runSpawn(record, "Task: lazy-race-close", makeOpts(), makeCtx());
|
|
1037
1039
|
|
|
@@ -73,7 +73,7 @@ vi.mock("../alive-store.ts", () => ({
|
|
|
73
73
|
// temp-prompt:mock 掉真实 fs.promises I/O(mkdtemp/writeFile/rm)。
|
|
74
74
|
// 原先保留真实实现导致 fake-timers 测试偶发 flaky——writePromptToTempFile 的真实异步
|
|
75
75
|
// I/O 在 CI 慢机器上无法在 advanceTimersByTimeAsync 的有限步数内 resolve,spawn 永不触发。
|
|
76
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
76
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
|
|
77
77
|
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
78
78
|
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
79
79
|
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
@@ -81,7 +81,7 @@ vi.mock("../temp-prompt.ts", () => ({
|
|
|
81
81
|
cleanupTempPrompt: vi.fn(async () => {}),
|
|
82
82
|
}));
|
|
83
83
|
|
|
84
|
-
import { runSpawn } from "../session-runner.ts";
|
|
84
|
+
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
85
85
|
import {
|
|
86
86
|
emitStdoutLine,
|
|
87
87
|
type FakeChild,
|
|
@@ -53,7 +53,7 @@ vi.mock("../alive-store.ts", () => ({
|
|
|
53
53
|
writeAliveMarker: vi.fn(),
|
|
54
54
|
}));
|
|
55
55
|
|
|
56
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
56
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
|
|
57
57
|
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
58
58
|
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
59
59
|
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
@@ -61,7 +61,7 @@ vi.mock("../temp-prompt.ts", () => ({
|
|
|
61
61
|
cleanupTempPrompt: vi.fn(async () => {}),
|
|
62
62
|
}));
|
|
63
63
|
|
|
64
|
-
import { getChildByRecord, runSpawn, spawnedChildren } from "../session-runner.ts";
|
|
64
|
+
import { getChildByRecord, runSpawn, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
|
|
65
65
|
import {
|
|
66
66
|
emitStdoutLine,
|
|
67
67
|
type FakeChild,
|
|
@@ -53,7 +53,7 @@ vi.mock("../alive-store.ts", () => ({
|
|
|
53
53
|
writeAliveMarker: vi.fn(),
|
|
54
54
|
}));
|
|
55
55
|
|
|
56
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
56
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
|
|
57
57
|
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
58
58
|
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
59
59
|
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
@@ -61,7 +61,7 @@ vi.mock("../temp-prompt.ts", () => ({
|
|
|
61
61
|
cleanupTempPrompt: vi.fn(async () => {}),
|
|
62
62
|
}));
|
|
63
63
|
|
|
64
|
-
import { runSpawn } from "../session-runner.ts";
|
|
64
|
+
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
65
65
|
import {
|
|
66
66
|
emitStdoutLine,
|
|
67
67
|
type FakeChild,
|