@zhushanwen/pi-subagent-workflow 8.14.7 → 9.0.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 (58) hide show
  1. package/README.md +6 -5
  2. package/package.json +11 -7
  3. package/relay/relay.mjs +45 -2
  4. package/skills/workflow-script-format/SKILL.md +2 -56
  5. package/src/host/__tests__/inflight-reporter.test.ts +26 -1
  6. package/src/host/__tests__/pi-host.test.ts +418 -4
  7. package/src/host/inflight-reporter.ts +13 -10
  8. package/src/host/pi-host.ts +205 -17
  9. package/src/index.ts +21 -72
  10. package/src/injectors/__tests__/engine-awareness.test.ts +236 -33
  11. package/src/injectors/__tests__/helpers/injector-test-mocks.ts +15 -7
  12. package/src/injectors/__tests__/workflow-list-injector.test.ts +447 -40
  13. package/src/injectors/resource-list-injector.ts +103 -32
  14. package/src/injectors/subagent-list-injector.ts +4 -28
  15. package/src/injectors/workflow-list-injector.ts +8 -4
  16. package/src/interface/__tests__/capture-tool.ts +56 -0
  17. package/src/interface/__tests__/commands-resume.test.ts +103 -0
  18. package/src/interface/__tests__/tool-prompt-contract.test.ts +361 -0
  19. package/src/interface/__tests__/tool-render.test.ts +105 -2
  20. package/src/interface/__tests__/tool-subagents.test.ts +24 -26
  21. package/src/interface/__tests__/tool-workflow-resume.test.ts +227 -0
  22. package/src/interface/__tests__/tool-workflow-script.test.ts +557 -0
  23. package/src/interface/__tests__/tool-workflow.test.ts +884 -0
  24. package/src/interface/bg-notify-render.ts +18 -9
  25. package/src/interface/command-actions.ts +22 -17
  26. package/src/interface/commands.ts +58 -12
  27. package/src/interface/format.ts +50 -16
  28. package/src/interface/id-preview.ts +4 -4
  29. package/src/interface/subagent-actions.ts +18 -50
  30. package/src/interface/subagent-tool.ts +26 -18
  31. package/src/interface/subagents.ts +5 -11
  32. package/src/interface/tool-render.ts +14 -30
  33. package/src/interface/tool-shared.ts +55 -12
  34. package/src/interface/tool-subagents.ts +21 -11
  35. package/src/interface/tool-workflow-script.ts +52 -72
  36. package/src/interface/tool-workflow.ts +345 -139
  37. package/src/interface/views/WorkflowsView.ts +10 -7
  38. package/src/interface/views/__tests__/{WorkflowsView-signature.test.ts → WorkflowsView.test.ts} +155 -11
  39. package/src/interface/views/detail-content.ts +3 -5
  40. package/src/jsonl-run-store.ts +656 -544
  41. package/src/model-events.ts +35 -0
  42. package/src/session-lifecycle.ts +376 -85
  43. package/src/subagents-events.ts +56 -0
  44. package/src/workflow-events.ts +285 -200
  45. package/src/workflow-notify.ts +456 -0
  46. package/src/injectors/__tests__/engine-section-stability.test.ts +0 -353
  47. package/src/injectors/__tests__/model-list-injector.test.ts +0 -136
  48. package/src/injectors/__tests__/subagent-list-injector.test.ts +0 -376
  49. package/src/interface/__tests__/detectors.test.ts +0 -161
  50. package/src/interface/__tests__/subagent-tool-prompt.test.ts +0 -183
  51. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +0 -219
  52. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +0 -251
  53. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +0 -215
  54. package/src/interface/__tests__/workflow-state-file-exposure.test.ts +0 -38
  55. package/src/interface/__tests__/workflow-tool-prompt.test.ts +0 -129
  56. package/src/interface/helpers.ts +0 -272
  57. package/src/interface/views/__tests__/detail-content-session-file.test.ts +0 -73
  58. package/src/interface/views/__tests__/record-progress.test.ts +0 -235
@@ -18,7 +18,11 @@
18
18
  // project/workspace 根不在壳侧提供——core 消费方按 workspaceRoot 自行推导
19
19
  // (u0-data-discovery 波次接注入消费)。C5⑥ 起 agents kind 追加第 4 根:
20
20
  // core 包一级父目录(source "npm")——core agents/ 资产进 pi 发现面,见
