@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
|
@@ -5,8 +5,17 @@
|
|
|
5
5
|
// (usage 收口进 getTotalUsage,text 收口进 getFullText,均在 execution-record.test 测)。
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
classifyFailureKind,
|
|
10
|
+
collectResult,
|
|
11
|
+
describeMissingParsedOutput,
|
|
12
|
+
DETERMINISTIC_SCHEMA_FAILURE_PREFIX,
|
|
13
|
+
extractParsedOutput,
|
|
14
|
+
isDeterministicSchemaFailureMsg,
|
|
15
|
+
isStaleContextErrorMsg,
|
|
16
|
+
neutralizeStalePatterns,
|
|
17
|
+
STALE_CONTEXT_PATTERNS,
|
|
18
|
+
} from "../engine/engines/pi/output-collector.ts";
|
|
10
19
|
import type { ExecutionRecord, ToolCall } from "../types.ts";
|
|
11
20
|
|
|
12
21
|
// ============================================================
|
|
@@ -162,9 +171,9 @@ describe("describeMissingParsedOutput", () => {
|
|
|
162
171
|
// ── [F-R1] 态2(isError 分支)错误摘要拼接段中和 ──
|
|
163
172
|
//
|
|
164
173
|
// 动态错误文本(模型/provider 原始错误)可能携带 "aborted"/"ctx is stale" 等
|
|
165
|
-
// STALE_CONTEXT_PATTERNS 词;不中和则归因 error 经 collectResult
|
|
166
|
-
//
|
|
167
|
-
//
|
|
174
|
+
// STALE_CONTEXT_PATTERNS 词;不中和则归因 error 经 collectResult 的
|
|
175
|
+
// classifyFailureKind 分诊被误标 stale_context(归因语义被污染)。
|
|
176
|
+
// 固定前缀静态无命中(上一用例锁定),中和只针对动态段。
|
|
168
177
|
describe("F-R1: 态2 错误摘要 STALE_CONTEXT_PATTERNS 中和", () => {
|
|
169
178
|
function failedSoCallsWith(text: string): ToolCall[] {
|
|
170
179
|
return [
|
|
@@ -233,7 +242,7 @@ describe("describeMissingParsedOutput", () => {
|
|
|
233
242
|
// ============================================================
|
|
234
243
|
|
|
235
244
|
describe("MF-1: 三态可重试性矩阵(确定性失败标记)", () => {
|
|
236
|
-
// 矩阵(SSOT
|
|
245
|
+
// 矩阵(SSOT 注释在本模块 DETERMINISTIC_SCHEMA_FAILURE_PREFIX 与
|
|
237
246
|
// describeMissingParsedOutput JSDoc,本 describe 是执行面锁定):
|
|
238
247
|
// 态① 从未调用 SO → 带标记 → 不可重试(环境确定性,C1 安装盲区)
|
|
239
248
|
// 态② SO isError(gate 终止/不可满足 schema)→ 带标记 → 不可重试(同 schema 重试必同结果)
|
|
@@ -356,3 +365,105 @@ describe("collectResult — F-1 schemaExpected 失败标注", () => {
|
|
|
356
365
|
expect(isDeterministicSchemaFailureMsg(result.error)).toBe(true);
|
|
357
366
|
});
|
|
358
367
|
});
|
|
368
|
+
|
|
369
|
+
// ============================================================
|
|
370
|
+
// D5-③: failureKind 分类(产出侧单点识别)
|
|
371
|
+
// ============================================================
|
|
372
|
+
|
|
373
|
+
describe("classifyFailureKind(词表 → 结构化标签)", () => {
|
|
374
|
+
it("pi 真实 stale 文案 → stale_context", () => {
|
|
375
|
+
const piRealStale =
|
|
376
|
+
"This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession().";
|
|
377
|
+
expect(classifyFailureKind(piRealStale)).toBe("stale_context");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("abort 族(context canceled / aborted)→ stale_context", () => {
|
|
381
|
+
expect(classifyFailureKind("Request context canceled by provider")).toBe("stale_context");
|
|
382
|
+
expect(classifyFailureKind("Subprocess aborted by runtime shutdown")).toBe("stale_context");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("确定性 schema 失败标记前缀 → schema_deterministic", () => {
|
|
386
|
+
expect(classifyFailureKind(
|
|
387
|
+
`${DETERMINISTIC_SCHEMA_FAILURE_PREFIX} Agent finished without producing a structured output`,
|
|
388
|
+
)).toBe("schema_deterministic");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("未知文案(词表零命中,pi 升级改写文案后的形态)→ unknown(安全默认,可重试)", () => {
|
|
392
|
+
expect(classifyFailureKind("provider 503 service unavailable")).toBe("unknown");
|
|
393
|
+
expect(classifyFailureKind("spawn EAGAIN")).toBe("unknown");
|
|
394
|
+
expect(classifyFailureKind("extension runtime was superseded by a newer orchestration epoch")).toBe("unknown");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("undefined(成功路径)→ undefined(不落失败分诊)", () => {
|
|
398
|
+
expect(classifyFailureKind(undefined)).toBeUndefined();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("标记词与 stale 词表零交集(分诊优先级无歧义)", () => {
|
|
402
|
+
expect(classifyFailureKind(DETERMINISTIC_SCHEMA_FAILURE_PREFIX)).toBe("schema_deterministic");
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe("collectResult — D5-③ failureKind 产出", () => {
|
|
407
|
+
/** 最小 ExecutionRecord stub(collectResult 只读 turns/toolCalls 派生面)。 */
|
|
408
|
+
function makeRecordWithCalls(calls: ToolCall[]): ExecutionRecord {
|
|
409
|
+
return {
|
|
410
|
+
id: "rec-fk",
|
|
411
|
+
turns: [{ toolCalls: calls, text: "done", turnCount: 1, closed: true }],
|
|
412
|
+
turnCount: 1,
|
|
413
|
+
} as unknown as ExecutionRecord;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const baseArgs = {
|
|
417
|
+
startTime: Date.now(),
|
|
418
|
+
sessionId: "s-fk",
|
|
419
|
+
sessionFile: undefined,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
it("error 命中 stale 词表 → failureKind=stale_context", () => {
|
|
423
|
+
const record = makeRecordWithCalls([]);
|
|
424
|
+
const result = collectResult(record, {
|
|
425
|
+
...baseArgs,
|
|
426
|
+
success: false,
|
|
427
|
+
error: "This extension ctx is stale after session replacement or reload.",
|
|
428
|
+
});
|
|
429
|
+
expect(result.failureKind).toBe("stale_context");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("F-1 归因覆写(态①②确定性标记)→ failureKind=schema_deterministic", () => {
|
|
433
|
+
const record = makeRecordWithCalls([]);
|
|
434
|
+
const result = collectResult(record, { ...baseArgs, success: true, error: undefined, schemaExpected: true });
|
|
435
|
+
expect(result.error).toBeDefined();
|
|
436
|
+
expect(result.failureKind).toBe("schema_deterministic");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("态③归因(调用过但无 details,无标记)→ failureKind=unknown(可重试,矩阵保留)", () => {
|
|
440
|
+
const record = makeRecordWithCalls([
|
|
441
|
+
{ toolName: "structured-output", result: { content: [] } },
|
|
442
|
+
]);
|
|
443
|
+
const result = collectResult(record, { ...baseArgs, success: true, error: undefined, schemaExpected: true });
|
|
444
|
+
expect(result.error).toBeDefined();
|
|
445
|
+
expect(result.failureKind).toBe("unknown");
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("普通 provider 错误 → failureKind=unknown", () => {
|
|
449
|
+
const record = makeRecordWithCalls([]);
|
|
450
|
+
const result = collectResult(record, { ...baseArgs, success: false, error: "provider boom 5xx" });
|
|
451
|
+
expect(result.failureKind).toBe("unknown");
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("成功路径(error undefined)→ failureKind 不写(缺省 = unknown = 可重试,消费侧语义)", () => {
|
|
455
|
+
const record = makeRecordWithCalls([]);
|
|
456
|
+
const result = collectResult(record, { ...baseArgs, success: true, error: undefined });
|
|
457
|
+
expect(result.failureKind).toBeUndefined();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("词表漂移失效模式:未知文案(未来 pi 形态)→ failureKind=unknown(保守重试,非静默漏诊)", () => {
|
|
461
|
+
const record = makeRecordWithCalls([]);
|
|
462
|
+
const result = collectResult(record, {
|
|
463
|
+
...baseArgs,
|
|
464
|
+
success: false,
|
|
465
|
+
error: "extension runtime was superseded by a newer orchestration epoch",
|
|
466
|
+
});
|
|
467
|
+
expect(result.failureKind).toBe("unknown");
|
|
468
|
+
});
|
|
469
|
+
});
|
|
@@ -3,10 +3,42 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
|
|
6
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
7
|
-
|
|
8
|
-
import { getPiInvocation } from "../pi-invocation.ts";
|
|
9
|
-
import {
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { getPiInvocation } from "../engine/engines/pi/pi-invocation.ts";
|
|
9
|
+
import {
|
|
10
|
+
RELAY_ENV_NODE,
|
|
11
|
+
RELAY_ENV_RECORD_ID,
|
|
12
|
+
RELAY_ENV_SCRIPT,
|
|
13
|
+
RELAY_ENV_SESSION_ID,
|
|
14
|
+
RELAY_ENV_SOCKET,
|
|
15
|
+
} from "../relay-env.ts";
|
|
16
|
+
|
|
17
|
+
// [验收门修复] getPiInvocation 的 relay 分支优先读宿主 RELAY env——根级 pnpm test
|
|
18
|
+
// 进程若继承宿主(pi 子进程链)的 RELAY 身份,直连分支用例的 execPath/argv 断言
|
|
19
|
+
// 会被改道(非自免疫,依赖 runner 环境)。文件级钩子对全部用例统一消毒:beforeEach
|
|
20
|
+
// 删五键、afterEach 恢复原值——有泄漏无泄漏双向绿;第二组 relay 分支用例自设
|
|
21
|
+
// RELAY_FULL 在清理之后执行,语义不受影响。
|
|
22
|
+
const RELAY_ENV_KEYS = [
|
|
23
|
+
RELAY_ENV_SOCKET,
|
|
24
|
+
RELAY_ENV_NODE,
|
|
25
|
+
RELAY_ENV_SCRIPT,
|
|
26
|
+
RELAY_ENV_SESSION_ID,
|
|
27
|
+
RELAY_ENV_RECORD_ID,
|
|
28
|
+
] as const;
|
|
29
|
+
let savedRelayEnv: Record<string, string | undefined>;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
savedRelayEnv = {};
|
|
32
|
+
for (const key of RELAY_ENV_KEYS) savedRelayEnv[key] = process.env[key];
|
|
33
|
+
for (const key of RELAY_ENV_KEYS) delete process.env[key];
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
for (const key of RELAY_ENV_KEYS) {
|
|
37
|
+
const value = savedRelayEnv[key];
|
|
38
|
+
if (value === undefined) delete process.env[key];
|
|
39
|
+
else process.env[key] = value;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
10
42
|
|
|
11
43
|
describe("getPiInvocation", () => {
|
|
12
44
|
const originalArgv = process.argv;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/execution/__tests__/record-entry-collect.test.ts
|
|
2
|
+
//
|
|
3
|
+
// subagent-record entry 序列化白名单:collectMode / batchFinalized 两字段
|
|
4
|
+
// (subagent-sync-collect U1 foundation)。
|
|
5
|
+
//
|
|
6
|
+
// 锁三件事(设计 §3.1.3):
|
|
7
|
+
// 1. 持久化:两字段在 entry data 中如实透传(round-trip 经 JSON 序列化不丢);
|
|
8
|
+
// 2. 零迁移:无字段(旧 record 形态)的 entry 产物不含新键(JSON.stringify 自然缺省),
|
|
9
|
+
// 与改动前逐字节一致;
|
|
10
|
+
// 3. customType 稳定:SUBAGENT_RECORD_CUSTOM_TYPE 不因扩字段漂移。
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from "vitest";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
SUBAGENT_RECORD_CUSTOM_TYPE,
|
|
16
|
+
toSubagentRecordEntry,
|
|
17
|
+
type SubagentRecordEntryData,
|
|
18
|
+
} from "../record-entry.ts";
|
|
19
|
+
import type { SubagentRecord } from "../types.ts";
|
|
20
|
+
|
|
21
|
+
/** 最小合法 SubagentRecord(缺省无 collect 两字段 = 旧记录形态)。 */
|
|
22
|
+
function makeRecord(over: Partial<SubagentRecord> = {}): SubagentRecord {
|
|
23
|
+
return {
|
|
24
|
+
id: "sa-1",
|
|
25
|
+
agent: "/home/u/agents/worker.md",
|
|
26
|
+
task: "t",
|
|
27
|
+
slug: "worker",
|
|
28
|
+
status: "running",
|
|
29
|
+
mode: "background",
|
|
30
|
+
startedAt: 1000,
|
|
31
|
+
rootSessionId: "root-A",
|
|
32
|
+
parentRecordId: undefined,
|
|
33
|
+
depth: 0,
|
|
34
|
+
endedAt: undefined,
|
|
35
|
+
turns: 1,
|
|
36
|
+
totalTokens: 42,
|
|
37
|
+
model: "prov/m1",
|
|
38
|
+
thinkingLevel: undefined,
|
|
39
|
+
eventLog: [],
|
|
40
|
+
displayItems: [],
|
|
41
|
+
sessionFile: "sess-1.jsonl",
|
|
42
|
+
...over,
|
|
43
|
+
} as SubagentRecord;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** entry data 的 JSONL 形态字符串(appendEntry 落盘即此产物)。 */
|
|
47
|
+
function serialize(entry: SubagentRecordEntryData): string {
|
|
48
|
+
return JSON.stringify(entry);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("record-entry serialization: collect fields (U1 foundation)", () => {
|
|
52
|
+
it("keeps SUBAGENT_RECORD_CUSTOM_TYPE stable", () => {
|
|
53
|
+
expect(SUBAGENT_RECORD_CUSTOM_TYPE).toBe("subagent-record");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("passes collectMode through for a sync record", () => {
|
|
57
|
+
const entry = toSubagentRecordEntry(makeRecord({ collectMode: "sync" }));
|
|
58
|
+
expect(entry.collectMode).toBe("sync");
|
|
59
|
+
expect(entry.batchFinalized).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("passes batchFinalized through for a member that left the batch", () => {
|
|
63
|
+
const entry = toSubagentRecordEntry(
|
|
64
|
+
makeRecord({
|
|
65
|
+
collectMode: "sync",
|
|
66
|
+
batchFinalized: true,
|
|
67
|
+
status: "closed",
|
|
68
|
+
endedAt: 2000,
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
expect(entry.collectMode).toBe("sync");
|
|
72
|
+
expect(entry.batchFinalized).toBe(true);
|
|
73
|
+
expect(entry.status).toBe("closed");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("round-trips both fields through JSON serialization (persisted form)", () => {
|
|
77
|
+
const entry = toSubagentRecordEntry(
|
|
78
|
+
makeRecord({ collectMode: "sync", batchFinalized: true }),
|
|
79
|
+
);
|
|
80
|
+
const revived = JSON.parse(serialize(entry)) as SubagentRecordEntryData;
|
|
81
|
+
expect(revived.collectMode).toBe("sync");
|
|
82
|
+
expect(revived.batchFinalized).toBe(true);
|
|
83
|
+
expect(revived.v).toBe(1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("emits no new keys for legacy records (旧记录零迁移)", () => {
|
|
87
|
+
const json = serialize(toSubagentRecordEntry(makeRecord()));
|
|
88
|
+
expect(json).not.toContain("collectMode");
|
|
89
|
+
expect(json).not.toContain("batchFinalized");
|
|
90
|
+
const revived = JSON.parse(json) as SubagentRecordEntryData;
|
|
91
|
+
expect(revived.collectMode).toBeUndefined();
|
|
92
|
+
expect(revived.batchFinalized).toBeUndefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("still emits the full legacy whitelist for legacy records (既有字段不受扩字段影响)", () => {
|
|
96
|
+
const entry = toSubagentRecordEntry(makeRecord());
|
|
97
|
+
expect(entry.id).toBe("sa-1");
|
|
98
|
+
expect(entry.status).toBe("running");
|
|
99
|
+
expect(entry.model).toBe("prov/m1");
|
|
100
|
+
expect(entry.sessionFile).toBe("sess-1.jsonl");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -52,8 +52,9 @@ function makeRecord(over: Partial<ExecutionRecord> = {}): ExecutionRecord {
|
|
|
52
52
|
const base = createRecord("r1", {
|
|
53
53
|
agent: "worker",
|
|
54
54
|
model: "m",
|
|
55
|
-
mode: "
|
|
55
|
+
mode: "background",
|
|
56
56
|
task: "t",
|
|
57
|
+
slug: "test",
|
|
57
58
|
startedAt: 1000,
|
|
58
59
|
rootSessionId: "sess-current",
|
|
59
60
|
// 对齐生产 register 路径(subagent-service createRecord:one-shot 显式 false)
|
|
@@ -68,7 +69,7 @@ function makeRecord(over: Partial<ExecutionRecord> = {}): ExecutionRecord {
|
|
|
68
69
|
*/
|
|
69
70
|
function writeSessionJsonl(
|
|
70
71
|
filePath: string,
|
|
71
|
-
identity: { id: string; agent: string; mode: "
|
|
72
|
+
identity: { id: string; agent: string; mode: "background"; task: string; startedAt: number; rootSessionId?: string; parentRecordId?: string; depth?: number; lastTs?: number; chatMode?: boolean },
|
|
72
73
|
assistantText = "result text",
|
|
73
74
|
): void {
|
|
74
75
|
const lastTs = identity.lastTs ?? identity.startedAt + 1000;
|
|
@@ -138,7 +139,7 @@ describe("RecordStore", () => {
|
|
|
138
139
|
describe("archive 立即移除", () => {
|
|
139
140
|
it("archive 后 record 立即从内存移除(不再 linger)", () => {
|
|
140
141
|
const store = new RecordStore(tmpDir);
|
|
141
|
-
const r = makeRecord({ id: "sync-1", mode: "
|
|
142
|
+
const r = makeRecord({ id: "sync-1", mode: "background", status: "closed" });
|
|
142
143
|
store.register(r);
|
|
143
144
|
expect(store.getMutable("sync-1")).toBeDefined();
|
|
144
145
|
store.archive(r);
|
|
@@ -1012,7 +1013,7 @@ describe("RecordStore", () => {
|
|
|
1012
1013
|
// chatMode 必须显式在场(one-shot=false)——renderer isDone 判据依赖它。
|
|
1013
1014
|
expect(Object.keys(data).sort()).toEqual([
|
|
1014
1015
|
"agent", "chatMode", "depth", "displayItems", "eventLog",
|
|
1015
|
-
"id", "mode", "model", "rootSessionId", "round", "startedAt",
|
|
1016
|
+
"id", "mode", "model", "rootSessionId", "round", "slug", "startedAt",
|
|
1016
1017
|
"status", "task", "totalTokens", "turns", "v", "worktree",
|
|
1017
1018
|
]);
|
|
1018
1019
|
expect(data).toMatchObject({
|
|
@@ -1021,7 +1022,7 @@ describe("RecordStore", () => {
|
|
|
1021
1022
|
agent: "worker",
|
|
1022
1023
|
task: "t",
|
|
1023
1024
|
status: "running",
|
|
1024
|
-
mode: "
|
|
1025
|
+
mode: "background",
|
|
1025
1026
|
startedAt: 1000,
|
|
1026
1027
|
rootSessionId: "sess-current",
|
|
1027
1028
|
chatMode: false,
|
|
@@ -1091,13 +1092,13 @@ describe("RecordStore", () => {
|
|
|
1091
1092
|
writeSessionJsonl(path.join(tmpDir, "sa-ps14.jsonl"), {
|
|
1092
1093
|
id: "ps14",
|
|
1093
1094
|
agent: "worker",
|
|
1094
|
-
mode: "
|
|
1095
|
+
mode: "background",
|
|
1095
1096
|
task: "t",
|
|
1096
1097
|
startedAt: 1000,
|
|
1097
1098
|
});
|
|
1098
1099
|
|
|
1099
1100
|
const store = new RecordStore(tmpDir);
|
|
1100
|
-
const records = store.collectRecords();
|
|
1101
|
+
const records = store.collectRecords(100);
|
|
1101
1102
|
expect(records).toHaveLength(1); // 扫描本身不受索引写失败影响
|
|
1102
1103
|
|
|
1103
1104
|
// fire-and-forget saveIndex:等 .catch 执行
|
|
@@ -1122,13 +1123,13 @@ describe("RecordStore", () => {
|
|
|
1122
1123
|
writeSessionJsonl(path.join(tmpDir, "sa-ps14-ok.jsonl"), {
|
|
1123
1124
|
id: "ps14ok",
|
|
1124
1125
|
agent: "worker",
|
|
1125
|
-
mode: "
|
|
1126
|
+
mode: "background",
|
|
1126
1127
|
task: "t",
|
|
1127
1128
|
startedAt: 1000,
|
|
1128
1129
|
});
|
|
1129
1130
|
|
|
1130
1131
|
const store = new RecordStore(tmpDir);
|
|
1131
|
-
store.collectRecords();
|
|
1132
|
+
store.collectRecords(100);
|
|
1132
1133
|
await vi.waitFor(() => expect(saveIndexMock).toHaveBeenCalled());
|
|
1133
1134
|
|
|
1134
1135
|
const warnIndexMsgs = loggerMock.warn.mock.calls.filter((c) =>
|
|
@@ -26,7 +26,7 @@ beforeEach(() => {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
afterEach(() => {
|
|
29
|
-
fs.rmSync(tmpAgentDir, { recursive: true, force: true });
|
|
29
|
+
fs.rmSync(tmpAgentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
/** 写一个最小合法的 manifest 到指定 records 目录。 */
|
|
@@ -36,7 +36,7 @@ function writeManifest(recordsDir: string, id: string): string {
|
|
|
36
36
|
id,
|
|
37
37
|
rootSessionId: "sess-1",
|
|
38
38
|
agentName: "worker",
|
|
39
|
-
status: "
|
|
39
|
+
status: "closed",
|
|
40
40
|
createdAt: Date.now(),
|
|
41
41
|
};
|
|
42
42
|
const filePath = path.join(recordsDir, `${id}.json`);
|
|
@@ -16,102 +16,32 @@
|
|
|
16
16
|
// 4. forkDepth 基线:env PI_SUBAGENT_FORK_DEPTH → fork spawn env 递增
|
|
17
17
|
//
|
|
18
18
|
// mock 策略与 execute-nesting.test.ts 一致(spawn → FakeChild,fs 同步方法 mock,
|
|
19
|
-
// temp-prompt / alive-store / finalized-marker / manifest-store mock
|
|
20
|
-
|
|
21
|
-
import type { PassThrough } from "node:stream";
|
|
19
|
+
// temp-prompt / alive-store / finalized-marker / manifest-store mock;已收敛
|
|
20
|
+
// ./helpers/subagent-service-mocks.ts 四文件共享单源)。
|
|
22
21
|
|
|
23
22
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
stdout = new PassThrough();
|
|
34
|
-
stderr = new PassThrough();
|
|
35
|
-
killed = false;
|
|
36
|
-
killSignal: string | undefined;
|
|
37
|
-
kill(sig?: string): boolean {
|
|
38
|
-
this.killed = true;
|
|
39
|
-
this.killSignal = sig;
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
spawn: vi.fn(() => new FakeChild()),
|
|
46
|
-
// buildEnvBlock 用 execFile 异步取 git branch:默认 err-first 兜底(catch → branch="")
|
|
47
|
-
execFile: vi.fn(
|
|
48
|
-
(
|
|
49
|
-
_cmd: string,
|
|
50
|
-
_args: readonly string[],
|
|
51
|
-
_opts: unknown,
|
|
52
|
-
cb: (err: Error | null, stdout?: string, stderr?: string) => void,
|
|
53
|
-
) => cb(new Error("execFile not configured in this test")),
|
|
54
|
-
),
|
|
55
|
-
};
|
|
56
|
-
});
|
|
24
|
+
import {
|
|
25
|
+
aliveStoreModule,
|
|
26
|
+
childProcessModule,
|
|
27
|
+
finalizedMarkerModule,
|
|
28
|
+
fsSyncModule,
|
|
29
|
+
manifestStoreModule,
|
|
30
|
+
tempPromptModule,
|
|
31
|
+
} from "./helpers/subagent-service-mocks.ts";
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
const actual = await import("node:fs");
|
|
60
|
-
return {
|
|
61
|
-
default: {
|
|
62
|
-
...actual,
|
|
63
|
-
mkdirSync: vi.fn(),
|
|
64
|
-
existsSync: vi.fn(() => false),
|
|
65
|
-
appendFileSync: vi.fn(),
|
|
66
|
-
writeFileSync: vi.fn(),
|
|
67
|
-
readdirSync: vi.fn(() => []),
|
|
68
|
-
},
|
|
69
|
-
mkdirSync: vi.fn(),
|
|
70
|
-
existsSync: vi.fn(() => false),
|
|
71
|
-
appendFileSync: vi.fn(),
|
|
72
|
-
writeFileSync: vi.fn(),
|
|
73
|
-
readdirSync: vi.fn(() => []),
|
|
74
|
-
promises: actual.promises,
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
vi.mock("../alive-store.ts", async (importOriginal) => {
|
|
79
|
-
const actual = await importOriginal<typeof import("../alive-store.ts")>();
|
|
80
|
-
return {
|
|
81
|
-
...actual,
|
|
82
|
-
writeAliveMarker: vi.fn(),
|
|
83
|
-
removeAliveMarker: vi.fn(),
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
vi.mock("../finalized-marker.ts", () => ({
|
|
88
|
-
writeFinalized: vi.fn(),
|
|
89
|
-
readFinalized: vi.fn(() => false),
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
vi.mock("../manifest-store.ts", () => {
|
|
93
|
-
class FakeManifestStore {
|
|
94
|
-
writeManifest = vi.fn(async () => {});
|
|
95
|
-
readManifest = vi.fn(async () => null);
|
|
96
|
-
listAllSync = vi.fn(() => []);
|
|
97
|
-
recoverTmpFiles = vi.fn(async () => []);
|
|
98
|
-
}
|
|
99
|
-
// vi.fn 包裹:构造参数(recordsDir)可从 mock.calls 断言([MF-3] 目录统一验证)。
|
|
100
|
-
// 注意用普通 function(箭头函数不能被 new 调用)。
|
|
101
|
-
return { ManifestStore: vi.fn(function (_recordsDir: string) { return new FakeManifestStore(); }) };
|
|
102
|
-
});
|
|
33
|
+
// ── mock modules(同 execute-nesting.test.ts)──
|
|
103
34
|
|
|
104
|
-
vi.mock("
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}));
|
|
35
|
+
vi.mock("node:child_process", () => childProcessModule());
|
|
36
|
+
vi.mock("node:fs", async (importOriginal) => fsSyncModule(await importOriginal<typeof import("node:fs")>()));
|
|
37
|
+
vi.mock("../alive-store.ts", async (importOriginal) => aliveStoreModule(await importOriginal<typeof import("../alive-store.ts")>()));
|
|
38
|
+
vi.mock("../finalized-marker.ts", () => finalizedMarkerModule());
|
|
39
|
+
vi.mock("../manifest-store.ts", () => manifestStoreModule());
|
|
40
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => tempPromptModule());
|
|
111
41
|
|
|
112
42
|
import { spawn } from "node:child_process";
|
|
113
43
|
|
|
114
|
-
import { waitForSpawn } from "./helpers/spawn-mock.ts";
|
|
44
|
+
import { waitForSpawn, lastSpawnedChild } from "./helpers/spawn-mock.ts";
|
|
115
45
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
116
46
|
import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
|
|
117
47
|
import { ManifestStore } from "../manifest-store.ts";
|
|
@@ -128,43 +58,10 @@ const ENV_DEPTH = "PI_SUBAGENT_DEPTH";
|
|
|
128
58
|
const ENV_ROOT_CWD = "PI_SUBAGENT_ROOT_CWD";
|
|
129
59
|
const ENV_FORK_DEPTH = "PI_SUBAGENT_FORK_DEPTH";
|
|
130
60
|
|
|
131
|
-
interface FakeChild {
|
|
132
|
-
pid: number;
|
|
133
|
-
stdout: PassThrough;
|
|
134
|
-
stderr: PassThrough;
|
|
135
|
-
killed: boolean;
|
|
136
|
-
killSignal: string | undefined;
|
|
137
|
-
kill(sig?: string): boolean;
|
|
138
|
-
emit(event: string, ...args: unknown[]): boolean;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function lastSpawnedChild(): FakeChild {
|
|
142
|
-
const result = mockSpawn.mock.results.at(-1);
|
|
143
|
-
if (!result) throw new Error("spawn was not called yet");
|
|
144
|
-
return result.value as FakeChild;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
61
|
function getLastSpawnEnv(): Record<string, string | undefined> {
|
|
148
62
|
return (mockSpawn.mock.calls.at(-1)?.[2]?.env as Record<string, string | undefined>) ?? {};
|
|
149
63
|
}
|
|
150
64
|
|
|
151
|
-
function sessionHeader(id = "baseline-session"): Record<string, unknown> {
|
|
152
|
-
return { type: "session", id, timestamp: "2026-08-11T00-00-00-000Z", cwd: "/tmp/test" };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function emitStdoutLine(child: FakeChild, obj: Record<string, unknown>): void {
|
|
156
|
-
child.stdout.write(`${JSON.stringify(obj)}\n`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** 驱动 FakeChild 完成:header + 可选事件 + close(0)(runSpawn 自然 resolve)。 */
|
|
160
|
-
async function driveChildToCompletion(child: FakeChild, events: Record<string, unknown>[] = []): Promise<void> {
|
|
161
|
-
emitStdoutLine(child, sessionHeader());
|
|
162
|
-
for (const e of events) emitStdoutLine(child, e);
|
|
163
|
-
child.stdout.end();
|
|
164
|
-
child.stderr.end();
|
|
165
|
-
child.emit("close", 0);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
65
|
// ── 辅助:service 构造 ──
|
|
169
66
|
|
|
170
67
|
function makeEmptyRegistry(): ModelRegistryLike {
|
|
@@ -177,7 +74,7 @@ function makePi() {
|
|
|
177
74
|
|
|
178
75
|
function setup(env: Record<string, string>): { service: SubagentService; store: RecordStore } {
|
|
179
76
|
const agentDir = "/tmp/baseline-it";
|
|
180
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
77
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
181
78
|
modelService.initModel({
|
|
182
79
|
modelRegistry: makeEmptyRegistry(),
|
|
183
80
|
sessionId: "baseline-it",
|
|
@@ -227,7 +124,7 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
227
124
|
const { service, store } = setup({});
|
|
228
125
|
|
|
229
126
|
// 不在 execCtxAls.run 内调用(模拟 ALS 断裂:事件回调上下文读不到 store)
|
|
230
|
-
const handle = await service.execute({ task: "child of parent", ctxModel });
|
|
127
|
+
const handle = await service.execute({ task: "child of parent", slug: "test", ctxModel });
|
|
231
128
|
|
|
232
129
|
const rec = store.collectRecords(10, "all", "root-main").find((r) => r.id === handle.subagentId);
|
|
233
130
|
expect(rec).toBeDefined();
|
|
@@ -239,7 +136,7 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
239
136
|
// 旧实现传 this.sessionId(子进程自己的 session id ≠ ROOT)→ 子进程内列表恒空。
|
|
240
137
|
// 子进程的本进程 sessionId 是 "baseline-it"(initSession 注入),而 record 归属
|
|
241
138
|
// root-main(env 贯穿的真 ROOT)——能查到即证明过滤用的是 sessionRootId。
|
|
242
|
-
const viaService = service.collectRecords(10);
|
|
139
|
+
const viaService = service.queries.collectRecords(10);
|
|
243
140
|
expect(viaService.map((r) => r.id)).toContain(handle.subagentId);
|
|
244
141
|
expect(viaService[0]!.rootSessionId).toBe("root-main");
|
|
245
142
|
});
|
|
@@ -247,7 +144,7 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
247
144
|
it("[顶层] 无 env(根进程):parentRecordId undefined / depth 0", async () => {
|
|
248
145
|
const { service, store } = setup({});
|
|
249
146
|
|
|
250
|
-
const handle = await service.execute({ task: "top level", ctxModel });
|
|
147
|
+
const handle = await service.execute({ task: "top level", slug: "test", ctxModel });
|
|
251
148
|
|
|
252
149
|
const rec = store.collectRecords(10, "all", "baseline-it").find((r) => r.id === handle.subagentId);
|
|
253
150
|
expect(rec).toBeDefined();
|
|
@@ -261,10 +158,10 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
261
158
|
process.env[ENV_DEPTH] = "0";
|
|
262
159
|
|
|
263
160
|
const { service, store } = setup({});
|
|
264
|
-
const
|
|
161
|
+
const execNesting = Reflect.get(service, "execNesting") as ExecCtxAls;
|
|
265
162
|
|
|
266
|
-
const handle = await
|
|
267
|
-
service.execute({ task: "inline nested", ctxModel }),
|
|
163
|
+
const handle = await execNesting.run({ recordId: "sa-inline-parent", depth: 3 }, () =>
|
|
164
|
+
service.execute({ task: "inline nested", slug: "test", ctxModel }),
|
|
268
165
|
);
|
|
269
166
|
|
|
270
167
|
const rec = store.collectRecords(10, "all", "root-main").find((r) => r.id === handle.subagentId);
|
|
@@ -281,14 +178,14 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
281
178
|
|
|
282
179
|
const { service } = setup({});
|
|
283
180
|
|
|
284
|
-
const execPromise = service.execute({ task: "fork child", ctxModel, fork: true });
|
|
181
|
+
const execPromise = service.execute({ task: "fork child", slug: "test", ctxModel, fork: true });
|
|
285
182
|
await waitForSpawn(mockSpawn);
|
|
286
183
|
const childEnv = getLastSpawnEnv();
|
|
287
184
|
|
|
288
185
|
// 742 行 parentDepth = forkDepthAls.getStore() ?? forkDepthBaseline —— ALS 断裂时基线=1,+1 → 2
|
|
289
186
|
expect(childEnv.PI_SUBAGENT_FORK_DEPTH).toBe("2");
|
|
290
187
|
|
|
291
|
-
const child = lastSpawnedChild();
|
|
188
|
+
const child = lastSpawnedChild(mockSpawn);
|
|
292
189
|
child.emit("close", 0);
|
|
293
190
|
await execPromise;
|
|
294
191
|
});
|
|
@@ -303,7 +200,7 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
303
200
|
// MAX_FORK_DEPTH 至少 > 5 才不会被误拒;这里验证基线参与计数的方式是
|
|
304
201
|
// 用直接深度断言——execute 不抛错说明 nestingDepth=6 未超限,护栏不误伤。
|
|
305
202
|
// (MAX_FORK_DEPTH 具体值由 session-context-resolver 定义,这里不硬编码。)
|
|
306
|
-
await expect(service.execute({ task: "deep but allowed", ctxModel })).resolves.toBeDefined();
|
|
203
|
+
await expect(service.execute({ task: "deep but allowed", slug: "test", ctxModel })).resolves.toBeDefined();
|
|
307
204
|
});
|
|
308
205
|
|
|
309
206
|
it("[MF-3 回归] 有 ENV_ROOT_CWD(worktree 子进程):sessions 与 records 两套目录统一编码在 ROOT cwd 段", () => {
|
|
@@ -314,7 +211,7 @@ describe("进程级基线兜底(ALS 断裂修复,pi 事件回调模型)",
|
|
|
314
211
|
|
|
315
212
|
const agentDir = "/tmp/baseline-it";
|
|
316
213
|
const checkoutPath = "/var/folders/worktree/pi-subagents/--root-project--/branch";
|
|
317
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
214
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
318
215
|
modelService.initModel({
|
|
319
216
|
modelRegistry: makeEmptyRegistry(),
|
|
320
217
|
sessionId: "baseline-it",
|