@zhushanwen/pi-subagent-workflow 8.9.0 → 8.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-subagent-workflow",
3
- "version": "8.9.0",
3
+ "version": "8.10.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "Unified subagent execution and multi-agent workflow orchestration for Pi — spawned-process agent runtime with sync/background modes, stateful workflow management with persistence, state machine, and execution tracing.",
@@ -58,15 +58,17 @@
58
58
  "@xyz-agent/session-delivery": "0.3.1",
59
59
  "@zhushanwen/pi-ext-guards": "0.2.1",
60
60
  "@zhushanwen/pi-extension-logger": "0.4.1",
61
- "@zhushanwen/subagent-core": "0.6.0"
61
+ "@zhushanwen/subagent-core": "0.7.0",
62
+ "@zhushanwen/pi-subagent-cli": "0.1.1",
63
+ "@zhushanwen/zcode-subagent-cli": "0.1.1"
62
64
  },
63
65
  "peerDependencies": {
64
66
  "@earendil-works/pi-ai": "^0.84.4",
65
67
  "@earendil-works/pi-coding-agent": "^0.84.4",
66
68
  "@earendil-works/pi-tui": "^0.84.4",
67
69
  "typebox": "*",
68
- "@zhushanwen/pi-pending-notifications": "0.5.3",
69
- "@zhushanwen/pi-structured-output": "5.1.3"
70
+ "@zhushanwen/pi-pending-notifications": "0.6.0",
71
+ "@zhushanwen/pi-structured-output": "5.1.4"
70
72
  },
