@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.
Files changed (176) hide show
  1. package/dist/{chunk-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
  2. package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
  3. package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
  4. package/dist/execution/engine/engine-discovery-scan.cjs +325 -154
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/execution/relay-env.js +5 -4
  9. package/dist/index.cjs +9799 -7954
  10. package/dist/index.d.cts +1896 -1360
  11. package/dist/index.d.ts +1896 -1360
  12. package/dist/index.js +8952 -7513
  13. package/dist.bundle/index.cjs +7897 -6054
  14. package/package.json +5 -5
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/record-store-last-line.test.ts +12 -0
  17. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  18. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  19. package/src/core/logger.ts +1 -1
  20. package/src/execution/__tests__/alive-store.test.ts +117 -1
  21. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  22. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  23. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  24. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  25. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  26. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  27. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  28. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  29. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  30. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  31. package/src/execution/__tests__/execution-record.test.ts +8 -148
  32. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  33. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  34. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  35. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  36. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  37. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  38. package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
  39. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  40. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  41. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  42. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  43. package/src/execution/__tests__/nested-visibility.test.ts +13 -1
  44. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  45. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  46. package/src/execution/__tests__/record-binding.test.ts +613 -0
  47. package/src/execution/__tests__/record-origin.test.ts +318 -0
  48. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  49. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  50. package/src/execution/__tests__/record-store.test.ts +72 -54
  51. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  52. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  53. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  54. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  55. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  56. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  57. package/src/execution/__tests__/state-marker.test.ts +288 -0
  58. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  59. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  60. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  61. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  62. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  63. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  64. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  65. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  66. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  67. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  68. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  69. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  70. package/src/execution/alive-store.ts +37 -28
  71. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  72. package/src/execution/config.ts +1 -1
  73. package/src/execution/conversation-continuation.ts +541 -0
  74. package/src/execution/dialog-queue.ts +60 -12
  75. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  76. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  77. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  78. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  79. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  80. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  81. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
  82. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  83. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  84. package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
  85. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  86. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  87. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  88. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  89. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  90. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  91. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  92. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  93. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  94. package/src/execution/engine/client/engine-client.ts +111 -26
  95. package/src/execution/engine/client/mirror.ts +9 -0
  96. package/src/execution/engine/client/remote-engine.ts +52 -34
  97. package/src/execution/engine/client/reverse-router.ts +6 -27
  98. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  99. package/src/execution/engine/common/capability-gate.ts +27 -0
  100. package/src/execution/engine/common/data-dir.ts +3 -2
  101. package/src/execution/engine/common/errors.ts +1 -1
  102. package/src/execution/engine/common/journal-wiring.ts +18 -9
  103. package/src/execution/engine/common/run-signals.ts +83 -0
  104. package/src/execution/engine/d8-compat.ts +1 -1
  105. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  106. package/src/execution/engine/host/host-bridge.ts +26 -41
  107. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  108. package/src/execution/engine/host/spawned-children.ts +1 -1
  109. package/src/execution/engine/inflight-snapshot.ts +92 -0
  110. package/src/execution/engine/port.ts +52 -54
  111. package/src/execution/engine/types.ts +11 -14
  112. package/src/execution/execution-record.ts +15 -47
  113. package/src/execution/finalize-record.ts +94 -197
  114. package/src/execution/idle-gc.ts +9 -3
  115. package/src/execution/lifecycle-manager.ts +25 -361
  116. package/src/execution/lifecycle-predicates.ts +22 -19
  117. package/src/execution/manifest-store.ts +40 -42
  118. package/src/execution/model-config-service.ts +1 -1
  119. package/src/execution/notifier.ts +39 -1
  120. package/src/execution/notify-host.ts +13 -4
  121. package/src/execution/path-encoding.ts +6 -0
  122. package/src/execution/record-entry.ts +20 -2
  123. package/src/execution/record-store.ts +925 -160
  124. package/src/execution/relay-env.ts +8 -3
  125. package/src/execution/round-supervisor/domain.ts +17 -5
  126. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  127. package/src/execution/round-supervisor/service-binding.ts +71 -21
  128. package/src/execution/round-supervisor/supervisor.ts +23 -9
  129. package/src/execution/service/record-access.ts +547 -0
  130. package/src/execution/service/record-lifecycle.ts +542 -0
  131. package/src/execution/service/run-orchestration.ts +1647 -0
  132. package/src/execution/service/service-bootstrap.ts +111 -0
  133. package/src/execution/service/service-constants.ts +27 -0
  134. package/src/execution/service/session-baselines.ts +401 -0
  135. package/src/execution/service/sync-collect-domain.ts +442 -0
  136. package/src/execution/service/workflow-dispatch.ts +567 -0
  137. package/src/execution/session-file-gc.ts +11 -8
  138. package/src/execution/session-reconstructor.ts +44 -1
  139. package/src/execution/sessions-index.ts +20 -3
  140. package/src/execution/settled-watchdog.ts +86 -43
  141. package/src/execution/state-marker.ts +516 -0
  142. package/src/execution/stream-sink.ts +1 -1
  143. package/src/execution/subagent-actions-core.ts +53 -31
  144. package/src/execution/subagent-service.ts +643 -2955
  145. package/src/execution/subprocess-agent-runner.ts +46 -323
  146. package/src/execution/types.ts +60 -43
  147. package/src/execution/ui-request-handler-factory.ts +5 -0
  148. package/src/execution/worktree-manager.ts +1 -1
  149. package/src/execution/worktree-registry.ts +12 -8
  150. package/src/index.ts +16 -5
  151. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  152. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  153. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  154. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  155. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  156. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  157. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  158. package/src/orchestration/execute-agent-call.ts +1 -1
  159. package/src/orchestration/file-run-store.ts +1 -1
  160. package/src/orchestration/models/ports.ts +20 -13
  161. package/src/orchestration/models/types.ts +11 -13
  162. package/src/orchestration/run-snapshot.ts +18 -22
  163. package/src/orchestration/worker-message-pump.ts +33 -57
  164. package/src/shared/atomic-write.ts +10 -8
  165. package/dist/chunk-T2SYBW3I.js +0 -24
  166. package/dist/engine-discovery-scan-ocpM8FmI.d.cts +0 -1608
  167. package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
  168. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  169. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  170. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  171. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  172. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  173. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  174. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  175. package/src/execution/finalized-marker.ts +0 -70
  176. package/src/execution/tombstone-store.ts +0 -72
@@ -0,0 +1,567 @@
1
+ // [H3/R4] WorkflowDispatch 聚合(域 #14 的 H2 workflow 族:executeWorkflowAgent +
2
+ // runWorkflowEngineTask + 类外派发 helper 整段)——自 SubagentService 上帝类 strangler
3
+ // 抽取的第五个聚合文件(设计 docs/design/subagent-service-decomposition.md §2.1 域 #14
4
+ // 增项 / impl-plan §2 R4 行「H2 workflow 族整段随族迁入」)。
5
+ //
6
+ // [G1 超限预授权拆分 / 偏差 D-R4-1] 主 agent 派发预授权:R4 域段体量大(派发估算
7
+ // 1200+ 物理行),按内聚边界拆两个文件——本文件(workflow 族独立)+ run-orchestration.
8
+ // ts(核心编排)。**实测与派发估算不符**:R0 重排后域段实测 1662 物理行 > 两文件容量
9
+ // 上限(2×700),run-orchestration.ts 物理超限不可避免(偏差登记待主 agent 追认;
10
+ // 备选方案 = 第三文件再拆 Continuation 协作面)。两文件零互调零 import(G2 / R1 打样
11
+ // 模式 3):跨文件协作经壳 deps 回调编排(acquirePoolOrFinalize / settleOneShotOutcome
12
+ // / outcomeToAgentResult / releaseRoundResources / resolveChatEnginePort /
13
+ // assertIdleTimeoutMsSafe——壳装配闭包指 run-orchestration 实例方法)。
14
+ //
15
+ // 单一职责:workflow 脚本 agent() 派发链(H2 W2 八步迁移的 service 侧承接)——
16
+ // AgentCallOpts 进站映射、引擎路由 + 预检、origin:"workflow" record 注册、journal /
17
+ // no-progress 守护 / signal 合流 / spawned-children 治理、D7 成功即终态化。与手动
18
+ // subagent 共享的编排原语(池槽 / 终态收口)经 deps 回调回流 RunOrchestration。
19
+ //
20
+ // [R1 打样模式——R4 落地](模式权威定义见 session-baselines.ts 文件头)
21
+ // 1. 依赖注入形态:deps 全晚绑定闭包(构造期零求值)——execNesting / streamSink /
22
+ // sessionRootId 等会话基线运行时可变态经壳 getter 现读;modelService /
23
+ // roundSupervisor 等 #1 留壳共享依赖 getter 现读同一实例。
24
+ // 2. 模块常量 SSOT:PRIORITY_BACKGROUND / MS_PER_SECOND / SECONDS_PER_MINUTE 已
25
+ // [R6/D-R4-4] 归一常量叶子文件 service-constants.ts(原两聚合重复声明消除,
26
+ // 改 import 消费)。
27
+ // 3. 只搬不改:两方法 + 类外 5 helper 自壳文件迁移,方法体除依赖通道替换
28
+ // (this.X → this.deps.getY())外逐字节保留(审计 /tmp/r4-move-audit.py)。
29
+
30
+ import { getLogger } from "../../core/logger.ts";
31
+
32
+ import type { AgentResult as WorkflowAgentResult, AgentCallOpts } from "../../orchestration/models/types.ts";
33
+ import { SLUG_MAX_LENGTH } from "../../orchestration/models/types.ts";
34
+ import { mapToWorkflowAgentResult } from "../agent-result-mapper.ts";
35
+ import { updateFromEvent } from "../execution-record.ts";
36
+ import { assertTaskShapeSupported } from "../engine/common/capability-gate.ts";
37
+ import { JOURNAL_INITIAL_POOL_KEY, wireEventJournal } from "../engine/common/journal-wiring.ts";
38
+ import type { ExecutionNestingContext } from "../engine/common/nesting-guard.ts";
39
+ // [H2 W2 迁移步⑥] mergeRunSignals 提公共 helper(原 SAR 模块内直调)——workflow
40
+ // 派发的 timeout+watchdog+外部 signal 三源合流。
41
+ import { mergeRunSignals, type MergedRunSignalHandle } from "../engine/common/run-signals.ts";
42
+ import type { EnginePort, RunContext } from "../engine/port.ts";
43
+ import { registerSpawnedChildForRecord } from "../engine/host/spawned-children.ts";
44
+ import { DEFAULT_ENGINE_ID, getEngine } from "../engine/registry.ts";
45
+ import { type EngineRouteResult, routeEngineForHost } from "../engine/routing.ts";
46
+ import type { AgentOutcome } from "../engine/types.ts";
47
+ import type { ModelConfigService } from "../model-config-service.ts";
48
+ import type { AgentConfig } from "../model-resolver.ts";
49
+ import type { NotifyHost } from "../notify-host.ts";
50
+ // [R3] ResolvedIdentity 接口本体在 record-access.ts(生产者 resolveIdentity 所属聚合),
51
+ // 本聚合单向 type import(D-R3-2 同款非环形态)。
52
+ import type { ResolvedIdentity } from "./record-access.ts";
53
+ import type { RoundSupervisor } from "../round-supervisor/index.ts";
54
+ import {
55
+ armMidRoundNoProgress,
56
+ disarmSettledWatchdog,
57
+ refreshFromProtocolEvent,
58
+ type SettledWatchdogFireInfo,
59
+ } from "../settled-watchdog.ts";
60
+ import { createBackgroundStream, type StreamSink, type SubagentStream } from "../stream-sink.ts";
61
+ import { MAX_FORK_DEPTH } from "../session-context-resolver.ts";
62
+ import type { UiRequestObservability } from "../ui-request-observability.ts";
63
+ import {
64
+ DEFAULT_AGENT_NAME,
65
+ ForkDepthExceededError,
66
+ type AgentEvent,
67
+ type AgentResult,
68
+ type ExecuteOptions,
69
+ type ExecutionMode,
70
+ type ExecutionRecord,
71
+ } from "../types.ts";
72
+ // [R6/D-R4-4] 跨两聚合消费的值语义纯量归一常量叶子文件(聚合→支撑文件方向合法)。
73
+ import { PRIORITY_BACKGROUND, MS_PER_SECOND, SECONDS_PER_MINUTE } from "./service-constants.ts";
74
+
75
+ const logger = getLogger("subagents");
76
+
77
+ /**
78
+ * [R1 打样模式 1] 聚合协作 deps——**全部晚绑定闭包,构造期零求值**。
79
+ *
80
+ * 窄结构类型只声明聚合真实消费的通道(不整实例注入)。四类成员:
81
+ * - 断言/校验面(assertReady / assertIdleTimeoutMsSafe):入口就绪门与 idleTimeoutMs
82
+ * 合法域校验(本体在 SessionBaselines / RunOrchestration)。
83
+ * - 会话基线 getter(getExecNesting / getStreamSink / getUiObservability /
84
+ * getSessionRootId / getPi 通道外的 NotifyHost 投影):initSession 注入的运行时可变
85
+ * 态现读。
86
+ * - R3 聚合显式接口(resolveIdentity / resolveIdentityForEngine / createRecordForMode)
87
+ * 与 #1 留壳共享依赖(getModelService / getNotifyHost / getRoundSupervisor)。
88
+ * - 同域跨文件协作回调(经壳编排指 run-orchestration 实例方法,零 import):
89
+ * resolveChatEnginePort / acquirePoolOrFinalize / outcomeToAgentResult /
90
+ * settleOneShotOutcome / releaseRoundResources;finalizeFailed 指 RecordLifecycle。
91
+ */
92
+ export interface WorkflowDispatchDeps {
93
+ /** [D4 下沉] assertReady 断言(本体在 SessionBaselines,壳转发)。 */
94
+ readonly assertReady: () => void;
95
+ /** [T4② / PS-4] idleTimeoutMs 入口校验(本体在 RunOrchestration,与 execute/
96
+ * executeAndAwait 两入口同款;经壳编排回调)。 */
97
+ readonly assertIdleTimeoutMsSafe: (opts: ExecuteOptions) => void;
98
+ /** 嵌套身份基线(BC-12 嵌套护栏深度检查;SessionBaselines 现读)。 */
99
+ readonly getExecNesting: () => ExecutionNestingContext;
100
+ /** ModelConfigService(agentConfig 宽松面读取 + 引擎路由全局缺省)。 */
101
+ readonly getModelService: () => ModelConfigService;
102
+ /** pi 引擎 port 解析(RunOrchestration 组内方法,经壳编排回调)。 */
103
+ readonly resolveChatEnginePort: () => EnginePort;
104
+ /** [R3 RecordAccess 显式接口] pi 链三层身份解析。 */
105
+ readonly resolveIdentity: (opts: ExecuteOptions) => Promise<ResolvedIdentity>;
106
+ /** [R3 RecordAccess 显式接口] 非 pi 引擎的 identity 解析(含 validateModelForEngine)。 */
107
+ readonly resolveIdentityForEngine: (
108
+ engine: EnginePort,
109
+ engineModel: string | undefined,
110
+ agent: string,
111
+ agentConfig: AgentConfig | undefined,
112
+ opts: ExecuteOptions,
113
+ ) => ResolvedIdentity;
114
+ /** [R3 RecordAccess 显式接口] 按 mode 创建 record 并注册(含 workflow originFields)。 */
115
+ readonly createRecordForMode: (
116
+ identity: ResolvedIdentity,
117
+ opts: ExecuteOptions,
118
+ mode: ExecutionMode,
119
+ originFields?: { origin: "workflow"; parentRunId: string },
120
+ ) => ExecutionRecord;
121
+ /** NotifyHost(record 级 pending:register 注销面)。 */
122
+ readonly getNotifyHost: () => NotifyHost;
123
+ /** UI streaming sink(内构 background stream 的 widget 通道;SessionBaselines 现读)。 */
124
+ readonly getStreamSink: () => StreamSink | null;
125
+ /** UI observability(stream 通道形态判据 getMode;SessionBaselines 现读)。 */
126
+ readonly getUiObservability: () => UiRequestObservability;
127
+ /** 根 session id(relay 归属键 SESSION_ID 权威源;SessionBaselines 现读)。 */
128
+ readonly getSessionRootId: () => string | null;
129
+ /** [B-6 留壳] 轮次活性监督器(在途记账 noteRunStarted/noteRunEnded)。 */
130
+ readonly getRoundSupervisor: () => RoundSupervisor;
131
+ /** [RunOrchestration 协作回调] 池槽获取(失败路径含 S1 cancelled 终态收口)。 */
132
+ readonly acquirePoolOrFinalize: (
133
+ record: ExecutionRecord,
134
+ signal: AbortSignal | undefined,
135
+ priority: number,
136
+ ) => Promise<AgentResult | undefined>;
137
+ /** [RunOrchestration 协作回调] AgentOutcome → execution AgentResult 单一映射源。 */
138
+ readonly outcomeToAgentResult: (record: ExecutionRecord, outcome: AgentOutcome) => AgentResult;
139
+ /** [RunOrchestration 协作回调] one-shot 终态收口(顶部 D7 origin==="workflow"
140
+ * CAS 抢锁分支 = 成功即终态化 closed/gc)。 */
141
+ readonly settleOneShotOutcome: (record: ExecutionRecord, result: AgentResult, aborted: boolean) => Promise<void>;
142
+ /** [R3 RecordLifecycle 显式接口] run 创建期异常收尾(catch swallow 分支)。 */
143
+ readonly finalizeFailed: (record: ExecutionRecord, err: unknown) => Promise<AgentResult>;
144
+ /** [RunOrchestration 协作回调] 轮次资源回收(finally 语义,幂等)。 */
145
+ readonly releaseRoundResources: (
146
+ record: ExecutionRecord,
147
+ holdSlot: boolean,
148
+ stream: SubagentStream | undefined,
149
+ ) => void;
150
+ }
151
+
152
+ /**
153
+ * 域 #14 workflow 族聚合:workflow 脚本 agent() 派发链(R4 自 SubagentService 抽取)。
154
+ * 壳(subagent-service.ts)经 executeWorkflowAgent 单行转发透传,对外签名零变化。
155
+ */
156
+ export class WorkflowDispatch {
157
+ private readonly deps: WorkflowDispatchDeps;
158
+
159
+ constructor(deps: WorkflowDispatchDeps) {
160
+ this.deps = deps;
161
+ }
162
+
163
+ // [R4 等价形态复刻] sessionRootId 经 getter 转发 deps(属性访问形态保留——TS 对
164
+ // getter 可做 null 收窄,runCtx 条件 spread 的类型推导与壳内原形态一致;函数调用
165
+ // 形态 this.deps.getSessionRootId() 不参与收窄)。
166
+ private get sessionRootId(): string | null {
167
+ return this.deps.getSessionRootId();
168
+ }
169
+
170
+ /**
171
+ * [H2 W2] workflow 域统一派发入口(设计 subagent-workflow-record-unification
172
+ * §3.5 / G1 等同语义):workflow 脚本 agent() 经 pump 薄转调进此(W3 接线;本
173
+ * 单元入口就位,测试直接调用),与手动 subagent 同一 service 编排——共享池
174
+ * (DefaultConcurrencyPool)、record 注册进 store(origin:"workflow" +
175
+ * parentRunId)、journal / no-progress 守护 / spawned-children 治理、终态收口
176
+ * (D7 成功即终态化)。编排接管自 SAR.run 八步迁移(W4 已掏空 SAR.run 为纯转调
177
+ * ffbe595c5,
178
+ * ctxModel 孪生守卫按清单放弃——resolveIdentity 已有 model 解析,禁止双轨)。
179
+ *
180
+ * 顺序红线(D3):路由/预检/model 校验**先于**池 acquire——失败零池占用;路由
181
+ * 失败/预检命中/嵌套超限同步抛错回脚本且不产生孤儿 record(与 executeViaEngine
182
+ * 「全部同步拒绝发生在 record 创建前」同一不变量)。
183
+ *
184
+ * 错误规格(§3.4):池排队被 abort → run 域 cancelled 收口(acquirePoolOrFinalize
185
+ * 同款 S1 分支);record 创建失败 → 同步抛错;引擎死亡 → catch 合成 failed result
186
+ * 回脚本(swallow 语义,脚本观察到失败结果非异常)+ record 由失败路径立即终态化
187
+ * (adopt 豁免——workflow record 无脚本可回,resumable 等待无意义)。
188
+ */
189
+ async executeWorkflowAgent(
190
+ opts: AgentCallOpts,
191
+ parentRunId: string,
192
+ signal?: AbortSignal,
193
+ onEvent?: (event: AgentEvent) => void,
194
+ stream?: SubagentStream,
195
+ ): Promise<WorkflowAgentResult> {
196
+ this.deps.assertReady();
197
+ // 入口校验与嵌套护栏(与 execute/executeAndAwait 同款 BC-12 / T4②)。
198
+ const execOpts = workflowCallToExecuteOptions(opts);
199
+ this.deps.assertIdleTimeoutMsSafe(execOpts);
200
+ const parentNesting = this.deps.getExecNesting().current();
201
+ const nestingDepth = parentNesting ? parentNesting.depth + 1 : 0;
202
+ if (nestingDepth > MAX_FORK_DEPTH) {
203
+ throw new ForkDepthExceededError(
204
+ `subagent nesting depth ${nestingDepth} > ${MAX_FORK_DEPTH} (max recursion), refusing to spawn deeper`,
205
+ );
206
+ }
207
+
208
+ // ── 八步迁移 ①②③:路由 → 预检 → identity(含非 pi 引擎 model 校验)──
209
+ // 全部先于 record 创建与池 acquire(D3 失败零池占用)。agentConfig 取宽松面
210
+ //(getAgentConfig——路由输入只要 frontmatter engine;显式 ref 解析失败的报错归
211
+ // identity 阶段的 getRequiredAgentConfig,不在路由层重复)。
212
+ const agentConfig = opts.agent ? this.deps.getModelService().getAgentConfig(opts.agent) : undefined;
213
+ const route = await this.routeWorkflowEngine(opts, agentConfig);
214
+ // ② 预检(capability-gate 单点;AgentCallOpts 直传——TaskShapeForGate 是结构子集)
215
+ assertTaskShapeSupported(route.engineId, route.engine.capabilities(), opts);
216
+ // ③ 非 pi 引擎 model 校验 + identity 解析(详见 resolveWorkflowIdentity)+
217
+ // record 引擎留痕盖章(详见 stampWorkflowEngineTrace)。
218
+ const identity = await this.resolveWorkflowIdentity(route, opts, execOpts, agentConfig);
219
+ this.stampWorkflowEngineTrace(route, execOpts);
220
+
221
+ // ── record 注册(origin:"workflow" + parentRunId;record 级 pending:register
222
+ // 照旧——与既有派发路径同款)──
223
+ const record = this.deps.createRecordForMode(identity, execOpts, "background", {
224
+ origin: "workflow",
225
+ parentRunId,
226
+ });
227
+ this.deps.getNotifyHost().emitPendingRegister(record.id, record.agent);
228
+
229
+ const effectiveSignal = signal ?? record.controller?.signal;
230
+ return this.runWorkflowEngineTask(record, opts, identity, route.engine, effectiveSignal, onEvent, stream);
231
+ }
232
+
233
+ /** [executeWorkflowAgent 阶段拆分] 八步迁移①:引擎路由(D2 单轨——统一经
234
+ * routeEngineForHost:三层输入 + probe 守卫 + pi 同步短路)+ Promise 决议。 */
235
+ private async routeWorkflowEngine(
236
+ opts: AgentCallOpts,
237
+ agentConfig: AgentConfig | undefined,
238
+ ): Promise<EngineRouteResult> {
239
+ const routed = routeEngineForHost({
240
+ routing: {
241
+ callEngine: opts.engine,
242
+ agentEngine: agentConfig?.engine,
243
+ globalDefaultEngine: this.deps.getModelService().getGlobalConfig().defaultEngine,
244
+ },
245
+ taskModel: opts.model,
246
+ strict: this.deps.getModelService().getGlobalConfig().engineRouting?.strict === true,
247
+ probe: (engineId) => getEngine(engineId).probe(),
248
+ piEngine: this.deps.resolveChatEnginePort(),
249
+ });
250
+ return routed instanceof Promise ? await routed : routed;
251
+ }
252
+
253
+ /** [executeWorkflowAgent 阶段拆分] 八步迁移③:非 pi 引擎 model 校验:经
254
+ * resolveIdentityForEngine 内的 validateModelForEngine(与 chat 域 executeViaEngine
255
+ * 同一入口同一文案;model 源 = 显式 opts.model > agent frontmatter——u-h2 D2-1③
256
+ * 同款)。model 源非空时同步覆写 execOpts.model(record 留痕 + taskSpec 直传一致)。 */
257
+ private async resolveWorkflowIdentity(
258
+ route: EngineRouteResult,
259
+ opts: AgentCallOpts,
260
+ execOpts: ExecuteOptions,
261
+ agentConfig: AgentConfig | undefined,
262
+ ): Promise<ResolvedIdentity> {
263
+ const isPiRoute = route.engineId === DEFAULT_ENGINE_ID;
264
+ const engineModel = isPiRoute ? undefined : (opts.model ?? agentConfig?.model);
265
+ const identity = isPiRoute
266
+ ? await this.deps.resolveIdentity(execOpts)
267
+ : this.deps.resolveIdentityForEngine(
268
+ route.engine,
269
+ engineModel,
270
+ execOpts.agent ?? DEFAULT_AGENT_NAME,
271
+ agentConfig,
272
+ execOpts,
273
+ );
274
+ if (!isPiRoute && engineModel !== undefined) execOpts.model = engineModel;
275
+ return identity;
276
+ }
277
+
278
+ /** [executeWorkflowAgent 阶段拆分] record 引擎留痕(对齐 executeViaEngine 盖章规则:
279
+ * pi 纯缺省不盖键;pi 兜底盖 'pi'+from;非 pi 盖 engineId)。原位 mutate execOpts。 */
280
+ private stampWorkflowEngineTrace(route: EngineRouteResult, execOpts: ExecuteOptions): void {
281
+ const isPiRoute = route.engineId === DEFAULT_ENGINE_ID;
282
+ if (!isPiRoute) {
283
+ execOpts.engine = route.engineId;
284
+ } else if (route.engineFallback !== undefined) {
285
+ execOpts.engine = DEFAULT_ENGINE_ID;
286
+ }
287
+ if (route.engineFallback !== undefined) execOpts.engineFallback = route.engineFallback;
288
+ }
289
+
290
+ /**
291
+ * executeWorkflowAgent 的执行核(acquire 后主体 + finally 回收)。八步迁移 ④⑤⑥⑦
292
+ * 的落点:
293
+ * ④ journal 接线(wireEventJournal 单点;taskId = record.id——真实 record 在
294
+ * store,不再用 SAR 的占位 id);
295
+ * ⑤ no-progress 守护(arm/disarm 键 = record.id(D4);双刷新源 = journal.onEvent
296
+ * 包装 ∪ stream.onDelta 包装;fire 后失败结果追注恢复指引——M3 语义逐项复刻);
297
+ * ⑥ mergeRunSignals(timeoutMs + 守护 abort + 外部 signal 合流);
298
+ * ⑦ spawned-children 注册(dispose killAll 收割兜底,键 = record.id)。
299
+ * 池 = DefaultConcurrencyPool 共享(acquirePoolOrFinalize 同链,D3);成功收口 =
300
+ * settleOneShotOutcome 顶部 D7 origin 分支(closed/gc 立即终态化)。stream 实参
301
+ * 缺省时自构 createBackgroundStream(设计 D2「streaming 由 service 派发路径既有
302
+ * 通道承载」——widget 通道收口点,kickOffChatRound 同款策略,见函数体注释)。
303
+ */
304
+ async runWorkflowEngineTask(
305
+ record: ExecutionRecord,
306
+ opts: AgentCallOpts,
307
+ identity: ResolvedIdentity,
308
+ engine: EnginePort,
309
+ signal: AbortSignal | undefined,
310
+ onEvent?: (event: AgentEvent) => void,
311
+ stream?: SubagentStream,
312
+ ): Promise<WorkflowAgentResult> {
313
+ // [H2 W3 must-fix] stream 实参缺省时自构 background stream——设计 D2「streaming
314
+ // 由 service 派发路径既有通道承载」的实体落点:W3 切换后 pump 不再构造
315
+ // SubagentStream(旁路 record 族退役),workflow agent 的 text_delta widget 通道
316
+ // 在 service 侧收口,与 kickOffChatRound 的 createBackgroundStream 完全同款
317
+ //(含 H1 widget 退役策略:GUI+relay 激活停发私货 / TUI·未激活原样创建 / sink
318
+ // 未注入降级 undefined——照单继承 chat 域现行策略,非行为变化)。显式传 stream
319
+ // 时用传入值(测试注入面保留)。创建先于池 acquire(kickOffChatRound 同序:
320
+ // acquire 失败早退时 stream 尚未 onDelta,无 widget/timer 副作用可泄漏)。
321
+ const effectiveStream =
322
+ stream ?? createBackgroundStream(record.id, this.deps.getStreamSink(), this.deps.getUiObservability().getMode(), process.env);
323
+ const pooled = record.mode === "background";
324
+ let acquired = false;
325
+ if (pooled) {
326
+ const acquireFailure = await this.deps.acquirePoolOrFinalize(record, signal, PRIORITY_BACKGROUND);
327
+ if (acquireFailure !== undefined) return mapToWorkflowAgentResult(acquireFailure);
328
+ acquired = true;
329
+ }
330
+
331
+ const journal = wireEventJournal({ engineId: engine.id, taskId: record.id, forwardEvents: onEvent });
332
+ let runSignal: MergedRunSignalHandle | undefined;
333
+ let unbindStream: (() => void) | undefined;
334
+ let noProgress: WorkflowNoProgressGuard | undefined;
335
+ // [W4] 在途记账(监督器「该等」判据源;与 kickOffEngineRun/kickOffChatRound 同款
336
+ //——运行期监督对 workflow record 照旧纳管,只豁免 adopt 接管)。
337
+ this.deps.getRoundSupervisor().noteRunStarted(record.id);
338
+ try {
339
+ // ⑤ arm(池槽已到手、engine.run 派发前——排队窗口不计入 no-progress 静默,
340
+ // kickOffChatRound 同款窗语义)。
341
+ noProgress = armWorkflowNoProgressWatchdog(record.id);
342
+ // ⑥ timeoutMs + 守护 abort 并入同一合流(第三信号源)
343
+ runSignal = mergeRunSignals(
344
+ signal ?? new AbortController().signal,
345
+ opts.timeoutMs,
346
+ noProgress.signal,
347
+ );
348
+ // ⑤ 双刷新源(两路缺一不可——只接 journal 会漏纯流式产出的活性信号):
349
+ // journal.onEvent 包装(协议事件)∪ stream.onDelta 包装(流式增量反向帧;
350
+ // 内构 stream 同样经本包裹——refresh 与 widget flush 在同一 onDelta 调用点)。
351
+ const journalOnEvent = journal.onEvent;
352
+ const observedEvent = (event: AgentEvent): void => {
353
+ // [H2 A3 修复] live reducer 喂入恢复:W3 删 inproc pi 引擎时,原
354
+ // engines/pi/session-runner.ts agentEvent 出口的 updateFromEvent(record, event)
355
+ // 一并消失,协议化 service 侧未重建——record.turns/totalTokens 在 live 通路
356
+ // 零喂入,终态 entry 落盘同为 0(Gate B A3:workflow agent 实际消耗 LLM 而
357
+ // record 恒 0)。此处恢复 workflow 域喂入:reducer 与 journal-replay /
358
+ // session-view-service 重放路径同源(C5 守护),live ≡ replay 构造性成立;
359
+ // 事件序 = 引擎协议事件序,message_end(usage) 携带 token 增量。
360
+ updateFromEvent(record, event);
361
+ refreshFromProtocolEvent(record.id);
362
+ journalOnEvent(event);
363
+ };
364
+ unbindStream = effectiveStream === undefined ? undefined : bindWorkflowStreamRefresh(effectiveStream, record.id);
365
+
366
+ const runCtx: RunContext = {
367
+ taskId: record.id,
368
+ poolKey: JOURNAL_INITIAL_POOL_KEY,
369
+ signal: runSignal.signal,
370
+ ctxModel: identity.resolved.model,
371
+ onEvent: observedEvent,
372
+ onPoolResolved: journal.onPoolResolved,
373
+ ...(effectiveStream !== undefined ? { stream: effectiveStream } : {}),
374
+ ...(record.engineFallback !== undefined ? { engineFallback: record.engineFallback } : {}),
375
+ ...(this.sessionRootId !== null && this.sessionRootId !== ""
376
+ ? { sessionRootId: this.sessionRootId }
377
+ : {}),
378
+ // ⑦ D10 终止链:引擎 spawn 的子进程注册进 spawnedChildren 记账(cancel
379
+ // SIGTERM / dispose killAll 收割兜底,键 = record.id)
380
+ onChildSpawned: (child) => registerSpawnedChildForRecord(record.id, child),
381
+ };
382
+ // 任务声明:opts 直传(D6 合流——AgentCallOpts 即 EnginePort 任务形状,SAR 同款
383
+ // 零映射),model 覆写为 record 留痕词形(resolveIdentity 解析产物,与
384
+ // runAndFinalize 的 taskSpecWithModel 同源权威)。
385
+ const taskSpec: AgentCallOpts = {
386
+ ...opts,
387
+ ...(record.model !== undefined ? { model: record.model } : {}),
388
+ };
389
+ const { handle, outcome } = await engine.run(taskSpec, runCtx);
390
+ journal.backfillHandle(handle);
391
+ record.engineHandle = {
392
+ sessionRef: handle.data.sessionRef,
393
+ poolKey: handle.data.poolKey,
394
+ journalPath: journal.path,
395
+ };
396
+ const result = this.deps.outcomeToAgentResult(record, outcome);
397
+ // D7 收口(origin 分支在 settleOneShotOutcome 函数顶部;aborted 判外部 signal
398
+ // ——timeout/watchdog 的 abort 走失败 result 语义,不映射 cancelled)。
399
+ await this.deps.settleOneShotOutcome(record, result, signal?.aborted === true);
400
+ return noteIfWorkflowNoProgressFired(outcomeToWorkflowResult(outcome), noProgress);
401
+ } catch (err) {
402
+ // swallow(不 re-throw):脚本观察到合成 failed result 而非异常(引擎死亡
403
+ // engine_crashed 同路);record 由失败路径立即终态化(finalizeFailed CAS →
404
+ // finalizeRecord;adopt 豁免——workflow record 不保持 resumable 交监督器)。
405
+ const failed = await this.deps.finalizeFailed(record, err);
406
+ return noteIfWorkflowNoProgressFired(mapToWorkflowAgentResult(failed), noProgress);
407
+ } finally {
408
+ // 先摘 stream 包裹与信号桥接(不残留 listener/覆写),再清守护,再归还池槽与
409
+ // journal 收口(SAR 同序)。内构 stream 的 widget 清除(dispose)同经本回收。
410
+ runSignal?.dispose();
411
+ unbindStream?.();
412
+ disarmSettledWatchdog(record.id);
413
+ this.deps.releaseRoundResources(record, pooled && acquired, effectiveStream);
414
+ await journal.close();
415
+ this.deps.getRoundSupervisor().noteRunEnded(record.id);
416
+ }
417
+ }
418
+ }
419
+
420
+ // ── [H2 W2] workflow 域派发 helper(executeWorkflowAgent 专用,M3 语义逐项复刻)──
421
+
422
+ /**
423
+ * [H2 W2 迁移步⑤] workflow 派发路径 no-progress 守护句柄:signal 供 mergeRunSignals
424
+ * 合流(步⑥),fired 供 run 收敛后判定是否追注恢复指引(fire 是异步 timer 事件)。
425
+ * 与 chat 域 armMidRoundNoProgress 同一原语(settled-watchdog)同一量级(30min
426
+ * 连续静默 = 回收层有界兜底,非任务级墙钟);差异仅 arm 键 = record.id(D4 守护
427
+ * 单点——真实 record 在 store,原 SAR 模块内守护函数随 [H2 W4] 掏空退役)。
428
+ */
429
+ interface WorkflowNoProgressGuard {
430
+ signal: AbortSignal;
431
+ fired(): boolean;
432
+ }
433
+
434
+ /**
435
+ * arm workflow 派发路径 no-progress 守护(复用 settled-watchdog 原语,不新造第二套
436
+ * 计时器)。fire 回调契约:timer 同步上下文内只做 warn + AbortController.abort()
437
+ * (abort 幂等不抛);真正终止由 abort 经 mergedSignal → RemoteEngine
438
+ * wireAbortSignal 阶梯(cancel 帧 → 收敛窗 → killAll)承载。onSettleTimeout 与
439
+ * onMidTimeout 同体(workflow 域无 agent_end 交棒点,同体保证未来接交棒语义不变)。
440
+ */
441
+ function armWorkflowNoProgressWatchdog(recordId: string): WorkflowNoProgressGuard {
442
+ const controller = new AbortController();
443
+ let fired = false;
444
+ const fire = (info: SettledWatchdogFireInfo): void => {
445
+ fired = true;
446
+ // 恢复指引闭环(错误 → 权威源 → 重试):killAll 组杀连带面在此显式出声——
447
+ // 引擎对 cancel 帧 >收敛窗无响应时组杀引擎 CLI,同引擎其余并发 run 会以
448
+ // engine_crashed 失败终态化(失败结果照回脚本、executeAgentCall 重试通道仍在)。
449
+ logger.warn(
450
+ `[subagents] workflow no-progress watchdog (${info.phase}) fired for ${recordId}: ` +
451
+ `no valid protocol event for ${info.waitedMs / MS_PER_SECOND / SECONDS_PER_MINUTE} min after run dispatched — ` +
452
+ `aborting run (cancel frame → settle grace window → killAll if the engine does not settle). ` +
453
+ `Note: a killAll group-kills the engine CLI process, so other concurrent runs on the same ` +
454
+ `engine may end as engine_crashed (they still get a failure result and retry). ` +
455
+ `Recovery: check state with subagents action:'list' includeFinished:true (add includeWorkflow:true to also see workflow-dispatched subagents), then re-dispatch the workflow.`,
456
+ );
457
+ controller.abort();
458
+ };
459
+ armMidRoundNoProgress(recordId, { onMidTimeout: fire, onSettleTimeout: fire });
460
+ return { signal: controller.signal, fired: () => fired };
461
+ }
462
+
463
+ /**
464
+ * streamDelta 刷新接线(守护双刷新源之二):在**原实例**上包裹 onDelta(先刷新再
465
+ * 委托原实现),返回 unbind 函数由 finally 调用恢复。保持对象 identity(不建代理)
466
+ * ——下游 stream 透传契约要求同一实例,而 host/streamDelta 反向帧只经
467
+ * `ctx.stream.onDelta` 到达,原地包裹是唯一既保 identity 又能观测 delta 的接法。
468
+ */
469
+ function bindWorkflowStreamRefresh(stream: SubagentStream, recordId: string): () => void {
470
+ const originalOnDelta = stream.onDelta;
471
+ const hadOwnOnDelta = Object.prototype.hasOwnProperty.call(stream, "onDelta");
472
+ stream.onDelta = (delta: string): void => {
473
+ refreshFromProtocolEvent(recordId);
474
+ originalOnDelta.call(stream, delta);
475
+ };
476
+ return () => {
477
+ if (hadOwnOnDelta) stream.onDelta = originalOnDelta;
478
+ else Reflect.deleteProperty(stream, "onDelta");
479
+ };
480
+ }
481
+
482
+ /**
483
+ * fire 判定 + 追注的单一出口(正常收敛与 catch 两路共用同一判定,消除「两路文案
484
+ * 分叉」)。只对已带 error 的结果追注——成功收敛或被外部 cancel 的形态保持原样,
485
+ * 不伪造失败(M3 U-B2 语义复刻)。
486
+ */
487
+ function noteIfWorkflowNoProgressFired(
488
+ result: WorkflowAgentResult,
489
+ guard: WorkflowNoProgressGuard | undefined,
490
+ ): WorkflowAgentResult {
491
+ return guard?.fired() === true && result.error !== undefined
492
+ ? {
493
+ ...result,
494
+ error:
495
+ `${result.error} | workflow no-progress watchdog fired: the run was aborted after a long ` +
496
+ `silence window with no protocol event or stream delta. ` +
497
+ `Recovery: check state with subagents action:'list' includeFinished:true (add includeWorkflow:true to also see workflow-dispatched subagents), then re-dispatch the workflow.`,
498
+ }
499
+ : result;
500
+ }
501
+
502
+ /**
503
+ * AgentOutcome → workflow AgentResult 直映射(SAR outcomeToRunnerResult 的 service
504
+ * 侧等价物):保留 usage/sessionFile/worktreePath/failureKind 等引擎层字段——
505
+ * outcomeToAgentResult(execution AgentResult)不含这些字段,经它中转会丢 workflow
506
+ * 消费面(usage 进预算、sessionFile/worktreePath 进 returnMeta、failureKind 进
507
+ * 失败分诊)依赖的字段。
508
+ */
509
+ function outcomeToWorkflowResult(outcome: AgentOutcome): WorkflowAgentResult {
510
+ return {
511
+ content: outcome.content,
512
+ ...(outcome.failureKind !== undefined ? { failureKind: outcome.failureKind } : {}),
513
+ parsedOutput: outcome.parsedOutput,
514
+ usage: outcome.usage,
515
+ durationMs: outcome.durationMs,
516
+ error: outcome.error,
517
+ sessionId: outcome.sessionId,
518
+ sessionFile: outcome.sessionFile,
519
+ worktreePath: outcome.worktreePath,
520
+ toolCalls: outcome.toolCalls,
521
+ };
522
+ }
523
+
524
+ /**
525
+ * AgentCallOpts → ExecuteOptions 的最小正向映射(record 创建 + identity 解析消费面;
526
+ * host-task-spec.executeOptionsToEngineTaskSpec 的逆映射)。slug 推导 = 唯一规则
527
+ * (description ?? agent ?? "unknown",超长按 SLUG_MAX_LENGTH 截断;原 pump
528
+ * dispatchAgentCall trace 命名同源规则随 [H2 W3] 删除)。returnMeta/scene 等
529
+ * worker 层/引擎层独有字段不入 record
530
+ * 消费面(taskSpec 装配走 opts 原样直传,不经本映射)。
531
+ */
532
+ /**
533
+ * 字段存在性投影(条件 spread 的查表化承载):value 非 undefined 时经 project 产出
534
+ * 键值片段,否则空对象——语义等价 `...(x !== undefined ? { key: x } : {})`(仅查
535
+ * undefined,falsy 值照常透传),把逐字段条件分支的复杂度堆叠收敛到单点。
536
+ */
537
+ function present<T>(
538
+ value: T | undefined,
539
+ project: (value: T) => Partial<ExecuteOptions>,
540
+ ): Partial<ExecuteOptions> {
541
+ return value === undefined ? {} : project(value);
542
+ }
543
+
544
+ function workflowCallToExecuteOptions(opts: AgentCallOpts): ExecuteOptions {
545
+ const agentName = opts.description ?? opts.agent ?? "unknown";
546
+ const slug = agentName.length > SLUG_MAX_LENGTH ? agentName.slice(0, SLUG_MAX_LENGTH) : agentName;
547
+ return {
548
+ task: opts.prompt,
549
+ slug,
550
+ ...present(opts.agent, (agent) => ({ agent })),
551
+ ...present(opts.model, (model) => ({ model })),
552
+ ...present(opts.thinkingLevel, (thinkingLevel) => ({ thinkingLevel })),
553
+ ...present(opts.skillPath, (skillPath) => ({ skillPath })),
554
+ ...present(opts.appendSystemPrompt, (appendSystemPrompt) => ({ appendSystemPrompt })),
555
+ ...present(opts.schema, (schema) => ({ schema })),
556
+ ...present(opts.schemaEnv, (schemaEnv) => ({ schemaEnv })),
557
+ ...present(opts.maxTurns, (maxTurns) => ({ maxTurns })),
558
+ ...present(opts.graceTurns, (graceTurns) => ({ graceTurns })),
559
+ ...present(opts.fork, (fork) => ({ fork })),
560
+ // forkSource → forkFromSessionFile:两 DTO 键名不同源的显式映射。
561
+ ...present(opts.forkSource, (forkSource) => ({ forkFromSessionFile: forkSource })),
562
+ ...present(opts.worktree, (worktree) => ({ worktree })),
563
+ ...present(opts.cwd, (cwd) => ({ cwd })),
564
+ ...present(opts.conversation, (conversation) => ({ conversation })),
565
+ ...present(opts.idleTimeoutMs, (idleTimeoutMs) => ({ idleTimeoutMs })),
566
+ };
567
+ }
@@ -1,4 +1,4 @@
1
- // src/runtime/session-file-gc.ts
1
+ // src/execution/session-file-gc.ts
2
2
  //
3
3
  // 概率性清理过期 subagent session 文件(TTL 30 天)。
4
4
  // session_start 时调用,best-effort(失败不影响启动)。
@@ -63,14 +63,17 @@ function cleanupEnginePoolsBestEffort(): void {
63
63
  }
64
64
  }
