@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
@@ -82,11 +82,13 @@ const MODEL_LIST_LIMIT = 20;
82
82
  // ║ 3. ctxModel (主 agent 当前模型,直接透传) ║
83
83
  // ║ ║
84
84
  // ║ 1/2 级查 registry + auth 校验;3 级无需 lookup(主 agent 在用 ║
85
- // ║ 说明 auth OK)。thinkingLevel 无指定时 undefined(model 默认) ║
85
+ // ║ 说明 auth OK)。thinkingLevel 无显式指定时兜底「模型最高可用档」║
86
+ // ║ (maxThinkingForModel,含 "max" 时用 max)——不落回 pi 默认 ║
87
+ // ║ medium,subagent 任务需要最大推理深度。 ║
86
88
  // ║ ║
87
89
  // ║ 显式指定但 lookup/auth 失败 → 抛错(不静默降级到主 agent, ║
88
90
  // ║ 因为用户明确要求了某个 model,降级会造成「以为用了 X 实际用 Y」║
89
- // ╚═══════════════════════════════════════════════════════════════╝
91
+ // ╚════════════════════════════════════════════════════════════════╝
90
92
  *
91
93
  * @param agentConfig agent .md 解析结果(查 model override + thinkingLevel)
92
94
  * @param modelRegistry registry(仅 override 路径用)
@@ -120,11 +122,12 @@ export function resolveModel(
120
122
  );
121
123
  }
122
124
 
123
- // 3. 主 agent model(直接透传,thinkingLevel override 或 undefined)
125
+ // 3. 主 agent model(直接透传)。无显式 thinking 兜底最高可用档。
124
126
  if (ctxModel) {
125
127
  return {
126
128
  model: ctxModel,
127
- thinkingLevel: paramOverride?.thinkingLevel ?? agentConfig?.thinkingLevel,
129
+ thinkingLevel: paramOverride?.thinkingLevel ?? agentConfig?.thinkingLevel
130
+ ?? maxThinkingForModel(ctxModel),
128
131
  };
129
132
  }
130
133
 
@@ -166,10 +169,28 @@ function lookupAndResolve(
166
169
  }
167
170
  return {
168
171
  model,
169
- thinkingLevel: resolveThinkingLevel(model, requestedThinking),
172
+ // 无显式请求时兜底「模型最高可用档」(不落 pi 默认 medium)。
173
+ thinkingLevel: resolveThinkingLevel(model, requestedThinking ?? maxThinkingForModel(model)),
170
174
  };
171
175
  }
172
176
 
