@zhushanwen/pi-subagent-workflow 7.4.0 → 8.1.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 (166) hide show
  1. package/README.md +15 -0
  2. package/package.json +7 -4
  3. package/scripts/rfl.mjs +308 -0
  4. package/skills/workflow-script-format/SKILL.md +1 -1
  5. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
  6. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +10 -4
  7. package/src/execution/__tests__/before-agent-start-injection.test.ts +132 -0
  8. package/src/execution/__tests__/bg-notify-render.test.ts +15 -15
  9. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +365 -0
  10. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
  11. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +215 -0
  12. package/src/execution/__tests__/conversation-wiring.test.ts +198 -0
  13. package/src/execution/__tests__/crash-recovery.test.ts +8 -2
  14. package/src/execution/__tests__/delivery-methods.test.ts +385 -0
  15. package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
  16. package/src/execution/__tests__/execute-and-await-worktree.test.ts +49 -2
  17. package/src/execution/__tests__/execute-nesting.test.ts +20 -72
  18. package/src/execution/__tests__/execution-record.test.ts +199 -0
  19. package/src/execution/__tests__/finalize-record.test.ts +197 -15
  20. package/src/execution/__tests__/format.test.ts +131 -7
  21. package/src/execution/__tests__/gc-timer.test.ts +184 -0
  22. package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
  23. package/src/execution/__tests__/helpers/spawn-mock.ts +37 -10
  24. package/src/execution/__tests__/index-session-start-identity.test.ts +371 -0
  25. package/src/execution/__tests__/index-session-start.test.ts +257 -5
  26. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
  27. package/src/execution/__tests__/lifecycle-manager.test.ts +337 -0
  28. package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
  29. package/src/execution/__tests__/list-component.test.ts +59 -5
  30. package/src/execution/__tests__/list-fields.test.ts +109 -0
  31. package/src/execution/__tests__/model-resolver.test.ts +38 -1
  32. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
  33. package/src/execution/__tests__/nested-visibility.test.ts +325 -0
  34. package/src/execution/__tests__/notifier-flush.test.ts +209 -7
  35. package/src/execution/__tests__/one-shot-upgrade.test.ts +205 -0
  36. package/src/execution/__tests__/parent-child-matrix.test.ts +336 -0
  37. package/src/execution/__tests__/record-store.test.ts +442 -54
  38. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +11 -12
  39. package/src/execution/__tests__/recursive-visibility-env.test.ts +18 -20
  40. package/src/execution/__tests__/resource-policy.test.ts +109 -0
  41. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
  42. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
  43. package/src/execution/__tests__/run-spawn-edges.test.ts +18 -25
  44. package/src/execution/__tests__/run-spawn-integration.test.ts +29 -25
  45. package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
  46. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +14 -11
  47. package/src/execution/__tests__/session-pending.test.ts +61 -2
  48. package/src/execution/__tests__/session-reconstructor.test.ts +4 -4
  49. package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
  50. package/src/execution/__tests__/session-runner-schema-env.test.ts +15 -21
  51. package/src/execution/__tests__/session-start-reaper.test.ts +10 -8
  52. package/src/execution/__tests__/spawn-args.test.ts +127 -49
  53. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +1 -0
  54. package/src/execution/__tests__/spawned-children.test.ts +92 -0
  55. package/src/execution/__tests__/status-refactor.test.ts +345 -0
  56. package/src/execution/__tests__/stdin-writer.test.ts +97 -0
  57. package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
  58. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
  59. package/src/execution/__tests__/subagent-service.test.ts +49 -11
  60. package/src/execution/__tests__/timeout-integration.test.ts +27 -13
  61. package/src/execution/__tests__/tool-action.test.ts +12 -10
  62. package/src/execution/__tests__/truncline-snapshot.test.ts +81 -0
  63. package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
  64. package/src/execution/__tests__/worktree-manager.test.ts +300 -90
  65. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +17 -13
  66. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
  67. package/src/execution/__tests__/worktree-registry.test.ts +72 -34
  68. package/src/execution/agent-result-mapper.ts +4 -1
  69. package/src/execution/argv-mirror.ts +21 -2
  70. package/src/execution/channel-registry-access.ts +3 -1
  71. package/src/execution/execution-record.ts +126 -9
  72. package/src/execution/finalize-record.ts +99 -13
  73. package/src/execution/idle-gc.ts +47 -0
  74. package/src/execution/lifecycle-manager.ts +491 -0
  75. package/src/execution/lifecycle-predicates.ts +65 -0
  76. package/src/execution/manifest-store.ts +61 -16
  77. package/src/execution/model-resolver.ts +26 -5
  78. package/src/execution/notifier.ts +69 -12
  79. package/src/execution/pi-invocation.ts +21 -1
  80. package/src/execution/record-entry.ts +118 -0
  81. package/src/execution/record-store.ts +844 -108
  82. package/src/execution/session-pending.ts +121 -49
  83. package/src/execution/session-reconstructor.ts +224 -7
  84. package/src/execution/session-runner.ts +713 -316
  85. package/src/execution/sessions-index.ts +304 -0
  86. package/src/execution/stdin-writer.ts +93 -7
  87. package/src/execution/stream-sink.ts +20 -3
  88. package/src/execution/subagent-service.ts +917 -138
  89. package/src/execution/temp-prompt.ts +8 -3
  90. package/src/execution/turn-limiter.ts +14 -0
  91. package/src/execution/types.ts +218 -19
  92. package/src/execution/worktree-manager.ts +449 -59
  93. package/src/execution/worktree-registry.ts +97 -29
  94. package/src/index.ts +318 -20
  95. package/src/injectors/subagent-list-injector.ts +26 -8
  96. package/src/injectors/workflow-list-injector.ts +25 -8
  97. package/src/interface/__tests__/subagent-tool-prompt.test.ts +17 -4
  98. package/src/interface/__tests__/tool-render.test.ts +10 -8
  99. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +103 -26
  100. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +179 -0
  101. package/src/interface/bg-notify-render.ts +32 -8
  102. package/src/interface/command-actions.ts +26 -7
  103. package/src/interface/commands.ts +21 -22
  104. package/src/interface/format.ts +53 -20
  105. package/src/interface/gui-mappers.ts +6 -8
  106. package/src/interface/helpers.ts +170 -10
  107. package/src/interface/list-component.ts +53 -14
  108. package/src/interface/subagent-actions.ts +235 -17
  109. package/src/interface/subagent-tool.ts +81 -16
  110. package/src/interface/subagents.ts +2 -1
  111. package/src/interface/tool-render.ts +21 -27
  112. package/src/interface/tool-workflow-script.ts +29 -33
  113. package/src/interface/tool-workflow.ts +67 -100
  114. package/src/interface/views/WorkflowsView.ts +89 -32
  115. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +264 -0
  116. package/src/interface/views/detail-content.ts +1 -1
  117. package/src/interface/views/format.ts +3 -3
  118. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +333 -0
  119. package/src/orchestration/__tests__/args-validator.test.ts +1 -1
  120. package/src/orchestration/__tests__/config-loader.test.ts +38 -0
  121. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +394 -4
  122. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +4 -4
  123. package/src/orchestration/__tests__/execute-agent-call.test.ts +144 -1
  124. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +171 -0
  125. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +820 -19
  126. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -2
  127. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
  128. package/src/orchestration/__tests__/lifecycle.test.ts +332 -149
  129. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +1108 -19
  130. package/src/orchestration/__tests__/skill-discovery.test.ts +130 -61
  131. package/src/orchestration/__tests__/test-mocks.ts +197 -0
  132. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
  133. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +110 -0
  134. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -2
  135. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
  136. package/src/orchestration/__tests__/workflows-e2e.test.ts +38 -40
  137. package/src/orchestration/agent-opts-resolver.ts +4 -1
  138. package/src/orchestration/args-validator.ts +2 -2
  139. package/src/orchestration/config-loader.ts +30 -1
  140. package/src/orchestration/error-recovery.ts +137 -30
  141. package/src/orchestration/execute-agent-call.ts +44 -10
  142. package/src/orchestration/jsonl-run-store.ts +397 -71
  143. package/src/orchestration/launcher.ts +7 -1
  144. package/src/orchestration/lifecycle.ts +145 -133
  145. package/src/orchestration/models/__tests__/trace.test.ts +408 -0
  146. package/src/orchestration/models/budget.ts +1 -1
  147. package/src/orchestration/models/run-runtime.ts +15 -17
  148. package/src/orchestration/models/run-spec.ts +2 -2
  149. package/src/orchestration/models/run-state.ts +3 -3
  150. package/src/orchestration/models/trace.ts +95 -15
  151. package/src/orchestration/models/types.ts +8 -9
  152. package/src/orchestration/models/workflow-run.ts +50 -71
  153. package/src/orchestration/models/workflow-script.ts +32 -1
  154. package/src/orchestration/skill-discovery.ts +30 -0
  155. package/src/orchestration/worker-handle.ts +1 -1
  156. package/src/orchestration/worker-host.ts +1 -1
  157. package/src/orchestration/worker-script-builder.ts +38 -11
  158. package/src/shared/__tests__/agent-ref.test.ts +34 -0
  159. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +280 -0
  160. package/src/shared/__tests__/resource-discovery.test.ts +79 -0
  161. package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
  162. package/src/shared/agent-ref.ts +22 -1
  163. package/src/shared/resource-discovery.ts +162 -59
  164. package/src/shared/schema-jsonify.ts +56 -0
  165. package/workflows/review-fix-loop-utils.cjs +542 -32
  166. package/workflows/review-fix-loop.js +462 -109
