@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,117 @@
|
|
|
1
|
+
// src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts
|
|
2
|
+
//
|
|
3
|
+
// [LC-5/T6①] armIdleTimer 超时回调身份比对(按值守卫,对齐 session-runner
|
|
4
|
+
// removeChildRegistration 先例)。
|
|
5
|
+
//
|
|
6
|
+
// 覆盖(设计 §4.3 LC-5 + §11-2):
|
|
7
|
+
// 1. 「旧 timer 已到期、回调迟到执行」与「同 recordId re-arm」精确交错 → 守卫不误删
|
|
8
|
+
// 新条目(旧代码此处误删 = 新 timer 脱管、disarm 失效 → turn 中途误杀)。
|
|
9
|
+
// 2. 无交错时旧 timer 到点删除自己的条目 + 后续 re-arm 正常(既有语义不回归)。
|
|
10
|
+
// 3. fake timers 下「re-arm 发生在旧 timer 到期回调处置中」的同轮交错端到端:
|
|
11
|
+
// 新 timer 存活、到点正常触发、Map 清理干净。
|
|
12
|
+
//
|
|
13
|
+
// 交错构造说明(§11-2 勘误锚点):sinon fake clock 的同步 tick 模型在「时间推进」的
|
|
14
|
+
// 同一同步流程内立即执行到期回调,不存在「已到期、回调已入队、尚未执行」的中间态
|
|
15
|
+
// 暴露给 clearTimeout——纯 fake timers 无法表达 Node 真实窗口(fire 后回调入
|
|
16
|
+
// macrotask 队列,clearTimeout 不可撤销)。用例 1/2 以手动排程 fake(stub setTimeout
|
|
17
|
+
// 捕获回调、测试显式控制 fire 时机)精确复现该中间态;用例 3 补真实 clock 语义下的
|
|
18
|
+
// 同轮交错端到端回归。
|
|
19
|
+
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
_resetLifecycleState,
|
|
24
|
+
armIdleTimer,
|
|
25
|
+
disarmIdleTimer,
|
|
26
|
+
hasIdleTimer,
|
|
27
|
+
} from "../lifecycle-manager.ts";
|
|
28
|
+
|
|
29
|
+
/** 手动排程 fake 捕获的到期回调(fire = 模拟 timer 到点,时机由用例显式控制)。 */
|
|
30
|
+
let scheduled: Array<{ fire: () => void; timer: object }>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
_resetLifecycleState();
|
|
34
|
+
scheduled = [];
|
|
35
|
+
// 手动排程模型:setTimeout 只登记不排程;clearTimeout no-op(「已入队回调不可撤销」
|
|
36
|
+
// 的真实语义由用例不 fire 被撤销者来表达)。
|
|
37
|
+
vi.stubGlobal(
|
|
38
|
+
"setTimeout",
|
|
39
|
+
(cb: () => void, _delay?: number) => {
|
|
40
|
+
void _delay;
|
|
41
|
+
const timer: object = {};
|
|
42
|
+
scheduled.push({ fire: cb, timer });
|
|
43
|
+
return Object.assign(timer, { unref() {} });
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
vi.stubGlobal("clearTimeout", (_t?: unknown) => {
|
|
47
|
+
void _t;
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
_resetLifecycleState();
|
|
53
|
+
vi.unstubAllGlobals();
|
|
54
|
+
vi.useRealTimers();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("[LC-5] armIdleTimer 超时回调身份比对", () => {
|
|
58
|
+
it("旧 timer 到点回调迟到执行(re-arm 已覆盖条目)→ 守卫不误删新条目,disarm 仍有效", () => {
|
|
59
|
+
const onTimeoutA = vi.fn();
|
|
60
|
+
const onTimeoutB = vi.fn();
|
|
61
|
+
|
|
62
|
+
armIdleTimer("sa-x", onTimeoutA, 1000); // timerA 入手 排程表,Map: timerA
|
|
63
|
+
// re-arm(agent_settled 刷新):disarm 旧 timer(真实 Node 中 clearTimeout 对已
|
|
64
|
+
// 入队回调无效)→ timerB 入表,Map: timerB。此后 timerA 的回调才迟到执行。
|
|
65
|
+
armIdleTimer("sa-x", onTimeoutB, 1000);
|
|
66
|
+
expect(hasIdleTimer("sa-x")).toBe(true);
|
|
67
|
+
|
|
68
|
+
scheduled[0]?.fire(); // 旧 timerA 回调迟到执行
|
|
69
|
+
|
|
70
|
+
expect(onTimeoutA).toHaveBeenCalledTimes(1); // 旧超时回调本身照常触发
|
|
71
|
+
expect(hasIdleTimer("sa-x")).toBe(true); // [核心] 新条目未被误删(旧代码此处 false = 脱管)
|
|
72
|
+
|
|
73
|
+
disarmIdleTimer("sa-x"); // B 未脱管:disarm 有效
|
|
74
|
+
expect(hasIdleTimer("sa-x")).toBe(false);
|
|
75
|
+
expect(onTimeoutB).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("无交错:旧 timer 到点删除自己的条目(守卫不改变既有语义),后续 re-arm 正常", () => {
|
|
79
|
+
const onTimeoutA = vi.fn();
|
|
80
|
+
const onTimeoutB = vi.fn();
|
|
81
|
+
|
|
82
|
+
armIdleTimer("sa-y", onTimeoutA, 1000);
|
|
83
|
+
expect(hasIdleTimer("sa-y")).toBe(true);
|
|
84
|
+
|
|
85
|
+
scheduled[0]?.fire(); // 正常到点:Map 中是自己 → delete → 回调
|
|
86
|
+
|
|
87
|
+
expect(onTimeoutA).toHaveBeenCalledTimes(1);
|
|
88
|
+
expect(hasIdleTimer("sa-y")).toBe(false); // 旧条目删除正常(不残留失效 entry)
|
|
89
|
+
|
|
90
|
+
armIdleTimer("sa-y", onTimeoutB, 1000); // 后续 re-arm 正常
|
|
91
|
+
expect(hasIdleTimer("sa-y")).toBe(true);
|
|
92
|
+
scheduled[1]?.fire();
|
|
93
|
+
expect(onTimeoutB).toHaveBeenCalledTimes(1);
|
|
94
|
+
expect(hasIdleTimer("sa-y")).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("fake timers 同轮交错:re-arm 发生在旧 timer 到期回调处置中 → 新 timer 存活到点触发", () => {
|
|
98
|
+
vi.useFakeTimers();
|
|
99
|
+
const onTimeoutB = vi.fn();
|
|
100
|
+
const onTimeoutA = vi.fn(() => {
|
|
101
|
+
// 到期回调处置中同步 re-arm(同轮交错的 fake-timer 可达形态)
|
|
102
|
+
armIdleTimer("sa-z", onTimeoutB, 1000);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
armIdleTimer("sa-z", onTimeoutA, 1000);
|
|
106
|
+
vi.advanceTimersByTime(1000); // A 到点:删自己 → onTimeoutA → re-arm B
|
|
107
|
+
|
|
108
|
+
expect(onTimeoutA).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(hasIdleTimer("sa-z")).toBe(true); // B armed,交错不丢 timer
|
|
110
|
+
|
|
111
|
+
vi.advanceTimersByTime(999);
|
|
112
|
+
expect(onTimeoutB).not.toHaveBeenCalled(); // B 从 arm 时刻重新计时
|
|
113
|
+
vi.advanceTimersByTime(1);
|
|
114
|
+
expect(onTimeoutB).toHaveBeenCalledTimes(1); // B 存活到点正常触发
|
|
115
|
+
expect(hasIdleTimer("sa-z")).toBe(false); // 触发后 Map 清理干净
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
11
|
|
|
12
|
+
// [LC-7/T7①] Mock 共享 logger:env 非法值回落默认的 warn 留痕可被断言
|
|
13
|
+
//(对齐 channel-registry-handshake.test.ts 模式;vi.mock 自动 hoist 到 import 前)。
|
|
14
|
+
const { loggerMock } = vi.hoisted(() => ({
|
|
15
|
+
loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
16
|
+
}));
|
|
17
|
+
vi.mock("../../core/logger.ts", () => ({
|
|
18
|
+
getLogger: () => loggerMock,
|
|
19
|
+
}));
|
|
20
|
+
|
|
12
21
|
import {
|
|
13
22
|
DEFAULT_IDLE_TIMEOUT_MS,
|
|
14
23
|
DEFAULT_MAX_ALIVE_PROCESSES,
|
|
@@ -30,6 +39,9 @@ describe("lifecycle-manager — V2 §5.2 模块 1", () => {
|
|
|
30
39
|
beforeEach(() => {
|
|
31
40
|
vi.useFakeTimers();
|
|
32
41
|
_resetLifecycleState();
|
|
42
|
+
loggerMock.debug.mockClear();
|
|
43
|
+
loggerMock.warn.mockClear();
|
|
44
|
+
loggerMock.error.mockClear();
|
|
33
45
|
// [F-4 同源修复] env 隔离:「默认超时」用例依赖 XYZ_SUBAGENT_IDLE_TIMEOUT_MS
|
|
34
46
|
// 未设基线(getEnvIdleTimeoutMs 会覆盖 DEFAULT),宿主 export 即假红。空串 = 未设。
|
|
35
47
|
vi.stubEnv("XYZ_SUBAGENT_IDLE_TIMEOUT_MS", "");
|
|
@@ -138,6 +150,33 @@ describe("lifecycle-manager — V2 §5.2 模块 1", () => {
|
|
|
138
150
|
expect(() => armIdleTimer("sa-env-overflow", onTimeout)).toThrowError(/2147483647/);
|
|
139
151
|
expect(hasIdleTimer("sa-env-overflow")).toBe(false);
|
|
140
152
|
});
|
|
153
|
+
|
|
154
|
+
// [LC-7/T7①] env 非法值回落默认不再静默:warn 留痕(env 名 + 实际值 + 生效行为)。
|
|
155
|
+
// 「以为设了极长保活、实际回落 5min」的语义漂移必须可诊断。
|
|
156
|
+
it("[LC-7] env 非法值('30m' 非纯数字)→ 回落 DEFAULT_IDLE_TIMEOUT_MS 且 warn 留痕", () => {
|
|
157
|
+
vi.stubEnv("XYZ_SUBAGENT_IDLE_TIMEOUT_MS", "30m");
|
|
158
|
+
const onTimeout = vi.fn();
|
|
159
|
+
armIdleTimer("sa-lc7-invalid", onTimeout); // 不传 timeoutMs → env(非法)→ DEFAULT
|
|
160
|
+
|
|
161
|
+
// 生效行为 = 默认 5min(非法值没有按字面 '30m' 也不按禁用处理)
|
|
162
|
+
vi.advanceTimersByTime(DEFAULT_IDLE_TIMEOUT_MS - 1);
|
|
163
|
+
expect(onTimeout).not.toHaveBeenCalled();
|
|
164
|
+
vi.advanceTimersByTime(1);
|
|
165
|
+
expect(onTimeout).toHaveBeenCalledTimes(1);
|
|
166
|
+
|
|
167
|
+
expect(loggerMock.warn).toHaveBeenCalledTimes(1);
|
|
168
|
+
const msg = String(loggerMock.warn.mock.calls[0]?.[0] ?? "");
|
|
169
|
+
expect(msg).toContain("XYZ_SUBAGENT_IDLE_TIMEOUT_MS"); // env 变量名
|
|
170
|
+
expect(msg).toContain("30m"); // 实际值
|
|
171
|
+
expect(msg).toContain("DEFAULT_IDLE_TIMEOUT_MS"); // 生效行为:回落默认
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("[LC-7] env 未设(空串基线)→ 静默走 DEFAULT,零 warn(未配置不是异常)", () => {
|
|
175
|
+
const onTimeout = vi.fn();
|
|
176
|
+
armIdleTimer("sa-lc7-clean", onTimeout);
|
|
177
|
+
expect(hasIdleTimer("sa-lc7-clean")).toBe(true);
|
|
178
|
+
expect(loggerMock.warn).not.toHaveBeenCalled();
|
|
179
|
+
});
|
|
141
180
|
});
|
|
142
181
|
|
|
143
182
|
// ============================================================
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
disarmIdleTimer,
|
|
11
11
|
_resetLifecycleState,
|
|
12
12
|
} from "../lifecycle-manager.ts";
|
|
13
|
-
import { spawnedChildren } from "../session-runner.ts";
|
|
13
|
+
import { spawnedChildren } from "../engine/engines/pi/session-runner.ts";
|
|
14
14
|
import type { ChildProcess } from "node:child_process";
|
|
15
15
|
import type { ExecutionRecord } from "../types.ts";
|
|
16
16
|
|
|
@@ -25,7 +25,6 @@ function makeRecord(overrides: Partial<ExecutionRecord> = {}): ExecutionRecord {
|
|
|
25
25
|
thinkingLevel: undefined,
|
|
26
26
|
mode: "background",
|
|
27
27
|
task: "test",
|
|
28
|
-
slug: "test",
|
|
29
28
|
startedAt: Date.now(),
|
|
30
29
|
rootSessionId: "root",
|
|
31
30
|
parentRecordId: undefined,
|
|
@@ -22,7 +22,7 @@ function makeBaseManifest(overrides: Partial<ManifestRecord> = {}): ManifestReco
|
|
|
22
22
|
id: "rec-test",
|
|
23
23
|
rootSessionId: "session-main",
|
|
24
24
|
agentName: "worker",
|
|
25
|
-
status: "
|
|
25
|
+
status: "closed",
|
|
26
26
|
createdAt: 1000,
|
|
27
27
|
...overrides,
|
|
28
28
|
};
|
|
@@ -38,7 +38,7 @@ describe("ManifestStore — parentRecordId 落盘 (M3a)", () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
afterEach(() => {
|
|
41
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
41
|
+
fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
// ── TC-m3a-new-record-parentid ──
|
|
@@ -71,7 +71,7 @@ describe("ManifestStore — parentRecordId 落盘 (M3a)", () => {
|
|
|
71
71
|
id: "rec-old",
|
|
72
72
|
rootSessionId: "session-main",
|
|
73
73
|
agentName: "worker",
|
|
74
|
-
status: "
|
|
74
|
+
status: "closed",
|
|
75
75
|
createdAt: 2000,
|
|
76
76
|
// 无 parentRecordId —— 旧版本写的
|
|
77
77
|
};
|
|
@@ -104,7 +104,7 @@ describe("ManifestStore — parentRecordId 落盘 (M3a)", () => {
|
|
|
104
104
|
id: "rec-minimal",
|
|
105
105
|
rootSessionId: "session-main",
|
|
106
106
|
agentName: "worker",
|
|
107
|
-
status: "
|
|
107
|
+
status: "closed",
|
|
108
108
|
createdAt: 3000,
|
|
109
109
|
};
|
|
110
110
|
fs.writeFileSync(
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/execution/__tests__/manifest-store-tmp-recovery.test.ts
|
|
2
|
+
//
|
|
3
|
+
// [u-svc / T5④ / PS-13] recoverTmpFiles 循环内 per-file 容错:
|
|
4
|
+
// 单个 tmp 文件操作失败(ENOENT——并发回收/外部清理抢先)只 warn + 跳过,
|
|
5
|
+
// 不再中断整轮——剩余 tmp 继续处理;返回值形态不变(跳过者不计数)。
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
|
|
9
|
+
const { loggerMock } = vi.hoisted(() => ({
|
|
10
|
+
loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
11
|
+
}));
|
|
12
|
+
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
13
|
+
|
|
14
|
+
// fs partial mock:unlinkSync 可按路径注错,其余转发真实实现。
|
|
15
|
+
const { unlinkSyncMock } = vi.hoisted(() => ({ unlinkSyncMock: vi.fn() }));
|
|
16
|
+
vi.mock("node:fs", async (importOriginal) => {
|
|
17
|
+
const actual = await importOriginal<typeof import("node:fs")>();
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
unlinkSync: unlinkSyncMock,
|
|
21
|
+
default: { ...actual, unlinkSync: unlinkSyncMock },
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
import * as os from "node:os";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
|
|
29
|
+
import { ManifestStore } from "../manifest-store.ts";
|
|
30
|
+
|
|
31
|
+
const VALID_MANIFEST = {
|
|
32
|
+
id: "sa-good",
|
|
33
|
+
rootSessionId: "root-1",
|
|
34
|
+
agentName: "general-purpose",
|
|
35
|
+
createdAt: 1,
|
|
36
|
+
status: "running",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("T5④ recoverTmpFiles per-file tolerance", () => {
|
|
40
|
+
let dir: string;
|
|
41
|
+
let store: ManifestStore;
|
|
42
|
+
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), "manifest-tmp-recovery-"));
|
|
45
|
+
store = new ManifestStore(dir);
|
|
46
|
+
// 真实 unlinkSync(mock 模块对象上的 unlinkSync 已被替换,不能自引用转发)
|
|
47
|
+
const actualFs = await vi.importActual<typeof import("node:fs")>("node:fs");
|
|
48
|
+
unlinkSyncMock.mockReset();
|
|
49
|
+
unlinkSyncMock.mockImplementation((p: fs.PathLike) => actualFs.unlinkSync(p));
|
|
50
|
+
loggerMock.warn.mockClear();
|
|
51
|
+
loggerMock.error.mockClear();
|
|
52
|
+
loggerMock.debug.mockClear();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
vi.restoreAllMocks();
|
|
57
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("processes remaining tmp files when one unlink fails mid-loop", async () => {
|
|
61
|
+
const actualFs = await vi.importActual<typeof import("node:fs")>("node:fs");
|
|
62
|
+
// 三个 tmp:good(合法 → promote)、stale(manifest sa-stale.json 已存在 → 删)、
|
|
63
|
+
// doomed(unlink ENOENT)
|
|
64
|
+
fs.writeFileSync(path.join(dir, "sa-good.json.tmp.111"), JSON.stringify(VALID_MANIFEST));
|
|
65
|
+
fs.writeFileSync(path.join(dir, "sa-stale.json"), JSON.stringify(VALID_MANIFEST));
|
|
66
|
+
fs.writeFileSync(path.join(dir, "sa-stale.json.tmp.222"), "{}");
|
|
67
|
+
fs.writeFileSync(path.join(dir, "sa-doomed.json.tmp.333"), "not json");
|
|
68
|
+
const doomedPath = path.join(dir, "sa-doomed.json.tmp.333");
|
|
69
|
+
unlinkSyncMock.mockImplementation((p: fs.PathLike) => {
|
|
70
|
+
if (String(p) === doomedPath) {
|
|
71
|
+
const err = new Error("ENOENT: file vanished") as NodeJS.ErrnoException;
|
|
72
|
+
err.code = "ENOENT";
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
return actualFs.unlinkSync(p);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await store.recoverTmpFiles();
|
|
79
|
+
|
|
80
|
+
// 整轮不中断:doomed 之外的 tmp 全部处理完
|
|
81
|
+
expect(result.recovered).toBe(1); // good promote
|
|
82
|
+
expect(result.deleted).toBe(1); // stale 删
|
|
83
|
+
// good 被提升为正式 manifest
|
|
84
|
+
expect(fs.existsSync(path.join(dir, "sa-good.json"))).toBe(true);
|
|
85
|
+
// 失败留痕(warn 级,含文件名)
|
|
86
|
+
expect(loggerMock.warn).toHaveBeenCalledWith(
|
|
87
|
+
expect.stringContaining("sa-doomed.json.tmp.333"),
|
|
88
|
+
expect.objectContaining({ detail: expect.stringContaining("ENOENT") }),
|
|
89
|
+
);
|
|
90
|
+
expect(loggerMock.warn).toHaveBeenCalledWith(expect.stringContaining("1 of 3 tmp file(s)"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("returns counts unchanged and no warnings when all tmp files succeed", async () => {
|
|
94
|
+
fs.writeFileSync(path.join(dir, "sa-ok.json.tmp.444"), JSON.stringify(VALID_MANIFEST));
|
|
95
|
+
const result = await store.recoverTmpFiles();
|
|
96
|
+
expect(result).toEqual({ deleted: 0, recovered: 1 });
|
|
97
|
+
expect(loggerMock.warn).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/execution/__tests__/max-turns-to-watchdog-ms.test.ts
|
|
2
|
+
//
|
|
3
|
+
// U3(subagent-core-sink-design):computeWatchdogMs 语义化更名 maxTurnsToWatchdogMs
|
|
4
|
+
// + floor 语义文档化。本文件锚定换算纯函数语义:
|
|
5
|
+
// - floor 断言(设计 §4 S2):maxTurnsToWatchdogMs(2) >= 1_800_000——zsw 曾自实现
|
|
6
|
+
// 无 floor 版本致 maxTurns=2 被 10min 误杀(设计 §2.1 例 1),floor 是两宿主
|
|
7
|
+
// 预算一致性的锚点(G1)。
|
|
8
|
+
// - 边界:0.5 轮 / 缺省(不挂 watchdog)/ 极大值。
|
|
9
|
+
//
|
|
10
|
+
// 纯函数换算,无 timer 参与,无需 fake timers。
|
|
11
|
+
import { describe, expect, it, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
// [LC-7/T7①] Mock 共享 logger:非法 env 值的 warn 留痕可被断言
|
|
14
|
+
//(对齐 channel-registry-handshake.test.ts 模式;vi.mock 自动 hoist 到 import 前)。
|
|
15
|
+
const { loggerMock } = vi.hoisted(() => ({
|
|
16
|
+
loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
17
|
+
}));
|
|
18
|
+
vi.mock("../../core/logger.ts", () => ({
|
|
19
|
+
getLogger: () => loggerMock,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
maxTurnsToWatchdogMs,
|
|
24
|
+
resolveSpawnWatchdogMs,
|
|
25
|
+
SPAWN_WATCHDOG_ENV,
|
|
26
|
+
} from "../engine/engines/pi/session-runner.ts";
|
|
27
|
+
|
|
28
|
+
const MIN_MS = 60_000;
|
|
29
|
+
/** watchdog floor(session-runner WATCHDOG_FLOOR_MINUTES=30 的毫秒值)。 */
|
|
30
|
+
const FLOOR_MS = 30 * MIN_MS;
|
|
31
|
+
/** Node setTimeout delay 安全域上界(>2^31-1 被置 1ms 立即触发,assertSafeTimerDelay 拦截域)。 */
|
|
32
|
+
const TIMER_SAFE_MAX = 2 ** 31 - 1;
|
|
33
|
+
|
|
34
|
+
describe("maxTurnsToWatchdogMs(U3 watchdog 换算,floor 语义)", () => {
|
|
35
|
+
it("floor 断言(S2):maxTurns=2 → >= 1_800_000(floor 生效,非 2×5=10min)", () => {
|
|
36
|
+
expect(maxTurnsToWatchdogMs(2)).toBeGreaterThanOrEqual(1_800_000);
|
|
37
|
+
// 精确值锚定:floor 是 max 不是「至少再加余量」
|
|
38
|
+
expect(maxTurnsToWatchdogMs(2)).toBe(FLOOR_MS);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("0.5 轮(小数)→ floor 30min(不出现 2.5min 的短 watchdog)", () => {
|
|
42
|
+
expect(maxTurnsToWatchdogMs(0.5)).toBe(FLOOR_MS);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("maxTurns=6 → 恰为 floor 临界(max(30min, 30min))", () => {
|
|
46
|
+
expect(maxTurnsToWatchdogMs(6)).toBe(FLOOR_MS);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("线性段:maxTurns=20 → 100min;maxTurns=100 → 500min(M-1 长任务不被 30min 误杀)", () => {
|
|
50
|
+
expect(maxTurnsToWatchdogMs(20)).toBe(100 * MIN_MS);
|
|
51
|
+
expect(maxTurnsToWatchdogMs(100)).toBe(500 * MIN_MS);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("极大值 → 保持纯算术结果(> 2^31-1 溢出域;挂载前由 assertSafeTimerDelay fail-fast 拦截)", () => {
|
|
55
|
+
expect(maxTurnsToWatchdogMs(Number.MAX_SAFE_INTEGER)).toBeGreaterThan(TIMER_SAFE_MAX);
|
|
56
|
+
expect(maxTurnsToWatchdogMs(Number.POSITIVE_INFINITY)).toBe(Number.POSITIVE_INFINITY);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("上层防线:换算链路入口对极大值 fail-fast(resolveSpawnWatchdogMs(Infinity) 抛出)", () => {
|
|
60
|
+
expect(() => resolveSpawnWatchdogMs(Number.POSITIVE_INFINITY)).toThrow();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("缺省(maxTurns 未传/null 且 env 未设)→ resolveSpawnWatchdogMs 返回 undefined(不挂 watchdog)", () => {
|
|
64
|
+
// 空串与未设同义(getEnvSpawnWatchdogMs 对 falsy raw 返回 undefined);
|
|
65
|
+
// 显式 stub 防御宿主 shell export 污染(vitest.setup.ts 已在模块加载前净化)。
|
|
66
|
+
vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
|
|
67
|
+
try {
|
|
68
|
+
expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
|
|
69
|
+
expect(resolveSpawnWatchdogMs(null)).toBeUndefined();
|
|
70
|
+
} finally {
|
|
71
|
+
vi.unstubAllEnvs();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// [LC-7/T7①] env 非法值不再静默失效:warn 留痕(env 名 + 实际值 + 生效行为)。
|
|
76
|
+
it("[LC-7] env 设非法值('30m' 非纯数字)→ undefined(不挂 watchdog)且 warn 留痕", () => {
|
|
77
|
+
vi.stubEnv(SPAWN_WATCHDOG_ENV, "30m");
|
|
78
|
+
try {
|
|
79
|
+
expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
|
|
80
|
+
expect(loggerMock.warn).toHaveBeenCalledTimes(1);
|
|
81
|
+
const msg = String(loggerMock.warn.mock.calls[0]?.[0] ?? "");
|
|
82
|
+
expect(msg).toContain("XYZ_SUBAGENT_SPAWN_WATCHDOG_MS"); // env 变量名
|
|
83
|
+
expect(msg).toContain("30m"); // 实际值
|
|
84
|
+
expect(msg).toContain("NOT armed"); // 生效行为:等价关闭,运维可诊断「以为有兜底、实际裸奔」
|
|
85
|
+
} finally {
|
|
86
|
+
loggerMock.warn.mockClear();
|
|
87
|
+
vi.unstubAllEnvs();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("[LC-7] env 设 <=0 → 同样 warn 留痕(不挂 watchdog,语义与非法值一致可见)", () => {
|
|
92
|
+
vi.stubEnv(SPAWN_WATCHDOG_ENV, "-5");
|
|
93
|
+
try {
|
|
94
|
+
expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
|
|
95
|
+
expect(loggerMock.warn).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(String(loggerMock.warn.mock.calls[0]?.[0] ?? "")).toContain("-5");
|
|
97
|
+
} finally {
|
|
98
|
+
loggerMock.warn.mockClear();
|
|
99
|
+
vi.unstubAllEnvs();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("[LC-7] env 未设(空串)→ 静默 undefined,零 warn(未配置不是异常,不制造噪音)", () => {
|
|
104
|
+
vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
|
|
105
|
+
try {
|
|
106
|
+
expect(resolveSpawnWatchdogMs(undefined)).toBeUndefined();
|
|
107
|
+
expect(loggerMock.warn).not.toHaveBeenCalled();
|
|
108
|
+
} finally {
|
|
109
|
+
loggerMock.warn.mockClear();
|
|
110
|
+
vi.unstubAllEnvs();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -422,12 +422,12 @@ describe("ModelConfigService: ctx.model plumb-through (EA-4)", () => {
|
|
|
422
422
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subagents-resolver-test-"));
|
|
423
423
|
});
|
|
424
424
|
afterEach(() => {
|
|
425
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
425
|
+
fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
426
426
|
});
|
|
427
427
|
|
|
428
428
|
/** 构造已 initModel 的 ModelConfigService,ctxModel 注入缓存。 */
|
|
429
429
|
function makeService(ctxModel: ModelInfo | undefined, registry: ModelRegistryLike = makeRegistry([])): ModelConfigService {
|
|
430
|
-
const svc = new ModelConfigService({ agentDir: tmpDir });
|
|
430
|
+
const svc = new ModelConfigService({ agentDir: tmpDir, cwd: tmpDir });
|
|
431
431
|
svc.initModel({
|
|
432
432
|
modelRegistry: registry,
|
|
433
433
|
sessionId: "test-session",
|
|
@@ -9,96 +9,28 @@
|
|
|
9
9
|
// TC-3d: 子进程 execute 创建的 record.rootSessionId = env 贯穿的真 ROOT
|
|
10
10
|
// TC-3e: 子进程 collectRecords 用 sessionRootId 过滤
|
|
11
11
|
//
|
|
12
|
-
// mock 策略与 recursive-visibility-baseline.test.ts
|
|
13
|
-
|
|
14
|
-
import type { PassThrough } from "node:stream";
|
|
12
|
+
// mock 策略与 recursive-visibility-baseline.test.ts 一致(已收敛
|
|
13
|
+
// ./helpers/subagent-service-mocks.ts 四文件共享单源)。
|
|
15
14
|
|
|
16
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
stdout = new PassThrough();
|
|
27
|
-
stderr = new PassThrough();
|
|
28
|
-
killed = false;
|
|
29
|
-
killSignal: string | undefined;
|
|
30
|
-
kill(sig?: string): boolean {
|
|
31
|
-
this.killed = true;
|
|
32
|
-
this.killSignal = sig;
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
spawn: vi.fn(() => new FakeChild()),
|
|
39
|
-
// buildEnvBlock 用 execFile 异步取 git branch:默认 err-first 兜底(catch → branch="")
|
|
40
|
-
execFile: vi.fn(
|
|
41
|
-
(
|
|
42
|
-
_cmd: string,
|
|
43
|
-
_args: readonly string[],
|
|
44
|
-
_opts: unknown,
|
|
45
|
-
cb: (err: Error | null, stdout?: string, stderr?: string) => void,
|
|
46
|
-
) => cb(new Error("execFile not configured in this test")),
|
|
47
|
-
),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
17
|
+
import {
|
|
18
|
+
aliveStoreModule,
|
|
19
|
+
childProcessModule,
|
|
20
|
+
finalizedMarkerModule,
|
|
21
|
+
fsSyncModule,
|
|
22
|
+
manifestStoreModule,
|
|
23
|
+
tempPromptModule,
|
|
24
|
+
} from "./helpers/subagent-service-mocks.ts";
|
|
50
25
|
|
|
51
|
-
|
|
52
|
-
const actual = await import("node:fs");
|
|
53
|
-
return {
|
|
54
|
-
default: {
|
|
55
|
-
...actual,
|
|
56
|
-
mkdirSync: vi.fn(),
|
|
57
|
-
existsSync: vi.fn(() => false),
|
|
58
|
-
appendFileSync: vi.fn(),
|
|
59
|
-
writeFileSync: vi.fn(),
|
|
60
|
-
readdirSync: vi.fn(() => []),
|
|
61
|
-
},
|
|
62
|
-
mkdirSync: vi.fn(),
|
|
63
|
-
existsSync: vi.fn(() => false),
|
|
64
|
-
appendFileSync: vi.fn(),
|
|
65
|
-
writeFileSync: vi.fn(),
|
|
66
|
-
readdirSync: vi.fn(() => []),
|
|
67
|
-
promises: actual.promises,
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
vi.mock("../alive-store.ts", async (importOriginal) => {
|
|
72
|
-
const actual = await importOriginal<typeof import("../alive-store.ts")>();
|
|
73
|
-
return {
|
|
74
|
-
...actual,
|
|
75
|
-
writeAliveMarker: vi.fn(),
|
|
76
|
-
removeAliveMarker: vi.fn(),
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
vi.mock("../finalized-marker.ts", () => ({
|
|
81
|
-
writeFinalized: vi.fn(),
|
|
82
|
-
readFinalized: vi.fn(() => false),
|
|
83
|
-
}));
|
|
84
|
-
|
|
85
|
-
vi.mock("../manifest-store.ts", () => {
|
|
86
|
-
class FakeManifestStore {
|
|
87
|
-
writeManifest = vi.fn(async () => {});
|
|
88
|
-
readManifest = vi.fn(async () => null);
|
|
89
|
-
listAllSync = vi.fn(() => []);
|
|
90
|
-
recoverTmpFiles = vi.fn(async () => []);
|
|
91
|
-
}
|
|
92
|
-
return { ManifestStore: vi.fn(function (_recordsDir: string) { return new FakeManifestStore(); }) };
|
|
93
|
-
});
|
|
26
|
+
// ── mock modules ──
|
|
94
27
|
|
|
95
|
-
vi.mock("
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}));
|
|
28
|
+
vi.mock("node:child_process", () => childProcessModule());
|
|
29
|
+
vi.mock("node:fs", async (importOriginal) => fsSyncModule(await importOriginal<typeof import("node:fs")>()));
|
|
30
|
+
vi.mock("../alive-store.ts", async (importOriginal) => aliveStoreModule(await importOriginal<typeof import("../alive-store.ts")>()));
|
|
31
|
+
vi.mock("../finalized-marker.ts", () => finalizedMarkerModule());
|
|
32
|
+
vi.mock("../manifest-store.ts", () => manifestStoreModule());
|
|
33
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => tempPromptModule());
|
|
102
34
|
|
|
103
35
|
import { spawn } from "node:child_process";
|
|
104
36
|
|
|
@@ -119,42 +51,10 @@ const ENV_ROOT_CWD = "PI_SUBAGENT_ROOT_CWD";
|
|
|
119
51
|
|
|
120
52
|
// ── helpers ──
|
|
121
53
|
|
|
122
|
-
interface FakeChild {
|
|
123
|
-
pid: number;
|
|
124
|
-
stdout: PassThrough;
|
|
125
|
-
stderr: PassThrough;
|
|
126
|
-
killed: boolean;
|
|
127
|
-
killSignal: string | undefined;
|
|
128
|
-
kill(sig?: string): boolean;
|
|
129
|
-
emit(event: string, ...args: unknown[]): boolean;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function lastSpawnedChild(): FakeChild {
|
|
133
|
-
const result = mockSpawn.mock.results.at(-1);
|
|
134
|
-
if (!result) throw new Error("spawn was not called yet");
|
|
135
|
-
return result.value as FakeChild;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
54
|
function getLastSpawnEnv(): Record<string, string | undefined> {
|
|
139
55
|
return (mockSpawn.mock.calls.at(-1)?.[2]?.env as Record<string, string | undefined>) ?? {};
|
|
140
56
|
}
|
|
141
57
|
|
|
142
|
-
function sessionHeader(id = "env-prop-session"): Record<string, unknown> {
|
|
143
|
-
return { type: "session", id, timestamp: "2026-08-11T00-00-00-000Z", cwd: "/tmp/test" };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function emitStdoutLine(child: FakeChild, obj: Record<string, unknown>): void {
|
|
147
|
-
child.stdout.write(`${JSON.stringify(obj)}\n`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function driveChildToCompletion(child: FakeChild, events: Record<string, unknown>[] = []): Promise<void> {
|
|
151
|
-
emitStdoutLine(child, sessionHeader());
|
|
152
|
-
for (const e of events) emitStdoutLine(child, e);
|
|
153
|
-
child.stdout.end();
|
|
154
|
-
child.stderr.end();
|
|
155
|
-
child.emit("close", 0);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
58
|
function makeEmptyRegistry(): ModelRegistryLike {
|
|
159
59
|
return { getAvailable: () => [], find: () => undefined, hasConfiguredAuth: () => true };
|
|
160
60
|
}
|
|
@@ -167,7 +67,7 @@ const ctxModel: ModelInfo = { id: "m", name: "M", provider: "p", reasoning: fals
|
|
|
167
67
|
|
|
168
68
|
function setupService(sessionId: string, env?: Record<string, string>): SubagentService {
|
|
169
69
|
const agentDir = "/tmp/env-prop-it";
|
|
170
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
70
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
171
71
|
modelService.initModel({
|
|
172
72
|
modelRegistry: makeEmptyRegistry(),
|
|
173
73
|
sessionId,
|
|
@@ -225,8 +125,8 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
|
|
|
225
125
|
const sessionRootId = Reflect.get(service, "sessionRootId") as string;
|
|
226
126
|
expect(sessionRootId).toBe("root-main-session");
|
|
227
127
|
|
|
228
|
-
// B
|
|
229
|
-
const baseline = Reflect.get(service, "
|
|
128
|
+
// B 的嵌套基线记录了 B 自己的身份(recordId + depth;[D3-⑤] execNesting 公共层)
|
|
129
|
+
const baseline = (Reflect.get(service, "execNesting") as { baseline(): { recordId: string; depth: number } | null }).baseline()!;
|
|
230
130
|
expect(baseline.recordId).toBe("sa-a-record");
|
|
231
131
|
expect(baseline.depth).toBe(1);
|
|
232
132
|
});
|
|
@@ -241,6 +141,7 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
|
|
|
241
141
|
// execute 创建 record(模拟 B 内创建 C)
|
|
242
142
|
const handle = await service.execute({
|
|
243
143
|
task: "nested task",
|
|
144
|
+
slug: "test",
|
|
244
145
|
ctxModel,
|
|
245
146
|
});
|
|
246
147
|
const store = Reflect.get(service, "store") as RecordStore;
|
|
@@ -268,6 +169,7 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
|
|
|
268
169
|
agent: "worker",
|
|
269
170
|
model: "test/model",
|
|
270
171
|
mode: "background",
|
|
172
|
+
slug: "t",
|
|
271
173
|
task: "deep nested",
|
|
272
174
|
startedAt: Date.now(),
|
|
273
175
|
rootSessionId: "root-main-session",
|
|
@@ -277,7 +179,7 @@ describe("TC-3: PI_SUBAGENT_ROOT_SESSION_ID env 贯穿正确", () => {
|
|
|
277
179
|
store.register(recordC);
|
|
278
180
|
|
|
279
181
|
// collectRecords(不带 filter)用 this.sessionRootId 过滤
|
|
280
|
-
const viaService = service.collectRecords(100);
|
|
182
|
+
const viaService = service.queries.collectRecords(100);
|
|
281
183
|
const ids = viaService.map((r) => r.id);
|
|
282
184
|
|
|
283
185
|
// C 的 record 归 ROOT(rootSessionId=root-main-session),能被子进程查到
|
|
@@ -195,6 +195,7 @@ describe("TC-2: collectRecords 返回全树(含深层)", () => {
|
|
|
195
195
|
agent: "worker",
|
|
196
196
|
model: "test/model",
|
|
197
197
|
mode: "background",
|
|
198
|
+
slug: "t",
|
|
198
199
|
task: "task C",
|
|
199
200
|
startedAt: 3000,
|
|
200
201
|
rootSessionId: ROOT_SESSION,
|
|
@@ -286,6 +287,7 @@ describe("TC-2: collectRecords 返回全树(含深层)", () => {
|
|
|
286
287
|
agent: "worker",
|
|
287
288
|
model: "test/model",
|
|
288
289
|
mode: "background",
|
|
290
|
+
slug: "t",
|
|
289
291
|
task: "task C",
|
|
290
292
|
startedAt: 3000,
|
|
291
293
|
rootSessionId: ROOT_SESSION,
|