21
- // corePackageNpmRoot 注释。
21
+ // corePackageNpmRoot 注释。[P5 D4-2] agents/workflows 的 npm/npm-dev 两根
22
+ // 形态感知(taiji 态指 agentDir 父目录 = 实际安装目录,独立态保留 agentDir
23
+ // 直拼),且 core 根对 workflows kind 同样注入(staged 副本入扫描面)——
24
+ // 见 agentDirKindRoots 注释。skills/engines 的同构 npm 槽错位不在 P5 范围
25
+ // (D4 发现层 = agents/workflows 注入面;skills 另有「刻意不补」既有决策)。
22
26
  // - countActiveFromEntries 适配:pi 侧真函数返回 CountActiveResult 对象,core
23
27
  // 端口契约是 number(core 消费面只读 .count,notify-ports.ts 契约注释)——
24
28
  // foundation 单元登记给本单元的适配责任。
@@ -27,6 +31,7 @@
27
31
  // 是结构超集成员,多不碍兼容)——结构兼容由本注入点 typecheck 守护,上游签名
28
32
  // 漂移即红(notify-ports.ts「闭包红线」段)。
29
33
 
34
+ import { existsSync } from "node:fs";
30
35
  import { createRequire } from "node:module";
31
36
  import { dirname, join } from "node:path";
32
37
  import { fileURLToPath } from "node:url";
@@ -81,28 +86,40 @@ function corePackageNpmRoot(): string | undefined {
81
86
 
82
87
  /** agents/workflows 共享的 agentDir 派生根(末级目录名由 kind 决定)。
83
88
  * 顺序与 source 标签逐项对齐 resource-discovery.ts buildScanTargets 的
84
- * user-pi → npm → npm-dev 段(根列表按优先级低→高排列,D2 语义边界)。 */
89
+ * user-pi → npm → npm-dev 段(根列表按优先级低→高排列,D2 语义边界)。
90
+ *
91
+ * [P5 D4-2 扫描根修正] npm/npm-dev 两根形态感知:taiji 宿主注入态改指 taiji
92
+ * 实际安装目录(agentDir 的父目录——taiji 布局迁移后 npm/extensions 是 agentDir
93
+ * 的 sibling,对齐 runtime pi-paths.ts getNpmDir/getExtensionsDir 的推导),独立
94
+ * pi 形态保留 getAgentDir() 派生的 pi 语义根(现状即正确)。 */
85
95
  function agentDirKindRoots(kind: "agents" | "workflows"): DiscoveryRoot[] {
86
96
  const agentDir = getAgentDir();
97
+ // taiji 态 npm/extensions 布局 = agentDir 的 sibling(<dataDir>/npm、
98
+ // <dataDir>/extensions,agentDir = <dataDir>/agent);旧 agentDir 直拼形态在
99
+ // taiji 态恒为不存在目录——taiji 形态下 npm 安装位与 staged 包发现面恒空的
100
+ // 病灶根因。已知误判面(有意接受,登记 deviations):独立态用户以 -e 手动加载
101
+ // 本扩展时 argv 同样有 --extension,npm 槽根指到不存在的 <agentDir> 父目录 →
102
+ // 扫描静默空(损失 = 罕见场景的 npm 安装位发现;agentDir/npm 本就非 pi 官方布局)。
103
+ const installBase = isTaijiHostInjected() ? dirname(agentDir) : agentDir;
87
104
  const roots: DiscoveryRoot[] = [
88
105
  // 1. user .pi/agent/<kind>/
89
106
  { dir: join(agentDir, kind), source: "user-pi" },
90
- // 2. npm global: agentDir/npm/node_modules/*/<pkg>/
91
- { dir: join(agentDir, "npm", "node_modules"), source: "npm" },
92
- // 3. npm dev symlink: agentDir/extensions/*/<pkg>/
93
- { dir: join(agentDir, "extensions"), source: "npm-dev" },
107
+ // 2. npm global: <installBase>/npm/node_modules/*/<pkg>/
108
+ { dir: join(installBase, "npm", "node_modules"), source: "npm" },
109
+ // 3. npm dev symlink: <installBase>/extensions/*/<pkg>/
110
+ { dir: join(installBase, "extensions"), source: "npm-dev" },
94
111
  ];
95
- // 4. core 包根(C5⑥,仅 agents kind):追加在既有 npm 根之后——同标签多根依注入
96
- // 序扫描 + last-writer-wins,core(随本包依赖分发的新模板)遮蔽同 agentDir 内
97
- // 旧版残留副本;序位仍在 user 级之上、npm-dev/project 级之下(红线 1)。
98
- // workflows kind 刻意不注入:<available_workflows> 的 <location> 是 CA2 快照
99
- // 不豁免面(红线 8 豁免仅限 10 内置 agent 角色路径前缀),注入会翻转内置
100
- // workflow 的胜出路径。
101
- if (kind === "agents") {
102
- const coreNpmRoot = corePackageNpmRoot();
103
- if (coreNpmRoot !== undefined) {
104
- roots.push({ dir: coreNpmRoot, source: "npm" });
105
- }
112
+ // 4. core 包根(C5⑥ + P5):agents/workflows 两 kind 都追加在既有 npm 根之后——
113
+ // 同标签多根依注入序扫描 + last-writer-wins,core(随本包依赖分发的新模板)
114
+ // 遮蔽同安装位内旧版残留副本;序位仍在 user 级之上、project 级之下(红线 1)。
115
+ // [P5] workflows kind 的「刻意不注入」禁区拆除:原顾虑 = <available_workflows>
116
+ // 的 <location> 是 CA2 快照不豁免面(红线 8 豁免仅限 agent 角色路径前缀)。
117
+ // P-C6 实施期核查裁决:CA2 锚定测试为 fixture 输入驱动(不消费运行时路径),
118
+ // turn 间字节稳定由工厂渲染缓存 + 码点序构造性保证——staged 绝对路径渲染
119
+ // <location> 不破坏快照稳定性,内置 workflow 因此可列出、路径可派发。
120
+ const coreNpmRoot = corePackageNpmRoot();
121
+ if (coreNpmRoot !== undefined) {
122
+ roots.push({ dir: coreNpmRoot, source: "npm" });
106
123
  }
107
124
  return roots;
108
125
  }
@@ -188,9 +205,180 @@ export function createPiHostServices(): HostServices {
188
205
  engines: engineRoots(),
189
206
  };
190
207
  },
208
+
209
+ // [D2 扩展加载显式化] 孙进程扩展路径集(per-host 常量,双形态解析见
210
+ // resolveGrandchildExtensionPaths 注释)。每次调用现解析(P-C2 惰性求值:
211
+ // configureCore 只挂函数引用,取值发生在 core run 派发期)。
212
+ extensionPaths(): string[] {
213
+ return resolveGrandchildExtensionPaths(process.argv);
214
+ },
191
215
  };
