@zhushanwen/subagent-core 0.5.1 → 0.7.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/README.md +1 -1
- package/dist/chunk-KXVBIVOU.js +16 -0
- package/dist/chunk-T2SYBW3I.js +24 -0
- package/dist/chunk-VURUAGMM.js +2305 -0
- package/dist/{types-Dv4QhSJ_.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +835 -668
- package/dist/{types-Dv4QhSJ_.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +835 -668
- package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
- package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
- package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
- package/dist/execution/engine/engine-discovery-scan.js +20 -0
- package/dist/execution/engine/paths.cjs +6 -23
- package/dist/execution/engine/paths.d.cts +1 -8
- package/dist/execution/engine/paths.d.ts +1 -8
- package/dist/execution/engine/paths.js +1 -1
- package/dist/execution/relay-env.cjs +12 -28
- package/dist/execution/relay-env.d.cts +1 -33
- package/dist/execution/relay-env.d.ts +1 -33
- package/dist/execution/relay-env.js +5 -3
- package/dist/index.cjs +14123 -15467
- package/dist/index.d.cts +1179 -1540
- package/dist/index.d.ts +1179 -1540
- package/dist/index.js +7694 -10986
- package/dist.bundle/index.cjs +33485 -0
- package/package.json +20 -26
- package/src/__tests__/record-store-last-line.test.ts +16 -7
- package/src/core/error-message.ts +7 -6
- package/src/core/host-services.ts +8 -2
- package/src/core/notify-ports.ts +17 -2
- package/src/execution/__tests__/agent-registry.test.ts +1 -1
- package/src/execution/__tests__/alive-store.test.ts +1 -1
- package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
- package/src/execution/__tests__/collect-budget.test.ts +291 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +316 -0
- package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +229 -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 +179 -237
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -13
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +21 -34
- package/src/execution/__tests__/finalized-marker.test.ts +1 -1
- package/src/execution/__tests__/gc-timer.test.ts +8 -23
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
- package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
- package/src/execution/__tests__/idle-gc.test.ts +200 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/notify-batch.test.ts +500 -0
- package/src/execution/__tests__/notify-ledger.test.ts +27 -0
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
- package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
- package/src/execution/__tests__/record-store.test.ts +60 -6
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
- package/src/execution/__tests__/relay-env.test.ts +12 -1
- package/src/execution/__tests__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-file-gc.test.ts +1 -1
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-actions-core.test.ts +6 -0
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +14 -11
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
- package/src/execution/__tests__/subagent-service.test.ts +2 -2
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
- package/src/execution/__tests__/sync-collect-recovery.test.ts +1102 -0
- package/src/execution/__tests__/tombstone-store.test.ts +1 -1
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
- 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 +6 -2
- package/src/execution/collect-coordinator.ts +200 -0
- package/src/execution/config.ts +77 -5
- package/src/execution/dialog-queue.ts +6 -73
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
- package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
- package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +2 -2
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
- package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +82 -55
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
- package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
- package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +8 -3
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
- package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
- package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
- package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
- package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
- package/src/execution/engine/__tests__/registry.test.ts +206 -1
- package/src/execution/engine/__tests__/routing.test.ts +97 -0
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
- package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
- package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
- package/src/execution/engine/client/client-options.ts +103 -0
- package/src/execution/engine/client/engine-client.ts +687 -0
- package/src/execution/engine/client/mirror.ts +167 -0
- package/src/execution/engine/client/pid-file.ts +392 -0
- package/src/execution/engine/client/reaper.ts +95 -0
- package/src/execution/engine/client/remote-engine.ts +441 -0
- package/src/execution/engine/client/reverse-router.ts +255 -0
- package/src/execution/engine/common/capability-gate.ts +109 -22
- package/src/execution/engine/common/errors.ts +9 -25
- package/src/execution/engine/common/event-journal.ts +4 -50
- package/src/execution/engine/common/journal-replay.ts +3 -1
- package/src/execution/engine/common/kill-chain.ts +18 -251
- package/src/execution/engine/common/nesting-guard.ts +18 -114
- package/src/execution/engine/common/pool-manager.ts +4 -0
- package/src/execution/engine/common/session-view-service.ts +56 -84
- package/src/execution/engine/config.ts +151 -0
- package/src/execution/engine/d8-compat.ts +346 -0
- package/src/execution/engine/engine-discovery-roots.ts +107 -0
- package/src/execution/engine/engine-discovery-scan.ts +269 -0
- package/src/execution/engine/engine-discovery.ts +30 -7
- package/src/execution/engine/engine-inspect-package.ts +282 -0
- package/src/execution/engine/engine-manifest.ts +244 -0
- package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
- package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
- package/src/execution/engine/host/host-bridge.ts +150 -0
- package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
- package/src/execution/engine/host/pi-host-binding.ts +91 -0
- package/src/execution/engine/host/spawned-children.ts +139 -0
- package/src/execution/engine/host-task-spec.ts +29 -2
- package/src/execution/engine/model-validation.ts +55 -2
- package/src/execution/engine/paths.ts +16 -41
- package/src/execution/engine/port.ts +48 -2
- package/src/execution/engine/registry.ts +153 -34
- package/src/execution/engine/routing.ts +225 -52
- package/src/execution/engine/types.ts +25 -200
- package/src/execution/execution-record.ts +7 -0
- package/src/execution/finalize-record.ts +40 -11
- package/src/execution/idle-gc.ts +89 -14
- package/src/execution/lifecycle-manager.ts +10 -7
- package/src/execution/lifecycle-predicates.ts +10 -8
- package/src/execution/notifier.ts +234 -25
- package/src/execution/notify-host.ts +21 -1
- package/src/execution/record-entry.ts +16 -0
- package/src/execution/record-store.ts +189 -31
- package/src/execution/relay-env.ts +19 -36
- package/src/execution/round-supervisor/domain.ts +89 -0
- package/src/execution/round-supervisor/index.ts +33 -0
- package/src/execution/round-supervisor/notify-accounting.ts +87 -0
- package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
- package/src/execution/round-supervisor/service-binding.test.ts +661 -0
- package/src/execution/round-supervisor/service-binding.ts +273 -0
- package/src/execution/round-supervisor/supervisor.ts +426 -0
- package/src/execution/session-pending.ts +68 -74
- package/src/execution/settled-watchdog.ts +39 -0
- package/src/execution/subagent-actions-core.ts +62 -6
- package/src/execution/subagent-service.ts +1161 -406
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/sync-rebuild.ts +87 -0
- package/src/execution/types.ts +104 -117
- package/src/execution/ui-channels.ts +13 -214
- package/src/execution/ui-request-handler-factory.ts +7 -4
- package/src/execution/ui-request-observability.ts +2 -2
- package/src/execution/workflow-state-root.ts +67 -0
- package/src/execution/worktree-git-ops.ts +5 -0
- package/src/execution/worktree-manager.ts +11 -1
- package/src/index.ts +55 -26
- package/src/orchestration/__tests__/config-loader.test.ts +1 -1
- package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
- 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__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
- package/src/orchestration/__tests__/script-generate.test.ts +5 -5
- package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
- package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +144 -59
- package/src/orchestration/models/types.ts +43 -42
- package/src/shared/__tests__/atomic-write.test.ts +1 -1
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
- package/src/shared/zcode-model-ref.ts +31 -0
- package/dist/chunk-43ONBFZX.js +0 -240
- package/dist/chunk-A4IVZWVX.js +0 -31
- package/dist/chunk-APZY4IME.js +0 -27
- package/dist/chunk-V3VHZ2VX.js +0 -59
- package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
- package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
- package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
- package/dist/execution/engine/engines/zcode/constants.js +0 -54
- package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
- package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
- package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
- package/dist/execution/engine/engines/zcode/reader.js +0 -9
- package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
- package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
- package/src/__tests__/session-runner.test.ts +0 -69
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
- package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
- package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
- package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
- package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
- package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
- package/src/execution/__tests__/output-collector.test.ts +0 -469
- package/src/execution/__tests__/pi-invocation.test.ts +0 -134
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
- package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
- package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
- package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
- package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
- package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
- package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
- package/src/execution/__tests__/spawn-args.test.ts +0 -446
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
- package/src/execution/__tests__/spawned-children.test.ts +0 -236
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/stdin-writer.test.ts +0 -464
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -616
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
- package/src/execution/__tests__/turn-limiter.test.ts +0 -65
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/common/schema-emulation.ts +0 -189
- package/src/execution/engine/common/session-view-projection.ts +0 -51
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
- package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
- package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
- package/src/execution/engine/engines/pi/output-collector.ts +0 -317
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
- package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
- package/src/execution/engine/engines/pi/reader.ts +0 -59
- package/src/execution/engine/engines/pi/registration.ts +0 -37
- package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
- package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
- package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
- package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
- package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
- package/src/execution/engine/engines/zcode/connection.ts +0 -598
- package/src/execution/engine/engines/zcode/constants.ts +0 -176
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
- package/src/execution/engine/engines/zcode/parser.ts +0 -102
- package/src/execution/engine/engines/zcode/preparer.ts +0 -235
- package/src/execution/engine/engines/zcode/reader.ts +0 -381
- package/src/execution/engine/engines/zcode/registration.ts +0 -37
- package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
- package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
- package/src/execution/round-settlement.ts +0 -93
- package/src/execution/ui-request-queue.ts +0 -215
- package/src/shared/schema-env.ts +0 -44
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
// src/execution/__tests__/epipe-fallback.test.ts
|
|
2
|
-
//
|
|
3
|
-
// deliverChatMessage EPIPE 兜底集成测试——热路径 stdin 写入 EPIPE 时自动转冷路径续轮 + 消息重放
|
|
4
|
-
//(D2 后投递协议知识在 PiEngine.deliverPrompt,编排层经 engine.interactRecord 进入)。
|
|
5
|
-
//
|
|
6
|
-
// 场景覆盖:
|
|
7
|
-
// 1. 首次 EPIPE → 自动转冷路径续轮(runSpawn 收到 resume 参数 + 原消息)
|
|
8
|
-
// 2. 连续 2 次 EPIPE → throw 含恢复指引(防无限循环)
|
|
9
|
-
// 3. 热路径成功写入后清零 EPIPE 计数(正常路径不受影响)
|
|
10
|
-
|
|
11
|
-
import * as fs from "node:fs";
|
|
12
|
-
import * as os from "node:os";
|
|
13
|
-
import * as path from "node:path";
|
|
14
|
-
import type { ChildProcess } from "node:child_process";
|
|
15
|
-
import { PassThrough } from "node:stream";
|
|
16
|
-
|
|
17
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
18
|
-
|
|
19
|
-
const { loggerMock } = vi.hoisted(() => ({
|
|
20
|
-
loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
21
|
-
}));
|
|
22
|
-
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
23
|
-
|
|
24
|
-
// mock session-runner:runSpawn 受控 + killAllSpawnedChildren 空实现;
|
|
25
|
-
// getChildByRecord/spawnedChildren 提供真实 Map 语义。
|
|
26
|
-
vi.mock("../engine/engines/pi/session-runner.ts", () => {
|
|
27
|
-
const spawnedChildren = new Map<string, unknown>();
|
|
28
|
-
return {
|
|
29
|
-
runSpawn: vi.fn(),
|
|
30
|
-
killAllSpawnedChildren: vi.fn(),
|
|
31
|
-
registerSpawnedChildForRecord: vi.fn(),
|
|
32
|
-
killRecordChildWithEscalation: vi.fn(),
|
|
33
|
-
spawnedChildren,
|
|
34
|
-
getChildByRecord: (id: string): unknown => spawnedChildren.get(id),
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
import { runSpawn, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
|
|
39
|
-
import * as lifecycle from "../lifecycle-manager.ts";
|
|
40
|
-
import { createRecord } from "../execution-record.ts";
|
|
41
|
-
import { ModelConfigService } from "../model-config-service.ts";
|
|
42
|
-
import type { ModelInfo } from "../model-resolver.ts";
|
|
43
|
-
import { SubagentService } from "../subagent-service.ts";
|
|
44
|
-
import type { PiLike } from "../subagent-service.ts";
|
|
45
|
-
import type { AgentResult, ExecutionRecord } from "../types.ts";
|
|
46
|
-
|
|
47
|
-
const mockRunSpawn = vi.mocked(runSpawn);
|
|
48
|
-
|
|
49
|
-
function makeTmpAgentDir(): string {
|
|
50
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), "epipe-test-"));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function makePi(): PiLike & {
|
|
54
|
-
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
55
|
-
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
56
|
-
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
57
|
-
} {
|
|
58
|
-
return {
|
|
59
|
-
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
60
|
-
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
61
|
-
sendMessage: vi.fn(() => {}),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function makeResult(success: boolean): AgentResult {
|
|
66
|
-
return {
|
|
67
|
-
text: success ? "done" : "err",
|
|
68
|
-
turns: 1,
|
|
69
|
-
durationMs: 100,
|
|
70
|
-
success,
|
|
71
|
-
error: success ? undefined : "boom",
|
|
72
|
-
sessionId: "sess-1",
|
|
73
|
-
toolCalls: [],
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** chatMode running-resumable record(第一轮已完成,等待续聊;v4 B-1 旧 idle 折入 running)。 */
|
|
78
|
-
function makeIdleRecord(id = "sa-epipe"): ExecutionRecord {
|
|
79
|
-
const record = createRecord(id, {
|
|
80
|
-
agent: "general-purpose",
|
|
81
|
-
model: "test/test-model",
|
|
82
|
-
thinkingLevel: "low",
|
|
83
|
-
mode: "background",
|
|
84
|
-
task: "initial task",
|
|
85
|
-
slug: "epipe-chat",
|
|
86
|
-
startedAt: 1000,
|
|
87
|
-
rootSessionId: "root-session",
|
|
88
|
-
chatMode: true,
|
|
89
|
-
});
|
|
90
|
-
record.status = "running";
|
|
91
|
-
record.round = 1;
|
|
92
|
-
record.controller = new AbortController();
|
|
93
|
-
return record;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 构造 EPIPE child:stdin.write 抛 {code:'EPIPE'},模拟子进程已退出但 close 事件尚未到达的窗口。
|
|
98
|
-
*/
|
|
99
|
-
function makeEpipeChild(): ChildProcess {
|
|
100
|
-
const fakeStdin = {
|
|
101
|
-
write: vi.fn(() => {
|
|
102
|
-
const err = Object.assign(new Error("write after end"), { code: "EPIPE" });
|
|
103
|
-
throw err;
|
|
104
|
-
}),
|
|
105
|
-
destroyed: false,
|
|
106
|
-
} as unknown as PassThrough;
|
|
107
|
-
return { stdin: fakeStdin, pid: 99999 } as unknown as ChildProcess;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 构造正常 child:PassThrough stdin(可正常写入)。
|
|
112
|
-
*/
|
|
113
|
-
function makeNormalChild(): ChildProcess {
|
|
114
|
-
return { stdin: new PassThrough(), pid: 88888 } as unknown as ChildProcess;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ============================================================
|
|
118
|
-
// deliverChatMessage EPIPE 兜底
|
|
119
|
-
// ============================================================
|
|
120
|
-
|
|
121
|
-
describe("deliverChatMessage EPIPE 兜底(热路径 stdin EPIPE → 冷路径续轮)", () => {
|
|
122
|
-
let agentDir: string;
|
|
123
|
-
let service: SubagentService;
|
|
124
|
-
let record: ExecutionRecord;
|
|
125
|
-
|
|
126
|
-
beforeEach(() => {
|
|
127
|
-
agentDir = makeTmpAgentDir();
|
|
128
|
-
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
129
|
-
service = new SubagentService({ cwd: agentDir, modelService });
|
|
130
|
-
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
131
|
-
record = makeIdleRecord();
|
|
132
|
-
record.sessionFile = path.join(agentDir, "fake-session.jsonl");
|
|
133
|
-
spawnedChildren.clear();
|
|
134
|
-
mockRunSpawn.mockReset();
|
|
135
|
-
lifecycle._resetLifecycleState();
|
|
136
|
-
loggerMock.warn.mockClear();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
afterEach(() => {
|
|
140
|
-
service.dispose();
|
|
141
|
-
spawnedChildren.clear();
|
|
142
|
-
lifecycle._resetLifecycleState();
|
|
143
|
-
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("首次 EPIPE → 自动转冷路径 resumeRound + 原消息重放", async () => {
|
|
147
|
-
mockRunSpawn.mockResolvedValueOnce(makeResult(true));
|
|
148
|
-
// 注册 EPIPE child(热路径会命中但 stdin.write 抛 EPIPE)
|
|
149
|
-
spawnedChildren.set(record.id, makeEpipeChild());
|
|
150
|
-
|
|
151
|
-
// 投递不 throw(首次 EPIPE 走续轮兜底)
|
|
152
|
-
await expect(service.chatActions.deliverChatMessage(record, "msg after epipe", false)).resolves.toBeUndefined();
|
|
153
|
-
|
|
154
|
-
// spawnedChildren 中的死进程条目已清理(EPIPE catch 块 delete)
|
|
155
|
-
expect(spawnedChildren.has(record.id)).toBe(false);
|
|
156
|
-
|
|
157
|
-
// 冷路径续轮被触发,runSpawn 收到 resume 参数 + 原消息
|
|
158
|
-
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
159
|
-
const call = mockRunSpawn.mock.calls[0]!;
|
|
160
|
-
expect(call[1]).toBe("msg after epipe");
|
|
161
|
-
expect(call[4]).toEqual({
|
|
162
|
-
sessionFile: record.sessionFile,
|
|
163
|
-
model: record.model,
|
|
164
|
-
thinkingLevel: record.thinkingLevel,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// warn 日志包含 EPIPE 关键词
|
|
168
|
-
expect(loggerMock.warn).toHaveBeenCalled();
|
|
169
|
-
const warnCalls = loggerMock.warn.mock.calls;
|
|
170
|
-
const epipeWarn = warnCalls.find((c: unknown[]) =>
|
|
171
|
-
typeof c[0] === "string" && c[0].includes("EPIPE"),
|
|
172
|
-
);
|
|
173
|
-
expect(epipeWarn).toBeDefined();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("连续 2 次 EPIPE → throw 含恢复指引(防无限循环)", async () => {
|
|
177
|
-
mockRunSpawn.mockResolvedValue(makeResult(true));
|
|
178
|
-
// 注册 EPIPE child
|
|
179
|
-
spawnedChildren.set(record.id, makeEpipeChild());
|
|
180
|
-
|
|
181
|
-
// 第 1 次:EPIPE → resume(不 throw)
|
|
182
|
-
await service.chatActions.deliverChatMessage(record, "first epipe", false);
|
|
183
|
-
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
184
|
-
|
|
185
|
-
// resume 完成后 record 回 running(v4 B-1:finalizeRoundToIdle 设 running,旧 idle 折入)
|
|
186
|
-
await vi.waitFor(() => expect(record.status).toBe("running"));
|
|
187
|
-
|
|
188
|
-
// 第 2 次:再次注入 EPIPE child + EPIPE
|
|
189
|
-
spawnedChildren.set(record.id, makeEpipeChild());
|
|
190
|
-
|
|
191
|
-
// 第 2 次 EPIPE → throw 含恢复指引(连续 2 次触发 exhaustion)
|
|
192
|
-
// 注意:第二 EPIPE 时 count 已为 2,catch 块立即 throw(不经过续轮)。
|
|
193
|
-
let thrown: unknown;
|
|
194
|
-
try {
|
|
195
|
-
await service.chatActions.deliverChatMessage(record, "second epipe", false);
|
|
196
|
-
} catch (err) {
|
|
197
|
-
thrown = err;
|
|
198
|
-
}
|
|
199
|
-
expect(thrown).toBeInstanceOf(Error);
|
|
200
|
-
expect((thrown as Error).message).toContain("EPIPE fallback exhausted");
|
|
201
|
-
expect((thrown as Error).message).toContain("2 consecutive EPIPE");
|
|
202
|
-
expect((thrown as Error).message).toContain("action:'close'");
|
|
203
|
-
expect((thrown as Error).message).toContain("action:'start'");
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("热路径成功写入后清零 EPIPE 计数(正常路径不受 EPIPE 计数影响)", async () => {
|
|
207
|
-
mockRunSpawn.mockResolvedValue(makeResult(true));
|
|
208
|
-
|
|
209
|
-
// 第 1 轮:EPIPE → resume
|
|
210
|
-
spawnedChildren.set(record.id, makeEpipeChild());
|
|
211
|
-
await service.chatActions.deliverChatMessage(record, "epipe msg", false);
|
|
212
|
-
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
213
|
-
await vi.waitFor(() => expect(record.status).toBe("running"));
|
|
214
|
-
|
|
215
|
-
// 第 2 轮:正常 child,成功写入(清零 EPIPE 计数)
|
|
216
|
-
spawnedChildren.set(record.id, makeNormalChild());
|
|
217
|
-
await service.chatActions.deliverChatMessage(record, "normal msg", true);
|
|
218
|
-
// 不 throw,status=running(热路径成功)
|
|
219
|
-
expect(record.status).toBe("running");
|
|
220
|
-
|
|
221
|
-
// 第 3 轮:再次 EPIPE → 但因为第 2 轮清零了计数,这次是首次 EPIPE(不 throw)
|
|
222
|
-
// 需要先让 record 回 running(等待续聊态)
|
|
223
|
-
record.status = "running";
|
|
224
|
-
spawnedChildren.set(record.id, makeEpipeChild());
|
|
225
|
-
await expect(service.chatActions.deliverChatMessage(record, "after reset", false)).resolves.toBeUndefined();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("EPIPE 兜底与冷路径共用 in-flight 守卫:resume 在途时 EPIPE 兜底不再二次 spawn(review round2 MF1)", async () => {
|
|
229
|
-
// 冷路径发起 resume,runSpawn 挂起(spawn 异步窗口内)
|
|
230
|
-
let releaseFirst!: (r: AgentResult) => void;
|
|
231
|
-
mockRunSpawn.mockImplementationOnce(
|
|
232
|
-
() => new Promise<AgentResult>((res) => { releaseFirst = res; }),
|
|
233
|
-
);
|
|
234
|
-
await service.chatActions.deliverChatMessage(record, "cold path first", false);
|
|
235
|
-
await vi.waitFor(() => expect(mockRunSpawn).toHaveBeenCalledTimes(1));
|
|
236
|
-
|
|
237
|
-
// 注册 EPIPE child → 投递走热路径 → stdin EPIPE → 兜底调冷路径续轮
|
|
238
|
-
// → in-flight 命中 → throw(不再走第二次 spawn。EPIPE 计数仍 +1 但未达阈值 2,
|
|
239
|
-
// exhaustion 分支不触发;throw 由兜底路径的 resumeRound 守卫抛出)
|
|
240
|
-
spawnedChildren.set(record.id, makeEpipeChild());
|
|
241
|
-
await expect(service.chatActions.deliverChatMessage(record, "epipe during resume", false)).rejects.toThrow(
|
|
242
|
-
/already starting a new round/,
|
|
243
|
-
);
|
|
244
|
-
expect(mockRunSpawn).toHaveBeenCalledTimes(1);
|
|
245
|
-
|
|
246
|
-
releaseFirst(makeResult(true));
|
|
247
|
-
});
|
|
248
|
-
});
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
// src/execution/__tests__/execution-runtime-face.test.ts
|
|
2
|
-
//
|
|
3
|
-
// U10① execution 运行时面导出形态测试 = 第三宿主最小构造示例(验收证据)。
|
|
4
|
-
//
|
|
5
|
-
// 示例语义(设计 docs/design/subagent-core-sink-design.md §3.3 D6 / §4 S5):第三宿主
|
|
6
|
-
// 仅凭模块导出面(不看 core 内部实现)完成——
|
|
7
|
-
// 1. createSubagentService({cwd, modelService, ...}) 参数注入构造(无全局查找);
|
|
8
|
-
// 2. record 状态查询面调用(SubagentService.lookupRecordAnyState 按 id 全态查询 +
|
|
9
|
-
// RecordStore 按状态枚举 listRunning/collectRecords 与按 id findLightById/getFullRecord);
|
|
10
|
-
// 3. 错误类型族 instanceof 分流。
|
|
11
|
-
//
|
|
12
|
-
// 构造先例同 subagent-service.test.ts:真实 ModelConfigService 指向 tmpdir 空目录
|
|
13
|
-
// (loadGlobalConfig 对不存在文件返回默认配置,AgentRegistry 空目录安全),不 mock
|
|
14
|
-
// service 内核;pi 仅在 initSession 场景以最小 duck-typed mock 注入。
|
|
15
|
-
|
|
16
|
-
import * as fs from "node:fs";
|
|
17
|
-
import * as os from "node:os";
|
|
18
|
-
import * as path from "node:path";
|
|
19
|
-
|
|
20
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
21
|
-
|
|
22
|
-
// MF-4:barrel 可达性用例的命名空间导入——「仅凭 src/index.ts 导入」的字面载体
|
|
23
|
-
//(不经深路径),证明第三宿主按文档用法可构造。
|
|
24
|
-
import * as subagentCore from "../../index.ts";
|
|
25
|
-
import { EngineError } from "../engine/common/errors.ts";
|
|
26
|
-
import { EngineNotFoundError } from "../engine/registry.ts";
|
|
27
|
-
import { ZcodePrepareError } from "../engine/engines/zcode/preparer.ts";
|
|
28
|
-
import { ZcodeReaderError } from "../engine/engines/zcode/reader.ts";
|
|
29
|
-
import { ZcodeTaskShapeError } from "../engine/engines/zcode/zcode-engine.ts";
|
|
30
|
-
import { ModelConfigService } from "../model-config-service.ts";
|
|
31
|
-
import {
|
|
32
|
-
SUBAGENT_RECORD_CUSTOM_TYPE,
|
|
33
|
-
toSubagentRecordEntry,
|
|
34
|
-
} from "../record-entry.ts";
|
|
35
|
-
import { RecordStore } from "../record-store.ts";
|
|
36
|
-
import { createSubagentService, getSubagentService } from "../subagent-service.ts";
|
|
37
|
-
import {
|
|
38
|
-
DirtyWorktreeError,
|
|
39
|
-
ForkDepthExceededError,
|
|
40
|
-
ResurrectDeniedError,
|
|
41
|
-
} from "../types.ts";
|
|
42
|
-
import { GitRunError as WorktreeManagerGitRunError } from "../worktree-manager.ts";
|
|
43
|
-
import { GitRunError as WorktreeGitOpsGitRunError } from "../worktree-git-ops.ts";
|
|
44
|
-
|
|
45
|
-
// ── 工具:tmpdir agentDir + 真实 ModelConfigService(第三宿主自备的最小依赖集)──
|
|
46
|
-
|
|
47
|
-
function makeAgentDir(): string {
|
|
48
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), "exec-runtime-face-"));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function makeModelService(agentDir: string): ModelConfigService {
|
|
52
|
-
// cwd 为 ModelConfigServiceInit 必填键(与 agentDir 同指 tmp 目录;构造期仅消费 agentDir)
|
|
53
|
-
return new ModelConfigService({ agentDir, cwd: agentDir });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** 最小 PiLike duck-type(initSession 场景用;本文件不触发真实 pi 调用)。 */
|
|
57
|
-
function makePi() {
|
|
58
|
-
return {
|
|
59
|
-
appendEntry: vi.fn(),
|
|
60
|
-
events: { emit: vi.fn() },
|
|
61
|
-
sendMessage: vi.fn(),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
describe("execution 运行时面(U10① D6)", () => {
|
|
66
|
-
let agentDir: string;
|
|
67
|
-
|
|
68
|
-
beforeEach(() => {
|
|
69
|
-
agentDir = makeAgentDir();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
afterEach(() => {
|
|
73
|
-
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ============================================================
|
|
77
|
-
// ① createSubagentService 工厂:参数注入构造(第三宿主最小构造示例主体)
|
|
78
|
-
// ============================================================
|
|
79
|
-
describe("createSubagentService 工厂(参数注入构造)", () => {
|
|
80
|
-
it("仅凭参数注入构造成功,不读写全局单例槽位(无全局查找)", () => {
|
|
81
|
-
// 前置:全局槽位为空(工厂与 getSubagentService/setSubagentService 单例流程解耦)
|
|
82
|
-
expect(getSubagentService()).toBeNull();
|
|
83
|
-
|
|
84
|
-
const service = createSubagentService({
|
|
85
|
-
cwd: agentDir,
|
|
86
|
-
modelService: makeModelService(agentDir),
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// 构造产物可用(实例方法存在)且未被登记进全局单例槽位——
|
|
90
|
-
// session_start 单例流程行为零改动,宿主自持实例。
|
|
91
|
-
expect(typeof service["lookupRecordAnyState"]).toBe("function");
|
|
92
|
-
expect(getSubagentService()).toBeNull();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("构造后即可调用按 id 全态查询面:空 store 返回 undefined(未 initSession 按「不存在」处理)", () => {
|
|
96
|
-
const service = createSubagentService({
|
|
97
|
-
cwd: agentDir,
|
|
98
|
-
modelService: makeModelService(agentDir),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// lookupRecordAnyState 契约:未初始化/disposed 时 assertReady 抛错 → 按「不存在」处理返回 undefined
|
|
102
|
-
expect(service["lookupRecordAnyState"]("no-such-record")).toBeUndefined();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("initSession(宿主注入最小 pi mock)后查询面同型可用", () => {
|
|
106
|
-
const service = createSubagentService({
|
|
107
|
-
cwd: agentDir,
|
|
108
|
-
modelService: makeModelService(agentDir),
|
|
109
|
-
});
|
|
110
|
-
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
111
|
-
|
|
112
|
-
expect(service["lookupRecordAnyState"]("no-such-record")).toBeUndefined();
|
|
113
|
-
service.dispose();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ============================================================
|
|
118
|
-
// ② record 状态查询面:按状态枚举 / 按 id(RecordStore 公共构造)
|
|
119
|
-
// ============================================================
|
|
120
|
-
describe("record 状态查询面(RecordStore)", () => {
|
|
121
|
-
it("按状态枚举查询:空 store 下 listRunning 与 collectRecords 返回空数组", () => {
|
|
122
|
-
const sessionsDir = path.join(agentDir, "sessions");
|
|
123
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
124
|
-
const store = new RecordStore(sessionsDir);
|
|
125
|
-
|
|
126
|
-
expect(store.listRunning()).toEqual([]);
|
|
127
|
-
expect(store.collectRecords(10, "all", undefined)).toEqual([]);
|
|
128
|
-
expect(store.collectRecords(10, "running", undefined)).toEqual([]);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("按 id 查询:findLightById / getFullRecord / getMutable 对缺失 id 均返回 undefined", () => {
|
|
132
|
-
const sessionsDir = path.join(agentDir, "sessions");
|
|
133
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
134
|
-
const store = new RecordStore(sessionsDir);
|
|
135
|
-
|
|
136
|
-
expect(store.findLightById("missing-id")).toBeUndefined();
|
|
137
|
-
expect(store.getFullRecord("missing-id")).toBeUndefined();
|
|
138
|
-
expect(store.getMutable("missing-id")).toBeUndefined();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// ============================================================
|
|
143
|
-
// ③ 错误类型族:instanceof 分流(导出形态 = 宿主可程序化判别)
|
|
144
|
-
// ============================================================
|
|
145
|
-
describe("错误类型族 instanceof", () => {
|
|
146
|
-
it("EngineError:code 前缀 message + 结构化投影", () => {
|
|
147
|
-
const err = new EngineError("model_not_available", "no provider", "pick another model");
|
|
148
|
-
expect(err).toBeInstanceOf(Error);
|
|
149
|
-
expect(err).toBeInstanceOf(EngineError);
|
|
150
|
-
expect(err.message).toBe("model_not_available: no provider");
|
|
151
|
-
expect(err.toStructured()).toEqual({
|
|
152
|
-
code: "model_not_available",
|
|
153
|
-
message: "model_not_available: no provider",
|
|
154
|
-
recovery: "pick another model",
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("EngineNotFoundError:携带 engineId / registered / source 供分流", () => {
|
|
159
|
-
const err = new EngineNotFoundError("nope", ["pi", "zcode"], "agent.md");
|
|
160
|
-
expect(err).toBeInstanceOf(Error);
|
|
161
|
-
expect(err).toBeInstanceOf(EngineNotFoundError);
|
|
162
|
-
expect(err.code).toBe("engine_not_found");
|
|
163
|
-
expect(err.engineId).toBe("nope");
|
|
164
|
-
expect(err.registered).toEqual(["pi", "zcode"]);
|
|
165
|
-
expect(err.source).toBe("agent.md");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("zcode 引擎错误族:ZcodePrepareError / ZcodeReaderError / ZcodeTaskShapeError", () => {
|
|
169
|
-
const prepare = new ZcodePrepareError("model_not_available", "model gone");
|
|
170
|
-
expect(prepare).toBeInstanceOf(Error);
|
|
171
|
-
expect(prepare).toBeInstanceOf(ZcodePrepareError);
|
|
172
|
-
expect(prepare.code).toBe("model_not_available");
|
|
173
|
-
|
|
174
|
-
const reader = new ZcodeReaderError("db missing", "check journal");
|
|
175
|
-
expect(reader).toBeInstanceOf(Error);
|
|
176
|
-
expect(reader).toBeInstanceOf(ZcodeReaderError);
|
|
177
|
-
|
|
178
|
-
// [U10①] 原 class 未导出(仅内部 throw),本断言钉住 export 补齐后宿主可 instanceof
|
|
179
|
-
const shape = new ZcodeTaskShapeError("capability_unsupported", "no schema");
|
|
180
|
-
expect(shape).toBeInstanceOf(Error);
|
|
181
|
-
expect(shape).toBeInstanceOf(ZcodeTaskShapeError);
|
|
182
|
-
expect(shape.code).toBe("capability_unsupported");
|
|
183
|
-
expect(shape.name).toBe("ZcodeTaskShapeError");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it("执行守卫错误族:ResurrectDeniedError / ForkDepthExceededError / DirtyWorktreeError", () => {
|
|
187
|
-
const resurrect = new ResurrectDeniedError();
|
|
188
|
-
expect(resurrect).toBeInstanceOf(Error);
|
|
189
|
-
expect(resurrect).toBeInstanceOf(ResurrectDeniedError);
|
|
190
|
-
|
|
191
|
-
const forkDepth = new ForkDepthExceededError("depth > max");
|
|
192
|
-
expect(forkDepth).toBeInstanceOf(Error);
|
|
193
|
-
expect(forkDepth).toBeInstanceOf(ForkDepthExceededError);
|
|
194
|
-
|
|
195
|
-
const dirty = new DirtyWorktreeError("uncommitted changes");
|
|
196
|
-
expect(dirty).toBeInstanceOf(Error);
|
|
197
|
-
expect(dirty).toBeInstanceOf(DirtyWorktreeError);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("GitRunError 两处同名列(worktree-manager / worktree-git-ops)各自可 instanceof", () => {
|
|
201
|
-
const fromManager = new WorktreeManagerGitRunError("git fail", { exitCode: 1 });
|
|
202
|
-
expect(fromManager).toBeInstanceOf(Error);
|
|
203
|
-
expect(fromManager).toBeInstanceOf(WorktreeManagerGitRunError);
|
|
204
|
-
expect(fromManager).not.toBeInstanceOf(WorktreeGitOpsGitRunError);
|
|
205
|
-
|
|
206
|
-
const fromGitOps = new WorktreeGitOpsGitRunError("git fail", { exitCode: 128 });
|
|
207
|
-
expect(fromGitOps).toBeInstanceOf(Error);
|
|
208
|
-
expect(fromGitOps).toBeInstanceOf(WorktreeGitOpsGitRunError);
|
|
209
|
-
expect(fromGitOps).not.toBeInstanceOf(WorktreeManagerGitRunError);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// ============================================================
|
|
214
|
-
// ④ record-entry 自描述 entry 面(导出形态确认;投影逐字段等值由
|
|
215
|
-
// execution-record.test.ts 钉住,此处不重复)
|
|
216
|
-
// ============================================================
|
|
217
|
-
describe("record-entry 面", () => {
|
|
218
|
-
it("SUBAGENT_RECORD_CUSTOM_TYPE 值契约:'subagent-record'", () => {
|
|
219
|
-
expect(SUBAGENT_RECORD_CUSTOM_TYPE).toBe("subagent-record");
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("toSubagentRecordEntry 可达且对最小 SubagentRecord 投影出 v===1", () => {
|
|
223
|
-
const entry = toSubagentRecordEntry({
|
|
224
|
-
id: "r1",
|
|
225
|
-
agent: "a.md",
|
|
226
|
-
task: "t",
|
|
227
|
-
slug: "s",
|
|
228
|
-
status: "running",
|
|
229
|
-
mode: "background",
|
|
230
|
-
startedAt: 1,
|
|
231
|
-
rootSessionId: undefined,
|
|
232
|
-
parentRecordId: undefined,
|
|
233
|
-
depth: 0,
|
|
234
|
-
endedAt: undefined,
|
|
235
|
-
turns: 0,
|
|
236
|
-
totalTokens: 0,
|
|
237
|
-
model: "m",
|
|
238
|
-
thinkingLevel: undefined,
|
|
239
|
-
eventLog: [],
|
|
240
|
-
displayItems: [],
|
|
241
|
-
});
|
|
242
|
-
expect(entry.v).toBe(1);
|
|
243
|
-
expect(entry.id).toBe("r1");
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// ============================================================
|
|
248
|
-
// ⑤ barrel 可达性(MF-4):仅凭 src/index.ts 导入完成第三宿主构造
|
|
249
|
-
// (SubagentServiceInit.modelService: ModelConfigService——构造依赖
|
|
250
|
-
// 与 jsdoc 示例引用的访问器必须全部从 barrel 可达)
|
|
251
|
-
// ============================================================
|
|
252
|
-
describe("barrel 可达性(仅凭 src/index.ts 导入,MF-4)", () => {
|
|
253
|
-
it("ModelConfigService / getModelConfigService / createSubagentService 从 barrel 可达并可构造", () => {
|
|
254
|
-
// 符号面:类与访问器均为函数(导出存在性)
|
|
255
|
-
expect(typeof subagentCore.ModelConfigService).toBe("function");
|
|
256
|
-
expect(typeof subagentCore.getModelConfigService).toBe("function");
|
|
257
|
-
|
|
258
|
-
// 进程单例槽位未被本文件写入(同 ① 的全局查找断言口径)
|
|
259
|
-
expect(subagentCore.getModelConfigService()).toBeNull();
|
|
260
|
-
|
|
261
|
-
// 运行时可达:仅凭 barrel 符号完成参数注入构造(ModelConfigServiceInit
|
|
262
|
-
// = { cwd, agentDir },指向 tmp 目录——loadGlobalConfig 对不存在文件
|
|
263
|
-
// 返回默认配置,AgentRegistry 空目录安全,同文件头既有 stub 口径)
|
|
264
|
-
const modelService = new subagentCore.ModelConfigService({ cwd: agentDir, agentDir });
|
|
265
|
-
const service = subagentCore.createSubagentService({ cwd: agentDir, modelService });
|
|
266
|
-
|
|
267
|
-
// 构造产物可用(实例方法存在),且不写进程单例槽位(第三宿主自持实例)
|
|
268
|
-
expect(typeof service["lookupRecordAnyState"]).toBe("function");
|
|
269
|
-
expect(subagentCore.getModelConfigService()).toBeNull();
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
});
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// src/execution/__tests__/get-state-handshake.test.ts
|
|
2
|
-
//
|
|
3
|
-
// [T1/RC-1] requestGetStateOnce 单测:agent_end 决策点惰性回补用的单次 get_state 请求辅助。
|
|
4
|
-
//
|
|
5
|
-
// 契约锚点(与 performGetStateHandshake 共用消息构造 sendGetStateCommand + 字段提取
|
|
6
|
-
// extractGetStateFields,但不做重试循环):
|
|
7
|
-
// 1. response 到达 → resolve 提取后的 GetStateResult(无条件 finish,单次语义无重试);
|
|
8
|
-
// 2. 超时 → resolve 空对象(调用方走保守分支);
|
|
9
|
-
// 3. stdin 同步写失败(EPIPE 形态)→ resolve 空对象、永不 reject(fire-and-forget 契约);
|
|
10
|
-
// 4. 自清理:finish 后从监听表注销本请求 resolver(消费注册器返回的注销函数);
|
|
11
|
-
// 注册器不返回注销函数时退化为 no-op(与握手同形态)。
|
|
12
|
-
//
|
|
13
|
-
// performGetStateHandshake 既有行为由 run-spawn-rpc-mode.test.ts 集成覆盖,此处不重复。
|
|
14
|
-
|
|
15
|
-
import { describe, expect, it, vi } from "vitest";
|
|
16
|
-
|
|
17
|
-
import { requestGetStateOnce } from "../engine/engines/pi/get-state-handshake.ts";
|
|
18
|
-
import type { ChildProcess } from "node:child_process";
|
|
19
|
-
|
|
20
|
-
/** 最小 FakeChild:只需 stdin.write 行为(成功 / 可注入同步 throw)。 */
|
|
21
|
-
function makeFakeStdin(behavior?: { throwOnWrite?: Error }): { child: ChildProcess; writes: string[] } {
|
|
22
|
-
const writes: string[] = [];
|
|
23
|
-
const child = {
|
|
24
|
-
stdin: {
|
|
25
|
-
write(line: string): boolean {
|
|
26
|
-
if (behavior?.throwOnWrite) throw behavior.throwOnWrite;
|
|
27
|
-
writes.push(line);
|
|
28
|
-
return true;
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
} as unknown as ChildProcess;
|
|
32
|
-
return { child, writes };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** 可控的监听表:模拟 stdout pump 的 get_stateListeners(注册返回注销函数)。 */
|
|
36
|
-
function makeListenerRegistry() {
|
|
37
|
-
const resolvers = new Map<string, (data: unknown) => void>();
|
|
38
|
-
const removed: string[] = [];
|
|
39
|
-
const add = (id: string, resolver: (data: unknown) => void) => {
|
|
40
|
-
resolvers.set(id, resolver);
|
|
41
|
-
return () => {
|
|
42
|
-
if (resolvers.get(id) === resolver) resolvers.delete(id);
|
|
43
|
-
removed.push(id);
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
return { resolvers, removed, add };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe("requestGetStateOnce([T1/RC-1] 惰性回补单次请求)", () => {
|
|
50
|
-
it("response 到达 → resolve 提取的 sessionFile/sessionId,并从监听表注销", async () => {
|
|
51
|
-
const { child, writes } = makeFakeStdin();
|
|
52
|
-
const reg = makeListenerRegistry();
|
|
53
|
-
|
|
54
|
-
const promise = requestGetStateOnce(child, reg.add, 1000);
|
|
55
|
-
// 消息构造:单行 JSON {id, type:"get_state"}(与 performGetStateHandshake 同一 sendGetStateCommand)
|
|
56
|
-
expect(writes).toHaveLength(1);
|
|
57
|
-
const sent = JSON.parse(writes[0]!) as { id: string; type: string };
|
|
58
|
-
expect(sent.type).toBe("get_state");
|
|
59
|
-
expect(reg.resolvers.has(sent.id)).toBe(true);
|
|
60
|
-
|
|
61
|
-
// response 到达(含空串 sessionFile 应被过滤的字段形态覆盖提取规则)
|
|
62
|
-
reg.resolvers.get(sent.id)?.({
|
|
63
|
-
sessionFile: "/tmp/sessions/abc.jsonl",
|
|
64
|
-
sessionId: "sess-1",
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await expect(promise).resolves.toEqual({
|
|
68
|
-
sessionFile: "/tmp/sessions/abc.jsonl",
|
|
69
|
-
sessionId: "sess-1",
|
|
70
|
-
});
|
|
71
|
-
// 自清理:finish 后从监听表移除本请求 resolver
|
|
72
|
-
expect(reg.resolvers.has(sent.id)).toBe(false);
|
|
73
|
-
expect(reg.removed).toEqual([sent.id]);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("response 只含部分字段(仅 sessionId)→ resolve 已提取部分(单次语义:到达即 finish,不等重试)", async () => {
|
|
77
|
-
const { child } = makeFakeStdin();
|
|
78
|
-
const reg = makeListenerRegistry();
|
|
79
|
-
|
|
80
|
-
const promise = requestGetStateOnce(child, reg.add, 1000);
|
|
81
|
-
const id = reg.resolvers.keys().next().value as string;
|
|
82
|
-
reg.resolvers.get(id)?.({ sessionId: "only-id" });
|
|
83
|
-
|
|
84
|
-
await expect(promise).resolves.toEqual({ sessionId: "only-id" });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("超时无 response → resolve 空对象(不 reject,不调注销前的 resolver)", async () => {
|
|
88
|
-
vi.useFakeTimers();
|
|
89
|
-
try {
|
|
90
|
-
const { child } = makeFakeStdin();
|
|
91
|
-
const reg = makeListenerRegistry();
|
|
92
|
-
|
|
93
|
-
const promise = requestGetStateOnce(child, reg.add, 1000);
|
|
94
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
95
|
-
|
|
96
|
-
await expect(promise).resolves.toEqual({});
|
|
97
|
-
} finally {
|
|
98
|
-
vi.useRealTimers();
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("stdin 同步写失败(EPIPE code,writeStdinLine rethrow 路径)→ 立即 resolve 空对象、永不 reject", async () => {
|
|
103
|
-
// writeStdinLine 只对 code 为 EPIPE / ERR_STREAM_DESTROYED 的错误 rethrow([R3]),
|
|
104
|
-
// requestGetStateOnce 的 catch 捕获后按「回补失败」resolve 空对象(同超时语义)。
|
|
105
|
-
const epipeErr = Object.assign(new Error("write after end"), { code: "EPIPE" });
|
|
106
|
-
const { child } = makeFakeStdin({ throwOnWrite: epipeErr });
|
|
107
|
-
const reg = makeListenerRegistry();
|
|
108
|
-
|
|
109
|
-
await expect(requestGetStateOnce(child, reg.add, 1000)).resolves.toEqual({});
|
|
110
|
-
// 写失败路径不注册监听(未发出请求)
|
|
111
|
-
expect(reg.resolvers.size).toBe(0);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("注册器不返回注销函数(void 形态,对齐旧握手调用方)→ 正常 resolve 不抛", async () => {
|
|
115
|
-
const { child } = makeFakeStdin();
|
|
116
|
-
const resolvers = new Map<string, (data: unknown) => void>();
|
|
117
|
-
const addVoid = (id: string, resolver: (data: unknown) => void): void => {
|
|
118
|
-
resolvers.set(id, resolver);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const promise = requestGetStateOnce(child, addVoid, 1000);
|
|
122
|
-
const id = resolvers.keys().next().value as string;
|
|
123
|
-
resolvers.get(id)?.({ sessionFile: "/tmp/x.jsonl" });
|
|
124
|
-
|
|
125
|
-
await expect(promise).resolves.toEqual({ sessionFile: "/tmp/x.jsonl" });
|
|
126
|
-
});
|
|
127
|
-
});
|