@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
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
//
|
|
3
3
|
// registry 专属测试(P1 验收 4):注册/获取/listEngines/hasEngine/
|
|
4
4
|
// 未注册 id 报 engine_not_found(错误文案含已注册清单——错误规格表第 1 行契约)。
|
|
5
|
+
// [R1 D6] 追加:重注册先 dispose 旧单例(D6②)+ disposeEngines 收割遍历(D6③)。
|
|
5
6
|
|
|
6
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
8
|
|
|
8
9
|
import type { EnginePort, RunContext } from "../port.ts";
|
|
9
10
|
import {
|
|
10
11
|
clearEngines,
|
|
11
12
|
DEFAULT_ENGINE_ID,
|
|
13
|
+
disposeEngines,
|
|
12
14
|
EngineNotFoundError,
|
|
13
15
|
getEngine,
|
|
14
16
|
hasEngine,
|
|
15
17
|
listEngines,
|
|
16
18
|
registerEngine,
|
|
17
19
|
} from "../registry.ts";
|
|
18
|
-
import type {
|
|
20
|
+
import type { SessionView } from "../types.ts";
|
|
21
|
+
import type { AgentCallOpts } from "../../../orchestration/models/types.ts";
|
|
19
22
|
|
|
20
23
|
/** 最小可运行假引擎(完整实现 EnginePort 五面——不 cast,防接口漂移失检)。 */
|
|
21
24
|
function makeFakeEngine(id: string): EnginePort {
|
|
@@ -32,10 +35,11 @@ function makeFakeEngine(id: string): EnginePort {
|
|
|
32
35
|
resume: "unsupported",
|
|
33
36
|
interrupt: "kill-only",
|
|
34
37
|
permissionMode: "fixed",
|
|
38
|
+
maxTurns: false,
|
|
35
39
|
}),
|
|
36
40
|
probe: () =>
|
|
37
41
|
Promise.resolve({ ok: true, engineVersion: "0.0.0-test", checks: [{ name: "stub", ok: true }] }),
|
|
38
|
-
run: (_task:
|
|
42
|
+
run: (_task: AgentCallOpts, _ctx: RunContext) =>
|
|
39
43
|
Promise.reject(new Error("fake engine: run not implemented")),
|
|
40
44
|
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
|
|
41
45
|
read: (_handle: Parameters<EnginePort["read"]>[0]): Promise<SessionView> =>
|
|
@@ -117,4 +121,91 @@ describe("engine registry", () => {
|
|
|
117
121
|
it("DEFAULT_ENGINE_ID 缺省为 'pi'(D9:回填期零风险默认)", () => {
|
|
118
122
|
expect(DEFAULT_ENGINE_ID).toBe("pi");
|
|
119
123
|
});
|
|
124
|
+
|
|
125
|
+
// ── [R1 D6②] 重注册同名:先 dispose 已实例化的旧单例(防常驻资源泄漏)──
|
|
126
|
+
|
|
127
|
+
it("重注册同名:已实例化的旧单例 dispose 被调用一次,新工厂实例生效", () => {
|
|
128
|
+
const dispose = vi.fn(() => Promise.resolve());
|
|
129
|
+
registerEngine("fake", () => ({ ...makeFakeEngine("fake"), dispose }));
|
|
130
|
+
getEngine("fake"); // 实例化旧引擎(未实例化 = 无常驻资源可回收)
|
|
131
|
+
registerEngine("fake", () => makeFakeEngine("fake-v2"));
|
|
132
|
+
expect(dispose).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(getEngine("fake").id).toBe("fake-v2");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("重注册同名:旧实例 dispose 同步 throw 不阻断替换(best-effort)", () => {
|
|
137
|
+
const dispose = vi.fn(() => {
|
|
138
|
+
throw new Error("dispose boom");
|
|
139
|
+
});
|
|
140
|
+
registerEngine("fake", () => ({ ...makeFakeEngine("fake"), dispose }));
|
|
141
|
+
getEngine("fake");
|
|
142
|
+
expect(() => registerEngine("fake", () => makeFakeEngine("fake-v2"))).not.toThrow();
|
|
143
|
+
expect(dispose).toHaveBeenCalledTimes(1);
|
|
144
|
+
expect(getEngine("fake").id).toBe("fake-v2");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("重注册同名:旧实例 dispose 异步 reject 不阻断替换、不产生 unhandledRejection", async () => {
|
|
148
|
+
const dispose = vi.fn(() => Promise.reject(new Error("dispose async boom")));
|
|
149
|
+
registerEngine("fake", () => ({ ...makeFakeEngine("fake"), dispose }));
|
|
150
|
+
getEngine("fake");
|
|
151
|
+
expect(() => registerEngine("fake", () => makeFakeEngine("fake-v2"))).not.toThrow();
|
|
152
|
+
expect(getEngine("fake").id).toBe("fake-v2");
|
|
153
|
+
// flush macrotask:reject 必须已被 registry 侧 catch 吞掉——否则 vitest 以
|
|
154
|
+
// unhandledRejection 判本文件失败,用例即失效
|
|
155
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("重注册同名:旧引擎仅注册工厂未实例化时不触发 dispose(惰性单例无资源)", () => {
|
|
159
|
+
const dispose = vi.fn(() => Promise.resolve());
|
|
160
|
+
registerEngine("fake", () => ({ ...makeFakeEngine("fake"), dispose }));
|
|
161
|
+
// 不 getEngine——singletons 无记录
|
|
162
|
+
registerEngine("fake", () => makeFakeEngine("fake-v2"));
|
|
163
|
+
expect(dispose).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("重注册同名:旧实例未实现 dispose(可选成员)时直接替换不抛", () => {
|
|
167
|
+
registerEngine("fake", () => makeFakeEngine("fake"));
|
|
168
|
+
getEngine("fake");
|
|
169
|
+
expect(() => registerEngine("fake", () => makeFakeEngine("fake-v2"))).not.toThrow();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ── [R1 D6③] disposeEngines:宿主收割(killAllSpawnedChildren)前的触发遍历 ──
|
|
173
|
+
|
|
174
|
+
describe("disposeEngines(D6③)", () => {
|
|
175
|
+
it("只对已实例化的引擎触发 dispose(绝不实例化未用引擎)", () => {
|
|
176
|
+
const instantiated = vi.fn(() => Promise.resolve());
|
|
177
|
+
const neverInstantiated = vi.fn(() => Promise.resolve());
|
|
178
|
+
registerEngine("used", () => ({ ...makeFakeEngine("used"), dispose: instantiated }));
|
|
179
|
+
registerEngine("unused", () => ({ ...makeFakeEngine("unused"), dispose: neverInstantiated }));
|
|
180
|
+
getEngine("used");
|
|
181
|
+
disposeEngines();
|
|
182
|
+
expect(instantiated).toHaveBeenCalledTimes(1);
|
|
183
|
+
expect(neverInstantiated).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("未实现 dispose 的引擎(可选面)跳过不抛", () => {
|
|
187
|
+
registerEngine("plain", () => makeFakeEngine("plain"));
|
|
188
|
+
getEngine("plain");
|
|
189
|
+
expect(() => disposeEngines()).not.toThrow();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("dispose 幂等(实现承诺):disposeEngines 重复调用不抛、逐次触发", async () => {
|
|
193
|
+
const dispose = vi.fn(() => Promise.resolve());
|
|
194
|
+
registerEngine("fake", () => ({ ...makeFakeEngine("fake"), dispose }));
|
|
195
|
+
getEngine("fake");
|
|
196
|
+
expect(() => disposeEngines()).not.toThrow();
|
|
197
|
+
expect(() => disposeEngines()).not.toThrow();
|
|
198
|
+
// 幂等语义由引擎实现承诺(不变量 4),本断言只验证 registry 重复触发不抛
|
|
199
|
+
expect(dispose).toHaveBeenCalledTimes(2);
|
|
200
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("dispose 后单例保留(run 自动重建归引擎承诺,registry 不删——不变量 4 边界)", () => {
|
|
204
|
+
const dispose = vi.fn(() => Promise.resolve());
|
|
205
|
+
registerEngine("fake", () => ({ ...makeFakeEngine("fake"), dispose }));
|
|
206
|
+
const engine = getEngine("fake");
|
|
207
|
+
disposeEngines();
|
|
208
|
+
expect(getEngine("fake")).toBe(engine);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
120
211
|
});
|
|
@@ -6,8 +6,10 @@ import { describe, expect, it } from "vitest";
|
|
|
6
6
|
import type { EnginePort, RunContext } from "../port.ts";
|
|
7
7
|
import { DEFAULT_ENGINE_ID, EngineNotFoundError } from "../registry.ts";
|
|
8
8
|
import { EngineError } from "../common/errors.ts";
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
9
|
+
import type { ProbeReport, SessionView } from "../types.ts";
|
|
10
|
+
import type { AgentCallOpts } from "../../../orchestration/models/types.ts";
|
|
11
|
+
import { resolveEngineRouting, routeEngine, routeEngineForHost, type EngineRouteOptions, type HostRouteOptions } from "../routing.ts";
|
|
12
|
+
import type { EngineRouteResult } from "../routing.ts";
|
|
11
13
|
|
|
12
14
|
/** 最小可运行假引擎(probe 结果可注入)。 */
|
|
13
15
|
function makeFakeEngine(id: string, probeOk: boolean): EnginePort {
|
|
@@ -24,6 +26,7 @@ function makeFakeEngine(id: string, probeOk: boolean): EnginePort {
|
|
|
24
26
|
resume: "cold",
|
|
25
27
|
interrupt: "kill-only",
|
|
26
28
|
permissionMode: "native",
|
|
29
|
+
maxTurns: false,
|
|
27
30
|
}),
|
|
28
31
|
probe: () =>
|
|
29
32
|
Promise.resolve(
|
|
@@ -36,7 +39,7 @@ function makeFakeEngine(id: string, probeOk: boolean): EnginePort {
|
|
|
36
39
|
error: { code: "engine_probe_failed", recovery: "reinstall the engine binary and retry the probe" },
|
|
37
40
|
} satisfies ProbeReport,
|
|
38
41
|
),
|
|
39
|
-
run: (_t:
|
|
42
|
+
run: (_t: AgentCallOpts, _c: RunContext) => Promise.reject(new Error("fake: run not implemented")),
|
|
40
43
|
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
|
|
41
44
|
read: (_h: Parameters<EnginePort["read"]>[0]): Promise<SessionView> =>
|
|
42
45
|
Promise.resolve({ engineId: id, turns: [], source: "outcome-only" }),
|
|
@@ -162,9 +165,10 @@ describe("routeEngine:路由 + 探针 + 守卫编排(验收 1/2)", () => {
|
|
|
162
165
|
expect((err as EngineError).recovery).toContain("retry the probe");
|
|
163
166
|
});
|
|
164
167
|
|
|
165
|
-
it("守卫 b(首期与 a
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
+
it("守卫 b(首期与 a 合流):能力依赖声明无独立载体,frontmatter 指定(非 call)不阻断 fallback", async () => {
|
|
169
|
+
// [D6 合流] 原 AgentTaskSpec.requires 形状预留已随任务形状合流裁撤(无生产写入方,
|
|
170
|
+
// 见 AgentCallOpts 类型注释);行为面保持:frontmatter 指定(非 call)+ 无 model
|
|
171
|
+
// → 仍走 fallback(能力依赖声明的独立载体留给将来下钻)
|
|
168
172
|
const { opts } = makeRoute({ zcodeProbeOk: false, routing: { agentEngine: "zcode" } });
|
|
169
173
|
const result = await routeEngine(opts);
|
|
170
174
|
expect(result.engineFallback).toBeDefined();
|
|
@@ -229,3 +233,108 @@ describe("routeEngine:路由 + 探针 + 守卫编排(验收 1/2)", () => {
|
|
|
229
233
|
expect(result.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
|
|
230
234
|
});
|
|
231
235
|
});
|
|
236
|
+
|
|
237
|
+
// ============================================================
|
|
238
|
+
// routeEngineForHost(D3-② 路由单点:宿主两调用点的统一编排)
|
|
239
|
+
// ============================================================
|
|
240
|
+
|
|
241
|
+
describe("routeEngineForHost:宿主统一路由(D3-②)", () => {
|
|
242
|
+
/** 本地 pi 引擎实例替身(chat 域 = chatPiEngine / workflow 域 = SAR per-session DI)。 */
|
|
243
|
+
function makeLocalPi(): EnginePort {
|
|
244
|
+
return makeFakeEngine("local-pi", true);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** 装配 host 路由参数(registry 面复用 makeRoute 的注入件)。 */
|
|
248
|
+
function makeHostRoute(overrides?: Parameters<typeof makeRoute>[0]): {
|
|
249
|
+
hostOpts: HostRouteOptions;
|
|
250
|
+
probeCalls: string[];
|
|
251
|
+
engines: Map<string, EnginePort>;
|
|
252
|
+
piEngine: EnginePort;
|
|
253
|
+
} {
|
|
254
|
+
const { opts, probeCalls, engines } = makeRoute(overrides);
|
|
255
|
+
const piEngine = makeLocalPi();
|
|
256
|
+
const { routing, ...rest } = opts;
|
|
257
|
+
return { hostOpts: { ...rest, routing: routing ?? {}, piEngine }, probeCalls, engines, piEngine };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
it("pi 请求(缺省):同步短路——返回值不是 Promise(零微任务,缺省路径时序契约)且免探", () => {
|
|
261
|
+
const { hostOpts, piEngine, probeCalls } = makeHostRoute();
|
|
262
|
+
const routed = routeEngineForHost(hostOpts);
|
|
263
|
+
expect(routed).not.toBeInstanceOf(Promise);
|
|
264
|
+
const route = routed as EngineRouteResult;
|
|
265
|
+
expect(route.engine).toBe(piEngine); // 本地 pi 实例接管,不经 registry
|
|
266
|
+
expect(route.engineId).toBe("pi");
|
|
267
|
+
expect(route.source).toBe("default");
|
|
268
|
+
expect(probeCalls).toEqual([]); // pi 免探(D7 轻量口径,缺省路径零 probe 开销)
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("显式 engine='pi':同步短路同形(call source 留痕)", () => {
|
|
272
|
+
const { hostOpts, piEngine } = makeHostRoute({ routing: { callEngine: "pi" } });
|
|
273
|
+
const routed = routeEngineForHost(hostOpts);
|
|
274
|
+
expect(routed).not.toBeInstanceOf(Promise);
|
|
275
|
+
const route = routed as EngineRouteResult;
|
|
276
|
+
expect(route.engine).toBe(piEngine);
|
|
277
|
+
expect(route.source).toBe("call");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("非 pi 请求(frontmatter zcode + probe ok):返回 Promise,resolve 经注入获取引擎", async () => {
|
|
281
|
+
const { hostOpts, engines } = makeHostRoute({ routing: { agentEngine: "zcode" } });
|
|
282
|
+
const routed = routeEngineForHost(hostOpts);
|
|
283
|
+
expect(routed).toBeInstanceOf(Promise);
|
|
284
|
+
const route = await routed;
|
|
285
|
+
expect(route.engine).toBe(engines.get("zcode"));
|
|
286
|
+
expect(route.engineId).toBe("zcode");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("probe 失败兜底回 pi:本地 pi 实例接管(不依赖 registry 的 pi 注册态)+ fallback 留痕", async () => {
|
|
290
|
+
const { hostOpts, piEngine } = makeHostRoute({
|
|
291
|
+
zcodeProbeOk: false,
|
|
292
|
+
routing: { agentEngine: "zcode" },
|
|
293
|
+
});
|
|
294
|
+
const route = await routeEngineForHost(hostOpts);
|
|
295
|
+
expect(route.engine).toBe(piEngine);
|
|
296
|
+
expect(route.engineId).toBe("pi");
|
|
297
|
+
expect(route.requestedEngineId).toBe("zcode");
|
|
298
|
+
expect(route.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("registry 面未注册 pi:兜底/清单两口径都不把本地 pi 漏报(SAR 单测 mock 形态)", async () => {
|
|
302
|
+
// registry 面只含 zcode(probe 失败)——模拟「本地 pi 不在全局注册表」的注入形态
|
|
303
|
+
const engines = new Map<string, EnginePort>([["zcode", makeFakeEngine("zcode", false)]]);
|
|
304
|
+
const probeCalls: string[] = [];
|
|
305
|
+
const piEngine = makeLocalPi();
|
|
306
|
+
const mkOpts = (): HostRouteOptions => ({
|
|
307
|
+
routing: { agentEngine: "zcode" },
|
|
308
|
+
strict: false,
|
|
309
|
+
probe: (id) => {
|
|
310
|
+
probeCalls.push(id);
|
|
311
|
+
return engines.get(id)!.probe();
|
|
312
|
+
},
|
|
313
|
+
piEngine,
|
|
314
|
+
getEngineFn: (id) => {
|
|
315
|
+
const e = engines.get(id);
|
|
316
|
+
if (e === undefined) throw new EngineNotFoundError(id, [...engines.keys()]);
|
|
317
|
+
return e;
|
|
318
|
+
},
|
|
319
|
+
hasEngineFn: (id) => engines.has(id),
|
|
320
|
+
listEnginesFn: () => [...engines.keys()],
|
|
321
|
+
});
|
|
322
|
+
// probe 失败 + 无守卫 → 兜底回 pi:本地实例接管,不触 registry 的 pi 缺失
|
|
323
|
+
const route = await routeEngineForHost(mkOpts());
|
|
324
|
+
expect(route.engine).toBe(piEngine);
|
|
325
|
+
expect(route.engineId).toBe("pi");
|
|
326
|
+
expect(probeCalls).toEqual(["zcode"]);
|
|
327
|
+
// 未注册 id:engine_not_found 文案清单含 pi(本地 pi 恒可用,不漏报)
|
|
328
|
+
const ghostOpts: HostRouteOptions = {
|
|
329
|
+
...mkOpts(),
|
|
330
|
+
routing: { callEngine: "ghost" },
|
|
331
|
+
};
|
|
332
|
+
const err = await Promise.resolve(routeEngineForHost(ghostOpts)).then(
|
|
333
|
+
(r: unknown) => r,
|
|
334
|
+
(e: unknown) => e,
|
|
335
|
+
);
|
|
336
|
+
expect(err).toBeInstanceOf(Error);
|
|
337
|
+
expect((err as Error).message).toContain("engine_not_found");
|
|
338
|
+
expect((err as EngineNotFoundError).registered).toContain("pi");
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/execution/engine/common/capability-gate.ts
|
|
2
|
+
//
|
|
3
|
+
// [D3-④ 预检 capabilities 化] 调用前预检的唯一实现(capabilities 驱动,无引擎 id
|
|
4
|
+
// 特判)。设计权威源:docs/design/subagent-dual-track-convergence.md §3.3 D3-④ + r3 裁定
|
|
5
|
+
// (EngineCapabilities 新增 maxTurns 能力位,不保留硬编码 shape 检查)+ §3.4 错误规格
|
|
6
|
+
// 第 1 行(engine_capability_unsupported,既有错误族)。
|
|
7
|
+
//
|
|
8
|
+
// 判据形态:每个引擎拦截「自己 capabilities 声明不支持的能力」——
|
|
9
|
+
// - conversation → capabilities.conversation === 'unsupported'
|
|
10
|
+
// - fork/forkFromSessionFile → session 分叉通道族全缺(steer 与 conversation 均
|
|
11
|
+
// 'unsupported',见下方 fork 判据说明)
|
|
12
|
+
// - maxTurns → capabilities.maxTurns === false(r3 扩位)
|
|
13
|
+
// - worktree(boolean true 或 WorktreeHandle)→ capabilities.sandbox === 'none'
|
|
14
|
+
//
|
|
15
|
+
// 调用点仍是两处(单点的是实现,不是调用点):
|
|
16
|
+
// - chat 域:executeViaEngine 同步段、record 创建前(engine.capabilities() 同步可得,
|
|
17
|
+
// 承接「全部同步拒绝发生在 record 创建前、不产生孤儿 record」不变量);
|
|
18
|
+
// - workflow 域:SAR.run 路由后、engine.run 前(同模块调用)。
|
|
19
|
+
//
|
|
20
|
+
// 行为变化声明(§3.4):唯一有意行为变化 = workflow 域 zcode+worktree 由漏拦变拦截
|
|
21
|
+
// (修复双轨清单 #7 的跨域缺口);pi 的 maxTurns/fork/conversation/worktree 等既有
|
|
22
|
+
// 合法能力零拦截(V4⑤ 反向守护——pi 声明 conversation='native'、sandbox='emulated'、
|
|
23
|
+
// maxTurns=true,全数放行)。
|
|
24
|
+
|
|
25
|
+
import { EngineError } from "./errors.ts";
|
|
26
|
+
import type { EngineCapabilities } from "../types.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 预检输入的任务形状子集——ExecuteOptions(chat 域)与 AgentCallOpts(D6 合流后的
|
|
30
|
+
* 单一任务形状,workflow 域 SAR 直传)共有的能力相关字段面。结构子集而非具体类型:
|
|
31
|
+
* 两域的 opts 类型都能直接传入。
|
|
32
|
+
*/
|
|
33
|
+
export interface TaskShapeForGate {
|
|
34
|
+
conversation?: boolean;
|
|
35
|
+
fork?: boolean;
|
|
36
|
+
forkFromSessionFile?: string;
|
|
37
|
+
worktree?: boolean | { path: string };
|
|
38
|
+
maxTurns?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 调用前预检:任务形状中的能力参数对引擎 capabilities 逐一对照,声明不支持即抛
|
|
43
|
+
* EngineError(engine_capability_unsupported)——同步 throw、进程/record 创建前,文案
|
|
44
|
+
* 含 capabilities 依据与恢复指引(换参数/换引擎)。
|
|
45
|
+
*
|
|
46
|
+
* fork 判据说明(借位裁定):fork 依赖父 session 上下文继承(父会话文件作为分叉源),
|
|
47
|
+
* 引擎具备该语义的能力面信号 = 会话分叉/交互通道族(steer 或 conversation 任一非
|
|
48
|
+
* 'unsupported')。pi conversation='native'(chatMode idle 复用)→ 放行;zcode 双
|
|
49
|
+
* 'unsupported'(argv-only spawn 单轮,无父 session 分叉通道)→ 拒绝。仅凭 steer 判
|
|
50
|
+
* 会误拦 pi(pi 的 steer 声明 'unsupported'——RPC 有但 spawn 链路未接通,与 fork 的
|
|
51
|
+
* 初始上下文继承是两条轴),故取通道族任一可用即支持的分寸。
|
|
52
|
+
*/
|
|
53
|
+
export function assertTaskShapeSupported(
|
|
54
|
+
engineId: string,
|
|
55
|
+
caps: EngineCapabilities,
|
|
56
|
+
task: TaskShapeForGate,
|
|
57
|
+
): void {
|
|
58
|
+
if (task.conversation === true && caps.conversation === "unsupported") {
|
|
59
|
+
throw new EngineError(
|
|
60
|
+
"engine_capability_unsupported",
|
|
61
|
+
`engine '${engineId}' 不支持 conversation(capabilities.conversation = 'unsupported',` +
|
|
62
|
+
`spawn 单轮模式无同进程 idle 复用,message/close 交互控制面不可用)`,
|
|
63
|
+
`改用 engine: pi(支持 conversation 续聊),或不传该参数(一次性任务默认形态)`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (task.fork === true || task.forkFromSessionFile !== undefined) {
|
|
67
|
+
if (caps.steer === "unsupported" && caps.conversation === "unsupported") {
|
|
68
|
+
throw new EngineError(
|
|
69
|
+
"engine_capability_unsupported",
|
|
70
|
+
`engine '${engineId}' 不支持 fork${task.forkFromSessionFile !== undefined ? "(fork-from 同为父 session 上下文继承)" : ""}(fork 依赖父 session 上下文继承,` +
|
|
71
|
+
`capabilities.steer = '${caps.steer}' / conversation = '${caps.conversation}'——引擎无父 session 分叉通道)`,
|
|
72
|
+
`把所需父上下文写进 task 正文后不传 fork,或改用 engine: pi`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (task.maxTurns !== undefined && caps.maxTurns === false) {
|
|
77
|
+
throw new EngineError(
|
|
78
|
+
"engine_capability_unsupported",
|
|
79
|
+
`engine '${engineId}' 不支持 maxTurns(capabilities.maxTurns = false,轮数上限依赖 turn_end 事件流,` +
|
|
80
|
+
`本引擎无此语义——静默丢弃会造成「传了上限却失控」的假象)`,
|
|
81
|
+
`去掉 maxTurns 参数重派,或改用 engine: pi(turn limiter 执行轮数上限)`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if ((task.worktree === true || typeof task.worktree === "object") && caps.sandbox === "none") {
|
|
85
|
+
throw new EngineError(
|
|
86
|
+
"engine_capability_unsupported",
|
|
87
|
+
`engine '${engineId}' 不支持 worktree 隔离(capabilities.sandbox = 'none',` +
|
|
88
|
+
`引擎未接文件系统隔离层)`,
|
|
89
|
+
`改用 engine: pi(worktree 隔离可用),或不传该参数(在 parent cwd 执行)`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/execution/engine/common/journal-wiring.ts
|
|
2
|
+
//
|
|
3
|
+
// [D3-③ journal 接线合一] host 侧 event journal 接线的共享 helper(唯一实现,两调用
|
|
4
|
+
// 点——SubagentService.runEngineTask(chat 域)与 SubprocessAgentRunner.run(workflow
|
|
5
|
+
// 域))。设计权威源:docs/design/subagent-dual-track-convergence.md §3.3 D3-③(writer +
|
|
6
|
+
// retarget + handle 回填两份提为 common 层共享 helper)+ 双轨清单 #6。
|
|
7
|
+
//
|
|
8
|
+
// 收敛前形态(两份同构接线):
|
|
9
|
+
// - subagent-service.ts:new JournalWriter(占位池 key 'shared')+ retarget 闭包 +
|
|
10
|
+
// record.engineHandle.journalPath 回填;
|
|
11
|
+
// - subprocess-agent-runner.ts:new JournalWriter(占位 PI_POOL_KEY)+ retarget 闭包 +
|
|
12
|
+
// journalingOnEvent 包装(先落盘再转发 workflow liveRecord)+ handle.data.journalPath 回填。
|
|
13
|
+
//
|
|
14
|
+
// 机制语义(对齐点③:路径权威 = 引擎声明的池 key):writer 初始用占位池 key 建路径,
|
|
15
|
+
// 非池化稳定引擎(zcode)在 prepare 期经 RunContext.onPoolResolved 声明实际池 key →
|
|
16
|
+
// retarget——保证 journal 落盘路径与 handle.poolKey 同源。run 终态后 close(flush +
|
|
17
|
+
// fsync 一次,§3.3.6 写入纪律;写失败已由 writer 内部 warn + failed 收口,close 不抛,
|
|
18
|
+
// journal 是②级尽力而为数据源)。
|
|
19
|
+
|
|
20
|
+
import type { AgentEvent } from "../../../shared/agent-event.ts";
|
|
21
|
+
import { getEngineDataDir } from "./data-dir.ts";
|
|
22
|
+
import { JournalWriter } from "./event-journal.ts";
|
|
23
|
+
import { resolveJournalPath } from "../paths.ts";
|
|
24
|
+
import type { EngineHandle } from "../types.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* journal 初始占位池 key(= pi 的恒定池 key 'shared')。pi 无隔离池
|
|
28
|
+
* (PI_CODING_AGENT_DIR 全局一份,设计 §3.3.9),占位即终值;zcode 在 prepare 期
|
|
29
|
+
* retarget 到实际池 key。值与 RunContext.poolKey 的初始占位同源——本常量是该值在
|
|
30
|
+
* common 层的单一权威(原先 Service 用 'shared' 字面量、SAR 用 PI_POOL_KEY,等值异名)。
|
|
31
|
+
*/
|
|
32
|
+
export const JOURNAL_INITIAL_POOL_KEY = "shared";
|
|
33
|
+
|
|
34
|
+
/** wireEventJournal 的参数。 */
|
|
35
|
+
export interface JournalWiringOptions {
|
|
36
|
+
/** 实际执行引擎 id(journal 路径分段 + line 元数据)。 */
|
|
37
|
+
engineId: string;
|
|
38
|
+
/** 宿主侧任务标识(journal 文件名与池引用计数 key:chat 域 = record.id;workflow 域 = 'sa-' 前缀占位)。 */
|
|
39
|
+
taskId: string;
|
|
40
|
+
/**
|
|
41
|
+
* journal 落盘后的事件转发(workflow 域的 liveRecord 通道)。缺省不转发(chat 域
|
|
42
|
+
* 无下游 onEvent 消费者——journal 是事件唯一出口)。
|
|
43
|
+
*/
|
|
44
|
+
forwardEvents?: (event: AgentEvent) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** wireEventJournal 的产物(喂给 RunContext 的回调簇 + 终态收口/回填面)。 */
|
|
48
|
+
export interface JournalWiring {
|
|
49
|
+
/** journaling onEvent:先落盘再转发——RunContext.onEvent 的值。 */
|
|
50
|
+
onEvent: (event: AgentEvent) => void;
|
|
51
|
+
/** RunContext.onPoolResolved 的值(引擎声明实际池 key 后重定向落盘路径)。 */
|
|
52
|
+
onPoolResolved: (poolKey: string) => void;
|
|
53
|
+
/**
|
|
54
|
+
* 终态落盘路径(writer 是路径权威——retarget 后的实际路径)。read 第②级的自描述
|
|
55
|
+
* 定位符数据源:record.engineHandle.journalPath(chat 域)与 handle.data.journalPath
|
|
56
|
+
* (workflow 域 backfillHandle)都取本值。
|
|
57
|
+
*/
|
|
58
|
+
readonly path: string;
|
|
59
|
+
/** run 终态收口(flush + fsync;幂等,不抛——见文件头)。 */
|
|
60
|
+
close(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* handle 回填:EngineHandleData.journalPath = writer 终态路径(read ②级经
|
|
63
|
+
* handle.journalPath 自描述定位——运行期落盘路径权威在 writer)。
|
|
64
|
+
*/
|
|
65
|
+
backfillHandle(handle: EngineHandle): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 接线 host 侧 event journal(两域共用):创建 writer(占位池 key)+ retarget 回调 +
|
|
70
|
+
* journaling onEvent 包装 + 终态路径访问。close 在 run 终态(成功/失败路径均达)调用。
|
|
71
|
+
*/
|
|
72
|
+
export function wireEventJournal(opts: JournalWiringOptions): JournalWiring {
|
|
73
|
+
const journal = new JournalWriter({
|
|
74
|
+
path: resolveJournalPath(getEngineDataDir(), opts.engineId, JOURNAL_INITIAL_POOL_KEY, opts.taskId),
|
|
75
|
+
taskId: opts.taskId,
|
|
76
|
+
engineId: opts.engineId,
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
// 先落盘再转发(原 onEvent 未传时也恒传包装版——下游 onEvent 通道是事件生成后的
|
|
80
|
+
// 纯转发,无行为分支,仅多一次入队)
|
|
81
|
+
onEvent: (event) => {
|
|
82
|
+
journal.append(event);
|
|
83
|
+
opts.forwardEvents?.(event);
|
|
84
|
+
},
|
|
85
|
+
onPoolResolved: (poolKey) => {
|
|
86
|
+
journal.retarget(resolveJournalPath(getEngineDataDir(), opts.engineId, poolKey, opts.taskId));
|
|
87
|
+
},
|
|
88
|
+
// getter 而非快照:retarget 后取实际落盘路径(writer 是路径权威)
|
|
89
|
+
get path() {
|
|
90
|
+
return journal.path;
|
|
91
|
+
},
|
|
92
|
+
close: () => journal.close(),
|
|
93
|
+
backfillHandle: (handle) => {
|
|
94
|
+
handle.data.journalPath = journal.path;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
import { getLogger } from "../../../core/logger.ts";
|
|
11
11
|
|
|
12
12
|
import { engineTimeoutDetail } from "./errors.ts";
|
|
13
|
-
import type {
|
|
13
|
+
import type { AgentCallOpts } from "../../../orchestration/models/types.ts";
|
|
14
|
+
import type { AgentOutcome } from "../types.ts";
|
|
14
15
|
import { DEFAULT_ENGINE_ID } from "../registry.ts";
|
|
16
|
+
import { toErrorMessage } from "../../../core/error-message.ts";
|
|
15
17
|
|
|
16
18
|
const logger = getLogger("subagents");
|
|
17
19
|
|
|
@@ -34,6 +36,11 @@ export interface KillableChild {
|
|
|
34
36
|
once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
// 毫秒→秒换算(SIGKILL 升级 warn 日志的秒数显示)。文件内私有定义:工程内 MS_PER_SECOND
|
|
40
|
+
// 惯例是各使用文件私有常量(execution-record / lifecycle-manager / session-file-gc /
|
|
41
|
+
// session-runner 四处先例),无共享导出源可 import,保持同惯例不另立导出点。
|
|
42
|
+
const MS_PER_SECOND = 1_000;
|
|
43
|
+
|
|
37
44
|
/** SIGTERM 优雅窗口默认值(ms)。实测校准点:设计 §5 待验证检查点⑤(zcode 对 SIGTERM 的响应时序)。 */
|
|
38
45
|
export const DEFAULT_KILL_GRACE_MS = 5_000;
|
|
39
46
|
|
|
@@ -47,6 +54,25 @@ const SIGKILL_REAP_TIMEOUT_MS = 10_000;
|
|
|
47
54
|
*/
|
|
48
55
|
export const HOST_TIMEOUT_ABORT_REASON = "agent-call-timeout";
|
|
49
56
|
|
|
57
|
+
/** killChain 的参数形状。 */
|
|
58
|
+
export interface KillChainOptions {
|
|
59
|
+
/**
|
|
60
|
+
* SIGTERM 优雅窗口(ms)。grace 窗口按引擎参数化(D3-① 杀链合一):pi 传 30s
|
|
61
|
+
* ([race-F4] 现状值)、zcode 传 5s(ZCODE_KILL_GRACE_MS)——两引擎现状逐字节保持。
|
|
62
|
+
*/
|
|
63
|
+
graceMs: number;
|
|
64
|
+
/**
|
|
65
|
+
* [D3-①] 内部 timer unref。pi 路径现状 = 升级 timer unref(不阻止主进程退出,dispose
|
|
66
|
+
* killAll 兜底);zcode 现状 = ref'd(timer 挂起时进程等待收尸)——各引擎按现状传。
|
|
67
|
+
*/
|
|
68
|
+
unrefTimers?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* SIGKILL 升级时的 warn 留痕载体(如 `child sa-xxx (source: spawn watchdog)`)。
|
|
71
|
+
* 不传则升级静默(zcode 现状)——pi 侧现状有 warn,由调用方组装完整语境。
|
|
72
|
+
*/
|
|
73
|
+
escalationNote?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
50
76
|
/**
|
|
51
77
|
* 杀链:SIGTERM → 等待 graceMs → 仍存活则 SIGKILL。
|
|
52
78
|
*
|
|
@@ -54,7 +80,7 @@ export const HOST_TIMEOUT_ABORT_REASON = "agent-call-timeout";
|
|
|
54
80
|
*/
|
|
55
81
|
export async function killChain(
|
|
56
82
|
child: KillableChild,
|
|
57
|
-
opts:
|
|
83
|
+
opts: KillChainOptions,
|
|
58
84
|
): Promise<"terminated" | "killed"> {
|
|
59
85
|
// 已退出(自然/已被杀)→ 无需发信号,按优雅终止口径返回
|
|
60
86
|
if (child.exitCode !== null || child.signalCode !== null) return "terminated";
|
|
@@ -62,14 +88,19 @@ export async function killChain(
|
|
|
62
88
|
const exited = waitForExit(child);
|
|
63
89
|
safeKill(child, "SIGTERM");
|
|
64
90
|
|
|
65
|
-
const graceful = await raceTimeout(exited, opts.graceMs);
|
|
91
|
+
const graceful = await raceTimeout(exited, opts.graceMs, opts.unrefTimers === true);
|
|
66
92
|
if (graceful === "settled") return "terminated";
|
|
67
93
|
// grace 超时后进程可能恰好在检查前一刻退出——复核退出态,避免误杀已死进程
|
|
68
94
|
if (child.exitCode !== null || child.signalCode !== null) return "terminated";
|
|
69
95
|
|
|
96
|
+
if (opts.escalationNote !== undefined) {
|
|
97
|
+
logger.warn(
|
|
98
|
+
`[kill-chain] ${opts.escalationNote} still alive ${opts.graceMs / MS_PER_SECOND}s after SIGTERM, escalating to SIGKILL`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
70
101
|
safeKill(child, "SIGKILL");
|
|
71
102
|
// 有界收尸:无论等到与否都返回 'killed'(信号已发出,返回值表达「走了 SIGKILL」)
|
|
72
|
-
await raceTimeout(exited, SIGKILL_REAP_TIMEOUT_MS);
|
|
103
|
+
await raceTimeout(exited, SIGKILL_REAP_TIMEOUT_MS, opts.unrefTimers === true);
|
|
73
104
|
return "killed";
|
|
74
105
|
}
|
|
75
106
|
|
|
@@ -83,7 +114,7 @@ function safeKill(child: KillableChild, signal: NodeJS.Signals): void {
|
|
|
83
114
|
child.kill(signal);
|
|
84
115
|
} catch (err) {
|
|
85
116
|
logger.debug(
|
|
86
|
-
`[kill-chain] ${signal} on exited process: ${
|
|
117
|
+
`[kill-chain] ${signal} on exited process: ${toErrorMessage(err)}`,
|
|
87
118
|
);
|
|
88
119
|
}
|
|
89
120
|
}
|
|
@@ -98,14 +129,14 @@ function safeKill(child: KillableChild, signal: NodeJS.Signals): void {
|
|
|
98
129
|
* exitCode: null = 被信号杀死(AgentOutcome 的杀链判据,§3.3.5)。
|
|
99
130
|
*/
|
|
100
131
|
export function synthesizeTimeoutOutcome(
|
|
101
|
-
task:
|
|
132
|
+
task: AgentCallOpts,
|
|
102
133
|
stdoutTail: string,
|
|
103
134
|
engineId: string = DEFAULT_ENGINE_ID,
|
|
104
135
|
): AgentOutcome {
|
|
105
136
|
return {
|
|
106
137
|
content: "",
|
|
107
138
|
// slug 进错误信息:单看 outcome(record 之外)也能定位是哪个任务超时
|
|
108
|
-
error: `engine_timeout: [slug=${task.
|
|
139
|
+
error: `engine_timeout: [slug=${task.description ?? "unknown"}] ${engineTimeoutDetail(stdoutTail)}`,
|
|
109
140
|
// 被信号杀死:退出码语义为 null(§3.3.5 AgentOutcome.exitCode 注释)
|
|
110
141
|
exitCode: null,
|
|
111
142
|
engineId,
|
|
@@ -161,7 +192,7 @@ export function abortWithFallback(
|
|
|
161
192
|
// debug 级留诊断线索:这不是错误终态,只是该引擎优雅中断不可用
|
|
162
193
|
logger.debug(
|
|
163
194
|
`[kill-chain] native interrupt failed, falling back to kill chain: ${
|
|
164
|
-
|
|
195
|
+
toErrorMessage(err)
|
|
165
196
|
}`,
|
|
166
197
|
);
|
|
167
198
|
}
|
|
@@ -202,10 +233,11 @@ function waitForExit(child: KillableChild): Promise<void> {
|
|
|
202
233
|
});
|
|
203
234
|
}
|
|
204
235
|
|
|
205
|
-
/** promise vs 超时:超时先到返回 'timeout'(promise 继续但被放弃等待)。 */
|
|
206
|
-
function raceTimeout(p: Promise<void>, ms: number): Promise<"settled" | "timeout"> {
|
|
236
|
+
/** promise vs 超时:超时先到返回 'timeout'(promise 继续但被放弃等待)。unref 见 KillChainOptions。 */
|
|
237
|
+
function raceTimeout(p: Promise<void>, ms: number, unref = false): Promise<"settled" | "timeout"> {
|
|
207
238
|
return new Promise<"settled" | "timeout">((resolve) => {
|
|
208
239
|
const timer = setTimeout(() => resolve("timeout"), ms);
|
|
240
|
+
if (unref) timer.unref();
|
|
209
241
|
p.then(
|
|
210
242
|
() => {
|
|
211
243
|
clearTimeout(timer);
|