@@ -1,7 +1,8 @@
1
1
  // src/core/temp-prompt.ts
2
2
  //
3
3
  // 将 agent systemPrompt 写入临时文件,供 pi CLI --append-system-prompt 使用。
4
- // Core 叶子原语(仅依赖 node 内置)。
4
+ // Core 叶子原语(node 内置 + 同目录 bestEffort 清理 helper)。注意 bestEffort
5
+ // 经 extension-logger 有到 pi 的传递依赖(仅日志通路,无 pi 运行时调用)。
5
6
  //
6
7
  // pi CLI 的 --append-system-prompt 接受文件路径(非内联字符串),故 spawn 前
7
8
  // 需把 systemPrompt 落盘。每次调用创建唯一临时目录,用完由 runSpawn 清理。
@@ -13,6 +14,8 @@ import * as fs from "node:fs";
13
14
  import * as os from "node:os";
14
15
  import * as path from "node:path";
15
16
 
17
+ import { bestEffort } from "./best-effort.ts";
18
+
16
19
  /** 临时 prompt 文件创建结果。dir 供调用方清理。 */
17
20
  export interface TempPromptFile {
18
21
  /** 临时目录绝对路径(mkdtemp 创建,调用方负责删除)。 */
@@ -51,7 +54,9 @@ export async function writePromptToTempFile(
51
54
  export async function cleanupTempPrompt(file: TempPromptFile): Promise<void> {
52
55
  try {
53
56
  await fs.promises.rm(file.dir, { recursive: true, force: true });
54
- } catch {
55
- // best-effort:临时文件泄漏不影响功能,OS tmpdir 清理机制兜底
57
+ } catch (err) {
58
+ // best-effort:临时文件泄漏不影响功能,OS tmpdir 清理机制兜底;失败经
59
+ // bestEffort 记 debug 日志(对齐 idle-gc / finalize-record 的清理惯例)
60
+ bestEffort(err, `cleanup temp prompt dir ${file.dir}`);
56
61
  }
57
62
  }
@@ -51,6 +51,14 @@ export interface TurnLimiterOptions {
51
51
  export interface TurnLimiter {
52
52
  /** 每次 turn_end 调用。 */
53
53
  onTurnEnd(currentTurns: number): void;
54
+ /**
55
+ * 重置 steered/aborted 标志(新一轮开始)。
56
+ *
57
+ * chatMode 下 agent_settled 时调用——每轮(message → agent_settled)独立计数,
58
+ * maxTurns 不跨轮累计(续聊本质是无限轮,累计上限违背 G1)。
59
+ * 非 chatMode 不调用,行为不变(全程累计)。
60
+ */
61
+ reset(): void;
54
62
  /** 是否已发过 steer(诊断用)。 */
55
63
  readonly didSteer: boolean;
56
64
  /** 是否已 abort(诊断用)。 */
@@ -76,8 +84,14 @@ export function createTurnLimiter(opts: TurnLimiterOptions): TurnLimiter {
76
84
  }
77
85
  };
78
86
 
87
+ const reset = (): void => {
88
+ steered = false;
89
+ aborted = false;
90
+ };
91
+
79
92
  return {
80
93
  onTurnEnd,
94
+ reset,
81
95
  get didSteer(): boolean {
82
96
  return steered;
83
97
  },
@@ -33,8 +33,48 @@ export const DEFAULT_AGENT_NAME = "general-purpose";
33
33
  // 执行状态机
34
34
  // ============================================================
35
35
 
36
- /** 唯一执行状态。所有路径共用。crashed 为进程崩溃终态(重建推断)。 */
37
- export type ExecutionStatus = "running" | "done" | "failed" | "cancelled" | "crashed";
36
+ /**
37
+ * 唯一执行状态。所有路径共用。v4 B-1 两态收敛:旧 idle 折入 running、
38
+ * 旧 cancelled 折入 closed(closedReason='cancelled' 区分)。
39
+ *
40
+ * running = 活跃态。含两种子态(由派生谓词区分,见 lifecycle-predicates.ts):
41
+ * - 对话模式等待续聊(旧 idle):进程可能保活(isIdle=hasIdleTimer)或已回收
42
+ * 待冷路径 resume(isResumable=running && 无活进程句柄)。
43
+ * - 正在执行(有活进程句柄)。
44
+ *
45
+ * closed = 统一终态(done/failed/crashed/cancelled 合并)。具体关闭原因由
46
+ * {@link ClosedReason} 子枚举表达(如 user-close / gc / cancelled / parent-shutdown)。
47
+ * ExecutionRecord.closedReason 携带 L2 原因,投影层按需派生对外语义(error / ended)。
48
+ */
49
+ export type ExecutionStatus = "running" | "closed";
50
+
51
+ /**
52
+ * closed 终态的 L2 关闭原因子枚举。
53
+ *
54
+ * 与 ExecutionStatus="closed" 配合使用,表达「为什么关闭」:
55
+ * parent-shutdown — 父进程 session_shutdown 时回收子进程
56
+ * parent-fork — 父进程 fork 新 session 时清理旧子进程
57
+ * parent-new — 父进程创建新 subagent 时清理旧子进程
58
+ * user-close — 用户手动 close action(含对话模式 close)
59
+ * cancelled — 用户取消(close(force:true) / cancelBackground)
60
+ * gc — 通用完成/失败(一次执行自然结束、超时、错误等无专属 reason 的终态)
61
+ */
62
+ export type ClosedReason = 'parent-shutdown' | 'parent-fork' | 'parent-new' | 'user-close' | 'cancelled' | 'gc';
63
+
64
+ /**
65
+ * 对外四态(设计决策 10 细则 3):内部 ExecutionStatus(v4 B-1 两态)收敛为 agent
66
+ * 可理解的状态语义。真实映射只有两条:
67
+ * running → active / closed → ended(closed 统一终态,含 cancelled)。
68
+ * mapExternalState 不消费 ClosedReason——closed 恒映射 ended。
69
+ *
70
+ * waiting / error 是历史多态映射(idle→waiting / failed+crashed→error)的遗留声明:
71
+ * 对外四态联合契约不变,但当前状态机不产生这两个值。
72
+ *
73
+ * 原始 ExecutionStatus 进 list item 的 status 字段供调试;state 是对外主字段。
74
+ * 映射实现见 subagent-actions.ts mapExternalState——未来内部加态必须扩展该处,
75
+ * 漏加会在 default 分支编译报错,不影响对外契约。
76
+ */
77
+ export type ExternalState = "active" | "waiting" | "ended" | "error";
38
78
 
39
79
  /** 执行模式。background = 调用方立即拿 handle 返回,子 agent 在 detached promise 里跑。 */
40
80
  export type ExecutionMode = "background";
@@ -130,6 +170,8 @@ export type SdkEvent = {
130
170
  usage?: AgentUsage & { cost?: { total: number } };
131
171
  stopReason?: string;
132
172
  errorMessage?: string;
173
+ /** 消息角色(message_start 事件携带,user/assistant/toolResult/custom)。 */
174
+ role?: string;
133
175
  };
134
176
  assistantMessageEvent?: { type?: string; delta?: string };
135
177
  reason?: string;
@@ -218,18 +260,9 @@ export interface AgentResult {
218
260
  // ExecutionRecord —— 唯一状态对象(Core 拥有,Runtime 引用)
219
261
  // ============================================================
220
262
 
221
- /**
222
- * 所有执行路径的唯一状态源。
223
- *
224
- * 收口设计:一次执行的完整内容(text/thinking/toolCalls/usage)按 turn 收口在
225
- * `turns: Turn[]` 里。eventLog / currentActivity / result 文本均从 turns[] 派生
226
- * (getEventLog / getCurrentActivity / getFullText),不再独立存储切片或缓冲。
227
- *
228
- * 生命周期:createRecord() 创建 → updateFromEvent() 实时更新(累积进 turns)→
229
- * completeRecord() 冻结 → archive 立即移出内存(读时从 session.jsonl 重建)。
230
- *
231
- * TUI 永远拿 RecordSnapshot(.slice() 快照),不直接持此可变对象。
232
- */
263
+ // 本 section 先声明 ExecutionRecord 的组成值对象(WorktreeHandle / AliveMarker /
264
+ // PatchResult 等),ExecutionRecord 本体及其文档注释在 section 末尾。
265
+
233
266
  /**
234
267
  * worktree handle 值对象。仅 worktree:true 时持有——worktree 是独立维度,
235
268
  * 需显式开启,fork alone 不创建 worktree。
@@ -297,6 +330,18 @@ export class DirtyWorktreeError extends Error {
297
330
  }
298
331
  }
299
332
 
333
+ /**
334
+ * 所有执行路径的唯一状态源。
335
+ *
336
+ * 收口设计:一次执行的完整内容(text/thinking/toolCalls/usage)按 turn 收口在
337
+ * `turns: Turn[]` 里。eventLog / currentActivity / result 文本均从 turns[] 派生
338
+ * (getEventLog / getCurrentActivity / getFullText),不再独立存储切片或缓冲。
339
+ *
340
+ * 生命周期:createRecord() 创建 → updateFromEvent() 实时更新(累积进 turns)→
341
+ * completeRecord() 冻结 → archive 立即移出内存(读时从 session.jsonl 重建)。
342
+ *
343
+ * TUI 永远拿 RecordSnapshot(.slice() 快照),不直接持此可变对象。
344
+ */
300
345
  export interface ExecutionRecord {
301
346
  /** 唯一 ID(sync: "run-N",bg: "bg-N-xxx")。 */
302
347
  readonly id: string;
@@ -319,9 +364,33 @@ export interface ExecutionRecord {
319
364
  readonly parentRecordId: string | undefined;
320
365
  /** subagent 递归深度。顶层(主 session 直接创建)=0,每层嵌套 +1。 */
321
366
  readonly depth: number;
367
+ /**
368
+ * 对话模式标志(可持续对话 subagent)。true = 轮次完成进 idle 态(保留 record +
369
+ * worktree)等待续聊,而非一次性终态化。
370
+ * undefined/false = 一次性模式(默认,行为完全不变)。
371
+ * 向后兼容:旧 record / 旧 session 文件无此字段,按一次性模式处理。
372
+ */
373
+ readonly chatMode?: boolean;
374
+ /**
375
+ * 执行态信号(residual-fixes 设计):true = 该 record 无活进程驱动(轮终 idle /
376
+ * 重建孤儿兜底),处于「可续聊/等续聊」态——不是后台真在跑。轮终迁移
377
+ * (doFinalizeRoundToIdle)置 true,冷路径续轮(进程启动)清除;GUI 侧
378
+ * streaming/waiting 细分与 hasRunning 判据消费。缺省 falsy = 有进程或旧数据。
379
+ */
380
+ resumable?: boolean;
381
+ /**
382
+ * 空闲超时毫秒数(仅 chatMode 有意义)。覆盖默认 5min idle timeout。
383
+ * 优先级:参数 > env XYZ_SUBAGENT_IDLE_TIMEOUT_MS > 默认 300000ms。
384
+ * 向后兼容:旧 record 无此字段,按默认值处理。
385
+ */
386
+ readonly idleTimeoutMs?: number;
322
387
 
323
388
  // ── 状态(实时更新)──
324
389
  status: ExecutionStatus;
390
+ /** L2 关闭原因子枚举(仅 status="closed" 时有意义)。表达「为什么关闭」。
391
+ * 由 tryTransition(record, "closed", reason) 写入;投影层按需派生对外语义。
392
+ * 向后兼容:旧 record 无此字段,按 gc 处理(通用完成/失败)。 */
393
+ closedReason?: ClosedReason;
325
394
  /** 完整执行内容,按 turn 组织。createRecord 初始化为 [空 turn]。 */
326
395
  turns: Turn[];
327
396
  /** turn 计数(= turns.filter(closed).length,冗余存储供投影直接读)。 */
@@ -329,6 +398,49 @@ export interface ExecutionRecord {
329
398
  totalTokens: number;
330
399
  /** 运行期最近一次 error 事件的消息(getEventLog 派生 error 条目用)。 */
331
400
  lastError: string | undefined;
401
+ /**
402
+ * 对话轮次计数(仅 chatMode 有意义)。首轮运行时 = 0;每完成一轮(finalizeRoundToIdle
403
+ * 进 idle)+1。undefined 时视为 0。非 chatMode 不自增。
404
+ */
405
+ round?: number;
406
+ /**
407
+ * [增量通知] 当前轮次增量的 turns[] 起始下标(仅 chatMode 有意义;内存态记账,D4 不持久化)。
408
+ *
409
+ * - 生命周期:undefined 视为 0(首轮增量 = 全量,与改造前首轮通知逐字节一致,向后兼容旧
410
+ * record);唯一写点 onRoundSettled 第 5 步(notify 之后推进),唯一读点同回调第 2 步
411
+ * (`getFullTextFrom(record, record.roundBaseTurnIndex ?? 0)`)。非 chatMode 恒
412
+ * undefined(onRoundSettled 是 session-runner chatMode 分支专属回调)。
413
+ * - D1 滞后空 turn 防丢文本(防御性):pi 当前事件序下该形态不可达——带 usage 的
414
+ * message_end 恒先于 turn_end(@earendil-works/pi-agent-core dist/agent-loop.js
415
+ * :240/:253/:547 三处 message_end emit 均在 :131 正常路径 turn_end 之前),settle 时
416
+ * turn 全闭合。防 pi 未来事件序变化:若 settle 时刻末 turn 是滞后 message_end 开出的
417
+ * 空 turn(execution-record.ts message_end 分支经 currentTurn,需同时过两层 usage 守卫:
418
+ * session-runner.ts 转发层 `if (msg?.usage)`(bare message_end 不转发)+ execution-record.ts
419
+ * 累积层 `if (event.usage)`(bare message_end 不开 turn)),推进公式
420
+ * nextRoundBaseTurnIndex 把它留在下一轮增量内(新轮首个 text_delta 经 currentTurn 复用该
421
+ * 空 turn,复用累积被 slice 覆盖);直用 turns.length 推进会把下轮首段文本挤出 slice
422
+ * 范围静默丢失。
423
+ * - D4 不持久化:磁盘重建走 createRecord(turns 仅为初始 [emptyTurn()]),base=0 对空 turn
424
+ * 的增量派生等价为空、天然产出仅新轮增量,持久化是死数据。故不写 manifest、不参与重建。
425
+ * - pi 内部序锚定依据(R1 mitigation):@earendil-works/pi-agent-core 0.84.2
426
+ * dist/agent-loop.js :108-111(error/aborted stopReason 也先 emit turn_end 再 agent_end)
427
+ * 与 :131(正常路径 turn_end 收尾);agent_settled 在 agent_end 之后 emit,故未闭合
428
+ * turn 只可能来自滞后事件。pi 升级若改变 turn_end/agent_end 时序,onRoundSettled 推进前
429
+ * 的观测哨(末 turn 未闭合且 text 非空 → logger.warn)会留痕。
430
+ */
431
+ roundBaseTurnIndex?: number;
432
+ /**
433
+ * record 进入 idle 态的时间戳(ms)。finalizeRoundToIdle 设值;GC 定时器据此计算
434
+ * 剩余 TTL。undefined = 非 idle 态(running/closed/cancelled)或旧 record 缺失字段。
435
+ */
436
+ idleSince?: number;
437
+ /**
438
+ * close 优雅关闭标志(M2-B3)。chatMode record 运行中调 `close {force:false}` 时置 true;
439
+ * runAndFinalize 的 done 分流检查此标志——true 则终态化为 done(而非进 idle),并清标志。
440
+ * undefined/false = 正常 idle 分流(对话模式轮次完成进 idle 等续聊)。
441
+ * 仅 chatMode + running 时有意义;force:true(立即终止)不走此标志。
442
+ */
443
+ closeAfterRound?: boolean;
332
444
 
333
445
  // ── 完成 ──
334
446
  endedAt: number | undefined;
@@ -340,12 +452,30 @@ export interface ExecutionRecord {
340
452
  /** session jsonl 文件名。session 创建成功后由 session-runner.run() 回填(窗口期内 undefined)。 */
341
453
  sessionFile?: string;
342
454
 
455
+ /**
456
+ * [V2 决策 3] 子进程 pid(spawn 后由 session-runner 回填到内存 record)。
457
+ *
458
+ * 用于 lifecycle-manager 孤儿扫描(V2 §5.2 职责 4:父进程重启时按持久化 pid 扫收
459
+ * 上次崩溃遗留的孤儿)。本字段仅在内存记账,持久化留 Step 5(record
460
+ * 文件写入 pid + 启动时 scanOrphanProcesses 消费)。undefined = 尚未 spawn / 已退出。
461
+ * 向后兼容:旧 record 无此字段,按无 pid 处理(孤儿扫描跳过)。
462
+ */
463
+ pid?: number;
464
+
343
465
  /** [MF#3] worktree 模式下子 agent 改动的 patch 文件路径(worktree 外,供调用方应用)。 */
344
466
  patchFile?: string;
345
467
 
346
468
  /** worktree 隔离时的 handle(仅 worktree:true 时存在;fork alone 无此字段)。 */
347
469
  worktreeHandle?: WorktreeHandle;
348
470
 
471
+ /**
472
+ * [review round2] 该 record 创建时启用了 worktree 隔离(跨重启磁盘重建时从 session
473
+ * entry 的 worktree 标志恢复)。handle 本体不可序列化——跨重启后 worktreeHandle 恒
474
+ * undefined,续聊(冷路径 resume)须拒绝(防 cwd 静默回落主 repo 破坏隔离)。仅内存
475
+ * record 使用,与持久化无关;execute() 新建 record 不设(有真 handle 时无意义)。
476
+ */
477
+ hadWorktree?: boolean;
478
+
349
479
  // ── 控制(仅 background 持有)──
350
480
  controller: AbortController | undefined;
351
481
  }
@@ -414,8 +544,6 @@ export interface ExecuteOptions {
414
544
  signal?: AbortSignal;
415
545
  /** 主 agent 当前模型(模型解析第三层兼底)。execute 调用方从 ctx.model 传入。 */
416
546
  ctxModel?: ModelInfo;
417
- /** live 状态回流(对话流 block 实时刷新)。 */
418
- onUpdate?: (details: SubagentToolDetails) => void;
419
547
  /** background 完成回调(sync 不调)。 */
420
548
  onComplete?: (record: RecordSnapshot) => void;
421
549
  /** 是否继承父会话上下文(fork 模式,只继承上下文)。 */
@@ -424,6 +552,17 @@ export interface ExecuteOptions {
424
552
  worktree?: boolean | WorktreeHandle;
425
553
  /** 覆盖执行 cwd(默认 mainCwd)。 */
426
554
  cwd?: string;
555
+ /**
556
+ * 可持续对话模式(决策 8:独立 chatMode 标志,不扩展 ExecutionMode)。
557
+ * true = record 标记 chatMode,轮次完成进 idle 态(保留 record + worktree,等待 message 续聊);
558
+ * undefined/false = 一次性模式(默认,行为完全不变)。service.execute 透传到 createRecordForMode。
559
+ */
560
+ conversation?: boolean;
561
+ /**
562
+ * 空闲超时毫秒数(仅 conversation 模式有意义)。覆盖默认 5min idle timeout。
563
+ * 优先级:参数 > env XYZ_SUBAGENT_IDLE_TIMEOUT_MS > 默认 300000ms。
564
+ */
565
+ idleTimeoutMs?: number;
427
566
  // 注:fork 深度不从外部传入(曾暴露 parentForkDepth,改用 ALS 后 execute 内部从调用链派生,
428
567
  // 公开字段成为死字段误导调用方,已移除)。深度限制检查见 session-runner.ts 内部 RunOptions.parentForkDepth
429
568
  // (与历史残留的 types.ts RunOptions 同名不同 interface——后者已删除)。
@@ -445,12 +584,15 @@ export type ExecutionHandle = {
445
584
  // tool action 出参(外层分组,adapter 产出)
446
585
  // ============================================================
447
586
 
448
- /** list 的 item 结构(8 字段)。 */
587
+ /** list 的 item 结构。 */
449
588
  export interface SubagentListItem {
450
589
  subagentId: string;
451
590
  agent: string;
452
591
  /** 短标签(≤35 字符),来自 record.slug。旧 record 反序列化时为空串。 */
453
592
  slug: string;
593
+ /** 对外四态(决策 10 细则 3,主字段)。由 mapExternalState(status) 派生。 */
594
+ state: ExternalState;
595
+ /** 原始内部状态(调试用,供 details 展示)。 */
454
596
  status: ExecutionStatus;
455
597
  mode: ExecutionMode;
456
598
  /** 运行秒数(running 态实时计算,终态 endedAt-startedAt)。 */
@@ -459,6 +601,16 @@ export interface SubagentListItem {
459
601
  totalTokens: number;
460
602
  /** session jsonl 文件名(窗口期内可能 undefined)。 */
461
603
  sessionFile?: string;
604
+ /** 直接父 subagent record ID(顶层 record 为 undefined)。[v4 A-6] 从
605
+ * record.parentRecordId 派生,配合 A-5 直接父守卫(message/close 仅作用于直接子)。 */
606
+ parent?: string;
607
+ /** 可冷路径 resume(running 且无活进程句柄)。[v4 A-6] B-1「可续聊」对外表达,
608
+ * agent 据 list 判断哪些 running subagent 实际可续聊(vs 正在忙)。 */
609
+ resumable?: boolean;
610
+ /** L2 关闭原因子枚举(仅 status="closed" 时有意义)。[v4 A-6] SP-4 级联关闭告知
611
+ * 替代——砍 before_agent_start 注入通道后,被级联关闭的 record 经 list
612
+ * (includeFinished:true)可查,closedReason 显示 'parent-fork'/'parent-new' 等。 */
613
+ closedReason?: ClosedReason;
462
614
  }
463
615
 
464
616
  /** background 启动的内层响应(挂在 SubagentToolResult.bgResponse)。 */
@@ -481,18 +633,39 @@ export interface CancelResponse {
481
633
  cancelled: true;
482
634
  }
483
635
 
636
+ /**
637
+ * message 的内层响应(挂在 SubagentToolResult.messageResponse,决策 10 瘦身)。
638
+ *
639
+ * [R1 删除记录] 旧 PendingMessage(在途消息缓存条目,消费确认制,设计决策 6 状态×
640
+ * interrupt 映射)已随 deliverToRunning 一并删除——SP-5 upgrade 后无生产调用方,
641
+ * 配套三段消费链(push / message_start shift / redeliverPending 补投)全部不可达。
642
+ * 详见 subagent-service.ts 的删除记录注释。
643
+ */
644
+ export interface MessageResponse {
645
+ delivered: true;
646
+ }
647
+
648
+ /** close 的内层响应(挂在 SubagentToolResult.closeResponse,决策 10 瘦身)。 */
649
+ export interface CloseResponse {
650
+ closed: true;
651
+ }
652
+
484
653
  /**
485
654
  * Tool 外层出参(renderResult + LLM content JSON 同源)。
486
- * adapter 唯一产出:领域对象(bg/list/cancel 三选一)+ action/subagentId/sessionFile。
655
+ * adapter 唯一产出:领域对象(bg/list/cancel/message/close 五选一)+ action/subagentId/sessionFile。
487
656
  *
488
657
  * - background 启动 → bgResponse(subagentId 有值;sessionFile 窗口期可能 undefined)
489
658
  * - list → listResponse(最外层 subagentId/sessionFile 为 null,sessionFile 在各 item 内)
490
659
  * - cancel → cancelResponse(subagentId 有值;sessionFile 无意义,可为 null)
660
+ * - message → messageResponse(subagentId 有值;sessionFile 无意义,可为 null)
661
+ * - close → closeResponse(subagentId 有值;sessionFile 无意义,可为 null)
491
662
  */
492
663
  export type SubagentToolResult =
493
664
  | { action: "start"; subagentId: string; sessionFile: string | null; slug: string; bgResponse: BgResponse; __gui__?: GuiRenderResult }
494
665
  | { action: "list"; subagentId: null; sessionFile: null; listResponse: ListResponse; __gui__?: GuiRenderResult }
495
- | { action: "cancel"; subagentId: string; sessionFile: null; cancelResponse: CancelResponse; __gui__?: GuiRenderResult };
666
+ | { action: "cancel"; subagentId: string; sessionFile: null; cancelResponse: CancelResponse; __gui__?: GuiRenderResult }
667
+ | { action: "message"; subagentId: string; sessionFile: null; messageResponse: MessageResponse; __gui__?: GuiRenderResult }
668
+ | { action: "close"; subagentId: string; sessionFile: null; closeResponse: CloseResponse; __gui__?: GuiRenderResult };
496
669
 
497
670
  // ============================================================
498
671
  // TUI list 视图的合并 record(4 源 merge 后的形状)
@@ -507,6 +680,8 @@ export interface SubagentRecord {
507
680
  /** 短标签(≤35 字符)。磁盘重建源旧文件可能缺失→兜底空串。 */
508
681
  slug: string;
509
682
  status: ExecutionStatus;
683
+ /** L2 关闭原因子枚举(仅 status="closed" 时有意义)。SP-1 新增。 */
684
+ closedReason?: ClosedReason;
510
685
  mode: ExecutionMode;
511
686
  startedAt: number;
512
687
  /** 根 Pi session ID(session 隔离过滤用)。递归链上所有层 record 同值。 */
@@ -530,6 +705,28 @@ export interface SubagentRecord {
530
705
  sessionFile?: string;
531
706
  /** [MF#3] worktree 模式下子 agent 改动的 patch 文件路径(worktree 外,供调用方应用)。 */
532
707
  patchFile?: string;
708
+ /**
709
+ * [review round2] 创建时启用 worktree 隔离(磁盘重建源从 session entry 恢复;内存源由
710
+ * recordToSubagent 从 worktreeHandle 投影)。getRecordForAction 跨重启重建时据此拒绝续聊。
711
+ */
712
+ worktree?: boolean;
713
+ /**
714
+ * 对话轮次计数(仅 chatMode idle record 有意义)。round 仅在内存维护(doFinalizeRoundToIdle
715
+ * 递增),跨重启不恢复(round 无磁盘持久化);非对话模式 / 非 idle record 为 undefined。内存源由 recordToSubagent 从
716
+ * ExecutionRecord.round 投影。
717
+ */
718
+ round?: number;
719
+ /**
720
+ * 对话模式标志(与 ExecutionRecord.chatMode 同义;投影给 GUI 侧 streaming/waiting/done
721
+ * 细分判据——one-shot 轮终(chatMode 非 true + result 有值)显示完成态,chat 轮终显示
722
+ * 等续聊。内存源由 recordToSubagent 投影,磁盘源经 subagent-record entry 重建)。
723
+ */
724
+ chatMode?: boolean;
725
+ /**
726
+ * 执行态信号(与 ExecutionRecord.resumable 同义):true = 无活进程驱动的 running
727
+ * (轮终 idle / 重建孤儿兜底),GUI 侧据此排除「真在跑」判定。
728
+ */
729
+ resumable?: boolean;
533
730
  /** 外部 Pi 实例(进程隔离模式下由外部启动的子进程)。 */
534
731
  externalInstance?: AliveMarker;
535
732
  /** fork 模式下的 worktree handle。 */
@@ -574,6 +771,8 @@ export interface RecordSnapshot {
574
771
  /** 短标签(≤35 字符)。来自 record.slug。 */
575
772
  readonly slug: string;
576
773
  readonly status: ExecutionStatus;
774
+ /** 对话模式标志(与 ExecutionRecord.chatMode 同源)。cancel 别名判定用。 */
775
+ readonly chatMode?: boolean;
577
776
  readonly turns: number;
578
777
  readonly totalTokens: number;
579
778
  readonly startedAt: number;