@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
|
@@ -26,6 +26,7 @@ import type { ManifestStore } from "./manifest-store.ts";
|
|
|
26
26
|
import type { ModelConfigService } from "./model-config-service.ts";
|
|
27
27
|
import { getSubagentSessionDir } from "./path-encoding.ts";
|
|
28
28
|
import type { RecordStore } from "./record-store.ts";
|
|
29
|
+
import { readIdentityHeader, readIdentityTail } from "./session-reconstructor.ts";
|
|
29
30
|
import { writeCancelledTombstone } from "./tombstone-store.ts";
|
|
30
31
|
import type { AgentResult, ClosedReason, ExecutionRecord } from "./types.ts";
|
|
31
32
|
import type { WorktreeManager } from "./worktree-manager.ts";
|
|
@@ -42,105 +43,147 @@ export interface FinalizeDeps {
|
|
|
42
43
|
pi: { appendEntry?: (type: string, data: unknown) => void } | null;
|
|
43
44
|
/** pending-notifications 终态注销(绑定 pi.events.emit,由调用方闭包提供)。 */
|
|
44
45
|
emitUnregister(id: string, status: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* [T1/PS-9] subagent sessionDir(getSubagentSessionDir(agentDir, rootCwd),调用方注入)。
|
|
48
|
+
*
|
|
49
|
+
* record.sessionFile 缺失(RC-1 握手失败 + LC-4 反查也未命中的残余形态)时用于磁盘
|
|
50
|
+
* 反查:按 identity(record.id)扫目录找真实 session 文件,作为 tombstone/finalized
|
|
51
|
+
* sidecar 与 removeAliveMarker 的依据——消除「sessionFile 缺失 → 终态原因丢失 +
|
|
52
|
+
* alive marker 残留」。undefined = 调用方无法提供(反查跳过,行为退回修复前)。
|
|
53
|
+
*/
|
|
54
|
+
sessionDir?: string;
|
|
45
55
|
// [review 修复] 已删除 redeliverPending 回调(MF-1 消费确认制补投):pendingMessages
|
|
46
56
|
// 三段消费链随 deliverToRunning 一并移除(无生产调用方,死机制)。
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
/**
|
|
50
|
-
*
|
|
60
|
+
* [T1/PS-9] 按 record 身份在 sessionDir 反查 session 文件(record.sessionFile 缺失时用)。
|
|
51
61
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
62
|
+
* 匹配依据:session JSONL 的 identity custom entry 携带 record id(子进程 session_start
|
|
63
|
+
* hook 写入),探测函数与 RecordStore 列表扫描同源(readIdentityHeader 头部 64KB →
|
|
64
|
+
* readIdentityTail 尾部 64KB;续聊场景 identity 靠尾)。readIdentityAnywhere 需全文读,
|
|
65
|
+
* 本反查是收尾路径的 best-effort,不做。
|
|
66
|
+
*
|
|
67
|
+
* @returns 匹配到的 session 文件绝对路径;目录不可读 / 无匹配返回 undefined(不抛)。
|
|
55
68
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
72
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
73
|
-
const patchFile = path.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
|
|
74
|
-
const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
|
|
75
|
-
if (patch.written) record.patchFile = patchFile;
|
|
76
|
-
} catch (pe: unknown) {
|
|
77
|
-
bestEffort(pe, "collectPatch (finalizeRecord Step0)");
|
|
78
|
-
}
|
|
69
|
+
function findSessionFileByRecordIdentity(
|
|
70
|
+
sessionDir: string,
|
|
71
|
+
recordId: string,
|
|
72
|
+
): string | undefined {
|
|
73
|
+
let names: string[];
|
|
74
|
+
try {
|
|
75
|
+
names = fs.readdirSync(sessionDir);
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined; // 目录缺失/不可读 → 反查跳过,行为退回无反查
|
|
78
|
+
}
|
|
79
|
+
for (const name of names) {
|
|
80
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
81
|
+
const full = path.join(sessionDir, name);
|
|
82
|
+
const recon = readIdentityHeader(full) ?? readIdentityTail(full);
|
|
83
|
+
if (recon?.id === recordId) return full;
|
|
79
84
|
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
/**
|
|
89
|
+
* [T1/PS-9] Step 序言:sessionFile 缺失时按 record 身份在 sessionDir 反查回填。
|
|
90
|
+
*
|
|
91
|
+
* 放在一切步骤前,让 archive 投影 / Step 3 marker / Step 4 manifest 统一受益。
|
|
92
|
+
* RC-1(握手失败)+ LC-4(收尾反查未命中)的残余形态下,磁盘上 session 文件仍可能
|
|
93
|
+
* 真实存在(identity 携带 record.id)——反查命中则 tombstone/finalized sidecar 与
|
|
94
|
+
* removeAliveMarker 有了依据,终态原因不再丢失、alive marker 不再残留;未命中则
|
|
95
|
+
* 保持旧行为(best-effort 跳过)。
|
|
96
|
+
*/
|
|
97
|
+
function resolveMissingSessionFile(deps: FinalizeDeps, record: ExecutionRecord): void {
|
|
98
|
+
if (record.sessionFile || !deps.sessionDir) return;
|
|
99
|
+
const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
|
|
100
|
+
if (resolved) {
|
|
101
|
+
record.sessionFile = resolved;
|
|
102
|
+
logger.warn(
|
|
103
|
+
`[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`,
|
|
104
|
+
);
|
|
86
105
|
}
|
|
106
|
+
}
|
|
87
107
|
|
|
88
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Step 0: collectPatch(best-effort,仅 worktree 绑定时执行)。
|
|
110
|
+
* [MF#3] patchFile 写到 worktree 之外(sessionsDir/<branch>.patch),避免被 cleanup 删除;
|
|
111
|
+
* 路径回填 record.patchFile,供调用方(tool result / /subagents list)应用。
|
|
112
|
+
*/
|
|
113
|
+
async function collectPatchIfWorktree(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
|
|
114
|
+
if (!record.worktreeHandle) return;
|
|
89
115
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
const sessionsDir = getSubagentSessionDir(
|
|
117
|
+
deps.modelService.getAgentDir(),
|
|
118
|
+
record.worktreeHandle.mainCwd,
|
|
119
|
+
);
|
|
120
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
121
|
+
const patchFile = path.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
|
|
122
|
+
const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
|
|
123
|
+
if (patch.written) record.patchFile = patchFile;
|
|
124
|
+
} catch (pe: unknown) {
|
|
125
|
+
bestEffort(pe, "collectPatch (finalizeRecord Step0)");
|
|
93
126
|
}
|
|
127
|
+
}
|
|
94
128
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
writeFinalized(record.sessionFile, closedReason);
|
|
115
|
-
}
|
|
116
|
-
} catch (err) {
|
|
117
|
-
bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (record.worktreeHandle) {
|
|
121
|
-
try {
|
|
122
|
-
await deps.worktreeManager.cleanup(record.worktreeHandle);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
|
|
129
|
+
/**
|
|
130
|
+
* Step 3a: finalized/tombstone sidecar(best-effort 幂等,仅 sessionFile 存在时执行)。
|
|
131
|
+
* MF-1 fix / v4 B-1: cancelled(closedReason='cancelled')写 tombstone 而非 finalized,
|
|
132
|
+
* 防重建丢失 cancelled;其余 reason 写 finalized sidecar(真实 reason 进 sidecar 内容,
|
|
133
|
+
* 磁盘重建用它还原 closedReason,不再一律硬编码 gc)。
|
|
134
|
+
*/
|
|
135
|
+
function writeFinalizedOrTombstone(record: ExecutionRecord, closedReason: ClosedReason | undefined): void {
|
|
136
|
+
if (!record.sessionFile) return;
|
|
137
|
+
try {
|
|
138
|
+
if (closedReason === "cancelled") {
|
|
139
|
+
writeCancelledTombstone(record.sessionFile, {
|
|
140
|
+
id: record.id,
|
|
141
|
+
status: "cancelled",
|
|
142
|
+
agent: record.agent,
|
|
143
|
+
startedAt: record.startedAt,
|
|
144
|
+
endedAt: record.endedAt ?? Date.now(),
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
writeFinalized(record.sessionFile, closedReason);
|
|
125
148
|
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
|
|
126
151
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Step 3b: worktree cleanup(best-effort 幂等,仅 worktree 绑定时执行)。
|
|
156
|
+
* [Critical] 绝不能因 manifest 写失败而跳过(否则 worktree 泄漏)。
|
|
157
|
+
*/
|
|
158
|
+
async function cleanupWorktreeIfBound(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
|
|
159
|
+
if (!record.worktreeHandle) return;
|
|
160
|
+
try {
|
|
161
|
+
await deps.worktreeManager.cleanup(record.worktreeHandle);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
|
|
133
164
|
}
|
|
165
|
+
}
|
|
134
166
|
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
/** Step 3c: 删 .alive marker(best-effort 幂等,仅 sessionFile 存在时执行)。 */
|
|
168
|
+
function removeAliveMarkerIfPresent(record: ExecutionRecord): void {
|
|
169
|
+
if (!record.sessionFile) return;
|
|
170
|
+
try {
|
|
171
|
+
removeAliveMarker(record.sessionFile);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
bestEffort(err, "removeAliveMarker (finalizeRecord Step3)");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
137
176
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)。
|
|
179
|
+
*
|
|
180
|
+
* [Critical #1] manifest 是诊断辅助(orphan recovery),不是正确性依赖。写失败时
|
|
181
|
+
* 仅记录(console.error + appendEntry),绝不让 manifest 写失败跳过 Step 3 cleanup
|
|
182
|
+
* 或抛出打断 finalize 链。旧实现 Step 2.5 throw 会跳过 Step 3 cleanup。
|
|
183
|
+
* task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord 补字段),
|
|
184
|
+
* manifestToSubagent 投影时用真实值而非硬编码空串。
|
|
185
|
+
*/
|
|
186
|
+
async function writeManifestBestEffort(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
|
|
144
187
|
try {
|
|
145
188
|
await deps.manifestStore.writeManifest({
|
|
146
189
|
id: record.id,
|
|
@@ -166,6 +209,55 @@ export async function doFinalizeRecord(
|
|
|
166
209
|
}
|
|
167
210
|
}
|
|
168
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 时序收尾(D-017)。步骤 0→4 全部 best-effort 互不阻断(除 manifest 外都幂等)。
|
|
214
|
+
*
|
|
215
|
+
* [Critical #1] Step 3 cleanup 全部在 Step 4 manifest 之前——manifest 写失败仅 console.error +
|
|
216
|
+
* appendEntry,不 throw 不跳过 cleanup。task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord
|
|
217
|
+
* 补字段),manifestToSubagent 投影真实值而非硬编码空串。
|
|
218
|
+
*/
|
|
219
|
+
export async function doFinalizeRecord(
|
|
220
|
+
deps: FinalizeDeps,
|
|
221
|
+
record: ExecutionRecord,
|
|
222
|
+
result: AgentResult,
|
|
223
|
+
status: "closed",
|
|
224
|
+
closedReason?: ClosedReason,
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
// [T1/PS-9] sessionFile 缺失时 sessionDir 反查(序言,先于一切步骤)。
|
|
227
|
+
resolveMissingSessionFile(deps, record);
|
|
228
|
+
|
|
229
|
+
// ── Step 0: collectPatch(best-effort)──
|
|
230
|
+
await collectPatchIfWorktree(deps, record);
|
|
231
|
+
|
|
232
|
+
// ── Step 1: completeRecord(B9: 抛错→后续仍执行)──
|
|
233
|
+
try {
|
|
234
|
+
completeRecord(record, result, status, closedReason);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
bestEffort(err, "completeRecord (finalizeRecord B9)", "error");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── Step 2: archive(B9: 抛错→后续仍执行)──
|
|
240
|
+
try {
|
|
241
|
+
deps.store.archive(record);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
bestEffort(err, "store.archive (finalizeRecord B9)", "error");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Step 3: finalized + cleanup + aliveMarker(全部先执行,幂等)──
|
|
247
|
+
// [Critical] 清理必须在 manifest 写入之前:worktree cleanup / finalized marker / aliveMarker
|
|
248
|
+
// 都是幂等且不可跳过的副作用。绝不能因 manifest 写失败而跳过 worktree cleanup
|
|
249
|
+
// (否则 worktree 泄漏)。各件独立 try/catch,互不阻断。
|
|
250
|
+
writeFinalizedOrTombstone(record, closedReason);
|
|
251
|
+
await cleanupWorktreeIfBound(deps, record);
|
|
252
|
+
removeAliveMarkerIfPresent(record);
|
|
253
|
+
|
|
254
|
+
// pending-notifications:终态注销(只记 registry 状态,通知由 BgNotifier 发)
|
|
255
|
+
deps.emitUnregister(record.id, status);
|
|
256
|
+
|
|
257
|
+
// ── Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)──
|
|
258
|
+
await writeManifestBestEffort(deps, record);
|
|
259
|
+
}
|
|
260
|
+
|
|
169
261
|
/**
|
|
170
262
|
* 对话模式轮次完成收尾:record 进 idle 态(非终态化,等待续聊)。
|
|
171
263
|
*
|
|
@@ -179,7 +271,9 @@ export async function doFinalizeRecord(
|
|
|
179
271
|
*
|
|
180
272
|
* MF-2:设 record.result = result.text(否则 notifier idle 回复正文恒为 "(empty)",
|
|
181
273
|
* G1/G2 多轮回复送达不成立)。失败轮次(result.success=false,MF-6 回退 idle 路径)
|
|
182
|
-
*
|
|
274
|
+
* error 优先于 text:collectResult 恒带 getFullText 全量正文,text 优先会把失败的
|
|
275
|
+
* 恢复指引覆盖成轮内旧正文(Gate B S-B-1 实测:settled watchdog kill 后宿主收到
|
|
276
|
+
* 成功形态通知,无从得知挂死与恢复方式)。
|
|
183
277
|
*
|
|
184
278
|
* 状态:record.status = "idle"(覆盖 tryTransition 设的 done/failed),record.round += 1。
|
|
185
279
|
* 各步骤 best-effort 互不阻断(参照 doFinalizeRecord 的 bestEffort 用法)。
|
|
@@ -193,7 +287,9 @@ export async function doFinalizeRoundToIdle(
|
|
|
193
287
|
result: AgentResult,
|
|
194
288
|
): Promise<void> {
|
|
195
289
|
// MF-2:设 record.result 供 notifier idle 回复正文(否则恒 "(empty)",G1/G2 不成立)。
|
|
196
|
-
//
|
|
290
|
+
// [T2-③ / LC-1] 失败轮 error 优先:collectResult 恒带 getFullText 全量正文(即使
|
|
291
|
+
// success=false),text 优先会把 watchdog 等失败的恢复指引覆盖成轮内正文(半成品 /
|
|
292
|
+
// 回补值),宿主无从得知失败与恢复方式(S-B-1 首轮 settled watchdog 实测)。
|
|
197
293
|
// [R2-1] 轮终写点恒写非空:one-shot 空文本成功完成(collectResult getFullText 返回 ""、
|
|
198
294
|
// success=true、真实可达,本写点被 subagent-service runAndFinalize 的成功分支共用)首轮
|
|
199
295
|
// result 前值 undefined,兜底补 "(empty)" 占位——措辞与 notifier buildLlmContent 的
|
|
@@ -205,10 +301,10 @@ export async function doFinalizeRoundToIdle(
|
|
|
205
301
|
// "(no output this round)"(D5:增量语义下沿用旧 record.result = 上一轮增量,本轮通知
|
|
206
302
|
// 正文 = 上一轮内容,父 agent 误读为原样重复回复)。
|
|
207
303
|
let nextResult: string | undefined;
|
|
208
|
-
if (result.text) {
|
|
209
|
-
nextResult = result.text;
|
|
210
|
-
} else if (result.error) {
|
|
304
|
+
if (result.error && (!result.success || !result.text)) {
|
|
211
305
|
nextResult = `round did not complete: ${result.error}`;
|
|
306
|
+
} else if (result.text) {
|
|
307
|
+
nextResult = result.text;
|
|
212
308
|
} else if (record.chatMode) {
|
|
213
309
|
nextResult = "(no output this round)";
|
|
214
310
|
} else {
|
package/src/execution/idle-gc.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* 启动幂等由调用方(service.startGcTimer)守卫;返回 stop 函数供 dispose 清理。
|
|
5
5
|
*/
|
|
6
6
|
import { getLogger } from "../core/logger.ts";
|
|
7
|
+
import { getEngineDataDir } from "./engine/common/data-dir.ts";
|
|
8
|
+
import { releasePoolRef } from "./engine/common/pool-manager.ts";
|
|
7
9
|
import { bestEffort } from "./best-effort.ts";
|
|
8
10
|
import { isResumable } from "./lifecycle-predicates.ts";
|
|
9
11
|
import type { RecordStore } from "./record-store.ts";
|
|
12
|
+
import type { ExecutionRecord } from "./types.ts";
|
|
10
13
|
|
|
11
14
|
const logger = getLogger("subagents");
|
|
12
15
|
|
|
@@ -24,6 +27,15 @@ const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
|
24
27
|
* 启动 idle record GC 定时器,返回 stop 函数(清理 interval;幂等)。
|
|
25
28
|
* 每个扫描周期:对 store 内全部 active record 中 resumable 且带 idleSince 的,
|
|
26
29
|
* 超过 IDLE_TTL_MS 的归档(archive 单条失败不阻断其余,bestEffort 留痕)。
|
|
30
|
+
*
|
|
31
|
+
* [D8 池引用计数接线] 归档时同步释放该 record 的引擎池引用(releasePoolRef:
|
|
32
|
+
* journal 跟随 record 删除 + refs 移除 + 归零删池内原生状态)。锚点选在 30 天 TTL
|
|
33
|
+
* 而非 dispose/archive 类时机,依据:idle record 30 天后引擎侧不会再有活动(archive
|
|
34
|
+
* 后 message 续聊走 fork-from/新 start,不触原引擎),且与 pi 域 session 文件 30 天
|
|
35
|
+
* TTL(session-file-gc 同期删①级读源)形成两引擎对称衰减;disposeAllRecords/close
|
|
36
|
+
* 类时机 record 数据仍保留(可 resurrect / GUI 历史重建可见),journal(②级数据源)
|
|
37
|
+
* 不能删——那是 record 主数据死亡(主 session 文件被 pi 侧删除,core 无触发点)才有
|
|
38
|
+
* 的处置,由 cleanupExpiredPoolRefs 的 TTL 兜底覆盖。
|
|
27
39
|
*/
|
|
28
40
|
export function startIdleGc(store: RecordStore): () => void {
|
|
29
41
|
const timer = setInterval(() => {
|
|
@@ -38,6 +50,7 @@ export function startIdleGc(store: RecordStore): () => void {
|
|
|
38
50
|
} catch (err) {
|
|
39
51
|
bestEffort(err, `GC archive record ${record.id}`);
|
|
40
52
|
}
|
|
53
|
+
releaseEnginePoolRef(record);
|
|
41
54
|
}
|
|
42
55
|
}
|
|
43
56
|
}
|
|
@@ -45,3 +58,18 @@ export function startIdleGc(store: RecordStore): () => void {
|
|
|
45
58
|
timer.unref?.();
|
|
46
59
|
return () => clearInterval(timer);
|
|
47
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 释放 record 的引擎池引用(best-effort)。无 engineHandle(存量 record / 非引擎
|
|
64
|
+
* 路径)时 no-op;engine 缺省投影 'pi'(与读侧存量 entry 零迁移口径一致)。
|
|
65
|
+
*/
|
|
66
|
+
function releaseEnginePoolRef(record: ExecutionRecord): void {
|
|
67
|
+
const poolKey = record.engineHandle?.poolKey;
|
|
68
|
+
if (poolKey === undefined) return;
|
|
69
|
+
const engineId = record.engine ?? "pi";
|
|
70
|
+
try {
|
|
71
|
+
releasePoolRef(getEngineDataDir(), engineId, poolKey, record.id);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
bestEffort(err, `GC release pool ref for record ${record.id}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -24,8 +24,13 @@
|
|
|
24
24
|
// 设计参考:session-runner 的 MF-3/MF-4 setTimeout→SIGTERM 骨架(复用 timer 形态,
|
|
25
25
|
// 触发条件重构);spawnedChildren Map 的模块级单例模式。
|
|
26
26
|
|
|
27
|
+
import { getLogger } from "../core/logger.ts";
|
|
27
28
|
import { assertSafeTimerDelay } from "../shared/timer-delay.ts";
|
|
28
29
|
|
|
30
|
+
// core/logger 无本地状态依赖(不 import execution/orchestration 层),与本模块
|
|
31
|
+
// 「可独立编译 + 单测」约束兼容;不 import session-runner / subagent-service(循环依赖)。
|
|
32
|
+
const logger = getLogger("subagents");
|
|
33
|
+
|
|
29
34
|
// ============================================================
|
|
30
35
|
// 默认常量
|
|
31
36
|
// ============================================================
|
|
@@ -48,6 +53,10 @@ export const DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE
|
|
|
48
53
|
* 从环境变量 XYZ_SUBAGENT_IDLE_TIMEOUT_MS 读取全局默认超时。
|
|
49
54
|
* 返回 undefined 表示 env 未设置或非法(调用方回落 DEFAULT_IDLE_TIMEOUT_MS)。
|
|
50
55
|
*
|
|
56
|
+
* [LC-7/T7①] env 已设但非法(非数字/<=0)回落默认值时必须 warn 留痕——「以为设了
|
|
57
|
+
* 极长保活、实际回落 5min」的静默语义漂移不可见(设计 §4.3 LC-7),生效行为必须
|
|
58
|
+
* 可见。禁用语义不认 env(禁用只能显式传参 <=0,见 armIdleTimer 注释)。
|
|
59
|
+
*
|
|
51
60
|
* [review 修复] 原 PI_ 前缀(PI_SUBAGENT_IDLE_TIMEOUT_MS)不在 ENV_WHITELIST_PREFIXES
|
|
52
61
|
* 白名单(packages/shared/src/constants.ts 只有 XYZ_ 等,无 PI_),xyz-agent 桌面
|
|
53
62
|
* spawn 链(safe-env 过滤)会丢弃该 env,配置口在桌面场景静默失效——已改名 XYZ_
|
|
@@ -59,7 +68,12 @@ function getEnvIdleTimeoutMs(): number | undefined {
|
|
|
59
68
|
const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
|
|
60
69
|
if (!raw) return undefined;
|
|
61
70
|
const parsed = Number(raw);
|
|
62
|
-
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
71
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
72
|
+
logger.warn(
|
|
73
|
+
`[lifecycle-manager] XYZ_SUBAGENT_IDLE_TIMEOUT_MS="${raw}" is invalid (expected a positive millisecond number) — falling back to DEFAULT_IDLE_TIMEOUT_MS (${DEFAULT_IDLE_TIMEOUT_MS}ms); set a plain ms value (e.g. 1800000) to override`,
|
|
74
|
+
);
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
63
77
|
return parsed;
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -134,8 +148,18 @@ export function armIdleTimer(
|
|
|
134
148
|
// 刷新:先清旧 timer,避免同一 record 叠加多个 armed timer。
|
|
135
149
|
disarmIdleTimer(recordId);
|
|
136
150
|
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
// [LC-5/T6①] 超时回调按值守卫(对齐同包 session-runner.ts removeChildRegistration
|
|
152
|
+
// 的按句守卫先例):clearTimeout 对「已到期、回调已入 macrotask 队列」的 timer 无效
|
|
153
|
+
//(Node 语义:fire 后不可撤销),若此刻同 recordId 发生 disarm + re-arm(agent_settled
|
|
154
|
+
// 刷新与旧 timer 到点同轮交错),旧回调无条件 delete(recordId) 会误删**新** timer 条目
|
|
155
|
+
// ——新 timer 脱管,后续 disarm 失效 → turn 中途被 idle GC 误杀。回调捕获自己的 timer
|
|
156
|
+
// 引用,仅当 Map 当前条目仍是自己(未被 re-arm 覆盖)才删除。设计 §11-2:fake-timer
|
|
157
|
+
// 模型内无法复现「旧回调迟到于 re-arm」的精确交错(sinon clock 同步 tick 消除了该
|
|
158
|
+
// 中间态),身份比对按防御性守卫保留(成本一行)。
|
|
159
|
+
const timer: NodeJS.Timeout = setTimeout(() => {
|
|
160
|
+
if (idleTimers.get(recordId)?.timer === timer) {
|
|
161
|
+
idleTimers.delete(recordId);
|
|
162
|
+
}
|
|
139
163
|
onTimeout();
|
|
140
164
|
}, resolved);
|
|
141
165
|
// node 默认 setTimeout 返回的 timer 会被事件循环 keep-alive;unref 让它不阻塞
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// 故 isIdle 在这两个检查点恒为 true——notify 守卫与 early-return 判据可靠。
|
|
21
21
|
|
|
22
22
|
import { hasIdleTimer } from "./lifecycle-manager.ts";
|
|
23
|
-
import { getChildByRecord } from "./session-runner.ts";
|
|
23
|
+
import { getChildByRecord } from "./engine/engines/pi/session-runner.ts";
|
|
24
24
|
import type { ExecutionRecord } from "./types.ts";
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -2,7 +2,12 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as fsPromises from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { getLogger } from "../core/logger.ts";
|
|
6
|
+
|
|
5
7
|
import { bestEffort } from "./best-effort.ts";
|
|
8
|
+
import { writeAtomicFile } from "../shared/atomic-write.ts";
|
|
9
|
+
|
|
10
|
+
const logger = getLogger("subagents");
|
|
6
11
|
|
|
7
12
|
export interface ManifestRecord {
|
|
8
13
|
id: string;
|
|
@@ -89,54 +94,19 @@ export class ManifestStore {
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/**
|
|
92
|
-
* 原子写:tmp → fsync → rename → fsync dir
|
|
97
|
+
* 原子写:tmp → fsync → rename → fsync dir(shared/atomic-write 统一原语,
|
|
98
|
+
* U6b 迁移——原逐行实现与 writeAtomicFile 逐环等值)。真异步(fs.promises,
|
|
99
|
+
* 不阻塞 event loop)。
|
|
93
100
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
101
|
+
* 失败时原语尽力清理残留 tmp(debug 记录,不掩盖原错误)并原样上抛——
|
|
102
|
+
* 调用方(finalizeRecord)决定降级策略。
|
|
96
103
|
*/
|
|
97
104
|
async writeManifest(record: ManifestRecord): Promise<void> {
|
|
98
105
|
const filePath = path.join(this.dir, `${record.id}.json`);
|
|
99
|
-
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
100
106
|
const content = JSON.stringify(record, null, MANIFEST_INDENT_SPACES);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// 1. 写 tmp → fsync 文件
|
|
105
|
-
const fh = await fsPromises.open(tmpPath, "w");
|
|
106
|
-
try {
|
|
107
|
-
await fh.writeFile(content, "utf-8");
|
|
108
|
-
await fh.sync();
|
|
109
|
-
} finally {
|
|
110
|
-
await fh.close();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 2. rename tmp → final(放入 try:失败时 catch 清理 tmp)
|
|
114
|
-
await fsPromises.rename(tmpPath, filePath);
|
|
115
|
-
renamed = true;
|
|
116
|
-
|
|
117
|
-
// 3. fsync 目录(best-effort:POSIX 不要求,失败不否定已成功的 rename)
|
|
118
|
-
try {
|
|
119
|
-
const dirFh = await fsPromises.open(this.dir, "r");
|
|
120
|
-
try {
|
|
121
|
-
await dirFh.sync();
|
|
122
|
-
} finally {
|
|
123
|
-
await dirFh.close();
|
|
124
|
-
}
|
|
125
|
-
} catch (dirSyncErr) {
|
|
126
|
-
bestEffort(dirSyncErr, "fsync dir (writeManifest)");
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
// rename 未成功 → 清理残留 tmp(best-effort,不掩盖原错误)
|
|
130
|
-
if (!renamed) {
|
|
131
|
-
try {
|
|
132
|
-
await fsPromises.unlink(tmpPath);
|
|
133
|
-
} catch (cleanupErr) {
|
|
134
|
-
// best-effort:tmp 可能已被 rename 消费或从未创建。不影响主错误(下面 re-throw err)
|
|
135
|
-
bestEffort(cleanupErr, "unlink tmp (writeManifest)");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
throw err;
|
|
139
|
-
}
|
|
107
|
+
// ensureDir:false:目录由构造函数负责创建(缺目录 = 外部删除的异常态,
|
|
108
|
+
// 维持旧实现的 fail-fast 上抛语义,不静默重建)
|
|
109
|
+
await writeAtomicFile(filePath, content, { ensureDir: false });
|
|
140
110
|
}
|
|
141
111
|
|
|
142
112
|
/**
|
|
@@ -212,10 +182,16 @@ export class ManifestStore {
|
|
|
212
182
|
* 1. manifest 已存在 → 删 tmp(陈旧)
|
|
213
183
|
* 2. tmp 合法 + manifest 缺失 → rename tmp 为 manifest
|
|
214
184
|
* 3. tmp 非法 + manifest 缺失 → 删 tmp
|
|
185
|
+
*
|
|
186
|
+
* [T5④ / PS-13] per-file 容错:单个 tmp 文件操作失败(ENOENT——并发回收/外部清理
|
|
187
|
+
* 抢先、EACCES 等)只 warn + 跳过该文件,不再中断整轮——旧实现单文件 ENOENT 即抛,
|
|
188
|
+
* 剩余 tmp 本轮不再处理,自愈但不可见(残留顺延下次启动)。跳过数经 warn 汇总留痕,
|
|
189
|
+
* 调用方返回值形态不变(跳过者不计数)。
|
|
215
190
|
*/
|
|
216
191
|
async recoverTmpFiles(): Promise<{ deleted: number; recovered: number }> {
|
|
217
192
|
let deleted = 0;
|
|
218
193
|
let recovered = 0;
|
|
194
|
+
let failed = 0;
|
|
219
195
|
|
|
220
196
|
const files = fs.readdirSync(this.dir);
|
|
221
197
|
const tmpFiles = files.filter((f) => f.includes(".json.tmp."));
|
|
@@ -225,32 +201,47 @@ export class ManifestStore {
|
|
|
225
201
|
const manifestId = tmpFile.split(".json.tmp.")[0];
|
|
226
202
|
const manifestPath = path.join(this.dir, `${manifestId}.json`);
|
|
227
203
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
204
|
+
try {
|
|
205
|
+
if (fs.existsSync(manifestPath)) {
|
|
206
|
+
// 分支 1: manifest 已存在,删 tmp
|
|
207
|
+
fs.unlinkSync(tmpPath);
|
|
208
|
+
deleted++;
|
|
209
|
+
} else {
|
|
210
|
+
// 试解析 tmp
|
|
211
|
+
try {
|
|
212
|
+
const content = fs.readFileSync(tmpPath, "utf-8");
|
|
213
|
+
const parsed: unknown = JSON.parse(content);
|
|
214
|
+
if (isValidManifest(parsed)) {
|
|
215
|
+
// 分支 2: tmp 是合法 manifest,rename 为正式文件
|
|
216
|
+
fs.renameSync(tmpPath, manifestPath);
|
|
217
|
+
recovered++;
|
|
218
|
+
} else {
|
|
219
|
+
// 分支 3b: 合法 JSON 但非合法 manifest(缺必填字段),删
|
|
220
|
+
fs.unlinkSync(tmpPath);
|
|
221
|
+
deleted++;
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
// 分支 3a: JSON.parse 失败,删
|
|
243
225
|
fs.unlinkSync(tmpPath);
|
|
244
226
|
deleted++;
|
|
245
227
|
}
|
|
246
|
-
} catch {
|
|
247
|
-
// 分支 3a: JSON.parse 失败,删
|
|
248
|
-
fs.unlinkSync(tmpPath);
|
|
249
|
-
deleted++;
|
|
250
228
|
}
|
|
229
|
+
} catch (fileErr) {
|
|
230
|
+
// [T5④/PS-13] 单文件失败不中断整轮:warn 留痕(含文件名与原因)后继续处理
|
|
231
|
+
// 剩余 tmp。常见于 tmp 已被并发回收/外部清理删除(ENOENT)——自愈场景不再放大。
|
|
232
|
+
failed++;
|
|
233
|
+
logger.warn(`[subagents] recoverTmpFiles: failed to recover ${tmpFile}, skipping (leftovers retry on next startup)`, {
|
|
234
|
+
detail: fileErr instanceof Error ? fileErr.message : String(fileErr),
|
|
235
|
+
});
|
|
251
236
|
}
|
|
252
237
|
}
|
|
253
238
|
|
|
239
|
+
if (failed > 0) {
|
|
240
|
+
logger.warn(
|
|
241
|
+
`[subagents] recoverTmpFiles: ${failed} of ${tmpFiles.length} tmp file(s) could not be recovered`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
254
245
|
return { deleted, recovered };
|
|
255
246
|
}
|
|
256
247
|
}
|
|
@@ -95,9 +95,9 @@ export interface NotifierHost {
|
|
|
95
95
|
isIdle?: () => boolean;
|
|
96
96
|
/**
|
|
97
97
|
* 原生 settled 事件订阅能力(D8):注册 handler 监听主 agent settled 边沿。
|
|
98
|
-
* 无退订语义(pi 0.84.
|
|
98
|
+
* 无退订语义(pi 0.84.4 的 `pi.on(...)` 全部重载返回 void、无 off——实装锚点:
|
|
99
99
|
* node_modules `@earendil-works/pi-coding-agent` dist/core/extensions/types.d.ts
|
|
100
|
-
* `on()` 系列重载,0.84.
|
|
100
|
+
* `on()` 系列重载,0.84.4 实测)——退订由本模块的 disposed
|
|
101
101
|
* 标志包装兑现(见下方 port 装配)。可选:未注入时内核退化退避轮询(busy 消息
|
|
102
102
|
* 靠退避达上限强发,不走 settled 边沿驱动)。
|
|
103
103
|
*/
|
|
@@ -226,14 +226,14 @@ export function createNotifier(host: NotifierHost): BgNotifier {
|
|
|
226
226
|
host.onAgentSettled === undefined
|
|
227
227
|
? undefined
|
|
228
228
|
: (cb) => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
229
|
+
let disposed = false;
|
|
230
|
+
host.onAgentSettled?.(() => {
|
|
231
|
+
if (!disposed) cb();
|
|
232
|
+
});
|
|
233
|
+
return () => {
|
|
234
|
+
disposed = true;
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
237
|
send: (msg, _intent) => {
|
|
238
238
|
// D5 单通道:投递意图唯一化——steer/followUp/nextTurn 多通道全部删除,
|
|
239
239
|
// 唯一发送形态 = sendCustomMessage({triggerTurn:true})。busy 场景由 ledger
|