@zhushanwen/subagent-core 0.2.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-43ONBFZX.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-V3VHZ2VX.js +59 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
- package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
- package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
- package/dist/execution/engine/engines/zcode/constants.js +39 -7
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +18234 -1277
- package/dist/index.d.cts +5646 -673
- package/dist/index.d.ts +5646 -673
- package/dist/index.js +17828 -1277
- package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
- package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
- package/package.json +7 -4
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +93 -3
- package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +77 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +278 -16
- package/src/execution/__tests__/gc-timer.test.ts +57 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +89 -5
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
- package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
- package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +471 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +155 -11
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +13 -12
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +298 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +94 -3
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +598 -0
- package/src/execution/engine/engines/zcode/constants.ts +147 -14
- package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +22 -150
- package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +65 -7
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +181 -85
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +148 -88
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +127 -102
- package/src/execution/settled-watchdog.ts +311 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +1106 -907
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +126 -75
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +542 -9
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +8 -8
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +25 -79
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +160 -104
- package/src/orchestration/lifecycle.ts +371 -97
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +15 -15
- package/src/orchestration/models/trace.ts +9 -9
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +28 -28
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +123 -90
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +261 -217
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +323 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +182 -68
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +97 -204
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/schema-jsonify.ts +1 -1
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +165 -92
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/execution/session-runner.ts +0 -1789
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// src/execution/__tests__/subagent-service-recovery-bounds.test.ts
|
|
2
|
+
//
|
|
3
|
+
// [u-svc / T2] 服务侧回收上界与 kill 收敛:
|
|
4
|
+
// - T2④/LC-2:closeChatIdle / closeAfterRoundSettled / cancelBackground 三条裸 SIGTERM
|
|
5
|
+
// 收敛到 killRecordChildWithEscalation(spy 断言调用点与参数);
|
|
6
|
+
// - T2⑥/PS-1:disposeAllRecords 补三回收面(controller.abort + kill + disarm idle timer
|
|
7
|
+
// + disarm settled watchdog);
|
|
8
|
+
// - T2③/LC-1 + D9:热路径投递 armMidRoundNoProgress(挂载点 D2 下沉后在编排层
|
|
9
|
+
// deliverChatMessage 的 interact 返回点;挂载中段无进展检测 + 到期 onMidTimeout/
|
|
10
|
+
// onSettleTimeout 处置:kill + 失败终态化 + error 含 'settled watchdog' 标记与恢复
|
|
11
|
+
// 指引;agent_end 交棒收尾段的接线在 session-runner stdout pump,两路径共用);
|
|
12
|
+
// - T2⑧/PS-3:非 EPIPE 热路径写失败 re-arm idle timer(进程不再裸奔;挂载位 D2
|
|
13
|
+
// 下沉后在 PiEngine.deliverPrompt 的非 EPIPE catch——编排层无法区分错误类别)。
|
|
14
|
+
//
|
|
15
|
+
// mock 形态沿用 subagent-service-message-close.test.ts(真实 SubagentService + ServiceInternals
|
|
16
|
+
// cast 暴露 store + mock session-runner/stdin-writer/logger)。
|
|
17
|
+
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import * as os from "node:os";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
|
|
22
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
23
|
+
|
|
24
|
+
const { loggerMock } = vi.hoisted(() => ({
|
|
25
|
+
loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
26
|
+
}));
|
|
27
|
+
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
28
|
+
|
|
29
|
+
// killRecordChildWithEscalation 换 spy(收敛调用点断言);spawnedChildren 用真 Map
|
|
30
|
+
//(热路径投递判定 / EPIPE 兜底按值删除的消费者);getChildByRecord 从同一 Map 查
|
|
31
|
+
//(mock 工厂与测试共享同一实例)。D2 后模块物理下沉 engines/pi/(mock 路径随迁)。
|
|
32
|
+
const { killChildSpy, spawnedMap, getChildByRecordMock } = vi.hoisted(() => {
|
|
33
|
+
const map = new Map<string, unknown>();
|
|
34
|
+
return {
|
|
35
|
+
killChildSpy: vi.fn(),
|
|
36
|
+
spawnedMap: map,
|
|
37
|
+
getChildByRecordMock: vi.fn((recordId: string) => map.get(recordId)),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
41
|
+
runSpawn: vi.fn(),
|
|
42
|
+
killAllSpawnedChildren: vi.fn(),
|
|
43
|
+
getChildByRecord: getChildByRecordMock,
|
|
44
|
+
registerSpawnedChildForRecord: vi.fn(),
|
|
45
|
+
spawnedChildren: spawnedMap,
|
|
46
|
+
killRecordChildWithEscalation: killChildSpy,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// sendPromptCommand 换 spy(控制热路径写成功 / 抛非 EPIPE 错误)。
|
|
50
|
+
const { sendPromptCommandMock } = vi.hoisted(() => ({ sendPromptCommandMock: vi.fn() }));
|
|
51
|
+
vi.mock("../engine/engines/pi/stdin-writer.ts", () => ({
|
|
52
|
+
sendPromptCommand: sendPromptCommandMock,
|
|
53
|
+
clearEpipeFailure: vi.fn(),
|
|
54
|
+
recordEpipeFailure: vi.fn(() => 1),
|
|
55
|
+
resetAllEpipeFailures: vi.fn(),
|
|
56
|
+
EPIPE_FAILURE_THRESHOLD: 3,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
import { FakeChild } from "./helpers/spawn-mock.ts";
|
|
60
|
+
import { createRecord } from "../execution-record.ts";
|
|
61
|
+
import { ModelConfigService } from "../model-config-service.ts";
|
|
62
|
+
import type { RecordStore } from "../record-store.ts";
|
|
63
|
+
import { SubagentService } from "../subagent-service.ts";
|
|
64
|
+
import type { PiLike } from "../subagent-service.ts";
|
|
65
|
+
import { armSettledWatchdog, hasSettledWatchdog, SETTLED_MID_ROUND_NO_PROGRESS_MS, _resetSettledWatchdogsForTest } from "../settled-watchdog.ts";
|
|
66
|
+
import { armIdleTimer, hasIdleTimer, _resetLifecycleState } from "../lifecycle-manager.ts";
|
|
67
|
+
import type { ExecutionRecord } from "../types.ts";
|
|
68
|
+
|
|
69
|
+
function makeTmpAgentDir(): string {
|
|
70
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "svc-recovery-bounds-"));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function makePi(): PiLike & {
|
|
74
|
+
appendEntry: ReturnType<typeof vi.fn>;
|
|
75
|
+
events: { emit: ReturnType<typeof vi.fn> };
|
|
76
|
+
sendMessage: ReturnType<typeof vi.fn>;
|
|
77
|
+
} {
|
|
78
|
+
return {
|
|
79
|
+
appendEntry: vi.fn(),
|
|
80
|
+
events: { emit: vi.fn() },
|
|
81
|
+
sendMessage: vi.fn(),
|
|
82
|
+
} as unknown as PiLike & {
|
|
83
|
+
appendEntry: ReturnType<typeof vi.fn>;
|
|
84
|
+
events: { emit: ReturnType<typeof vi.fn> };
|
|
85
|
+
sendMessage: ReturnType<typeof vi.fn>;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function makeRecord(overrides: Partial<ExecutionRecord> & { id?: string } = {}): ExecutionRecord {
|
|
90
|
+
const { id = "sa-test", ...rest } = overrides;
|
|
91
|
+
const r = createRecord(id, {
|
|
92
|
+
agent: "general-purpose",
|
|
93
|
+
model: "test/model",
|
|
94
|
+
mode: "background",
|
|
95
|
+
task: "test",
|
|
96
|
+
slug: "test",
|
|
97
|
+
startedAt: 1000,
|
|
98
|
+
rootSessionId: "root-session",
|
|
99
|
+
chatMode: true,
|
|
100
|
+
controller: new AbortController(),
|
|
101
|
+
});
|
|
102
|
+
Object.assign(r, rest);
|
|
103
|
+
return r;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface ServiceInternals {
|
|
107
|
+
store: RecordStore;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type MockPi = ReturnType<typeof makePi>;
|
|
111
|
+
|
|
112
|
+
function setup(): { agentDir: string; service: SubagentService; store: RecordStore; pi: MockPi } {
|
|
113
|
+
const agentDir = makeTmpAgentDir();
|
|
114
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
115
|
+
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
116
|
+
const pi = makePi();
|
|
117
|
+
service.initSession({ pi, sessionId: "root-session" });
|
|
118
|
+
const store = (service as unknown as ServiceInternals).store;
|
|
119
|
+
return { agentDir, service, store, pi };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** bracket 调 private 方法(ServiceInternals cast 先例的扩展形态)。 */
|
|
123
|
+
function privateFn<K extends string>(service: SubagentService, key: K): (...args: never[]) => unknown {
|
|
124
|
+
return (service as unknown as Record<string, (...args: never[]) => unknown>)[key];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* message 投递入口(D2 后形态:编排层私有 deliverChatMessage → PiEngine.interactRecord
|
|
129
|
+
* → deliverPrompt;旧直调形态 deliverMessage 已随协议知识下沉删除)。
|
|
130
|
+
*/
|
|
131
|
+
async function deliverChat(
|
|
132
|
+
service: SubagentService,
|
|
133
|
+
record: ExecutionRecord,
|
|
134
|
+
text: string,
|
|
135
|
+
interrupt: boolean,
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
return (
|
|
138
|
+
privateFn(service, "deliverChatMessage") as (
|
|
139
|
+
this: SubagentService,
|
|
140
|
+
r: ExecutionRecord,
|
|
141
|
+
t: string,
|
|
142
|
+
i: boolean,
|
|
143
|
+
) => Promise<void>
|
|
144
|
+
).call(service, record, text, interrupt);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
describe("T2④ service-side kill convergence", () => {
|
|
148
|
+
let agentDir: string;
|
|
149
|
+
let service: SubagentService;
|
|
150
|
+
let store: RecordStore;
|
|
151
|
+
let pi: MockPi;
|
|
152
|
+
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
vi.restoreAllMocks();
|
|
155
|
+
killChildSpy.mockClear();
|
|
156
|
+
sendPromptCommandMock.mockReset();
|
|
157
|
+
({ agentDir, service, store, pi } = setup());
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
afterEach(() => {
|
|
161
|
+
_resetLifecycleState();
|
|
162
|
+
_resetSettledWatchdogsForTest();
|
|
163
|
+
spawnedMap.clear();
|
|
164
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("cancelBackground (via cancel) routes through killRecordChildWithEscalation", async () => {
|
|
168
|
+
const record = makeRecord({ id: "sa-cancel" });
|
|
169
|
+
store.register(record);
|
|
170
|
+
spawnedMap.set(record.id, new FakeChild());
|
|
171
|
+
const ok = service.cancel(record.id);
|
|
172
|
+
expect(ok).toBe(true);
|
|
173
|
+
expect(killChildSpy).toHaveBeenCalledWith(record.id, "cancelBackground");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("closeChatIdle (via closeSubagent force:false on idle) routes through killRecordChildWithEscalation and disarms timers", async () => {
|
|
177
|
+
const record = makeRecord({ id: "sa-close-idle" });
|
|
178
|
+
store.register(record);
|
|
179
|
+
spawnedMap.set(record.id, new FakeChild());
|
|
180
|
+
armIdleTimer(record.id, () => {}); // Path A:idle timer armed(进程保活)
|
|
181
|
+
armSettledWatchdog(record.id, () => {});
|
|
182
|
+
await service["closeSubagent"](record, false);
|
|
183
|
+
expect(killChildSpy).toHaveBeenCalledWith(record.id, "closeChatIdle");
|
|
184
|
+
expect(hasIdleTimer(record.id)).toBe(false);
|
|
185
|
+
expect(hasSettledWatchdog(record.id)).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("closeAfterRoundSettled routes through killRecordChildWithEscalation", async () => {
|
|
189
|
+
const record = makeRecord({ id: "sa-close-round" });
|
|
190
|
+
await (
|
|
191
|
+
privateFn(service, "closeAfterRoundSettled") as (this: SubagentService, r: ExecutionRecord) => Promise<void>
|
|
192
|
+
).call(service, record);
|
|
193
|
+
expect(killChildSpy).toHaveBeenCalledWith(record.id, "closeAfterRoundSettled");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("disposeAllRecords applies all recovery surfaces: abort + escalation kill + disarm idle/settled timers", () => {
|
|
197
|
+
const running = makeRecord({ id: "sa-dispose-run" });
|
|
198
|
+
const idle = makeRecord({ id: "sa-dispose-idle" });
|
|
199
|
+
store.register(running);
|
|
200
|
+
store.register(idle);
|
|
201
|
+
spawnedMap.set(running.id, new FakeChild());
|
|
202
|
+
armIdleTimer(idle.id, () => {});
|
|
203
|
+
armSettledWatchdog(idle.id, () => {});
|
|
204
|
+
|
|
205
|
+
const count = service.disposeAllRecords("parent-new");
|
|
206
|
+
|
|
207
|
+
expect(count).toBe(2);
|
|
208
|
+
// 回收面 i:controller.abort
|
|
209
|
+
expect(running.controller?.signal.aborted).toBe(true);
|
|
210
|
+
expect(idle.controller?.signal.aborted).toBe(true);
|
|
211
|
+
// 回收面 ii:kill 收敛到 escalation 入口
|
|
212
|
+
expect(killChildSpy).toHaveBeenCalledWith(running.id, "disposeAllRecords (parent-new)");
|
|
213
|
+
expect(killChildSpy).toHaveBeenCalledWith(idle.id, "disposeAllRecords (parent-new)");
|
|
214
|
+
// 回收面 iii:idle timer + settled watchdog 双 disarm
|
|
215
|
+
expect(hasIdleTimer(idle.id)).toBe(false);
|
|
216
|
+
expect(hasSettledWatchdog(idle.id)).toBe(false);
|
|
217
|
+
// record 终态化
|
|
218
|
+
expect(running.status).toBe("closed");
|
|
219
|
+
expect(idle.status).toBe("closed");
|
|
220
|
+
expect(running.closedReason).toBe("parent-new");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("T2③ hot-path settled watchdog", () => {
|
|
225
|
+
let agentDir: string;
|
|
226
|
+
let service: SubagentService;
|
|
227
|
+
let store: RecordStore;
|
|
228
|
+
let pi: MockPi;
|
|
229
|
+
|
|
230
|
+
beforeEach(() => {
|
|
231
|
+
vi.restoreAllMocks();
|
|
232
|
+
killChildSpy.mockClear();
|
|
233
|
+
// 模拟真 kill 的语义面(置 killed 标记):isResumable 判定(无活进程)与失败通知
|
|
234
|
+
// 放行依赖它——真实 kill 后 close 事件把句柄移出 spawnedChildren,语义相同。
|
|
235
|
+
killChildSpy.mockImplementation((recordId: string) => {
|
|
236
|
+
const c = spawnedMap.get(recordId) as FakeChild | undefined;
|
|
237
|
+
if (c) c.killed = true;
|
|
238
|
+
});
|
|
239
|
+
sendPromptCommandMock.mockReset();
|
|
240
|
+
sendPromptCommandMock.mockImplementation(() => {});
|
|
241
|
+
({ agentDir, service, store, pi } = setup());
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
afterEach(() => {
|
|
245
|
+
vi.useRealTimers();
|
|
246
|
+
_resetLifecycleState();
|
|
247
|
+
_resetSettledWatchdogsForTest();
|
|
248
|
+
spawnedMap.clear();
|
|
249
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("arms the settled watchdog after a successful hot-path prompt", async () => {
|
|
253
|
+
const record = makeRecord({ id: "sa-hot-arm" });
|
|
254
|
+
store.register(record);
|
|
255
|
+
spawnedMap.set(record.id, new FakeChild());
|
|
256
|
+
await deliverChat(service, record, "hello", false);
|
|
257
|
+
expect(sendPromptCommandMock).toHaveBeenCalledTimes(1);
|
|
258
|
+
expect(hasSettledWatchdog(record.id)).toBe(true);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("onMidTimeout: kills the child, fails the round, error carries 'settled watchdog' marker and recovery hint", async () => {
|
|
262
|
+
// fake timers 必须先于 arm 生效(useFakeTimers 不接管已存在的真实 timer)
|
|
263
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
264
|
+
const record = makeRecord({ id: "sa-hot-timeout" });
|
|
265
|
+
store.register(record);
|
|
266
|
+
spawnedMap.set(record.id, new FakeChild());
|
|
267
|
+
await deliverChat(service, record, "hello", false);
|
|
268
|
+
expect(hasSettledWatchdog(record.id)).toBe(true);
|
|
269
|
+
|
|
270
|
+
// [D9 两段式] 热路径 prompt 发出后挂中段:静默满中段窗长触发(本测试 mock 了
|
|
271
|
+
// session-runner,无 stdout pump 驱动事件刷新/交棒,中段静默形态直达)
|
|
272
|
+
await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS + 1);
|
|
273
|
+
|
|
274
|
+
// kill 收敛入口被触发(onTimeout 第一步)
|
|
275
|
+
expect(killChildSpy).toHaveBeenCalledWith(record.id, "settled watchdog (hot path)");
|
|
276
|
+
// watchdog 到期自清(armedTimers 先删条目再执行回调)
|
|
277
|
+
expect(hasSettledWatchdog(record.id)).toBe(false);
|
|
278
|
+
// 该轮失败终态化(chatMode → MF-6 回退 resumable)+ 失败通知
|
|
279
|
+
expect(record.resumable).toBe(true);
|
|
280
|
+
expect(record.status).toBe("running");
|
|
281
|
+
expect(record.result).toContain("settled watchdog");
|
|
282
|
+
expect(record.result).toContain("Recovery");
|
|
283
|
+
expect(record.result).toContain("action:'list'");
|
|
284
|
+
// 失败通知送达(内核直发路径)
|
|
285
|
+
expect(pi.sendMessage).toHaveBeenCalled();
|
|
286
|
+
const notifyContent = (pi.sendMessage.mock.calls[0]?.[0] as { content?: string } | undefined)?.content ?? "";
|
|
287
|
+
expect(notifyContent).toContain("settled watchdog");
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("T2⑧ non-EPIPE hot-path failure re-arms idle timer", () => {
|
|
292
|
+
let agentDir: string;
|
|
293
|
+
let service: SubagentService;
|
|
294
|
+
let store: RecordStore;
|
|
295
|
+
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
vi.restoreAllMocks();
|
|
298
|
+
killChildSpy.mockClear();
|
|
299
|
+
({ agentDir, service, store } = setup());
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
afterEach(() => {
|
|
303
|
+
_resetLifecycleState();
|
|
304
|
+
_resetSettledWatchdogsForTest();
|
|
305
|
+
spawnedMap.clear();
|
|
306
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("re-arms the idle timer (default duration) and rethrows on non-EPIPE write failure", async () => {
|
|
310
|
+
const record = makeRecord({ id: "sa-nonpipe" });
|
|
311
|
+
store.register(record);
|
|
312
|
+
spawnedMap.set(record.id, new FakeChild());
|
|
313
|
+
sendPromptCommandMock.mockImplementation(() => {
|
|
314
|
+
throw new Error("write after end: ERR_STREAM_DESTROYED");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
await expect(deliverChat(service, record, "hello", false)).rejects.toThrow(/ERR_STREAM_DESTROYED/);
|
|
318
|
+
|
|
319
|
+
// 防泄漏前提恢复:入口 disarm 过的 idle timer 被 re-arm(进程不再裸奔)
|
|
320
|
+
expect(hasIdleTimer(record.id)).toBe(true);
|
|
321
|
+
expect(loggerMock.warn).toHaveBeenCalledWith(
|
|
322
|
+
expect.stringContaining("idle timer re-armed"),
|
|
323
|
+
expect.objectContaining({ detail: expect.stringContaining("ERR_STREAM_DESTROYED") }),
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("falls back to DEFAULT idle duration (visible) when record.idleTimeoutMs is invalid", async () => {
|
|
328
|
+
const record = makeRecord({ id: "sa-nonpipe-bad" });
|
|
329
|
+
// 直接注入非法值模拟「入口校验前已存在的 record」/ 回归形态:re-arm 首选值非法 →
|
|
330
|
+
// 降级挂 DEFAULT(防御性兜底可见,不静默不挂)
|
|
331
|
+
(record as { idleTimeoutMs: number }).idleTimeoutMs = 3_000_000_000;
|
|
332
|
+
store.register(record);
|
|
333
|
+
spawnedMap.set(record.id, new FakeChild());
|
|
334
|
+
sendPromptCommandMock.mockImplementation(() => {
|
|
335
|
+
throw new Error("ERR_STREAM_DESTROYED");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await expect(deliverChat(service, record, "hello", false)).rejects.toThrow(/ERR_STREAM_DESTROYED/);
|
|
339
|
+
expect(hasIdleTimer(record.id)).toBe(true);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
@@ -36,18 +36,18 @@ function makeTmpAgentDir(): string {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function makeModelService(agentDir: string): ModelConfigService {
|
|
39
|
-
return new ModelConfigService({ agentDir });
|
|
39
|
+
return new ModelConfigService({ agentDir, cwd: agentDir });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function makePi(): PiLike & {
|
|
43
|
-
appendEntry: ReturnType<typeof vi.fn
|
|
44
|
-
events: { emit: ReturnType<typeof vi.fn
|
|
45
|
-
sendMessage: ReturnType<typeof vi.fn
|
|
43
|
+
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
44
|
+
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
45
|
+
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
46
46
|
} {
|
|
47
47
|
return {
|
|
48
|
-
appendEntry: vi.fn(),
|
|
49
|
-
events: { emit: vi.fn() },
|
|
50
|
-
sendMessage: vi.fn(),
|
|
48
|
+
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
49
|
+
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
50
|
+
sendMessage: vi.fn(() => {}),
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ describe("SubagentService", () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
afterEach(() => {
|
|
64
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
64
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
// ============================================================
|
|
@@ -75,7 +75,7 @@ describe("SubagentService", () => {
|
|
|
75
75
|
|
|
76
76
|
it("未 initSession 时 findRecord/cancel 抛 'pi not injected'", () => {
|
|
77
77
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
78
|
-
expect(() => service.findRecord("any")).toThrow(/pi not injected/);
|
|
78
|
+
expect(() => service.queries.findRecord("any")).toThrow(/pi not injected/);
|
|
79
79
|
expect(() => service.cancel("any")).toThrow(/pi not injected/);
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -83,7 +83,7 @@ describe("SubagentService", () => {
|
|
|
83
83
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
84
84
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
85
85
|
// findRecord 现在能过 assertReady,但 record 不存在 → 返回 undefined
|
|
86
|
-
expect(service.findRecord("missing")).toBeUndefined();
|
|
86
|
+
expect(service.queries.findRecord("missing")).toBeUndefined();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
it("dispose 后 findRecord 抛含 'disposed' 且带恢复指引", () => {
|
|
@@ -92,8 +92,8 @@ describe("SubagentService", () => {
|
|
|
92
92
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
93
93
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
94
94
|
service.dispose();
|
|
95
|
-
expect(() => service.findRecord("any")).toThrow(/disposed/);
|
|
96
|
-
expect(() => service.findRecord("any")).toThrow(/session ended|session_start|new session/i);
|
|
95
|
+
expect(() => service.queries.findRecord("any")).toThrow(/disposed/);
|
|
96
|
+
expect(() => service.queries.findRecord("any")).toThrow(/session ended|session_start|new session/i);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
it("dispose 幂等(多次调用不抛)", () => {
|
|
@@ -113,7 +113,7 @@ describe("SubagentService", () => {
|
|
|
113
113
|
// revive
|
|
114
114
|
service.initSession({ pi: makePi(), sessionId: "s2" });
|
|
115
115
|
// 现在 assertReady 又通过(findRecord 返回 undefined 而非 disposed)
|
|
116
|
-
expect(service.findRecord("any")).toBeUndefined();
|
|
116
|
+
expect(service.queries.findRecord("any")).toBeUndefined();
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
119
|
|
|
@@ -125,7 +125,7 @@ describe("SubagentService", () => {
|
|
|
125
125
|
it("findRecord 不存在的 id 返回 undefined", () => {
|
|
126
126
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
127
127
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
128
|
-
expect(service.findRecord("nonexistent-id")).toBeUndefined();
|
|
128
|
+
expect(service.queries.findRecord("nonexistent-id")).toBeUndefined();
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
it("cancel 不存在的 id 返回 false(不抛错,boolean 契约不变)", () => {
|
|
@@ -140,16 +140,13 @@ describe("SubagentService", () => {
|
|
|
140
140
|
// ============================================================
|
|
141
141
|
|
|
142
142
|
describe("状态查询", () => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
146
|
-
expect(service.listRunning()).toEqual([]);
|
|
147
|
-
});
|
|
143
|
+
// [D4] listRunning 已从 Service 删除(零生产调用方)——初始空态语义由
|
|
144
|
+
// collectRecords 用例与 store 层 listRunning 测试覆盖。
|
|
148
145
|
|
|
149
146
|
it("collectRecords 返回数组(空 sessions 目录时为空)", () => {
|
|
150
147
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
151
148
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
152
|
-
const records = service.collectRecords(100);
|
|
149
|
+
const records = service.queries.collectRecords(100);
|
|
153
150
|
expect(Array.isArray(records)).toBe(true);
|
|
154
151
|
});
|
|
155
152
|
|
|
@@ -157,7 +154,7 @@ describe("SubagentService", () => {
|
|
|
157
154
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
158
155
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
159
156
|
const listener = vi.fn();
|
|
160
|
-
const unsubscribe = service.onChange(listener);
|
|
157
|
+
const unsubscribe = service.queries.onChange(listener);
|
|
161
158
|
expect(typeof unsubscribe).toBe("function");
|
|
162
159
|
expect(() => unsubscribe()).not.toThrow();
|
|
163
160
|
});
|
|
@@ -218,6 +215,7 @@ describe("SubagentService", () => {
|
|
|
218
215
|
agent: "general-purpose",
|
|
219
216
|
model: "test/model",
|
|
220
217
|
mode: "background",
|
|
218
|
+
slug: "t",
|
|
221
219
|
task: "long task",
|
|
222
220
|
startedAt: 1_000_000,
|
|
223
221
|
rootSessionId: "s1",
|
|
@@ -233,8 +231,9 @@ describe("SubagentService", () => {
|
|
|
233
231
|
const record = createRecord(id, {
|
|
234
232
|
agent: "general-purpose",
|
|
235
233
|
model: "test/model",
|
|
236
|
-
mode: "
|
|
234
|
+
mode: "background",
|
|
237
235
|
task: "sync task",
|
|
236
|
+
slug: "test",
|
|
238
237
|
startedAt: 1_000_000,
|
|
239
238
|
rootSessionId: "s1",
|
|
240
239
|
// sync 不传 controller → controller === undefined
|
|
@@ -250,13 +249,14 @@ describe("SubagentService", () => {
|
|
|
250
249
|
agent: "general-purpose",
|
|
251
250
|
model: "test/model",
|
|
252
251
|
mode: "background",
|
|
252
|
+
slug: "t",
|
|
253
253
|
task: "done task",
|
|
254
254
|
startedAt: 1_000_000,
|
|
255
255
|
rootSessionId: "s1",
|
|
256
256
|
controller,
|
|
257
257
|
});
|
|
258
258
|
// 直接改 status 模拟终态(不走 CAS——测试不关心状态机,只关心 dispose 的 abort 过滤)
|
|
259
|
-
record.status = "
|
|
259
|
+
record.status = "closed";
|
|
260
260
|
getStore(service).register(record);
|
|
261
261
|
return record;
|
|
262
262
|
}
|
|
@@ -362,7 +362,8 @@ describe("SubagentService", () => {
|
|
|
362
362
|
|
|
363
363
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
364
364
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
365
|
-
|
|
365
|
+
// [D4-④] setUiRequestHandler 已删——handler 注入走 initSession 参数(唯一入口)
|
|
366
|
+
service.initSession({ pi: makePi(), sessionId: "s1", uiRequestHandler: staleHandler });
|
|
366
367
|
|
|
367
368
|
service.dispose();
|
|
368
369
|
|
|
@@ -427,6 +428,7 @@ describe("SubagentService", () => {
|
|
|
427
428
|
try {
|
|
428
429
|
await service.execute({
|
|
429
430
|
task: "worktree without fork (decoupled)",
|
|
431
|
+
slug: "test",
|
|
430
432
|
worktree: true,
|
|
431
433
|
fork: false,
|
|
432
434
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -443,6 +445,7 @@ describe("SubagentService", () => {
|
|
|
443
445
|
try {
|
|
444
446
|
await service.execute({
|
|
445
447
|
task: "worktree with fork",
|
|
448
|
+
slug: "test",
|
|
446
449
|
worktree: true,
|
|
447
450
|
fork: true,
|
|
448
451
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -460,6 +463,7 @@ describe("SubagentService", () => {
|
|
|
460
463
|
try {
|
|
461
464
|
await service.execute({
|
|
462
465
|
task: "default path",
|
|
466
|
+
slug: "test",
|
|
463
467
|
worktree: false,
|
|
464
468
|
fork: false,
|
|
465
469
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -473,7 +477,7 @@ describe("SubagentService", () => {
|
|
|
473
477
|
// ============================================================
|
|
474
478
|
// create-await 竞态守卫(Phase 2):create 的 await 窗口内 dispose/cancel
|
|
475
479
|
// 可把 record CAS 成 closed——守卫须主动 cleanup + early-failed 返回,不 kickOff。
|
|
476
|
-
// 实现约束固化:「赋值 record.worktreeHandle → 终态检查 →
|
|
480
|
+
// 实现约束固化:「赋值 record.worktreeHandle → 终态检查 → 轮次 kick-off」
|
|
477
481
|
// 必须同一同步段(中间禁止 await),本用例即该不变量的回归锚点。
|
|
478
482
|
// ============================================================
|
|
479
483
|
it("守卫:create await 窗口内 dispose 抢先 → cleanup 被调 + early-failed 返回(不 kickOff)", async () => {
|
|
@@ -487,12 +491,13 @@ describe("SubagentService", () => {
|
|
|
487
491
|
}) as Parameters<WorktreeManager["cleanup"]>[0];
|
|
488
492
|
let resolveCreate!: (h: unknown) => void;
|
|
489
493
|
vi.spyOn(wtm, "create").mockImplementation(
|
|
490
|
-
() => new Promise((r) => { resolveCreate = r; }) as ReturnType<WorktreeManager["create"]>,
|
|
494
|
+
() => new Promise<unknown>((r) => { resolveCreate = r; }) as ReturnType<WorktreeManager["create"]>,
|
|
491
495
|
);
|
|
492
496
|
const cleanupSpy = vi.spyOn(wtm, "cleanup").mockResolvedValue(undefined);
|
|
493
497
|
|
|
494
498
|
const execP = service.execute({
|
|
495
499
|
task: "guard test",
|
|
500
|
+
slug: "test",
|
|
496
501
|
worktree: true,
|
|
497
502
|
fork: false,
|
|
498
503
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -529,7 +534,7 @@ describe("SubagentService", () => {
|
|
|
529
534
|
//
|
|
530
535
|
// [未覆盖路径] 需 mock spawn 才能跑完 runSpawn 的路径,本文件约定不 mock spawn
|
|
531
536
|
// (见文件头——execute 集成测试在 execute-nesting.test.ts / run-spawn-integration.test.ts):
|
|
532
|
-
// - finalizeRecord status="
|
|
537
|
+
// - finalizeRecord status="closed"(background 正常完成 → unregister(closed))
|
|
533
538
|
// - finalizeRecord status="cancelled" 经 runAndFinalize 路径(cancel 抢先 CAS 时
|
|
534
539
|
// runAndFinalize 侧 tryTransition 失败跳过 finalizeRecord,由 cancelBackground 侧 emit——
|
|
535
540
|
// 本块 cancel 用例覆盖的即此后端 emit)
|
|
@@ -572,6 +577,7 @@ describe("SubagentService", () => {
|
|
|
572
577
|
agent: "general-purpose",
|
|
573
578
|
model: "test/model",
|
|
574
579
|
mode: "background",
|
|
580
|
+
slug: "t",
|
|
575
581
|
task: "cancel target",
|
|
576
582
|
startedAt: 1_000_000,
|
|
577
583
|
rootSessionId: "s1",
|
|
@@ -588,12 +594,13 @@ describe("SubagentService", () => {
|
|
|
588
594
|
|
|
589
595
|
const handle = await service.execute({
|
|
590
596
|
task: "wt fail bg",
|
|
597
|
+
slug: "test",
|
|
591
598
|
worktree: true,
|
|
592
599
|
fork: true,
|
|
593
600
|
ctxModel,
|
|
594
601
|
});
|
|
595
602
|
|
|
596
|
-
// worktree create
|
|
603
|
+
// worktree create 抛错在轮次 kick-off 之前(executeViaEngine 同步 catch),返回 background 形状
|
|
597
604
|
expect(handle.mode).toBe("background");
|
|
598
605
|
|
|
599
606
|
// createRecordForMode 生成的 subagentId 带 sa- 前缀(sa-<uuid>)
|
|
@@ -670,7 +677,7 @@ describe("SubagentService", () => {
|
|
|
670
677
|
injectRunningBackground(service, "bg-running-1");
|
|
671
678
|
injectRunningBackground(service, "bg-running-2");
|
|
672
679
|
const terminal = injectRunningBackground(service, "bg-done");
|
|
673
|
-
terminal.status = "
|
|
680
|
+
terminal.status = "closed"; // 模拟终态,dispose 不应为其 emit
|
|
674
681
|
|
|
675
682
|
service.dispose();
|
|
676
683
|
|
|
@@ -704,7 +711,7 @@ describe("ModelConfigService ctxModel 缓存", () => {
|
|
|
704
711
|
agentDir = makeTmpAgentDir();
|
|
705
712
|
});
|
|
706
713
|
afterEach(() => {
|
|
707
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
714
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
708
715
|
});
|
|
709
716
|
|
|
710
717
|
/** 最小 mock registry:空可用列表(ctxModel 路径不需要 lookup)。 */
|