@zhushanwen/subagent-core 0.6.0 → 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-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
- package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -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 +14045 -16059
- package/dist/index.d.cts +1035 -1579
- package/dist/index.d.ts +1035 -1579
- package/dist/index.js +9838 -13800
- package/dist.bundle/index.cjs +28875 -30323
- 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__/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-coordinator-service.test.ts +36 -47
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
- 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 +20 -33
- package/src/execution/__tests__/gc-timer.test.ts +7 -22
- 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__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -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__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -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 +21 -36
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/agent-registry.ts +6 -2
- 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/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- 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 +81 -54
- 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 +7 -2
- 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/finalize-record.ts +25 -0
- 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/record-store.ts +29 -2
- 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-service.ts +746 -400
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/types.ts +41 -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__/execute-agent-call.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/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 -166
- 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,426 @@
|
|
|
1
|
+
// src/execution/round-supervisor/supervisor.ts
|
|
2
|
+
//
|
|
3
|
+
// [W4] 轮次活性监督器——「等待有主」的权威层(本设计事故根因主修单元的核心)。
|
|
4
|
+
//
|
|
5
|
+
// 设计权威源:docs/design/chat-domain-v1x-liveness-governance.md §3.2 D2 三态判定表
|
|
6
|
+
// + record 去向单一裁决表 + 通知对账 + 纳管模型。
|
|
7
|
+
//
|
|
8
|
+
// 职责一句话:后台子代理被杀后,goal 守卫(消费 pending 注册表)看不到 record 级
|
|
9
|
+
// resumable 状态 → 恒放行 continuation → 64min 空转;本监督器把「该等 / 该唤醒 /
|
|
10
|
+
// 该放弃」三态判定收拢为 record 级权威,唤醒的是主 agent 的注意力(决策指引),
|
|
11
|
+
// 不是自动复活任务——主 agent 是唯一决策者(resume 或重派)。
|
|
12
|
+
//
|
|
13
|
+
// 三态判定(判据状态源 = record 级,domain.ts 谓词单源):
|
|
14
|
+
// - 该等:有在途 run / 有进程驱动 → 不干预(事件流推进性归 settled-watchdog
|
|
15
|
+
// 中段守护管辖,监督器不重复判定——消两判据并集的第三态缝隙)。
|
|
16
|
+
// - 该唤醒:resumable 未终态 且 无在途 run / 无进程驱动(run 终态 failed 即驱动
|
|
17
|
+
// 死亡证据;镜像置死仅作死亡事件触发信号——判定只消费 record 级视图,结构性
|
|
18
|
+
// 保证「重建不解管」:引擎进程被动重建重填镜像也不翻转本监督器的判定)。
|
|
19
|
+
// → 通知主 agent 决策(经 notifier steer 通道)。
|
|
20
|
+
// - 该放弃:决策看门狗到期(指引无响应且无收敛信号)→ record 终态化 failed +
|
|
21
|
+
// 注销(合法发射点枚举①:内存 record 走 finalizeRecord 路径;磁盘态 record 走
|
|
22
|
+
// 终态 entry 落盘,注销由对账 sweep ⑤ 补发)+ 终止通知(此时可重派)。
|
|
23
|
+
//
|
|
24
|
+
// record 视图(SupervisorRecordView):监督器不持有 ExecutionRecord 引用——内存
|
|
25
|
+
// record(store.getMutable)与磁盘重建 record(collectRecords 投影)统一投影为
|
|
26
|
+
// 只读视图后再判定。boot 分区/重认领面对的存量 record 只在磁盘(重启后内存
|
|
27
|
+
// records Map 为空),视图化是两形态统一判定的前提。
|
|
28
|
+
//
|
|
29
|
+
// 定时器模型:每纳管 record 至多一个决策看门狗 timer(类实例 Map 记账,挂载/
|
|
30
|
+
// 清除责任内聚——对齐 settled-watchdog / armIdleTimer 的「忘清旧窗」防护先例)。
|
|
31
|
+
// 看门狗属回收层防挂死兜底(AGENTS.md 规则 19:回收层允许默认有界 opt-out)——
|
|
32
|
+
// 默认 2h(量级依据:通知账本跨 60 分钟完成 3 次投递的事故实测,窗须覆盖
|
|
33
|
+
// 「指引重投 + 主 agent 反应」),env 可覆盖/关闭。
|
|
34
|
+
//
|
|
35
|
+
// 通知对账:送指引前查替代(classifyReplacement 两级启发式,notify-accounting.ts)。
|
|
36
|
+
|
|
37
|
+
import { getLogger } from "../../core/logger.ts";
|
|
38
|
+
import { assertSafeTimerDelay } from "../../shared/timer-delay.ts";
|
|
39
|
+
import type { ExecutionRecord } from "../types.ts";
|
|
40
|
+
import {
|
|
41
|
+
classifySupervisorDomain,
|
|
42
|
+
isAwakeWarrantedShape,
|
|
43
|
+
isBootReadoptable,
|
|
44
|
+
} from "./domain.ts";
|
|
45
|
+
import {
|
|
46
|
+
classifyReplacement,
|
|
47
|
+
} from "./notify-accounting.ts";
|
|
48
|
+
|
|
49
|
+
const logger = getLogger("subagents");
|
|
50
|
+
|
|
51
|
+
const MS_PER_HOUR = 3_600_000;
|
|
52
|
+
/** 决策看门狗默认窗的小时数(量级推演见下方常量注释)。 */
|
|
53
|
+
const WATCHDOG_DEFAULT_HOURS = 2;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 决策看门狗默认窗(2h):接管/指引送达后,主 agent 无决策且无收敛信号的最长
|
|
57
|
+
* 等待。到期 = 该放弃(record 终态化 failed + 注销 + 终止通知,此时可重派)。
|
|
58
|
+
* 量级依据见文件头注(通知账本 60min/3 次投递的事故实测 × 安全系数)。
|
|
59
|
+
*/
|
|
60
|
+
export const ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS = WATCHDOG_DEFAULT_HOURS * MS_PER_HOUR;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 决策看门狗窗的用户覆盖 env(规则 19:用户显式指定才生效;XYZ_SUBAGENT_* 前缀
|
|
64
|
+
* 过 ENV_WHITELIST_PREFIXES 白名单——PI_ 前缀在桌面 spawn 链被静默丢弃)。
|
|
65
|
+
* >0 覆盖默认窗;≤0 关闭该放弃路径(只送达指引、永不放弃——显式 opt-out,
|
|
66
|
+
* warn 明示后果);未设 = 默认 2h;非数字 = 回落默认 + warn 留痕。
|
|
67
|
+
*/
|
|
68
|
+
export const ROUND_SUPERVISOR_WATCHDOG_ENV = "XYZ_SUBAGENT_ROUND_SUPERVISOR_WATCHDOG_MS";
|
|
69
|
+
|
|
70
|
+
/** 看门狗窗解析缓存(惰性首读定案;_resetEnvCacheForTest 清缓存供测试改 env)。 */
|
|
71
|
+
let watchdogEnvCache: { disabled: boolean; overrideMs?: number } | undefined;
|
|
72
|
+
|
|
73
|
+
function resolveWatchdogEnv(): { disabled: boolean; overrideMs?: number } {
|
|
74
|
+
if (watchdogEnvCache) return watchdogEnvCache;
|
|
75
|
+
watchdogEnvCache = { disabled: false };
|
|
76
|
+
const raw = process.env[ROUND_SUPERVISOR_WATCHDOG_ENV];
|
|
77
|
+
if (raw === undefined || raw.trim() === "") return watchdogEnvCache;
|
|
78
|
+
const parsed = Number(raw);
|
|
79
|
+
if (!Number.isFinite(parsed)) {
|
|
80
|
+
logger.warn(
|
|
81
|
+
`[round-supervisor] ${ROUND_SUPERVISOR_WATCHDOG_ENV}="${raw}" is invalid — falling back to ` +
|
|
82
|
+
`default decision watchdog window (${ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS}ms)`,
|
|
83
|
+
);
|
|
84
|
+
return watchdogEnvCache;
|
|
85
|
+
}
|
|
86
|
+
if (parsed <= 0) {
|
|
87
|
+
logger.warn(
|
|
88
|
+
`[round-supervisor] ${ROUND_SUPERVISOR_WATCHDOG_ENV}=${parsed} disables the give-up path: a dead ` +
|
|
89
|
+
`background task stays resumable and registered forever (goal guard keeps deferring). ` +
|
|
90
|
+
`Recovery: unset the env or set a positive millisecond value.`,
|
|
91
|
+
);
|
|
92
|
+
watchdogEnvCache.disabled = true;
|
|
93
|
+
return watchdogEnvCache;
|
|
94
|
+
}
|
|
95
|
+
watchdogEnvCache.overrideMs = parsed;
|
|
96
|
+
return watchdogEnvCache;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 看门狗窗生效值(ms);disabled=true 时返回值无意义。 */
|
|
100
|
+
export function getSupervisorWatchdogMs(): number {
|
|
101
|
+
return resolveWatchdogEnv().overrideMs ?? ROUND_SUPERVISOR_WATCHDOG_DEFAULT_MS;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 该放弃路径是否被 env 显式关闭。 */
|
|
105
|
+
export function isSupervisorGiveUpDisabled(): boolean {
|
|
106
|
+
return resolveWatchdogEnv().disabled;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── record 视图与依赖注入面 ────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* record 只读视图(监督器判定的唯一状态源——内存/磁盘两形态统一投影)。
|
|
113
|
+
* 字段语义对齐 ExecutionRecord / SubagentRecord 同名项。
|
|
114
|
+
*/
|
|
115
|
+
export interface SupervisorRecordView {
|
|
116
|
+
id: string;
|
|
117
|
+
status: "running" | "closed";
|
|
118
|
+
/** 执行态信号(轮终 idle 写点 / 孤儿恢复兜底)。 */
|
|
119
|
+
resumable: boolean;
|
|
120
|
+
/** 已有完成产出(record.result !== undefined)——SP-5 upgrade 等待态判据。 */
|
|
121
|
+
hasResult: boolean;
|
|
122
|
+
chatMode: boolean;
|
|
123
|
+
rootSessionId: string | undefined;
|
|
124
|
+
agent: string;
|
|
125
|
+
slug: string;
|
|
126
|
+
startedAt: number;
|
|
127
|
+
closedReason: string | undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 该放弃的执行结果分类(文案与终态编排由 deps 侧分叉)。
|
|
131
|
+
* [F3] 无 "boot-abort" 成员:boot 直断(表 3 行 2 in-flight 直断 failed)已在
|
|
132
|
+
* record-store 孤儿恢复层完成(finalizeOrphanRecord 的 error 载体 failed 语义 +
|
|
133
|
+
* 终态 entry + sidecar),监督器不重复终态化(双收尾防线)——原 "boot-abort"
|
|
134
|
+
* GiveUpKind 是头注声称但全链无调用点的死分支,随本轮回清理。 */
|
|
135
|
+
export type GiveUpKind = "watchdog-expired" | "superseded";
|
|
136
|
+
|
|
137
|
+
/** 对账/扫描候选(listCandidateRecords 的元素——本 root session 的 running record)。 */
|
|
138
|
+
export interface SupervisorCandidateRecord {
|
|
139
|
+
id: string;
|
|
140
|
+
rootSessionId: string | undefined;
|
|
141
|
+
agent: string;
|
|
142
|
+
slug: string;
|
|
143
|
+
startedAt: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** RoundSupervisor 的依赖(SubagentService 注入;全部单测替身友好)。 */
|
|
147
|
+
export interface RoundSupervisorDeps {
|
|
148
|
+
now(): number;
|
|
149
|
+
/** record 视图查询(内存 getMutable ∪ 磁盘投影,内存优先)。undefined = 不存在。 */
|
|
150
|
+
getRecordView(id: string): SupervisorRecordView | undefined;
|
|
151
|
+
/** 本 root session 的 running record 候选(boot 分区扫描 + 通知对账候选源)。 */
|
|
152
|
+
listCandidateRecords(): SupervisorCandidateRecord[];
|
|
153
|
+
/** 生命周期镜像谓词(hasLiveProcessHandle)。「有进程驱动」判据源。 */
|
|
154
|
+
hasLiveProcess(recordId: string): boolean;
|
|
155
|
+
/** 表 3 行 1 的合并单条通知(failed 如实 + 已接管契约——消除双通知时序窗口)。 */
|
|
156
|
+
sendMergedFailureNotice(record: SupervisorRecordView, errMsg: string): void;
|
|
157
|
+
/** 决策指引(steer)。exemptionDisclaimer = 低置信对账(指引自带豁免声明)。 */
|
|
158
|
+
sendDecisionGuidance(record: SupervisorRecordView, opts: { exemptionDisclaimer: boolean }): void;
|
|
159
|
+
/** 高置信替代的终止通知(「原任务已被新任务替代」)。 */
|
|
160
|
+
sendReplacedNotice(record: SupervisorRecordView, replacementId: string): void;
|
|
161
|
+
/**
|
|
162
|
+
* 该放弃执行(终态化 failed + 注销发射 + 通知编排归 deps——内存
|
|
163
|
+
* record 走 finalizeRecord(注销①),磁盘态 record 走终态 entry 落盘(注销交
|
|
164
|
+
* sweep ⑤)。CAS 防双收尾由 deps 内部承担。boot 直断不经本方法(已在孤儿恢复
|
|
165
|
+
* 层完成,见 bootPartition 头注)。
|
|
166
|
+
*/
|
|
167
|
+
giveUp(recordId: string, kind: GiveUpKind, detail: { replacementId?: string }): void;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** 纳管记账条目。 */
|
|
171
|
+
interface SupervisedEntry {
|
|
172
|
+
/** 决策指引已送达(一窗内只送一次;run 重启收敛后清除重置)。 */
|
|
173
|
+
guidanceSent: boolean;
|
|
174
|
+
/** 决策看门狗 timer(awaiting-decision 时 armed;该等/解除时清)。 */
|
|
175
|
+
timer: NodeJS.Timeout | undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 轮次活性监督器(SubagentService 持有的实例;无模块级全局态——测试直接构造)。
|
|
180
|
+
*/
|
|
181
|
+
export class RoundSupervisor {
|
|
182
|
+
private readonly supervised = new Map<string, SupervisedEntry>();
|
|
183
|
+
/** 在途 run 记账(subagent-service 在 run 发起/收口时报告)。「有在途 run」判据源。 */
|
|
184
|
+
private readonly inFlightRuns = new Set<string>();
|
|
185
|
+
private disposed = false;
|
|
186
|
+
|
|
187
|
+
constructor(private readonly deps: RoundSupervisorDeps) {}
|
|
188
|
+
|
|
189
|
+
// ── 纳管模型:死亡事件纳管、重建不解管 ─────────────────────────────────
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 死亡事件纳管(表 3 行 1:引擎/子进程死亡,宿主存活——record 保持 resumable
|
|
193
|
+
* 已由调用方完成,本方法只做监督侧三件事):合并单条通知(failed 如实 + 已接管
|
|
194
|
+
* 契约)+ 纳管记账 + 立即三态评估。
|
|
195
|
+
*
|
|
196
|
+
* 幂等/竞态守卫:conversation 形态豁免;record 已终态(cancel/dispose 抢先)不
|
|
197
|
+
* 纳管;重复纳管(同 record 二次死亡事件)不重复通知(保留既有 entry)。
|
|
198
|
+
*/
|
|
199
|
+
adoptOnProcessDeath(record: ExecutionRecord, errMsg: string): void {
|
|
200
|
+
if (this.disposed) return;
|
|
201
|
+
if (classifySupervisorDomain(record) !== "run") return;
|
|
202
|
+
if (record.status !== "running") return;
|
|
203
|
+
const existing = this.supervised.get(record.id);
|
|
204
|
+
if (!existing) {
|
|
205
|
+
this.supervised.set(record.id, { guidanceSent: false, timer: undefined });
|
|
206
|
+
this.deps.sendMergedFailureNotice(this.toView(record), errMsg);
|
|
207
|
+
}
|
|
208
|
+
this.evaluate(record.id);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* boot 分区(裁决表行 2/3——initSession 扫描):
|
|
213
|
+
* - already-resumable-idle(非 conversation)→ 重认领接管(注册存续,process 档),
|
|
214
|
+
* 监督器三态继续;
|
|
215
|
+
* - conversation 形态 → 现状(轮终 idle 机制管辖,不入监督域)。
|
|
216
|
+
*
|
|
217
|
+
* in-flight(重启前在途、无 resumable 信号)不在本方法处置面:直断 failed 已由
|
|
218
|
+
* record-store 孤儿恢复(recoverOrphanRecords → finalizeOrphanRecord)在先完成
|
|
219
|
+
* ([F3] 表 3 行 2 语义:error 载体 failed 投影 + 终态 entry + sidecar),本方法
|
|
220
|
+
* 重复终态化会双收尾——isBootReadoptable 谓词把 in-flight 形态挡在本方法之外
|
|
221
|
+
* (谓词与孤儿恢复保留分支同源,domain.ts);已直断 record 的注册残留注销由
|
|
222
|
+
* reconcile sweep(发射点⑤)对「已终态 record」差集补 appendEntry 权威落盘。
|
|
223
|
+
*
|
|
224
|
+
* 依赖时序:须在 store 孤儿恢复(recoverOrphanRecords)之后调用——孤儿恢复已把
|
|
225
|
+
* 「重启前在途且无 resumable 信号」的非 chatMode record 直断 closed(boot 直断的
|
|
226
|
+
* 唯一实现锚点),并把 resumable 形态保留 running 落 entry(本方法的重认领源)。
|
|
227
|
+
*/
|
|
228
|
+
bootPartition(): { readopted: string[] } {
|
|
229
|
+
const readopted: string[] = [];
|
|
230
|
+
if (this.disposed) return { readopted };
|
|
231
|
+
for (const candidate of this.deps.listCandidateRecords()) {
|
|
232
|
+
const view = this.deps.getRecordView(candidate.id);
|
|
233
|
+
if (view === undefined || view.status !== "running") continue;
|
|
234
|
+
if (view.chatMode) continue; // conversation 豁免(现状,轮终 idle 机制管辖)
|
|
235
|
+
// boot 分区只处理「孤儿恢复保留下来的 resumable 且无完成产出的形态」(W4 死亡
|
|
236
|
+
// 纳管态跨重启)——in-flight 已被孤儿恢复直断(见头注时序),不再此处重复
|
|
237
|
+
// 终态化(双收尾防线);resumable 且 result 有值是 SP-5 完成态(已完成挂账,
|
|
238
|
+
// idle-gc 锚归档收口,无需监督)。谓词与 record-store 孤儿恢复保留分支同源
|
|
239
|
+
// (isBootReadoptable,domain.ts)。
|
|
240
|
+
if (!isBootReadoptable(view)) continue;
|
|
241
|
+
if (!this.supervised.has(view.id)) {
|
|
242
|
+
this.supervised.set(view.id, { guidanceSent: false, timer: undefined });
|
|
243
|
+
}
|
|
244
|
+
readopted.push(view.id);
|
|
245
|
+
this.evaluate(view.id);
|
|
246
|
+
}
|
|
247
|
+
if (readopted.length > 0) {
|
|
248
|
+
logger.warn(
|
|
249
|
+
`[round-supervisor] boot partition: readopted ${readopted.length} idle-resumable record(s): ${readopted.join(",")}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return { readopted };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── 在途记账(subagent-service 报告)──────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/** run 发起(engine run / chat 轮 kickoff):该等 → 不干预;解除看门狗等待。 */
|
|
258
|
+
noteRunStarted(recordId: string): void {
|
|
259
|
+
this.inFlightRuns.add(recordId);
|
|
260
|
+
const entry = this.supervised.get(recordId);
|
|
261
|
+
if (entry !== undefined) {
|
|
262
|
+
// run 恢复 = 主 agent 决策已收敛(resume)——清指引标记与看门狗,回归该等。
|
|
263
|
+
this.clearWatchdogTimer(entry);
|
|
264
|
+
entry.guidanceSent = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** run 收口(终态迁移 / 失败 / 被杀):驱动可能死亡 → 重新三态评估。 */
|
|
269
|
+
noteRunEnded(recordId: string): void {
|
|
270
|
+
this.inFlightRuns.delete(recordId);
|
|
271
|
+
if (this.supervised.has(recordId)) {
|
|
272
|
+
this.evaluate(recordId);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── 三态判定 ──────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
/** 单 record 三态评估(幂等;record 终态/消失即解除纳管)。 */
|
|
279
|
+
private evaluate(recordId: string): void {
|
|
280
|
+
if (this.disposed) return;
|
|
281
|
+
const view = this.deps.getRecordView(recordId);
|
|
282
|
+
const entry = this.supervised.get(recordId);
|
|
283
|
+
if (view === undefined || entry === undefined) {
|
|
284
|
+
this.release(recordId);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (view.status !== "running") {
|
|
288
|
+
// 终态 = 自然收口(finalizeRecord 已走注销①发射)——解除纳管。
|
|
289
|
+
this.release(recordId);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const hasInFlight = this.inFlightRuns.has(recordId);
|
|
293
|
+
const hasLive = this.deps.hasLiveProcess(recordId);
|
|
294
|
+
if (hasInFlight || hasLive) {
|
|
295
|
+
// 该等:不干预(解除看门狗等待——驱动存在即无需放弃计时)。
|
|
296
|
+
this.clearWatchdogTimer(entry);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!isAwakeWarrantedShape(
|
|
300
|
+
{ status: view.status, resumable: view.resumable, chatMode: view.chatMode },
|
|
301
|
+
hasInFlight,
|
|
302
|
+
hasLive,
|
|
303
|
+
)) {
|
|
304
|
+
// conversation 豁免(纳管入口已滤,防御性到达)——不唤醒。
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (view.hasResult) {
|
|
308
|
+
// 已有完成产出(SP-5 upgrade 等待态挂账归 idle-gc)——不唤醒。
|
|
309
|
+
// [F5] 挂账态必须解除看门狗:此前的该唤醒评估可能已 armed,转挂账后 timer
|
|
310
|
+
// 若留存,2h 到期会把已完成挂账 record 误 giveUp(watchdog-expired)。对照上方
|
|
311
|
+
// 该等分支的清理形态——不再需要放弃计时的形态(有驱动 / 已收敛挂账)统一
|
|
312
|
+
// 先清 timer 再退出。
|
|
313
|
+
this.clearWatchdogTimer(entry);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// 该唤醒 → 通知对账(送指引前查替代)。
|
|
317
|
+
const windowMs = getSupervisorWatchdogMs();
|
|
318
|
+
const verdict = classifyReplacement(
|
|
319
|
+
{ id: view.id, rootSessionId: view.rootSessionId, agentName: view.agent, slug: view.slug },
|
|
320
|
+
this.deps.listCandidateRecords().map((c) => ({
|
|
321
|
+
id: c.id,
|
|
322
|
+
rootSessionId: c.rootSessionId,
|
|
323
|
+
agentName: c.agent,
|
|
324
|
+
slug: c.slug,
|
|
325
|
+
startedAt: c.startedAt,
|
|
326
|
+
})),
|
|
327
|
+
windowMs,
|
|
328
|
+
this.deps.now(),
|
|
329
|
+
);
|
|
330
|
+
if (verdict.kind === "high-confidence") {
|
|
331
|
+
logger.warn(
|
|
332
|
+
`[round-supervisor] record ${view.id} superseded by ${verdict.replacementId} ` +
|
|
333
|
+
`(same root/agent/slug within watchdog window) — withdrawing guidance, giving up original`,
|
|
334
|
+
);
|
|
335
|
+
this.release(recordId);
|
|
336
|
+
this.deps.sendReplacedNotice(view, verdict.replacementId);
|
|
337
|
+
this.deps.giveUp(recordId, "superseded", { replacementId: verdict.replacementId });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// 低置信 / 无命中:送达决策指引(低置信自带豁免声明——把残余决策交还唯一
|
|
341
|
+
// 决策者,不诱导冲突动作);一窗内只送一次。
|
|
342
|
+
if (!entry.guidanceSent) {
|
|
343
|
+
this.deps.sendDecisionGuidance(view, {
|
|
344
|
+
exemptionDisclaimer: verdict.kind === "low-confidence",
|
|
345
|
+
});
|
|
346
|
+
entry.guidanceSent = true;
|
|
347
|
+
}
|
|
348
|
+
this.armWatchdog(recordId, entry);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── 看门狗(该放弃)───────────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
private armWatchdog(recordId: string, entry: SupervisedEntry): void {
|
|
354
|
+
this.clearWatchdogTimer(entry);
|
|
355
|
+
if (isSupervisorGiveUpDisabled()) return;
|
|
356
|
+
const windowMs = getSupervisorWatchdogMs();
|
|
357
|
+
// 常量/env 值入口校验:防越界值静默引入 1ms 溢出语义反转(对齐 settled-watchdog)。
|
|
358
|
+
assertSafeTimerDelay(windowMs, "round supervisor decision watchdog");
|
|
359
|
+
const timer = setTimeout(() => {
|
|
360
|
+
entry.timer = undefined;
|
|
361
|
+
this.supervised.delete(recordId);
|
|
362
|
+
const view = this.deps.getRecordView(recordId);
|
|
363
|
+
if (view === undefined || view.status !== "running") return; // 已自然收口
|
|
364
|
+
logger.warn(
|
|
365
|
+
`[round-supervisor] decision watchdog expired for ${recordId} after ${windowMs}ms ` +
|
|
366
|
+
`(guidance unanswered, no convergence) — giving up (failed + unregister + termination notice)`,
|
|
367
|
+
);
|
|
368
|
+
this.deps.giveUp(recordId, "watchdog-expired", {});
|
|
369
|
+
}, windowMs);
|
|
370
|
+
timer.unref?.();
|
|
371
|
+
entry.timer = timer;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private clearWatchdogTimer(entry: SupervisedEntry): void {
|
|
375
|
+
if (entry.timer !== undefined) {
|
|
376
|
+
clearTimeout(entry.timer);
|
|
377
|
+
entry.timer = undefined;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** 解除纳管(终态/消失/放弃后;幂等)。 */
|
|
382
|
+
release(recordId: string): void {
|
|
383
|
+
const entry = this.supervised.get(recordId);
|
|
384
|
+
if (entry !== undefined) {
|
|
385
|
+
this.clearWatchdogTimer(entry);
|
|
386
|
+
this.supervised.delete(recordId);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** 纳管中的 record id(测试断言面)。 */
|
|
391
|
+
supervisedIds(): string[] {
|
|
392
|
+
return [...this.supervised.keys()];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** ExecutionRecord → 视图(死亡事件纳管入口用——该路径恒有内存 record)。 */
|
|
396
|
+
private toView(record: ExecutionRecord): SupervisorRecordView {
|
|
397
|
+
return {
|
|
398
|
+
id: record.id,
|
|
399
|
+
status: record.status === "closed" ? "closed" : "running",
|
|
400
|
+
resumable: record.resumable === true,
|
|
401
|
+
hasResult: record.result !== undefined,
|
|
402
|
+
chatMode: record.chatMode === true,
|
|
403
|
+
rootSessionId: record.rootSessionId,
|
|
404
|
+
agent: record.agent,
|
|
405
|
+
slug: record.slug,
|
|
406
|
+
startedAt: record.startedAt,
|
|
407
|
+
closedReason: record.closedReason,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** dispose(session_shutdown 链):清全部 timer + 纳管记账(注册/record 不动——
|
|
412
|
+
* process 档跨 shutdown 存活,重开 session 由 boot 分区重认领 + sweep 对账)。 */
|
|
413
|
+
dispose(): void {
|
|
414
|
+
this.disposed = true;
|
|
415
|
+
for (const entry of this.supervised.values()) {
|
|
416
|
+
this.clearWatchdogTimer(entry);
|
|
417
|
+
}
|
|
418
|
+
this.supervised.clear();
|
|
419
|
+
this.inFlightRuns.clear();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** 测试隔离:重置 env 解析缓存(vi.stubEnv 改 env 后生效)。 */
|
|
423
|
+
static _resetEnvCacheForTest(): void {
|
|
424
|
+
watchdogEnvCache = undefined;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
@@ -10,10 +10,20 @@
|
|
|
10
10
|
// 若 runSpawn 在 agent_end 无条件 kill,进程被回收、steer 唤醒送不到,递归树断。
|
|
11
11
|
// 判定依据:子进程的 session 文件里 pending:register entry(其进程内 appendEntry
|
|
12
12
|
// 同步写盘,见 pi SessionManager._persist)减去 pending:unregister 的差集。
|
|
13
|
-
// fork
|
|
14
|
-
//
|
|
13
|
+
// fork 继承的父级 register 残留(翻 process 档后 U4 补注销不再中性化,永久留存于
|
|
14
|
+
// 子 session 文件)由读侧过滤收口 [W4 读侧过滤② / F1]:本文件读侧以「被读文件
|
|
15
|
+
// 所属 session id」(pi session 文件首行 SessionHeader.id)为基准,经通知域端口
|
|
16
|
+
// countActiveFromEntries 的第二参传入,端口实现按「entry sessionId ≠ 基准 → 跳过」
|
|
17
|
+
// 过滤——差集不受污染;落盘收口归 core 注册对账 sweep(reconcile-sweep.ts)。
|
|
15
18
|
//
|
|
16
|
-
// 纯函数 + fs
|
|
19
|
+
// 纯函数 + fs,独立于执行链路,可单测。
|
|
20
|
+
//
|
|
21
|
+
// [D5 消亡登记(chat-domain-v1x-liveness-governance W3)] listActivePendingFromSessionFile
|
|
22
|
+
// (后代补杀清单口径)随 inproc session-runner(已删) 删除消亡——其消费方全在该待删
|
|
23
|
+
// 文件;「层主死后孤儿后代止损」语义由 W4 轮次活性监督器逐 record 三态(该放弃)承接,
|
|
24
|
+
// 禁止按旧「不过滤跨 session」语义把补杀迁入新路径(翻档后 fork 残留会进补杀清单,
|
|
25
|
+
// 误杀父 session 活跃后代)。readActivePendingFromSessionFile(count 口径)保留:
|
|
26
|
+
// 判定语义仍由其单测锁守,生产消费方随删件清零(存活面 = 机制本体,勿误认清理仍在工作)。
|
|
17
27
|
|
|
18
28
|
import * as fs from "node:fs";
|
|
19
29
|
|
|
@@ -62,6 +72,10 @@ interface PendingReadCursor {
|
|
|
62
72
|
offset: number;
|
|
63
73
|
activeRegisters: Map<string, unknown>;
|
|
64
74
|
latestUnregisterMs: number;
|
|
75
|
+
/** [F1] 被读文件所属 session id(pi session 文件首行 SessionHeader.id,首读全量
|
|
76
|
+
* 时提取一次随 cursor 缓存——增量读不重读首行)。undefined = 首行非 session
|
|
77
|
+
* header(旧 fixture/手工构造形态)→ 端口调用不传基准(不过滤,向后兼容)。 */
|
|
78
|
+
headerSessionId?: string;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
const cursors = new Map<string, PendingReadCursor>();
|
|
@@ -91,8 +105,7 @@ function isPendingLineLike(v: unknown): v is { customType?: string; timestamp?:
|
|
|
91
105
|
/**
|
|
92
106
|
* [内部共享] 读 session 文件并把 pending register/unregister 行累积进 per-file 增量
|
|
93
107
|
* 游标的**活跃差集**(见 PendingReadCursor)。readActivePendingFromSessionFile(count
|
|
94
|
-
*
|
|
95
|
-
* 口径交错调用同一文件时差集不错位、不重复读已消费区间。
|
|
108
|
+
* 口径,唯一存活口径——清单口径已随 D5 消亡登记,见文件头)消费同一 cursor。
|
|
96
109
|
*
|
|
97
110
|
* 快速路径:行内含 pending 值(`"pending:register"` / `"pending:unregister"`)才解析,
|
|
98
111
|
* 大量 message 行只付 includes 扫描跳过 JSON.parse。
|
|
@@ -106,7 +119,7 @@ function isPendingLineLike(v: unknown): v is { customType?: string; timestamp?:
|
|
|
106
119
|
*/
|
|
107
120
|
function accumulatePendingEntries(
|
|
108
121
|
sessionFile: string | undefined,
|
|
109
|
-
): { activeRegisters: Map<string, unknown>; latestUnregisterMs: number; error?: string } {
|
|
122
|
+
): { activeRegisters: Map<string, unknown>; latestUnregisterMs: number; headerSessionId?: string; error?: string } {
|
|
110
123
|
const emptyAcc = { activeRegisters: new Map<string, unknown>(), latestUnregisterMs: 0 };
|
|
111
124
|
if (!sessionFile) {
|
|
112
125
|
return { ...emptyAcc, error: "no sessionFile (handshake not settled)" };
|
|
@@ -128,6 +141,12 @@ function accumulatePendingEntries(
|
|
|
128
141
|
return { ...emptyAcc, error: chunk.error };
|
|
129
142
|
}
|
|
130
143
|
|
|
144
|
+
// [F1] 首读全量时提取被读文件所属 session id(首行 SessionHeader.id),随 cursor
|
|
145
|
+
// 缓存——增量读不重读首行。须在 offset 推进前判(offset===0 = 本次为全量读)。
|
|
146
|
+
if (cursor.offset === 0 && cursor.headerSessionId === undefined) {
|
|
147
|
+
cursor.headerSessionId = extractHeaderSessionId(chunk);
|
|
148
|
+
}
|
|
149
|
+
|
|
131
150
|
// 只消费到最后一个完整行:EOF 半行(append 写入竞态)不入账,offset 不推进,
|
|
132
151
|
// 下次从该行起点重读(补全后正常入账)。
|
|
133
152
|
const lastNl = chunk.lastIndexOf("\n");
|
|
@@ -136,7 +155,40 @@ function accumulatePendingEntries(
|
|
|
136
155
|
cursors.set(sessionFile, cursor);
|
|
137
156
|
consumePendingLines(complete, cursor, sessionFile);
|
|
138
157
|
|
|
139
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
activeRegisters: cursor.activeRegisters,
|
|
160
|
+
latestUnregisterMs: cursor.latestUnregisterMs,
|
|
161
|
+
headerSessionId: cursor.headerSessionId,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* [F1 读侧过滤②基准] pi session 文件首行 SessionHeader 的 id 提取(pi 实装锚定:
|
|
167
|
+
* session-manager.js newSession 置 fileEntries=[header],首次 flush 整体落盘——
|
|
168
|
+
* 首行 = `{type:"session", id, ...}`)。首行非 session header(旧 fixture / 手工
|
|
169
|
+
* 构造 / 坏行)返回 undefined = 端口调用不传基准(不过滤,向后兼容——漏计的危害
|
|
170
|
+
* 方向是幻 defer,宁放行不误杀活跃计数,对齐 pending-notifications 的容错语义)。
|
|
171
|
+
*/
|
|
172
|
+
function extractHeaderSessionId(chunk: string): string | undefined {
|
|
173
|
+
const nl = chunk.indexOf("\n");
|
|
174
|
+
const firstLine = nl === -1 ? chunk : chunk.slice(0, nl);
|
|
175
|
+
try {
|
|
176
|
+
const parsed: unknown = JSON.parse(firstLine);
|
|
177
|
+
if (
|
|
178
|
+
typeof parsed === "object" && parsed !== null &&
|
|
179
|
+
(parsed as { type?: unknown }).type === "session" &&
|
|
180
|
+
typeof (parsed as { id?: unknown }).id === "string"
|
|
181
|
+
) {
|
|
182
|
+
return (parsed as { id: string }).id;
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
// 首行坏行:无基准(不过滤)。预期内可忽略(旧 fixture / 手工构造 / append
|
|
186
|
+
// 中途崩溃的截断行),debug 留痕对齐 applyPendingLine 的坏行处理。
|
|
187
|
+
logger.debug("skipped malformed session header line", {
|
|
188
|
+
error: err instanceof Error ? err.message : String(err),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
140
192
|
}
|
|
141
193
|
|
|
142
194
|
/** stat + 失败剪枝的统一文案(stat/read 两路 [LC-6] 剪枝共用)。 */
|
|
@@ -244,8 +296,16 @@ export function readActivePendingFromSessionFile(
|
|
|
244
296
|
// 收敛在端口层,见 core/notify-ports.ts DEFAULT_NOTIFY_PORTS;此处 `?? 0` 仅防御
|
|
245
297
|
// 宿主注入部分端口对象的形态)。[LC-6] 入参是差集后的活跃 register 集合(unregister
|
|
246
298
|
// 抵消已内联),端口语义(TTL/跨 session 过滤作用于 register entry 本体)不受影响。
|
|
299
|
+
// [F1 读侧过滤②] 以「被读文件所属 session id」为基准传入端口(提取不到 =
|
|
300
|
+
// undefined = 不过滤,向后兼容)——fork 继承的父级注册残留(entry sessionId =
|
|
301
|
+
// 父 session ≠ 基准)不进后代判定差集,层主不被残留误判「尚有活跃后代」。
|
|
247
302
|
const countActive = getNotifyDomainPorts().countActiveFromEntries;
|
|
248
|
-
const active = countActive
|
|
303
|
+
const active = countActive
|
|
304
|
+
? countActive(
|
|
305
|
+
[...acc.activeRegisters.values()],
|
|
306
|
+
acc.headerSessionId !== undefined ? { currentSessionId: acc.headerSessionId } : undefined,
|
|
307
|
+
)
|
|
308
|
+
: 0;
|
|
249
309
|
return {
|
|
250
310
|
count: active,
|
|
251
311
|
recentUnregister:
|
|
@@ -253,69 +313,3 @@ export function readActivePendingFromSessionFile(
|
|
|
253
313
|
Date.now() - acc.latestUnregisterMs < RECENT_UNREGISTER_WINDOW_MS,
|
|
254
314
|
};
|
|
255
315
|
}
|
|
256
|
-
|
|
257
|
-
/** 活跃 pending 清单条目(T2-② 后代补杀的 id/sessionId 线索)。 */
|
|
258
|
-
export interface ActivePendingItem {
|
|
259
|
-
/** pending 操作 id(register − unregister 差集的键,id 全局唯一)。 */
|
|
260
|
-
id: string;
|
|
261
|
-
/**
|
|
262
|
-
* 注册时的后代 pi session id(pending:register entry data.sessionId)——
|
|
263
|
-
* 反查后代 sessionFile 的线索。缺失(异常形态)时 undefined,调用方降级处理。
|
|
264
|
-
*/
|
|
265
|
-
sessionId: string | undefined;
|
|
266
|
-
/** 操作来源类型(workflow / session / process),诊断用。 */
|
|
267
|
-
type: string | undefined;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/** listActivePendingFromSessionFile 的结果(error 非空时 items 为空)。 */
|
|
271
|
-
export interface ActivePendingListResult {
|
|
272
|
-
items: ActivePendingItem[];
|
|
273
|
-
/** 读取/解析失败的原因(undefined = 成功)。 */
|
|
274
|
-
error?: string;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** pending:register/unregister entry 的 data 最小形状(对齐 pending-notifications
|
|
278
|
-
* extension 的 RegisterEntryData/UnregisterEntryData:id 均在 data.id)。 */
|
|
279
|
-
interface PendingEntryDataLike {
|
|
280
|
-
id?: unknown;
|
|
281
|
-
sessionId?: unknown;
|
|
282
|
-
type?: unknown;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** entry.data 的运行时守卫([taste/no-unsafe-cast]:字段访问前校验)。 */
|
|
286
|
-
function isPendingEntryDataLike(v: unknown): v is PendingEntryDataLike {
|
|
287
|
-
return typeof v === "object" && v !== null;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* [T2-② / P-T2b 主路径] 读 session 文件列出活跃 pending 清单(register − unregister
|
|
292
|
-
* 差集,按 data.id)。与 readActivePendingFromSessionFile 共享 per-file 增量游标的
|
|
293
|
-
* 活跃差集(count 与 list 交错调用不错位)。
|
|
294
|
-
*
|
|
295
|
-
* 与 count 口径的两点刻意差异(后代补杀语境):
|
|
296
|
-
* 1. 不经 countActiveFromEntries 端口——补杀需要「谁还活着」的 id/sessionId 线索,
|
|
297
|
-
* 端口只给数量(差集本身已由游标内联完成,此处直接遍历活跃 register)。
|
|
298
|
-
* 2. 不套用端口的 TTL/跨 session 过期语义——层主死后,TTL 过期或跨 session 的后代
|
|
299
|
-
* 同样是无人管理的孤儿进程,列入补杀正是本函数的目的而非误杀。
|
|
300
|
-
*/
|
|
301
|
-
export function listActivePendingFromSessionFile(
|
|
302
|
-
sessionFile: string | undefined,
|
|
303
|
-
): ActivePendingListResult {
|
|
304
|
-
const acc = accumulatePendingEntries(sessionFile);
|
|
305
|
-
if (acc.error) {
|
|
306
|
-
return { items: [], error: acc.error };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const items: ActivePendingItem[] = [];
|
|
310
|
-
for (const [id, raw] of acc.activeRegisters) {
|
|
311
|
-
if (!isPendingLineLike(raw)) continue;
|
|
312
|
-
const data = (raw as { data?: unknown }).data;
|
|
313
|
-
if (!isPendingEntryDataLike(data)) continue;
|
|
314
|
-
items.push({
|
|
315
|
-
id,
|
|
316
|
-
sessionId: typeof data.sessionId === "string" ? data.sessionId : undefined,
|
|
317
|
-
type: typeof data.type === "string" ? data.type : undefined,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
return { items };
|
|
321
|
-
}
|
|
@@ -309,3 +309,42 @@ export function _resetSettledWatchdogsForTest(): void {
|
|
|
309
309
|
armedEntries.clear();
|
|
310
310
|
envCache = undefined;
|
|
311
311
|
}
|
|
312
|
+
|
|
313
|
+
// ── [W4] 协议事件面接线 API(W3 删件重接的 core 半边)──────────────────
|
|
314
|
+
//
|
|
315
|
+
// 设计权威源:docs/design/chat-domain-v1x-liveness-governance.md §3.2 D2 前置 1 +
|
|
316
|
+
// D5「settled-watchdog 生产接线重接」:两段守护的 refresh 源随 chat 域 cli 化改挂
|
|
317
|
+
// 协议事件流(arm 点 = 轮开始;refresh 源 = host/streamDelta + host/roundLifecycle
|
|
318
|
+
// 事件;kill/终态 = 既有杀链)。现状 refresh/handover/disarm 的驱动源在待删的
|
|
319
|
+
// session-runner.ts stdout pump(inproc 过渡形态,W3 删件前不得删除——本 API 面即
|
|
320
|
+
// 其替换目标:W3 删旧接线时改调下面三个命名入口,语义逐一同构)。
|
|
321
|
+
//
|
|
322
|
+
// 命名入口与既有原语的映射(刻意薄委托、零新语义——本单元「把新接线立起来」,
|
|
323
|
+
// W3 负责删旧,两套接线并存期由原语幂等吸收重复调用):
|
|
324
|
+
// refreshFromProtocolEvent ↔ refreshMidRoundNoProgress(协议事件行到达)
|
|
325
|
+
// noteRoundSettledFromProtocol ↔ handoverMidRoundToSettled(roundLifecycle
|
|
326
|
+
// settled 相位 = 轮收敛,中段让位收尾段)
|
|
327
|
+
// disarmRoundFromProtocol ↔ disarmSettledWatchdog(idle 相位 / close / 终态)
|
|
328
|
+
//
|
|
329
|
+
// 监督器域 resume 轮的 refresh 覆盖:resume 轮的 arm 点在 subagent-service
|
|
330
|
+
// deliverChatMessage(interact 返回点,冷热路径同点),其轮内协议事件(streamDelta /
|
|
331
|
+
// roundLifecycle)到达时经 refreshFromProtocolEvent 刷新——W3 接线后两域轮(chat 轮
|
|
332
|
+
// 与监督器域 resume 轮)共用同一事件面。
|
|
333
|
+
|
|
334
|
+
/** [W4 协议事件面] 协议事件行(host/streamDelta / host/roundLifecycle)到达:刷新
|
|
335
|
+
* 中段无进展计时。未挂载 / 已交棒(收尾段不刷新)/ 已 fire 时幂等 no-op。 */
|
|
336
|
+
export function refreshFromProtocolEvent(recordId: string): void {
|
|
337
|
+
refreshMidRoundNoProgress(recordId);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** [W4 协议事件面] roundLifecycle settled 相位(轮收敛):中段让位收尾段
|
|
341
|
+
* (两段独立计时,交棒语义见 handoverMidRoundToSettled)。未挂载时幂等 no-op。 */
|
|
342
|
+
export function noteRoundSettledFromProtocol(recordId: string): void {
|
|
343
|
+
handoverMidRoundToSettled(recordId);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** [W4 协议事件面] roundLifecycle idle 相位 / close / 终态化处置:两段一并清。
|
|
347
|
+
* 不存在 armed entry 时幂等 no-op。 */
|
|
348
|
+
export function disarmRoundFromProtocol(recordId: string): void {
|
|
349
|
+
disarmSettledWatchdog(recordId);
|
|
350
|
+
}
|