@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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// registry-fork-filter.test.ts —— [W6] 翻档 × fork 继承残留的读侧过滤契约 +
|
|
2
|
+
// bash 跨 session 可见性显式断言(R4 一刀语义钉住)+ 偏差 #4 探针(goal 守卫口径)。
|
|
3
|
+
//
|
|
4
|
+
// 设计权威源:chat-domain-v1x-liveness-governance.md §3.2 D4(翻档三连带——读侧
|
|
5
|
+
// 过滤三口 + PENDING_LIFECYCLE 翻 process 档)+ 修订记录 v5-⑤(bash 跨 session
|
|
6
|
+
// 可见性一刀钉成显式选择)+ 验收 A9② + impl-plan §5 偏差 #4(W4 交接:goal 守卫
|
|
7
|
+
// 消费点未传基准,W6 实测裁决是否需 goal 侧两行传参)。
|
|
8
|
+
//
|
|
9
|
+
// 被测函数 = `extensions/universal/pending-notifications/src/state.ts` 的导出纯函数
|
|
10
|
+
// (零依赖、不触 Pi 运行时——文件头注自证;经相对路径源码消费,subagent-core 无该
|
|
11
|
+
// workspace 依赖声明,不加 phantom dep)。分工:
|
|
12
|
+
// - 过滤函数本体 / rebuild 投影的两口径行为:本套件钉语义(conformance 视角);
|
|
13
|
+
// - 过滤①②③的**接线**(goal/subagent-workflow/pending-notifications 三消费点
|
|
14
|
+
// 透传 currentSessionId):W4 包内测试已承载(pi-pending-notifications
|
|
15
|
+
// __tests__ :523/:532、subagent-workflow pi-host.test :199-223);
|
|
16
|
+
// - 后代判定口(读侧过滤②)的差集口径与①共用同一函数——本套件①的断言即其
|
|
17
|
+
// 语义核心;接线透传由上述 W4 测试守护。
|
|
18
|
+
//
|
|
19
|
+
// 探针结论(偏差 #4,实测证据 = 「goal 守卫口径」describe):**有虚增**——fork 后
|
|
20
|
+
// 父 session 仍活跃的后台任务残留使 goal 守卫现状口径(无基准)幻 defer;确需的
|
|
21
|
+
// 两行传参改动点列于该 describe 注释,留给主会话裁决(本单元不改 goal——红线)。
|
|
22
|
+
|
|
23
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
27
|
+
|
|
28
|
+
// 相对路径源码消费(7 层上溯到仓库根):state.ts 是零依赖纯函数(不触 Pi 运行时),
|
|
29
|
+
// 无需为探针/契约测试给 subagent-core 添加 workspace 依赖声明。
|
|
30
|
+
import {
|
|
31
|
+
PENDING_LIFECYCLE,
|
|
32
|
+
PENDING_TTL_MS,
|
|
33
|
+
countActiveFromEntries,
|
|
34
|
+
createRegistry,
|
|
35
|
+
rebuildFromEntries,
|
|
36
|
+
} from "../../../../../../../extensions/universal/pending-notifications/src/state.ts";
|
|
37
|
+
|
|
38
|
+
import { runReconcileSweep } from "../../../round-supervisor/index.ts";
|
|
39
|
+
|
|
40
|
+
/** pending:register entry 的落盘形态(对齐 pending-notifications 写入侧契约)。 */
|
|
41
|
+
function registerEntry(
|
|
42
|
+
id: string,
|
|
43
|
+
type: "subagent" | "workflow" | "bash",
|
|
44
|
+
sessionId: string,
|
|
45
|
+
overrides: { expiresAt?: number } = {},
|
|
46
|
+
): { customType: string; data: Record<string, unknown> } {
|
|
47
|
+
return {
|
|
48
|
+
customType: "pending:register",
|
|
49
|
+
data: {
|
|
50
|
+
id,
|
|
51
|
+
type,
|
|
52
|
+
name: id,
|
|
53
|
+
registeredAt: 1_000,
|
|
54
|
+
sessionId,
|
|
55
|
+
// bash 真实落盘无 expiresAt 键(写入侧省略,D16);subagent/workflow 翻
|
|
56
|
+
// process 档后同理——overrides 仅用于 TTL 对照面。
|
|
57
|
+
...(overrides.expiresAt !== undefined ? { expiresAt: overrides.expiresAt } : {}),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 语义对照面的基准时刻(远超旧 1h TTL——钉「无 TTL 清理」而非「窗口内未到期」假绿)。 */
|
|
63
|
+
const NOW = 10 * PENDING_TTL_MS + 1;
|
|
64
|
+
|
|
65
|
+
let tmpDirs: string[] = [];
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
for (const dir of tmpDirs) rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
69
|
+
tmpDirs = [];
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("[W6/D4 翻档] PENDING_LIFECYCLE 三类型全 process 档(跨时长无 TTL 清理的判据锚)", () => {
|
|
73
|
+
it("subagent/workflow 翻 process 档(bash 原生 process 档)——翻档事实钉住", () => {
|
|
74
|
+
expect(PENDING_LIFECYCLE).toEqual({ subagent: "process", workflow: "process", bash: "process" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("翻档后跨时长(远超旧 1h TTL)无 TTL 清理:三类型 register 均不进 expiredToFlush", () => {
|
|
78
|
+
const registry = createRegistry();
|
|
79
|
+
const entries = [
|
|
80
|
+
registerEntry("bg-1", "subagent", "sess-root", { expiresAt: 1_000 + 1 }), // 远过去
|
|
81
|
+
registerEntry("wf-1", "workflow", "sess-root", { expiresAt: 1_000 + 1 }),
|
|
82
|
+
registerEntry("bt-1", "bash", "sess-root"), // 真实落盘形态:无 expiresAt 键
|
|
83
|
+
];
|
|
84
|
+
const result = rebuildFromEntries(registry, entries, "sess-root", NOW);
|
|
85
|
+
// 旧 session 档语义(1h TTL + 跨 session 补注销)曾把长任务/跨重启注册静默
|
|
86
|
+
// 清除 → 守卫失明(事故环 4 放大器);翻档后三类型全部续存。
|
|
87
|
+
expect(result.expiredToFlush).toEqual([]);
|
|
88
|
+
expect(result.activeIds).toEqual(["bg-1", "wf-1", "bt-1"]);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("[W6/D4 读侧过滤①] fork 继承残留 × countActiveFromEntries 基准", () => {
|
|
93
|
+
it("带基准(currentSessionId)→ 父 session 残留不入差集、本 session 注册计入", () => {
|
|
94
|
+
const entries = [
|
|
95
|
+
registerEntry("parent-bg", "subagent", "sess-parent"),
|
|
96
|
+
registerEntry("own-bg", "subagent", "sess-child"),
|
|
97
|
+
registerEntry("own-bt", "bash", "sess-child"),
|
|
98
|
+
];
|
|
99
|
+
const result = countActiveFromEntries(entries, { currentSessionId: "sess-child" });
|
|
100
|
+
expect(result.ids).toEqual(["own-bg", "own-bt"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("缺 sessionId 的旧形态条目视为本 session(宁放行不误杀——防误逐活跃计数)", () => {
|
|
104
|
+
const entries = [{ customType: "pending:register", data: { id: "legacy-bg", type: "subagent" } }];
|
|
105
|
+
expect(countActiveFromEntries(entries, { currentSessionId: "sess-child" }).ids).toEqual(["legacy-bg"]);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("[W6/R4 一刀] bash 跨 session 可见性显式断言(钉成显式选择的语义变更)", () => {
|
|
110
|
+
it("读侧过滤一刀后:子 session 的 goal 不再为父 session 的 bash 任务 defer(带基准口径)", () => {
|
|
111
|
+
// fork 继承:子 session entries 复制父 session 的 bash 后台任务注册
|
|
112
|
+
// (process 档跨 session 存活——U4 补注销对翻档类型不再中性化,残留永久留存)。
|
|
113
|
+
const childEntries = [
|
|
114
|
+
registerEntry("parent-bt", "bash", "sess-parent"),
|
|
115
|
+
registerEntry("child-bt", "bash", "sess-child"),
|
|
116
|
+
];
|
|
117
|
+
// 设计选择(R4):按当前 session 过滤——父 session 的 bash 任务不再让子
|
|
118
|
+
// session 的 goal 守卫 defer(子 session 有自己的等待域)。
|
|
119
|
+
const scoped = countActiveFromEntries(childEntries, { currentSessionId: "sess-child" });
|
|
120
|
+
expect(scoped.ids).toEqual(["child-bt"]);
|
|
121
|
+
expect(scoped.count).toBe(1); // 只数本 session 的任务
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("旧行为显式登记:无基准口径(goal 守卫现状)父 bash 残留仍入计数(= defer 驱动)", () => {
|
|
125
|
+
const childEntries = [registerEntry("parent-bt", "bash", "sess-parent")];
|
|
126
|
+
// 显式钉住「一刀的对照面」:不传基准 = 不过滤 = 子 session goal 仍为父 bash
|
|
127
|
+
// 任务 defer。这是读侧过滤一刀**改变的旧行为**——保留此断言防止有人误以为
|
|
128
|
+
// 过滤是缺省语义(偏差 #4:goal 消费点现状即此口径,见探针结论)。
|
|
129
|
+
expect(countActiveFromEntries(childEntries).count).toBe(1);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("[W6/D4 读侧过滤③] registry rebuild 投影(pending_notifications 工具读侧)", () => {
|
|
134
|
+
it("跨 session 残留不入 registry、不补 unregister entry(跳过不补注销——落盘收口归 core sweep)", () => {
|
|
135
|
+
const registry = createRegistry();
|
|
136
|
+
const entries = [
|
|
137
|
+
registerEntry("parent-bg", "subagent", "sess-parent"),
|
|
138
|
+
registerEntry("own-bg", "subagent", "sess-child"),
|
|
139
|
+
];
|
|
140
|
+
const result = rebuildFromEntries(registry, entries, "sess-child", NOW);
|
|
141
|
+
// 工具投影面(count/list)不虚报继承残留(A9②:count/list 不含继承残留)。
|
|
142
|
+
expect(result.activeIds).toEqual(["own-bg"]);
|
|
143
|
+
expect(registry.operations.has("parent-bg")).toBe(false);
|
|
144
|
+
// 「跳过不补注销」:残留 entry 留在 session 文件(读侧①③口各自兜住差集消费
|
|
145
|
+
// 方),禁止 rebuild 写侧补发——跨 session 写达域缺口由 core 对账 sweep 收口。
|
|
146
|
+
expect(result.expiredToFlush).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("[W6/偏差 #4 探针] goal 守卫口径(无基准)× fork 残留——实测结论", () => {
|
|
151
|
+
// 偏差 #4(impl-plan §5,W4 交接):W4 读侧过滤①的 currentSessionId 为可选参数,
|
|
152
|
+
// goal 守卫消费点未传基准。本探针在 fork 场景实测该口径是否被父 session 注册
|
|
153
|
+
// 残留虚增,结论二选一回写报告:
|
|
154
|
+
// (a) 无影响(其他机制覆盖)→ 偏差 #4 关闭,goal 零改动;
|
|
155
|
+
// (b) 有虚增 → 列出确需的两行传参改动点(不改 goal——红线),留主会话裁决。
|
|
156
|
+
//
|
|
157
|
+
// **实测结论:(b) 有虚增。**
|
|
158
|
+
// 证据链:goal 守卫两处消费点现状均无基准调用——
|
|
159
|
+
// `extensions/universal/goal/src/adapters/event-handlers/agent-end.ts:198`
|
|
160
|
+
// `const pendingOps = countActiveFromEntries(entries);`(defer 分支判据
|
|
161
|
+
// = `pendingOps.count > 0`)
|
|
162
|
+
// `extensions/universal/goal/src/adapters/event-handlers/agent-end.ts:294`
|
|
163
|
+
// `if (countActiveFromEntries(ctx.sessionManager.getEntries()).count > 0) return;`
|
|
164
|
+
// (backoff 延迟发射前守卫)
|
|
165
|
+
// 下方用例 1 证明该口径在 fork 残留下 count>0 → defer 命中(幻 defer);
|
|
166
|
+
// 用例 2 证明仅当父任务 record 已终态(core sweep 补注销落盘)才归零——
|
|
167
|
+
// 「父任务仍活跃」窗口内无任何机制覆盖(W4 翻档后 U4 不再中性化)。
|
|
168
|
+
//
|
|
169
|
+
// 确需的两行改动点(主会话裁决后由 goal 侧单元执行):
|
|
170
|
+
// agent-end.ts:198 → `countActiveFromEntries(entries, { currentSessionId: ctx.sessionManager.getSessionId() })`
|
|
171
|
+
// agent-end.ts:294 → `countActiveFromEntries(ctx.sessionManager.getEntries(), { currentSessionId: ctx.sessionManager.getSessionId() })`
|
|
172
|
+
// (getSessionId 是 extension 侧既有取 sessionId 形态——pending-notifications
|
|
173
|
+
// index.ts:216、subagent-workflow session-lifecycle.ts 同款先例。)
|
|
174
|
+
|
|
175
|
+
it("用例 1:父 session 活跃 subagent 残留 → goal 现状口径(无基准)count>0 → defer 分支命中(虚增)", () => {
|
|
176
|
+
// fork 时刻快照:父 session 派了后台子代理(record 仍 running-resumable),
|
|
177
|
+
// 子 session 继承其 register 残留;goal 守卫读到的 entries = 本 session 文件
|
|
178
|
+
// 全量(含继承残留)。现状口径 = 无基准调用(agent-end.ts:198 逐字形态)。
|
|
179
|
+
const childEntries = [
|
|
180
|
+
registerEntry("parent-bg", "subagent", "sess-parent"),
|
|
181
|
+
registerEntry("child-bg", "subagent", "sess-child"),
|
|
182
|
+
];
|
|
183
|
+
const pendingOps = countActiveFromEntries(childEntries); // ← goal 现状口径
|
|
184
|
+
// defer 分支判据(agent-end.ts:199 `if (pendingOps.count > 0)`)命中:
|
|
185
|
+
// 父 session 的任务让子 session 的 goal 停发 continuation 并 notify
|
|
186
|
+
// 「Goal waiting for 2 background task(s)」——计数含非本 session 任务 = 虚增。
|
|
187
|
+
expect(pendingOps.count).toBe(2);
|
|
188
|
+
expect(pendingOps.ids).toContain("parent-bg");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("用例 2:父任务 record 终态后,core sweep 补注销落盘使现状口径归零(虚增仅限活跃窗口)", () => {
|
|
192
|
+
const dir = mkdtempSync(join(tmpdir(), "w6-goal-probe-sweep-"));
|
|
193
|
+
tmpDirs.push(dir);
|
|
194
|
+
const sessionFile = join(dir, "session-child.jsonl");
|
|
195
|
+
writeFileSync(
|
|
196
|
+
sessionFile,
|
|
197
|
+
`${JSON.stringify(registerEntry("parent-bg", "subagent", "sess-parent"))}\n`,
|
|
198
|
+
"utf8",
|
|
199
|
+
);
|
|
200
|
+
// 父任务 record 已终态(closed):core 对账 sweep 差集补发——appendEntry 权威
|
|
201
|
+
// 落盘到子 session 文件(sweep 的注销对「无基准口径」也生效:差集读侧不过滤,
|
|
202
|
+
// 补发的 unregister entry 抵消 register)。
|
|
203
|
+
const result = runReconcileSweep({
|
|
204
|
+
sessionFile,
|
|
205
|
+
lookupRecordState: () => ({ terminal: true, closedReason: "completed" }),
|
|
206
|
+
appendEntry: (customType, data) => {
|
|
207
|
+
writeFileSync(sessionFile, `${JSON.stringify({ customType, data })}\n`, { flag: "a" });
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
expect(result.reconciled).toEqual(["parent-bg"]);
|
|
211
|
+
|
|
212
|
+
// 落盘后 goal 守卫(无基准口径)重读 entries:计数归零——sweep 是现状口径的
|
|
213
|
+
// 唯一收敛通道,且只覆盖「record 可判定终态」的窗口;「父任务仍活跃」窗口
|
|
214
|
+
// (用例 1)无机制覆盖 → 虚增结论 (b) 成立。
|
|
215
|
+
const entriesAfter = readFileSync(sessionFile, "utf8")
|
|
216
|
+
.trim()
|
|
217
|
+
.split("\n")
|
|
218
|
+
.map((l) => JSON.parse(l));
|
|
219
|
+
expect(countActiveFromEntries(entriesAfter).count).toBe(0);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// round-liveness-supervisor.test.ts —— [W6] 轮次活性监督器的 conformance 黑盒场景。
|
|
2
|
+
//
|
|
3
|
+
// 设计权威源:chat-domain-v1x-liveness-governance.md §3.2 D2(record 去向单一裁决
|
|
4
|
+
// 表 + 三态判定 + 通知对账 + 纳管模型)+ D5(H9 keep-alive-no-progress 承接落点)
|
|
5
|
+
// + 验收 A2/A5/A8。
|
|
6
|
+
//
|
|
7
|
+
// 与 W4 单测(execution/__tests__/round-supervisor.test.ts)的分工:W4 用替身 deps
|
|
8
|
+
// 全排列覆盖判定矩阵(白盒视角);本套件是**语义钉住层**——把设计裁决表的场景链
|
|
9
|
+
// (协议事件 → record 处置 → 监督动作)按黑盒视角钉成契约,只经公共出口
|
|
10
|
+
// (`round-supervisor/index.ts`)消费,不 import 内部符号。每条链对应一个真实验收
|
|
11
|
+
// 场景,替身 deps 是场景道具而非判定逻辑复制品:
|
|
12
|
+
//
|
|
13
|
+
// 场景一(表 3 行 1 全链,事故主链):run 在途 → 引擎进程被 SIGTERM(crash
|
|
14
|
+
// message 形态取自 engine-crash.test.ts 的协议实证)→ noteRunEnded +
|
|
15
|
+
// adoptOnProcessDeath → 合并单条通知 + 决策指引(该唤醒)→ 看门狗到期 →
|
|
16
|
+
// 该放弃(giveUp(watchdog-expired),此时可重派)。
|
|
17
|
+
// 场景二(裁决表 conversation 行):chatMode record 死亡事件 → 豁免不纳管
|
|
18
|
+
// (轮终 idle / settled-watchdog 管辖,D2 前置 1)。
|
|
19
|
+
// 场景三(表 3 行 2/3 × A5 落盘断言):boot 分区重认领(already-resumable-idle)
|
|
20
|
+
// vs conversation 豁免;in-flight 直断后的注册残留由对账 sweep **appendEntry
|
|
21
|
+
// 权威落盘**补发(A5「注销落盘可查」的构造性证据——sweep 写法钉死:不经 bus
|
|
22
|
+
// emit 作权威)。
|
|
23
|
+
// 场景四(通知对账两级启发式,R4):高置信替代 → 撤销指引改送终止通知 + 该放弃
|
|
24
|
+
// (superseded);低置信 → 指引自带豁免声明。
|
|
25
|
+
// 场景五(纳管模型:死亡事件纳管、重建不解管):crash 纳管后引擎被动重建
|
|
26
|
+
// (镜像重填)不解除纳管、不重发指引——判定只消费 record 级状态。
|
|
27
|
+
//
|
|
28
|
+
// timer 用 fake timers;监督器对 fs 零依赖(场景三的 session file 由 sweep 读侧
|
|
29
|
+
// 消费,mkdtempSync 自建自删)。
|
|
30
|
+
|
|
31
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
32
|
+
import { tmpdir } from "node:os";
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS,
|
|
38
|
+
RoundSupervisor,
|
|
39
|
+
runReconcileSweep,
|
|
40
|
+
type RoundSupervisorDeps,
|
|
41
|
+
type SupervisorCandidateRecord,
|
|
42
|
+
type SupervisorRecordView,
|
|
43
|
+
} from "../../../round-supervisor/index.ts";
|
|
44
|
+
import type { ExecutionRecord } from "../../../types.ts";
|
|
45
|
+
|
|
46
|
+
// ── 场景道具(替身 deps——黑盒观测面:通知/指引/替代/放弃四出口 + 三状态源)────
|
|
47
|
+
|
|
48
|
+
function makeDeps() {
|
|
49
|
+
const views = new Map<string, SupervisorRecordView>();
|
|
50
|
+
const candidates: SupervisorCandidateRecord[] = [];
|
|
51
|
+
const live = new Set<string>();
|
|
52
|
+
const notices: string[] = [];
|
|
53
|
+
const guidances: Array<{ record: SupervisorRecordView; exemptionDisclaimer: boolean }> = [];
|
|
54
|
+
const replaced: Array<{ record: SupervisorRecordView; replacementId: string }> = [];
|
|
55
|
+
const givenUp: Array<{ recordId: string; kind: string; detail: { replacementId?: string } }> = [];
|
|
56
|
+
const deps: RoundSupervisorDeps = {
|
|
57
|
+
now: () => Date.now(),
|
|
58
|
+
getRecordView: (id) => views.get(id),
|
|
59
|
+
listCandidateRecords: () => candidates,
|
|
60
|
+
hasLiveProcess: (recordId) => live.has(recordId),
|
|
61
|
+
sendMergedFailureNotice: (record) => notices.push(`merged:${record.id}`),
|
|
62
|
+
sendDecisionGuidance: (record, opts) =>
|
|
63
|
+
guidances.push({ record, exemptionDisclaimer: opts.exemptionDisclaimer }),
|
|
64
|
+
sendReplacedNotice: (record, replacementId) => replaced.push({ record, replacementId }),
|
|
65
|
+
giveUp: (recordId, kind, detail) => givenUp.push({ recordId, kind, detail }),
|
|
66
|
+
};
|
|
67
|
+
return Object.assign(deps, { views, candidates, live, notices, guidances, replaced, givenUp });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 后台子代理 record 替身(死亡事件纳管入口的入参形态)。 */
|
|
71
|
+
function makeRecord(overrides: Partial<ExecutionRecord> = {}): ExecutionRecord {
|
|
72
|
+
return {
|
|
73
|
+
id: "bg-1",
|
|
74
|
+
agent: "worker",
|
|
75
|
+
model: "m",
|
|
76
|
+
mode: "background",
|
|
77
|
+
task: "t",
|
|
78
|
+
slug: "fix-bug",
|
|
79
|
+
startedAt: Date.now() - 1000,
|
|
80
|
+
status: "running",
|
|
81
|
+
resumable: true,
|
|
82
|
+
rootSessionId: "sess-root",
|
|
83
|
+
turnCount: 0,
|
|
84
|
+
...overrides,
|
|
85
|
+
} as ExecutionRecord;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function viewOf(record: ExecutionRecord, overrides: Partial<SupervisorRecordView> = {}): SupervisorRecordView {
|
|
89
|
+
return {
|
|
90
|
+
id: record.id,
|
|
91
|
+
status: record.status === "closed" ? "closed" : "running",
|
|
92
|
+
resumable: record.resumable === true,
|
|
93
|
+
hasResult: record.result !== undefined,
|
|
94
|
+
chatMode: record.chatMode === true,
|
|
95
|
+
rootSessionId: record.rootSessionId,
|
|
96
|
+
agent: record.agent,
|
|
97
|
+
slug: record.slug,
|
|
98
|
+
startedAt: record.startedAt,
|
|
99
|
+
closedReason: record.closedReason,
|
|
100
|
+
...overrides,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 引擎进程信号死亡的真实错误 message 形态(engine-crash.test.ts 的协议实证——
|
|
106
|
+
* EngineClient.onEngineExit signal 分支 → engineCrashedError)。
|
|
107
|
+
*/
|
|
108
|
+
const SIGTERM_CRASH_MESSAGE =
|
|
109
|
+
"engine_crashed: engine process exited unexpectedly: signal SIGTERM. stderr tail: (captured diagnostics)";
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
vi.useFakeTimers();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
vi.useRealTimers();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("[W6/D2 表 3 行 1] 引擎死亡 → failed 如实 + 监督器接管(事故主链)", () => {
|
|
120
|
+
it("run 在途 → SIGTERM crash → 纳管:合并单条通知 + 决策指引(该唤醒)→ 看门狗到期 giveUp(该放弃)", async () => {
|
|
121
|
+
const deps = makeDeps();
|
|
122
|
+
const supervisor = new RoundSupervisor(deps);
|
|
123
|
+
const record = makeRecord();
|
|
124
|
+
deps.views.set(record.id, viewOf(record));
|
|
125
|
+
|
|
126
|
+
// 该等:run 在途(协议 run 帧受理——noteRunStarted 记账)→ 死亡事件前不干预。
|
|
127
|
+
supervisor.noteRunStarted(record.id);
|
|
128
|
+
|
|
129
|
+
// 死亡事件:run 终态 failed(engine_crashed,SIGTERM 形态)→ 驱动记账解除 +
|
|
130
|
+
// record 保持 resumable(subagent-service adoptResumableAfterEngineDeath 的
|
|
131
|
+
// 生产序列)→ 监督器纳管。
|
|
132
|
+
supervisor.noteRunEnded(record.id);
|
|
133
|
+
supervisor.adoptOnProcessDeath(record, SIGTERM_CRASH_MESSAGE);
|
|
134
|
+
|
|
135
|
+
// 表 3 行 1 通知契约:failed 如实 + 已接管契约合并为**单条**通知(无双通知
|
|
136
|
+
// 时序窗口);主 agent 是唯一决策者——监督器不自动复活,只送决策指引。
|
|
137
|
+
expect(deps.notices).toEqual([`merged:${record.id}`]);
|
|
138
|
+
expect(deps.guidances).toHaveLength(1);
|
|
139
|
+
expect(deps.guidances[0].exemptionDisclaimer).toBe(false);
|
|
140
|
+
expect(supervisor.supervisedIds()).toEqual([record.id]);
|
|
141
|
+
|
|
142
|
+
// 该唤醒后的等待有主:决策看门狗 armed(默认 2h)——到期且无决策/无收敛 →
|
|
143
|
+
// 该放弃(failed 终态化 + 注销 + 终止通知,此时可重派)。
|
|
144
|
+
await vi.advanceTimersByTimeAsync(ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS + 1);
|
|
145
|
+
expect(deps.givenUp).toEqual([{ recordId: record.id, kind: "watchdog-expired", detail: {} }]);
|
|
146
|
+
expect(supervisor.supervisedIds()).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("死亡事件重复(二次 crash 事件)幂等:不重复通知、不重复指引", () => {
|
|
150
|
+
const deps = makeDeps();
|
|
151
|
+
const supervisor = new RoundSupervisor(deps);
|
|
152
|
+
const record = makeRecord();
|
|
153
|
+
deps.views.set(record.id, viewOf(record));
|
|
154
|
+
supervisor.adoptOnProcessDeath(record, SIGTERM_CRASH_MESSAGE);
|
|
155
|
+
supervisor.adoptOnProcessDeath(record, SIGTERM_CRASH_MESSAGE);
|
|
156
|
+
expect(deps.notices).toHaveLength(1);
|
|
157
|
+
expect(deps.guidances).toHaveLength(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("[W6/D2 裁决表 conversation 行] chat 域豁免", () => {
|
|
162
|
+
it("chatMode record 的死亡事件不入监督域(轮终 idle / settled-watchdog 管辖)", () => {
|
|
163
|
+
const deps = makeDeps();
|
|
164
|
+
const supervisor = new RoundSupervisor(deps);
|
|
165
|
+
supervisor.adoptOnProcessDeath(makeRecord({ chatMode: true, resumable: true }), SIGTERM_CRASH_MESSAGE);
|
|
166
|
+
expect(deps.notices).toHaveLength(0);
|
|
167
|
+
expect(deps.guidances).toHaveLength(0);
|
|
168
|
+
expect(supervisor.supervisedIds()).toEqual([]);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("[W6/D2 表 3 行 2/3 × A5] boot 分区与 sweep 落盘衔接", () => {
|
|
173
|
+
it("already-resumable-idle(非 conversation)→ 重认领接管并送达指引", () => {
|
|
174
|
+
const deps = makeDeps();
|
|
175
|
+
const supervisor = new RoundSupervisor(deps);
|
|
176
|
+
const record = makeRecord({ id: "bg-survivor" });
|
|
177
|
+
deps.candidates.push({ id: record.id, rootSessionId: record.rootSessionId, agent: record.agent, slug: record.slug, startedAt: record.startedAt });
|
|
178
|
+
deps.views.set(record.id, viewOf(record));
|
|
179
|
+
const { readopted } = supervisor.bootPartition();
|
|
180
|
+
expect(readopted).toEqual([record.id]);
|
|
181
|
+
expect(deps.guidances).toHaveLength(1);
|
|
182
|
+
expect(supervisor.supervisedIds()).toEqual([record.id]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("in-flight 直断后的注册残留 → 对账 sweep appendEntry 权威落盘(A5「注销落盘可查」)", () => {
|
|
186
|
+
const dir = mkdtempSync(join(tmpdir(), "w6-supervisor-sweep-"));
|
|
187
|
+
try {
|
|
188
|
+
const sessionFile = join(dir, "session-child.jsonl");
|
|
189
|
+
// 子 session 文件里的 register 残留(进程死亡窗口内注销发射源已消失——
|
|
190
|
+
// notify-host getPi() 为 null 的缺位形态)。
|
|
191
|
+
writeFileSync(
|
|
192
|
+
sessionFile,
|
|
193
|
+
`${JSON.stringify({ customType: "pending:register", data: { id: "bg-inflight", type: "subagent", name: "worker", sessionId: "sess-root" } })}\n`,
|
|
194
|
+
"utf8",
|
|
195
|
+
);
|
|
196
|
+
const appended: Array<{ customType: string; data: unknown }> = [];
|
|
197
|
+
const result = runReconcileSweep({
|
|
198
|
+
sessionFile,
|
|
199
|
+
lookupRecordState: (id) =>
|
|
200
|
+
id === "bg-inflight" ? { terminal: true, closedReason: "boot-abort" } : "active",
|
|
201
|
+
appendEntry: (customType, data) => {
|
|
202
|
+
appended.push({ customType, data });
|
|
203
|
+
// 权威落盘 = 直接 appendEntry(写法钉死:不经 bus emit 作权威)——
|
|
204
|
+
// 用真实文件追加复现「落盘可查」。
|
|
205
|
+
writeFileSync(
|
|
206
|
+
sessionFile,
|
|
207
|
+
`${JSON.stringify({ customType, data })}\n`,
|
|
208
|
+
{ flag: "a" },
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
expect(result.reconciled).toEqual(["bg-inflight"]);
|
|
213
|
+
expect(appended).toHaveLength(1);
|
|
214
|
+
expect(appended[0].customType).toBe("pending:unregister");
|
|
215
|
+
// 差集消费方(goal 守卫)从持久化 entries 算差集——文件里补发的 unregister
|
|
216
|
+
// 对守卫直接生效(守卫计数归零,A5 断言「重开后守卫计数归零」)。
|
|
217
|
+
const lines = readFileSync(sessionFile, "utf8").trim().split("\n").map((l) => JSON.parse(l));
|
|
218
|
+
expect(lines.some((l) => l.customType === "pending:unregister" && l.data.id === "bg-inflight")).toBe(true);
|
|
219
|
+
} finally {
|
|
220
|
+
rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("sweep 保守性:record 仍活跃的注册跳过(不误注销活跃后台任务)", () => {
|
|
225
|
+
const dir = mkdtempSync(join(tmpdir(), "w6-supervisor-sweep-active-"));
|
|
226
|
+
try {
|
|
227
|
+
const sessionFile = join(dir, "session-child.jsonl");
|
|
228
|
+
writeFileSync(
|
|
229
|
+
sessionFile,
|
|
230
|
+
`${JSON.stringify({ customType: "pending:register", data: { id: "bg-alive", type: "subagent", name: "worker", sessionId: "sess-root" } })}\n`,
|
|
231
|
+
"utf8",
|
|
232
|
+
);
|
|
233
|
+
const result = runReconcileSweep({
|
|
234
|
+
sessionFile,
|
|
235
|
+
lookupRecordState: () => "active",
|
|
236
|
+
});
|
|
237
|
+
expect(result.reconciled).toEqual([]);
|
|
238
|
+
expect(result.skippedActive).toEqual(["bg-alive"]);
|
|
239
|
+
} finally {
|
|
240
|
+
rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("[W6/D2 通知对账(R4 两级启发式)]", () => {
|
|
246
|
+
it("高置信替代(同 root + 同 agent + 同 slug + 窗内)→ 撤销指引改送终止通知 + 该放弃(superseded)", () => {
|
|
247
|
+
const deps = makeDeps();
|
|
248
|
+
const supervisor = new RoundSupervisor(deps);
|
|
249
|
+
const record = makeRecord();
|
|
250
|
+
deps.views.set(record.id, viewOf(record));
|
|
251
|
+
// 主 agent 在指引送达前已重派同任务(唯一决策者,重派合法)。
|
|
252
|
+
deps.candidates.push({ id: "bg-2", rootSessionId: "sess-root", agent: "worker", slug: "fix-bug", startedAt: Date.now() - 1000 });
|
|
253
|
+
|
|
254
|
+
supervisor.adoptOnProcessDeath(record, SIGTERM_CRASH_MESSAGE);
|
|
255
|
+
|
|
256
|
+
expect(deps.guidances).toHaveLength(0); // 指引撤销(无双执行残径)
|
|
257
|
+
expect(deps.replaced).toHaveLength(1);
|
|
258
|
+
expect(deps.replaced[0].replacementId).toBe("bg-2");
|
|
259
|
+
expect(deps.givenUp).toEqual([{ recordId: record.id, kind: "superseded", detail: { replacementId: "bg-2" } }]);
|
|
260
|
+
expect(supervisor.supervisedIds()).toEqual([]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("低置信(仅 agent 同名)→ 不撤销指引,指引自带豁免声明(残余决策交还主 agent)", () => {
|
|
264
|
+
const deps = makeDeps();
|
|
265
|
+
const supervisor = new RoundSupervisor(deps);
|
|
266
|
+
const record = makeRecord();
|
|
267
|
+
deps.views.set(record.id, viewOf(record));
|
|
268
|
+
deps.candidates.push({ id: "bg-2", rootSessionId: "sess-root", agent: "worker", slug: "other-task", startedAt: Date.now() - 1000 });
|
|
269
|
+
|
|
270
|
+
supervisor.adoptOnProcessDeath(record, SIGTERM_CRASH_MESSAGE);
|
|
271
|
+
|
|
272
|
+
expect(deps.guidances).toHaveLength(1);
|
|
273
|
+
expect(deps.guidances[0].exemptionDisclaimer).toBe(true);
|
|
274
|
+
expect(deps.givenUp).toHaveLength(0);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("[W6/D2 纳管模型] 死亡事件纳管、重建不解管", () => {
|
|
279
|
+
it("crash 纳管后引擎被动重建(镜像重填再置死)不解除纳管、不重发指引", async () => {
|
|
280
|
+
const deps = makeDeps();
|
|
281
|
+
const supervisor = new RoundSupervisor(deps);
|
|
282
|
+
const record = makeRecord();
|
|
283
|
+
deps.views.set(record.id, viewOf(record));
|
|
284
|
+
|
|
285
|
+
supervisor.adoptOnProcessDeath(record, SIGTERM_CRASH_MESSAGE);
|
|
286
|
+
expect(deps.guidances).toHaveLength(1);
|
|
287
|
+
|
|
288
|
+
// 引擎被动重建(ensureConnected 退避重填镜像——任一会话的下个请求触发):
|
|
289
|
+
// 镜像判「该等」仅解除看门狗计时;record 级状态未变 → 纳管持续、指引不重发。
|
|
290
|
+
deps.live.add(record.id);
|
|
291
|
+
supervisor.noteRunEnded(record.id);
|
|
292
|
+
expect(supervisor.supervisedIds()).toEqual([record.id]);
|
|
293
|
+
expect(deps.guidances).toHaveLength(1);
|
|
294
|
+
|
|
295
|
+
// 重建后的进程又死(镜像再置死)——判定仍只消费 record 级状态。
|
|
296
|
+
deps.live.delete(record.id);
|
|
297
|
+
supervisor.noteRunEnded(record.id);
|
|
298
|
+
expect(supervisor.supervisedIds()).toEqual([record.id]);
|
|
299
|
+
expect(deps.guidances).toHaveLength(1);
|
|
300
|
+
expect(deps.givenUp).toHaveLength(0);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// static-spawn-guard.test.ts —— W10 静态断言「任务子进程 spawn 必经 SDK
|
|
2
|
+
// spawnEngineChild」的 CI 挂载点(vitest 内执行 scripts/check-engine-spawn-guard.mjs,
|
|
3
|
+
// 不依赖 W9 的 workflows 领地;进 CI invariants job 的落点与 W9 协调——本地经本
|
|
4
|
+
// 测试即恒绿门)。
|
|
5
|
+
//
|
|
6
|
+
// 正例:两引擎包 src 干净 + 存在 spawnEngineChild 消费点(防空转)。
|
|
7
|
+
// 负例:临时 root 内放置直接 spawn 的源文件 → 脚本必须红(守卫有牙,A12 同款
|
|
8
|
+
// 负例自证)。
|
|
9
|
+
//
|
|
10
|
+
// 与 C-proc-09(check_spawn_env_boundary.py)分工互不代偿:C-proc-09 盖 spawn 点
|
|
11
|
+
// env 出站卫生(全仓),本守卫盖引擎包 spawn 单一入口(形态 + stdin fd 不外泄)。
|
|
12
|
+
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { promisify } from "node:util";
|
|
19
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
20
|
+
|
|
21
|
+
const execFileAsync = promisify(execFile);
|
|
22
|
+
const GUARD = join(
|
|
23
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
24
|
+
"../../../../../scripts/check-engine-spawn-guard.mjs",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
let tmpRoots: string[] = [];
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
for (const root of tmpRoots) rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
31
|
+
tmpRoots = [];
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("静态断言:引擎包任务子进程必经 spawnEngineChild", () => {
|
|
35
|
+
it("正例:两引擎包 src 干净且存在 spawnEngineChild 消费点", async () => {
|
|
36
|
+
const { stdout } = await execFileAsync("node", [GUARD]);
|
|
37
|
+
expect(stdout).toContain("[engine-spawn-guard] PASS");
|
|
38
|
+
expect(stdout).toMatch(/spawnEngineChild/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("负例:直接 spawn 的源文件必须被检出(守卫有牙)", async () => {
|
|
42
|
+
const root = mkdtempSync(join(tmpdir(), "spawn-guard-neg-"));
|
|
43
|
+
tmpRoots.push(root);
|
|
44
|
+
mkdirSync(join(root, "__tests__"), { recursive: true });
|
|
45
|
+
// __tests__ 排除面(测试文件可用 child_process mock)——放一个不触发的样本
|
|
46
|
+
writeFileSync(join(root, "__tests__", "mock.test.ts"), 'import { spawn } from "node:child_process";\n');
|
|
47
|
+
// src 直陈违规:直接 spawn(未走 SDK spawnEngineChild)
|
|
48
|
+
mkdirSync(join(root, "engine"), { recursive: true });
|
|
49
|
+
writeFileSync(
|
|
50
|
+
join(root, "engine", "bypass.ts"),
|
|
51
|
+
'import { spawn } from "node:child_process";\nexport const p = spawn("ls");\n',
|
|
52
|
+
);
|
|
53
|
+
await expect(execFileAsync("node", [GUARD, "--roots", root])).rejects.toMatchObject({
|
|
54
|
+
code: 1,
|
|
55
|
+
stderr: expect.stringContaining("spawn"),
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|