65
65
 
66
- /** 孤儿 sidecar 名判定:.cancelled/.finalized/.alive,以及 [MF#2] patch 回传
67
- * (<branch>.patch branch 命名,与 .jsonl basename 无关联,删除 .jsonl 时无法同删;
68
- * 作为孤儿按 TTL 清理,避免永久堆积)。 */
66
+ /** 孤儿 sidecar 名判定:终态 `.state` + 兼容期旧名(.finalized/.cancelled/.alive
67
+ * + `.record-binding` 身份绑定(UF-1;I-16 销账——GC 领地批次已扩名单),
68
+ * 以及 [MF#2] patch 回传(<branch>.patch 按 branch 命名,与 .jsonl basename 无关联,
69
+ * 删除 .jsonl 时无法同删;作为孤儿按 TTL 清理,避免永久堆积)。 */
69
70
  function isOrphanSidecarName(name: string): boolean {
70
71
  return (
72
+ name.endsWith(".state") ||
71
73
  name.endsWith(".cancelled") ||
72
74
  name.endsWith(".finalized") ||
73
75
  name.endsWith(".alive") ||
76
+ name.endsWith(".record-binding") ||
74
77
  name.endsWith(".patch")
75
78
  );
76
79
  }
@@ -100,8 +103,8 @@ function cleanExpiredJsonl(full: string, now: number): void {
100
103
  return; // 活进程 → 跳过,不清理
101
104
  }
