@zhushanwen/subagent-core 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
- package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
- package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
- package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
- package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
- package/dist/execution/engine/engines/zcode/constants.js +17 -27
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +2 -2
- package/dist/index.cjs +17100 -14082
- package/dist/index.d.cts +5336 -4033
- package/dist/index.d.ts +5336 -4033
- package/dist/index.js +17020 -14055
- package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
- package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
- package/dist.bundle/index.cjs +34933 -0
- package/package.json +3 -3
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +4 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +4 -4
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +2 -2
- package/src/execution/__tests__/alive-store.test.ts +1 -1
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/collect-budget.test.ts +291 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
- package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
- package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
- package/src/execution/__tests__/config.test.ts +1 -1
- package/src/execution/__tests__/delivery-methods.test.ts +75 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
- package/src/execution/__tests__/dialog-queue.test.ts +2 -2
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
- package/src/execution/__tests__/finalize-record.test.ts +173 -17
- package/src/execution/__tests__/finalized-marker.test.ts +1 -1
- package/src/execution/__tests__/gc-timer.test.ts +56 -3
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
- package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-batch.test.ts +500 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +36 -4
- package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
- package/src/execution/__tests__/record-store.test.ts +10 -9
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/relay-env.test.ts +12 -1
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
- package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +36 -1
- package/src/execution/__tests__/session-pending.test.ts +257 -14
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
- package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +3 -3
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +2 -1
- package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +48 -22
- package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +6 -5
- package/src/execution/__tests__/tombstone-store.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
- package/src/execution/__tests__/worktree-registry.test.ts +1 -1
- package/src/execution/agent-registry.ts +74 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/collect-coordinator.ts +200 -0
- package/src/execution/config.ts +77 -5
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +213 -53
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +4 -2
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +24 -11
- package/src/execution/engine/engines/zcode/constants.ts +75 -71
- package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +12 -143
- package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
- package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +24 -6
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +229 -144
- package/src/execution/finalize-record.ts +141 -99
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/notifier.ts +234 -25
- package/src/execution/notify-host.ts +232 -0
- package/src/execution/record-entry.ts +16 -0
- package/src/execution/record-store.ts +280 -103
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +117 -47
- package/src/execution/settled-watchdog.ts +264 -52
- package/src/execution/subagent-actions-core.ts +84 -28
- package/src/execution/subagent-service.ts +1225 -964
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/sync-rebuild.ts +87 -0
- package/src/execution/types.ts +72 -0
- package/src/execution/ui-request-handler-factory.ts +102 -74
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/index.ts +442 -316
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/config-loader.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
- package/src/orchestration/__tests__/script-generate.test.ts +5 -5
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +1 -1
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +19 -73
- package/src/orchestration/launcher.ts +128 -81
- package/src/orchestration/lifecycle.ts +43 -45
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +2 -2
- package/src/orchestration/models/trace.ts +1 -1
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +1 -1
- package/src/orchestration/script-lint.ts +91 -58
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
- package/src/shared/__tests__/atomic-write.test.ts +1 -1
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
- package/src/shared/__tests__/resource-discovery.test.ts +258 -215
- package/src/shared/atomic-write.ts +12 -9
- package/src/shared/meta-parser.ts +158 -84
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +9 -183
- package/src/shared/schema-jsonify.ts +1 -1
- package/workflows/review-fix-loop-utils.cjs +151 -89
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
- package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
- package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// [D4-③ 冷路径复活职责轴] message action 冷查/复活链(原 SubagentService 私有的
|
|
2
|
+
// findColdLookupCandidate / assertReconnectAllowed / resurrectColdRecord /
|
|
3
|
+
// coldLookupForAction + isReconnectableClosed 判定)整体搬移至 record-store 邻接处
|
|
4
|
+
// (行为逐字节等价:搬移 + 依赖注入,不重写逻辑)。变化轴:改跨重启重建 /
|
|
5
|
+
// 透明重生回边 / 可重连守卫语义,只改本文件;Service 的 getRecordForAction 保留
|
|
6
|
+
// 归属校验编排(内存未命中分支委托 coldLookupForAction)。
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
|
|
10
|
+
import { findForeignLiveInstance, writeAliveMarker } from "./alive-store.ts";
|
|
11
|
+
import { createRecord, resurrectClosed } from "./execution-record.ts";
|
|
12
|
+
import type { StatusFilter } from "./record-store.ts";
|
|
13
|
+
import type { ExecutionRecord, SubagentRecord } from "./types.ts";
|
|
14
|
+
import { isReconnectableFinalReason, ResurrectDeniedError } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
/** SP-2 冷路径按 id 查 record 的 collectRecords 扫描上限(全扫兜底的容量 cap)。
|
|
17
|
+
* 原定义于 subagent-service.ts,随冷查链搬移;Service.lookupRecordAnyState
|
|
18
|
+
* (全态查询)同样消费本常量。 */
|
|
19
|
+
export const COLD_LOOKUP_SCAN_LIMIT = 1000;
|
|
20
|
+
|
|
21
|
+
/** 冷查/复活的依赖注入(Service 侧供给,store 查询 + 归属上下文)。 */
|
|
22
|
+
export interface ColdResurrectDeps {
|
|
23
|
+
/** store 的 idToFile 索引直查(light 快照)。 */
|
|
24
|
+
findLightById: (id: string) => SubagentRecord | undefined;
|
|
25
|
+
/** store 的磁盘全扫(内存未命中 / 索引未热时兜底)。rootFilter 恒 undefined
|
|
26
|
+
* (冷查不做 root 过滤——归属校验在候选定位之后,见 coldLookupForAction)。 */
|
|
27
|
+
collectRecords: (limit: number, statusFilter: StatusFilter, rootFilter: undefined) => SubagentRecord[];
|
|
28
|
+
/** 重建 record 注册进内存 store。 */
|
|
29
|
+
register: (record: ExecutionRecord) => void;
|
|
30
|
+
/** 重建后的 transition entry 上报(live ≡ reload 等价性)。 */
|
|
31
|
+
reportRecordTransition: (record: ExecutionRecord) => void;
|
|
32
|
+
/** 当前所属根 session id(归属校验用,运行时可变——initSession 建立)。 */
|
|
33
|
+
getSessionRootId: () => string | null;
|
|
34
|
+
/** 本进程嵌套基线 recordId(直接父校验用;根进程 undefined)。 */
|
|
35
|
+
getBaselineRecordId: () => string | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 冷查候选定位(coldLookupForAction 步骤 1):idToFile 索引直查 running 命中,
|
|
39
|
+
* 未命中再全目录 collectRecords 兜底(running,或 allowReconnect 且可重连 closed)。
|
|
40
|
+
*
|
|
41
|
+
* [T5③ / PS-7b] running 候选异进程活实例守卫:冷查 running 候选(跨重启 / 内存重建)
|
|
42
|
+
* 此前不经任何探针直接 resurrect + resume spawn——若其 .alive marker 仍指向活着的
|
|
43
|
+
* 异进程实例(父进程重启后旧子进程尚存的窗口),resume 会 spawn 第二个 pi 子进程
|
|
44
|
+
* 写同一 session JSONL(本代码最忌惮的双写者形态,v4 A-5/P7 事故模式)。closed 候选
|
|
45
|
+
* 的同款守卫已在 assertReconnectAllowed(v8.5 D);本守卫闭合 running 候选的防御
|
|
46
|
+
* 不对称。marker 的 pid 是子进程 pi 的 pid(非父进程),本进程持有的 running record
|
|
47
|
+
* 恒在内存(archive 才移出),可达本冷查分支的 running 候选必然来自磁盘重建——
|
|
48
|
+
* 探针命中即拒绝(ResurrectDeniedError,与 closed 候选守卫同异常类型,错误含 pid
|
|
49
|
+
* 与恢复指引)。 */
|
|
50
|
+
function findColdLookupCandidate(
|
|
51
|
+
deps: ColdResurrectDeps,
|
|
52
|
+
id: string,
|
|
53
|
+
allowReconnect: boolean,
|
|
54
|
+
): SubagentRecord | undefined {
|
|
55
|
+
const direct = deps.findLightById(id);
|
|
56
|
+
const found =
|
|
57
|
+
(direct?.status === "running" ? direct : undefined) ??
|
|
58
|
+
deps
|
|
59
|
+
.collectRecords(COLD_LOOKUP_SCAN_LIMIT, "all", undefined)
|
|
60
|
+
.find((r) => r.id === id && (r.status === "running" || (allowReconnect && isReconnectableClosed(r))));
|
|
61
|
+
if (found?.status === "running" && found.sessionFile) {
|
|
62
|
+
const foreign = findForeignLiveInstance(found.sessionFile);
|
|
63
|
+
if (foreign) {
|
|
64
|
+
throw new ResurrectDeniedError(
|
|
65
|
+
`subagent ${id} is currently running in another process instance (pid ${foreign.pid}, ` +
|
|
66
|
+
`startedAt=${new Date(foreign.startedAt).toISOString()}); resuming here would double-write ${found.sessionFile}. ` +
|
|
67
|
+
`Recovery: retry once that process exits; if it never exits, action:'close' this subagent, then action:'start' a fresh one.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** [v8.5 D] 冷查候选过滤:closed 且死因落在可重连集。判定源 = closedReason(buildRecord
|
|
75
|
+
* 归一化后的对外字段:A 档真实死因直通、旧空 sidecar 兑底 disconnected——SubagentRecord
|
|
76
|
+
* 不暴露 raw finalizedReason);cancelled/user-close/gc 等主动关闭与自然完成死因天然不在集合内。
|
|
77
|
+
* 防线在集合本身而非调用点。 */
|
|
78
|
+
function isReconnectableClosed(r: SubagentRecord): boolean {
|
|
79
|
+
return isReconnectableFinalReason(r.closedReason);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 可重连守卫(coldLookupForAction 步骤 2,[v8.5 D]):先于任何状态突变与注册。
|
|
83
|
+
* worktree 绑定丢失 / 异进程活实例以 ResurrectDeniedError 抛出(endedMessageGuard
|
|
84
|
+
* 必须原样透传,不得改写为 fork-from 指引误导 agent 走已被判死的通道);拒绝时
|
|
85
|
+
* 内存不得残留该记录(findRecord 契约)。 */
|
|
86
|
+
function assertReconnectAllowed(found: SubagentRecord, id: string): void {
|
|
87
|
+
if (found.status !== "closed") return;
|
|
88
|
+
if (found.worktree === true) {
|
|
89
|
+
throw new ResurrectDeniedError(
|
|
90
|
+
`subagent ${id} cannot be transparently resumed: it was created with worktree isolation, ` +
|
|
91
|
+
`and its worktree checkout no longer exists after restart (resuming in place would make spawn cwd fall back to the main repo). ` +
|
|
92
|
+
`Recovery: action:'start' a fresh subagent (with a new worktree if isolation is still needed); ` +
|
|
93
|
+
`its conversation history remains intact at ${found.sessionFile}.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const foreign = found.sessionFile ? findForeignLiveInstance(found.sessionFile) : undefined;
|
|
97
|
+
if (foreign) {
|
|
98
|
+
throw new ResurrectDeniedError(
|
|
99
|
+
`subagent ${id} is not transparently resumable: its previous instance still finishing in another process ` +
|
|
100
|
+
`(pid ${foreign.pid}, startedAt=${new Date(foreign.startedAt).toISOString()}). ` +
|
|
101
|
+
`Resuming in place would double-write ${found.sessionFile}. ` +
|
|
102
|
+
`Recovery: retry once that process exits; if it never exits, action:'start' a fresh subagent and treat the history at ${found.sessionFile} as read-only reference.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 磁盘候选重建为可变 record 并 register + 上报(coldLookupForAction 步骤 4)。 */
|
|
108
|
+
function resurrectColdRecord(
|
|
109
|
+
deps: ColdResurrectDeps,
|
|
110
|
+
found: SubagentRecord,
|
|
111
|
+
id: string,
|
|
112
|
+
): ExecutionRecord {
|
|
113
|
+
const record = createRecord(id, {
|
|
114
|
+
agent: found.agent,
|
|
115
|
+
model: found.model,
|
|
116
|
+
thinkingLevel: found.thinkingLevel,
|
|
117
|
+
mode: found.mode,
|
|
118
|
+
task: found.task,
|
|
119
|
+
slug: found.slug,
|
|
120
|
+
startedAt: found.startedAt,
|
|
121
|
+
rootSessionId: found.rootSessionId,
|
|
122
|
+
parentRecordId: found.parentRecordId,
|
|
123
|
+
depth: found.depth,
|
|
124
|
+
// [v4 A-3] 跨重启恢复入口——message 路径磁盘重建无条件置 chatMode=true(现状机制,
|
|
125
|
+
// V3 方案 A 方向兑现)。改动此处必须带 S3 回归场景(跨重启 message 续聊验证)。
|
|
126
|
+
// V3 SP-5 探针定案:机制已存在,本注释即定案,不再悬置。
|
|
127
|
+
chatMode: true,
|
|
128
|
+
controller: new AbortController(),
|
|
129
|
+
});
|
|
130
|
+
record.sessionFile = found.sessionFile;
|
|
131
|
+
record.round = found.round;
|
|
132
|
+
// [review round2] 跨重启 worktree 绑定丢失防护:原 record 创建时启用了 worktree 隔离
|
|
133
|
+
//(session entry 的 worktree 标志),但 WorktreeHandle 不可序列化、重建后恒缺失。
|
|
134
|
+
// 标记 hadWorktree,冷路径续轮守卫据此拒绝续聊(防 spawn cwd 静默回落主 repo 破坏
|
|
135
|
+
// 隔离——正是 worktree 要防的并发写冲突场景)。close 不受影响(closeChatIdle 走
|
|
136
|
+
// doFinalizeRecord,泄漏的 worktree 由 reaper 兜底回收)。
|
|
137
|
+
record.hadWorktree = found.worktree === true;
|
|
138
|
+
// [v8.5 D] 透明重生回边:独立函数不走 tryTransition 单向语义(closed 单向性对正常
|
|
139
|
+
// 执行流完整保留);准入唯一依据 = A 档 sidecar 真实死因 ∈ 可重连集。死亡语义位由
|
|
140
|
+
// resurrectClosed 清除;register 后立刻上报 transition entry,live/reload 视图同步
|
|
141
|
+
// 翻回 running(等价性由 applyEntry reducer 保证,对齐 SP-2 重建即报告先例)。
|
|
142
|
+
const wasClosed = found.status !== "running";
|
|
143
|
+
if (wasClosed) {
|
|
144
|
+
// [review MF-8] 磁盘终态位同步翻转:record-store buildRecord 分支 2(.finalized
|
|
145
|
+
// 存在 → closed)优先级高于 .alive 活态分支 3,重生若不删 sidecar,任何磁盘扫描
|
|
146
|
+
// (异进程 / reload / session-reader)都会把本进程内存里 running 的 record 报成
|
|
147
|
+
// closed/disconnected——破坏 live ≡ reload,且为跨进程二次 resurrect 开门。
|
|
148
|
+
// best-effort 对齐 BC-4 语义;.alive 刷新为当前进程(后续 resume spawn 会覆盖写)。
|
|
149
|
+
if (record.sessionFile) {
|
|
150
|
+
try {
|
|
151
|
+
fs.rmSync(`${record.sessionFile}.finalized`, { force: true });
|
|
152
|
+
writeAliveMarker(record.sessionFile, { pid: process.pid, id, startedAt: Date.now() });
|
|
153
|
+
} catch (_e) {
|
|
154
|
+
void _e; // best-effort:sidecar 翻转失败不阻断重生主流程
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
resurrectClosed(record);
|
|
158
|
+
}
|
|
159
|
+
deps.register(record);
|
|
160
|
+
if (wasClosed) {
|
|
161
|
+
deps.reportRecordTransition(record);
|
|
162
|
+
}
|
|
163
|
+
return record;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** [D4-③] 冷查编排(原 Service.coldLookupForAction):getRecordForAction 内存未命中
|
|
167
|
+
* 分支——候选定位 → 可重连守卫 → 归属/直接父校验 → 重建注册。
|
|
168
|
+
* @returns 重建的 record;磁盘也无则 undefined
|
|
169
|
+
* @throws ResurrectDeniedError 可重连候选被 worktree/异进程活实例守卫拦截
|
|
170
|
+
* @throws Error parentRecordId 跨层不匹配(direct parent 错误,与外层校验同文案) */
|
|
171
|
+
export function coldLookupForAction(
|
|
172
|
+
deps: ColdResurrectDeps,
|
|
173
|
+
id: string,
|
|
174
|
+
allowReconnect: boolean,
|
|
175
|
+
): ExecutionRecord | undefined {
|
|
176
|
+
const found = findColdLookupCandidate(deps, id, allowReconnect);
|
|
177
|
+
if (!found) return undefined;
|
|
178
|
+
// [v8.5 D] 可重连候选的守卫先于任何状态突变与注册(细节见 assertReconnectAllowed)
|
|
179
|
+
assertReconnectAllowed(found, id);
|
|
180
|
+
// [review MF-9] 归属校验先于任何持久化副作用:coldLookup 是 getRecordForAction 的
|
|
181
|
+
// 内存未命中分支,若先 resurrect/register/report 再由调用方抛归属错误,会在磁盘/
|
|
182
|
+
// 内存留下幽灵 running record + running transition entry(跨进程双 resurrect 窗口)。
|
|
183
|
+
// rootSessionId 不匹配 → 返回 undefined,由 getRecordForAction 抛统一「not found or
|
|
184
|
+
// not owned」(不区分失败形态,防跨 session 探测);parentRecordId 跨层不匹配 →
|
|
185
|
+
// 原样抛 direct parent 错误(与外层校验同文案,保留跨层导航指引)。
|
|
186
|
+
if (found.rootSessionId !== deps.getSessionRootId()) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const baselineRecordId = deps.getBaselineRecordId();
|
|
190
|
+
if (found.parentRecordId !== baselineRecordId) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`subagent ${id} is owned by its direct parent; message it through that parent ` +
|
|
193
|
+
`(see /subagents list, parent=${found.parentRecordId ?? "(root layer)"}). [v4 A-5] cross-layer ` +
|
|
194
|
+
`ownership guard: this process's baseline=${baselineRecordId ?? "(root)"} is not the direct parent of ${id}; ` +
|
|
195
|
+
`operating here would race the owning child process's handle and double-write the session file.`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return resurrectColdRecord(deps, found, id);
|
|
199
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// src/execution/collect-coordinator.ts
|
|
2
|
+
//
|
|
3
|
+
// collectCoordinator:notifyComplete 唯一路由入口——async 直通 / sync 批缓冲 / 闭合检测
|
|
4
|
+
// (subagent-sync-collect 设计 §3.1.4 终态数据流 / D2 隐式批 / D5 service 层缓冲)。
|
|
5
|
+
//
|
|
6
|
+
// 职责(U2):
|
|
7
|
+
// route(record):
|
|
8
|
+
// - record.collectMode !== "sync" → deps.notifyAsync(现状 notifier.notify 路径,
|
|
9
|
+
// 执行序列逐字节一致,G3/A5 零回归);
|
|
10
|
+
// - record.collectMode === "sync" → deps.toNotifyRecord 终态快照登记入缓冲(内存,
|
|
11
|
+
// 登记时定格——record 对象后续可变不影响快照)→ 闭合检测。
|
|
12
|
+
//
|
|
13
|
+
// 闭合判定(⛔3 已核实 U1,impl-plan §7):缓冲非空 && 无非终态 sync 成员。
|
|
14
|
+
// 「非终态」口径 = collectMode="sync" && 无 batchFinalized 标记 && status !== "closed"
|
|
15
|
+
// ——池排队成员在 store.register 时即 status="running",自动计入非终态(闭合等待它);
|
|
16
|
+
// batchFinalized=true 的已离场成员不阻止闭合。
|
|
17
|
+
//
|
|
18
|
+
// 跨轮续累(D2 隐式批):缓冲跨多次 route 累积不清空(分两轮派 2+1 sync → 闭合时
|
|
19
|
+
// 单批三成员);flush 后缓冲清空,后续 sync 成员自然开新批(语义 = "等所有 sync 待收")。
|
|
20
|
+
//
|
|
21
|
+
// [U8 拆批盲窗修复] 闭合满足 → 同宏任务去抖合批 flush(不再立即 flush):
|
|
22
|
+
// 盲窗机理:kickOffBackground 的 runAndFinalize(completeRecord+archive,record 从
|
|
23
|
+
// store.listAllActive 移除)先于 .then 链的 notifyComplete。同同步段背靠背终态时,
|
|
24
|
+
// 成员 A route 触发闭合检测(只扫 listAllActive 非终态)看不到「已 archive 未路由」
|
|
25
|
+
// 的成员 B → 立即 flush([A]);B route 再 flush([B]) → 拆两条单成员批(G1 弱化,
|
|
26
|
+
// 单唤醒变两次)。
|
|
27
|
+
// 修复:闭合首次满足改排程 flush(setTimeout(0),Node 1ms clamp——同一宏任务级窗口,
|
|
28
|
+
// 确定性、无可观察延迟);窗口内后续 route 落缓冲后一并 flush(背靠背成员合单批)。
|
|
29
|
+
// 触发时重验闭合条件:窗口内若有新 sync 成员注册 running(跨轮续累反转)→ 保留缓冲
|
|
30
|
+
// 不 flush,等其终态 route 重新排程(终态必经 notifyComplete,不丢、不悬挂)。
|
|
31
|
+
// 交互语义(授权修复轮决策):
|
|
32
|
+
// - E9 dispose:service.convertPendingSyncBufferToAsync 先 cancelScheduledFlush
|
|
33
|
+
// (取消而非同步 flush——放任触发会与逐条转 async 写账双通道并发 → 同成员双投递,
|
|
34
|
+
// 且触发点可能落在 notifier/store dispose 之后);取消后缓冲原样保留,E9 单通路
|
|
35
|
+
// 完整接管(选语义最简)。
|
|
36
|
+
// - E1 恢复补发:只在 session_start 编排处运行(此前 dispose 已取消挂起排程),
|
|
37
|
+
// 补发直走 notifier.notifyBatch 不经协调器;异常时序相撞由账本 sync-batch:<hash>
|
|
38
|
+
// 幂等拒绝兜底。
|
|
39
|
+
//
|
|
40
|
+
// flush 注入式(U3 已接线):deps.flushBatch 由宿主注入——U3 接 service 闭包的
|
|
41
|
+
//「manifest 屏障(await 落盘)→ notifier.notifyBatch 单条批投递(ledger sync-batch
|
|
42
|
+
// hash 写账)→ batchFinalized 落标」(见 subagent-service.ts flushBatch 闭包)。
|
|
43
|
+
// 测试注入 fake 断言闭合条件。flush 异步(Promise<void>)、协调器 void 调用不等待:
|
|
44
|
+
// 缓冲在调用前已整体移交清空,flush 内部时序(屏障 → 写账 → 落标)与失败语义归
|
|
45
|
+
// 实现方(与 notifier.notify 同风格)。
|
|
46
|
+
//
|
|
47
|
+
// 不变量:
|
|
48
|
+
// - 每个成员只登记一次:notifyComplete 调用点自带 CAS/gate 单次性(与既有 notify
|
|
49
|
+
// 单次性同源——cancelBackground CAS 抢锁 / kickOffBackground.then gate / watchdog
|
|
50
|
+
// CAS),协调器不重复去重,不改变 at-least-once 语义。
|
|
51
|
+
// - toNotifyRecord 返回 undefined(gate 未过/非终态)→ 静默跳过不入缓冲,与 async
|
|
52
|
+
// 路径的静默跳过对称——调用点 gate 语义不变,协调器不重复判定。
|
|
53
|
+
// - 闭合判定只敏感于「是否还有非终态 sync」:本条终态在 store 的可见性(archive 后
|
|
54
|
+
// 磁盘重建时序)不影响判定正确性。
|
|
55
|
+
// - [U8] 排程幂等:窗口内多条 route 至多一个挂起定时器;flush 只在触发时重验后执行,
|
|
56
|
+
// 缓冲在排程期内保持可枚举(pendingMembers 供 E9 dispose 转换读取)。
|
|
57
|
+
|
|
58
|
+
import type { BgNotifyRecord } from "./notifier.ts";
|
|
59
|
+
import type { ExecutionRecord } from "./types.ts";
|
|
60
|
+
|
|
61
|
+
/** 闭合判定的 record 最小视图(ExecutionRecord / SubagentRecord 的同构子集)。
|
|
62
|
+
*
|
|
63
|
+
* [U3 微调 / U5 已修] 原 deps 用 SubagentRecord——生产通路 collectRecords 经
|
|
64
|
+
* recordToSubagent 投影曾不含 collectMode/batchFinalized(U2 披露,U5 已补投影),
|
|
65
|
+
* 保留结构化最小接口 + service 接线走 store.listAllActive()(原始 ExecutionRecord
|
|
66
|
+
* 内存态,不经投影)——非终态 sync 成员必在内存(archive 只删终态),内存视图语义
|
|
67
|
+
* 完整;纯注入测试的 SubagentRecord stub 结构兼容零改动。 */
|
|
68
|
+
export interface CollectScanRecord {
|
|
69
|
+
status: string;
|
|
70
|
+
collectMode?: "sync";
|
|
71
|
+
batchFinalized?: boolean;
|
|
72
|
+
/** SP-5 执行态信号:true = 无活进程驱动的 running(one-shot 成功回退态)。
|
|
73
|
+
* [U3 补丁] ⛔3 非终态口径的必要补充——one-shot 成功完成后 record 不 archive,
|
|
74
|
+
* 以 running+resumable 留内存等 message 升级(真链 trace 实证):结果已定格、
|
|
75
|
+
* 进程已死 = 已完成待通知,不阻止闭合;否则 sync 批永不闭合。 */
|
|
76
|
+
resumable?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 闭合判定的全 record 扫描上限(service 冷路径 COLD_LOOKUP_SCAN_LIMIT 同量级)。 */
|
|
80
|
+
export const COLLECT_SCAN_LIMIT = 1000;
|
|
81
|
+
|
|
82
|
+
/** 协调器宿主依赖(全部注入——协调器零 service/store 直依,可独立单测)。 */
|
|
83
|
+
export interface CollectCoordinatorDeps {
|
|
84
|
+
/** async 直通(现状 notifier.notify 路径,字节不变)。 */
|
|
85
|
+
notifyAsync(record: ExecutionRecord): void;
|
|
86
|
+
/** record → 通知快照(登记时定格)。undefined = gate 未过/非终态(静默跳过)。 */
|
|
87
|
+
toNotifyRecord(record: ExecutionRecord): BgNotifyRecord | undefined;
|
|
88
|
+
/** 闭合判定数据源(CollectScanRecord 最小视图,见接口注释——数据源须携带
|
|
89
|
+
* collectMode/batchFinalized 原始值,禁经 recordToSubagent 投影)。 */
|
|
90
|
+
listRecords(limit: number): CollectScanRecord[];
|
|
91
|
+
/** 批投递回调(合批窗口到期触发,缓冲整体移交后清空)。U3 接 service 闭包:
|
|
92
|
+
* manifest 屏障 → notifier.notifyBatch 写账 → batchFinalized 落标——屏障先于写账
|
|
93
|
+
* 是「通知可达 ⇒ 索引就位」的构造性保证,故异步;协调器 void 调用不等待其完成。 */
|
|
94
|
+
flushBatch(members: BgNotifyRecord[]): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** route 去向(测试/诊断断言用)。 */
|
|
98
|
+
export type CollectRouteResult =
|
|
99
|
+
/** 非 sync record:notifyAsync 直通(现状路径)。 */
|
|
100
|
+
| "async"
|
|
101
|
+
/** gate 未过(toNotifyRecord undefined):静默跳过,不入缓冲。 */
|
|
102
|
+
| "sync-skipped"
|
|
103
|
+
/** 入缓冲未闭合(仍有非终态 sync 成员)。 */
|
|
104
|
+
| "sync-buffered"
|
|
105
|
+
/** 闭合触发([U8] flush 已排程合批——同宏任务窗口内的后续 route 一并投递)。 */
|
|
106
|
+
| "sync-flushed";
|
|
107
|
+
|
|
108
|
+
export class CollectCoordinator {
|
|
109
|
+
/** 批缓冲(内存):已终态未 flush 的 sync 成员终态快照,跨 route 累积。
|
|
110
|
+
* [U8] 排程合批窗口期内保持可枚举(E9 dispose 转换依赖 pendingMembers 全量)。 */
|
|
111
|
+
private readonly buffer: BgNotifyRecord[] = [];
|
|
112
|
+
|
|
113
|
+
/** [U8] 挂起的合批排程定时器(undefined = 无排程)。 */
|
|
114
|
+
private flushTimer: ReturnType<typeof setTimeout> | undefined;
|
|
115
|
+
|
|
116
|
+
constructor(private readonly deps: CollectCoordinatorDeps) {}
|
|
117
|
+
|
|
118
|
+
/** notifyComplete 唯一路由入口。返回去向供测试/诊断断言。 */
|
|
119
|
+
route(record: ExecutionRecord): CollectRouteResult {
|
|
120
|
+
if (record.collectMode !== "sync") {
|
|
121
|
+
this.deps.notifyAsync(record);
|
|
122
|
+
return "async";
|
|
123
|
+
}
|
|
124
|
+
const snapshotRecord = this.deps.toNotifyRecord(record);
|
|
125
|
+
if (!snapshotRecord) return "sync-skipped";
|
|
126
|
+
this.buffer.push(snapshotRecord);
|
|
127
|
+
return this.armFlushIfClosed() ? "sync-flushed" : "sync-buffered";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 诊断/测试:当前缓冲成员数(= 已终态未 flush 的 sync 成员数,含合批窗口内成员)。 */
|
|
131
|
+
get pendingCount(): number {
|
|
132
|
+
return this.buffer.length;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 诊断/测试 + E9 dispose 转换数据源:缓冲成员快照(副本,非活引用)。 */
|
|
136
|
+
pendingMembers(): BgNotifyRecord[] {
|
|
137
|
+
return [...this.buffer];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** [U8·E9 交互] 取消挂起的合批排程(缓冲原样保留——dispose 转 async 通路仍读得到
|
|
141
|
+
* 全部成员)。无排程时 no-op;幂等。 */
|
|
142
|
+
cancelScheduledFlush(): void {
|
|
143
|
+
if (this.flushTimer !== undefined) {
|
|
144
|
+
clearTimeout(this.flushTimer);
|
|
145
|
+
this.flushTimer = undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 闭合判定 + 合批排程武装:缓冲非空 && 无非终态 sync 成员 → 排程 flush。
|
|
150
|
+
* 已有排程挂起时幂等(同窗口合批,不重复定时器)。(S6 改名:原名 closeDetected
|
|
151
|
+
* 读作纯检测,实带副作用——武装排程改状态,名实相符优先。) */
|
|
152
|
+
private armFlushIfClosed(): boolean {
|
|
153
|
+
if (this.buffer.length === 0) return false;
|
|
154
|
+
if (this.hasRunningSync()) return false;
|
|
155
|
+
this.scheduleFlush();
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** [U8] 同宏任务去抖合批排程:setTimeout(0)(Node 1ms clamp)——窗口跨度覆盖一个
|
|
160
|
+
* 完整事件循环轮次(当前 poll 段 I/O 回调 + 微任务链排空),足以收纳 finalize 链
|
|
161
|
+
* 间隙内的背靠背 route,同时无可观察延迟(毫秒级 < 任意 LLM/投递时标)。 */
|
|
162
|
+
private scheduleFlush(): void {
|
|
163
|
+
if (this.flushTimer !== undefined) return;
|
|
164
|
+
this.flushTimer = setTimeout(() => {
|
|
165
|
+
this.flushTimer = undefined;
|
|
166
|
+
this.flushIfClosed();
|
|
167
|
+
}, 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** 合批窗口到期:重验闭合条件后整体移交 flushBatch 并清空。
|
|
171
|
+
* 窗口内新 sync 成员注册 running(跨轮续累反转)→ 保留缓冲不 flush,等其终态
|
|
172
|
+
* route 重新排程(终态必经 notifyComplete,不丢、不悬挂)。
|
|
173
|
+
* void 不等待 flush 的 async 屏障序列(manifest → 写账 → 落标):缓冲已 splice
|
|
174
|
+
* 先行整体移交,屏障 await 期间窗口内新成员重新入空缓冲开新批,互不干扰。 */
|
|
175
|
+
private flushIfClosed(): void {
|
|
176
|
+
if (this.buffer.length === 0) return;
|
|
177
|
+
if (this.hasRunningSync()) return;
|
|
178
|
+
const members = this.buffer.splice(0, this.buffer.length);
|
|
179
|
+
void this.deps.flushBatch(members);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 是否存在非终态 sync 成员(collectMode=sync && 无 batchFinalized && 非终态)。
|
|
183
|
+
* 非终态口径(⛔3 U1 已核实 + U3 resumable 补丁):status 非 closed 且非 resumable
|
|
184
|
+
* ——池排队/在跑成员在 store.register 时即 status="running",自动计入(闭合等待它);
|
|
185
|
+
* batchFinalized=true 的已离场成员不阻止闭合;running+resumable(SP-5 one-shot
|
|
186
|
+
* 成功回退态,进程已死结果已定格)视为已完成,不阻止闭合。 */
|
|
187
|
+
private hasRunningSync(): boolean {
|
|
188
|
+
for (const record of this.deps.listRecords(COLLECT_SCAN_LIMIT)) {
|
|
189
|
+
if (
|
|
190
|
+
record.collectMode === "sync" &&
|
|
191
|
+
record.batchFinalized !== true &&
|
|
192
|
+
record.resumable !== true &&
|
|
193
|
+
record.status !== "closed"
|
|
194
|
+
) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
package/src/execution/config.ts
CHANGED
|
@@ -9,7 +9,11 @@ import * as path from "node:path";
|
|
|
9
9
|
|
|
10
10
|
import { getLogger } from "../core/logger.ts";
|
|
11
11
|
|
|
12
|
-
import type { SubagentsGlobalConfig } from "./types.ts";
|
|
12
|
+
import type { CollectSyncConfig, SubagentsGlobalConfig } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
// 同步收集配置节类型 re-export(类型权威定义在 types.ts,避免 config → types 反向依赖成环;
|
|
15
|
+
// 消费方从本模块 import 与从 types.ts import 等价,U1 foundation 契约)。
|
|
16
|
+
export type { CollectSyncConfig };
|
|
13
17
|
|
|
14
18
|
// 包内 execution 模块统一具名 logger(getLogger 缓存单例,见 notify-ledger.ts 同款)。
|
|
15
19
|
const logger = getLogger("subagents");
|
|
@@ -33,8 +37,25 @@ export const DEFAULT_CONFIG: SubagentsGlobalConfig = {
|
|
|
33
37
|
maxConcurrent: 6,
|
|
34
38
|
};
|
|
35
39
|
|
|
40
|
+
// 注意:DEFAULT_CONFIG 刻意不含 collectSync 键(与 defaultEngine/engineRouting 同风格)——
|
|
41
|
+
// SW 包 package.json 的 pi.startupConfig 声明守护(startup-config-declaration.test.ts)
|
|
42
|
+
// 断言与 DEFAULT_CONFIG 深相等,声明面只含既有键。collectSync 缺省语义由
|
|
43
|
+
// DEFAULT_COLLECT_SYNC 承载,消费方以 `config.collectSync ?? DEFAULT_COLLECT_SYNC` 兜底
|
|
44
|
+
// (loadGlobalConfig 文件缺失/坏 JSON 路径返回 {...DEFAULT_CONFIG} 同样无键)。
|
|
45
|
+
|
|
36
46
|
/** 默认 maxConcurrent(DEFAULT_CONFIG 的镜像,sanitize 用)。 */
|
|
37
47
|
const DEFAULT_MAX_CONCURRENT = 6;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 同步收集配置节权威默认值(subagent-sync-collect 设计 §3.1.3,U1 foundation)。
|
|
51
|
+
* 消费方:startHandler 缺省模式解析 / U3+U4 批通知预算(flush 时热读)。
|
|
52
|
+
* 供守护测试断言(E5 各坏值回默认)。展开拷贝防调用方 mutate。
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_COLLECT_SYNC: CollectSyncConfig = {
|
|
55
|
+
default: "async",
|
|
56
|
+
perItemChars: 4000,
|
|
57
|
+
totalChars: 24000,
|
|
58
|
+
};
|
|
38
59
|
// ============================================================
|
|
39
60
|
// 路径
|
|
40
61
|
// ============================================================
|
|
@@ -133,19 +154,70 @@ function errnoCodeOf(err: unknown): string | undefined {
|
|
|
133
154
|
function sanitizeParsedConfig(parsed: Partial<SubagentsGlobalConfig>): SubagentsGlobalConfig {
|
|
134
155
|
const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
|
|
135
156
|
const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
|
|
157
|
+
const collectSync = sanitizeCollectSync(parsed.collectSync);
|
|
136
158
|
return {
|
|
137
159
|
version: parsed.version ?? DEFAULT_CONFIG.version,
|
|
138
160
|
maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
|
|
139
161
|
...(defaultEngine !== undefined ? { defaultEngine } : {}),
|
|
140
162
|
...(engineRouting !== undefined ? { engineRouting } : {}),
|
|
163
|
+
...(collectSync !== undefined ? { collectSync } : {}),
|
|
141
164
|
};
|
|
142
165
|
}
|
|
143
166
|
|
|
144
|
-
/** maxConcurrent
|
|
167
|
+
/** maxConcurrent 校验:正整数,否则默认(S5:委托 sanitizePositiveInt,正整数规则单点化)。 */
|
|
145
168
|
function sanitizeMaxConcurrent(value: unknown): number {
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
return sanitizePositiveInt(value, DEFAULT_MAX_CONCURRENT);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* collectSync 节校验(E5:负数/非枚举/坏类型回默认,不炸启动;与 maxConcurrent 同判。
|
|
174
|
+
* subagent-sync-collect 设计 §3.1.5 E5 + §3.1.3)。逐字段 sanitize:坏字段回该字段
|
|
175
|
+
* 默认,好字段透传(部分覆盖合法)。整节缺失返回 undefined(不落键,与 engineRouting
|
|
176
|
+
* 同风格——缺省语义由消费方读 DEFAULT_COLLECT_SYNC)。
|
|
177
|
+
*
|
|
178
|
+
* [D6 #8] 字段级坏值回默认时 warn 一条(缺省字段不 warn——部分覆盖合法):config.json
|
|
179
|
+
* 是用户手编文件,静默回落使「改了配置不生效」无从排查(sanitize 行为不变,只补可观测)。
|
|
180
|
+
*
|
|
181
|
+
* 消费时机:startHandler 缺省 collect 解析(config.default,新 session 生效)与
|
|
182
|
+
* U3/U4 批通知预算(perItemChars/totalChars,flush 时热读)。
|
|
183
|
+
*/
|
|
184
|
+
export function sanitizeCollectSync(value: unknown): CollectSyncConfig | undefined {
|
|
185
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
186
|
+
const v = value as Record<string, unknown>;
|
|
187
|
+
const fallback = DEFAULT_COLLECT_SYNC;
|
|
188
|
+
// [D6 #8] 坏值字段清单(仅「显式给了坏值」计——缺省是合法部分覆盖,不 warn)。
|
|
189
|
+
// 值格式化不用 JSON.stringify:Symbol/BigInt 等 stringify 返回 undefined,String() 全类型安全。
|
|
190
|
+
const fmt = (val: unknown): string => (typeof val === "string" ? JSON.stringify(val) : String(val));
|
|
191
|
+
const bad: string[] = [];
|
|
192
|
+
if (v.default !== undefined && v.default !== "sync" && v.default !== "async") {
|
|
193
|
+
bad.push(`default=${fmt(v.default)}`);
|
|
194
|
+
}
|
|
195
|
+
if (v.perItemChars !== undefined && !isPositiveInt(v.perItemChars)) {
|
|
196
|
+
bad.push(`perItemChars=${fmt(v.perItemChars)}`);
|
|
197
|
+
}
|
|
198
|
+
if (v.totalChars !== undefined && !isPositiveInt(v.totalChars)) {
|
|
199
|
+
bad.push(`totalChars=${fmt(v.totalChars)}`);
|
|
200
|
+
}
|
|
201
|
+
if (bad.length > 0) {
|
|
202
|
+
logger.warn(
|
|
203
|
+
`[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
default: v.default === "sync" ? "sync" : v.default === "async" ? "async" : fallback.default,
|
|
208
|
+
perItemChars: sanitizePositiveInt(v.perItemChars, fallback.perItemChars),
|
|
209
|
+
totalChars: sanitizePositiveInt(v.totalChars, fallback.totalChars),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** 正整数校验(collectSync 预算字段用;与 sanitizeMaxConcurrent 同判)。 */
|
|
214
|
+
function sanitizePositiveInt(value: unknown, fallback: number): number {
|
|
215
|
+
return isPositiveInt(value) ? (value as number) : fallback;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 正整数判定(sanitizePositiveInt 的探测面,坏值 warn 清单复用)。 */
|
|
219
|
+
function isPositiveInt(value: unknown): boolean {
|
|
220
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
149
221
|
}
|
|
150
222
|
|
|
151
223
|
/**
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// capability-gate.test.ts —— [D3-④ 预检 capabilities 化] 拦截矩阵单测。
|
|
2
|
+
// 设计权威源:docs/design/subagent-dual-track-convergence.md §3.3 D3-④(r3 裁定:
|
|
3
|
+
// EngineCapabilities +maxTurns 位 pi=true/zcode=false,不保留硬编码 shape 检查)+
|
|
4
|
+
// §3.4 错误规格第 1 行 + §4 V4④⑤(正反向验收)。
|
|
5
|
+
//
|
|
6
|
+
// 覆盖(构建者白盒 + 使用者黑盒):
|
|
7
|
+
// 1. per-engine 拦截矩阵:pi 全放行(含 maxTurns/fork/conversation/worktree——V4⑤
|
|
8
|
+
// 反向守护,pi 既有合法能力零拦截);zcode 四参数全拦(fork/conversation/
|
|
9
|
+
// maxTurns/worktree)
|
|
10
|
+
// 2. 错误族:engine_capability_unsupported + recovery 含可操作指引(换参数/换引擎)
|
|
11
|
+
// 3. fork 判据(借位裁定):session 分叉通道族任一可用即放行——pi conversation=
|
|
12
|
+
// native 放行(steer 虽 unsupported 不参与否决);通道族全缺才拦
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
|
|
16
|
+
import { assertTaskShapeSupported, type TaskShapeForGate } from "../../common/capability-gate.ts";
|
|
17
|
+
import { EngineError } from "../../common/errors.ts";
|
|
18
|
+
import type { EngineCapabilities } from "../../types.ts";
|
|
19
|
+
import { PiEngine } from "../../engines/pi/pi-engine.ts";
|
|
20
|
+
import { ZcodeEngine } from "../../engines/zcode/zcode-engine.ts";
|
|
21
|
+
|
|
22
|
+
/** 真实引擎的 capabilities 面单测直取(D3 声明是判据本体——pi=true/zcode=false 扩位)。 */
|
|
23
|
+
const PI_CAPS = new PiEngine({ getService: () => null }).capabilities();
|
|
24
|
+
const ZCODE_CAPS = new ZcodeEngine({ engineDataDir: () => "/tmp/zcode-gate" }).capabilities();
|
|
25
|
+
|
|
26
|
+
/** 会话分叉通道族可用的 zcode 形态变体(fork 判据用)。 */
|
|
27
|
+
const ZCODE_CAPS_WITH_CONVERSATION: EngineCapabilities = { ...ZCODE_CAPS, conversation: "native" };
|
|
28
|
+
|
|
29
|
+
function gate(
|
|
30
|
+
caps: EngineCapabilities,
|
|
31
|
+
task: TaskShapeForGate,
|
|
32
|
+
engineId = "zcode",
|
|
33
|
+
): void {
|
|
34
|
+
assertTaskShapeSupported(engineId, caps, task);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function gateError(caps: EngineCapabilities, task: TaskShapeForGate, engineId = "zcode"): EngineError {
|
|
38
|
+
try {
|
|
39
|
+
gate(caps, task, engineId);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
expect(err).toBeInstanceOf(EngineError);
|
|
42
|
+
return err as EngineError;
|
|
43
|
+
}
|
|
44
|
+
throw new Error("expected assertTaskShapeSupported to throw");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("capability-gate(D3-④ 拦截矩阵)", () => {
|
|
48
|
+
it("能力位声明:pi maxTurns=true、zcode maxTurns=false(r3 扩位裁定)", () => {
|
|
49
|
+
expect(PI_CAPS.maxTurns).toBe(true);
|
|
50
|
+
expect(ZCODE_CAPS.maxTurns).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("[V4⑤ 反向] pi 引擎全参数放行:maxTurns/fork/forkFromSessionFile/conversation/worktree 零拦截", () => {
|
|
54
|
+
expect(() =>
|
|
55
|
+
gate(PI_CAPS, { maxTurns: 3, fork: true, conversation: true, worktree: true }, "pi"),
|
|
56
|
+
).not.toThrow();
|
|
57
|
+
expect(() => gate(PI_CAPS, { forkFromSessionFile: "/tmp/sess.jsonl", worktree: { path: "/tmp/wt" } }, "pi")).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("[V4④ 正向] zcode 引擎四参数全拦:fork/conversation/maxTurns/worktree → engine_capability_unsupported", () => {
|
|
61
|
+
const cases: Array<[TaskShapeForGate, RegExp]> = [
|
|
62
|
+
[{ fork: true }, /不支持 fork/],
|
|
63
|
+
[{ forkFromSessionFile: "/tmp/sess.jsonl" }, /fork-from 同为父 session 上下文继承/],
|
|
64
|
+
[{ conversation: true }, /不支持 conversation/],
|
|
65
|
+
[{ maxTurns: 10 }, /不支持 maxTurns/],
|
|
66
|
+
[{ worktree: true }, /不支持 worktree 隔离/],
|
|
67
|
+
[{ worktree: { path: "/tmp/wt" } }, /不支持 worktree 隔离/],
|
|
68
|
+
];
|
|
69
|
+
for (const [task, pattern] of cases) {
|
|
70
|
+
const err = gateError(ZCODE_CAPS, task);
|
|
71
|
+
expect(err.code).toBe("engine_capability_unsupported");
|
|
72
|
+
expect(err.message).toMatch(pattern);
|
|
73
|
+
expect(err.message).toContain("capabilities");
|
|
74
|
+
// 错误信息可操作(§3.4):恢复指引指向换参数/换引擎
|
|
75
|
+
expect(err.recovery).toMatch(/engine: pi|不传|去掉/);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("zcode 放行面:无能力参数的任务全放行;maxTurns undefined(未传)不拦", () => {
|
|
80
|
+
expect(() => gate(ZCODE_CAPS, {})).not.toThrow();
|
|
81
|
+
expect(() => gate(ZCODE_CAPS, { maxTurns: undefined, fork: false, conversation: false, worktree: false })).not.toThrow();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("fork 判据(借位裁定):session 分叉通道族任一可用即放行——conversation=native 的引擎 fork 放行", () => {
|
|
85
|
+
// pi 的 steer 声明 unsupported(spawn 链路未接通),fork 仍放行——通道族判定不因
|
|
86
|
+
// 单一通道缺失而误拦(上面 pi 用例已覆盖)。此处补 zcode 形态 + conversation 通道
|
|
87
|
+
// 可用的对照:fork 从拦截翻为放行。
|
|
88
|
+
expect(() => gate(ZCODE_CAPS_WITH_CONVERSATION, { fork: true })).not.toThrow();
|
|
89
|
+
expect(() => gate({ ...ZCODE_CAPS, steer: "native" }, { fork: true })).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -49,7 +49,7 @@ describe("getEngineDataDir", () => {
|
|
|
49
49
|
|
|
50
50
|
it("缺 env → 回退宿主数据根(HostServices.dataRoot(),假宿主值)并 warn 一次", () => {
|
|
51
51
|
const warnings: string[] = [];
|
|
52
|
-
const warn = (m: string): void => warnings.push(m);
|
|
52
|
+
const warn = (m: string): void => { warnings.push(m); };
|
|
53
53
|
const dir1 = getEngineDataDir({}, warn);
|
|
54
54
|
expect(dir1).toBe(FAKE_DATA_ROOT);
|
|
55
55
|
expect(warnings.length).toBe(1);
|
|
@@ -25,7 +25,7 @@ function tmpPath(name: string): string {
|
|
|
25
25
|
|
|
26
26
|
afterEach(() => {
|
|
27
27
|
if (tmpRoot !== undefined) {
|
|
28
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
28
|
+
rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
29
29
|
tmpRoot = undefined;
|
|
30
30
|
}
|
|
31
31
|
});
|