@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,298 +0,0 @@
|
|
|
1
|
-
// src/__tests__/ui-request-queue.test.ts
|
|
2
|
-
//
|
|
3
|
-
// W3 测试:UI 请求队列机制(L1 per-child createUiRequestQueue)
|
|
4
|
-
// 1. 多个请求按 FIFO 顺序处理
|
|
5
|
-
// 2. 第一个请求未完成时第二个不开始
|
|
6
|
-
//
|
|
7
|
-
// 直接测试 createUiRequestQueue 的队列逻辑(纯函数,不需要 mock runSpawn)。
|
|
8
|
-
// 用 fake child(PassThrough stdin)+ 手动控制 Promise resolve 时序。
|
|
9
|
-
//
|
|
10
|
-
// 协议迁移(W2):enqueue 第二参从旧 Record params(含 questions/context)改为
|
|
11
|
-
// ExtensionUiRequest(method 平铺);handler 签名从 (questions,context) 改为
|
|
12
|
-
// UiRequestHandler (req: UiRequest) => Promise<UiResponse>。
|
|
13
|
-
|
|
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
|
-
import {
|
|
20
|
-
createUiRequestQueue,
|
|
21
|
-
type UiRequest,
|
|
22
|
-
type UiRequestHandler,
|
|
23
|
-
type UiResponse,
|
|
24
|
-
} from "../ui-request-queue.ts";
|
|
25
|
-
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.useFakeTimers();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
vi.useRealTimers();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
function makeFakeChild(): ChildProcess {
|
|
35
|
-
const stdin = new PassThrough();
|
|
36
|
-
return {
|
|
37
|
-
stdin,
|
|
38
|
-
killed: false,
|
|
39
|
-
pid: 10001,
|
|
40
|
-
on: vi.fn(),
|
|
41
|
-
removeListener: vi.fn(),
|
|
42
|
-
} as unknown as ChildProcess;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 构造 select method 的 ExtensionUiRequest(ask_user 借道 select dialog 通道)。 */
|
|
46
|
-
function makeSelectReq(question: string): { method: "select"; title: string; options: string[] } {
|
|
47
|
-
return {
|
|
48
|
-
method: "select",
|
|
49
|
-
title: question,
|
|
50
|
-
options: [JSON.stringify({ question, options: [{ label: "A" }] })],
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
describe("UI 请求队列", () => {
|
|
55
|
-
it("多个 extension_ui_request 按 FIFO 顺序处理", async () => {
|
|
56
|
-
const callOrder: string[] = [];
|
|
57
|
-
// 每个请求返回独立的可控 Promise
|
|
58
|
-
const resolvers: Array<(v: UiResponse) => void> = [];
|
|
59
|
-
|
|
60
|
-
const handler: UiRequestHandler = vi.fn((req: UiRequest) => {
|
|
61
|
-
callOrder.push(req.title ?? "");
|
|
62
|
-
return new Promise<UiResponse>((resolve) => {
|
|
63
|
-
resolvers.push(resolve);
|
|
64
|
-
});
|
|
65
|
-
}) as unknown as UiRequestHandler;
|
|
66
|
-
|
|
67
|
-
const child = makeFakeChild();
|
|
68
|
-
const ctx = { uiRequestHandler: handler } as Parameters<
|
|
69
|
-
typeof createUiRequestQueue
|
|
70
|
-
>[1];
|
|
71
|
-
const enqueue = createUiRequestQueue(child, ctx);
|
|
72
|
-
|
|
73
|
-
// 快速入队三个请求(handler 被调用但不 resolve)
|
|
74
|
-
enqueue("r1", makeSelectReq("Q1"));
|
|
75
|
-
enqueue("r2", makeSelectReq("Q2"));
|
|
76
|
-
enqueue("r3", makeSelectReq("Q3"));
|
|
77
|
-
|
|
78
|
-
// 第一个请求立即开始处理
|
|
79
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
80
|
-
expect(callOrder).toEqual(["Q1"]);
|
|
81
|
-
|
|
82
|
-
// resolve 第一个 → 第二个开始处理
|
|
83
|
-
resolvers[0]({ value: "a1" });
|
|
84
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
85
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
86
|
-
expect(callOrder).toEqual(["Q1", "Q2"]);
|
|
87
|
-
|
|
88
|
-
// resolve 第二个 → 第三个开始处理
|
|
89
|
-
resolvers[1]({ value: "a2" });
|
|
90
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
91
|
-
expect(handler).toHaveBeenCalledTimes(3);
|
|
92
|
-
expect(callOrder).toEqual(["Q1", "Q2", "Q3"]);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("第一个请求未 resolve 时第二个不调用 uiRequestHandler", async () => {
|
|
96
|
-
const callOrder: string[] = [];
|
|
97
|
-
let firstResolve: (v: UiResponse) => void;
|
|
98
|
-
|
|
99
|
-
const handler: UiRequestHandler = vi.fn((req: UiRequest) => {
|
|
100
|
-
callOrder.push(req.title ?? "");
|
|
101
|
-
if (req.title === "Q1") {
|
|
102
|
-
return new Promise<UiResponse>((resolve) => {
|
|
103
|
-
firstResolve = resolve;
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return Promise.resolve<UiResponse>({ value: "done" });
|
|
107
|
-
}) as unknown as UiRequestHandler;
|
|
108
|
-
|
|
109
|
-
const child = makeFakeChild();
|
|
110
|
-
const ctx = { uiRequestHandler: handler } as Parameters<
|
|
111
|
-
typeof createUiRequestQueue
|
|
112
|
-
>[1];
|
|
113
|
-
const enqueue = createUiRequestQueue(child, ctx);
|
|
114
|
-
|
|
115
|
-
enqueue("r1", makeSelectReq("Q1"));
|
|
116
|
-
enqueue("r2", makeSelectReq("Q2"));
|
|
117
|
-
|
|
118
|
-
// 只有 Q1 被调用,Q2 还在队列里
|
|
119
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
120
|
-
expect(callOrder).toEqual(["Q1"]);
|
|
121
|
-
|
|
122
|
-
// 等一下,Q2 仍然不应该被调用
|
|
123
|
-
await vi.advanceTimersByTimeAsync(100);
|
|
124
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
125
|
-
expect(callOrder).toEqual(["Q1"]);
|
|
126
|
-
|
|
127
|
-
// resolve Q1 → Q2 才开始
|
|
128
|
-
firstResolve!({ value: "a1" });
|
|
129
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
130
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
131
|
-
expect(callOrder).toEqual(["Q1", "Q2"]);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ── LC-3/T2⑦ 验收⑤:timeout 字段透传链路(L1 队列 → handler 段)──
|
|
136
|
-
// 下游段(handler → dialogQueue 消费)由 dialog-queue.test.ts / ui-request-handler-factory.test.ts 锁死。
|
|
137
|
-
|
|
138
|
-
describe("UI 请求队列 — timeout 字段透传(LC-3/T2⑦)", () => {
|
|
139
|
-
it("ExtensionUiRequest.timeout 经 handleUiRequest 到达 uiRequestHandler 收到的 UiRequest", async () => {
|
|
140
|
-
const received: UiRequest[] = [];
|
|
141
|
-
const handler: UiRequestHandler = (req: UiRequest) => {
|
|
142
|
-
received.push(req);
|
|
143
|
-
return Promise.resolve<UiResponse>({ value: "ok" });
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const child = makeFakeChild();
|
|
147
|
-
const ctx = { uiRequestHandler: handler } as Parameters<
|
|
148
|
-
typeof createUiRequestQueue
|
|
149
|
-
>[1];
|
|
150
|
-
const enqueue = createUiRequestQueue(child, ctx);
|
|
151
|
-
|
|
152
|
-
enqueue("r1", { ...makeSelectReq("Q1"), timeout: 5000 });
|
|
153
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
154
|
-
|
|
155
|
-
expect(received).toHaveLength(1);
|
|
156
|
-
expect(received[0].timeout).toBe(5000);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// ── extractMethodFields 字段复制链路(经 createUiRequestQueue → handleUiRequest 锁定)──
|
|
161
|
-
// 13 个 method-specific 字段的复制语义:typed 守卫字段(string/number/array)验型通过才复制、
|
|
162
|
-
// presence-only 字段(statusText/widgetLines/widgetPlacement)仅 in 检查直赋、缺失字段不复制。
|
|
163
|
-
// 协议形状来自 JSON 反序列化,坏类型运行时可达——用例以 as unknown as 构造越界形状做防御锁定。
|
|
164
|
-
|
|
165
|
-
describe("UI 请求队列 — method-specific 字段复制(extractMethodFields 链路)", () => {
|
|
166
|
-
function makeQueue(handler: UiRequestHandler): { enqueue: ReturnType<typeof createUiRequestQueue> } {
|
|
167
|
-
const child = makeFakeChild();
|
|
168
|
-
const ctx = { uiRequestHandler: handler } as Parameters<
|
|
169
|
-
typeof createUiRequestQueue
|
|
170
|
-
>[1];
|
|
171
|
-
return { enqueue: createUiRequestQueue(child, ctx) };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
it("全字段请求:typed 守卫字段与 presence-only 字段全部复制到 UiRequest", async () => {
|
|
175
|
-
const received: UiRequest[] = [];
|
|
176
|
-
const { enqueue } = makeQueue((req) => {
|
|
177
|
-
received.push(req);
|
|
178
|
-
return Promise.resolve<UiResponse>({ ack: true });
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// 13 字段全量形状(协议 union 无单变体携带全部字段,运行时平铺可达)
|
|
182
|
-
const req = {
|
|
183
|
-
method: "select",
|
|
184
|
-
title: "t",
|
|
185
|
-
options: ["a", "b"],
|
|
186
|
-
message: "m",
|
|
187
|
-
placeholder: "p",
|
|
188
|
-
prefill: "pf",
|
|
189
|
-
notifyType: "warning",
|
|
190
|
-
statusKey: "sk",
|
|
191
|
-
statusText: "st",
|
|
192
|
-
widgetKey: "wk",
|
|
193
|
-
widgetLines: ["l1"],
|
|
194
|
-
widgetPlacement: "belowEditor",
|
|
195
|
-
text: "tx",
|
|
196
|
-
timeout: 2500,
|
|
197
|
-
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
198
|
-
|
|
199
|
-
enqueue("r1", req);
|
|
200
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
201
|
-
|
|
202
|
-
expect(received).toHaveLength(1);
|
|
203
|
-
expect(received[0]).toMatchObject({
|
|
204
|
-
title: "t",
|
|
205
|
-
options: ["a", "b"],
|
|
206
|
-
message: "m",
|
|
207
|
-
placeholder: "p",
|
|
208
|
-
prefill: "pf",
|
|
209
|
-
notifyType: "warning",
|
|
210
|
-
statusKey: "sk",
|
|
211
|
-
statusText: "st",
|
|
212
|
-
widgetKey: "wk",
|
|
213
|
-
widgetLines: ["l1"],
|
|
214
|
-
widgetPlacement: "belowEditor",
|
|
215
|
-
text: "tx",
|
|
216
|
-
timeout: 2500,
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("typed 守卫字段类型不符时跳过复制,其余字段照常", async () => {
|
|
221
|
-
const received: UiRequest[] = [];
|
|
222
|
-
const { enqueue } = makeQueue((req) => {
|
|
223
|
-
received.push(req);
|
|
224
|
-
return Promise.resolve<UiResponse>({ ack: true });
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const req = {
|
|
228
|
-
method: "select",
|
|
229
|
-
title: "kept-title", // 合法(title 非法值会在链路上游 startsWith 处炸掉整条请求,见下一条用例)
|
|
230
|
-
options: "not-array", // string ≠ array → 跳过
|
|
231
|
-
timeout: "5000", // string ≠ number → 跳过
|
|
232
|
-
message: "kept",
|
|
233
|
-
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
234
|
-
|
|
235
|
-
enqueue("r1", req);
|
|
236
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
237
|
-
|
|
238
|
-
expect(received).toHaveLength(1);
|
|
239
|
-
expect(received[0].title).toBe("kept-title");
|
|
240
|
-
expect("options" in received[0]).toBe(false);
|
|
241
|
-
expect("timeout" in received[0]).toBe(false);
|
|
242
|
-
expect(received[0].message).toBe("kept");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("typed 非法 title(number)在上游 startsWith 处炸链路 → handler 不被调、单请求失败被吞不阻塞队列", async () => {
|
|
246
|
-
// HEAD 真实行为锚定(探针实证):title: 42 在 extractMethodFields 之前的环节
|
|
247
|
-
// 触发 str.startsWith is not a function,异常被 handleUiRequest 的 .catch 吞掉
|
|
248
|
-
//(注释声明的「单个请求失败不阻塞后续」语义)——守卫的「跳过该字段」对 title 不可达。
|
|
249
|
-
const received: UiRequest[] = [];
|
|
250
|
-
const { enqueue } = makeQueue((req) => {
|
|
251
|
-
received.push(req);
|
|
252
|
-
return Promise.resolve<UiResponse>({ ack: true });
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const badReq = {
|
|
256
|
-
method: "select",
|
|
257
|
-
title: 42,
|
|
258
|
-
message: "kept",
|
|
259
|
-
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
260
|
-
enqueue("r1", badReq);
|
|
261
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
262
|
-
expect(received).toHaveLength(0); // handler 未被调(链路在上游失败)
|
|
263
|
-
|
|
264
|
-
// 队列不阻塞:后续合法请求照常处理
|
|
265
|
-
enqueue("r2", {
|
|
266
|
-
method: "select",
|
|
267
|
-
title: "Q2",
|
|
268
|
-
options: [JSON.stringify({ question: "Q2", options: [{ label: "A" }] })],
|
|
269
|
-
});
|
|
270
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
271
|
-
expect(received).toHaveLength(1);
|
|
272
|
-
expect(received[0].title).toBe("Q2");
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it("presence-only 字段仅 in 检查直赋;未声明的字段不复制(保持 UiRequest 可选)", async () => {
|
|
276
|
-
const received: UiRequest[] = [];
|
|
277
|
-
const { enqueue } = makeQueue((req) => {
|
|
278
|
-
received.push(req);
|
|
279
|
-
return Promise.resolve<UiResponse>({ ack: true });
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// statusText 存在但值为 undefined(statusText: string | undefined 变体合法形态)→ 仍复制
|
|
283
|
-
const req = {
|
|
284
|
-
method: "setStatus",
|
|
285
|
-
statusKey: "k",
|
|
286
|
-
statusText: undefined,
|
|
287
|
-
unknownField: "should-not-copy",
|
|
288
|
-
} as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
|
|
289
|
-
|
|
290
|
-
enqueue("r1", req);
|
|
291
|
-
await vi.advanceTimersByTimeAsync(0);
|
|
292
|
-
|
|
293
|
-
expect(received).toHaveLength(1);
|
|
294
|
-
expect("statusText" in received[0]).toBe(true);
|
|
295
|
-
expect(received[0].statusText).toBeUndefined();
|
|
296
|
-
expect("unknownField" in received[0]).toBe(false);
|
|
297
|
-
});
|
|
298
|
-
});
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// src/execution/__tests__/worktree-pid-registration.integration.test.ts
|
|
2
|
-
//
|
|
3
|
-
// [worktree-reaper-fix] 端到端集成测试:验证 worktree pid 注册链路(接线层)。
|
|
4
|
-
//
|
|
5
|
-
// 背景:2026-08-11 生产事故——reaper 误清活 worktree。根因:pid 补全代码唯一生产调用点
|
|
6
|
-
// 挂在 session-runner 的 header 分支(RPC mode 永不触发),注册表 pid 恒为 0,超
|
|
7
|
-
// SPAWN_GRACE_MS(60s) 后任意 session_start 触发的 scan() 必然误删活 worktree。
|
|
8
|
-
// 修复:spawn() 返回后同步补 pid。
|
|
9
|
-
//
|
|
10
|
-
// 为什么用真实 spawn(而非现有 run-spawn-* 的 FakeChild mock):
|
|
11
|
-
// 现有测试全 mock registerPid(session-start-reaper/crash-recovery/index-session-start/
|
|
12
|
-
// stream-sink-guard),验证的是「mock 了补全回调后的 reaper 行为」,从未验证
|
|
13
|
-
// 「真实调用链中补全回调是否被调用」——接线错误零检测能力(结构性盲区)。
|
|
14
|
-
// 本测试走真实链路:真实 git repo + 真实 worktree 创建 + 真实 spawn node 子进程 +
|
|
15
|
-
// 真实注册表文件,仅 mock ./pi-invocation.ts(把 pi 二进制替换为 node -e 脚本)。
|
|
16
|
-
//
|
|
17
|
-
// mock 最小化原则:
|
|
18
|
-
// - node:child_process 不 mock(真实 spawn / spawnSync git)
|
|
19
|
-
// - node:fs 不 mock(真实目录/文件:worktree checkout、注册表 JSON)
|
|
20
|
-
// - alive-store 不 mock(真实 process.kill(pid, 0) 探活)
|
|
21
|
-
// - 仅 vi.mock("./pi-invocation.ts"):getPiInvocation 返回 node -e 脚本
|
|
22
|
-
// - fake timers 仅 toFake: ["Date"]:推进注册表宽限判定用,不干扰真实 I/O 事件
|
|
23
|
-
|
|
24
|
-
import { spawnSync } from "node:child_process";
|
|
25
|
-
import * as fs from "node:fs";
|
|
26
|
-
import * as os from "node:os";
|
|
27
|
-
import * as path from "node:path";
|
|
28
|
-
|
|
29
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
30
|
-
|
|
31
|
-
// vi.hoisted:vi.mock 工厂体内不能引用顶层 let/const(提升限制),脚本字符串必须放这。
|
|
32
|
-
// scriptHolder 是可变对象:getPiInvocation 每次调用时读它(工厂函数体在运行时执行),
|
|
33
|
-
// 用例内可切换长驻/短命脚本。
|
|
34
|
-
const { scriptHolder, LONG_RUNNING_SCRIPT, SHORT_LIVED_SCRIPT } = vi.hoisted(() => {
|
|
35
|
-
const scriptHolder: { script: string } = { script: "process.exit(0)" };
|
|
36
|
-
return {
|
|
37
|
-
scriptHolder,
|
|
38
|
-
// 长驻脚本:90s 后退出(测试在 61s scan 时它必须还活着,验证「活 worktree 不被清」)
|
|
39
|
-
LONG_RUNNING_SCRIPT:
|
|
40
|
-
"setTimeout(() => process.exit(0), 90000);",
|
|
41
|
-
// 短命脚本:立即退出(模拟快速完成的子 agent,验证「真孤儿被回收」)
|
|
42
|
-
SHORT_LIVED_SCRIPT: "process.exit(0)",
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
vi.mock("./pi-invocation.ts", () => ({
|
|
47
|
-
getPiInvocation: (userArgs: string[]) => ({
|
|
48
|
-
command: process.execPath,
|
|
49
|
-
args: ["-e", scriptHolder.script, ...userArgs],
|
|
50
|
-
}),
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
import { WorktreeManager } from "../worktree-manager.ts";
|
|
54
|
-
import { WorktreeRegistry, SPAWN_GRACE_MS } from "../worktree-registry.ts";
|
|
55
|
-
import { runSpawn } from "../engine/engines/pi/session-runner.ts";
|
|
56
|
-
import type { WorktreeHandle } from "../types.ts";
|
|
57
|
-
import { makeCtx, makeOpts, makeRecord } from "./helpers/spawn-mock.ts";
|
|
58
|
-
|
|
59
|
-
// ── 测试夹具:临时 git repo + 临时 agentDir(避免污染 ~/.pi/agent)──
|
|
60
|
-
|
|
61
|
-
let tmpRoot: string;
|
|
62
|
-
let repoDir: string;
|
|
63
|
-
let agentDir: string;
|
|
64
|
-
let wtm: WorktreeManager;
|
|
65
|
-
let registry: WorktreeRegistry;
|
|
66
|
-
let handle: WorktreeHandle | undefined;
|
|
67
|
-
let spawnedPid: number | undefined;
|
|
68
|
-
|
|
69
|
-
function git(args: string[], cwd: string): string {
|
|
70
|
-
const r = spawnSync("git", args, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
71
|
-
return (r.stdout ?? "").trim();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** 初始化临时 git repo(至少一个 commit,worktreeManager.create 需要 clean tree + HEAD)。 */
|
|
75
|
-
function initRepo(): void {
|
|
76
|
-
repoDir = path.join(tmpRoot, "repo");
|
|
77
|
-
fs.mkdirSync(repoDir, { recursive: true });
|
|
78
|
-
git(["init", "-b", "main"], repoDir);
|
|
79
|
-
git(["config", "user.email", "test@test.local"], repoDir);
|
|
80
|
-
git(["config", "user.name", "test"], repoDir);
|
|
81
|
-
git(["commit", "--allow-empty", "-m", "init"], repoDir);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** 从注册表文件读指定 branch 的条目(真实文件,轮询用)。 */
|
|
85
|
-
function readEntry(branch: string): { pid: number; createdAt: number } | undefined {
|
|
86
|
-
return registry.load().find((e) => e.branch === branch);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** 轮询注册表直到 pid 补全(真实 fs 读 + 真实 setTimeout 轮询)。 */
|
|
90
|
-
async function waitForPid(branch: string, timeoutMs = 5000): Promise<number> {
|
|
91
|
-
const start = Date.now();
|
|
92
|
-
while (Date.now() - start < timeoutMs) {
|
|
93
|
-
const entry = readEntry(branch);
|
|
94
|
-
if (entry && entry.pid !== 0) return entry.pid;
|
|
95
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
96
|
-
}
|
|
97
|
-
throw new Error(`pid not registered within ${timeoutMs}ms (branch=${branch})`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** 清理:kill 子进程 + worktree cleanup + 删除临时目录。 */
|
|
101
|
-
async function cleanup(): Promise<void> {
|
|
102
|
-
if (spawnedPid) {
|
|
103
|
-
try {
|
|
104
|
-
process.kill(spawnedPid, "SIGKILL");
|
|
105
|
-
} catch {
|
|
106
|
-
// 已退出
|
|
107
|
-
}
|
|
108
|
-
spawnedPid = undefined;
|
|
109
|
-
}
|
|
110
|
-
if (handle) {
|
|
111
|
-
try {
|
|
112
|
-
await wtm.cleanup(handle);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
// best-effort:git worktree remove 失败不阻断测试清理
|
|
115
|
-
// eslint-disable-next-line no-console -- 测试 teardown 兜底留痕(清理失败不阻断后续 rmSync),非产品日志面
|
|
116
|
-
console.warn("worktree cleanup failed in test teardown", err);
|
|
117
|
-
}
|
|
118
|
-
handle = undefined;
|
|
119
|
-
}
|
|
120
|
-
try {
|
|
121
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
122
|
-
} catch {
|
|
123
|
-
// best-effort
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
beforeEach(() => {
|
|
128
|
-
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "wt-reaper-it-"));
|
|
129
|
-
agentDir = path.join(tmpRoot, "agent");
|
|
130
|
-
initRepo();
|
|
131
|
-
wtm = new WorktreeManager(agentDir);
|
|
132
|
-
registry = new WorktreeRegistry(agentDir);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
afterEach(async () => {
|
|
136
|
-
vi.useRealTimers();
|
|
137
|
-
await cleanup();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// ── 用例 ──
|
|
141
|
-
|
|
142
|
-
// [HISTORICAL] 2026-08-20 PR #185:真实 spawn node 子进程 + 真实 git worktree + 注册表
|
|
143
|
-
// 文件锁集成用例显式超时——整包满并行 + 系统余载下真实子进程链路超 vitest 默认 5s
|
|
144
|
-
// testTimeout(对齐本包 worktree-registry D5a / process-shutdown-hook 口径)。
|
|
145
|
-
describe("worktree pid 注册链路(真实 spawn 集成)", { timeout: 30_000 }, () => {
|
|
146
|
-
it("正向:spawn 返回后注册表 pid 同步补全,活 worktree 超宽限不被 scan 误清", async () => {
|
|
147
|
-
// 0. 长驻脚本(子进程 90s 内不退出,模拟长跑子 agent)
|
|
148
|
-
scriptHolder.script = LONG_RUNNING_SCRIPT; // 1. 真实创建 worktree(pid=0 占位)
|
|
149
|
-
handle = await wtm.create(repoDir, "rec-1");
|
|
150
|
-
expect(readEntry(handle.branch)).toMatchObject({ pid: 0 });
|
|
151
|
-
|
|
152
|
-
// 2. runSpawn 挂后台(不 await——长驻子进程 close 不触发,await 会挂死),
|
|
153
|
-
// ctx.onWorktreePid 接真实 registerPid(模拟 subagent-service 接线)
|
|
154
|
-
const ctx = makeCtx({
|
|
155
|
-
agentDir,
|
|
156
|
-
cwd: repoDir,
|
|
157
|
-
mainCwd: repoDir,
|
|
158
|
-
rootCwd: repoDir,
|
|
159
|
-
onWorktreePid: (branch: string, pid: number) => wtm.registerPid(branch, pid),
|
|
160
|
-
});
|
|
161
|
-
const runPromise = runSpawn(
|
|
162
|
-
makeRecord(),
|
|
163
|
-
"test task",
|
|
164
|
-
makeOpts({ worktree: handle }),
|
|
165
|
-
ctx,
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// 3. 断言 spawn 后 pid 已补全(真实注册表文件轮询)——修复前此步超时红
|
|
169
|
-
spawnedPid = await waitForPid(handle.branch);
|
|
170
|
-
expect(spawnedPid).toBeGreaterThan(0);
|
|
171
|
-
|
|
172
|
-
// 4. 推进时钟超 SPAWN_GRACE_MS(仅 fake Date,不干扰真实 I/O)
|
|
173
|
-
vi.useFakeTimers({ toFake: ["Date"] });
|
|
174
|
-
vi.setSystemTime(Date.now() + SPAWN_GRACE_MS + 1000);
|
|
175
|
-
|
|
176
|
-
// 5. scan():活 worktree 必须不被清(修复前:pid=0 超宽限 → 误删 → 红)
|
|
177
|
-
await wtm.scan();
|
|
178
|
-
expect(fs.existsSync(handle.path)).toBe(true);
|
|
179
|
-
|
|
180
|
-
// 6. 收尾:真实时钟恢复 + kill 子进程让 runPromise settle
|
|
181
|
-
vi.useRealTimers();
|
|
182
|
-
try {
|
|
183
|
-
process.kill(spawnedPid, "SIGTERM");
|
|
184
|
-
} catch {
|
|
185
|
-
// 已退出
|
|
186
|
-
}
|
|
187
|
-
await runPromise;
|
|
188
|
-
|
|
189
|
-
// 7. 反向:进程死后 scan 回收真孤儿
|
|
190
|
-
await wtm.scan();
|
|
191
|
-
expect(fs.existsSync(handle.path)).toBe(false);
|
|
192
|
-
const entryAfter = readEntry(handle.branch);
|
|
193
|
-
expect(entryAfter).toBeUndefined();
|
|
194
|
-
}, 15000);
|
|
195
|
-
|
|
196
|
-
it("反向:短命子进程退出后,pid>0 且进程死 → scan 立即回收", async () => {
|
|
197
|
-
// 0. 短命脚本(子进程立即退出,模拟快速完成的子 agent)
|
|
198
|
-
scriptHolder.script = SHORT_LIVED_SCRIPT;
|
|
199
|
-
// 1. 真实创建 worktree
|
|
200
|
-
handle = await wtm.create(repoDir, "rec-2");
|
|
201
|
-
|
|
202
|
-
// 2. 短命脚本子进程:spawn 后同步补 pid(修复前 pid=0,且未超宽限 → 不回收 → 红)
|
|
203
|
-
const ctx = makeCtx({
|
|
204
|
-
agentDir,
|
|
205
|
-
cwd: repoDir,
|
|
206
|
-
mainCwd: repoDir,
|
|
207
|
-
rootCwd: repoDir,
|
|
208
|
-
onWorktreePid: (branch: string, pid: number) => wtm.registerPid(branch, pid),
|
|
209
|
-
});
|
|
210
|
-
const result = await runSpawn(
|
|
211
|
-
makeRecord(),
|
|
212
|
-
"test task",
|
|
213
|
-
makeOpts({ worktree: handle }),
|
|
214
|
-
ctx,
|
|
215
|
-
);
|
|
216
|
-
// 历史「status !== error」弱断言:AgentResult 无 status 字段,经视图访问保持断言语义不变
|
|
217
|
-
expect((result as { status?: string }).status).not.toBe("error"); // 进程正常退出(exit 0),非 spawn 失败
|
|
218
|
-
|
|
219
|
-
// 3. pid 已补全(短命进程退出后 pid 仍有效,registerPid 同步执行不受退出影响)
|
|
220
|
-
const entry = readEntry(handle.branch);
|
|
221
|
-
expect(entry).toBeDefined();
|
|
222
|
-
expect(entry!.pid).toBeGreaterThan(0);
|
|
223
|
-
spawnedPid = entry!.pid;
|
|
224
|
-
|
|
225
|
-
// 4. scan:pid>0 且进程死 → 立即判孤儿回收(无需等宽限)
|
|
226
|
-
await wtm.scan();
|
|
227
|
-
expect(fs.existsSync(handle.path)).toBe(false);
|
|
228
|
-
expect(readEntry(handle.branch)).toBeUndefined();
|
|
229
|
-
}, 15000);
|
|
230
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// schema-emulation.test.ts —— 三级容错提取逐级用例 + ajv 校验 + tail 载体。
|
|
2
|
-
//
|
|
3
|
-
// 三视角:①构建者——三级容错每级有独立触达用例(直接 parse / fence / 括号扫描);
|
|
4
|
-
// ②使用者——ok:false 时 error 简述 + tail 足以构造重试 prompt;③观察者——prompt
|
|
5
|
-
// 注入段含 schema 声明与输出约定(emulated 引擎拼进 prompt 后模型可遵循)。
|
|
6
|
-
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
buildSchemaEmulationSegment,
|
|
11
|
-
extractAndValidateStructuredOutput,
|
|
12
|
-
SCHEMA_EMULATION_TAIL_CHARS,
|
|
13
|
-
} from "../../common/schema-emulation.ts";
|
|
14
|
-
|
|
15
|
-
const objectSchema = {
|
|
16
|
-
type: "object",
|
|
17
|
-
properties: { verdict: { type: "string" }, issues: { type: "array", items: { type: "string" } } },
|
|
18
|
-
required: ["verdict"],
|
|
19
|
-
} as const;
|
|
20
|
-
|
|
21
|
-
describe("buildSchemaEmulationSegment", () => {
|
|
22
|
-
it("注入段含 schema JSON 声明 + 输出格式约定(用户可见 prompt 断言)", () => {
|
|
23
|
-
const segment = buildSchemaEmulationSegment(objectSchema);
|
|
24
|
-
expect(segment).toContain("## Structured Output Requirement");
|
|
25
|
-
expect(segment).toContain(JSON.stringify(objectSchema));
|
|
26
|
-
expect(segment).toMatch(/```json/);
|
|
27
|
-
expect(segment).toMatch(/ONLY the JSON value/);
|
|
28
|
-
expect(segment).toMatch(/JSON Schema \(draft-07\)/);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("extractAndValidateStructuredOutput 三级容错", () => {
|
|
33
|
-
it("第 1 级:输出整体即合法 JSON → 直接 parse 通过 + ajv 通过", () => {
|
|
34
|
-
const text = JSON.stringify({ verdict: "pass", issues: [] });
|
|
35
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
36
|
-
expect(result).toEqual({ ok: true, parsed: { verdict: "pass", issues: [] } });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("第 2 级:markdown code fence 包裹 → 剥 fence 后 parse 通过", () => {
|
|
40
|
-
const text = "Here is my answer:\n```json\n{\"verdict\": \"fail\", \"issues\": [\"a\", \"b\"]}\n```\n";
|
|
41
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
42
|
-
expect(result).toEqual({ ok: true, parsed: { verdict: "fail", issues: ["a", "b"] } });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("第 2 级:无语言标注的裸 fence 也能剥", () => {
|
|
46
|
-
const text = "```\n{\"verdict\": \"pass\"}\n```";
|
|
47
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
48
|
-
expect(result).toEqual({ ok: true, parsed: { verdict: "pass" } });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("第 3 级:前后杂文本包裹 → 首尾括号扫描提取", () => {
|
|
52
|
-
const text = "Sure! The review result is:\n\n{\"verdict\": \"fail\", \"issues\": [\"x\"]}\n\nHope this helps.";
|
|
53
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
54
|
-
expect(result).toEqual({ ok: true, parsed: { verdict: "fail", issues: ["x"] } });
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("第 3 级:array 根也支持(首个 '[' 到末个 ']')", () => {
|
|
58
|
-
const arraySchema = { type: "array", items: { type: "string" } };
|
|
59
|
-
const result = extractAndValidateStructuredOutput("prefix [\"a\",\"b\"] suffix", arraySchema);
|
|
60
|
-
expect(result).toEqual({ ok: true, parsed: ["a", "b"] });
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("提取成功但 ajv 校验失败 → ok:false 含 ajv 错误明细与原始尾部", () => {
|
|
64
|
-
// verdict 缺失(required)+ issues 类型错
|
|
65
|
-
const text = '{"issues": "not-an-array"}';
|
|
66
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
67
|
-
expect(result.ok).toBe(false);
|
|
68
|
-
if (!result.ok) {
|
|
69
|
-
expect(result.error).toContain("Schema validation failed");
|
|
70
|
-
expect(result.error).toContain("verdict");
|
|
71
|
-
expect(result.tail).toBe(text);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("三级提取全失败(无任何 JSON)→ ok:false 且 error 指明三级路径", () => {
|
|
76
|
-
const result = extractAndValidateStructuredOutput("no json here at all", objectSchema);
|
|
77
|
-
expect(result.ok).toBe(false);
|
|
78
|
-
if (!result.ok) {
|
|
79
|
-
expect(result.error).toContain("3-stage");
|
|
80
|
-
expect(result.error).toContain("code-fence");
|
|
81
|
-
expect(result.error).toContain("bracket scan");
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("提取成功但 JSON 语法损坏(括号扫描后仍 parse 失败)→ ok:false", () => {
|
|
86
|
-
const result = extractAndValidateStructuredOutput("result: {verdict: pass}", objectSchema);
|
|
87
|
-
expect(result.ok).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("ajv 编译失败(非法 schema)→ ok:false 指明 host 侧编译失败", () => {
|
|
91
|
-
// type: "foo" 是 ajv 编译期拒绝的非法类型值
|
|
92
|
-
const result = extractAndValidateStructuredOutput('{"a":1}', { type: "foo" });
|
|
93
|
-
expect(result.ok).toBe(false);
|
|
94
|
-
if (!result.ok) {
|
|
95
|
-
expect(result.error).toContain("compilation failed");
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe("tail 载体(错误展示)", () => {
|
|
101
|
-
it(`tail 截断到原始输出尾部 ${SCHEMA_EMULATION_TAIL_CHARS} 字`, () => {
|
|
102
|
-
const head = "H".repeat(300);
|
|
103
|
-
const tailPart = "T".repeat(400);
|
|
104
|
-
const text = `${head}${tailPart}`; // 700 字,尾部 500 = 末段(100H + 400T)
|
|
105
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
106
|
-
expect(result.ok).toBe(false);
|
|
107
|
-
if (!result.ok) {
|
|
108
|
-
expect(result.tail.length).toBe(SCHEMA_EMULATION_TAIL_CHARS);
|
|
109
|
-
expect(result.tail).toBe("H".repeat(100) + "T".repeat(400));
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("短输出 tail 为原文(不截断)", () => {
|
|
114
|
-
const text = "short broken output";
|
|
115
|
-
const result = extractAndValidateStructuredOutput(text, objectSchema);
|
|
116
|
-
expect(result.ok).toBe(false);
|
|
117
|
-
if (!result.ok) {
|
|
118
|
-
expect(result.tail).toBe(text);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe("D4 硬分流守护(静态断言)", () => {
|
|
124
|
-
it("本模块的 ajv 只在 emulated 路径——native 引擎(pi-env 链路)不 import 本模块(由全仓 grep 守护,见任务报告)", () => {
|
|
125
|
-
// 占位用例:硬分流是 import 约束,运行时无对应断言点;保留用例记录守护方式
|
|
126
|
-
expect(typeof extractAndValidateStructuredOutput).toBe("function");
|
|
127
|
-
});
|
|
128
|
-
});
|