102
105
  fs.unlinkSync(full);
103
- // 同名 sidecar 一起清理。
104
- for (const ext of [".cancelled", ".finalized", ".alive"]) {
106
+ // 同名 sidecar 一起清理(终态 .state + 兼容期旧名 + 探活 .alive + 身份绑定 .record-binding)。
107
+ for (const ext of [".state", ".cancelled", ".finalized", ".alive", ".record-binding"]) {
105
108
  try { fs.unlinkSync(`${full}${ext}`); } catch (_e) { void _e; /* sidecar 可能不存在 */ }
106
109
  }
107
110
  }
@@ -123,7 +126,7 @@ function cleanDirent(dir: string, entry: fs.Dirent, now: number, allowManifestJs
123
126
  } else if (
124
127
  allowManifestJson &&
125
128
  entry.name.endsWith(".json") &&
126
- // 跳过 .tmp.:recoverTmpFiles(session_start)同步处理 tmp,GC 不重复。
129
+ // 跳过 .tmp.:sweepTmpFiles(session_start)同步处理 tmp,GC 不重复。
127
130
  // 不校验内容——30 天 mtime 已是强 orphan 信号,扩展名 + 文件名足够。
128
131
  !entry.name.includes(".tmp.")
129
132
  ) {
@@ -138,7 +141,7 @@ function cleanDirent(dir: string, entry: fs.Dirent, now: number, allowManifestJs
138
141
 
139
142
  /** 递归扫描目录,unlink 超 TTL 的 .jsonl 文件及其 .cancelled sidecar。
140
143
  * [F2] 进入名为 records 的子目录时,额外清理超 TTL 的 manifest .json(跳过 .tmp.——
141
- * recoverTmpFiles 同步处理)。allowManifestJson 仅由父调用按目录名开启,其他位置
144
+ * sweepTmpFiles 同步处理)。allowManifestJson 仅由父调用按目录名开启,其他位置
142
145
  * (如 subagents/worktrees.json)不匹配 .json,避免误删 worktree reaper 状态文件。 */
143
146
  function walkAndClean(dir: string, now: number, allowManifestJson = false): void {
144
147
  let entries: fs.Dirent[];