177
+ /**
178
+ * subagent 默认 thinking:模型最高可用档。
179
+ *
180
+ * - thinkingLevelMap 配置了级别 → map 中最高档(模型显式配置 "max" 时即 max)
181
+ * - reasoning 但无 map → "xhigh"(pi 合法最高档;"max" 非 pi 合法值——
182
+ * spawn 侧 model:"max" 后缀会被 pi 判为非法 thinking 后缀导致 model 解析失败,
183
+ * 故无 map 时不能直接用 "max")
184
+ * - 非 reasoning 模型 → undefined(不支持 thinking)
185
+ */
186
+ function maxThinkingForModel(
187
+ model: { reasoning: boolean; thinkingLevelMap?: Record<string, unknown> },
188
+ ): string | undefined {
189
+ const levels = availableThinkingLevels(model);
190
+ if (levels.length > 0) return levels[levels.length - 1];
191
+ return model.reasoning ? "xhigh" : undefined;
192
+ }
193
+
173
194
  /**
174
195
  * 解析 "provider/modelId" 并查 registry。
175
196
  *
@@ -8,10 +8,20 @@
8
8
  // - 通过 pi.sendMessage({ deliverAs:"followUp", triggerTurn:true }) 注入——
9
9
  // 当前 turn 结束后唤醒父 agent 处理结果(followUp 不打断 streaming、不锁滚动)
10
10
 
11
- /** 一条待发送的完成通知记录。 */
11
+ /**
12
+ * 一条待发送的完成通知记录。
13
+ * SP-1: done/failed/crashed 合并为 closed + closedReason L2 子枚举。
14
+ */
12
15
  export interface BgNotifyRecord {
13
16
  id: string;
14
- status: "done" | "failed" | "cancelled";
17
+ /**
18
+ * 完成状态。v4 B-1 两态收敛:closed(终态,含 cancelled,closedReason 表达 L2 原因)
19
+ * 或 running(对话模式轮次完成,旧 idle 折入 running,携带本轮结果送回主 agent)。
20
+ * toNotifyRecord 守卫放行后经此联合穷尽。
21
+ */
22
+ status: "running" | "closed";
23
+ /** L2 关闭原因子枚举(仅 status="closed" 时有意义)。供通知文案按需展示。 */
24
+ closedReason?: import("./types.ts").ClosedReason;
15
25
  agent: string;
16
26
  /** 执行所用 model(RecordSnapshot.model),用于完成通知显示。 */
17
27
  model?: string;
@@ -19,10 +29,25 @@ export interface BgNotifyRecord {
19
29
  error?: string;
20
30
  startedAt: number;
21
31
  endedAt: number | undefined;
32
+ /**
33
+ * 对话轮次计数(仅 idle 有意义)。dedup key 按 `id:round` 去重——对话模式每轮 round
34
+ * 不同,60s 内多轮不被吞;非 chatMode round 恒定(0/undefined),key 同旧 id 行为不变。
35
+ */
36
+ round?: number;
22
37
  /** [MF#1] worktree 模式下子 agent 改动的 patch 路径(worktree 外,cleanup 后留存)。
23
38
  * done 时通知文本显式提示 `git apply`,否则 background 子 agent 在隔离 worktree 的改动
24
39
  * 会静默丢失——父 LLM 不知 patch 路径,无法应用。 */
25
40
  patchFile?: string;
41
+ /** [wave2] 子 agent session 文件路径(增量语义的全文恢复通道)。
42
+ * 仅 chatMode 透传(toNotifyRecord 条件透传)——轮次/完成通知末尾追加
43
+ * "Full transcript: <path>" 指针行,父 LLM 可按需读全文;one-shot 不透传,
44
+ * 通知输出逐字节不变。缺失时 buildLlmContent 省略整行。 */
45
+ sessionFile?: string;
46
+ /** [C-2] close 终态通知的轮次统计(文案 "completed after N rounds." 用)。
47
+ * 仅 chatMode close 语义(notifyClosed)构造时携带——此时 dedup 身份 round 已被
48
+ * 置 undefined(与轮次通知的 id:round key 区分,终态不被吞),轮数改由本字段进
49
+ * 文案。one-shot 完成通知不设置,文案保持 "completed. Result:" 逐字节(G4)。 */
50
+ totalRounds?: number;
26
51
  }
27
52
 
28
53
  /** notifier 依赖的 pi 最小接口(解耦,便于测试)。 */
@@ -108,9 +133,12 @@ export class BgNotifier {
108
133
  if (now - ts >= DEDUP_TTL_MS) this.dedup.delete(id);
109
134
  }
110
135
  }
111
- const lastSeen = this.dedup.get(record.id);
136
+ // dedup key:idle(对话模式每轮完成)按 id:round 去重——不同轮次不互相掩蔽;
137
+ // 非 idle(closed/cancelled)round 恒定 undefined,key 同旧行为不变。
138
+ const dedupKey = record.round != null ? `${record.id}:${record.round}` : record.id;
139
+ const lastSeen = this.dedup.get(dedupKey);
112
140
  if (lastSeen !== undefined && now - lastSeen < DEDUP_TTL_MS) return;
113
- this.dedup.set(record.id, now);
141
+ this.dedup.set(dedupKey, now);
114
142
 
115
143
  this.pending.push(record);
116
144
 
