@zhushanwen/subagent-core 0.2.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-43ONBFZX.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-V3VHZ2VX.js +59 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
- package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
- package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
- package/dist/execution/engine/engines/zcode/constants.js +39 -7
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +18234 -1277
- package/dist/index.d.cts +5646 -673
- package/dist/index.d.ts +5646 -673
- package/dist/index.js +17828 -1277
- package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
- package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
- package/package.json +7 -4
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +93 -3
- package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +77 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +278 -16
- package/src/execution/__tests__/gc-timer.test.ts +57 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +89 -5
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
- package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
- package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +471 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +155 -11
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +13 -12
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +298 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +94 -3
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +598 -0
- package/src/execution/engine/engines/zcode/constants.ts +147 -14
- package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +22 -150
- package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +65 -7
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +181 -85
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +148 -88
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +127 -102
- package/src/execution/settled-watchdog.ts +311 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +1106 -907
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +126 -75
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +542 -9
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +8 -8
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +25 -79
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +160 -104
- package/src/orchestration/lifecycle.ts +371 -97
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +15 -15
- package/src/orchestration/models/trace.ts +9 -9
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +28 -28
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +123 -90
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +261 -217
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +323 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +182 -68
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +97 -204
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/schema-jsonify.ts +1 -1
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +165 -92
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/execution/session-runner.ts +0 -1789
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// zcode-engine-retry.test.ts —— [P0-1 U4/D6] 瞬时失败自动重试一次 + 预算继承测试
|
|
2
|
+
// (设计权威源 docs/design/timeout-zcode-turn-and-settled-watchdog.md §6 D6、§5.2
|
|
3
|
+
// F-1/F-4、§10 U4、§11 P-Z4)。全部跑 __fixtures__/fake-appserver.mjs 子进程
|
|
4
|
+
// (scenario 注入;crashAfterSendMs 为 U4 扩展的崩溃收割注入通道),绝不 spawn 真
|
|
5
|
+
// zcode.cjs。覆盖:
|
|
6
|
+
// - 预算继承纯函数(P-Z4「显式预算下重试轮不重置总预算」的数学本体):剩余 =
|
|
7
|
+
// 总预算 − 已耗尽的精确断言 + 下限门禁 + 非显式 unbounded;
|
|
8
|
+
// - 重试一次成功(连接崩溃形态):crash → failAllTurns 收割 → 新会话重跑(进程
|
|
9
|
+
// 死后惰性重建 + scenario 切换)→ 自然终态;
|
|
10
|
+
// - 重试仍失败两形态各一(超时族 / 连接崩溃族):文案补「已自动重试一次」句
|
|
11
|
+
// (u-z2 留的 F-1 补句义务),连接崩溃族 boot 2 证据;
|
|
12
|
+
// - 不可重试形态不重试:RPC 错误(-32004/-32601 漂移)/ status=error 终态(D6
|
|
13
|
+
// 被否③)/ 用户取消(aborted 短路)——各 create×1 无补句;
|
|
14
|
+
// - 显式预算 vs 默认配置行为差异(P-Z4 门禁集成面):显式预算剩余充足 → 重试;
|
|
15
|
+
// 剩余不足下限 → depleted 不重试;env ≤0 显式关闭上界 → unbounded 重试不受
|
|
16
|
+
// 门禁(与 channel resolveTurnTimerMs 同一 env 通道,vi.stubEnv 缩短量级——
|
|
17
|
+
// 真实默认 60min/300s 下限不在单测等待范围)。
|
|
18
|
+
// 集成层无法在真实等待范围内让重试轮 fire 收窄后的上界(剩余 ≈ 300s 量级),「重试
|
|
19
|
+
// 轮上界=剩余」的数值精确断言由纯函数层承载(turnTimeoutMs 传递链路的集成行为由
|
|
20
|
+
// F1 用例的重试成功佐证)——分层等价,偏差已登记。
|
|
21
|
+
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as os from "node:os";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
28
|
+
|
|
29
|
+
import type { EngineRunResult, RunContext } from "../../../port.ts";
|
|
30
|
+
import type { AgentCallOpts } from "../../../../../orchestration/models/types.ts";
|
|
31
|
+
import { ZCODE_APPSERVER_GOLDEN } from "../golden-sample.ts";
|
|
32
|
+
import {
|
|
33
|
+
ZcodeEngine,
|
|
34
|
+
ZCODE_TURN_RETRY_MIN_BUDGET_MS,
|
|
35
|
+
resolveTransientRetryBudget,
|
|
36
|
+
type ZcodeEngineDeps,
|
|
37
|
+
} from "../zcode-engine.ts";
|
|
38
|
+
|
|
39
|
+
const FAKE_CLI = fileURLToPath(new URL("./__fixtures__/fake-appserver.mjs", import.meta.url));
|
|
40
|
+
const PROVIDER = "test-provider";
|
|
41
|
+
|
|
42
|
+
const GOLDEN_FULL_TEXT = "你好,任务完成";
|
|
43
|
+
/** 挂起场景:send 后零推送——turn 在途等待(crash 收割 / idle 判死的注入底座)。 */
|
|
44
|
+
const HANG_PUSHES: string[] = [];
|
|
45
|
+
/** 权威终态 turn.terminal 的 error 帧(status='error' 终态——D6 被否③的不可重试形态)。 */
|
|
46
|
+
const TERMINAL_ERROR_FRAME =
|
|
47
|
+
'{"method":"v4/telemetry/event","params":{"kind":"turn.terminal","status":"error"}}';
|
|
48
|
+
|
|
49
|
+
let engines: ZcodeEngine[] = [];
|
|
50
|
+
let seq = 0;
|
|
51
|
+
let tmpRoot: string;
|
|
52
|
+
let dataDir: string;
|
|
53
|
+
let v2Path: string;
|
|
54
|
+
|
|
55
|
+
function writeJson(p: string, v: unknown): void {
|
|
56
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
57
|
+
fs.writeFileSync(p, JSON.stringify(v, null, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zcode-eng-retry-"));
|
|
62
|
+
dataDir = path.join(tmpRoot, "data");
|
|
63
|
+
v2Path = path.join(tmpRoot, "v2.json");
|
|
64
|
+
writeJson(v2Path, {
|
|
65
|
+
provider: { [PROVIDER]: { options: { apiKey: "k", baseURL: "https://t.example" }, models: { m1: {} } } },
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
vi.unstubAllEnvs();
|
|
71
|
+
for (const engine of engines.splice(0)) await engine.dispose().catch(() => undefined);
|
|
72
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
interface RpcErrorSpec {
|
|
76
|
+
code: number;
|
|
77
|
+
message: string;
|
|
78
|
+
data?: unknown;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ScenarioOverrides {
|
|
82
|
+
replaceSendPushes?: string[];
|
|
83
|
+
stopBehavior?: "terminal" | "none" | "hang";
|
|
84
|
+
sendError?: RpcErrorSpec;
|
|
85
|
+
/** send 应答后 N ms 自杀(U4 扩展通道——连接崩溃收割注入)。 */
|
|
86
|
+
crashAfterSendMs?: number;
|
|
87
|
+
/** 非首代(崩溃后惰性重建代)send 的推送序列——重试轮的不同行为通道(U4)。 */
|
|
88
|
+
rebootSendPushes?: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface EngineFixture {
|
|
92
|
+
engine: ZcodeEngine;
|
|
93
|
+
stateFile: string;
|
|
94
|
+
workspace: string;
|
|
95
|
+
/** 重写 scenario(新 fake 进程启动时重读——崩溃重建轮的形态切换通道)。 */
|
|
96
|
+
rewriteScenario: (overrides: ScenarioOverrides) => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function makeEngine(overrides: ScenarioOverrides = {}): EngineFixture {
|
|
100
|
+
seq += 1;
|
|
101
|
+
const stateFile = path.join(tmpRoot, `state-${seq}.jsonl`);
|
|
102
|
+
const scenarioFile = path.join(tmpRoot, `scenario-${seq}.json`);
|
|
103
|
+
const workspace = path.join(tmpRoot, `ws-${seq}`);
|
|
104
|
+
const write = (o: ScenarioOverrides): void => {
|
|
105
|
+
writeJson(scenarioFile, {
|
|
106
|
+
createResult: JSON.parse(ZCODE_APPSERVER_GOLDEN.createResponse),
|
|
107
|
+
readResult: JSON.parse(ZCODE_APPSERVER_GOLDEN.readResponse),
|
|
108
|
+
sendPushes: (o.replaceSendPushes ?? HANG_PUSHES).map((l) => JSON.parse(l) as Record<string, unknown>),
|
|
109
|
+
...(o.stopBehavior !== undefined ? { stopBehavior: o.stopBehavior } : {}),
|
|
110
|
+
...(o.sendError !== undefined ? { sendError: o.sendError } : {}),
|
|
111
|
+
...(o.crashAfterSendMs !== undefined ? { crashAfterSendMs: o.crashAfterSendMs } : {}),
|
|
112
|
+
...(o.rebootSendPushes !== undefined
|
|
113
|
+
? { rebootSendPushes: o.rebootSendPushes.map((l) => JSON.parse(l) as Record<string, unknown>) }
|
|
114
|
+
: {}),
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
write(overrides);
|
|
118
|
+
const deps: ZcodeEngineDeps = {
|
|
119
|
+
engineDataDir: () => dataDir,
|
|
120
|
+
cliPath: FAKE_CLI,
|
|
121
|
+
sources: { v2ConfigPath: v2Path },
|
|
122
|
+
processEnv: {
|
|
123
|
+
PATH: process.env.PATH ?? "",
|
|
124
|
+
// 钉扎 appserver 定向(定向不探不降);turn 阈值走全局 env stub(session-channel
|
|
125
|
+
// 的 resolveTurnTimerMs 与引擎预算门禁都直读 process.env——D2 env 通道)
|
|
126
|
+
XYZ_ZCODE_MODE: "appserver",
|
|
127
|
+
FAKE_STATE_FILE: stateFile,
|
|
128
|
+
FAKE_SESSION_SCENARIO: scenarioFile,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
const engine = new ZcodeEngine(deps);
|
|
132
|
+
engines.push(engine);
|
|
133
|
+
return {
|
|
134
|
+
engine,
|
|
135
|
+
stateFile,
|
|
136
|
+
workspace,
|
|
137
|
+
rewriteScenario: (o) => write(o),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── 流水读取 helpers(与 zcode-engine-timeout.test.ts 同款) ──
|
|
142
|
+
|
|
143
|
+
interface StateEvent {
|
|
144
|
+
seq: number;
|
|
145
|
+
ev: string;
|
|
146
|
+
[key: string]: unknown;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function readState(file: string): StateEvent[] {
|
|
150
|
+
try {
|
|
151
|
+
return fs
|
|
152
|
+
.readFileSync(file, "utf8")
|
|
153
|
+
.trim()
|
|
154
|
+
.split("\n")
|
|
155
|
+
.filter(Boolean)
|
|
156
|
+
.map((l) => JSON.parse(l) as StateEvent);
|
|
157
|
+
} catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
163
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function sentMethods(stateFile: string): string[] {
|
|
167
|
+
return readState(stateFile)
|
|
168
|
+
.map((e) => e.frame)
|
|
169
|
+
.filter((f): f is Record<string, unknown> => isRecord(f) && typeof f.method === "string")
|
|
170
|
+
.map((f) => f.method as string);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createCount(stateFile: string): number {
|
|
174
|
+
return sentMethods(stateFile).filter((m) => m === "session/create").length;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function bootCount(stateFile: string): number {
|
|
178
|
+
return readState(stateFile).filter((e) => e.ev === "boot").length;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function makeTask(overrides?: Partial<AgentCallOpts>): AgentCallOpts {
|
|
182
|
+
return { prompt: "做点什么", description: "s", model: `${PROVIDER}/m1`, ...overrides };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function makeCtx(overrides?: Partial<RunContext>): RunContext {
|
|
186
|
+
return { taskId: "sa-retry", poolKey: "", ...overrides };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================
|
|
190
|
+
// 预算继承纯函数(P-Z4 数学本体:剩余 = 总预算 − 已耗尽,不重置)
|
|
191
|
+
// ============================================================
|
|
192
|
+
|
|
193
|
+
describe("resolveTransientRetryBudget(P-Z4:显式预算下重试轮不重置总预算)", () => {
|
|
194
|
+
it("显式预算下剩余 = 总预算 − 已耗尽(精确数值,重试轮上界=剩余)", () => {
|
|
195
|
+
expect(resolveTransientRetryBudget(3_600_000, 1_800_000)).toEqual({ state: "inherit", remainingMs: 1_800_000 });
|
|
196
|
+
expect(resolveTransientRetryBudget(400_000, 100_000)).toEqual({ state: "inherit", remainingMs: 300_000 });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("剩余恰为下限 → inherit(边界含);剩余低于下限 → depleted(不重试直接终态化)", () => {
|
|
200
|
+
expect(resolveTransientRetryBudget(ZCODE_TURN_RETRY_MIN_BUDGET_MS, 0)).toEqual({
|
|
201
|
+
state: "inherit",
|
|
202
|
+
remainingMs: ZCODE_TURN_RETRY_MIN_BUDGET_MS,
|
|
203
|
+
});
|
|
204
|
+
expect(resolveTransientRetryBudget(360_000, 300_000)).toEqual({ state: "depleted" });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("非显式预算(env 未设/非法回落/≤0 显式关闭)→ unbounded(无总预算面,重试不受门禁)", () => {
|
|
208
|
+
expect(resolveTransientRetryBudget(undefined, 0)).toEqual({ state: "unbounded" });
|
|
209
|
+
expect(resolveTransientRetryBudget(undefined, 999_999)).toEqual({ state: "unbounded" });
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ============================================================
|
|
214
|
+
// 集成:重试一次成功(连接崩溃形态,D6/F-4 主路径)
|
|
215
|
+
// ============================================================
|
|
216
|
+
|
|
217
|
+
describe("瞬时失败自动重试一次(P0-1 U4:新会话重跑)", () => {
|
|
218
|
+
it("连接崩溃 → 重试轮新会话重跑成功:boot 2(惰性重建)+ create×2 + 自然终态(默认配置,无显式预算)", async () => {
|
|
219
|
+
// 首代 hang + crash(收割 conn-closed);重建代(重试轮)走完整 golden 流自然终态
|
|
220
|
+
// ——run 整体 await 无法经 rewriteScenario 插手重试轮,rebootSendPushes 是代感知通道
|
|
221
|
+
const f = makeEngine({
|
|
222
|
+
crashAfterSendMs: 120,
|
|
223
|
+
rebootSendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, ...ZCODE_APPSERVER_GOLDEN.terminal],
|
|
224
|
+
});
|
|
225
|
+
const r: EngineRunResult = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
226
|
+
expect(r.outcome.error).toBeUndefined();
|
|
227
|
+
expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
|
|
228
|
+
expect(r.outcome.exitCode).toBe(0);
|
|
229
|
+
// 机械证据:崩溃 boot 1 + 重试轮惰性重建 boot 2;首轮 + 重试轮各自 create 新会话
|
|
230
|
+
expect(bootCount(f.stateFile)).toBe(2);
|
|
231
|
+
expect(createCount(f.stateFile)).toBe(2);
|
|
232
|
+
}, 20_000);
|
|
233
|
+
|
|
234
|
+
it("重试仍失败·超时族:文案补「已自动重试一次仍超时」句(F-1 补句义务),进程不杀(boot 1)", async () => {
|
|
235
|
+
vi.stubEnv("XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS", "300");
|
|
236
|
+
const f = makeEngine(); // hang + stopBehavior 缺省 terminal(两轮止损链都 stop-acked)
|
|
237
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
238
|
+
expect(r.outcome.error).toMatch(/^engine_timeout:/);
|
|
239
|
+
// u-z2 留的补句义务:仅重试真实发生后出现(F-1 样例句逐字)
|
|
240
|
+
expect(r.outcome.error).toContain("已自动重试一次仍超时");
|
|
241
|
+
expect(r.outcome.error).toContain("重试在止损链终局后启动,无新旧任务双跑窗");
|
|
242
|
+
// 重试轮同样走止损链(stop 已送达)+ 停止损不杀共享进程(boot 1)
|
|
243
|
+
expect(r.outcome.error).toContain("session/stop 已送达");
|
|
244
|
+
expect(bootCount(f.stateFile)).toBe(1);
|
|
245
|
+
expect(createCount(f.stateFile)).toBe(2);
|
|
246
|
+
}, 20_000);
|
|
247
|
+
|
|
248
|
+
it("重试仍失败·连接崩溃族:重建轮再崩溃 → 文案补「已自动重试一次仍失败」句(F-4 已重试 1 次)", async () => {
|
|
249
|
+
const f = makeEngine({ crashAfterSendMs: 120 }); // scenario 固定 crash——重建轮同样崩溃
|
|
250
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
251
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 会话执行失败/);
|
|
252
|
+
expect(r.outcome.error).toContain("已自动重试一次仍失败");
|
|
253
|
+
// 一次封顶:首轮 + 重试轮共两次会话、两代进程(崩溃自动重建),不再第三轮
|
|
254
|
+
expect(createCount(f.stateFile)).toBe(2);
|
|
255
|
+
expect(bootCount(f.stateFile)).toBe(2);
|
|
256
|
+
}, 20_000);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ============================================================
|
|
260
|
+
// 不可重试形态不重试(D6 被否谱系 + 形态排除)
|
|
261
|
+
// ============================================================
|
|
262
|
+
|
|
263
|
+
describe("不可重试形态不重试(一次会话收口,无补句)", () => {
|
|
264
|
+
it("RPC 错误(-32004 busy):非瞬时形态 → 不重试(create×1)", async () => {
|
|
265
|
+
const f = makeEngine({ sendError: { code: -32004, message: "Session is busy" } });
|
|
266
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
267
|
+
expect(r.outcome.error).toContain("-32004");
|
|
268
|
+
expect(r.outcome.error).not.toContain("已自动重试");
|
|
269
|
+
expect(createCount(f.stateFile)).toBe(1);
|
|
270
|
+
expect(bootCount(f.stateFile)).toBe(1);
|
|
271
|
+
}, 15_000);
|
|
272
|
+
|
|
273
|
+
it("漂移码(-32601):R5 降级链专属语义,不进瞬时重试(D6 被否②)", async () => {
|
|
274
|
+
const f = makeEngine({ sendError: { code: -32601, message: "method not found" } });
|
|
275
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
276
|
+
expect(r.outcome.error).toContain("-32601");
|
|
277
|
+
expect(r.outcome.error).not.toContain("已自动重试");
|
|
278
|
+
expect(createCount(f.stateFile)).toBe(1);
|
|
279
|
+
}, 15_000);
|
|
280
|
+
|
|
281
|
+
it("status='error' 终态:D6 被否③(错误内容可能非瞬时)→ 不重试(create×1)", async () => {
|
|
282
|
+
const f = makeEngine({ replaceSendPushes: [TERMINAL_ERROR_FRAME] });
|
|
283
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
284
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=error/);
|
|
285
|
+
expect(r.outcome.error).not.toContain("已自动重试");
|
|
286
|
+
expect(createCount(f.stateFile)).toBe(1);
|
|
287
|
+
expect(bootCount(f.stateFile)).toBe(1);
|
|
288
|
+
}, 15_000);
|
|
289
|
+
|
|
290
|
+
it("用户取消(turn 在途时 abort):aborted 短路优先 → 不重试(create×1)", async () => {
|
|
291
|
+
vi.stubEnv("XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS", "5000");
|
|
292
|
+
const f = makeEngine(); // hang + stopBehavior terminal(stop 优雅生效 → aborted 收口)
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
setTimeout(() => controller.abort(), 80);
|
|
295
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx({ signal: controller.signal }));
|
|
296
|
+
// 用户取消入口现状语义零改动(U2):engine_run_failed 中止标记(非超时语义)
|
|
297
|
+
expect(r.outcome.error).toContain("被中止");
|
|
298
|
+
expect(r.outcome.error).not.toContain("已自动重试");
|
|
299
|
+
expect(createCount(f.stateFile)).toBe(1);
|
|
300
|
+
}, 15_000);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ============================================================
|
|
304
|
+
// 显式预算 vs 默认配置(P-Z4 门禁集成面)
|
|
305
|
+
// ============================================================
|
|
306
|
+
|
|
307
|
+
describe("显式预算门禁与默认配置的行为差异(P-Z4 集成面)", () => {
|
|
308
|
+
it("显式预算剩余充足(310s > 300s 下限 + 首轮消耗)→ 重试发生:boot 2 + create×2 + 自然终态", async () => {
|
|
309
|
+
vi.stubEnv("XYZ_ZCODE_TURN_MAX_TIMEOUT_MS", "310000");
|
|
310
|
+
const f = makeEngine({
|
|
311
|
+
crashAfterSendMs: 120,
|
|
312
|
+
rebootSendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, ...ZCODE_APPSERVER_GOLDEN.terminal],
|
|
313
|
+
});
|
|
314
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
315
|
+
// 重试未被预算门禁拦截(剩余 ≥ 下限),重试轮成功收口
|
|
316
|
+
expect(r.outcome.error).toBeUndefined();
|
|
317
|
+
expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
|
|
318
|
+
expect(bootCount(f.stateFile)).toBe(2);
|
|
319
|
+
expect(createCount(f.stateFile)).toBe(2);
|
|
320
|
+
}, 20_000);
|
|
321
|
+
|
|
322
|
+
it("显式预算剩余不足下限(60s − 消耗 < 300s)→ depleted 不重试:boot 1 + create×1 + 无补句", async () => {
|
|
323
|
+
vi.stubEnv("XYZ_ZCODE_TURN_MAX_TIMEOUT_MS", "60000");
|
|
324
|
+
const f = makeEngine({ crashAfterSendMs: 120 }); // scenario 固定 crash(无重试即不重建)
|
|
325
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
326
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 会话执行失败/);
|
|
327
|
+
expect(r.outcome.error).not.toContain("已自动重试");
|
|
328
|
+
expect(createCount(f.stateFile)).toBe(1);
|
|
329
|
+
expect(bootCount(f.stateFile)).toBe(1);
|
|
330
|
+
}, 15_000);
|
|
331
|
+
|
|
332
|
+
it("env ≤0 显式关闭总上界 → 无显式预算(unbounded)→ 重试不受门禁:boot 2 证据", async () => {
|
|
333
|
+
vi.stubEnv("XYZ_ZCODE_TURN_MAX_TIMEOUT_MS", "0");
|
|
334
|
+
const f = makeEngine({ crashAfterSendMs: 120 }); // scenario 固定 crash——重试轮同样崩溃
|
|
335
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
336
|
+
expect(r.outcome.error).toContain("已自动重试一次仍失败");
|
|
337
|
+
expect(createCount(f.stateFile)).toBe(2);
|
|
338
|
+
expect(bootCount(f.stateFile)).toBe(2);
|
|
339
|
+
}, 20_000);
|
|
340
|
+
|
|
341
|
+
it("超时族 + 预算门禁拦截(depleted)→ 未重试:engine_timeout 文案不含补句(补句仅随真实重试,F-1 一致性)", async () => {
|
|
342
|
+
vi.stubEnv("XYZ_ZCODE_TURN_IDLE_TIMEOUT_MS", "300");
|
|
343
|
+
vi.stubEnv("XYZ_ZCODE_TURN_MAX_TIMEOUT_MS", "60000"); // 剩余 60s − 首轮消耗 < 300s 下限
|
|
344
|
+
const f = makeEngine(); // hang + stop terminal(首轮 idle 判死 → timeout 形态)
|
|
345
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
346
|
+
expect(r.outcome.error).toMatch(/^engine_timeout:/);
|
|
347
|
+
expect(r.outcome.error).toContain("idle 判定");
|
|
348
|
+
// 目标 3 的反面断言:未重试形态不含「已自动重试」句(与行为一致)
|
|
349
|
+
expect(r.outcome.error).not.toContain("已自动重试");
|
|
350
|
+
expect(createCount(f.stateFile)).toBe(1);
|
|
351
|
+
expect(bootCount(f.stateFile)).toBe(1);
|
|
352
|
+
}, 15_000);
|
|
353
|
+
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// zcode-engine-status.test.ts —— [P0-1 U3] 引擎终态 status 消费测试(设计权威源
|
|
2
|
+
// docs/design/timeout-zcode-turn-and-settled-watchdog.md §6 D5、§5.2 F-3、§8 A4;
|
|
3
|
+
// ⛔P-Z2 门修正:真实 status 枚举 = ["success","interrupted","failed"](app-server
|
|
4
|
+
// dist schema f.enum 实证,无 "error")——判据 failed 主 + error 容错,v1 的
|
|
5
|
+
// `=== "error"` 对真实 failed 终态漏分流即假成功(探针 /tmp/pz2-probe 实锤)。
|
|
6
|
+
// 覆盖:
|
|
7
|
+
// - status:'failed'(真实帧形,errorCode/errorMessage 携带)→ run-failed 且错误
|
|
8
|
+
// 详情来自 terminal 帧(errorCode/errorMessage 优先——真实形态错误详情只在
|
|
9
|
+
// terminal 帧,read/delta 携带不了;read 尾部为兜底):
|
|
10
|
+
// ① 帧序 A:turn.terminal failed 先到(权威 settle);
|
|
11
|
+
// ② 帧序 B:final-frame 宽松 settle success 先到 + turn.terminal failed 迟到
|
|
12
|
+
// (u-z1 lastTerminalStatus/lastTerminalError 衔接——只记录不改写落定);
|
|
13
|
+
// - P-Z2 降级形态:failed 无 errorCode/errorMessage + read 无错误信息 → 仍
|
|
14
|
+
// run-failed 不假成功(read/finalText 尾部兜底);
|
|
15
|
+
// - 全空形态(无 delta/收尾帧/read 内容)→ 不伪造错误详情;
|
|
16
|
+
// - status:'interrupted'(真实枚举成员)→ 不误判失败(用户中断不属引擎失败,
|
|
17
|
+
// 随宿主 abort 主路径——落定语义登记);
|
|
18
|
+
// - status:'error'(非真实枚举)→ 容错分支仍 run-failed(防协议漂移滑回假成功);
|
|
19
|
+
// - 成功路径零回归:terminal success 先到 / final-frame 先到 + terminal success
|
|
20
|
+
// 迟到 / final-frame 独走(无 turn.terminal)三形态均 parsed;
|
|
21
|
+
// - schema 任务遇 failed 终态:分流先于 schema 校验(不产 schema_emulation_failed、
|
|
22
|
+
// 不进强化重试)。
|
|
23
|
+
// 失败终态即时到达,idle/ceiling 默认阈值不参与——无需 env stub。
|
|
24
|
+
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
import * as os from "node:os";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
|
|
30
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
31
|
+
|
|
32
|
+
import type { EngineRunResult, RunContext } from "../../../port.ts";
|
|
33
|
+
import type { AgentCallOpts } from "../../../../../orchestration/models/types.ts";
|
|
34
|
+
import { ZCODE_APPSERVER_GOLDEN } from "../golden-sample.ts";
|
|
35
|
+
import { ZcodeEngine, hostZcodeDbPath, type ZcodeEngineDeps } from "../zcode-engine.ts";
|
|
36
|
+
|
|
37
|
+
const FAKE_CLI = fileURLToPath(new URL("./__fixtures__/fake-appserver.mjs", import.meta.url));
|
|
38
|
+
const PROVIDER = "test-provider";
|
|
39
|
+
|
|
40
|
+
const GOLDEN_SESSION_ID = "sess_golden_r3_01";
|
|
41
|
+
const GOLDEN_FULL_TEXT = "你好,任务完成";
|
|
42
|
+
/** 真实协议 failed 终态帧(⛔P-Z2 门 credfail 实测帧形:errorCode/errorMessage 携带)。 */
|
|
43
|
+
const TERMINAL_FAILED_REAL =
|
|
44
|
+
'{"method":"v4/telemetry/event","params":{"kind":"turn.terminal","status":"failed","errorCode":"model_request_failed","errorMessage":"Provider marked the request as failed (bad port / credential invalid)"}}';
|
|
45
|
+
/** failed 无错误详情变体(P-Z2 降级形态注入材料)。 */
|
|
46
|
+
const TERMINAL_FAILED_BARE =
|
|
47
|
+
'{"method":"v4/telemetry/event","params":{"kind":"turn.terminal","status":"failed"}}';
|
|
48
|
+
/** interrupted 终态(真实枚举成员——用户中断,不误判失败的回归面)。 */
|
|
49
|
+
const TERMINAL_INTERRUPTED_FRAME =
|
|
50
|
+
'{"method":"v4/telemetry/event","params":{"kind":"turn.terminal","status":"interrupted"}}';
|
|
51
|
+
/** 非真实枚举的 "error" 终态(容错分支——防协议漂移滑回假成功)。 */
|
|
52
|
+
const TERMINAL_ERROR_FRAME =
|
|
53
|
+
'{"method":"v4/telemetry/event","params":{"kind":"turn.terminal","status":"error"}}';
|
|
54
|
+
/** 收尾帧(A.2 ⑤ response 形态)——final-frame 宽松终态的注入材料(golden terminal[1])。 */
|
|
55
|
+
const GOLDEN_FINAL_FRAME = ZCODE_APPSERVER_GOLDEN.terminal[1];
|
|
56
|
+
/** read 兜底返回的服务端错误文本(read 尾部兜底路径的载体)。 */
|
|
57
|
+
const READ_ERROR_TEXT = "Error: provider request failed (401 Unauthorized)";
|
|
58
|
+
/** 真实 failed 终态的 terminal 帧错误详情(探针 credfail 实测值)。 */
|
|
59
|
+
const TERMINAL_ERROR_CODE = "model_request_failed";
|
|
60
|
+
const TERMINAL_ERROR_MESSAGE = "Provider marked the request as failed (bad port / credential invalid)";
|
|
61
|
+
|
|
62
|
+
let engines: ZcodeEngine[] = [];
|
|
63
|
+
let seq = 0;
|
|
64
|
+
let tmpRoot: string;
|
|
65
|
+
let dataDir: string;
|
|
66
|
+
let v2Path: string;
|
|
67
|
+
|
|
68
|
+
function writeJson(p: string, v: unknown): void {
|
|
69
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
70
|
+
fs.writeFileSync(p, JSON.stringify(v, null, 2));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
engines = [];
|
|
75
|
+
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zcode-eng-status-"));
|
|
76
|
+
dataDir = path.join(tmpRoot, "data");
|
|
77
|
+
v2Path = path.join(tmpRoot, "v2.json");
|
|
78
|
+
writeJson(v2Path, {
|
|
79
|
+
provider: { [PROVIDER]: { options: { apiKey: "k", baseURL: "https://t.example" }, models: { m1: {} } } },
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
afterEach(async () => {
|
|
84
|
+
for (const engine of engines.splice(0)) await engine.dispose().catch(() => undefined);
|
|
85
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
interface ScenarioOverrides {
|
|
89
|
+
/** send 应答前逐帧推送的注入序列(覆盖缺省 golden 流)。 */
|
|
90
|
+
sendPushes: string[];
|
|
91
|
+
/** 覆盖 read 应答(缺省 golden readResponse——assistant 全文)。 */
|
|
92
|
+
readResult?: unknown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface EngineFixture {
|
|
96
|
+
engine: ZcodeEngine;
|
|
97
|
+
workspace: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function makeEngine(overrides: ScenarioOverrides): EngineFixture {
|
|
101
|
+
seq += 1;
|
|
102
|
+
const scenarioFile = path.join(tmpRoot, `scenario-${seq}.json`);
|
|
103
|
+
const workspace = path.join(tmpRoot, `ws-${seq}`);
|
|
104
|
+
writeJson(scenarioFile, {
|
|
105
|
+
createResult: JSON.parse(ZCODE_APPSERVER_GOLDEN.createResponse),
|
|
106
|
+
readResult: overrides.readResult ?? JSON.parse(ZCODE_APPSERVER_GOLDEN.readResponse),
|
|
107
|
+
sendPushes: overrides.sendPushes.map((l) => JSON.parse(l) as Record<string, unknown>),
|
|
108
|
+
});
|
|
109
|
+
const deps: ZcodeEngineDeps = {
|
|
110
|
+
engineDataDir: () => dataDir,
|
|
111
|
+
cliPath: FAKE_CLI,
|
|
112
|
+
sources: { v2ConfigPath: v2Path },
|
|
113
|
+
processEnv: {
|
|
114
|
+
PATH: process.env.PATH ?? "",
|
|
115
|
+
// 钉扎 appserver 定向(定向不探不降,与 zcode-engine-appserver.test.ts 同款)
|
|
116
|
+
XYZ_ZCODE_MODE: "appserver",
|
|
117
|
+
FAKE_SESSION_SCENARIO: scenarioFile,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const engine = new ZcodeEngine(deps);
|
|
121
|
+
engines.push(engine);
|
|
122
|
+
return { engine, workspace };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function makeTask(overrides?: Partial<AgentCallOpts>): AgentCallOpts {
|
|
126
|
+
return { prompt: "做点什么", description: "s", model: `${PROVIDER}/m1`, ...overrides };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function makeCtx(overrides?: Partial<RunContext>): RunContext {
|
|
130
|
+
return { taskId: "sa-status", poolKey: "", ...overrides };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================
|
|
134
|
+
// status='failed'(真实帧形)→ run-failed(⛔P-Z2 门修正根修回归面)
|
|
135
|
+
// ============================================================
|
|
136
|
+
|
|
137
|
+
describe("failed 终态分流(真实协议枚举:parsed 前消费 status → run-failed,不假成功)", () => {
|
|
138
|
+
it("帧序 A:turn.terminal failed(errorCode/errorMessage)先到 + read 有全文 → run-failed 且详情取 terminal 帧(优先于 read 尾部)", async () => {
|
|
139
|
+
const f = makeEngine({
|
|
140
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, TERMINAL_FAILED_REAL],
|
|
141
|
+
// read 缺省 golden(有全文)——证明 terminal 帧详情优先级高于 read 尾部
|
|
142
|
+
});
|
|
143
|
+
const r: EngineRunResult = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
144
|
+
// F-3 文案形态:engine_run_failed 前缀 + 真实枚举值 failed + 会话 id(非 engine_timeout 族)
|
|
145
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=failed/);
|
|
146
|
+
expect(r.outcome.error).toContain(`(会话 ${GOLDEN_SESSION_ID})`);
|
|
147
|
+
expect(r.outcome.error).not.toContain("engine_timeout");
|
|
148
|
+
// ⛔P-Z2:错误详情来自 terminal 帧(read/delta 携带不了——errorCode/errorMessage 优先)
|
|
149
|
+
expect(r.outcome.error).toContain(`errorCode: ${TERMINAL_ERROR_CODE}`);
|
|
150
|
+
expect(r.outcome.error).toContain(TERMINAL_ERROR_MESSAGE);
|
|
151
|
+
// 详情已采信,read 尾部不再叠加(详情 > read 兜底的优先级裁决)
|
|
152
|
+
expect(r.outcome.error).not.toContain(GOLDEN_FULL_TEXT);
|
|
153
|
+
// 恢复指引(F-3)
|
|
154
|
+
expect(r.outcome.error).toContain("engine_credential_missing 同族排查");
|
|
155
|
+
// 失败形态(非成功):异常终态口径 exitCode=null、content 空
|
|
156
|
+
expect(r.outcome.exitCode).toBeNull();
|
|
157
|
+
expect(r.outcome.content).toBe("");
|
|
158
|
+
// run 不 reject——正常 handle 返回(record 必须收尾),sessionId 留痕进 sessionRef
|
|
159
|
+
//(dbPath 形态随共享宿主 HOME 重构为宿主 db 绝对路径——hostZcodeDbPath)
|
|
160
|
+
expect(r.outcome.sessionId).toBe(GOLDEN_SESSION_ID);
|
|
161
|
+
expect(r.handle.data.sessionRef).toEqual({ sessionId: GOLDEN_SESSION_ID, dbPath: hostZcodeDbPath() });
|
|
162
|
+
}, 15_000);
|
|
163
|
+
|
|
164
|
+
it("帧序 B:final-frame 宽松 settle success 先到 + turn.terminal failed 迟到(lastTerminalStatus/lastTerminalError 衔接)→ 仍 run-failed", async () => {
|
|
165
|
+
const f = makeEngine({
|
|
166
|
+
// final-frame 先落定(session-channel 恒 settle success),权威 failed 只被记录
|
|
167
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, GOLDEN_FINAL_FRAME, TERMINAL_FAILED_REAL],
|
|
168
|
+
});
|
|
169
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
170
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=failed/);
|
|
171
|
+
// 迟到 terminal 帧的详情仍透传(channel 只记录不改写落定,消费层识破假成功)
|
|
172
|
+
expect(r.outcome.error).toContain(`errorCode: ${TERMINAL_ERROR_CODE}`);
|
|
173
|
+
expect(r.outcome.error).toContain(TERMINAL_ERROR_MESSAGE);
|
|
174
|
+
expect(r.outcome.exitCode).toBeNull();
|
|
175
|
+
expect(r.outcome.content).toBe("");
|
|
176
|
+
}, 15_000);
|
|
177
|
+
|
|
178
|
+
it("P-Z2 降级形态:failed 无 errorCode/errorMessage + final-frame 先到 + read 无错误信息 → 尾部兜底,仍 run-failed 不假成功", async () => {
|
|
179
|
+
const f = makeEngine({
|
|
180
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, GOLDEN_FINAL_FRAME, TERMINAL_FAILED_BARE],
|
|
181
|
+
readResult: { messages: [] }, // read 无错误信息(降级形态判据)
|
|
182
|
+
});
|
|
183
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
184
|
+
// 覆盖面收窄但不假成功:final-frame 的宽松 success 不采信
|
|
185
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=failed/);
|
|
186
|
+
expect(r.outcome.exitCode).toBeNull();
|
|
187
|
+
expect(r.outcome.content).toBe("");
|
|
188
|
+
// 尾部以 finalText 降级合成(read 空 → 收尾帧全文 → delta 聚合的既有降级链)
|
|
189
|
+
expect(r.outcome.error).toContain(GOLDEN_FULL_TEXT);
|
|
190
|
+
}, 15_000);
|
|
191
|
+
|
|
192
|
+
it("全空形态:仅 turn.terminal failed(无 delta/收尾帧/错误详情)+ read 空 → run-failed 且不伪造错误详情", async () => {
|
|
193
|
+
const f = makeEngine({
|
|
194
|
+
sendPushes: [TERMINAL_FAILED_BARE],
|
|
195
|
+
readResult: { messages: [] },
|
|
196
|
+
});
|
|
197
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
198
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=failed/);
|
|
199
|
+
expect(r.outcome.error).toContain("服务端无返回内容");
|
|
200
|
+
expect(r.outcome.exitCode).toBeNull();
|
|
201
|
+
expect(r.outcome.content).toBe("");
|
|
202
|
+
}, 15_000);
|
|
203
|
+
|
|
204
|
+
it("schema 任务遇 failed 终态:分流先于 schema 校验(不产 schema_emulation_failed、不进强化重试)", async () => {
|
|
205
|
+
const f = makeEngine({
|
|
206
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, TERMINAL_FAILED_REAL],
|
|
207
|
+
});
|
|
208
|
+
const r = await f.engine.run(
|
|
209
|
+
makeTask({ cwd: f.workspace, schema: { type: "object", properties: { answer: { type: "string" } }, required: ["answer"] } }),
|
|
210
|
+
makeCtx(),
|
|
211
|
+
);
|
|
212
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=failed/);
|
|
213
|
+
expect(r.outcome.error).not.toContain("schema_emulation_failed");
|
|
214
|
+
expect(r.outcome.parsedOutput).toBeUndefined();
|
|
215
|
+
}, 15_000);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ============================================================
|
|
219
|
+
// interrupted 不误判 + error 容错分支(⛔P-Z2 门修正的两翼)
|
|
220
|
+
// ============================================================
|
|
221
|
+
|
|
222
|
+
describe("非失败终态裁决(interrupted 随宿主 abort 主路径;error 容错防漂移)", () => {
|
|
223
|
+
it("status:'interrupted'(真实枚举成员)→ 不误判失败:parsed 收口(用户中断不属引擎失败,落定语义登记)", async () => {
|
|
224
|
+
const f = makeEngine({
|
|
225
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, TERMINAL_INTERRUPTED_FRAME],
|
|
226
|
+
});
|
|
227
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
228
|
+
// 不分流:引擎侧不抢先把它终态化为失败(宿主 abort 主路径的收口语义保留)
|
|
229
|
+
expect(r.outcome.error).toBeUndefined();
|
|
230
|
+
expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
|
|
231
|
+
expect(r.outcome.exitCode).toBe(0);
|
|
232
|
+
}, 15_000);
|
|
233
|
+
|
|
234
|
+
it("status:'error'(非真实枚举,容错分支)→ 仍 run-failed 不假成功(防协议漂移滑回 v1 缺陷)", async () => {
|
|
235
|
+
const f = makeEngine({
|
|
236
|
+
sendPushes: [TERMINAL_ERROR_FRAME],
|
|
237
|
+
readResult: { messages: [] },
|
|
238
|
+
});
|
|
239
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
240
|
+
expect(r.outcome.error).toMatch(/^engine_run_failed: app-server 终态 status=error/);
|
|
241
|
+
expect(r.outcome.error).toContain("服务端无返回内容");
|
|
242
|
+
expect(r.outcome.exitCode).toBeNull();
|
|
243
|
+
expect(r.outcome.content).toBe("");
|
|
244
|
+
}, 15_000);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ============================================================
|
|
248
|
+
// 成功路径零回归(负面验证——§8 A6 同族)
|
|
249
|
+
// ============================================================
|
|
250
|
+
|
|
251
|
+
describe("成功路径零回归(U3 分流不误伤正常终态)", () => {
|
|
252
|
+
it("terminal success 先到(golden 流)→ parsed:content 全文、exitCode 0、无 error", async () => {
|
|
253
|
+
const f = makeEngine({
|
|
254
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, ...ZCODE_APPSERVER_GOLDEN.terminal],
|
|
255
|
+
});
|
|
256
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
257
|
+
expect(r.outcome.error).toBeUndefined();
|
|
258
|
+
expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
|
|
259
|
+
expect(r.outcome.exitCode).toBe(0);
|
|
260
|
+
}, 15_000);
|
|
261
|
+
|
|
262
|
+
it("final-frame 先到 + terminal success 迟到 → parsed(权威 success 后到不改写已落定的成功)", async () => {
|
|
263
|
+
const f = makeEngine({
|
|
264
|
+
// final-frame 在权威 terminal 之前到达(宽松终态先 settle,权威后到只记录)
|
|
265
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, GOLDEN_FINAL_FRAME, ZCODE_APPSERVER_GOLDEN.terminal[0]],
|
|
266
|
+
});
|
|
267
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
268
|
+
expect(r.outcome.error).toBeUndefined();
|
|
269
|
+
expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
|
|
270
|
+
expect(r.outcome.exitCode).toBe(0);
|
|
271
|
+
}, 15_000);
|
|
272
|
+
|
|
273
|
+
it("final-frame 独走(无 turn.terminal 到达)→ parsed(无权威 status 可消费,不判失败)", async () => {
|
|
274
|
+
const f = makeEngine({
|
|
275
|
+
sendPushes: [...ZCODE_APPSERVER_GOLDEN.pushStream, GOLDEN_FINAL_FRAME],
|
|
276
|
+
});
|
|
277
|
+
const r = await f.engine.run(makeTask({ cwd: f.workspace }), makeCtx());
|
|
278
|
+
expect(r.outcome.error).toBeUndefined();
|
|
279
|
+
expect(r.outcome.content).toBe(GOLDEN_FULL_TEXT);
|
|
280
|
+
expect(r.outcome.exitCode).toBe(0);
|
|
281
|
+
}, 15_000);
|
|
282
|
+
});
|