71
73
  "peerDependenciesMeta": {
72
74
  "@earendil-works/pi-coding-agent": {
package/relay/relay.mjs CHANGED
@@ -8,8 +8,9 @@
8
8
  * 通道,payload 一律 base64 封装保字节精确。
9
9
  *
10
10
  * ============================ 镜像常量表 ============================
11
- * SSOT = packages/subagent-core/src/execution/relay-env.ts(零依赖脚本不能 import workspace 包,只能内嵌镜像;
12
- * 改名/改值必须双侧同步,一致性由 E-3 conformance relay 变体断言锁定——设计 §10-5):
11
+ * SSOT = packages/subagent-engine-sdk/src/relay-env.ts(零依赖脚本不能 import workspace 包,只能内嵌镜像;
12
+ * core 侧经 subagent-core ./relay-env 子入口 re-export 保 semver 面。改名/改值必须双侧同步,
13
+ * 一致性由 E-3 conformance relay 变体断言锁定——设计 §10-5):
13
14
  *
14
15
  * | relay-env.ts 常量 | 本文件常量 | 值 |
15
16
  * |---------------------------|---------------------------|-----------------------------|
@@ -197,20 +197,31 @@ describe("createPiHostServices.log(桥接 pi-extension-logger)", () => {
197
197
  });
198
198
 
199
199
  describe("createPiNotifyDomainPorts.countActiveFromEntries(适配读 .count)", () => {
200
- it("拆 CountActiveResult.count 为 number(core 端口契约),entries 透传", () => {
201
- const entries: unknown[] = [{ customType: "pending:register" }];
202
- vi.mocked(countActiveFromEntries).mockReturnValue({ count: 3, ids: ["a", "b", "c"], entries: [] });
200
+ it("拆 CountActiveResult.count 为 number(core 端口契约),entries 透传(缺省过滤基准 undefined = 不过滤)", () => {
201
+ const entries: unknown[] = [{ customType: "pending:register" }];
202
+ vi.mocked(countActiveFromEntries).mockReturnValue({ count: 3, ids: ["a", "b", "c"], entries: [] });
203
203
 
204
- const result = createPiNotifyDomainPorts().countActiveFromEntries?.(entries);
204
+ const result = createPiNotifyDomainPorts().countActiveFromEntries?.(entries);
205
205
 
206
- expect(result).toBe(3);
207
- expect(vi.mocked(countActiveFromEntries)).toHaveBeenCalledWith(entries);
208
- });
206
+ expect(result).toBe(3);
207
+ // [W4 读侧过滤②] 端口适配恒透传过滤基准 opts(缺省 undefined = 不过滤,
208
+ // 既有调用方零改动行为不变)。
209
+ expect(vi.mocked(countActiveFromEntries)).toHaveBeenCalledWith(entries, { currentSessionId: undefined });
210
+ });
209
211
 
210
- it("零活跃返回 0{count: 0} 形状)", () => {
211
- vi.mocked(countActiveFromEntries).mockReturnValue({ count: 0, ids: [], entries: [] });
212
- expect(createPiNotifyDomainPorts().countActiveFromEntries?.([])).toBe(0);
213
- });
212
+ it("[W4 读侧过滤② / F1] 调用时传 currentSessionIdper-call 基准)→ 透传差集过滤基准", () => {
213
+ const entries: unknown[] = [{ customType: "pending:register" }];
214
+ vi.mocked(countActiveFromEntries).mockReturnValue({ count: 1, ids: ["a"], entries: [] });
215
+
216
+ createPiNotifyDomainPorts().countActiveFromEntries?.(entries, { currentSessionId: "sess-child" });
217
+
218
+ expect(vi.mocked(countActiveFromEntries)).toHaveBeenCalledWith(entries, { currentSessionId: "sess-child" });
219
+ });
220
+
221
+ it("零活跃返回 0({count: 0} 形状)", () => {
222
+ vi.mocked(countActiveFromEntries).mockReturnValue({ count: 0, ids: [], entries: [] });
223
+ expect(createPiNotifyDomainPorts().countActiveFromEntries?.([])).toBe(0);
224
+ });
214
225
  });
215
226
 
216
227
  describe("createPiNotifyDomainPorts.createDelivery(透传 session-delivery)", () => {
@@ -29,6 +29,7 @@
29
29
 
30
30
  import { createRequire } from "node:module";
31
31
  import { dirname, join } from "node:path";
32
+ import { fileURLToPath } from "node:url";
32
33
 
33
34
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
34
35
  import { getLogger } from "@zhushanwen/pi-extension-logger";
@@ -117,6 +118,45 @@ function skillRoots(): DiscoveryRoot[] {
117
118
  ];
118
119
  }
119
120
 
121
+ /**
122
+ * 引擎包发现根(W4 L1 第二通道,设计 §3.4)。打包态主通道 = env
123
+ * XYZ_AGENT_ENGINE_ROOTS(W9 注入),此处承载 pi 宿主的常规安装路径:
124
+ * 1. 宿主包自身 node_modules——引擎包(<engine>-subagent-cli)作为本包
125
+ * dependencies 安装位(W9「扩展 package.json 声明引擎包为 dependencies」);
126
+ * 2. <agentDir>/npm/node_modules——pi npm 安装位(org 分组二层布局由扫描器
127
+ * 下钻覆盖);
128
+ * 3. <agentDir>/extensions——dev symlink 位(agents kind 同款 npm-dev 根)。
129
+ * 根目录不存在时由扫描器静默跳过(不在此预检)。 */
130
+ function engineRoots(): DiscoveryRoot[] {
131
+ const agentDir = getAgentDir();
132
+ const roots: DiscoveryRoot[] = [];
133
+ const selfNodeModules = hostPackageNodeModulesRoot();
134
+ if (selfNodeModules !== undefined) {
135
+ roots.push({ dir: selfNodeModules, source: "npm" });
136
+ }
137
+ roots.push({ dir: join(agentDir, "npm", "node_modules"), source: "npm" });
138
+ roots.push({ dir: join(agentDir, "extensions"), source: "npm-dev" });
139
+ return roots;
140
+ }
141
+
142
+ /** 宿主包(本扩展)node_modules 根:src/host/pi-host.ts 上溯三级 = 包根。
143
+ * jiti 加载下 import.meta.url 可用(corePackageNpmRoot 同款可靠性先例);解析失败
144
+ * 降级为不注入并 warn——绝不因根定位失败阻断发现主链。 */
145
+ function hostPackageNodeModulesRoot(): string | undefined {
146
+ try {
147
+ const here = fileURLToPath(import.meta.url);
148
+ // src/host/pi-host.ts → src/host → src → 包根
149
+ const packageRoot = dirname(dirname(dirname(here)));
150
+ return join(packageRoot, "node_modules");
151
+ } catch (err) {
152
+ getLogger("pi-host").warn(
153
+ "[pi-host] 宿主包 node_modules 引擎发现根解析失败——dependencies 安装的引擎包可能不可发现",
154
+ { reason: toErrorMessage(err) },
155
+ );
156
+ return undefined;
157
+ }
158
+ }
159
+
120
160
  /** pi 宿主 HostServices 实现(扩展初始化最早处经 configureCore 注入)。 */
121
161
  export function createPiHostServices(): HostServices {
122
162
  return {
@@ -139,23 +179,37 @@ export function createPiHostServices(): HostServices {
139
179
  agents?: DiscoveryRoot[];
140
180
  skills?: DiscoveryRoot[];
141
181
  workflows?: DiscoveryRoot[];
182
+ engines?: DiscoveryRoot[];
142
183
  } {
143
184
  return {
144
185
  agents: agentDirKindRoots("agents"),
145
186
  skills: skillRoots(),
146
187
  workflows: agentDirKindRoots("workflows"),
188
+ engines: engineRoots(),
147
189
  };
148
190
  },
149
191
  };
150
192
  }
151
193
 
152
- /** pi 侧通知域窄端口实现(configureNotifyDomain 注入)。zsw 壳不注入本端口
153
- * (其完成通知走 HostServices.notify,P2 落地)。 */
194
+ /**
195
+ * pi 侧通知域窄端口实现(configureNotifyDomain 注入)。zsw 壳不注入本端口
196
+ * (其完成通知走 HostServices.notify,P2 落地)。
197
+ *
198
+ * [F1 形态升级] 跨 session 残留过滤基准(W4 读侧过滤②)不走 factory 定型——本装配
199
+ * 在扩展模块加载时执行一次,session id 逐 session 变化(session_start 每次更新),
200
+ * factory 参数表达不了 per-call 基准;且生产装配点从未传参(注释声称的防御实际
201
+ * 缺基准)。现形态 = core 端口契约第二参(NotifyDomainPorts.countActiveFromEntries
202
+ * 的 opts)per-call 透传,基准由 core 读侧按「被读 entries 所属 session」提供
203
+ * (session-pending.ts 读 pi session 文件首行 SessionHeader.id)——fork 继承的父级
204
+ * 注册残留不进后代判定差集,防御真实生效。
205
+ */
154
206
  export function createPiNotifyDomainPorts(): NotifyDomainPorts {
155
207
  return {
156
208
  // pi 真函数返回 CountActiveResult,core 契约只读 .count——壳侧拆数值。
157
- countActiveFromEntries(entries: unknown[]): number {
158
- return countActiveFromEntries(entries).count;
209
+ // [W4 读侧过滤② / F1] per-call 基准透传差集口径(缺省 undefined = 不过滤,
210
+ // 向后兼容:无基准的调用方零改动行为不变)。
211
+ countActiveFromEntries(entries: unknown[], opts?: { currentSessionId?: string }): number {
212
+ return countActiveFromEntries(entries, { currentSessionId: opts?.currentSessionId }).count;
159
213
  },
160
214
 
161
215
  // 投递内核工厂直传本体(结构兼容论证见文件头);不经包装避免多一层间接面。
package/src/index.ts CHANGED
@@ -26,10 +26,11 @@ import { bestEffort } from "@zhushanwen/subagent-core";
26
26
  // ═══ execution/ 层(subagents 核心 + 运行时) ═══
27
27
  // [U7] 引擎列表状态文件(registry → engines.json,GUI 引擎选择器数据源)
28
28
  import { syncEnginesFile } from "@zhushanwen/subagent-core";
29
- // [P1 引擎接线] 组合根登记 'pi' 引擎进 registry(引擎获取统一经 getEngine,缺省 id 'pi'
30
- import { registerPiEngine } from "@zhushanwen/subagent-core";
31
- // [P3 引擎接线] 组合根登记 'zcode' 引擎(spawn 单轮模式;engineDataDir 默认走
32
- // common/data-dir SSOT,见 engines/zcode/registration.ts)
29
+ // [W11/DoD#5] registerPiEngine(inproc 'pi' 注册)已随内建引擎删除:registry 'pi'
30
+ // 由下方 syncEnginesFile 的三级发现装载 cli descriptor(engines/zcode 同理由
31
+ // registerZcodeEngine D8 薄壳承载)。
32
+ // [P3 引擎接线] 组合根登记 'zcode' 引擎(D8 薄壳:vendored 定位 cli descriptor;
33
+ // engineDataDir 默认走 common/data-dir SSOT)
33
34
  import { registerZcodeEngine } from "@zhushanwen/subagent-core";
34
35
  import { getModelConfigService } from "@zhushanwen/subagent-core";
35
36
  import { getBoundNotifyLedger } from "@zhushanwen/subagent-core";
@@ -132,16 +133,17 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
132
133
  // 完整;且先于任何可能消费 core 端口的初始化逻辑(引擎登记等)。缺省态若被消费,
133
134
  // dataRoot 抛 core_host_not_configured(§3.4),接线后不再可达。
134
135
  configureCore(createPiHostServices());
136
+ // [F1] 通知域端口无 factory 基准参数:跨 session 残留过滤基准(W4 读侧过滤②)
137
+ // 由 core 读侧 per-call 提供(session-pending 读「被读 entries 所属 session」)——
138
+ // 本装配在扩展启动时执行一次,session id 逐 session 变化,factory 定型表达不了
139
+ // per-call 基准(生产装配也从未传参,防御曾实际缺基准)。
135
140
  configureNotifyDomain(createPiNotifyDomainPorts());
136
141
 
137
- // [P1 引擎接线] 组合根登记缺省引擎:进程级 SubagentService 单例(session_start 注入)
138
- // registry 'pi' 暴露——引擎获取从此统一走 getEngine(DEFAULT_ENGINE_ID),上层
139
- // 不再硬编码「spawn pi」。幂等(registerEngine 覆盖语义),工厂惰性解析服务单例。
140
- // P4 配置路由(agent frontmatter engine 字段 + 三层优先级)在本登记之上消费。
141
- registerPiEngine();
142
-
143
- // [P3 引擎接线] 登记 'zcode'(幂等同上)。惰性工厂:不触发 CLI/凭据探测,引擎被
144
- // 实际选用(P4 路由或显式 getEngine('zcode'))才解析 deps。
142
+ // [W11/DoD#5] 'pi' inproc 注册(registerPiEngine)已删除——缺省引擎 'pi' 的
143
+ // registry 条目由下方 syncEnginesFile 内的三级发现装载(cli descriptor,幂等),
144
+ // P4 配置路由(agent frontmatter engine 字段 + 三层优先级)在其上消费;chat 域
145
+ // pi 引擎不经 registry(SubagentService 自持 DI,W11 agent 裁决的临时豁免面)。
146
+ // [P3 引擎接线] 登记 'zcode'(幂等同上)。D8 薄壳:vendored 定位 cli descriptor。
145
147
  registerZcodeEngine();
146
148
 
147
149
  // [U7b] 引擎列表在 extension 模块加载时即同步 engines.json(不等 session_start——
@@ -1,6 +1,6 @@
1
1
  // src/interface/subagent-tool-schema.ts
2
2
  //
3
- // `subagent` 工具的参数 schema 纯常量叶子(零运行时依赖,先例 shared/schema-env.ts)。
3
+ // `subagent` 工具的参数 schema 纯常量叶子(零运行时依赖)。
4
4
  //
5
5
  // 抽取自 subagent-tool.ts(跨包契约另一半):subagent-tool 依赖树沉重(pi SDK /
6
6
  // handler / render 链),structured-output 侧的跨包契约测试若从它 import schema
@@ -87,6 +87,7 @@ import {
87
87
  SNAPSHOT_VERSION,
88
88
  fromRunSnapshot,
89
89
  toRunSnapshot,
90
+ pruneStateFilesBeyondCap,
90
91
  type RunSnapshot,
91
92
  } from "@zhushanwen/subagent-core";
92
93
  import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
@@ -217,56 +218,11 @@ async function loadRunFromStateFile(filePath: string): Promise<WorkflowRun | nul
217
218
  }
218
219
 
219
220
  // ── State file retention (OR-5 ⑥b, default-on) ───────────────
220
-
221
- /** run state 文件名 glob:runId 形如 `wf-<ts>-<rand>`(lifecycle.ts 生成),只删命中者。
222
- * 同目录可能存在的非 state 文件(及 session JSONL——在父目录,本就不在扫描范围)永不碰。 */
223
- const STATE_FILE_GLOB = /^wf-.*\.jsonl$/;
224
-
225
- /**
226
- * 把 workflow-state 目录裁剪到 maxRuns 个最新 state 文件(mtime 升序,删最旧)。
227
- *
228
- * 只删本目录内命中 {@link STATE_FILE_GLOB} 的文件;任何失败都不抛(清理是旁路
229
- * 维护,不能拖垮持久化主链路):readdir/stat 失败静默放弃本轮,单个 unlink 失败
230
- * (非 ENOENT)logger.warn 留证后继续删其余——ENOENT 视为并发删除竞态下的已达成
231
- * 目标,不告警。
232
- */
233
- async function pruneStateFilesBeyondCap(stateDir: string, maxRuns: number): Promise<void> {
234
- let names: string[];
235
- try {
236
- names = await fs.promises.readdir(stateDir);
237
- } catch (err) {
238
- if (!isEnoentError(err)) {
239
- const reason = toErrorMessage(err);
240
- logger.warn(`[subagent-workflow] state retention: readdir ${stateDir} failed: ${reason}`);
241
- }
242
- return;
243
- }
244
- const stateFiles = names.filter((n) => STATE_FILE_GLOB.test(n)).sort();
245
- if (stateFiles.length <= maxRuns) return;
246
-
247
- // stat 全集取 mtime;allSettled 部分降级——单文件 stat 失败(并发删除 ENOENT 等)
248
- // 静默跳过该文件,不阻断本轮裁剪
249
- const settled = await Promise.allSettled(
250
- stateFiles.map(async (name) => {
251
- const full = path.join(stateDir, name);
252
- return { full, mtimeMs: (await fs.promises.stat(full)).mtimeMs };
253
- }),
254
- );
255
- const byMtimeAsc = settled
256
- .flatMap((r) => (r.status === "fulfilled" ? [r.value] : []))
257
- .sort((a, b) => a.mtimeMs - b.mtimeMs);
258
- const victims = byMtimeAsc.slice(0, byMtimeAsc.length - maxRuns);
259
- for (const victim of victims) {
260
- try {
261
- await fs.promises.unlink(victim.full);
262
- logger.debug(`[subagent-workflow] state retention: pruned ${victim.full}`);
263
- } catch (err) {
264
- if (isEnoentError(err)) continue; // 并发删除已达成目标
265
- const reason = toErrorMessage(err);
266
- logger.warn(`[subagent-workflow] state retention: failed to delete ${victim.full}: ${reason}`);
267
- }
268
- }
269
- }
221
+ //
222
+ // 裁剪主体单源消费 core pruneStateFilesBeyondCap(S4-A7 收口:本地曾有一份逐段
223
+ // 同构的私有实现,retention 语义双份各自演化是漂移隐患);日志注入本模块
224
+ // logger + [subagent-workflow] tag,错误字符串化用 ext-guards toErrorMessage——
225
+ // 行为与收口前一致(差异仅经 core 单源演化时两宿主同步)。
270
226
 
271
227
  // ── JsonlRunStore ────────────────────────────────────────────
272
228
 
@@ -580,7 +536,13 @@ export class JsonlRunStore {
580
536
  if (rollbackFirstWrite) {
581
537
  const maxRuns = getEnvStateMaxRuns();
582
538
  if (maxRuns !== undefined) {
583
- await pruneStateFilesBeyondCap(this.stateDir, maxRuns);
539
+ // 单源裁剪(core pruneStateFilesBeyondCap),注入本扩展 logger tag 与
540
+ // toErrorMessage(行为与本地实现收口前一致)
541
+ await pruneStateFilesBeyondCap(this.stateDir, maxRuns, {
542
+ warn: (msg) => logger.warn(`[subagent-workflow] ${msg}`),
543
+ debug: (msg) => logger.debug(`[subagent-workflow] ${msg}`),
544
+ toMsg: toErrorMessage,
545
+ });
584
546
  }
585
547
  }
586
548
  for (const s of settlers) s.resolve();