@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
|
@@ -20,7 +20,7 @@ const { loggerMock, killEscalationSpy } = vi.hoisted(() => ({
|
|
|
20
20
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
21
21
|
|
|
22
22
|
// mock session-runner(import 链需要 runSpawn/killAllSpawnedChildren/getChildByRecord 存在)
|
|
23
|
-
vi.mock("../session-runner.ts", () => ({
|
|
23
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
24
24
|
runSpawn: vi.fn(),
|
|
25
25
|
killAllSpawnedChildren: vi.fn(),
|
|
26
26
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -30,12 +30,13 @@ vi.mock("../session-runner.ts", () => ({
|
|
|
30
30
|
}));
|
|
31
31
|
|
|
32
32
|
import { createRecord, updateFromEvent } from "../execution-record.ts";
|
|
33
|
+
import { createNotifyHost } from "../notify-host.ts";
|
|
33
34
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
34
35
|
import { RecordStore } from "../record-store.ts";
|
|
35
36
|
import { SubagentService } from "../subagent-service.ts";
|
|
36
37
|
import type { PiLike } from "../subagent-service.ts";
|
|
37
38
|
import type { ExecutionRecord } from "../types.ts";
|
|
38
|
-
import { getChildByRecord } from "../session-runner.ts";
|
|
39
|
+
import { getChildByRecord } from "../engine/engines/pi/session-runner.ts";
|
|
39
40
|
|
|
40
41
|
// [T2④] kill 收敛入口 spy(工厂引用,断言服务侧三条路径收敛到它)
|
|
41
42
|
const killRecordChildWithEscalationMock = killEscalationSpy;
|
|
@@ -95,7 +96,7 @@ function setup(): {
|
|
|
95
96
|
pi: MockPi;
|
|
96
97
|
} {
|
|
97
98
|
const agentDir = makeTmpAgentDir();
|
|
98
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
99
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
99
100
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
100
101
|
// [C2] pi 提为外层引用(对照 real-chain :139-140 形态)——close 现状语义断言需读
|
|
101
102
|
// pi.sendMessage.mock.calls(末条通知 content 指针行),内联构造不保留引用。
|
|
@@ -121,23 +122,23 @@ describe("getRecordForAction 归属守卫(决策 3)", () => {
|
|
|
121
122
|
|
|
122
123
|
afterEach(() => {
|
|
123
124
|
service.dispose();
|
|
124
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
125
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
it("正常(rootSessionId 匹配)→ 返回可变 record", () => {
|
|
128
129
|
const record = makeRecord({ rootSessionId: sessionRootId });
|
|
129
130
|
store.register(record);
|
|
130
|
-
expect(service.getRecordForAction(record.id)).toBe(record);
|
|
131
|
+
expect(service.chatActions.getRecordForAction(record.id)).toBe(record);
|
|
131
132
|
});
|
|
132
133
|
|
|
133
134
|
it("不存在 → throw not found or not owned", () => {
|
|
134
|
-
expect(() => service.getRecordForAction("sa-nonexistent")).toThrow(/not found or not owned/);
|
|
135
|
+
expect(() => service.chatActions.getRecordForAction("sa-nonexistent")).toThrow(/not found or not owned/);
|
|
135
136
|
});
|
|
136
137
|
|
|
137
138
|
it("rootSessionId 不匹配 → throw not found or not owned(不区分 not found vs not owned,防信息泄露)", () => {
|
|
138
139
|
const record = makeRecord({ id: "sa-other", rootSessionId: "other-session" });
|
|
139
140
|
store.register(record);
|
|
140
|
-
expect(() => service.getRecordForAction("sa-other")).toThrow(/not found or not owned/);
|
|
141
|
+
expect(() => service.chatActions.getRecordForAction("sa-other")).toThrow(/not found or not owned/);
|
|
141
142
|
});
|
|
142
143
|
});
|
|
143
144
|
|
|
@@ -157,7 +158,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
157
158
|
|
|
158
159
|
afterEach(() => {
|
|
159
160
|
service.dispose();
|
|
160
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
161
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
161
162
|
});
|
|
162
163
|
|
|
163
164
|
it("running + force:false + 有活进程 → 置 closeAfterRound=true(不立即终态,等轮完)", async () => {
|
|
@@ -167,7 +168,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
167
168
|
const record = makeRecord({ status: "running" });
|
|
168
169
|
store.register(record);
|
|
169
170
|
|
|
170
|
-
await service.closeSubagent(record, false);
|
|
171
|
+
await service.chatActions.closeSubagent(record, false);
|
|
171
172
|
|
|
172
173
|
expect(record.closeAfterRound).toBe(true);
|
|
173
174
|
expect(record.status).toBe("running"); // 仍 running
|
|
@@ -181,7 +182,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
181
182
|
record.sessionFile = path.join(agentDir, "test.jsonl");
|
|
182
183
|
store.register(record);
|
|
183
184
|
|
|
184
|
-
await service.closeSubagent(record, false);
|
|
185
|
+
await service.chatActions.closeSubagent(record, false);
|
|
185
186
|
|
|
186
187
|
expect(record.status).toBe("closed");
|
|
187
188
|
expect(store.getMutable(record.id)).toBeUndefined(); // archived
|
|
@@ -191,7 +192,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
191
192
|
const record = makeRecord({ status: "running", sessionFile: path.join(agentDir, "run.jsonl") });
|
|
192
193
|
store.register(record);
|
|
193
194
|
|
|
194
|
-
await service.closeSubagent(record, true);
|
|
195
|
+
await service.chatActions.closeSubagent(record, true);
|
|
195
196
|
|
|
196
197
|
// v4 B-1:cancelled 折入 closed(closedReason='cancelled' 区分用户取消语义)
|
|
197
198
|
expect(record.status).toBe("closed");
|
|
@@ -203,7 +204,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
203
204
|
const record = makeRecord({ status: "closed" });
|
|
204
205
|
store.register(record);
|
|
205
206
|
|
|
206
|
-
await service.closeSubagent(record, false);
|
|
207
|
+
await service.chatActions.closeSubagent(record, false);
|
|
207
208
|
|
|
208
209
|
expect(record.status).toBe("closed"); // 不变
|
|
209
210
|
});
|
|
@@ -223,7 +224,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
223
224
|
expect(hasIdleTimer(record.id)).toBe(true);
|
|
224
225
|
|
|
225
226
|
try {
|
|
226
|
-
await service.closeSubagent(record, false);
|
|
227
|
+
await service.chatActions.closeSubagent(record, false);
|
|
227
228
|
} finally {
|
|
228
229
|
disarmIdleTimer(record.id); // 断言失败路径的兜底清理
|
|
229
230
|
}
|
|
@@ -242,7 +243,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
242
243
|
record.sessionFile = path.join(agentDir, "path-b.jsonl");
|
|
243
244
|
store.register(record);
|
|
244
245
|
|
|
245
|
-
await service.closeSubagent(record, false);
|
|
246
|
+
await service.chatActions.closeSubagent(record, false);
|
|
246
247
|
|
|
247
248
|
expect(record.status).toBe("closed");
|
|
248
249
|
expect(record.closedReason).toBe("user-close");
|
|
@@ -269,7 +270,7 @@ describe("closeSubagent 行为分流", () => {
|
|
|
269
270
|
expect(hasIdleTimer(record.id)).toBe(true);
|
|
270
271
|
|
|
271
272
|
try {
|
|
272
|
-
await service.closeSubagent(record, true);
|
|
273
|
+
await service.chatActions.closeSubagent(record, true);
|
|
273
274
|
} finally {
|
|
274
275
|
disarmIdleTimer(record.id); // 断言失败路径的兜底清理
|
|
275
276
|
}
|
|
@@ -299,7 +300,7 @@ describe("cancelBackground CAS 抢锁(A2-1)——cancel 迟到不覆写已
|
|
|
299
300
|
|
|
300
301
|
afterEach(() => {
|
|
301
302
|
service.dispose();
|
|
302
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
303
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
303
304
|
});
|
|
304
305
|
|
|
305
306
|
it("record 已 closed 未 archive(doFinalizeRecord Step 0 await 窗口形态)→ cancel 返回 false,不覆写终态/不写 tombstone/不 notify/不 archive", () => {
|
|
@@ -354,7 +355,7 @@ describe("[M5] closeAfterRound 消费点挂 onRoundSettled(chatMode 轮完成
|
|
|
354
355
|
afterEach(() => {
|
|
355
356
|
service.dispose();
|
|
356
357
|
_resetLifecycleState();
|
|
357
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
358
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
358
359
|
});
|
|
359
360
|
|
|
360
361
|
it("热路径轮完成(onRoundSettled)+ closeAfterRound=true → 消费标志终态化(closed+user-close + archive)", async () => {
|
|
@@ -428,7 +429,7 @@ describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
|
|
|
428
429
|
afterEach(() => {
|
|
429
430
|
service.dispose();
|
|
430
431
|
_resetLifecycleState();
|
|
431
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
432
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
432
433
|
});
|
|
433
434
|
|
|
434
435
|
it("close 语义:轮次通知(含指针行)发出后 closeAfterRoundSettled 终态化 + 终态通知,两条都送达", async () => {
|
|
@@ -473,8 +474,10 @@ describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
|
|
|
473
474
|
|
|
474
475
|
// running + 无活进程(getChildByRecord mock undefined)→ isResumable 放行 →
|
|
475
476
|
// 非 chatMode 映射 closed 通知,立即 flush(无其他 running background)
|
|
476
|
-
|
|
477
|
-
|
|
477
|
+
// [D4-①] notifyComplete 已随通知簇搬至 notify-host——按轴直接构造(同 pi/store
|
|
478
|
+
// 注入,行为与经 Service 的原路径等价;dedup 集为空与单次调用断言相容)
|
|
479
|
+
createNotifyHost({ getPi: () => pi, listRunning: () => store.listRunning(), getIsIdle: () => undefined })
|
|
480
|
+
.notifyComplete(record);
|
|
478
481
|
|
|
479
482
|
expect(pi.sendMessage).toHaveBeenCalledTimes(1);
|
|
480
483
|
const msg = pi.sendMessage.mock.calls[0]![0] as {
|
|
@@ -493,8 +496,8 @@ describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
|
|
|
493
496
|
record.sessionFile = path.join(agentDir, "c2-chat.jsonl");
|
|
494
497
|
store.register(record);
|
|
495
498
|
|
|
496
|
-
|
|
497
|
-
|
|
499
|
+
createNotifyHost({ getPi: () => pi, listRunning: () => store.listRunning(), getIsIdle: () => undefined })
|
|
500
|
+
.notifyComplete(record);
|
|
498
501
|
|
|
499
502
|
expect(pi.sendMessage).toHaveBeenCalledTimes(1);
|
|
500
503
|
const msg = pi.sendMessage.mock.calls[0]![0] as {
|
|
@@ -526,7 +529,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
|
|
|
526
529
|
afterEach(() => {
|
|
527
530
|
service.dispose();
|
|
528
531
|
_resetLifecycleState();
|
|
529
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
532
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
530
533
|
});
|
|
531
534
|
|
|
532
535
|
/** 真实回调链(与生产一致:agent_settled → onRoundSettled),turns 经 updateFromEvent 累积。 */
|
|
@@ -601,7 +604,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
|
|
|
601
604
|
updateFromEvent(record, { type: "text_delta", delta: "LAST-ROUND-INCREMENT" });
|
|
602
605
|
updateFromEvent(record, { type: "turn_end" });
|
|
603
606
|
settleRound(record); // 第 1 条:轮次通知
|
|
604
|
-
await service.closeSubagent(record, false); // idle close(D2 路径②)
|
|
607
|
+
await service.chatActions.closeSubagent(record, false); // idle close(D2 路径②)
|
|
605
608
|
} finally {
|
|
606
609
|
disarmIdleTimer(record.id);
|
|
607
610
|
}
|
|
@@ -651,7 +654,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
|
|
|
651
654
|
record.sessionFile = path.join(agentDir, "oneshot-close.jsonl");
|
|
652
655
|
store.register(record);
|
|
653
656
|
|
|
654
|
-
await service.closeSubagent(record, false); // resumable → closeChatIdle → notifyClosed 拒绝
|
|
657
|
+
await service.chatActions.closeSubagent(record, false); // resumable → closeChatIdle → notifyClosed 拒绝
|
|
655
658
|
|
|
656
659
|
expect(record.status).toBe("closed");
|
|
657
660
|
expect(pi.sendMessage).not.toHaveBeenCalled();
|
|
@@ -672,7 +675,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
|
|
|
672
675
|
updateFromEvent(record, { type: "turn_end" });
|
|
673
676
|
settleRound(record); // 轮终:onRoundSettled 写 record.result = "ok"
|
|
674
677
|
expect(record.result).toBe("ok"); // 前提锚点:轮终真实值在位
|
|
675
|
-
await service.closeSubagent(record, false); // idle close → completeRecord + archive
|
|
678
|
+
await service.chatActions.closeSubagent(record, false); // idle close → completeRecord + archive
|
|
676
679
|
} finally {
|
|
677
680
|
disarmIdleTimer(record.id);
|
|
678
681
|
}
|
|
@@ -34,7 +34,7 @@ vi.mock("../alive-store.ts", () => ({
|
|
|
34
34
|
findForeignLiveInstance: foreignLiveSpy,
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
|
-
vi.mock("../session-runner.ts", () => ({
|
|
37
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
38
38
|
runSpawn: vi.fn(),
|
|
39
39
|
killAllSpawnedChildren: vi.fn(),
|
|
40
40
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -63,7 +63,7 @@ function makePi(): PiLike {
|
|
|
63
63
|
|
|
64
64
|
function setup(): { agentDir: string; service: SubagentService; store: RecordStore } {
|
|
65
65
|
const agentDir = makeTmpAgentDir();
|
|
66
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
66
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
67
67
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
68
68
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
69
69
|
const store = (service as unknown as { store: RecordStore }).store;
|
|
@@ -79,7 +79,7 @@ describe("T5① child process skips orphan recovery scan", () => {
|
|
|
79
79
|
recover: ReturnType<typeof vi.spyOn>;
|
|
80
80
|
entryOnly: ReturnType<typeof vi.spyOn>;
|
|
81
81
|
} => {
|
|
82
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
82
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
83
83
|
const svc = new SubagentService({ cwd: agentDir, modelService });
|
|
84
84
|
const store = (svc as unknown as { store: RecordStore }).store;
|
|
85
85
|
const recover = vi.spyOn(store, "recoverOrphanRecords").mockImplementation(() => {});
|
|
@@ -95,7 +95,7 @@ describe("T5① child process skips orphan recovery scan", () => {
|
|
|
95
95
|
afterEach(() => {
|
|
96
96
|
vi.unstubAllEnvs();
|
|
97
97
|
vi.restoreAllMocks();
|
|
98
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
98
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
it("root process (no self-record env) runs the orphan recovery scan", () => {
|
|
@@ -120,6 +120,19 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
|
|
|
120
120
|
let store: RecordStore;
|
|
121
121
|
|
|
122
122
|
beforeEach(() => {
|
|
123
|
+
// 身份 env 剥离:initSession 会把 PI_SUBAGENT_ROOT_SESSION_ID 读作 sessionRootId 基线
|
|
124
|
+
//(env 优先于入参 sessionId)——宿主进程(xyz-agent 子 agent 环境)泄漏的身份 env
|
|
125
|
+
// 会让「root-session」桩记录归属校验恒不匹配 → 冷查用例误红(与 collect-coordinator-
|
|
126
|
+
// service.test.ts 同款剥离纪律)。
|
|
127
|
+
for (const k of [
|
|
128
|
+
"PI_SUBAGENT_ROOT_SESSION_ID",
|
|
129
|
+
"PI_SUBAGENT_SELF_RECORD_ID",
|
|
130
|
+
"PI_SUBAGENT_DEPTH",
|
|
131
|
+
"PI_SUBAGENT_ROOT_CWD",
|
|
132
|
+
"PI_SUBAGENT_FORK_DEPTH",
|
|
133
|
+
] as const) {
|
|
134
|
+
delete process.env[k];
|
|
135
|
+
}
|
|
123
136
|
foreignLiveSpy.mockReset();
|
|
124
137
|
foreignLiveSpy.mockReturnValue(undefined);
|
|
125
138
|
({ agentDir, service, store } = setup());
|
|
@@ -127,7 +140,7 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
|
|
|
127
140
|
|
|
128
141
|
afterEach(() => {
|
|
129
142
|
vi.restoreAllMocks();
|
|
130
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
143
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
131
144
|
});
|
|
132
145
|
|
|
133
146
|
const stubColdCandidate = (record: {
|
|
@@ -150,9 +163,9 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
|
|
|
150
163
|
});
|
|
151
164
|
foreignLiveSpy.mockReturnValue({ pid: 4242, id: "sa-foreign", startedAt: Date.now() });
|
|
152
165
|
|
|
153
|
-
expect(() => service
|
|
166
|
+
expect(() => service["getRecordForAction"]("sa-foreign")).toThrow(ResurrectDeniedError);
|
|
154
167
|
try {
|
|
155
|
-
service
|
|
168
|
+
service["getRecordForAction"]("sa-foreign");
|
|
156
169
|
} catch (err) {
|
|
157
170
|
expect((err as Error).message).toContain("4242");
|
|
158
171
|
expect((err as Error).message).toContain("double-write");
|
|
@@ -168,7 +181,7 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
|
|
|
168
181
|
sessionFile: "/tmp/fake-session-2.jsonl",
|
|
169
182
|
rootSessionId: "root-session",
|
|
170
183
|
});
|
|
171
|
-
const found = service
|
|
184
|
+
const found = service["getRecordForAction"]("sa-cold");
|
|
172
185
|
expect(found.id).toBe("sa-cold");
|
|
173
186
|
});
|
|
174
187
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/execution/__tests__/subagent-service-notify-gate.test.ts
|
|
2
2
|
//
|
|
3
3
|
// [u-svc / T4] 通知可靠性(subagent-service 侧三措施):
|
|
4
|
-
// - T4①/PS-2:
|
|
4
|
+
// - T4①/PS-2:kickOffChatRound.then notify 门按 closedReason 白名单放行——
|
|
5
5
|
// parent-new/parent-fork 不注入(可能已切换的)新 session(A-6 决策落实);
|
|
6
6
|
// - T4②/PS-4:idleTimeoutMs 非法值(>2^31-1 / 非有限值)在 spawn 入口同步 fail-fast,
|
|
7
7
|
// 错误含合法范围(不静默 clamp、不静默不挂);
|
|
@@ -23,7 +23,7 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
23
23
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
24
24
|
|
|
25
25
|
const { killChildSpy } = vi.hoisted(() => ({ killChildSpy: vi.fn() }));
|
|
26
|
-
vi.mock("../session-runner.ts", () => ({
|
|
26
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
27
27
|
runSpawn: vi.fn(),
|
|
28
28
|
killAllSpawnedChildren: vi.fn(),
|
|
29
29
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -76,7 +76,7 @@ function setup(initOverrides: Partial<{ isIdle: () => boolean }> = {}): {
|
|
|
76
76
|
pi: MockPi;
|
|
77
77
|
} {
|
|
78
78
|
const agentDir = makeTmpAgentDir();
|
|
79
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
79
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
80
80
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
81
81
|
const pi = makePi();
|
|
82
82
|
service.initSession({ pi, sessionId: "root-session", isIdle: initOverrides.isIdle });
|
|
@@ -95,18 +95,19 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
95
95
|
expect(notifyGateAllowsDelivery(undefined)).toBe(true);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it("
|
|
98
|
+
it("kickOffChatRound.then does not inject parent-new closed records into the session", async () => {
|
|
99
99
|
const { agentDir, service, pi } = setup();
|
|
100
100
|
const record = createRecord("sa-gate-new", {
|
|
101
101
|
agent: "general-purpose",
|
|
102
102
|
model: "test/model",
|
|
103
103
|
mode: "background",
|
|
104
|
+
slug: "t",
|
|
104
105
|
task: "test",
|
|
105
106
|
startedAt: 1000,
|
|
106
107
|
rootSessionId: "root-session",
|
|
107
108
|
controller: new AbortController(),
|
|
108
109
|
});
|
|
109
|
-
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的
|
|
110
|
+
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的 kickOffChatRound.then 回注
|
|
110
111
|
record.closedReason = "parent-new";
|
|
111
112
|
const stub = vi.fn().mockResolvedValue({
|
|
112
113
|
text: "",
|
|
@@ -118,23 +119,27 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
118
119
|
toolCalls: [],
|
|
119
120
|
});
|
|
120
121
|
(service as unknown as Record<string, unknown>).runAndFinalize = stub;
|
|
121
|
-
|
|
122
|
+
// [D4 后形态] 轮次编排入口 kickOffChatRound(私有,bracket 调用先例见
|
|
123
|
+
// subagent-service-recovery-bounds.test.ts privateFn);opts 形参仍是 ExecuteOptions
|
|
124
|
+
//(task/slug),chat 分支由 ticket lossless 携带——task 声明形参不参与校验。
|
|
125
|
+
const kickOffChatRound = (
|
|
122
126
|
service as unknown as Record<string, (...args: unknown[]) => void>
|
|
123
|
-
)["
|
|
124
|
-
|
|
127
|
+
)["kickOffChatRound"];
|
|
128
|
+
kickOffChatRound.call(service, record, { task: "t", slug: "gate" }, {}, {}, undefined, 1000, undefined);
|
|
125
129
|
await vi.waitFor(() => expect(stub).toHaveBeenCalled());
|
|
126
130
|
await Promise.resolve();
|
|
127
131
|
await Promise.resolve();
|
|
128
132
|
expect(pi.sendMessage).not.toHaveBeenCalled();
|
|
129
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
133
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
130
134
|
});
|
|
131
135
|
|
|
132
|
-
it("
|
|
136
|
+
it("kickOffChatRound.then still notifies for real failure closures (gc)", async () => {
|
|
133
137
|
const { agentDir, service, pi } = setup();
|
|
134
138
|
const record = createRecord("sa-gate-gc", {
|
|
135
139
|
agent: "general-purpose",
|
|
136
140
|
model: "test/model",
|
|
137
141
|
mode: "background",
|
|
142
|
+
slug: "t",
|
|
138
143
|
task: "test",
|
|
139
144
|
startedAt: 1000,
|
|
140
145
|
rootSessionId: "root-session",
|
|
@@ -142,12 +147,12 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
142
147
|
});
|
|
143
148
|
record.closedReason = "gc";
|
|
144
149
|
(service as unknown as Record<string, unknown>).runAndFinalize = vi.fn().mockResolvedValue({});
|
|
145
|
-
const
|
|
150
|
+
const kickOffChatRound = (
|
|
146
151
|
service as unknown as Record<string, (...args: unknown[]) => void>
|
|
147
|
-
)["
|
|
148
|
-
|
|
152
|
+
)["kickOffChatRound"];
|
|
153
|
+
kickOffChatRound.call(service, record, { task: "t", slug: "gate" }, {}, {}, undefined, 1000, undefined);
|
|
149
154
|
await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalled());
|
|
150
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
155
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
151
156
|
});
|
|
152
157
|
});
|
|
153
158
|
|
|
@@ -160,30 +165,30 @@ describe("T4② idleTimeoutMs entry fail-fast", () => {
|
|
|
160
165
|
});
|
|
161
166
|
|
|
162
167
|
afterEach(() => {
|
|
163
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
168
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
164
169
|
});
|
|
165
170
|
|
|
166
171
|
it("execute rejects idleTimeoutMs above the setTimeout limit with the valid range in the message", async () => {
|
|
167
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: MAX_TIMER_DELAY_MS + 1 })).rejects.toThrow(
|
|
172
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: MAX_TIMER_DELAY_MS + 1 })).rejects.toThrow(
|
|
168
173
|
new RegExp(`${MAX_TIMER_DELAY_MS}`),
|
|
169
174
|
);
|
|
170
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow(/2\^31-1/);
|
|
175
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow(/2\^31-1/);
|
|
171
176
|
});
|
|
172
177
|
|
|
173
178
|
it("execute rejects non-finite idleTimeoutMs", async () => {
|
|
174
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: Number.NaN })).rejects.toThrow(/not a finite number/);
|
|
179
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: Number.NaN })).rejects.toThrow(/not a finite number/);
|
|
175
180
|
});
|
|
176
181
|
|
|
177
182
|
it("rejects before any record is created (no side effects)", async () => {
|
|
178
183
|
const internals = service as unknown as ServiceInternals;
|
|
179
|
-
await expect(service.executeAndAwait({ task: "x", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow();
|
|
184
|
+
await expect(service.executeAndAwait({ task: "x", slug: "test", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow();
|
|
180
185
|
expect(internals.store.listRunning()).toHaveLength(0);
|
|
181
186
|
});
|
|
182
187
|
|
|
183
188
|
it("accepts valid values (0 = explicit disable, positive within limit)", async () => {
|
|
184
189
|
// 校验通过后执行链继续(runSpawn 被 mock,返回 undefined result 会在后续流程抛错/
|
|
185
190
|
// 返回——但绝不能是 idleTimeoutMs 校验错误)
|
|
186
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: 0 })).rejects.not.toThrow(/idleTimeoutMs/);
|
|
191
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: 0 })).rejects.not.toThrow(/idleTimeoutMs/);
|
|
187
192
|
});
|
|
188
193
|
});
|
|
189
194
|
|
|
@@ -199,7 +204,7 @@ describe("T4④ shutdown flush blocked → pending persisted for replay", () =>
|
|
|
199
204
|
|
|
200
205
|
afterEach(() => {
|
|
201
206
|
_resetNotifyLedgerForTest();
|
|
202
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
207
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
203
208
|
});
|
|
204
209
|
|
|
205
210
|
it("persists undelivered pending notifications as ledger entries on dispose when main agent is busy", () => {
|
|
@@ -256,7 +261,7 @@ describe("T4④ shutdown flush blocked → pending persisted for replay", () =>
|
|
|
256
261
|
// idle service:flush 投出(无门拦),dispose 无复写动作
|
|
257
262
|
const idleService = new SubagentService({
|
|
258
263
|
cwd: agentDir,
|
|
259
|
-
modelService: new ModelConfigService({ agentDir }),
|
|
264
|
+
modelService: new ModelConfigService({ agentDir, cwd: agentDir }),
|
|
260
265
|
});
|
|
261
266
|
const piIdle = makePi();
|
|
262
267
|
idleService.initSession({ pi: piIdle, sessionId: "root-session", isIdle: () => true });
|
|
@@ -26,7 +26,7 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
26
26
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
27
27
|
|
|
28
28
|
// mock session-runner(import 链需要 runSpawn/killAllSpawnedChildren/getChildByRecord 存在)
|
|
29
|
-
vi.mock("../session-runner.ts", () => ({
|
|
29
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
30
30
|
runSpawn: vi.fn(),
|
|
31
31
|
killAllSpawnedChildren: vi.fn(),
|
|
32
32
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -59,11 +59,12 @@ function makePi(): PiLike {
|
|
|
59
59
|
} as unknown as PiLike;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/** 暴露私有字段供测试注入身份。
|
|
62
|
+
/** 暴露私有字段供测试注入身份。[D3-⑤] 基线住进 execNesting(公共层
|
|
63
|
+
* ExecutionNestingContext),注入经 setBaseline 测试面。 */
|
|
63
64
|
interface ServiceInternals {
|
|
64
65
|
store: RecordStore;
|
|
65
66
|
sessionRootId: string | null;
|
|
66
|
-
|
|
67
|
+
execNesting: { setBaseline(state: { recordId: string | undefined; depth: number } | null): void };
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/** 构造一个 chatMode idle record(message/close action 的典型目标态)。 */
|
|
@@ -77,12 +78,12 @@ function makeRecord(
|
|
|
77
78
|
model: "test/model",
|
|
78
79
|
mode: "background",
|
|
79
80
|
task: "test",
|
|
80
|
-
slug: "test",
|
|
81
81
|
startedAt: 1000,
|
|
82
82
|
rootSessionId: sessionRootId,
|
|
83
83
|
chatMode: true,
|
|
84
84
|
controller: new AbortController(),
|
|
85
85
|
...overrides,
|
|
86
|
+
slug: overrides.slug ?? "test",
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -95,7 +96,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
95
96
|
|
|
96
97
|
beforeEach(() => {
|
|
97
98
|
agentDir = makeTmpAgentDir();
|
|
98
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
99
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
99
100
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
100
101
|
// initSession 建立 sessionRootId=sessionId(本 session 树的根)。execCtxBaseline 的
|
|
101
102
|
// 真实值由 initSession 从 env 读取(见 recursive-visibility-baseline.test.ts),但本测试
|
|
@@ -108,65 +109,65 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
108
109
|
|
|
109
110
|
afterEach(() => {
|
|
110
111
|
service.dispose();
|
|
111
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
112
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
112
113
|
});
|
|
113
114
|
|
|
114
115
|
// ---------- 身份 sa-A:直接父,放行孙级 B ----------
|
|
115
116
|
|
|
116
117
|
it("身份=sa-A(直接父)→ 放行操作孙级 record B(parentRecordId=sa-A)", () => {
|
|
117
|
-
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "
|
|
118
|
+
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
118
119
|
store.register(recordB);
|
|
119
|
-
internals.
|
|
120
|
+
internals.execNesting.setBaseline(PARENT_A);
|
|
120
121
|
|
|
121
|
-
expect(service.getRecordForAction("sa-B")).toBe(recordB);
|
|
122
|
+
expect(service.chatActions.getRecordForAction("sa-B")).toBe(recordB);
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
// ---------- 身份 undefined(主进程):拒绝孙级,放行根层 ----------
|
|
125
126
|
|
|
126
127
|
it("身份=undefined(主进程)拒绝孙级 record B,错误含 'direct parent' + 'parent=sa-A'", () => {
|
|
127
|
-
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "
|
|
128
|
+
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
128
129
|
store.register(recordB);
|
|
129
|
-
internals.
|
|
130
|
+
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
130
131
|
|
|
131
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
132
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
132
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
133
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
it("身份=undefined(主进程)放行顶层 record(parentRecordId=undefined 视为根层)", () => {
|
|
136
|
-
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "
|
|
137
|
+
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "running" });
|
|
137
138
|
store.register(topRecord);
|
|
138
|
-
internals.
|
|
139
|
+
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
139
140
|
|
|
140
|
-
expect(service.getRecordForAction("sa-top")).toBe(topRecord);
|
|
141
|
+
expect(service.chatActions.getRecordForAction("sa-top")).toBe(topRecord);
|
|
141
142
|
});
|
|
142
143
|
|
|
143
144
|
// ---------- 身份 sa-B(兄弟进程):拒绝孙级 ----------
|
|
144
145
|
|
|
145
146
|
it("身份=sa-B(兄弟进程)拒绝孙级 record B(parentRecordId=sa-A ≠ baseline sa-B)", () => {
|
|
146
|
-
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "
|
|
147
|
+
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
147
148
|
store.register(recordB);
|
|
148
|
-
internals.
|
|
149
|
+
internals.execNesting.setBaseline(SIBLING_B);
|
|
149
150
|
|
|
150
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
151
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
151
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
152
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
152
153
|
});
|
|
153
154
|
|
|
154
155
|
// ---------- 身份缺省语义 + 更深递归 ----------
|
|
155
156
|
|
|
156
157
|
it("子进程(baseline=sa-A)拒绝顶层 record(身份缺省视为根层,仅主进程可操作)", () => {
|
|
157
|
-
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "
|
|
158
|
+
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "running" });
|
|
158
159
|
store.register(topRecord);
|
|
159
|
-
internals.
|
|
160
|
+
internals.execNesting.setBaseline(PARENT_A);
|
|
160
161
|
|
|
161
|
-
expect(() => service.getRecordForAction("sa-top")).toThrow(/direct parent/);
|
|
162
|
+
expect(() => service.chatActions.getRecordForAction("sa-top")).toThrow(/direct parent/);
|
|
162
163
|
});
|
|
163
164
|
|
|
164
165
|
it("P7 场景:主进程拒绝曾孙 record C(parentRecordId=sa-B),错误含 baseline=root + parent=sa-B", () => {
|
|
165
|
-
const recordC = makeRecord("sa-C", sessionRootId, { parentRecordId: "sa-B", depth: 3, status: "
|
|
166
|
+
const recordC = makeRecord("sa-C", sessionRootId, { parentRecordId: "sa-B", depth: 3, status: "running" });
|
|
166
167
|
store.register(recordC);
|
|
167
|
-
internals.
|
|
168
|
+
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
168
169
|
|
|
169
|
-
const fn = () => service.getRecordForAction("sa-C");
|
|
170
|
+
const fn = () => service.chatActions.getRecordForAction("sa-C");
|
|
170
171
|
expect(fn).toThrow(/direct parent/);
|
|
171
172
|
expect(fn).toThrow(/see \/subagents list, parent=sa-B/);
|
|
172
173
|
expect(fn).toThrow(/baseline=\(root\)/);
|
|
@@ -174,10 +175,10 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
174
175
|
|
|
175
176
|
it("rootSessionId 不匹配仍优先拒绝(直接父校验在 rootSessionId 校验之后,不绕过归属守卫)", () => {
|
|
176
177
|
// 跨 session 树的 record:rootSessionId 不匹配 → 走首个 throw(not found or not owned)
|
|
177
|
-
const foreignRecord = makeRecord("sa-foreign", "other-session", { parentRecordId: "sa-A", depth: 2, status: "
|
|
178
|
+
const foreignRecord = makeRecord("sa-foreign", "other-session", { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
178
179
|
store.register(foreignRecord);
|
|
179
|
-
internals.
|
|
180
|
+
internals.execNesting.setBaseline(PARENT_A);
|
|
180
181
|
|
|
181
|
-
expect(() => service.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
182
|
+
expect(() => service.chatActions.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
182
183
|
});
|
|
183
184
|
});
|