192
216
  }
193
217
 
218
+ // ── [D2 扩展加载显式化] 孙进程扩展白名单注入源(双形态) ──
219
+
220
+ /**
221
+ * 孙进程(subagent 任务子进程)schema 强制所需的最小扩展集(P-C5 起步裁决:
222
+ * 仅 structured-output)。全量 staged 下发会让孙进程加载全部无关扩展——每包都
223
+ * 可能注册工具/注入提示词,孙进程的工具面与 system prompt 行为不可控。
224
+ *
225
+ * 白名单组成唯一登记处(runtime 侧 getExtensionPaths 全量下发、本常量收窄,
226
+ * 无第二份判据副本——扩白名单只改此处并同步 pi-host.test.ts 用例,以及
227
+ * spawn-runner.ts 断言②的 SCHEMA_ENFORCEMENT_EXTENSION_SEGS 同判据两端)。
228
+ *
229
+ * 每条 = 路径段序列(`/`|`\` 分段后连续匹配)。两形态别名:
230
+ * - `@zhushanwen/pi-structured-output`:npm staged 布局
231
+ * `.../node_modules/@zhushanwen/pi-structured-output[/index.js]`。
232
+ * - `universal/structured-output`:dev 源码布局 `.../extensions/universal/structured-output`
233
+ * (dev 下 runtime 下发的 --extension 是源码目录,无 @zhushanwen scope 段——
234
+ * 只认 npm 布局时孙进程扩展集恒空,schema 拦截链静默失效)。
235
+ * 选型:两段连续匹配(分组目录名 + 包目录名)而非尾段单匹配 `structured-output`
236
+ * ——尾段会撞任意同名无关目录(如其他包内的同名子目录);两段含本仓 extensions
237
+ * 固定分组名 `universal`,且 --extension 值来源可控(runtime extension-service
238
+ * 下发 / pi CLI flags),实际碰撞面可忽略。
239
+ */
240
+ const GRANDCHILD_EXTENSION_PKG_SEGMENTS: readonly (readonly string[])[] = [
241
+ ["@zhushanwen", "pi-structured-output"],
242
+ ["universal", "structured-output"],
243
+ ];
244
+
245
+ /** npm 布局包名(peerDep 解析锚点与诊断文案用;匹配判据见上段集)。 */
246
+ const GRANDCHILD_EXTENSION_NPM_PKG = "@zhushanwen/pi-structured-output";
247
+
248
+ /**
249
+ * 孙进程扩展路径集解析(设计 D2 双形态;argv 与 peerDep 解析器经参数注入,纯函数
250
+ * 可测——HostServices 方法闭包传 process.argv):
251
+ * 1. taiji 宿主形态:主 pi 进程 argv 的 `--extension` 全量值 = runtime
252
+ * extension-service 经 spawn argv 下发的 staged 集——按白名单收窄。该形态
253
+ * 判据 = argv 有 `--extension`(taiji spawn 恒带)。
254
+ * 2. 独立 pi 形态(npm 安装、无 taiji runtime):从包自身 optional peerDep 解析
255
+ * structured-output sibling 路径(安装了即解析、未安装则空)。
256
+ * e2e 门:resolveE2eFaux 命中(faux-llm-ext.ts 在盘)时追加进结果末尾——见
257
+ * resolveE2eFauxExtensionPath 注释。双源皆空且无 e2e faux = 空数组(不炸;带
258
+ * schema run 的 fail-fast 属 H3 断言,非本通道职责)。
259
+ */
260
+ export function resolveGrandchildExtensionPaths(
261
+ argv: readonly string[],
262
+ resolvePeer: () => string | undefined = resolveStructuredOutputPeer,
263
+ resolveE2eFaux: () => string | undefined = resolveE2eFauxExtensionPath,
264
+ ): string[] {
265
+ const stagedAll = collectExtensionFlagValues(argv);
266
+ const whitelisted = stagedAll.filter(isGrandchildExtensionPath);
267
+ const e2eFaux = resolveE2eFaux();
268
+ if (whitelisted.length > 0 || e2eFaux !== undefined) {
269
+ return e2eFaux !== undefined ? [...whitelisted, e2eFaux] : whitelisted;
270
+ }
271
+ const peer = resolvePeer();
272
+ return peer !== undefined ? [peer] : [];
273
+ }
274
+
275
+ /**
276
+ * e2e harness 专用的孙进程扩展放行门(faux 轨装配)。
277
+ *
278
+ * faux 轨(e2e/fixtures/launch-app-real.ts seedFauxDataDir)的孙进程 LLM 演员注入
279
+ * 靠 `<agentDir>/extensions/faux-llm-ext.ts` 自动发现装载——[D2 扩展加载显式化]
280
+ * 孙进程恒 `--no-extensions`(自动发现全关)+ 仅加载本文件白名单集后,该路径被
281
+ * 排除,孙进程报 `Model "faux/..." not found`。faux-llm-ext 是 e2e harness 复制进
282
+ * 临时数据目录的测试 provider 壳(非生产扩展,路径也不经主 pi argv——白名单
283
+ * filter 无法命中,须在此主动解析),故按装配归属放行:
284
+ * 仅 `process.env.TAIJI_E2E === "1"`(e2e harness 独有 env,生产进程无此值——
285
+ * 白名单收窄语义对生产不变)且文件在盘时返回该路径,由
286
+ * resolveGrandchildExtensionPaths 追加进孙进程显式 `--extension` 集。
287
+ */
288
+ function resolveE2eFauxExtensionPath(): string | undefined {
289
+ if (process.env.TAIJI_E2E !== "1") return undefined;
290
+ const fauxExtPath = join(getAgentDir(), "extensions", "faux-llm-ext.ts");
291
+ return existsSync(fauxExtPath) ? fauxExtPath : undefined;
292
+ }
293
+
294
+ /** 路径是否命中孙进程扩展白名单:路径段序列包含白名单任一段集的连续匹配
295
+ * (目录形态与入口文件形态都命中——末尾越界段取 undefined 不等,天然界内;
296
+ * 前缀相似目录如 `pi-structured-output-lookalike` 因段值不等不误判)。 */
297
+ function isGrandchildExtensionPath(p: string): boolean {
298
+ const segs = p.split(/[\\/]/).filter((s) => s.length > 0);
299
+ return GRANDCHILD_EXTENSION_PKG_SEGMENTS.some((wanted) =>
300
+ segs.some((_, i) => wanted.every((w, j) => segs[i + j] === w)),
301
+ );
302
+ }
303
+
304
+ /**
305
+ * 独立形态回退源:optional peerDep sibling 解析(package.json 锚点 → 包根目录)。
306
+ * 解析失败(未安装 / 布局异常)= undefined,不炸——空集回退。
307
+ */
308
+ function resolveStructuredOutputPeer(): string | undefined {
309
+ try {
310
+ const require = createRequire(import.meta.url);
311
+ return dirname(require.resolve(`${GRANDCHILD_EXTENSION_NPM_PKG}/package.json`));
312
+ } catch (err) {
313
+ getLogger("pi-host").debug(
314
+ "[pi-host] structured-output optional peerDep 未解析(独立形态未安装,孙进程扩展集回退空)",
315
+ { reason: toErrorMessage(err) },
316
+ );
317
+ return undefined;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * 从 argv 提取 `--extension` / `-e` 的全部值(等号与空格两种形式;自旧
323
+ * mirrorMainProcessFlags 的解析规则精简迁移——该镜像机制已废弃,此处只取
324
+ * extension 值,布尔镜像面不再透传)。
325
+ *
326
+ * 有值 flag 表(跳过其他 flag 的值时不误吃)与 MF-7a 判定(`--extension` 后跟
327
+ * `--` 开头 token 不吃值)逐字保留原语义——解析坑防回归见 pi-host.test.ts
328
+ * 解析用例(--skill 值跳过 / 真 flag 不吃值 / 单 - 路径是值);
329
+ * spawn-args.test.ts 历史族已随镜像退役换代。
330
+ */
331
+ const ARGV_VALUED_FLAGS = new Set<string>([
332
+ "--extension", "-e",
333
+ "--skill",
334
+ // g4-allow: argv 有值 flag 解析词表(跳过非 extension flag 的值防误吃),非模型引用拼装
335
+ "--model", "--system-prompt", "--append-system-prompt", // g4-allow: 同上——解析词表成员,非拼装
336
+ "--tools", "-t", "--exclude-tools", "-xt",
337
+ "--fork", "--session-dir", "--mode",
338
+ "--thinking", "--models",
339
+ ]);
340
+
341
+ /** argv 中 flag 起始索引:argv[0]=runtime,argv[1]=binary 路径。 */
342
+ const ARGV_FLAG_START = 2;
343
+
344
+ /**
345
+ * taiji 宿主注入态判据(P5 D4-2 扫描根修正;形态判据复用 H2 先例):主 pi 进程
346
+ * argv 含 --extension/-e 值即 taiji spawn(runtime 恒带 staged 全量集下发的
347
+ * --extension;独立 pi 经 settings 清单加载扩展,argv 无该 flag)。
348
+ * 与 resolveGrandchildExtensionPaths 的白名单收窄是两个决策——此处只做形态判定
349
+ * (决定扫描根指向),不做白名单(白名单只约束孙进程扩展集)。
350
+ */
351
+ function isTaijiHostInjected(argv: readonly string[] = process.argv): boolean {
352
+ return collectExtensionFlagValues(argv).length > 0;
353
+ }
354
+
355
+ function collectExtensionFlagValues(argv: readonly string[]): string[] {
356
+ const values: string[] = [];
357
+ const flagArgs = argv.length > ARGV_FLAG_START ? argv.slice(ARGV_FLAG_START) : [];
358
+ for (let i = 0; i < flagArgs.length; i++) {
359
+ const tok = flagArgs[i];
360
+ const eqMatch = /^(--extension|-e)=(.*)$/.exec(tok);
361
+ if (eqMatch !== null) {
362
+ if (eqMatch[2]) values.push(eqMatch[2]);
363
+ continue;
364
+ }
365
+ if (tok === "--extension" || tok === "-e") {
366
+ const next = flagArgs[i + 1];
367
+ // 单 `-` 开头的合法路径不是 flag(原 MF-7a);`--` 开头是真 flag,不吃值
368
+ if (next !== undefined && !next.startsWith("--") && next.length > 0) {
369
+ values.push(next);
370
+ i++;
371
+ }
372
+ continue;
373
+ }
374
+ if (ARGV_VALUED_FLAGS.has(tok)) {
375
+ i++;
376
+ continue;
377
+ }
378
+ }
379
+ return values;
380
+ }
381
+
194
382
  /**
195
383
  * pi 侧通知域窄端口实现(configureNotifyDomain 注入)。zsw 壳不注入本端口
196
384
  * (其完成通知走 HostServices.notify,P2 落地)。
package/src/index.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * subagent-workflow Extension — Factory(extension 装配点)
3
3
  *
4
4
  * 合并 @zhushanwen/pi-subagents + @zhushanwen/pi-workflow 为统一包。
5
- * 注册项:3 tool(subagent + workflow + workflow-script)+ 2 command(subagents + workflows)
6
- * + messageRenderer(subagent-bg-notify)+ pi.__workflowRun + session 事件。
5
+ * 注册项:4 tool(subagent + subagents + workflow + workflow-script)+ 2 command(subagents + workflows)
6
+ * + messageRenderer(subagent-bg-notify)+ session 事件。
7
7
  *
8
8
  * 包内结构(执行运行时已迁 packages/subagent-core,本包只留注册面与宿主适配):
9
9
  * interface/ → 注册胶水(tools / commands / TUI 渲染 / GUI mappers)
@@ -12,12 +12,12 @@
12
12
  * session-lifecycle.ts → 会话生命周期装配 seam(测试可注入 fake 依赖)
13
13
  * workflow-events.ts → workflow 域事件族装配 seam(7 个 pi.on handler + deps 装配)
14
14
  *
15
- * 本文件 = 组合根:只留注册(tools / commands / renderer / pi.__workflowRun / 进程级
16
- * 信号 hook)与装配接线(core 端口 / 注入器 / 两个 seam 调用)。
15
+ * 本文件 = 组合根:只留注册(tools / commands / renderer / 进程级信号 hook)与
16
+ * 装配接线(core 端口 / 注入器 / 两个 seam 调用)。
17
17
  *
18
18
  * 架构导航见 docs/extensions/subagents/architecture.md。
19
19
  *
20
- * 设计基线:D-004(旧包不动)/ ADR-025(进程内执行)/ D-8(pi.__workflowRun 签名)。
20
+ * 设计基线:D-004(旧包不动)/ ADR-025(进程内执行)。
21
21
  */
22
22
 
23
23
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -28,10 +28,7 @@ import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
28
28
  // ═══ core 宿主端口接线(subagent-core 包抽离 u0-wire;实现见 src/host/pi-host.ts) ═══
29
29
  import { configureCore } from "@zhushanwen/subagent-core";
30
30
  import { configureNotifyDomain } from "@zhushanwen/subagent-core";
31
- import { setInFlightListener } from "@zhushanwen/subagent-core";
32
31
  import { createPiHostServices, createPiNotifyDomainPorts } from "./host/pi-host.ts";
33
- // [u7a D5] 壳层在途上报出口:core 状态迁移 → 本出口 → select 通道(marker 帧)→ runtime。
34
- import { createInFlightReporter } from "./host/inflight-reporter.ts";
35
32
 
36
33
  // ═══ 经 core barrel 消费执行域(执行运行时住 packages/subagent-core) ═══
37
34
  // [U7] 引擎列表状态文件(registry → engines.json,GUI 引擎选择器数据源)
@@ -42,8 +39,9 @@ import { syncEnginesFile } from "@zhushanwen/subagent-core";
42
39
  // [P3 引擎接线] 组合根登记 'zcode' 引擎(D8 薄壳:vendored 定位 cli descriptor;
43
40
  // engineDataDir 默认走 common/data-dir SSOT)
44
41
  import { registerZcodeEngine } from "@zhushanwen/subagent-core";
45
- import { killAllSpawnedChildren } from "@zhushanwen/subagent-core";
46
- import { runAndWait, type WorkflowRunResult } from "@zhushanwen/subagent-core";
42
+ import { markAllSpawnedChildrenDead } from "@zhushanwen/subagent-core";
43
+ // bg-notify 送达通道 customType(notify 词表单源,与 shared/runtime/core 消费侧同源)
44
+ import { SUBAGENT_BG_NOTIFY_CUSTOM_TYPE } from "@zhushanwen/extension-protocol";
47
45
  // [engine-awareness U3/D7-④] per-turn 引擎检测编排 + before_agent_start 链尾接线
48
46
  import { setupEngineAwarenessInjector } from "./injectors/engine-awareness.ts";
49
47
  import { setupModelListInjector } from "./injectors/model-list-injector.ts";
@@ -57,23 +55,10 @@ import { registerSubagentsCommand } from "./interface/subagents.ts";
57
55
  import { registerSubagentsTool } from "./interface/tool-subagents.ts";
58
56
  import { registerWorkflowTool } from "./interface/tool-workflow.ts";
59
57
  import { registerWorkflowScriptTool } from "./interface/tool-workflow-script.ts";
60
- // ═══ workflow 域事件族装配 seam(7 个 pi.on handler + makeDeps/getWorkflowDeps/
61
- // lazyDeps;与 session-lifecycle.ts 同构,D2 原样搬移 + lazyDeps 样板收敛) ═══
58
+ // ═══ workflow 域事件族装配 seam(7 个 pi.on handler + makeDeps/lazyDeps;
59
+ // 与 session-lifecycle.ts 同构,D2 原样搬移 + lazyDeps 样板收敛) ═══
62
60
  import { setupWorkflowDomain } from "./workflow-events.ts";
63
61
 
64
- // ── pi.__workflowRun 类型扩展(D-8 签名) ─────────────────
65
-
66
- declare module "@earendil-works/pi-coding-agent" {
67
- interface ExtensionAPI {
68
- __workflowRun?: (
69
- workflowName: string,
70
- workflowArgs: Record<string, unknown>,
71
- workflowSignal?: AbortSignal,
72
- workflowTimeoutMs?: number,
73
- ) => Promise<WorkflowRunResult>;
74
- }
75
- }
76
-
77
62
  // ── Factory ──────────────────────────────────────────────────
78
63
 
79
64
  // 模块级 logger(setPiHandle 注入后自动走 appendEntry)
@@ -83,23 +68,23 @@ const logger = getLogger("subagents");
83
68
  //
84
69
  // session_shutdown 是 pi 的 async hook,进程被 SIGTERM/SIGINT 强杀或崩溃时来不及
85
70
  // 触发;sync 子进程(controller 为 undefined,abortRunningControllers 跳过它们)会
86
- // 泄漏为孤儿。process.on 兜底调 killAllSpawnedChildren——[如实口径] core 侧该入口
87
- // 为镜像置死 no-op(仅清空 core spawnedChildren 镜像记账,不发任何进程信号),真实
88
- // 回收链 = 子进程 stdin-EOF 自灭(宿主退出 / EngineClient 销毁)+ dispose 链;本调用
89
- // 保留兜底占位(镜像一致性),不构成真实收割。guard 防多信号叠加(如 SIGINT 后又
90
- // beforeExit)重复触发。
71
+ // 泄漏为孤儿。process.on 兜底调 markAllSpawnedChildrenDead——core 侧该入口为
72
+ // 镜像置死 no-op(仅清空 core spawnedChildren 镜像记账,不发任何进程信号;命名即
73
+ // 语义,防「kill 前缀」误读为发信号),真实回收链 = 子进程 stdin-EOF 自灭(宿主
74
+ // 退出 / EngineClient 销毁)+ dispose 链;本调用保留兜底占位(镜像一致性),不构成
75
+ // 真实收割。guard 防多信号叠加(如 SIGINT 后又 beforeExit)重复触发。
91
76
  let processShutdownHookFired = false;
92
77
 
93
78
  function reapSpawnedChildrenOnShutdown(): void {
94
79
  if (processShutdownHookFired) return;
95
80
  processShutdownHookFired = true;
96
81
  try {
97
- killAllSpawnedChildren("SIGTERM");
82
+ markAllSpawnedChildrenDead();
98
83
  } catch (err) {
99
84
  // best-effort:收割失败不阻断退出流程——debug 留痕(孤儿子进程排查线索),
100
85
  // 不静默吞错,对齐「错误必须可操作」。
101
86
  logger.debug(
102
- "[subagents] process shutdown reap best-effort failed (killAllSpawnedChildren SIGTERM)",
87
+ "[subagents] process shutdown reap best-effort failed (markAllSpawnedChildrenDead)",
103
88
  { reason: toErrorMessage(err) },
104
89
  );
105
90
  }
@@ -139,13 +124,6 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
139
124
  // [P3 引擎接线] 登记 'zcode'(幂等同上)。D8 薄壳:vendored 定位 cli descriptor。
140
125
  registerZcodeEngine();
141
126
 
142
- // [u7a D5] 在途聚合上报接线:core 状态迁移(spawn/close/arm/disarm)→ 出口回调 →
143
- // 本 reporter 经 select 通道推绝对计数帧。出口为进程级单监听(在途记账本身是 pi
144
- // 进程级模块状态),后注册覆盖先注册(jiti 重载幂等);回调同步 void,不进任何
145
- // 生命周期 await 链(D5 接线约束①)。ctx 由 session_start 注入(factory 阶段无 ui)。
146
- const inflightReporter = createInFlightReporter();
147
- setInFlightListener(inflightReporter.onInFlightChanged);
148
-
149
127
  // [U7b] 引擎列表在 extension 模块加载时即同步 engines.json(不等 session_start——
150
128
  // 用户体验拍板 2026-08-25:taiji 打开后激活任意 session 的第一时间(含 TUI 等价
151
129
  // 场景)GUI 引擎选择器就该有数据;session_start 处保留幂等重写兜底 jiti 双路径/
@@ -165,7 +143,7 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
165
143
  // ════════════════════════════════════════════════════════════
166
144
  registerSubagentTool(pi);
167
145
  registerSubagentsCommand(pi);
168
- pi.registerMessageRenderer("subagent-bg-notify", renderBgNotifyMessage);
146
+ pi.registerMessageRenderer(SUBAGENT_BG_NOTIFY_CUSTOM_TYPE, renderBgNotifyMessage);
169
147
 
170
148
  // ════════════════════════════════════════════════════════════
171
149
  // injectors:before_agent_start 注入 <available_subagents> + <available_workflows>
@@ -186,9 +164,10 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
186
164
  // 状态创建(lsRef / notifiedRunIds / workerHost / registry / sessionState)、log、
187
165
  // makeLifecycleDeps / makeDeps、7 个 pi.on handler、getWorkflowDeps 守卫 +
188
166
  // lazyDeps 已收编 workflow-events.ts(事件族 seam,与 session-lifecycle.ts 同构;
189
- // 7 个 handler 的注册相对顺序原样保留)。此处仅接线。
167
+ // 7 个 handler 的注册相对顺序原样保留)。此处仅接线——在途上报(createInFlightReporter
168
+ // + setInFlightListener 登记与 attach/detach 驱动)同为该 seam 家内装配。
190
169
  // ════════════════════════════════════════════════════════════
191
- const workflow = setupWorkflowDomain(pi, { inflightReporter });
170
+ const workflow = setupWorkflowDomain(pi);
192
171
 
193
172
  // ════════════════════════════════════════════════════════════
194
173
  // [U7 + engine-awareness U3] before_agent_start:引擎感知注入(链尾注册,D7——
@@ -246,36 +225,6 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
246
225
  process.on("SIGINT", sigintHandler);
247
226
  process.on("beforeExit", reapSpawnedChildrenOnShutdown);
248
227
 
249
- // ════════════════════════════════════════════════════════════
250
- // pi.__workflowRun(D-8 签名)
251
- // ════════════════════════════════════════════════════════════
252
- pi.__workflowRun = async (
253
- workflowName: string,
254
- workflowArgs: Record<string, unknown>,
255
- workflowSignal?: AbortSignal,
256
- workflowTimeoutMs?: number,
257
- ): Promise<WorkflowRunResult> => {
258
- // 注意:lastSessionId 是单值假设——Pi 当前保证单 session 串行(一次只一个活跃 session)。
259
- // 若未来 Pi 支持多 session 并发,此处需改为从 ctx.sessionManager.getSessionId() 显式传入。
260
- // M-2 已记录此假设。
261
- const resolved = workflow.getWorkflowDeps(workflow.state.lsRef.lastSessionId);
262
- if (!resolved.ok) {
263
- return {
264
- status: "done",
265
- reason: "failed",
266
- error: resolved.reason,
267
- runId: "",
268
- };
269
- }
270
- return runAndWait(
271
- workflowName,
272
- workflowArgs,
273
- resolved.deps,
274
- workflowSignal,
275
- workflowTimeoutMs,
276
- );
277
- };
278
-
279
228
  // ════════════════════════════════════════════════════════════
280
229
  // Tools(3 个)+ Commands(2 个)—— 注册面
281
230
  //