@zhushanwen/subagent-core 0.7.1 → 0.9.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/dist/{chunk-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
- package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
- package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
- package/dist/execution/engine/engine-discovery-scan.cjs +325 -154
- package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
- package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
- package/dist/execution/engine/engine-discovery-scan.js +1 -1
- package/dist/execution/relay-env.js +5 -4
- package/dist/index.cjs +9799 -7954
- package/dist/index.d.cts +1896 -1360
- package/dist/index.d.ts +1896 -1360
- package/dist/index.js +8952 -7513
- package/dist.bundle/index.cjs +7897 -6054
- package/package.json +5 -5
- package/src/__tests__/manifest-store.test.ts +16 -15
- package/src/__tests__/record-store-last-line.test.ts +12 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
- package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
- package/src/core/logger.ts +1 -1
- package/src/execution/__tests__/alive-store.test.ts +117 -1
- package/src/execution/__tests__/batch-finalized.test.ts +206 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
- package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
- package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
- package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
- package/src/execution/__tests__/delivery-methods.test.ts +90 -176
- package/src/execution/__tests__/dialog-queue.test.ts +70 -3
- package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
- package/src/execution/__tests__/execute-nesting.test.ts +8 -5
- package/src/execution/__tests__/execution-record.test.ts +8 -148
- package/src/execution/__tests__/finalize-record.test.ts +295 -129
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
- package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
- package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
- package/src/execution/__tests__/idle-gc.test.ts +60 -0
- package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
- package/src/execution/__tests__/model-config-service.test.ts +111 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
- package/src/execution/__tests__/nested-visibility.test.ts +13 -1
- package/src/execution/__tests__/notify-ledger.test.ts +42 -1
- package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
- package/src/execution/__tests__/record-binding.test.ts +613 -0
- package/src/execution/__tests__/record-origin.test.ts +318 -0
- package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
- package/src/execution/__tests__/record-store.test.ts +72 -54
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
- package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
- package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
- package/src/execution/__tests__/run-orchestration.test.ts +199 -0
- package/src/execution/__tests__/session-file-gc.test.ts +35 -4
- package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
- package/src/execution/__tests__/state-marker.test.ts +288 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
- package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
- package/src/execution/__tests__/subagent-service.test.ts +17 -215
- package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
- package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
- package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
- package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
- package/src/execution/alive-store.ts +37 -28
- package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
- package/src/execution/config.ts +1 -1
- package/src/execution/conversation-continuation.ts +541 -0
- package/src/execution/dialog-queue.ts +60 -12
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
- package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
- package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
- package/src/execution/engine/__tests__/registry.test.ts +0 -1
- package/src/execution/engine/__tests__/routing.test.ts +0 -1
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
- package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
- package/src/execution/engine/client/engine-client.ts +111 -26
- package/src/execution/engine/client/mirror.ts +9 -0
- package/src/execution/engine/client/remote-engine.ts +52 -34
- package/src/execution/engine/client/reverse-router.ts +6 -27
- package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
- package/src/execution/engine/common/capability-gate.ts +27 -0
- package/src/execution/engine/common/data-dir.ts +3 -2
- package/src/execution/engine/common/errors.ts +1 -1
- package/src/execution/engine/common/journal-wiring.ts +18 -9
- package/src/execution/engine/common/run-signals.ts +83 -0
- package/src/execution/engine/d8-compat.ts +1 -1
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
- package/src/execution/engine/host/host-bridge.ts +26 -41
- package/src/execution/engine/host/pi-host-binding.ts +5 -5
- package/src/execution/engine/host/spawned-children.ts +1 -1
- package/src/execution/engine/inflight-snapshot.ts +92 -0
- package/src/execution/engine/port.ts +52 -54
- package/src/execution/engine/types.ts +11 -14
- package/src/execution/execution-record.ts +15 -47
- package/src/execution/finalize-record.ts +94 -197
- package/src/execution/idle-gc.ts +9 -3
- package/src/execution/lifecycle-manager.ts +25 -361
- package/src/execution/lifecycle-predicates.ts +22 -19
- package/src/execution/manifest-store.ts +40 -42
- package/src/execution/model-config-service.ts +1 -1
- package/src/execution/notifier.ts +39 -1
- package/src/execution/notify-host.ts +13 -4
- package/src/execution/path-encoding.ts +6 -0
- package/src/execution/record-entry.ts +20 -2
- package/src/execution/record-store.ts +925 -160
- package/src/execution/relay-env.ts +8 -3
- package/src/execution/round-supervisor/domain.ts +17 -5
- package/src/execution/round-supervisor/service-binding.test.ts +89 -53
- package/src/execution/round-supervisor/service-binding.ts +71 -21
- package/src/execution/round-supervisor/supervisor.ts +23 -9
- package/src/execution/service/record-access.ts +547 -0
- package/src/execution/service/record-lifecycle.ts +542 -0
- package/src/execution/service/run-orchestration.ts +1647 -0
- package/src/execution/service/service-bootstrap.ts +111 -0
- package/src/execution/service/service-constants.ts +27 -0
- package/src/execution/service/session-baselines.ts +401 -0
- package/src/execution/service/sync-collect-domain.ts +442 -0
- package/src/execution/service/workflow-dispatch.ts +567 -0
- package/src/execution/session-file-gc.ts +11 -8
- package/src/execution/session-reconstructor.ts +44 -1
- package/src/execution/sessions-index.ts +20 -3
- package/src/execution/settled-watchdog.ts +86 -43
- package/src/execution/state-marker.ts +516 -0
- package/src/execution/stream-sink.ts +1 -1
- package/src/execution/subagent-actions-core.ts +53 -31
- package/src/execution/subagent-service.ts +643 -2955
- package/src/execution/subprocess-agent-runner.ts +46 -323
- package/src/execution/types.ts +60 -43
- package/src/execution/ui-request-handler-factory.ts +5 -0
- package/src/execution/worktree-manager.ts +1 -1
- package/src/execution/worktree-registry.ts +12 -8
- package/src/index.ts +16 -5
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
- package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
- package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
- package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
- package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +1 -1
- package/src/orchestration/models/ports.ts +20 -13
- package/src/orchestration/models/types.ts +11 -13
- package/src/orchestration/run-snapshot.ts +18 -22
- package/src/orchestration/worker-message-pump.ts +33 -57
- package/src/shared/atomic-write.ts +10 -8
- package/dist/chunk-T2SYBW3I.js +0 -24
- package/dist/engine-discovery-scan-ocpM8FmI.d.cts +0 -1608
- package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
- package/src/execution/__tests__/finalized-marker.test.ts +0 -104
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
- package/src/execution/__tests__/tombstone-store.test.ts +0 -73
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
- package/src/execution/finalized-marker.ts +0 -70
- package/src/execution/tombstone-store.ts +0 -72
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// [H3/R3] RecordLifecycle 聚合(域 #4/#11/#17/#18:回收面 + close 三路 + cancel +
|
|
2
|
+
// finalize 簇)——自 SubagentService 上帝类 strangler 抽取的第三个聚合的**终态迁移
|
|
3
|
+
// 写面**(设计 docs/design/subagent-service-decomposition.md §2.1 / §3.3 D5;成员归属
|
|
4
|
+
// 以 r0-inventory.md 清单① + 域分区为准)。
|
|
5
|
+
//
|
|
6
|
+
// [计划变更 D-R3-1] G1「每聚合 ≤700 行」与 R0 八域体量(分区实测 833 物理行)冲突,
|
|
7
|
+
// 拆两文件(dev agent 停线报告、主 agent 核验追认):本文件(终态写面 #4/#11/#17/#18)
|
|
8
|
+
// + record-access.ts(读建面
|
|
9
|
+
// #3/#8/#10/#13)。两文件组间零互调(已验证——终态组方法只消费 deps + 组内互调),
|
|
10
|
+
// 各自独立 deps,壳分别装配——聚合间零 import(G2 / R1 打样模式 3)。
|
|
11
|
+
//
|
|
12
|
+
// 单一职责:record 的**终态迁移**——dispose 批量回收(含被否谱系 #3 的 onParentFork/
|
|
13
|
+
// onParentNew 编排性关闭 + GC timer)、close action 三路分流、cancel CAS 抢锁终态、
|
|
14
|
+
// finalize 簇(doFinalizeRecord 委托)。**本聚合 = store 与终态迁移入口的唯一宿主
|
|
15
|
+
// (D5,H4「意图级写操作」落点)**:H4 后续在本聚合接口面上收口持久化。
|
|
16
|
+
//
|
|
17
|
+
// [R1 打样模式——R3 落地](模式权威定义见 session-baselines.ts 文件头)
|
|
18
|
+
// 1. 依赖注入形态:deps 全晚绑定闭包(构造期零求值)。pi/会话基线运行时可变
|
|
19
|
+
// (initSession 注入 / 复活翻转),断言/调用时经壳 getter 现读;叶子模块实例(store/
|
|
20
|
+
// manifestStore/worktreeManager/modelService/notifyHost)为 #1 留壳共享依赖,getter
|
|
21
|
+
// 现读同一实例——深绑测试的 FR 替换语义保持。
|
|
22
|
+
// 2. 转发壳写法:壳保留同名方法(含原可见性)单行转发(D3 壳终态保留面:disposeAllRecords/
|
|
23
|
+
// onParentFork/onParentNew/startGcTimer/cancel/recoverManifestTmpFiles);聚合内部
|
|
24
|
+
// 互调(cancelBackground/closeChatIdle/finalizeRecord/promoteSessionFileFromEngineHandle/
|
|
25
|
+
// stopGcTimer)保持 private,不经壳。
|
|
26
|
+
// 3. 跨聚合边收敛(r0-inventory 清单① C-4/C-5/C-6):
|
|
27
|
+
// - C-5(onRecordFinalizedCleanup 跨域汇聚点,本体在壳 #14 Continuation 协作面):
|
|
28
|
+
// deps.onRecordFinalizedCleanup 回调——disposeAllRecords/cancelBackground 直调点
|
|
29
|
+
// 与 doFinalizeRecord deps.onFinalized 钩子闭包统一经此回调(现状显式注入形态
|
|
30
|
+
// 天然兼容,清单①预判兑现);R4 抽取 Continuation 协作面时回调改指聚合显式接口。
|
|
31
|
+
// - C-6(roundSupervisor/reconcile sweep 装配闭包调 finalizeRecord):闭包经壳
|
|
32
|
+
// late-bound 读取壳转发方法——天然兼容聚合化,壳装配零改动(清单①预判兑现)。
|
|
33
|
+
// - C-4(壳 dispose 直调 continuations.clear):#14 Continuation 状态清理,壳 dispose
|
|
34
|
+
// 编排消费——R4 领地,本单元留置不动(壳直调壳字段,非跨聚合写)。
|
|
35
|
+
// - closeSubagent 对 Continuation 队列的清空(continuations.get(...)?.abortAndClearQueue):
|
|
36
|
+
// deps.abortContinuationQueue 回调(同 C-5 邻接面,R4 改指聚合显式接口)。
|
|
37
|
+
// - cancelBackground 的 collectCoordinator.route(#5 SyncCollect 显式 getter 投影):
|
|
38
|
+
// deps.getCollectCoordinator() 晚绑定现读——聚合间经显式接口协作(route 是
|
|
39
|
+
// CollectCoordinator 公共方法),零私有互调。
|
|
40
|
+
// 4. 只搬不改:方法体除依赖注入通道替换外逐字节保留——本聚合是 H4 落点宿主,写点
|
|
41
|
+
// 通道表(r0-inventory 清单②)中 #4 的 promoteSessionFileFromEngineHandle(A 通道
|
|
42
|
+
// 唯一 R3 迁移项)+ store.archive(B)+ manifest/sidecar(D/E)原样随迁。
|
|
43
|
+
|
|
44
|
+
import { toErrorMessage } from "../../core/error-message.ts";
|
|
45
|
+
|
|
46
|
+
import { getLogger } from "../../core/logger.ts";
|
|
47
|
+
|
|
48
|
+
import { bestEffort } from "../best-effort.ts";
|
|
49
|
+
import type { CollectCoordinator } from "../collect-coordinator.ts";
|
|
50
|
+
import { completeRecord, tryTransition } from "../execution-record.ts";
|
|
51
|
+
import { killRecordChildWithEscalation } from "../engine/host/spawned-children.ts";
|
|
52
|
+
import { startIdleGc } from "../idle-gc.ts";
|
|
53
|
+
// [V2 决策 3] lifecycle-manager idle timer:chatMode record 的 disarm 面(终态化/取消
|
|
54
|
+
// 路径防误杀)。
|
|
55
|
+
import { disarmIdleTimer } from "../lifecycle-manager.ts";
|
|
56
|
+
import { isIdle, isResumable } from "../lifecycle-predicates.ts";
|
|
57
|
+
import { doFinalizeRecord } from "../finalize-record.ts";
|
|
58
|
+
import { FileRunStore } from "../../orchestration/file-run-store.ts";
|
|
59
|
+
import type { ModelConfigService } from "../model-config-service.ts";
|
|
60
|
+
import type { NotifyHost, PiLike } from "../notify-host.ts";
|
|
61
|
+
import type { RecordStore } from "../record-store.ts";
|
|
62
|
+
// [W4] 轮次活性监督器三态撤下 + settled watchdog disarm(终态路径防 timer 误触发)。
|
|
63
|
+
import { disarmRoundFromProtocol, disarmSettledWatchdog } from "../settled-watchdog.ts";
|
|
64
|
+
import { resolvePiWorkflowStateDir } from "../workflow-state-root.ts";
|
|
65
|
+
import type { WorktreeManager } from "../worktree-manager.ts";
|
|
66
|
+
import type { AgentResult, ClosedReason, ExecutionRecord } from "../types.ts";
|
|
67
|
+
|
|
68
|
+
const logger = getLogger("subagents");
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* [R1 打样模式 1] 聚合协作 deps——**全部晚绑定闭包,构造期零求值**。
|
|
72
|
+
*
|
|
73
|
+
* 窄结构类型只声明聚合真实消费的通道(不整实例注入)。跨域边三类:
|
|
74
|
+
* - 断言面(assertReady):close/cancel 入口的就绪门(本体在 SessionBaselines,壳转发)。
|
|
75
|
+
* - 跨域汇聚回调(onRecordFinalizedCleanup / abortContinuationQueue):#14 Continuation
|
|
76
|
+
* 协作面的终态清理与队列清空(C-5),壳装配指向壳方法;R4 抽取后改指聚合显式接口。
|
|
77
|
+
* - 显式接口协作(getCollectCoordinator):#5 SyncCollect 的公共投影(route 投递)。
|
|
78
|
+
*/
|
|
79
|
+
export interface RecordLifecycleDeps {
|
|
80
|
+
/** [D4 下沉] assertReady 断言(本体在 SessionBaselines,壳转发)。 */
|
|
81
|
+
readonly assertReady: () => void;
|
|
82
|
+
/** RecordStore(#1 留壳共享依赖;本聚合消费面:批量回收/终态 archive/GC 数据源)。 */
|
|
83
|
+
readonly getStore: () => RecordStore;
|
|
84
|
+
/** WorktreeManager(终态 worktree 绑定清理,fire-and-forget)。 */
|
|
85
|
+
readonly getWorktreeManager: () => WorktreeManager;
|
|
86
|
+
/** ModelConfigService(doFinalizeRecord FinalizeDeps 形参——独立模块签名要求具体类型)。 */
|
|
87
|
+
readonly getModelService: () => ModelConfigService;
|
|
88
|
+
/** NotifyHost(pending 注销 + closeChatIdle 终态通知)。 */
|
|
89
|
+
readonly getNotifyHost: () => NotifyHost;
|
|
90
|
+
/** subagent sessionDir(doFinalizeRecord FinalizeDeps.sessionDir——sessionFile 缺失时
|
|
91
|
+
* 磁盘 identity 反查依据;壳构造期同源推导)。 */
|
|
92
|
+
readonly getSessionsDir: () => string;
|
|
93
|
+
/** pi 句柄(manifest 写失败事件 appendEntry;initSession 时点晚绑定,dispose 后 null)。 */
|
|
94
|
+
readonly getPi: () => PiLike | null;
|
|
95
|
+
/** [C-5 显式回调] record 终态化路径的宿主侧收口汇聚点(#14 Continuation 实例清理,
|
|
96
|
+
* 本体在壳)。disposeAllRecords/cancelBackground 直调点 + doFinalizeRecord
|
|
97
|
+
* deps.onFinalized 钩子统一经此回调。 */
|
|
98
|
+
readonly onRecordFinalizedCleanup: (recordId: string) => void;
|
|
99
|
+
/** [C-5 邻接回调] Continuation 在途轮打断清队(#14 continuations Map 消费——原壳
|
|
100
|
+
* closeSubagent 内 continuations.get(id)?.abortAndClearQueue())。 */
|
|
101
|
+
readonly abortContinuationQueue: (recordId: string) => void;
|
|
102
|
+
/** [显式接口协作] #5 SyncCollect 的 collectCoordinator 公共投影(cancelBackground
|
|
103
|
+
* 终态 route 投递;晚绑定现读聚合 getter)。 */
|
|
104
|
+
readonly getCollectCoordinator: () => CollectCoordinator;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 域 #4/#11/#17/#18 聚合:record 终态迁移写面(R3 自 SubagentService 抽取)。
|
|
109
|
+
*
|
|
110
|
+
* 字段所有权(r0-inventory 清单①):#29 stopIdleGc(GC timer stop 句柄)——本聚合
|
|
111
|
+
* 唯一写者;壳经 startGcTimer/stopGcTimer 转发面触达,字段壳零感知。
|
|
112
|
+
*/
|
|
113
|
+
export class RecordLifecycle {
|
|
114
|
+
private readonly deps: RecordLifecycleDeps;
|
|
115
|
+
|
|
116
|
+
constructor(deps: RecordLifecycleDeps) {
|
|
117
|
+
this.deps = deps;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── 域 #4 回收面 ──
|
|
121
|
+
|
|
122
|
+
/** SP-4: 关闭所有活跃 record。
|
|
123
|
+
*
|
|
124
|
+
* 遍历 store 中所有 running record,逐个 CAS 转终态 + completeRecord + archive。
|
|
125
|
+
* 对有 worktreeHandle 的 record 触发 worktreeManager.cleanup(T3: worktree 绑定清理)。
|
|
126
|
+
*
|
|
127
|
+
* [T2⑥ / PS-1] 补齐三回收面(对照 dispose() 的既有形态,消除同文件双标):
|
|
128
|
+
* controller.abort + kill(收敛到 killChildWithEscalation)+ disarmIdleTimer +
|
|
129
|
+
* disarmSettledWatchdog。旧实现只关 record 不中止执行——「record 已关」≠「执行已
|
|
130
|
+
* 处置」:在途子进程继续跑且无任何用户可及的取消通道(cancel 只查内存 running,
|
|
131
|
+
* archive 后恒 false),若挂死唯一上界是默认关闭的 spawn watchdog → 泄漏至宿主退出。
|
|
132
|
+
* abort/kill/timer 三面对已终态/已死 record 均幂等 no-op,dispose() 先行的
|
|
133
|
+
* abortRunningControllers + killAllSpawnedChildren 不受影响(parent-shutdown 路径
|
|
134
|
+
* 双保险)。
|
|
135
|
+
*
|
|
136
|
+
* [v4 A-6] 旧实现的 recentlyCascaded 收集(供已删除的 before_agent_start 注入告知)
|
|
137
|
+
* 与 drainCascaded 已一并移除——被关 record 的告知改由 list 的 closedReason 表达。
|
|
138
|
+
*
|
|
139
|
+
* [M1 Gate B] 本路径曾整体缺 manifest 反查索引(不经 doFinalizeRecord 的唯一终态
|
|
140
|
+
* 写点缺口):优雅停机关掉的在途 record 重启后 list 不可见(records/<id>.json 不
|
|
141
|
+
* 存在)、message 报 not found——「展示层∪动作链」双失。[B3/D8] 终态化归口
|
|
142
|
+
* store.markFinalized 后 manifest 随终态原语落盘(manifestDir 接线后 writeSync 同步
|
|
143
|
+
* 完成于本同步函数体内,停机窗 fire-and-forget 竞态构造性消灭;生产构造点恒传
|
|
144
|
+
* manifestDir,缺省降级异步仅纯内存测试形态,SIGKILL 竞态丢失由
|
|
145
|
+
* recoverOrphanRecords 的可重连 entry 重物化自愈兜底)。
|
|
146
|
+
* 归口同时补齐 .state 权威写与 .alive release——重启后行为按 D8 矩阵五行
|
|
147
|
+
* (chat×fork-new 收紧硬拒 / one-shot×shutdown 放宽可续 / 纳管态降级手动 resurrect)。
|
|
148
|
+
*
|
|
149
|
+
* [M1 sessionFile 锚点提升] 在途 record 的 sessionFile 尚未回填(run 应答未到)时,
|
|
150
|
+
* R4 运行中句柄回填(onHandleReady → backfillEngineHandle)可能已把引擎上报的子
|
|
151
|
+
* session 文件路径写进 engineHandle.sessionRef——提升为 record.sessionFile 后,
|
|
152
|
+
* archive entry 与 manifest 都带上真实锚点,重启 revive/fork-from 可直接定位子文件。
|
|
153
|
+
* 提升源与 settle 回填(outcomeToAgentResult)同 authority(引擎 sessionRef)。
|
|
154
|
+
*
|
|
155
|
+
* @param reason 关闭原因(parent-fork / parent-new / parent-shutdown)
|
|
156
|
+
* @returns 被关闭的 record 数量
|
|
157
|
+
*/
|
|
158
|
+
disposeAllRecords(reason: ClosedReason): number {
|
|
159
|
+
const activeRecords = this.deps.getStore().listAllActive();
|
|
160
|
+
let count = 0;
|
|
161
|
+
for (const record of activeRecords) {
|
|
162
|
+
// [T2⑥/PS-1] 回收面 i:abort 在途 controller(排队的 acquire / 在途 signal listener
|
|
163
|
+
// 立即感知取消)。幂等:已 aborted 的 controller.abort() 是 no-op。
|
|
164
|
+
record.controller?.abort();
|
|
165
|
+
// 回收面 ii:回收子进程(SIGTERM + 30s SIGKILL 升级,收敛 T2④同款)。
|
|
166
|
+
// 回收面 iii:disarm idle timer + settled watchdog(进程回收后 timer 只会误触发)。
|
|
167
|
+
killRecordChildWithEscalation(record.id, `disposeAllRecords (${reason})`);
|
|
168
|
+
disarmIdleTimer(record.id);
|
|
169
|
+
disarmSettledWatchdog(record.id);
|
|
170
|
+
// closed 抢先的 record 由 continue 跳过(ExecutionStatus 两态后无第三态)。
|
|
171
|
+
// 与 closeChatIdle 对称:record 无在途 AgentResult,构造合成 result。
|
|
172
|
+
if (record.status === "running") {
|
|
173
|
+
if (!tryTransition(record, "closed", reason)) continue;
|
|
174
|
+
}
|
|
175
|
+
const result: AgentResult = {
|
|
176
|
+
text: "",
|
|
177
|
+
turns: record.turnCount,
|
|
178
|
+
durationMs: Date.now() - record.startedAt,
|
|
179
|
+
success: false,
|
|
180
|
+
error: `closed due to ${reason}`,
|
|
181
|
+
sessionId: record.id,
|
|
182
|
+
toolCalls: [],
|
|
183
|
+
};
|
|
184
|
+
completeRecord(record, result, "closed", reason);
|
|
185
|
+
// [M1 sessionFile 锚点提升] 先于终态原语——archive entry 与 manifest 随之携带锚点。
|
|
186
|
+
this.promoteSessionFileFromEngineHandle(record);
|
|
187
|
+
// [B3/D8] 终态化归口 markFinalized:原「不写 .state 不删 .alive + manifest
|
|
188
|
+
// fire-and-forget」升级为完整终态原语(.state writeSync 权威 + manifest + .alive
|
|
189
|
+
// 删,D8 v7 写序)——D8 行为变化矩阵五行生效点(chat×fork-new 硬拒收紧 /
|
|
190
|
+
// one-shot×shutdown 放宽可续 / 纳管态降级手动 resurrect,均已接受)。生产路径
|
|
191
|
+
// 恒 manifestDir 接线(subagent-service 构造点);缺省异步分支仅纯内存测试形态,
|
|
192
|
+
// 其余面不变。
|
|
193
|
+
const persisted = this.deps.getStore().markFinalized(record, reason);
|
|
194
|
+
if (!persisted) {
|
|
195
|
+
// [§3.4] .state 重试耗尽:响亮 entry 上报(record 磁盘留 running,boot 孤儿
|
|
196
|
+
// 恢复终态化承接);清理副作用继续——dispose 路径幂等且无重试机会。
|
|
197
|
+
logger.error(
|
|
198
|
+
`[subagents] disposeAllRecords: terminal state write failed after retries ` +
|
|
199
|
+
`(record=${record.id}, reason=${reason}); record stays running on disk — boot orphan recovery will finalize it`,
|
|
200
|
+
);
|
|
201
|
+
this.deps.getPi()?.appendEntry?.("subagent:state-write-failed", {
|
|
202
|
+
id: record.id,
|
|
203
|
+
status: "closed",
|
|
204
|
+
closedReason: reason,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// [F-5 修复] 本路径不经 doFinalizeRecord(编排性关闭直连 completeRecord+终态原语),
|
|
208
|
+
// chat 轮路由注销在此补齐(幂等;闭包持 record/stream 引用,防泄漏)。
|
|
209
|
+
// [H1 U2] 汇聚点扩为 onRecordFinalizedCleanup(路由注销 + Continuation 清理)。
|
|
210
|
+
this.deps.onRecordFinalizedCleanup(record.id);
|
|
211
|
+
// worktree 绑定清理(T3)。cleanup 已 async 化——同步签名(返回计数)不变,
|
|
212
|
+
// 清理 fire-and-forget:失败经 bestEffort 留痕,不阻塞/不影响计数返回。
|
|
213
|
+
if (record.worktreeHandle) {
|
|
214
|
+
void this.deps.getWorktreeManager().cleanup(record.worktreeHandle).catch((err: unknown) => {
|
|
215
|
+
bestEffort(err, `worktree cleanup (${reason})`);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// pending-notifications 注销
|
|
219
|
+
this.deps.getNotifyHost().emitPendingUnregister(record.id, "closed");
|
|
220
|
+
count++;
|
|
221
|
+
}
|
|
222
|
+
return count;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** SP-4: /fork 新 session 时清理旧 record。
|
|
226
|
+
* 调用 disposeAllRecords("parent-fork")。由 index.ts 的 session_before_fork handler 触发。 */
|
|
227
|
+
onParentFork(): number {
|
|
228
|
+
return this.disposeAllRecords("parent-fork");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** SP-4: /new 创建全新 session 时清理旧 record。
|
|
232
|
+
* 调用 disposeAllRecords("parent-new")。由 index.ts 的 session_before_switch
|
|
233
|
+
* (reason==="new")handler 触发。 */
|
|
234
|
+
onParentNew(): number {
|
|
235
|
+
return this.disposeAllRecords("parent-new");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* [M1 Gate B] sessionFile 锚点提升:record.sessionFile 未回填(run 应答未到)但 R4
|
|
240
|
+
* 运行中句柄回填已把引擎上报的子 session 文件路径写进 engineHandle.sessionRef 时,
|
|
241
|
+
* 提升为 record.sessionFile。提升源与 settle 回填(outcomeToAgentResult /
|
|
242
|
+
* finalizeEngineOutcome)同 authority(引擎 sessionRef.sessionFile)。
|
|
243
|
+
*
|
|
244
|
+
* 消费方:disposeAllRecords / cancelBackground 的终态化——让 archive entry 与
|
|
245
|
+
* manifest 带真实锚点,重启后 revive/fork-from 可定位子文件。无 engineHandle 或
|
|
246
|
+
* sessionRef 无 sessionFile(handle_ready 前就停机的残余形态)保持 undefined——
|
|
247
|
+
* 该子形态无确定性恢复路径(子文件名 `<ts>_<sessionId>.jsonl` 由子进程生成,
|
|
248
|
+
* 宿主无记录映射),恢复语义降级为「manifest 可见 + fork-from/start 指引」。
|
|
249
|
+
*/
|
|
250
|
+
private promoteSessionFileFromEngineHandle(record: ExecutionRecord): void {
|
|
251
|
+
if (record.sessionFile !== undefined) return;
|
|
252
|
+
const sessionFile = record.engineHandle?.sessionRef.sessionFile;
|
|
253
|
+
if (typeof sessionFile !== "string" || sessionFile === "") return;
|
|
254
|
+
record.sessionFile = sessionFile;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** SP-4: idle record GC(30 天 TTL,实现抽至 idle-gc.ts)。stop 函数(dispose 调)。 */
|
|
258
|
+
private stopIdleGc: (() => void) | undefined;
|
|
259
|
+
|
|
260
|
+
/** 启动 idle record GC 定时器(session_start 调用,幂等)。
|
|
261
|
+
* [W4] WorkflowRun store(FileRunStore)同批纳入:running 且 startedAt 超 30 天
|
|
262
|
+
* 锚窗的 run 终态化归档(只终态化不补注销,见 idle-gc.ts 头注)。宿主未
|
|
263
|
+
* configureCore 时 loadAll 抛错由 idle-gc 内部吞掉(单轮跳过)。
|
|
264
|
+
* [F-1 修复] stateDir 与 pi 壳 JsonlRunStore 落盘布局同源
|
|
265
|
+
* (resolvePiWorkflowStateDir → <sessionDir>/workflow-state/)——缺省 dataRoot 根
|
|
266
|
+
* 与 pi 生产落盘不相交,WorkflowRun GC 曾恒空转(W4 引入的装配错位)。 */
|
|
267
|
+
startGcTimer(): void {
|
|
268
|
+
if (this.stopIdleGc) return;
|
|
269
|
+
this.stopIdleGc = startIdleGc(this.deps.getStore(), new FileRunStore({ stateDir: resolvePiWorkflowStateDir() }));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** 停止 idle record GC 定时器(dispose 调用)。 */
|
|
273
|
+
stopGcTimer(): void {
|
|
274
|
+
this.stopIdleGc?.();
|
|
275
|
+
this.stopIdleGc = undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── 域 #11 close 三路 ──
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* close action 的统一行为分流(running 子态 × force)。
|
|
282
|
+
*
|
|
283
|
+
* chatMode([H1 U2 / D4] close 行统一):
|
|
284
|
+
* force:true → cancelBackground(显式 SIGTERM + closed+cancelled 终态)
|
|
285
|
+
* force:false(任意子态) → abort 在途轮 + 清空队列(Continuation)+ 立即终态化
|
|
286
|
+
* (closeChatIdle:closed/user-close + notifyClosed——「closeAfterRound 等轮终」
|
|
287
|
+
* 挂起标志随 chat 域长驻消亡退役:D4 close = abort + 清队列 + 立即终态化,
|
|
288
|
+
* 不等轮终;S7 无僵尸轮、无 close 后追加通知由 onRunSettled 终态守卫构造性保证)
|
|
289
|
+
* 非 chatMode(one-shot,现状不变):
|
|
290
|
+
* running + force:false + 无在跑轮 → closeChatIdle(立即终态化)
|
|
291
|
+
* running + force:false + 有活进程 → 置 closeAfterRound=true(settleOneShotOutcome 消费——照旧)
|
|
292
|
+
* 其他终态 → 幂等 no-op(已结束)
|
|
293
|
+
*
|
|
294
|
+
* 与设计决策 5 一致:close = 正式终态(走 finalize),force 只影响 running 时机。
|
|
295
|
+
*
|
|
296
|
+
* @param record 目标 record(getRecordForAction 已校验归属)
|
|
297
|
+
* @param force true=立即终止(running 时 SIGTERM)/ false=优雅关闭
|
|
298
|
+
*/
|
|
299
|
+
async closeSubagent(record: ExecutionRecord, force: boolean): Promise<void> {
|
|
300
|
+
this.deps.assertReady();
|
|
301
|
+
if (record.status === "running") {
|
|
302
|
+
if (force) {
|
|
303
|
+
// 立即终止:cancelBackground(controller.abort + tryTransition closed+cancelled + finalize)
|
|
304
|
+
this.cancelBackground(record);
|
|
305
|
+
} else if (record.chatMode) {
|
|
306
|
+
// [H1 U2 / D4] chat close:abort 在途轮(幂等——无在途轮 no-op)+ 清空队列,
|
|
307
|
+
// 随即立即终态化(closeChatIdle 收口——closeAfterRound 挂起标志在 chat 域退役,
|
|
308
|
+
// 不再有「轮完成时终态化」的等待窗;[H1 U6] 消费点 closeAfterRoundSettled 已删,
|
|
309
|
+
// S7 无僵尸轮、无 close 后追加通知由 onRunSettled 终态守卫构造性保证)。
|
|
310
|
+
this.deps.abortContinuationQueue(record.id);
|
|
311
|
+
await this.closeChatIdle(record);
|
|
312
|
+
} else if (isIdle(record) || isResumable(record)) {
|
|
313
|
+
// [M5] 无在跑轮:Path A(isIdle timer armed、进程保活等待续聊)/ Path B(isResumable
|
|
314
|
+
// 无活进程)→ 立即终态化 done(closeChatIdle 内回收保活进程 + disarm timer)。
|
|
315
|
+
await this.closeChatIdle(record);
|
|
316
|
+
} else {
|
|
317
|
+
// 优雅关闭:正在执行(有活进程在跑轮),标记 closeAfterRound,轮完成时终态化
|
|
318
|
+
//(one-shot 消费点在 settleOneShotOutcome CAS 分支——照旧,G3)
|
|
319
|
+
record.closeAfterRound = true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// 其他终态(closed):幂等 no-op
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 无在跑轮 record 的手动终态化为 done(close action 的 isIdle/isResumable 分支)。
|
|
327
|
+
*
|
|
328
|
+
* 无在途 AgentResult(轮次完成时 record 未冻结,turns[] 保留运行时状态),
|
|
329
|
+
* 构造合成 done result(对齐 cancelBackground 的 cancelledResult 模式)。
|
|
330
|
+
* 走 doFinalizeRecord 的完整终态化路径(completeRecord + archive + finalized + worktree
|
|
331
|
+
* cleanup + alive marker + manifest)。
|
|
332
|
+
*
|
|
333
|
+
* [M5] 覆盖两路:Path B(无活进程,同旧行为)与 Path A(idle timer armed、进程保活等待
|
|
334
|
+
* 续聊)。Path A 必须先显式回收进程 + disarm timer——否则 record 已终态化但保活进程
|
|
335
|
+
* 继续驻留(终态后无人再杀它:closeSubagent 不再来、idle timer 已 disarm、runSpawn
|
|
336
|
+
* promise 早已 resolve),直到宿主进程退出。
|
|
337
|
+
*
|
|
338
|
+
* 不走 tryTransition(v4 B-1 此态 status=running,但由 doFinalizeRecord 内部的
|
|
339
|
+
* completeRecord 直接覆盖 status,与 cancelBackground 对 record 的处理同构)。
|
|
340
|
+
*/
|
|
341
|
+
async closeChatIdle(record: ExecutionRecord): Promise<void> {
|
|
342
|
+
// [M5] Path A:回收保活进程 + disarm idle timer(终态化后无其他 kill 路径)。
|
|
343
|
+
// [T2④ / LC-2] 终止语义收敛:SIGTERM 被无视时 30s 升级 SIGKILL——终态化后
|
|
344
|
+
// record 已 archive,挂住 = 幽灵进程无任何后续回收通道。同时 disarm settled
|
|
345
|
+
// watchdog(本路径终态化 = settled 等待窗口终结,防 watchdog 误杀后续同 id 资源)。
|
|
346
|
+
// [W3] 实际终止在引擎进程内(协议 interact close force → 引擎侧杀链);
|
|
347
|
+
// killRecordChildWithEscalation 只做镜像置死记账。
|
|
348
|
+
disarmIdleTimer(record.id);
|
|
349
|
+
disarmSettledWatchdog(record.id);
|
|
350
|
+
disarmRoundFromProtocol(record.id);
|
|
351
|
+
killRecordChildWithEscalation(record.id, "closeChatIdle");
|
|
352
|
+
// 合成 closed result(无在途 AgentResult,`record.result ?? ""` 模式)。
|
|
353
|
+
// [W16 P-1 修复] text 必须沿用轮终真实 result:
|
|
354
|
+
// completeRecord 会执行 record.result = result.text,合成空串会把轮终真实值抹空,
|
|
355
|
+
// archive 落的 close 终态 subagent-record entry(D4 重建源)随之失真——重开
|
|
356
|
+
// session 后 result 回退空串。
|
|
357
|
+
const doneResult: AgentResult = {
|
|
358
|
+
text: record.result ?? "",
|
|
359
|
+
turns: record.turnCount,
|
|
360
|
+
durationMs: Date.now() - record.startedAt,
|
|
361
|
+
success: true,
|
|
362
|
+
sessionId: record.id,
|
|
363
|
+
toolCalls: [],
|
|
364
|
+
};
|
|
365
|
+
await doFinalizeRecord(
|
|
366
|
+
{
|
|
367
|
+
worktreeManager: this.deps.getWorktreeManager(),
|
|
368
|
+
store: this.deps.getStore(),
|
|
369
|
+
modelService: this.deps.getModelService(),
|
|
370
|
+
pi: this.deps.getPi(),
|
|
371
|
+
emitUnregister: (id, st) => this.deps.getNotifyHost().emitPendingUnregister(id, st),
|
|
372
|
+
// [F-5 修复] 同 finalizeRecord——chat 轮路由注销单一汇聚点钩子
|
|
373
|
+
//([H1 U2] 汇聚点扩为 onRecordFinalizedCleanup:路由注销 + Continuation 清理)。
|
|
374
|
+
onFinalized: (id) => this.deps.onRecordFinalizedCleanup(id),
|
|
375
|
+
sessionDir: this.deps.getSessionsDir(),
|
|
376
|
+
},
|
|
377
|
+
record,
|
|
378
|
+
doneResult,
|
|
379
|
+
"closed",
|
|
380
|
+
"user-close", // close action 主动关闭
|
|
381
|
+
);
|
|
382
|
+
// [C-1] 终态通知(设计 D2 路径②):正文空串占位 + sessionFile 指针行(idle 下
|
|
383
|
+
// 末轮增量已由该轮轮次通知送达,终态再发属重复)。doneResult.text 已改保真
|
|
384
|
+
// (P-1 修复),正文空由 notifyClosed 的 emptyBody 参数显式表达。
|
|
385
|
+
// dedup 身份独立于轮次通知(notifyClosed 置 round=undefined),60s 窗内不被吞。
|
|
386
|
+
// 防重入:closeSubagent 对 closed record 幂等 no-op,本路径不会被二次进入。
|
|
387
|
+
this.deps.getNotifyHost().notifyClosed(record, true);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// [H1 U6] closeAfterRoundSettled([M5] chat 域「轮完成时终态化」消费面)已随 chat 域
|
|
391
|
+
// closeAfterRound 挂起标志退役删除:D4 close = abort 在途 + 清空队列 + 立即终态化
|
|
392
|
+
//(closeChatIdle),不等轮终;one-shot 域的 closeAfterRound 消费走 consumeCloseAfterRound
|
|
393
|
+
//(settleOneShotOutcome,照旧)。
|
|
394
|
+
|
|
395
|
+
// ── 域 #17 取消 ──
|
|
396
|
+
|
|
397
|
+
/** 取消 background record(tryTransition CAS 抢锁防重复副作用)。 */
|
|
398
|
+
cancel(id: string): boolean {
|
|
399
|
+
this.deps.assertReady();
|
|
400
|
+
const record = this.deps.getStore().getMutable(id);
|
|
401
|
+
if (!record) return false;
|
|
402
|
+
return this.cancelBackground(record);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 取消 background record。CAS 抢锁(tryTransition)——抢到则 notify + 写 tombstone;
|
|
407
|
+
* 没抢到(detached 已 finalize,record 已终态)返回 false,不触碰任何收尾副作用。
|
|
408
|
+
*
|
|
409
|
+
* stop 手段(abort/kill/disarm)无条件先执行:对已终态 record 幂等无害,且保证
|
|
410
|
+
* cancel 语义 = 进程必死;收尾副作用(completeRecord/tombstone/archive/notify)只归
|
|
411
|
+
* CAS 赢家。[A2-1] 此前 CAS 被误删,cancel 可在 doFinalizeRecord Step 0 await 窗口
|
|
412
|
+
* 命中已终态 record——覆写终态 + tombstone/finalized 双标 + notify 双发 + 谎报 true。
|
|
413
|
+
*/
|
|
414
|
+
private cancelBackground(record: ExecutionRecord): boolean {
|
|
415
|
+
record.controller?.abort();
|
|
416
|
+
// [M6] 显式 kill + disarm:chatMode 首轮 agent_settled 后 runSpawn 提前 resolveRun(0)
|
|
417
|
+
// 返回,`opts.signal.removeEventListener("abort", onAbort)`(session-runner runSpawn 尾部)
|
|
418
|
+
// 已移除 abort→kill listener;热路径续聊轮(PiEngine 直接 stdin 写入)不再
|
|
419
|
+
// 进 runSpawn。此后 cancel 只有 controller.abort() 无人响应——record 已终态化 cancelled
|
|
420
|
+
// 但子进程继续跑完当前 turn(工具副作用继续发生),之后 agent_settled 还对已 archived
|
|
421
|
+
// record 触发脏通知(round+1 → 新 dedup key → "finished a round"),最终靠 5min idle
|
|
422
|
+
// timer 兜底 kill。故 cancel 必须显式 kill + disarm idle timer。非 chatMode 路径
|
|
423
|
+
// listener 仍在(abort 已 kill 一次),此处对已 killed child 是 no-op,无副作用。
|
|
424
|
+
// [T2④ / LC-2] 裸 SIGTERM 收敛到 killRecordChildWithEscalation:SIGTERM 被无视时
|
|
425
|
+
// 30s 升级 SIGKILL——cancel 语义 = 进程必死,不能赌 SIGTERM 生效(record 已终态化,
|
|
426
|
+
// 此后无其他回收通道)。settled watchdog 同步撤下(等待窗口随取消终结,幂等)。
|
|
427
|
+
// [W3 → H1 U6] 在途 run 的实际终止经 controller.abort → 轮级 signal(RemoteEngine
|
|
428
|
+
// cancel 分级:cancel 帧 → grace → 杀链);[H1 U6] 旧 interact cancel 通道随
|
|
429
|
+
// interact 面退役。
|
|
430
|
+
killRecordChildWithEscalation(record.id, "cancelBackground");
|
|
431
|
+
disarmIdleTimer(record.id);
|
|
432
|
+
disarmSettledWatchdog(record.id);
|
|
433
|
+
disarmRoundFromProtocol(record.id);
|
|
434
|
+
this.deps.onRecordFinalizedCleanup(record.id);
|
|
435
|
+
// [A2-1] CAS 抢锁防重复收尾:running 才放行。doFinalizeRecord Step 0 await 窗口内
|
|
436
|
+
// record 可能已被终态化(closed)但尚未 archive——没抢到说明 detached 已 finalize,
|
|
437
|
+
// cancel 来晚了,闭嘴返回 false(completeRecord 会覆写终态 + tombstone/finalized
|
|
438
|
+
// 双标 + notify 双发,绝不可执行)。
|
|
439
|
+
if (!tryTransition(record, "closed", "cancelled")) {
|
|
440
|
+
return false; // detached 已 finalize,cancel 来晚了
|
|
441
|
+
}
|
|
442
|
+
// 抢到锁:completeRecord(用空 result 填 cancelled)+ 终态原语(archive + notify)。
|
|
443
|
+
// cancelled 终态靠 sidecar 标记(session.jsonl 被 abort 截断),collectRecords 重建时
|
|
444
|
+
// override status=cancelled。durationMs 用真实耗时(startedAt → now)。
|
|
445
|
+
const cancelledResult: AgentResult = { text: "", turns: record.turnCount, durationMs: Date.now() - record.startedAt, success: false, error: "cancelled by user", sessionId: record.id, toolCalls: [] };
|
|
446
|
+
completeRecord(record, cancelledResult, "closed", "cancelled");
|
|
447
|
+
// [M1 sessionFile 锚点提升] 先于终态原语——spawn 窗口期 cancel 的 record
|
|
448
|
+
// 经 engineHandle 提升后,sidecar 与 manifest 都能落在真实子文件上。
|
|
449
|
+
this.promoteSessionFileFromEngineHandle(record);
|
|
450
|
+
// [B2] 终态写面归口 store.markCancelled:.state writeSync(cancelled + 精确 endedAt)+
|
|
451
|
+
// 终态 usage binding 快照 + archive + manifest(closedReason=cancelled 让
|
|
452
|
+
// endedMessageGuard 走「主动关闭」专属文案——sessionFile 缺失形态的重启可见性由此
|
|
453
|
+
// 承接,Gate B sq-c)+ .alive 删(release 出口①)。
|
|
454
|
+
const persisted = this.deps.getStore().markCancelled(record);
|
|
455
|
+
if (!persisted) {
|
|
456
|
+
// [§3.4] .state 重试耗尽:响亮 entry 上报(record 磁盘留 running,boot 孤儿恢复
|
|
457
|
+
// 终态化承接);cancel 副作用继续——进程已死、CAS 已抢锁,通知与清理不可丢。
|
|
458
|
+
logger.error(
|
|
459
|
+
`[subagents] cancelBackground: terminal state write failed after retries ` +
|
|
460
|
+
`(record=${record.id}); record stays running on disk — boot orphan recovery will finalize it`,
|
|
461
|
+
);
|
|
462
|
+
this.deps.getPi()?.appendEntry?.("subagent:state-write-failed", {
|
|
463
|
+
id: record.id,
|
|
464
|
+
status: "closed",
|
|
465
|
+
closedReason: "cancelled",
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
// worktree cleanup(终态 sidecar 单文件单状态,无互斥清理需求)。
|
|
469
|
+
// cleanup 已 async 化——boolean 同步返回语义不变,清理 fire-and-forget。
|
|
470
|
+
if (record.worktreeHandle) {
|
|
471
|
+
void this.deps.getWorktreeManager().cleanup(record.worktreeHandle).catch((err: unknown) => {
|
|
472
|
+
bestEffort(err, "worktree cleanup (cancelBackground)");
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
// pending-notifications:cancel 注销(只记 registry 状态)
|
|
476
|
+
this.deps.getNotifyHost().emitPendingUnregister(record.id, "closed");
|
|
477
|
+
// cancel 完成通知(与轮次收尾 .then 对称——cancel 抢先时 .then 跳过 notify)
|
|
478
|
+
this.deps.getCollectCoordinator().route(record);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ── 域 #18 finalize 簇(H4 落点 D5)──
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* D-017 时序收尾:委托 doFinalizeRecord(提取到 finalize-record.ts,降低本文件行数)。
|
|
486
|
+
* [Critical #1] cleanup 不因写失败被跳过(写失败在 Step 2 已响亮上报,cleanup 继续;
|
|
487
|
+
* 详见 finalize-record.ts)。 */
|
|
488
|
+
async finalizeRecord(
|
|
489
|
+
record: ExecutionRecord,
|
|
490
|
+
result: AgentResult,
|
|
491
|
+
status: "closed",
|
|
492
|
+
closedReason?: ClosedReason,
|
|
493
|
+
): Promise<void> {
|
|
494
|
+
await doFinalizeRecord(
|
|
495
|
+
{
|
|
496
|
+
worktreeManager: this.deps.getWorktreeManager(),
|
|
497
|
+
store: this.deps.getStore(),
|
|
498
|
+
modelService: this.deps.getModelService(),
|
|
499
|
+
pi: this.deps.getPi(),
|
|
500
|
+
emitUnregister: (id, st) => this.deps.getNotifyHost().emitPendingUnregister(id, st),
|
|
501
|
+
// [F-5 修复] 同 closeChatIdle——chat 轮路由注销单一汇聚点钩子
|
|
502
|
+
//([H1 U2] 汇聚点扩为 onRecordFinalizedCleanup:路由注销 + Continuation 清理)。
|
|
503
|
+
onFinalized: (id) => this.deps.onRecordFinalizedCleanup(id),
|
|
504
|
+
sessionDir: this.deps.getSessionsDir(),
|
|
505
|
+
},
|
|
506
|
+
record,
|
|
507
|
+
result,
|
|
508
|
+
status,
|
|
509
|
+
closedReason,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** run() 创建期异常的收尾(H1 修复):createAndConfigureSession 失败会抛,本方法合成 failed
|
|
514
|
+
* AgentResult → CAS 抢锁 → finalizeRecord(与正常路径同形)。返回合成 result 供 runAndFinalize
|
|
515
|
+
* 继续返回(不 re-throw,swallow 策略)。
|
|
516
|
+
* [W3 契约变更⑤(run 期失败清理前置副作用)] kickOffEngineRun 前已建的 worktree
|
|
517
|
+
* (executeViaEngine 创建点,record.worktreeHandle 已绑定)经本方法 → finalizeRecord
|
|
518
|
+
* → doFinalizeRecord Step 3b cleanupWorktreeIfBound 清理(manifest 多声明 run 期
|
|
519
|
+
* 失败 / engine.run prepare 期 reject 共用本收尾链);CAS 没抢锁(cancel 抢先终态)
|
|
520
|
+
* 时由 cancelBackground 的 worktree cleanup 覆盖。唯一前置副作用 = worktree(并发
|
|
521
|
+
* 池槽 acquire/release 在 kickOffEngineRun finally 内自回收,journal 是宿主数据不清理)。 */
|
|
522
|
+
async finalizeFailed(record: ExecutionRecord, err: unknown): Promise<AgentResult> {
|
|
523
|
+
const errMsg = toErrorMessage(err);
|
|
524
|
+
// durationMs 用真实耗时(startedAt → now),避免失败统计恒为 0 失真。
|
|
525
|
+
const failedResult: AgentResult = { text: "", turns: record.turnCount, durationMs: Date.now() - record.startedAt, success: false, error: errMsg, sessionId: record.id, toolCalls: [] };
|
|
526
|
+
// CAS 抢锁:抢到(status 仍 running)则完整收尾;没抢到(cancel 已先设 cancelled)跳过。
|
|
527
|
+
// SP-1: failed → closed + gc(通用失败终态)。
|
|
528
|
+
if (tryTransition(record, "closed", "gc")) {
|
|
529
|
+
await this.finalizeRecord(record, failedResult, "closed", "gc");
|
|
530
|
+
}
|
|
531
|
+
return failedResult;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** S1: 排队中被 abort 走 cancelled 终态(对齐已运行被 abort 的 cancelBackground)。 */
|
|
535
|
+
async finalizeAborted(record: ExecutionRecord): Promise<AgentResult> {
|
|
536
|
+
const cancelledResult: AgentResult = { text: "", turns: record.turnCount, durationMs: Date.now() - record.startedAt, success: false, error: "cancelled by user", sessionId: record.id, toolCalls: [] };
|
|
537
|
+
if (tryTransition(record, "closed", "cancelled")) {
|
|
538
|
+
await this.finalizeRecord(record, cancelledResult, "closed", "cancelled");
|
|
539
|
+
}
|
|
540
|
+
return cancelledResult;
|
|
541
|
+
}
|
|
542
|
+
}
|