@@ -209,18 +237,47 @@ export class BgNotifier {
209
237
  private buildLlmContent(record: BgNotifyRecord): string {
210
238
  const agent = record.agent;
211
239
  const id = record.id;
240
+ // [wave2 指针行] 增量语义下轮次通知只携带本轮增量,异步 flush 窗口丢失时不可重发
241
+ //(见 subagent-service.ts onRoundSettled 注释),恢复通道是 session 文件全文。
242
+ // 仅 chatMode 透传 sessionFile,one-shot 通知不含该行;cancelled/gc-failed 不追加
243
+ //(无成功结果可读,指针无意义);缺失时省略整行(追加空串 = 输出逐字节不变)。
244
+ const transcriptPointer = record.sessionFile
245
+ ? `\n\nFull transcript: ${record.sessionFile}`
246
+ : "";
212
247
  switch (record.status) {
213
- case "done": {
214
- const base = `Subagent "${agent}" (${id}) completed. Result:\n${record.result ?? "(empty)"}`;
248
+ case "closed": {
249
+ // v4 B-1: closed 统一终态(含 cancelled)。按 closedReason 派生通知文案。
250
+ const reason = record.closedReason ?? "gc";
251
+ if (reason === "cancelled") {
252
+ return `Subagent "${agent}" (${id}) cancelled.`;
253
+ }
254
+ // 失败场景(gc + 有 error):展示错误。判定必须先于 patchFile——失败轮也会写
255
+ // patchFile(doFinalizeRecord Step 0 对 worktreeHandle 无条件 collectPatch),若
256
+ // patch 分支在前,gc 失败 + worktree 并存时 LLM 被告知 completed。顺序与三处
257
+ // 同构契约的另外两处一致(shared/subagent.ts deriveClosedDisplay、
258
+ // bg-notify-render.ts renderRecordLines:cancelled → gc+error → patch/result)。
259
+ if (record.error && reason === "gc") {
260
+ return `Subagent "${agent}" (${id}) failed: ${record.error}`;
261
+ }
262
+ // 成功完成(user-close)或通用结束(gc/parent-shutdown 等):展示结果。
263
+ // [C-2] chatMode close 终态通知附轮次统计(设计 D2 路径①"completed after N rounds")。
264
+ // totalRounds 仅 close 语义携带(notifyClosed);one-shot 完成通知不设置(round 无轮次
265
+ // 语义),文案保持 "completed. Result:" 逐字节(G4 硬约束,one-shot 字节锁测试锚定)。
266
+ const roundsSuffix =
267
+ record.totalRounds != null && record.totalRounds > 0
268
+ ? ` after ${record.totalRounds} round${record.totalRounds === 1 ? "" : "s"}`
269
+ : "";
270
+ const base = `Subagent "${agent}" (${id}) completed${roundsSuffix}. Result:\n${record.result ?? "(empty)"}`;
215
271
  if (record.patchFile) {
216
- return `${base}\n\nThis subagent ran in an isolated worktree; its file changes were captured as a patch:\n ${record.patchFile}\nTo bring these changes into the current repo, run: \`git apply ${record.patchFile}\``;
272
+ // [wave2 review] return 拆行:模板串内不可直接换行(会改变输出内容),提取 patchHint 中转变量
273
+ const patchHint = `\n\nThis subagent ran in an isolated worktree; its file changes were captured as a patch:\n ${record.patchFile}\nTo bring these changes into the current repo, run: \`git apply ${record.patchFile}\``;
274
+ return `${base}${patchHint}${transcriptPointer}`;
217
275
  }
218
- return base;
276
+ return `${base}${transcriptPointer}`;
219
277
  }
220
- case "failed":
221
- return `Subagent "${agent}" (${id}) failed: ${record.error ?? "(unknown error)"}`;
222
- case "cancelled":
223
- return `Subagent "${agent}" (${id}) cancelled.`;
278
+ case "running":
279
+ // v4 B-1: 对话模式轮次完成(旧 idle 折入 running):携带本轮结果送回主 agent,等待下一轮 message。
280
+ return `Subagent "${agent}" (${id}) finished a round. Reply:\n${record.result ?? "(empty)"}${transcriptPointer}`;
224
281
  }
225
282
  }
226
283
 
@@ -51,12 +51,32 @@ function isGenericRuntime(execPath: string): boolean {
51
51
  * 2. execPath 非通用运行时(pi standalone binary)→ <execPath> <userArgs>
52
52
  * 3. 通用运行时但无可用脚本路径 → "pi" <userArgs>(依赖 PATH 中可找到 pi)
53
53
  */
54
+ // [perf] 决策链只依赖 process.argv[1] / process.execPath(进程内恒定),其中唯一的
55
+ // 磁盘探测(existsSync)结果 memo 一次——每次 spawn 免重复 syscall。memo 键含
56
+ // argv[1] 值:测试 mock 切换 argv[1] 模拟不同运行时时自动失效重算。
57
+ let scriptExistsCache: { script: string | undefined; exists: boolean } | undefined;
58
+
59
+ /** process.argv[1] 是真实磁盘脚本(非 bun 虚拟路径)且存在。 */
60
+ function currentScriptExists(): boolean {
61
+ const currentScript = process.argv[1];
62
+ if (scriptExistsCache === undefined || scriptExistsCache.script !== currentScript) {
63
+ scriptExistsCache = {
64
+ script: currentScript,
65
+ exists:
66
+ currentScript !== undefined &&
67
+ !currentScript.startsWith(BUN_VIRTUAL_PREFIX) &&
68
+ fs.existsSync(currentScript),
69
+ };
70
+ }
71
+ return scriptExistsCache.exists;
72
+ }
73
+
54
74
  export function getPiInvocation(userArgs: string[]): PiInvocation {
55
75
  const currentScript = process.argv[1];
56
76
  const isBunVirtualScript = currentScript?.startsWith(BUN_VIRTUAL_PREFIX);
57
77
 
58
78
  // 分支 1:有真实脚本路径 → 复现启动方式(node <pi-script> <args>)
59
- if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
79
+ if (currentScript && !isBunVirtualScript && currentScriptExists()) {
60
80
  return { command: process.execPath, args: [currentScript, ...userArgs] };
61
81
  }
62
82
 
@@ -0,0 +1,118 @@
1
+ // src/execution/record-entry.ts
2
+ //
3
+ // W16 [D4]:subagent record 自描述持久化 entry 的形态权威。
4
+ //
5
+ // pi 文件(session JSONL)是扩展数据持久化权威:record 状态每次迁移都经
6
+ // pi.appendEntry 落一条自描述完整快照(字段即 SubagentRecord),读取方无需
7
+ // 逆向解析 toolCall/toolResult。内存 record-store 保持运行时权威,entry 是
8
+ // 重建源(两者不冲突)。
9
+ //
10
+ // customType 与既有 `subagent-identity`(session 文件首行身份 entry)同族命名
11
+ //(连字符风格);写点见 record-store.ts 的状态迁移点(register/archive/
12
+ // reportRecordTransition),custom entry 由 pi 写进 session JSONL,不进 LLM context。
13
+
14
+ import type {
15
+ AgentEventLogEntry,
16
+ ClosedReason,
17
+ DisplayItem,
18
+ ExecutionMode,
19
+ ExecutionStatus,
20
+ SubagentRecord,
21
+ } from "./types.ts";
22
+
23
+ /** 自描述 record entry 的 customType。写点字面量与本常量的等值由
24
+ * __tests__/record-store.test.ts 断言钉住(消费方引用本常量,勿用裸字符串)。 */
25
+ export const SUBAGENT_RECORD_CUSTOM_TYPE = "subagent-record";
26
+
27
+ /**
28
+ * `subagent-record` entry 的 data schema(v1)。
29
+ *
30
+ * = 完整 SubagentRecord 快照(GUI 侧列表/详情需要的全部持久化字段)+ 版本号。
31
+ * 显式排除三个非持久化字段(与 SubagentRecord 的差集):
32
+ * - currentActivity:running 时的瞬时流态,重开 session 无重建价值;
33
+ * - externalInstance:跨重启探活态(pid/startedAt),由 .alive sidecar 重建;
34
+ * - worktreeHandle:不可 JSON 序列化的运行时句柄(布尔投影 worktree 保留)。
35
+ *
36
+ * undefined 字段经 JSON.stringify 自然缺省(与 SubagentRecord 重建侧语义一致)。
37
+ */
38
+ export interface SubagentRecordEntryData {
39
+ /** schema 版本(W16 起 v1)。消费方按 v 判别解析,不认识的版本跳过而非猜测。 */
40
+ v: 1;
41
+ id: string;
42
+ agent: string;
43
+ /** 任务提示词(详情面板置顶展示)。 */
44
+ task: string;
45
+ /** 短标签(≤35 字符)。 */
46
+ slug: string;
47
+ status: ExecutionStatus;
48
+ /** L2 关闭原因(仅 status="closed" 时有意义)。 */
49
+ closedReason?: ClosedReason;
50
+ mode: ExecutionMode;
51
+ startedAt: number;
52
+ /** 根 Pi session ID(session 隔离过滤用)。 */
53
+ rootSessionId: string | undefined;
54
+ /** 直接父 subagent record ID(层级树构建用)。顶层为 undefined。 */
55
+ parentRecordId: string | undefined;
56
+ /** subagent 递归深度。顶层 = 0。 */
57
+ depth: number;
58
+ endedAt: number | undefined;
59
+ /** turn 计数。 */
60
+ turns: number;
61
+ totalTokens: number;
62
+ model: string;
63
+ thinkingLevel: string | undefined;
64
+ /** 详情事件日志(/subagents 详情面板)。 */
65
+ eventLog: AgentEventLogEntry[];
66
+ /** 从 turns[] 派生的展示项。 */
67
+ displayItems: DisplayItem[];
68
+ result?: string;
69
+ error?: string;
70
+ sessionFile?: string;
71
+ /** [MF#3] worktree 模式改动 patch 文件路径。 */
72
+ patchFile?: string;
73
+ /** 创建时是否启用 worktree 隔离。 */
74
+ worktree?: boolean;
75
+ /** 对话轮次计数(仅 chatMode 有意义;round+1 由轮终迁移写点携带)。 */
76
+ round?: number;
77
+ /**
78
+ * 对话模式标志(residual-fixes):chat 与否——GUI 侧 done/waiting 细分判据
79
+ * (one-shot 轮终 chatMode=false + result 有值 → 完成态)。register 起写入显式值
80
+ * (one-shot 为显式 false);v1 前存量 entry 缺省,消费端按保守方向处理。
81
+ */
82
+ chatMode?: boolean;
83
+ /** 执行态信号(residual-fixes):true = 无活进程驱动的 running(轮终/孤儿兜底)。 */
84
+ resumable?: boolean;
85
+ }
86
+
87
+ /** SubagentRecord → 自描述 entry data(快照投影,不 mutate 源)。 */
88
+ export function toSubagentRecordEntry(record: SubagentRecord): SubagentRecordEntryData {
89
+ return {
90
+ v: 1,
91
+ id: record.id,
92
+ agent: record.agent,
93
+ task: record.task,
94
+ slug: record.slug,
95
+ status: record.status,
96
+ closedReason: record.closedReason,
97
+ mode: record.mode,
98
+ startedAt: record.startedAt,
99
+ rootSessionId: record.rootSessionId,
100
+ parentRecordId: record.parentRecordId,
101
+ depth: record.depth,
102
+ endedAt: record.endedAt,
103
+ turns: record.turns,
104
+ totalTokens: record.totalTokens,
105
+ model: record.model,
106
+ thinkingLevel: record.thinkingLevel,
107
+ eventLog: record.eventLog,
108
+ displayItems: record.displayItems,
109
+ result: record.result,
110
+ error: record.error,
111
+ sessionFile: record.sessionFile,
112
+ patchFile: record.patchFile,
113
+ worktree: record.worktree,
114
+ round: record.round,
115
+ chatMode: record.chatMode,
116
+ resumable: record.resumable,
117
+ };
118
+ }