@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
|
@@ -26,7 +26,8 @@ vi.mock("../../core/logger.ts", () => ({
|
|
|
26
26
|
|
|
27
27
|
import { doFinalizeRecord, doFinalizeRoundToIdle } from "../finalize-record.ts";
|
|
28
28
|
import { ManifestStore } from "../manifest-store.ts";
|
|
29
|
-
import
|
|
29
|
+
import { getSubagentSessionDir } from "../path-encoding.ts";
|
|
30
|
+
import type { AgentResult, ExecutionRecord, WorktreeHandle } from "../types.ts";
|
|
30
31
|
|
|
31
32
|
function makeMinimalRecord(overrides: Partial<ExecutionRecord> = {}): ExecutionRecord {
|
|
32
33
|
return {
|
|
@@ -36,7 +37,6 @@ function makeMinimalRecord(overrides: Partial<ExecutionRecord> = {}): ExecutionR
|
|
|
36
37
|
thinkingLevel: undefined,
|
|
37
38
|
mode: "background",
|
|
38
39
|
task: "test",
|
|
39
|
-
slug: "test",
|
|
40
40
|
startedAt: 1000,
|
|
41
41
|
rootSessionId: "session-main",
|
|
42
42
|
parentRecordId: undefined,
|
|
@@ -77,19 +77,19 @@ describe("doFinalizeRecord — manifest status 透传 (M3 4 态)", () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
afterEach(() => {
|
|
80
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
80
|
+
fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
/** 构造最小 FinalizeDeps:record 无 sessionFile/worktreeHandle,跳过 Step 0/3 文件操作。 */
|
|
84
|
-
function makeDeps() {
|
|
84
|
+
function makeDeps(): Parameters<typeof doFinalizeRecord>[0] {
|
|
85
85
|
return {
|
|
86
86
|
manifestStore,
|
|
87
|
-
worktreeManager: {}
|
|
88
|
-
store: { archive: vi.fn(), reportRecordTransition: vi.fn() }
|
|
89
|
-
modelService: {}
|
|
87
|
+
worktreeManager: {},
|
|
88
|
+
store: { archive: vi.fn(), reportRecordTransition: vi.fn() },
|
|
89
|
+
modelService: {},
|
|
90
90
|
pi: { appendEntry: vi.fn() },
|
|
91
91
|
emitUnregister: vi.fn(),
|
|
92
|
-
};
|
|
92
|
+
} as unknown as Parameters<typeof doFinalizeRecord>[0];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
it("status=closed + cancelled reason → manifest 写 closed(v4 B-1:cancelled 折入 closed)", async () => {
|
|
@@ -165,7 +165,7 @@ describe("doFinalizeRecord — manifest status 透传 (M3 4 态)", () => {
|
|
|
165
165
|
expect(errMsg).toContain("disk full");
|
|
166
166
|
|
|
167
167
|
// ── 核心 claim 6:pi.appendEntry 记录 "subagent:manifest-write-failed" 事件 ──
|
|
168
|
-
expect(deps.pi
|
|
168
|
+
expect(deps.pi!.appendEntry).toHaveBeenCalledWith(
|
|
169
169
|
"subagent:manifest-write-failed",
|
|
170
170
|
expect.objectContaining({ id: "rec-cleanup-first", error: "disk full" }),
|
|
171
171
|
);
|
|
@@ -198,7 +198,7 @@ describe("doFinalizeRecord — manifest status 透传 (M3 4 态)", () => {
|
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
afterEach(() => {
|
|
201
|
-
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
201
|
+
fs.rmSync(sessionDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
/** 在 sessionDir 写入带 identity 的 session 文件(1 行 identity entry,位于头部)。 */
|
|
@@ -271,6 +271,162 @@ describe("doFinalizeRecord — manifest status 透传 (M3 4 态)", () => {
|
|
|
271
271
|
loggerMock.error.mockClear();
|
|
272
272
|
});
|
|
273
273
|
});
|
|
274
|
+
|
|
275
|
+
// ── Step 3a 判别 wiring:closedReason → tombstone vs finalized(MF-1 fix / v4 B-1)──
|
|
276
|
+
//
|
|
277
|
+
// record 带 sessionFile 直接命中 writeFinalizedOrTombstone 的核心判别分支:
|
|
278
|
+
// cancelled → writeCancelledTombstone(防重建丢失 cancelled 语义)
|
|
279
|
+
// 其余 reason → writeFinalized(真实 reason 进 sidecar 内容,磁盘重建还原 closedReason)
|
|
280
|
+
// 两路 sidecar 互斥(BC-4):断言「写了哪个 + 没写哪个」双向锁定。
|
|
281
|
+
describe("writeFinalizedOrTombstone 判别 wiring(closedReason → tombstone vs finalized)", () => {
|
|
282
|
+
it("closedReason=cancelled → tombstone sidecar 携带 id/status/agent/startedAt/endedAt 且不写 .finalized", async () => {
|
|
283
|
+
const sessionFile = path.join(tmpDir, "session.jsonl");
|
|
284
|
+
const record = makeMinimalRecord({
|
|
285
|
+
id: "rec-wire-cancelled",
|
|
286
|
+
sessionFile,
|
|
287
|
+
// 预设 endedAt 会被 Step 1 completeRecord 冻结值覆盖(见下方 endedAt 断言注释)
|
|
288
|
+
endedAt: 5678,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await doFinalizeRecord(makeDeps(), record, makeMinimalResult(), "closed", "cancelled");
|
|
292
|
+
|
|
293
|
+
// tombstone 写出且内容为完整 CancelledTombstone(经 readCancelledTombstone 形态校验的
|
|
294
|
+
// 字段逐项断言,不用 objectContaining 放宽——重建链路靠这些字段还原)
|
|
295
|
+
expect(fs.existsSync(`${sessionFile}.cancelled`)).toBe(true);
|
|
296
|
+
const tomb = JSON.parse(fs.readFileSync(`${sessionFile}.cancelled`, "utf-8")) as {
|
|
297
|
+
id: string;
|
|
298
|
+
status: string;
|
|
299
|
+
agent: string;
|
|
300
|
+
startedAt: number;
|
|
301
|
+
endedAt: number;
|
|
302
|
+
};
|
|
303
|
+
expect(tomb.id).toBe("rec-wire-cancelled");
|
|
304
|
+
expect(tomb.status).toBe("cancelled");
|
|
305
|
+
expect(tomb.agent).toBe("worker");
|
|
306
|
+
expect(tomb.startedAt).toBe(1000);
|
|
307
|
+
// endedAt 来自 completeRecord 冻结后的 record.endedAt(Step 1 先于 Step 3a,
|
|
308
|
+
// record 预设的 endedAt 会被冻结值覆盖)——tombstone 携带真实收尾时间戳
|
|
309
|
+
expect(tomb.endedAt).toBe(record.endedAt);
|
|
310
|
+
expect(typeof tomb.endedAt).toBe("number");
|
|
311
|
+
// 互斥:cancelled 路径绝不写 finalized(否则重建判「正常结束」,cancelled 语义丢失)
|
|
312
|
+
expect(fs.existsSync(`${sessionFile}.finalized`)).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it.each(["user-close", "gc"] as const)(
|
|
316
|
+
"closedReason=%s → .finalized 内容携带真实 reason 且不写 tombstone",
|
|
317
|
+
async (reason) => {
|
|
318
|
+
const sessionFile = path.join(tmpDir, "session.jsonl");
|
|
319
|
+
const record = makeMinimalRecord({ id: `rec-wire-${reason}`, sessionFile });
|
|
320
|
+
|
|
321
|
+
await doFinalizeRecord(makeDeps(), record, makeMinimalResult(), "closed", reason);
|
|
322
|
+
|
|
323
|
+
// finalized sidecar 写出且内容 = 真实 reason(磁盘重建用它还原 closedReason,
|
|
324
|
+
// 不再一律硬编码 gc)
|
|
325
|
+
expect(fs.existsSync(`${sessionFile}.finalized`)).toBe(true);
|
|
326
|
+
expect(fs.readFileSync(`${sessionFile}.finalized`, "utf-8")).toBe(reason);
|
|
327
|
+
// 互斥:非 cancelled 路径不写 tombstone
|
|
328
|
+
expect(fs.existsSync(`${sessionFile}.cancelled`)).toBe(false);
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// ── Step 0 collectPatch wiring:worktreeHandle 置位 → patch 收集 + patchFile 回填 ──
|
|
334
|
+
//
|
|
335
|
+
// [MF#3] patchFile 必须写到 worktree 之外(sessionsDir/<branch>.patch),避免被
|
|
336
|
+
// cleanup 删除;仅 patch.written=true(diff 非空且写盘成功)才回填 record.patchFile,
|
|
337
|
+
// 避免悬空路径让 `git apply` 打不存在的文件。
|
|
338
|
+
describe("collectPatchIfWorktree wiring(worktreeHandle 置位 + patch.written 回填)", () => {
|
|
339
|
+
// agentDir 是真实写目标:finalize-record Step 0 对 sessionsDir 做 mkdirSync(recursive),
|
|
340
|
+
// 会在 agentDir 下创建整棵 subagents/<enc>/sessions 树。必须 mkdtemp 自建 + afterEach
|
|
341
|
+
// 清理(测试红线),禁止硬编码真实 /tmp 路径。mainCwd / handle.path 仅作编码键与
|
|
342
|
+
// startsWith 断言,源码不落盘,保持字面量即可。
|
|
343
|
+
let agentDir: string;
|
|
344
|
+
|
|
345
|
+
beforeEach(() => {
|
|
346
|
+
agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "collect-patch-agent-"));
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
afterEach(() => {
|
|
350
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const mainCwd = "/tmp/collect-patch-main-repo";
|
|
354
|
+
const branch = "subagent-b1";
|
|
355
|
+
const handle: WorktreeHandle = {
|
|
356
|
+
path: "/tmp/collect-patch-checkout",
|
|
357
|
+
branch,
|
|
358
|
+
baseCommit: "abc123",
|
|
359
|
+
mainCwd,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
function makeWorktreeDeps(
|
|
363
|
+
collectPatch: ReturnType<typeof vi.fn>,
|
|
364
|
+
): ReturnType<typeof makeDeps> & { agentDir: string } {
|
|
365
|
+
// 覆写 worktreeManager / modelService 为 stub(与外层 makeDeps 同款 unknown 断言——
|
|
366
|
+
// FinalizeDeps 的 ModelConfigService/WorktreeManager 是宽接口,stub 只需覆盖本次路径)
|
|
367
|
+
return {
|
|
368
|
+
...makeDeps(),
|
|
369
|
+
modelService: { getAgentDir: () => agentDir },
|
|
370
|
+
worktreeManager: { collectPatch, cleanup: vi.fn() },
|
|
371
|
+
agentDir,
|
|
372
|
+
} as unknown as ReturnType<typeof makeDeps> & { agentDir: string };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
it("patch.written=true → collectPatch 收到 handle + sessionsDir/<branch>.patch,record.patchFile 回填", async () => {
|
|
376
|
+
const { agentDir, ...deps } = makeWorktreeDeps(vi.fn().mockResolvedValue({
|
|
377
|
+
patchFile: "/ignored/collector-picked-path.patch",
|
|
378
|
+
failed: false,
|
|
379
|
+
written: true,
|
|
380
|
+
}));
|
|
381
|
+
const sessionsDir = getSubagentSessionDir(agentDir, mainCwd);
|
|
382
|
+
const expectedPatchFile = path.join(sessionsDir, `${branch}.patch`);
|
|
383
|
+
const record = makeMinimalRecord({ id: "rec-patch", worktreeHandle: handle });
|
|
384
|
+
|
|
385
|
+
await doFinalizeRecord(deps, record, makeMinimalResult(), "closed", "gc");
|
|
386
|
+
|
|
387
|
+
// collectPatch 被调用,patchFile 由 finalize-record 拼装(sessionsDir/<branch>.patch)
|
|
388
|
+
expect(deps.worktreeManager.collectPatch).toHaveBeenCalledTimes(1);
|
|
389
|
+
expect(deps.worktreeManager.collectPatch).toHaveBeenCalledWith(handle, expectedPatchFile);
|
|
390
|
+
// sessionsDir 已 mkdir(recursive)——patchFile 的父目录真实存在
|
|
391
|
+
expect(fs.existsSync(sessionsDir)).toBe(true);
|
|
392
|
+
// [MF#3] patchFile 落在 worktree 之外(不会被 Step 3 cleanup 连带删除)
|
|
393
|
+
expect(expectedPatchFile.startsWith(handle.path)).toBe(false);
|
|
394
|
+
// patch.written=true → record.patchFile 回填为 finalize-record 拼装的路径
|
|
395
|
+
expect(record.patchFile).toBe(expectedPatchFile);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("patch.written=false(空 diff / 写失败)→ record.patchFile 不回填(避免悬空路径)", async () => {
|
|
399
|
+
const { agentDir, ...deps } = makeWorktreeDeps(vi.fn().mockResolvedValue({
|
|
400
|
+
patchFile: "/ignored/never-written.patch",
|
|
401
|
+
failed: false,
|
|
402
|
+
written: false,
|
|
403
|
+
}));
|
|
404
|
+
const record = makeMinimalRecord({ id: "rec-patch-empty", worktreeHandle: handle });
|
|
405
|
+
|
|
406
|
+
await doFinalizeRecord(deps, record, makeMinimalResult(), "closed", "gc");
|
|
407
|
+
|
|
408
|
+
expect(deps.worktreeManager.collectPatch).toHaveBeenCalledTimes(1);
|
|
409
|
+
expect(record.patchFile).toBeUndefined();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("collectPatch 抛错 → best-effort 不阻断 finalize 链(manifest 照写、cleanup 照执行)", async () => {
|
|
413
|
+
const { agentDir, ...deps } = makeWorktreeDeps(vi.fn().mockRejectedValue(new Error("git died")));
|
|
414
|
+
const sessionFile = path.join(tmpDir, "session.jsonl");
|
|
415
|
+
const record = makeMinimalRecord({ id: "rec-patch-err", sessionFile, worktreeHandle: handle });
|
|
416
|
+
|
|
417
|
+
await expect(
|
|
418
|
+
doFinalizeRecord(deps, record, makeMinimalResult(), "closed", "gc"),
|
|
419
|
+
).resolves.toBeUndefined();
|
|
420
|
+
|
|
421
|
+
// Step 0 失败不影响后续步骤:finalized sidecar 与 manifest 照常落地
|
|
422
|
+
expect(fs.existsSync(`${sessionFile}.finalized`)).toBe(true);
|
|
423
|
+
const manifest = await manifestStore.readManifest("rec-patch-err");
|
|
424
|
+
expect(manifest?.status).toBe("closed");
|
|
425
|
+
expect(deps.worktreeManager.cleanup).toHaveBeenCalledTimes(1);
|
|
426
|
+
expect(record.patchFile).toBeUndefined();
|
|
427
|
+
loggerMock.error.mockClear();
|
|
428
|
+
});
|
|
429
|
+
});
|
|
274
430
|
});
|
|
275
431
|
|
|
276
432
|
// ============================================================
|
|
@@ -287,19 +443,19 @@ describe("doFinalizeRoundToIdle — chatMode 轮次完成进 idle (M2-A)", () =>
|
|
|
287
443
|
});
|
|
288
444
|
|
|
289
445
|
afterEach(() => {
|
|
290
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
446
|
+
fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
291
447
|
});
|
|
292
448
|
|
|
293
449
|
/** 构造 FinalizeDeps:worktreeManager.cleanup / store.archive 为 vi.fn 以断言「不调」。 */
|
|
294
|
-
function makeDeps() {
|
|
450
|
+
function makeDeps(): Parameters<typeof doFinalizeRoundToIdle>[0] {
|
|
295
451
|
return {
|
|
296
452
|
manifestStore,
|
|
297
|
-
worktreeManager: { cleanup: vi.fn(), collectPatch: vi.fn() }
|
|
298
|
-
store: { archive: vi.fn(), reportRecordTransition: vi.fn() }
|
|
299
|
-
modelService: {}
|
|
300
|
-
pi: { appendEntry: vi.fn() }
|
|
453
|
+
worktreeManager: { cleanup: vi.fn(), collectPatch: vi.fn() },
|
|
454
|
+
store: { archive: vi.fn(), reportRecordTransition: vi.fn() },
|
|
455
|
+
modelService: {},
|
|
456
|
+
pi: { appendEntry: vi.fn() },
|
|
301
457
|
emitUnregister: vi.fn(),
|
|
302
|
-
};
|
|
458
|
+
} as unknown as Parameters<typeof doFinalizeRoundToIdle>[0];
|
|
303
459
|
}
|
|
304
460
|
|
|
305
461
|
it("record 带 sessionFile → 删 .alive + record.status=running + round 0→1", async () => {
|
|
@@ -20,7 +20,7 @@ describe("finalized-marker", () => {
|
|
|
20
20
|
sessionFile = path.join(tmpDir, "2026-01-01_uuid.jsonl");
|
|
21
21
|
});
|
|
22
22
|
afterEach(() => {
|
|
23
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
23
|
+
fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
describe("write → read 往返", () => {
|
|
@@ -26,7 +26,7 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
|
26
26
|
|
|
27
27
|
// mock session-runner:runSpawn/killAllSpawnedChildren 占位(import 链需要),
|
|
28
28
|
// getChildByRecord 默认返回 undefined(无活进程 → isResumable=true),用例内按需覆盖。
|
|
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),
|
|
@@ -35,7 +35,16 @@ vi.mock("../session-runner.ts", () => ({
|
|
|
35
35
|
spawnedChildren: new Map(),
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// [D8] idle-gc 归档时释放引擎池引用(releasePoolRef 经 getEngineDataDir 解析
|
|
39
|
+
// dataDir)——测试钉到模块级 holder(vi.mock factory 闭包只能引用模块级变量),
|
|
40
|
+
// beforeEach 刷新为当前 tmp agentDir,防触碰真实数据目录。
|
|
41
|
+
let gcDataDirHolder = "";
|
|
42
|
+
vi.mock("../engine/common/data-dir.ts", () => ({
|
|
43
|
+
getEngineDataDir: () => gcDataDirHolder,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
import { acquirePool } from "../engine/common/pool-manager.ts";
|
|
47
|
+
import { getChildByRecord } from "../engine/engines/pi/session-runner.ts";
|
|
39
48
|
import { createRecord } from "../execution-record.ts";
|
|
40
49
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
41
50
|
import { RecordStore } from "../record-store.ts";
|
|
@@ -97,6 +106,7 @@ describe("[M8] idle record GC 定时器(startGcTimer)", () => {
|
|
|
97
106
|
beforeEach(() => {
|
|
98
107
|
vi.useFakeTimers();
|
|
99
108
|
agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "gc-timer-"));
|
|
109
|
+
gcDataDirHolder = agentDir;
|
|
100
110
|
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
101
111
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
102
112
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
@@ -109,7 +119,7 @@ describe("[M8] idle record GC 定时器(startGcTimer)", () => {
|
|
|
109
119
|
|
|
110
120
|
afterEach(() => {
|
|
111
121
|
service.dispose();
|
|
112
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
122
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
113
123
|
vi.useRealTimers();
|
|
114
124
|
});
|
|
115
125
|
|
|
@@ -183,4 +193,47 @@ describe("[M8] idle record GC 定时器(startGcTimer)", () => {
|
|
|
183
193
|
|
|
184
194
|
expect(store.getMutable("sa-after-dispose")).toBeDefined();
|
|
185
195
|
});
|
|
196
|
+
|
|
197
|
+
it("[D8] 归档超 TTL record 时释放引擎池引用:journal 跟随删除、refs 移除、归零删池原生状态", () => {
|
|
198
|
+
// 建池(engine 缺省投影 'pi' + poolKey 'shared'——与 runEngineTask 回填形态一致)
|
|
199
|
+
const poolDir = acquirePool(agentDir, "pi", "shared", "sa-pool-1");
|
|
200
|
+
fs.mkdirSync(path.join(poolDir, "native-state"), { recursive: true });
|
|
201
|
+
fs.writeFileSync(path.join(poolDir, "native-state", "db.sqlite"), "x");
|
|
202
|
+
fs.writeFileSync(path.join(poolDir, "journal-sa-pool-1.jsonl"), "{}\n");
|
|
203
|
+
fs.writeFileSync(path.join(poolDir, "journal-sa-pool-2.jsonl"), "{}\n"); // 他人 journal(refs 无条目)
|
|
204
|
+
|
|
205
|
+
const fireAt = Date.now() + GC_INTERVAL_MS;
|
|
206
|
+
const record = makeIdleRecord("sa-pool-1", fireAt - IDLE_TTL_MS - 1);
|
|
207
|
+
record.engineHandle = { sessionRef: {}, poolKey: "shared" };
|
|
208
|
+
store.register(record);
|
|
209
|
+
|
|
210
|
+
service.startGcTimer();
|
|
211
|
+
vi.advanceTimersByTime(GC_INTERVAL_MS);
|
|
212
|
+
|
|
213
|
+
expect(store.getMutable("sa-pool-1")).toBeUndefined(); // 已归档
|
|
214
|
+
// release 语义:自己的 journal 删、他人 journal 保留、归零删原生状态、目录保留(剩 journal)
|
|
215
|
+
expect(fs.existsSync(path.join(poolDir, "journal-sa-pool-1.jsonl"))).toBe(false);
|
|
216
|
+
expect(fs.existsSync(path.join(poolDir, "journal-sa-pool-2.jsonl"))).toBe(true);
|
|
217
|
+
expect(fs.existsSync(path.join(poolDir, "native-state"))).toBe(false);
|
|
218
|
+
expect(fs.existsSync(poolDir)).toBe(true);
|
|
219
|
+
// refs 归零后随池清理(无残留幻影引用)
|
|
220
|
+
const refsPath = path.join(poolDir, "refs.json");
|
|
221
|
+
if (fs.existsSync(refsPath)) {
|
|
222
|
+
expect(Object.keys((JSON.parse(fs.readFileSync(refsPath, "utf8")) as { refs: object }).refs)).toEqual([]);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("[D8] 无 engineHandle 的 record 归档不触碰引擎池(存量 record 零影响)", () => {
|
|
227
|
+
const poolDir = acquirePool(agentDir, "pi", "shared", "sa-plain");
|
|
228
|
+
fs.writeFileSync(path.join(poolDir, "journal-sa-plain.jsonl"), "{}\n");
|
|
229
|
+
|
|
230
|
+
const fireAt = Date.now() + GC_INTERVAL_MS;
|
|
231
|
+
store.register(makeIdleRecord("sa-plain", fireAt - IDLE_TTL_MS - 1)); // 无 engineHandle
|
|
232
|
+
|
|
233
|
+
service.startGcTimer();
|
|
234
|
+
vi.advanceTimersByTime(GC_INTERVAL_MS);
|
|
235
|
+
|
|
236
|
+
expect(store.getMutable("sa-plain")).toBeUndefined(); // 正常归档
|
|
237
|
+
expect(fs.existsSync(path.join(poolDir, "journal-sa-plain.jsonl"))).toBe(true); // 池/journal 不动
|
|
238
|
+
});
|
|
186
239
|
});
|
|
@@ -27,7 +27,7 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
|
27
27
|
|
|
28
28
|
// mock session-runner:import 链需要(getRecordForAction 本身不调 spawn,仅守卫读
|
|
29
29
|
// hasLiveProcessHandle——默认无活进程,与跨重启场景一致)。
|
|
30
|
-
vi.mock("../session-runner.ts", () => ({
|
|
30
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
31
31
|
runSpawn: vi.fn(),
|
|
32
32
|
killAllSpawnedChildren: vi.fn(),
|
|
33
33
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -169,7 +169,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
|
|
|
169
169
|
const file = writeSessionJsonl(sessionsDir, { id: "sa-restart-1", rootSessionId: "root-session" });
|
|
170
170
|
expect(store.getMutable("sa-restart-1")).toBeUndefined(); // 前置:内存确无
|
|
171
171
|
|
|
172
|
-
const record = service.getRecordForAction("sa-restart-1");
|
|
172
|
+
const record = service.chatActions.getRecordForAction("sa-restart-1");
|
|
173
173
|
|
|
174
174
|
// [v4 A-3] 无条件 chatMode=true(跨重启恢复入口)
|
|
175
175
|
expect(record.chatMode).toBe(true);
|
|
@@ -186,7 +186,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
|
|
|
186
186
|
expect(record.status).toBe("running");
|
|
187
187
|
// register 生效:进内存,二次调用走内存命中(同一引用)
|
|
188
188
|
expect(store.getMutable("sa-restart-1")).toBe(record);
|
|
189
|
-
expect(service.getRecordForAction("sa-restart-1")).toBe(record);
|
|
189
|
+
expect(service.chatActions.getRecordForAction("sa-restart-1")).toBe(record);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
it("rootSessionId 校验仍生效:异树 record(other-session)→ throw not found or not owned", () => {
|
|
@@ -194,7 +194,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
|
|
|
194
194
|
// 但后置校验 record.rootSessionId !== this.sessionRootId 必须拦截
|
|
195
195
|
writeSessionJsonl(sessionsDir, { id: "sa-foreign", rootSessionId: "other-session" });
|
|
196
196
|
|
|
197
|
-
expect(() => service.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
197
|
+
expect(() => service.chatActions.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
it("直接父校验仍生效:孙级 record(parentRecordId=sa-parent)→ 主进程 throw direct parent", () => {
|
|
@@ -206,14 +206,14 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
|
|
|
206
206
|
});
|
|
207
207
|
|
|
208
208
|
// 重建后 parentRecordId=sa-parent ≠ 主进程 baseline(undefined) → cross-layer 守卫拦截
|
|
209
|
-
expect(() => service.getRecordForAction("sa-grand")).toThrow(/direct parent/);
|
|
209
|
+
expect(() => service.chatActions.getRecordForAction("sa-grand")).toThrow(/direct parent/);
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
it(".finalized sidecar(closed 终态)不重建 → throw not found or not owned", () => {
|
|
213
213
|
const file = writeSessionJsonl(sessionsDir, { id: "sa-fin", rootSessionId: "root-session" });
|
|
214
214
|
writeFinalized(file); // sidecar 矩阵分支 2 → status=closed → find(status==="running") miss
|
|
215
215
|
|
|
216
|
-
expect(() => service.getRecordForAction("sa-fin")).toThrow(/not found or not owned/);
|
|
216
|
+
expect(() => service.chatActions.getRecordForAction("sa-fin")).toThrow(/not found or not owned/);
|
|
217
217
|
expect(store.getMutable("sa-fin")).toBeUndefined(); // 未重建注册
|
|
218
218
|
});
|
|
219
219
|
|
|
@@ -230,14 +230,14 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
|
|
|
230
230
|
worktree: true,
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
-
const record = service.getRecordForAction("sa-wt");
|
|
233
|
+
const record = service.chatActions.getRecordForAction("sa-wt");
|
|
234
234
|
// hadWorktree 从磁盘 identity entry 的 worktree 标志恢复
|
|
235
235
|
expect(record.hadWorktree).toBe(true);
|
|
236
236
|
// handle 无法恢复(不可序列化)
|
|
237
237
|
expect(record.worktreeHandle).toBeUndefined();
|
|
238
238
|
|
|
239
|
-
// 冷路径续聊(无活进程)→
|
|
240
|
-
await expect(service.
|
|
239
|
+
// 冷路径续聊(无活进程)→ 续轮守卫拒绝,不 spawn 回落主 repo
|
|
240
|
+
await expect(service.chatActions.deliverChatMessage(record, "resume after restart", false)).rejects.toThrow(
|
|
241
241
|
/worktree isolation.*lost when the parent process restarted/,
|
|
242
242
|
);
|
|
243
243
|
});
|
|
@@ -246,11 +246,11 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
|
|
|
246
246
|
// identity entry 无 worktree 字段(旧文件)→ found.worktree undefined → hadWorktree false
|
|
247
247
|
writeSessionJsonl(sessionsDir, { id: "sa-nowt", rootSessionId: "root-session" });
|
|
248
248
|
|
|
249
|
-
const record = service.getRecordForAction("sa-nowt");
|
|
249
|
+
const record = service.chatActions.getRecordForAction("sa-nowt");
|
|
250
250
|
expect(record.hadWorktree).toBe(false);
|
|
251
251
|
|
|
252
|
-
//
|
|
252
|
+
// 冷路径续聊不被 worktree 守卫拦截(resume 正常发起;后续 spawn 编排
|
|
253
253
|
// 超出本用例关注点——runSpawn 在本文件是 no-op mock)
|
|
254
|
-
await expect(service.
|
|
254
|
+
await expect(service.chatActions.deliverChatMessage(record, "resume normal", false)).resolves.toBeUndefined();
|
|
255
255
|
});
|
|
256
256
|
});
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { describe, expect, it, vi } from "vitest";
|
|
16
16
|
|
|
17
|
-
import { requestGetStateOnce } from "../get-state-handshake.ts";
|
|
17
|
+
import { requestGetStateOnce } from "../engine/engines/pi/get-state-handshake.ts";
|
|
18
18
|
import type { ChildProcess } from "node:child_process";
|
|
19
19
|
|
|
20
20
|
/** 最小 FakeChild:只需 stdin.write 行为(成功 / 可注入同步 throw)。 */
|
|
@@ -21,7 +21,7 @@ export function mockExtensionApi(
|
|
|
21
21
|
const noop = (): void => { /* test mock: method not invoked by this test */ };
|
|
22
22
|
// Proxy<T> 泛型参数决定返回类型——直接声明为 ExtensionAPI,
|
|
23
23
|
// TS 接受(Proxy handler 对 target 的类型不约束 T)。
|
|
24
|
-
return new Proxy<ExtensionAPI>(overrides as ExtensionAPI, {
|
|
24
|
+
return new Proxy<ExtensionAPI>(overrides as unknown as ExtensionAPI, {
|
|
25
25
|
get(target, prop: string | symbol): unknown {
|
|
26
26
|
if (prop in target) return target[prop as keyof ExtensionAPI];
|
|
27
27
|
return noop;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/execution/__tests__/helpers/session-runner-mocks.ts
|
|
2
|
+
//
|
|
3
|
+
// session-runner 系测试(keep-alive-no-progress / recursive-visibility-env 等)的
|
|
4
|
+
// 测试文件侧装配:vi.mocked 取回 + spawn/keep-alive 前奏 + 子进程收尾。
|
|
5
|
+
//
|
|
6
|
+
// 与 spawn-mock.ts 的分工(依赖约束,勿混淆):
|
|
7
|
+
// - spawn-mock.ts 会被各 vi.mock 工厂 `await import()`(求值时机 = 被 mock 模块首次
|
|
8
|
+
// 请求),故它禁止值依赖 session-runner / alive-store / session-pending——否则形成
|
|
9
|
+
// 「mock 工厂 → helper → session-runner → node:child_process(mock 求值中)」循环。
|
|
10
|
+
// mock 模块工厂 + FakeChild + 纯 fixture 都在 spawn-mock.ts。
|
|
11
|
+
// - 本文件只被测试文件顶层静态 import(vi.mock 已 hoist 注册完毕、mock 工厂已可安全
|
|
12
|
+
// 执行),故可值依赖 session-runner / alive-store / session-pending 做 vi.mocked 取回
|
|
13
|
+
// 与 runSpawn 前奏封装。
|
|
14
|
+
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
|
|
18
|
+
import { afterEach, beforeEach, vi } from "vitest";
|
|
19
|
+
|
|
20
|
+
import { runSpawn, SPAWN_WATCHDOG_ENV } from "../../engine/engines/pi/session-runner.ts";
|
|
21
|
+
import {
|
|
22
|
+
listActivePendingFromSessionFile,
|
|
23
|
+
readActivePendingFromSessionFile,
|
|
24
|
+
} from "../../session-pending.ts";
|
|
25
|
+
import { isProcessAlive, readAliveMarker, writeAliveMarker } from "../../alive-store.ts";
|
|
26
|
+
import {
|
|
27
|
+
emitStdoutLine,
|
|
28
|
+
lastSpawnedChild,
|
|
29
|
+
makeCtx,
|
|
30
|
+
makeOpts,
|
|
31
|
+
makeRecord,
|
|
32
|
+
waitForSpawn,
|
|
33
|
+
type FakeChild,
|
|
34
|
+
} from "./spawn-mock.ts";
|
|
35
|
+
|
|
36
|
+
/** keep-alive 用例的 session header 行(stdout 首行)。 */
|
|
37
|
+
function sessionHeaderLine(): Record<string, unknown> {
|
|
38
|
+
return { type: "session", id: "sess-ka", timestamp: "2026-09-01T00:00:00.000Z", cwd: "/tmp/test" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** vi.mocked 取回超集(各测试文件按需解构;与各 vi.mock 工厂返回同一 mock 实例)。 */
|
|
42
|
+
export function takeSessionRunnerMocks() {
|
|
43
|
+
return {
|
|
44
|
+
mockSpawn: vi.mocked(spawn),
|
|
45
|
+
mockExistsSync: vi.mocked(fs.existsSync),
|
|
46
|
+
mockReaddirSync: vi.mocked(fs.readdirSync as (path: fs.PathLike) => string[]),
|
|
47
|
+
mockPending: vi.mocked(readActivePendingFromSessionFile),
|
|
48
|
+
mockListPending: vi.mocked(listActivePendingFromSessionFile),
|
|
49
|
+
mockReadAliveMarker: vi.mocked(readAliveMarker),
|
|
50
|
+
mockWriteAliveMarker: vi.mocked(writeAliveMarker),
|
|
51
|
+
mockIsProcessAlive: vi.mocked(isProcessAlive),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 非 chatMode 层主 + fake timers + agent_end keep-alive 落位的公共前奏。
|
|
57
|
+
*
|
|
58
|
+
* 返回 promise(runSpawn 原始 promise,供手动 exit/close 收尾的用例)与 finish
|
|
59
|
+
* (标准 close 收尾)两种取用形态。
|
|
60
|
+
*/
|
|
61
|
+
export async function spawnAndReachKeepAlive(
|
|
62
|
+
opts = makeOpts(),
|
|
63
|
+
task = "Task: keep-alive no-progress",
|
|
64
|
+
): Promise<{
|
|
65
|
+
child: FakeChild;
|
|
66
|
+
promise: Promise<Awaited<ReturnType<typeof runSpawn>>>;
|
|
67
|
+
finish: (code?: number) => Promise<Awaited<ReturnType<typeof runSpawn>>>;
|
|
68
|
+
}> {
|
|
69
|
+
const record = makeRecord();
|
|
70
|
+
const promise = runSpawn(record, task, opts, makeCtx());
|
|
71
|
+
await waitForSpawn(vi.mocked(spawn));
|
|
72
|
+
const child = lastSpawnedChild(vi.mocked(spawn));
|
|
73
|
+
// fake timers 必须在 emit agent_end 之前启用(keep-alive timer 新建于 agent_end
|
|
74
|
+
// 处理器内;不 fake setImmediate——stream flush 靠真实事件循环交付,见 MF-3 先例)。
|
|
75
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
|
|
76
|
+
emitStdoutLine(child, sessionHeaderLine());
|
|
77
|
+
emitStdoutLine(child, { type: "agent_end", messages: [], willRetry: false });
|
|
78
|
+
await new Promise((r) => setImmediate(r));
|
|
79
|
+
return {
|
|
80
|
+
child,
|
|
81
|
+
promise,
|
|
82
|
+
finish: (code = 143) => {
|
|
83
|
+
child.stdout.end();
|
|
84
|
+
child.stderr.end();
|
|
85
|
+
child.emit("close", code);
|
|
86
|
+
return promise as Promise<Awaited<ReturnType<typeof runSpawn>>>;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 让最近 spawn 的 FakeChild 发 close(0) 并等 runSpawn 收尾(env 注入用例的标准收尾装配)。 */
|
|
92
|
+
export async function closeLastChildAndAwait<T>(promise: Promise<T>): Promise<T> {
|
|
93
|
+
const child = lastSpawnedChild(vi.mocked(spawn));
|
|
94
|
+
child.emit("close", 0);
|
|
95
|
+
return promise;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** keep-alive 系测试的统一 hooks:清 mock + stub 空 watchdog env + 统一恢复(在 describe 内调用)。 */
|
|
99
|
+
export function keepAliveTestHooks(
|
|
100
|
+
mockPending: ReturnType<typeof takeSessionRunnerMocks>["mockPending"],
|
|
101
|
+
): void {
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
vi.clearAllMocks();
|
|
104
|
+
vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
|
|
105
|
+
mockPending.mockReturnValue({ count: 1, recentUnregister: false });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
vi.restoreAllMocks();
|
|
110
|
+
vi.unstubAllEnvs();
|
|
111
|
+
vi.useRealTimers();
|
|
112
|
+
});
|
|
113
|
+
}
|