@zhushanwen/pi-subagent-workflow 8.13.0 → 8.14.1

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/README.md CHANGED
@@ -33,7 +33,7 @@ Pi 的 subagent + workflow 合并包:任务委派 + 多 agent 编排(chain /
33
33
 
34
34
  orchestrator 是纯协调器角色:拆解任务 → 委派 subagent → 汇总结果,自身不做执行类工作。orchestrator agent 自身也可递归委派子 orchestrator,实现分层任务拆解(深度受 `Depth: N/10` 护栏保护)。
35
35
 
36
- **工具约束变化(C1/D-5)**:内置模板不再携带 `tools:` frontmatter 白名单,subagent 不再以 `--tools` 白名单启动——工具约束回归宿主默认工具面,orchestrator 靠角色职责(职责边界段)约束自身只做协调。想要白名单的用户在 `<workspace>/.agents/agents/` 放同名 `.md` 覆写(project 级源稳定遮蔽内置,是唯一逃生门),或沿用 pi CLI 的 `--tools` 白名单(临时验证):
36
+ **工具约束(C1/D-5)**:内置模板不携带 `tools:` frontmatter 白名单,subagent 不以 `--tools` 白名单启动——工具约束回归宿主默认工具面,orchestrator 靠角色职责(职责边界段)约束自身只做协调。想要白名单的用户在 `<workspace>/.agents/agents/` 放同名 `.md` 覆写(project 级源稳定遮蔽内置,是唯一逃生门),或沿用 pi CLI 的 `--tools` 白名单(临时验证):
37
37
 
38
38
  ```bash
39
39
  pi --tools todo,goal_control,workflow,subagent,ask_user
@@ -57,14 +57,14 @@ Workflow run 是一次性执行,状态机两态:`running → done`(`done`
57
57
 
58
58
  Runs are one-shot: there is no pause/resume — to stop a run early use abort; for a fresh result start a new run.
59
59
 
60
- - **abort 是唯一的提前停止方式**:`{"action":"abort","runId":"<id>"}`(可选 `"error":"<reason>"`)。pause/resume action 已移除,调用会被 pi schema 校验拒绝(`Validation failed for tool "workflow"`);`/workflows pause|resume <id>` 返回 removed 提示
60
+ - **abort 是唯一的提前停止方式**:`{"action":"abort","runId":"<id>"}`(可选 `"error":"<reason>"`)。不存在 pause/resume action,调用会被 pi schema 校验拒绝(`Validation failed for tool "workflow"`);`/workflows pause|resume <id>` 返回 removed 提示
61
61
  - **session 切换/关闭时**,所有 running run 当即作废转 `done,failed`(state.error 为 `Session switched: run terminated` / `Session shutdown: run terminated`),已投入的 token 作废;需要结果就重新 run
62
62
  - **快照格式 `wf-run-v2`**(status 两态、无 `pausedAt`);旧 `wf-run-v1` 文件加载时静默跳过
63
63
  - **worker 崩溃自动重建重试**(默认 3 次):重建时在飞 agent 调用被清除重跑,已完成的调用保留 replay 缓存,不重复消耗 token
64
64
 
65
65
  ## 性能:sessions-index.json 持久化索引
66
66
 
67
- 冷启动首扫的 identity 探测结论持久化为 `<enc>/sessions-index.json`(stat 戳自校验、tmp(pid)+rename 原子写、60s 节流、损坏/版本不符静默回退全量探测),真实目录(1744 jsonl / 671MB)实测冷扫描中位数 972.8ms → 80.6ms(12.1x,预算 ≤300ms)。可复现验收脚本:`bench/cold-scan.bench.ts`(冷扫描计时 + 输出等价断言)、`bench/concurrent-scan.bench.ts`(3 实例并发 + 随机变异四判定),设计文档见 `.xyz-harness/2026-08-15-subagent-workflow-perf/sessions-index-design.md`。
67
+ 冷启动首扫的 identity 探测结论持久化为 `<enc>/sessions-index.json`(stat 戳自校验、tmp(pid)+rename 原子写、60s 节流、损坏/版本不符静默回退全量探测),真实目录(1744 jsonl / 671MB)实测冷扫描中位数 972.8ms → 80.6ms(12.1x,预算 ≤300ms)。可复现验收脚本:`bench/cold-scan.bench.ts`(冷扫描计时 + 输出等价断言)、`bench/concurrent-scan.bench.ts`(3 实例并发 + 随机变异四判定)。
68
68
 
69
69
  ## 安装
70
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-subagent-workflow",
3
- "version": "8.13.0",
3
+ "version": "8.14.1",
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.",
@@ -29,7 +29,7 @@
29
29
  "src/injectors/",
30
30
  "src/interface/"
31
31
  ],
32
- "xyz-agent": {
32
+ "taiji": {
33
33
  "role": "universal",
34
34
  "subagentEngines": [
35
35
  "pi",
@@ -54,21 +54,21 @@
54
54
  ]
55
55
  },
56
56
  "dependencies": {
57
- "@xyz-agent/extension-protocol": "0.10.0",
58
- "@xyz-agent/session-delivery": "0.3.1",
59
- "@zhushanwen/pi-ext-guards": "0.3.0",
57
+ "@zhushanwen/extension-protocol": "0.11.0",
58
+ "@zhushanwen/pi-ext-guards": "0.4.0",
59
+ "@zhushanwen/session-delivery": "0.10.0",
60
60
  "@zhushanwen/pi-extension-logger": "0.6.0",
61
- "@zhushanwen/subagent-core": "0.10.0",
62
- "@zhushanwen/pi-subagent-cli": "0.3.0",
63
- "@zhushanwen/zcode-subagent-cli": "0.3.0"
61
+ "@zhushanwen/subagent-core": "0.10.2",
62
+ "@zhushanwen/pi-subagent-cli": "0.4.0",
63
+ "@zhushanwen/zcode-subagent-cli": "0.3.1"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@earendil-works/pi-ai": "^0.84.4",
67
67
  "@earendil-works/pi-coding-agent": "^0.84.4",
68
68
  "@earendil-works/pi-tui": "^0.84.4",
69
69
  "typebox": "*",
70
- "@zhushanwen/pi-pending-notifications": "0.7.1",
71
- "@zhushanwen/pi-structured-output": "5.1.5"
70
+ "@zhushanwen/pi-pending-notifications": "0.7.2",
71
+ "@zhushanwen/pi-structured-output": "5.1.7"
72
72
  },
73
73
  "peerDependenciesMeta": {
74
74
  "@earendil-works/pi-coding-agent": {
package/relay/relay.mjs CHANGED
@@ -14,9 +14,9 @@
14
14
  *
15
15
  * | relay-env.ts 常量 | 本文件常量 | 值 |
16
16
  * |---------------------------|---------------------------|-----------------------------|
17
- * | RELAY_ENV_SOCKET | RELAY_ENV_SOCKET | 'XYZ_SUBAGENT_RELAY_SOCKET' |
18
- * | RELAY_ENV_SESSION_ID | RELAY_ENV_SESSION_ID | 'XYZ_SUBAGENT_RELAY_SESSION_ID' |
19
- * | RELAY_ENV_RECORD_ID | RELAY_ENV_RECORD_ID | 'XYZ_SUBAGENT_RELAY_RECORD_ID' |
17
+ * | RELAY_ENV_SOCKET | RELAY_ENV_SOCKET | 'TAIJI_SUBAGENT_RELAY_SOCKET' |
18
+ * | RELAY_ENV_SESSION_ID | RELAY_ENV_SESSION_ID | 'TAIJI_SUBAGENT_RELAY_SESSION_ID' |
19
+ * | RELAY_ENV_RECORD_ID | RELAY_ENV_RECORD_ID | 'TAIJI_SUBAGENT_RELAY_RECORD_ID' |
20
20
  * | RELAY_PROTOCOL_VERSION | RELAY_PROTOCOL_VERSION | 1 |
21
21
  * | RELAY_EXIT_CODES.* | RELAY_EXIT_CODES.* | 10/11/12/13 |
22
22
  *
@@ -46,9 +46,9 @@ import net from "node:net";
46
46
  import os from "node:os";
47
47
 
48
48
  // ---- 镜像常量(与 relay-env.ts 逐字一致,见头部对照表)----
49
- const RELAY_ENV_SOCKET = "XYZ_SUBAGENT_RELAY_SOCKET";
50
- const RELAY_ENV_SESSION_ID = "XYZ_SUBAGENT_RELAY_SESSION_ID";
51
- const RELAY_ENV_RECORD_ID = "XYZ_SUBAGENT_RELAY_RECORD_ID";
49
+ const RELAY_ENV_SOCKET = "TAIJI_SUBAGENT_RELAY_SOCKET";
50
+ const RELAY_ENV_SESSION_ID = "TAIJI_SUBAGENT_RELAY_SESSION_ID";
51
+ const RELAY_ENV_RECORD_ID = "TAIJI_SUBAGENT_RELAY_RECORD_ID";
52
52
 
53
53
  const RELAY_PROTOCOL_VERSION = 1;
54
54
 
@@ -137,7 +137,7 @@ function connectWithRetry(socketPath) {
137
137
  fail(
138
138
  RELAY_EXIT_CODES.SOCKET_UNREACHABLE,
139
139
  `relay socket unreachable after retry: ${socketPath} (${err.message}). ` +
140
- `Recovery: 重试任务;持续失败请重启 xyz-agent(runtime 未运行或已重启)`,
140
+ `Recovery: 重试任务;持续失败请重启 taiji(runtime 未运行或已重启)`,
141
141
  );
142
142
  }
143
143
  });
@@ -146,7 +146,7 @@ function connectWithRetry(socketPath) {
146
146
  fail(
147
147
  RELAY_EXIT_CODES.SOCKET_CLOSED,
148
148
  "relay socket closed by runtime(生命线断,可能是 runtime 崩溃/重启). " +
149
- "Recovery: 重启 xyz-agent 后重试任务",
149
+ "Recovery: 重启 taiji 后重试任务",
150
150
  );
151
151
  }
152
152
  });
@@ -205,7 +205,7 @@ function handleLine(line) {
205
205
  RELAY_EXIT_CODES.VERSION_MISMATCH,
206
206
  `relay protocol version mismatch: agent v${RELAY_PROTOCOL_VERSION}, ` +
207
207
  `runtime supports ${JSON.stringify(frame.supported)}. ` +
208
- "Recovery: 升级 xyz-agent(runtime 与代理资产同包分发,版本不一致意味着安装损坏,重装应用)",
208
+ "Recovery: 升级 taiji(runtime 与代理资产同包分发,版本不一致意味着安装损坏,重装应用)",
209
209
  );
210
210
  return;
211
211
  }
@@ -16,10 +16,10 @@ config.json 位于 pi agent 目录下的 `subagents/config.json`,随环境不
16
16
  | 环境 | 路径 |
17
17
  |------|------|
18
18
  | 独立 pi CLI | `~/.pi/agent/subagents/config.json` |
19
- | xyz-agent dev | `~/.xyz-agent-dev/pi/agent/subagents/config.json` |
20
- | xyz-agent prod | `~/.xyz-agent/pi/agent/subagents/config.json` |
19
+ | taiji dev | `~/.taiji-dev/pi/agent/subagents/config.json` |
20
+ | taiji prod | `~/.taiji/pi/agent/subagents/config.json` |
21
21
 
22
- **动态推导(推荐)**:agentDir 由 pi 核心 `getAgentDir()` 决定(读 `PI_CODING_AGENT_DIR`,默认 `~/.pi/agent`);xyz-agent 通过 `XYZ_AGENT_DATA_DIR` 隔离数据目录。排查时先查这两个 env 变量组合出实际路径(`<agentDir>/subagents/config.json`),不要假设单一环境——写错环境的配置文件改了也不生效。
22
+ **动态推导(推荐)**:agentDir 由 pi 核心 `getAgentDir()` 决定(读 `PI_CODING_AGENT_DIR`,默认 `~/.pi/agent`);taiji 通过 `TAIJI_AGENT_DATA_DIR` 隔离数据目录。排查时先查这两个 env 变量组合出实际路径(`<agentDir>/subagents/config.json`),不要假设单一环境——写错环境的配置文件改了也不生效。
23
23
 
24
24
  文件不存在 / JSON 解析失败 / 字段缺失时全部回默认配置,不报错。旧版 `categories` / `fallback` / `yoloByDefault` 等字段读取时忽略(模型解析已退化为「主 agent model 优先」)。
25
25
 
@@ -86,8 +86,8 @@ config.json 位于 pi agent 目录下的 `subagents/config.json`,随环境不
86
86
 
87
87
  1. **新建 session**(必须——当前 session 不重读配置)。
88
88
  2. 让主 agent 派一个 subagent(例:用 `subagents` 工具发个简单任务)。
89
- 3. xyz-agent 侧边栏 **Agents tab** 看该项最左的引擎 icon(pi / zcode)——这是统一验证面。
90
- 4. journal 落点 `~/.xyz-agent-dev/engines/<engineId>/` **仅适用非 pi 引擎**(zcode 分支建 journal);pi 分支不建 journal,pi 任务以 icon 为验证面。
89
+ 3. taiji 侧边栏 **Agents tab** 看该项最左的引擎 icon(pi / zcode)——这是统一验证面。
90
+ 4. journal 落点 `~/.taiji-dev/engines/<engineId>/` **仅适用非 pi 引擎**(zcode 分支建 journal);pi 分支不建 journal,pi 任务以 icon 为验证面。
91
91
 
92
92
  ## 常见错误排查
93
93
 
@@ -31,7 +31,7 @@ vi.mock("@zhushanwen/pi-extension-logger", () => extensionLoggerMock);
31
31
  vi.mock("@zhushanwen/subagent-core", () => ({ getInFlightSnapshot: mockSnapshot }));
32
32
 
33
33
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
34
- import { INFLIGHT_REPORT_ACK, SUBAGENT_INFLIGHT_MARKER } from "@xyz-agent/extension-protocol";
34
+ import { INFLIGHT_REPORT_ACK, SUBAGENT_INFLIGHT_MARKER } from "@zhushanwen/extension-protocol";
35
35
 
36
36
  import { createInFlightReporter } from "../inflight-reporter.ts";
37
37
 
@@ -41,12 +41,12 @@ const sessionDeliveryMock = vi.hoisted(() => ({
41
41
  vi.mock("@earendil-works/pi-coding-agent", () => piCodingAgentMock);
42
42
  vi.mock("@zhushanwen/pi-extension-logger", () => extensionLoggerMock);
43
43
  vi.mock("@zhushanwen/pi-pending-notifications", () => pendingNotificationsMock);
44
- vi.mock("@xyz-agent/session-delivery", () => sessionDeliveryMock);
44
+ vi.mock("@zhushanwen/session-delivery", () => sessionDeliveryMock);
45
45
 
46
46
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
47
47
  import { getLogger } from "@zhushanwen/pi-extension-logger";
48
48
  import { countActiveFromEntries } from "@zhushanwen/pi-pending-notifications";
49
- import { createDelivery } from "@xyz-agent/session-delivery";
49
+ import { createDelivery } from "@zhushanwen/session-delivery";
50
50
 
51
51
  import { createPiHostServices, createPiNotifyDomainPorts } from "../pi-host.ts";
52
52
 
@@ -15,7 +15,7 @@
15
15
  // ② 事件产生点在 core、上报出口在壳层——本文件即出口(core 零 pi SDK);
16
16
  // ③ marker 路由不广播前端是 u7b(runtime 侧)的约束,本文件不涉及。
17
17
  //
18
- // 环境门控(2026-09-13 oe-audit + 裸 TUI 闪框事故):上报的消费方是 xyz-agent
18
+ // 环境门控(2026-09-13 oe-audit + 裸 TUI 闪框事故):上报的消费方是 taiji
19
19
  // runtime 的 event-adapter marker 路由,而 runtime spawn pi 恒为 --mode rpc——
20
20
  // ctx.mode !== 'rpc' 即无拦截方(裸 pi TUI 下 marker select 会弹真框超时,2026-09-12
21
21
  // 实测无限闪框),此时不启动上报(ask-user channel-handler 的 mode 二值判定同款
@@ -37,9 +37,10 @@
37
37
  // (30min 有界),不丢 errs-safe 兜底。
38
38
 
39
39
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
40
- import { SUBAGENT_INFLIGHT_MARKER, isInFlightReportAck } from "@xyz-agent/extension-protocol";
40
+ import { SUBAGENT_INFLIGHT_MARKER, callMarkerRpc, isInFlightReportAck } from "@zhushanwen/extension-protocol";
41
41
  import { getInFlightSnapshot } from "@zhushanwen/subagent-core";
42
42
  import { getLogger } from "@zhushanwen/pi-extension-logger";
43
+ import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
43
44
 
44
45
  /** select 通道级超时(控制面单请求,秒级校准——超时默认原则规则 19)。取值对齐
45
46
  * plugin-bridge 启动 sync 的 2s 自愈闸:session_start 首帧可能早于 runtime adapter
@@ -50,7 +51,7 @@ const SELECT_TIMEOUT_MS = 2_000;
50
51
  const RETRY_DELAY_MS = 2_000;
51
52
 
52
53
  /** 累计失败放弃上限(对齐 plugin-bridge MAX_SYNC_ATTEMPTS:60s 窗口覆盖 attach 竞态,
53
- * 有界防 rpc-but-非-xyz orchestrator 场景的永久空转)。 */
54
+ * 有界防 rpc-but-非-taiji orchestrator 场景的永久空转)。 */
54
55
  const MAX_REPORT_ATTEMPTS = 30;
55
56
 
56
57
  /** 在途上报器(组合根 index.ts 持有;per-factory 实例,session_start/shutdown 驱动)。 */
@@ -142,25 +143,36 @@ export function createInFlightReporter(opts: InFlightReporterOpts = {}): InFligh
142
143
  sessionId: getSessionId(active),
143
144
  emittedAt: Date.now(),
144
145
  });
145
- let value: unknown;
146
- try {
147
- value = await active.ui.select(SUBAGENT_INFLIGHT_MARKER, [payload], { timeout: selectTimeoutMs });
148
- } catch (err) {
149
- // 通道异常折叠(plugin-bridge callBridge 同款:不静默吞,但只首败 warn)。
150
- value = undefined;
151
- logFailure("select channel threw", err);
152
- }
146
+ // 发送+折叠半边走 protocol 的 callMarkerRpc 原语(D8,fire-and-forget:void 发起
147
+ // 不变):ok:false 四态(cancelled/timeout/channel-error/non-json)统一折叠进下方
148
+ // 延迟重试路径;送达判据 = ack 全等匹配(不是 JSON 消费),留在本侧。原语的失败
149
+ // 留痕经注入的 log 承载本侧「首败 warn / 后续 debug」防刷屏策略。
150
+ // guiCtx = ExtensionContext 的 GuiContext 最小子集(ask-user runRpcInteraction 同款
151
+ // 先例:ui.custom 泛型签名静态不兼容,callMarkerRpc 只读 ui.select)。
152
+ const guiCtx = {
153
+ mode: active.mode,
154
+ hasUI: active.hasUI,
155
+ ui: { select: active.ui.select.bind(active.ui) },
156
+ };
157
+ const result = await callMarkerRpc(guiCtx, SUBAGENT_INFLIGHT_MARKER, payload, {
158
+ timeout: selectTimeoutMs,
159
+ log: primitiveLog,
160
+ });
153
161
  attemptInFlight = false;
154
- if (typeof value === "string" && isInFlightReportAck(value)) {
162
+ if (result.ok && isInFlightReportAck(result.value)) {
155
163
  // 送达确认:清重试与失败计数,补推积压脏帧。
156
164
  failureCount = 0;
157
165
  clearRetryTimer();
158
166
  if (dirty && ctx !== null) kick();
159
167
  return;
160
168
  }
161
- // 失败折叠(resolve undefined = 超时/取消/无路由)→ 延迟重试,累计到顶放弃
162
- //(放弃后镜像按 absent-report 走 errs 推迟,30min 有界,errs-safe 兜底不丢)。
163
- logFailure("no ack (timeout, cancelled, or runtime without marker routing)", value);
169
+ // 失败折叠(resolve undefined = 超时/取消/无路由 / 回包非 ack / 通道异常)→ 延迟
170
+ // 重试,累计到顶放弃(放弃后镜像按 absent-report 走 errs 推迟,30min 有界,
171
+ // errs-safe 兜底不丢)。
172
+ logFailure(
173
+ result.ok ? "no ack (non-ack response)" : `no ack (${result.reason})`,
174
+ result.ok ? result.value : undefined,
175
+ );
164
176
  failureCount += 1;
165
177
  if (failureCount >= maxAttempts) {
166
178
  givenUp = true;
@@ -181,11 +193,17 @@ export function createInFlightReporter(opts: InFlightReporterOpts = {}): InFligh
181
193
  }
182
194
  }
183
195
 
196
+ /** 原语留痕注入(D8):msg/detail 由 callMarkerRpc 产出;防刷屏策略(首败 warn /
197
+ * 后续 debug)留本侧 logFailure。detail 是原语侧小对象,序列化保信息。 */
198
+ function primitiveLog(msg: string, detail?: object): void {
199
+ logFailure(msg, detail === undefined ? undefined : JSON.stringify(detail));
200
+ }
201
+
184
202
  function logFailure(reason: string, detail: unknown): void {
185
203
  if (!firstFailureLogged) {
186
204
  firstFailureLogged = true;
187
205
  logger.warn(`[subagent-inflight] in-flight report failed (${reason}); retrying every ${retryDelayMs}ms (bounded at ${maxAttempts} attempts)`, {
188
- detail: detail instanceof Error ? detail.message : String(detail),
206
+ detail: toErrorMessage(detail),
189
207
  });
190
208
  return;
191
209
  }
@@ -5,12 +5,12 @@
5
5
  //
6
6
  // 本文件属壳侧(shell),不进 core 切面——对 pi SDK 与 pi 宿主协作件
7
7
  // (@earendil-works/pi-coding-agent / @zhushanwen/pi-extension-logger /
8
- // @zhushanwen/pi-pending-notifications / @xyz-agent/session-delivery)的运行时
8
+ // @zhushanwen/pi-pending-notifications / @zhushanwen/session-delivery)的运行时
9
9
  // import 收敛在此层,core 闭包(D9 守卫对象)不得出现这些包。
10
10
  //
11
11
  // 端口语义:
12
12
  // - dataRoot / discoveryRoots 每次调用现取 getAgentDir(),禁止模块级缓存:
13
- // getAgentDir 尊重 PI_CODING_AGENT_DIR 实例隔离(xyz-agent 按 session dir
13
+ // getAgentDir 尊重 PI_CODING_AGENT_DIR 实例隔离(taiji 按 session dir
14
14
  // 隔离 pi 实例),缓存会把后续切换实例的进程钉死在首个 agentDir。
15
15
  // - discoveryRoots 的根清单/顺序/source 标签与 shared/resource-discovery.ts
16
16
  // buildScanTargets、orchestration/skill-discovery.ts resolveSkillPath 的现推导
@@ -22,7 +22,7 @@
22
22
  // - countActiveFromEntries 适配:pi 侧真函数返回 CountActiveResult 对象,core
23
23
  // 端口契约是 number(core 消费面只读 .count,notify-ports.ts 契约注释)——
24
24
  // foundation 单元登记给本单元的适配责任。
25
- // - createDelivery 透传:@xyz-agent/session-delivery 的 createDelivery 与 core
25
+ // - createDelivery 透传:@zhushanwen/session-delivery 的 createDelivery 与 core
26
26
  // 的 Delivery* 结构化类型逐字段结构兼容(DeliveryHandle 的 sendChecked/depth
27
27
  // 是结构超集成员,多不碍兼容)——结构兼容由本注入点 typecheck 守护,上游签名
28
28
  // 漂移即红(notify-ports.ts「闭包红线」段)。
@@ -34,7 +34,7 @@ import { fileURLToPath } from "node:url";
34
34
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
35
35
  import { getLogger } from "@zhushanwen/pi-extension-logger";
36
36
  import { countActiveFromEntries } from "@zhushanwen/pi-pending-notifications";
37
- import { createDelivery } from "@xyz-agent/session-delivery";
37
+ import { createDelivery } from "@zhushanwen/session-delivery";
38
38
 
39
39
  import type { DiscoveryRoot, HostServices } from "@zhushanwen/subagent-core";
40
40
  import type { LogLevel } from "@zhushanwen/subagent-core";
@@ -120,7 +120,7 @@ function skillRoots(): DiscoveryRoot[] {
120
120
 
121
121
  /**
122
122
  * 引擎包发现根(W4 L1 第二通道,设计 §3.4)。打包态主通道 = env
123
- * XYZ_AGENT_ENGINE_ROOTS(W9 注入),此处承载 pi 宿主的常规安装路径:
123
+ * TAIJI_AGENT_ENGINE_ROOTS(W9 注入),此处承载 pi 宿主的常规安装路径:
124
124
  * 1. 宿主包自身 node_modules——引擎包(<engine>-subagent-cli)作为本包
125
125
  * dependencies 安装位(W9「扩展 package.json 声明引擎包为 dependencies」);
126
126
  * 2. <agentDir>/npm/node_modules——pi npm 安装位(org 分组二层布局由扫描器
package/src/index.ts CHANGED
@@ -163,7 +163,7 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
163
163
  setInFlightListener(inflightReporter.onInFlightChanged);
164
164
 
165
165
  // [U7b] 引擎列表在 extension 模块加载时即同步 engines.json(不等 session_start——
166
- // 用户体验拍板 2026-08-25:xyz-agent 打开后激活任意 session 的第一时间(含 TUI 等价
166
+ // 用户体验拍板 2026-08-25:taiji 打开后激活任意 session 的第一时间(含 TUI 等价
167
167
  // 场景)GUI 引擎选择器就该有数据;session_start 处保留幂等重写兜底 jiti 双路径/
168
168
  // 模块重载场景的刷新)。
169
169
  syncEnginesFile(getAgentDir());
@@ -495,7 +495,7 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
495
495
  //
496
496
  // - SIGTERM:pi 各 mode(rpc/interactive/print)自带 SIGTERM handler 负责退出编排,
497
497
  // 本 extension 的 handler 只做收割 + 设 exitCode(不 re-raise、不抢 pi 的退出语义;
498
- // xyz-agent 桌面 supervisor 用 SIGTERM 杀 pi 走这条路)。
498
+ // taiji 桌面 supervisor 用 SIGTERM 杀 pi 走这条路)。
499
499
  // - SIGINT:pi 本体不注册常规 SIGINT handler(interactive/print/rpc 均 SIGTERM only),
500
500
  // 依赖 Node 默认终止。本 extension 注册 listener 即取消默认终止——若只设 exitCode,
501
501
  // 本地 pi CLI 的 Ctrl-C 杀不死进程(TUI/stdin/agent loop 仍在事件循环)。故收割
@@ -42,6 +42,7 @@ import { getLogger } from "@zhushanwen/pi-extension-logger";
42
42
 
43
43
  // C5①/C5⑦:渲染统一走 core barrel(formatModelList + ModelEntry 类型为 barrel 导出面)
44
44
  import { formatModelList, type ModelEntry } from "@zhushanwen/subagent-core";
45
+ import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
45
46
 
46
47
  const logger = getLogger("injector");
47
48
 
@@ -87,7 +88,7 @@ export function setupModelListInjector(pi: ExtensionAPI): void {
87
88
  return { systemPrompt: event.systemPrompt + injection };
88
89
  } catch (err) {
89
90
  logger.error("[model-list-injector] before_agent_start failed", {
90
- reason: err instanceof Error ? err.message : String(err),
91
+ reason: toErrorMessage(err),
91
92
  });
92
93
  }
93
94
  },
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * subagent / workflow 两个资源清单 injector 的同构骨架单实现(原两文件逐字同构的
5
5
  * 缓存对 / 唯一写点 / 发现函数 / 三 handler 收敛于此,改 fallback 策略只改一处):
6
- * - 缓存对:entries + 渲染快照。per-process = per-session——xyz-agent session-pool
6
+ * - 缓存对:entries + 渲染快照。per-process = per-session——taiji session-pool
7
7
  * 模型下每 pi 子进程 = 一 session = 独立扩展实例,闭包级缓存天然 per-session 隔离
8
8
  * (split mode 多 session 各自独立进程)。渲染快照与数据缓存同步更新:before_agent_start
9
9
  * 每个 turn 都要注入,format(escapeXml 多趟正则 × 全部字段)在数据不变时输出完全
@@ -135,21 +135,24 @@ describe("C5③ run 内置名(fake registry)", () => {
135
135
  expect(vi.mocked(runWorkflow)).not.toHaveBeenCalled();
136
136
  });
137
137
 
138
- it("两者都 miss → 报错文案与改造前一致(含建议清单与 location 指引)", async () => {
138
+ it("两者都 miss → 报错含建议清单且逐条附绝对路径 location(按名失败后的自救指引)", async () => {
139
139
  const registry = {
140
140
  get: vi.fn().mockResolvedValue(undefined),
141
141
  getPath: vi.fn().mockResolvedValue(undefined),
142
142
  loadAll: vi.fn().mockResolvedValue([makeScript("chain", "/builtin/workflows/chain.js")]),
143
143
  };
144
- await expect(
145
- actionRun(
146
- { action: "run", name: "no-such" } as never,
147
- makeDeps(registry) as never,
148
- undefined,
149
- ),
150
- ).rejects.toThrow(
151
- "Workflow 'no-such' not found. Available:\n - chain: chain workflow\nUse <location> from <available_workflows> for the absolute .js path.",
144
+ const err = await actionRun(
145
+ { action: "run", name: "no-such" } as never,
146
+ makeDeps(registry) as never,
147
+ undefined,
148
+ ).catch((e: unknown) => e as Error);
149
+ // 逐条 location:模型按清单里的名字重试注定再 throw(8.6.0 getPath-only 时代
150
+ // 实测失败形态)——摘要必须直接给出可用的绝对路径,失败一次即自救。
151
+ expect(err.message).toContain(
152
+ "Workflow 'no-such' not found. Available (name — use the absolute location path as 'name' when the bare name is rejected):",
152
153
  );
154
+ expect(err.message).toContain(" - chain: chain workflow");
155
+ expect(err.message).toContain(" location: /builtin/workflows/chain.js");
153
156
  });
154
157
  });
155
158
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * command-actions — RPC 模式 slash command action 解析纯函数。
3
3
  *
4
- * xyz-agent GUI 通过 `client.prompt("/subagents cancel <id>")` 等触发生命周期操作,
4
+ * taiji GUI 通过 `client.prompt("/subagents cancel <id>")` 等触发生命周期操作,
5
5
  * 不经 LLM(pi 的 _tryExecuteExtensionCommand 在 agent loop 前短路)。command handler
6
6
  * 在 RPC 模式下用这两个函数解析 action 字符串,分发到对应 service/lifecycle 调用。
7
7
  *
@@ -96,7 +96,7 @@ export function registerWorkflowsCommand(
96
96
  return null;
97
97
  },
98
98
  handler: async (args: string, ctx: ExtensionCommandContext) => {
99
- // ── RPC 模式(xyz-agent GUI):解析 lifecycle action 直接执行,不打开 TUI ──
99
+ // ── RPC 模式(taiji GUI):解析 lifecycle action 直接执行,不打开 TUI ──
100
100
  // hasUI 在 TUI 和 RPC 都为 true,不能用于区分;用 ctx.mode === "rpc" 判定 GUI 通道。
101
101
  if (ctx.mode === "rpc") {
102
102
  await handleRpcMode(parseWorkflowRpcCommand(args), ctx, deps);
@@ -125,7 +125,7 @@ export function registerWorkflowsCommand(
125
125
  // ── Helpers ──────────────────────────────────────────────────
126
126
 
127
127
  /**
128
- * RPC 模式分支(xyz-agent GUI):解析后的 lifecycle action 直接执行,不打开 TUI。
128
+ * RPC 模式分支(taiji GUI):解析后的 lifecycle action 直接执行,不打开 TUI。
129
129
  *
130
130
  * 各 action 语义:
131
131
  * - abort → 调 lifecycle abortRun,成功/失败均 notify(不向上抛)
@@ -13,6 +13,8 @@ import os from "node:os";
13
13
 
14
14
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
15
15
 
16
+ import { firstContentText } from "@zhushanwen/extension-protocol";
17
+
16
18
  import type { AgentEventLogEntry, DisplayItem, ExecutionStatus } from "@zhushanwen/subagent-core";
17
19
  import { DEFAULT_AGENT_NAME } from "@zhushanwen/subagent-core";
18
20
  import type {
@@ -562,12 +564,13 @@ export function formatTokenStat(
562
564
  /**
563
565
  * renderResult 的文本兜底:从 result.content[0] 提取纯文本。
564
566
  * 多处 tool 的 renderResult 曾各自内联此逻辑,提取后统一调用。
567
+ * 内核收敛至 extension-protocol firstContentText(ext-simplify-17 D9);
568
+ * 本包装保留 content 可选的宽入参形态(tool renderResult 回调契约 content 可缺省)。
565
569
  */
566
570
  export function renderTextFallback(
567
571
  result: { content?: Array<{ type: string; text?: string }> },
568
572
  ): string {
569
- const first = result.content?.[0];
570
- return first?.type === "text" ? (first.text ?? "") : "";
573
+ return firstContentText({ content: result.content ?? [] });
571
574
  }
572
575
 
573
576
  /** Format a single activity line: ToolName(argsPreview). */
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * GUI 协议映射辅助函数 —— run/subagent 状态字符串 → 协议 TreeItem 状态 + 图标。
3
3
  *
4
- * 协议包 @xyz-agent/extension-protocol 的 list-tree 组件用 TreeItem.status(三态)
4
+ * 协议包 @zhushanwen/extension-protocol 的 list-tree 组件用 TreeItem.status(三态)
5
5
  * + TreeItem.icon 表达运行态。本模块把 workflow/subagent 领域的丰富状态字符串收口
6
6
  * 到这两个枚举,供 helpers.ts / tool-workflow.ts / subagent-actions.ts 复用。
7
7
  *
8
- * 参考:@xyz-agent/extension-protocol GuiComponentProps['list-tree']。
8
+ * 参考:@zhushanwen/extension-protocol GuiComponentProps['list-tree']。
9
9
  */
10
10
 
11
- import type { GuiContext, TreeItem, TreeItemIcon } from "@xyz-agent/extension-protocol";
11
+ import type { GuiContext, TreeItem, TreeItemIcon } from "@zhushanwen/extension-protocol";
12
12
 
13
13
  /**
14
14
  * 从 Pi ExtensionContext 构造协议 GuiContext 的最小子集。
@@ -26,7 +26,7 @@ import {
26
26
  type GuiRenderResult,
27
27
  guiResult,
28
28
  isGuiCapable,
29
- } from "@xyz-agent/extension-protocol";
29
+ } from "@zhushanwen/extension-protocol";
30
30
  import { mapRunIcon, mapRunStatus } from "./gui-mappers.ts";
31
31
 
32
32
  // ── 常量 ─────────────────────────────────────────────────────
@@ -19,7 +19,7 @@ import {
19
19
  type GuiContext,
20
20
  guiResult,
21
21
  isGuiCapable,
22
- } from "@xyz-agent/extension-protocol";
22
+ } from "@zhushanwen/extension-protocol";
23
23
 
24
24
  import type {
25
25
  CancelHandlerResult,
@@ -28,7 +28,7 @@ export { SLUG_MAX_LENGTH };
28
28
 
29
29
  // Params schema(跨包契约测试的真实 typebox 校验入口)。
30
30
  //
31
- // action:"start" 的 17 字段(task/slug/agent/model/...)拍平在顶层,不再用 startParam
31
+ // action:"start" 的 16 字段(task/slug/agent/model/...)拍平在顶层,不再用 startParam
32
32
  // 嵌套容器包。原因:弱模型(GLM/DeepSeek)信任 schema 结构信号 > 文本信号,经常省略
33
33
  // startParam 嵌套层把 task/slug 直接平铺到顶层导致调用失败。拍平后 schema 结构与模型
34
34
  // 的自然倾向一致,消除这层误用。task/slug 必填性由 startHandler runtime 校验(flat
@@ -40,7 +40,7 @@ export { SLUG_MAX_LENGTH };
40
40
  // 反映必填性。勿在此基础上继续堆 action 条件逻辑——要加就拆 tool。
41
41
  export const SubagentParams = Type.Object({
42
42
  action: StringEnum(["start", "list", "cancel", "message", "close", "fork-from"], {
43
- description: "Operation: 'start' runs a subagent, 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to any of your subagents (running or idle — an idle one transparently revives and continues on its original session file; one-shot subagents are auto-upgraded to conversation mode on first message), 'close' archives a subagent (immediately when idle; after the current round, or immediately with force:true, when running), 'fork-from' spawns a NEW subagent inheriting an older one's history (recovery for restart-disconnected subagents; the old record is untouched).",
43
+ description: "Operation: 'start' runs a subagent, 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to any of your subagents (running or idle — an idle one transparently revives and continues on its original session file), 'close' archives a subagent (immediately when idle; after the current round, or immediately with force:true, when running), 'fork-from' spawns a NEW subagent inheriting an older one's history (recovery for restart-disconnected subagents; the old record is untouched).",
44
44
  }),
45
45
  // ── action:"start" fields (flattened to top level). task/slug REQUIRED for start. ──
46
46
  // Missing/empty task or slug throws at runtime (startHandler).
@@ -88,40 +88,34 @@ export const SubagentParams = Type.Object({
88
88
  description: 'Override the working directory for the subagent execution. Must be an absolute path (no "~" shorthand, no relative paths); ".." segments are rejected. Defaults to the parent session\'s cwd.',
89
89
  pattern: "^/",
90
90
  })),
91
- conversation: Type.Optional(Type.Boolean({
92
- description:
93
- "Enable continuous chat with this subagent. When true, the subagent stays available after each reply — you can send follow-up messages (action:'message') and it keeps the full conversation context across rounds, with no need to re-spawn or re-explain. " +
94
- "\nUse conversation:true for: multi-round collaboration (iterative review-fix loops, back-and-forth refinement), any task where you expect to send follow-up messages after the initial result. " +
95
- "\nOmit (or false) for: one-shot tasks — single exploration, lookup, file read, code generation that needs no follow-up. The subagent runs once, notifies on completion, and is cleaned up automatically (default). " +
96
- "\nFor long-interval collaboration (each round spaced >5min apart), set conversation:true AND increase idleTimeoutMs to avoid premature timeout. " +
97
- "Cost: a conversation-mode subagent holds resources (memory, and a worktree if enabled) until you explicitly end it with action:'close'. Always close when done.",
98
- })),
91
+ // [modeless 波5] conversation 参数已删除:chatMode 字段消亡后「模式」不存在——
92
+ // 一切 record 永续可续聊(idle 后 message 即续、fork-from 可继承),无模式开关可表达。
99
93
  idleTimeoutMs: Type.Optional(Type.Number({
100
94
  description:
101
- "Idle timeout in milliseconds for conversation-mode subagents. Controls how long an idle subagent (between rounds) stays alive before automatic cleanup. " +
102
- "Default: 300000 (5min). Override for long-interval collaboration where each round is spaced >5min apart. " +
103
- "Pass 0 or a negative value to DISABLE idle cleanup entirely (subagent stays alive until explicitly closed). " +
104
- "Only meaningful with conversation:true; ignored for one-shot subagents.",
95
+ "Idle-recycle cadence for ALL subagents (modeless: every subagent stays continuable — this is NOT a mode switch). Controls how long an idle subagent (between rounds, no activity) stays before being automatically archived. " +
96
+ "Default: 300000 (5min). Raise it for long-interval collaboration where your next message may arrive more than 5min after a round ends. " +
97
+ "Pass 0 or a negative value to DISABLE idle recycling entirely (subagent stays available until you close it). " +
98
+ "Priority: this param > env TAIJI_SUBAGENT_IDLE_TIMEOUT_MS > default.",
105
99
  })),
106
100
  engine: Type.Optional(StringEnum(["pi", "zcode"], {
107
101
  description:
108
102
  "Execution engine for this subagent. Omit to inherit the global config. " +
109
103
  "Three-layer priority: this parameter > agent .md frontmatter engine > config.json defaultEngine. " +
110
- "Non-pi engines do not support conversation/fork/worktree (rejected before the subagent is created).",
104
+ "Non-pi engines do not support fork/worktree (rejected before the subagent is created).",
111
105
  })),
112
106
  collect: Type.Optional(StringEnum(["async", "sync"], {
113
107
  description:
114
- "Completion-notification collection mode for one-shot subagents (subagent-sync-collect). " +
108
+ "Completion-notification routing (NOT a record mode batch membership is routing bookkeeping only). " +
115
109
  "Omit to use the config default (currently async). " +
116
- "Use 'sync' when you dispatch >=2 independent one-shot subagents whose results you will combine: " +
117
- "their completions are held until ALL pending sync subagents finish, then delivered as ONE batch " +
118
- "notification (single wake-up, results inline). You may keep dispatching more sync subagents in " +
119
- "later turns they join the same pending batch. " +
120
- "Independent means no member's prompt or work depends on another member's output — dependent " +
121
- "tasks must be chained across messages (one start after the prior completes), never batched. " +
122
- "Use 'async' (or omit) for immediate per-subagent completion notifications. " +
123
- "Incompatible with conversation:truethat combination is rejected immediately before start; " +
124
- "remove either conversation or collect.",
110
+ "'async' = each subagent's completion notifies immediately. " +
111
+ "'sync' = batch wake-up: when you dispatch >=2 independent subagents whose results you will " +
112
+ "combine, their completions are held until ALL pending sync members finish, then delivered as " +
113
+ "ONE batch notification (single wake-up, results inline); when the batch closes, its members " +
114
+ "are automatically archived. Batch members cannot be messaged use action:'fork-from' to " +
115
+ "continue from one instead. You may keep dispatching more sync subagents in later turns " +
116
+ "they join the same pending batch. Independent means no member's prompt or work depends on " +
117
+ "another member's outputdependent tasks must be chained across messages (one start after " +
118
+ "the prior completes), never batched.",
125
119
  })),
126
120
  // action:"list" → listParam OPTIONAL (all fields optional, defaults apply). Ignored by other actions.
127
121
  listParam: Type.Optional(Type.Object({
@@ -147,11 +141,12 @@ export const SubagentParams = Type.Object({
147
141
  })),
148
142
  // action:"message" → messageParam.subagentId + text REQUIRED. Any reachable subagent works —
149
143
  // running joins the in-flight round (D2 打断入队);idle transparently revives on the same
150
- // session file([U4 §3.2.3] 万物可续——形态枚举 gate 消亡);one-shot auto-upgrades to
151
- // conversation mode on first message (SP-5)。description 与实现锚点见 messageHandler。
144
+ // session file([U4 §3.2.3] 万物可续——形态枚举 gate SP-5 升级路径均消亡,message 直接
145
+ // 续聊任何 idle record)。引擎续聊能力轴 gate 保留(core messageHandler 入口)。
146
+ // description 与实现锚点见 messageHandler。
152
147
  messageParam: Type.Optional(Type.Object({
153
148
  subagentId: Type.String({
154
- description: "REQUIRED for action:'message'. The subagentId to message. Any subagent reachable in this session tree works, running or idle: an idle subagent transparently revives on the same id and continues writing its original session file (a one-shot is auto-upgraded to conversation mode on first message); a running subagent has your message interrupt-and-join its in-flight round. Rejections: unknown id, session file held by another live process, a record from a different session tree, workflow-origin records (their results belong to the workflow run), and one-shot records on engines that do not support conversation upgrade.",
149
+ description: "REQUIRED for action:'message'. The subagentId to message. Any subagent reachable in this session tree works, running or idle: an idle subagent transparently revives on the same id and continues writing its original session file; a running subagent has your message interrupt-and-join its in-flight round. Rejections: unknown id, session file held by another live process, a record from a different session tree, workflow-origin records (their results belong to the workflow run), and records on engines that do not support continuation (fork-from or re-dispatch instead).",
155
150
  }),
156
151
  text: Type.String({
157
152
  description: "REQUIRED for action:'message'. The message to send. Whitespace-only throws.",
@@ -178,7 +173,7 @@ export const SubagentParams = Type.Object({
178
173
  // 源文件只读不续写);旧记录/状态机不动。pi 引擎限定(非 pi 在 execute 层拒绝)。
179
174
  forkFromParam: Type.Optional(Type.Object({
180
175
  sourceSubagentId: Type.String({
181
- description: "REQUIRED for action:'fork-from'. The OLD subagentId whose conversation history becomes the inherited context of the new subagent. Works for any idle record — disconnected by a session restart, already finished, or previously closed/cancelled. Rejections: still-running sources (message them instead), sources held by another live process, worktree-bound sources, and unknown ids; an unparseable history anchor is guided to action:'message' (same-id reopen) instead.",
176
+ description: "REQUIRED for action:'fork-from'. The OLD subagentId whose conversation history becomes the inherited context of the new subagent. Works for any idle record — disconnected by a session restart, already finished, or previously closed/cancelled. Rejections: still-running sources (message them instead; sync-collect members cannot be messaged — close them first, then fork-from), sources held by another live process, worktree-bound sources, and unknown ids; an unparseable history anchor is guided to action:'message' (same-id reopen) instead.",
182
177
  }),
183
178
  prompt: Type.Optional(Type.String({
184
179
  description: "Continuation instruction for the new subagent (what to do next on top of the inherited history). When omitted, a standard handover frame is injected: reconstruct done/decided/remaining from the inherited history, then continue to completion. Whitespace-only treated as omitted.",
@@ -162,11 +162,11 @@ action:"list" before action:"start" — a reusable subagent may exist; compactio
162
162
 
163
163
  ## Actions
164
164
 
165
- - action:"start" — run a subagent. Pass task and slug as top-level fields (REQUIRED). Optional: agent, model, thinkingLevel, engine, collect, skillPath, appendSystemPrompt, schema, maxTurns, graceTurns, fork, worktree, cwd, conversation, idleTimeoutMs. Background only: returns a subagentId immediately, notifies on completion.
166
- - action:"message" — send a follow-up to any of your subagents — running or idle (idle revives in place; one-shots become conversation-mode); full context retained. REQUIRED messageParam: { subagentId, text }. The reply auto-notifies.
165
+ - action:"start" — run a subagent. Pass task and slug as top-level fields (REQUIRED). Optional: agent, model, thinkingLevel, engine, collect, skillPath, appendSystemPrompt, schema, maxTurns, graceTurns, fork, worktree, cwd, idleTimeoutMs. Background only: returns a subagentId immediately, notifies on completion.
166
+ - action:"message" — send a follow-up to any of your subagents — running or idle (idle revives in place; full context retained). REQUIRED messageParam: { subagentId, text }. The reply auto-notifies.
167
167
  - action:"close" — archive a subagent (hidden from list, recoverable): idle closes immediately; running finishes the current round first unless force:true (then terminates mid-round). REQUIRED closeParam: { subagentId }.
168
168
  - action:"list" — list subagents. listParam: { includeFinished?, includeWorkflow?, limit? } (all optional; includeWorkflow defaults false — workflow-dispatched subagents are hidden unless true). Read an item's sessionFile for full detail.
169
- - action:"cancel" — stop a background subagent (for conversation-mode use close). REQUIRED cancelParam: { subagentId }.
169
+ - action:"cancel" — stop a background subagent (to archive it instead, use close). REQUIRED cancelParam: { subagentId }.
170
170
  - action:"fork-from" — restart-disconnect recovery: spawn a NEW subagent inheriting the old one's history via --fork. REQUIRED forkFromParam: { sourceSubagentId }. Optional: prompt (continuation; default handover frame). Returns { newSubagentId, sourceSessionFile }. Rejects still-running / foreign-live / worktree-bound sources; unparseable history anchors are guided to action:"message" (same-id reopen).
171
171
 
172
172
  ## Examples
@@ -191,8 +191,8 @@ Completion auto-notifies you (steer wakes the next turn):
191
191
 
192
192
  ## Batch collection (collect)
193
193
 
194
- - collect:"sync" — >=2 independent one-shot subagents whose results you will combine: completions are held until every pending sync member finishes, then ONE batch notification delivers all results inline (one wake-up). Later sync starts join the same batch; each sync start response reports {"collect":{"mode":"sync","pendingSyncCount":N}}.
195
- - collect:"async" (default, omit) — immediate per-subagent completion; for conversational work or when each result is needed early.
194
+ - collect:"sync" — >=2 independent subagents whose results you will combine: completions are held until every pending sync member finishes, then ONE batch notification delivers all results inline (one wake-up) and batch members auto-archive. Later sync starts join the same batch; each sync start response reports {"collect":{"mode":"sync","pendingSyncCount":N}}. Batch members cannot be messaged — fork-from continues from one.
195
+ - collect:"async" (default, omit) — immediate per-subagent completion; use when each result is needed early.
196
196
  - Subagents in one sync batch must not depend on each other's output — dependent tasks must be chained across messages (see Calling patterns), never batched.
197
197
  Items over budget are truncated with a pointer: session_read {"action":"result","session":"<id>"} fetches the full text.
198
198
 
@@ -204,16 +204,15 @@ Items over budget are truncated with a pointer: session_read {"action":"result",
204
204
  - Treating subagent results as authoritative without verification.
205
205
  - Canceling by guessing a subagentId instead of using action:"list" first.
206
206
 
207
- ## Continuous chat (conversation mode)
207
+ ## Continuing a subagent (modeless)
208
208
 
209
- conversation:true keeps a subagent available across replies action:"message" continues with full context, action:"close" releases it (always close when done). For review/fix loops and long-interval rounds (>5min apart, raise idleTimeoutMs); omit for one-shot tasks.
210
- idleTimeoutMs: idle timeout before auto-cleanup (default 300000 / 5min; env XYZ_SUBAGENT_IDLE_TIMEOUT_MS overrides globally, per-call wins).
209
+ Every subagent stays continuable no mode switch: action:"message" revives an idle record in place (or joins a running one's round), action:"fork-from" branches a new subagent from old history, action:"close" archives it.
210
+ idleTimeoutMs: idle-recycle cadence for ALL subagents — idle records auto-archive on expiry (default 300000 / 5min; 0/negative disables; env TAIJI_SUBAGENT_IDLE_TIMEOUT_MS: global default, per-call wins).
211
211
 
212
212
  ## You cannot
213
213
 
214
214
  - Get a synchronous/inline result — start always returns a subagentId immediately (background).
215
215
  - Read mid-flight streaming output — wait for the completion notification.
216
- - Combine collect:"sync" with conversation:true — rejected before start; sync is one-shot only (remove one).
217
216
  - See intermediate signals while a sync batch waits — nothing arrives until the whole batch closes. Hung member: action:"list" shows what is still running; action:"cancel" it — cancelled members count as terminal and the batch closes.
218
217
 
219
218
  ## Calling patterns
@@ -241,7 +240,7 @@ Do NOT recurse when: the work is linear/flat; the child needs your context; or y
241
240
 
242
241
  // ponytail: renderCall 每次 TUI invalidate 都触发。streaming 中 args 是 partial JSON
243
242
  // 解析结果(如 model="deep" 来自未流完的 "deepseek-router/ds-pro"),解析失败是预期。
244
- // 不走 appendEntry(非真实错误),只走 logger.debug(默认 no-op,XYZ_AGENT_DEBUG=1 写文件)。
243
+ // 不走 appendEntry(非真实错误),只走 logger.debug(默认 no-op,TAIJI_AGENT_DEBUG=1 写文件)。
245
244
  const renderCallLogger = getLogger("subagents");
246
245
 
247
246
  const subagentRenderCall: SubagentRenderCallCb = (args, theme, ctx) => {
@@ -261,7 +260,7 @@ const subagentRenderCall: SubagentRenderCallCb = (args, theme, ctx) => {
261
260
  if (r) resolved = { model: `${r.model.provider}/${r.model.id}`, thinkingLevel: r.thinkingLevel };
262
261
  } catch (err) {
263
262
  // streaming 中间态(partial JSON)或 service 未就绪 → 降级不显示 model(renderCall 不应崩)。
264
- // 不阻断渲染,不污染 TUI。开发期开 XYZ_AGENT_DEBUG=1 可写文件日志排查。
263
+ // 不阻断渲染,不污染 TUI。开发期开 TAIJI_AGENT_DEBUG=1 可写文件日志排查。
265
264
  renderCallLogger.debug("renderCall model resolution failed, degrading", {
266
265
  reason: toErrorMessage(err),
267
266
  });
@@ -3,7 +3,7 @@
3
3
  // /subagents 命令。薄壳——打开 list overlay(等同原 /subagents list [<id>])。
4
4
  //
5
5
  // 解析:args[0] 直接作可选 <id>(聚焦该 record)。
6
- // RPC 模式(xyz-agent GUI):解析 cancel/message/start action 直接执行,不打开 TUI。
6
+ // RPC 模式(taiji GUI):解析 cancel/message/start action 直接执行,不打开 TUI。
7
7
  // message/start 为 GUI 定向消息通道(设计 §3.3.3):GUI 经 client.prompt 短路
8
8
  // extension 命令(不经主 agent LLM),TUI 分支不消费这两个 verb(行为零变化)。
9
9
 
@@ -129,14 +129,14 @@ async function rpcStart(
129
129
  task: string,
130
130
  ctx: ExtensionCommandContext,
131
131
  ): Promise<void> {
132
- // GUI 定向新建(设计 §3.3.3):conversation 固定 true(GUI 定向对话场景需要可续聊)
132
+ // GUI 定向新建(设计 §3.3.3):万物可续(modeless),start 无续聊参数——
133
+ // 续聊资格由引擎 conversation 能力轴在 message 面把关。
133
134
  try {
134
135
  const result = await startHandler(
135
136
  service,
136
137
  {
137
138
  slug,
138
139
  task,
139
- conversation: true,
140
140
  },
141
141
  // RPC 命令无外层 AbortSignal(GUI 请求生命周期不映射到 subagent 取消——
142
142
  // start 是 detached 后台语义,取消走 /subagents cancel)
@@ -156,7 +156,7 @@ async function rpcStart(
156
156
  }
157
157
 
158
158
  /**
159
- * RPC 模式(xyz-agent GUI):解析后的 action 分发执行,不打开 TUI。
159
+ * RPC 模式(taiji GUI):解析后的 action 分发执行,不打开 TUI。
160
160
  * 行为等价拆分自 handler(fallow 圈复杂度 21 > 15):三个执行体
161
161
  * (cancel/message/start)各自成函数,本函数只做 switch 分发 +
162
162
  * usage notify + exhaustiveness 断言。
@@ -252,7 +252,7 @@ export function registerSubagentsCommand(pi: ExtensionAPI): void {
252
252
  return;
253
253
  }
254
254
 
255
- // ── RPC 模式(xyz-agent GUI):解析 action 直接执行,不打开 TUI ──
255
+ // ── RPC 模式(taiji GUI):解析 action 直接执行,不打开 TUI ──
256
256
  // hasUI 在 TUI 和 RPC 都为 true,不能用于区分;用 ctx.mode === "rpc" 判定 GUI 通道。
257
257
  if (ctx.mode === "rpc") {
258
258
  await executeRpcAction(pi, service, parseSubagentRpcCommand(argsStr), ctx);
@@ -32,7 +32,7 @@ import {
32
32
  type GuiRenderResult,
33
33
  guiResult,
34
34
  isGuiCapable,
35
- } from "@xyz-agent/extension-protocol";
35
+ } from "@zhushanwen/extension-protocol";
36
36
  // C5②/C5⑦:创作闭环统一走 core barrel(generateWorkflowScript/saveWorkflow/
37
37
  // deleteWorkflow/lintScript 均为 barrel 导出面;深路径在 npm/vendored 形态不可达)
38
38
  import {
@@ -28,7 +28,7 @@ import {
28
28
  type GuiRenderResult,
29
29
  guiResult,
30
30
  isGuiCapable,
31
- } from "@xyz-agent/extension-protocol";
31
+ } from "@zhushanwen/extension-protocol";
32
32
  import { type Static, Type } from "typebox";
33
33
 
34
34
  import { SLUG_MAX_LENGTH } from "@zhushanwen/subagent-core";
@@ -393,10 +393,16 @@ export async function actionRun(
393
393
  const all = await deps.registry.loadAll();
394
394
  const available = all.filter((wf) => wf.available);
395
395
  const suggestions = available
396
- .map((wf) => ` - ${wf.name}: ${wf.meta.description || "(no description)"}`)
396
+ .map((wf) => ` - ${wf.name}: ${wf.meta.description || "(no description)"}\n location: ${wf.path}`)
397
397
  .join("\n");
398
+ // [按名解析自救指引] 摘要逐条附绝对路径 location:run 的 name 形参最贴近的
399
+ // 读取面就是本清单(<available_workflows> 注入面在 start 时已过时/可能不在
400
+ // 上下文)——模型按清单里的名字重试(8.6.0 实装 getPath-only 时代的实测失败
401
+ // 形态,2026-09-14 嵌套 subagent 现场)还是注定 throw;带上 location 后失败
402
+ // 一次即可按绝对路径自救。main 的 C5③ 按名解析(registry.get 内置/已保存名
403
+ // 优先)是主修复,本行是清单侧恢复指引闭环。
398
404
  throw new Error(
399
- `Workflow '${name}' not found. Available:\n${suggestions || " (none)"}\nUse <location> from <available_workflows> for the absolute .js path.`,
405
+ `Workflow '${name}' not found. Available (name — use the absolute location path as 'name' when the bare name is rejected):\n${suggestions || " (none)"}`,
400
406
  );
401
407
  }
402
408
 
@@ -90,7 +90,7 @@ import {
90
90
  pruneStateFilesBeyondCap,
91
91
  type RunSnapshot,
92
92
  } from "@zhushanwen/subagent-core";
93
- import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
93
+ import { isEnoentError, toErrorMessage } from "@zhushanwen/pi-ext-guards";
94
94
 
95
95
  // ── Workflow-record self-describing entry (W17, D4) ─────────
96
96
 
@@ -226,16 +226,6 @@ async function loadRunFromStateFile(filePath: string): Promise<WorkflowRun | nul
226
226
 
227
227
  // ── JsonlRunStore ────────────────────────────────────────────
228
228
 
229
- /** Node fs 错误 code 判定(ENOENT = 路径不存在,并发删除场景)。 */
230
- function isEnoentError(err: unknown): boolean {
231
- return (
232
- typeof err === "object" &&
233
- err !== null &&
234
- "code" in err &&
235
- (err as { code: unknown }).code === "ENOENT"
236
- );
237
- }
238
-
239
229
  const logger = getLogger("subagents");
240
230
 
241
231
  /**
@@ -258,11 +248,11 @@ const DEFAULT_SAVE_DEBOUNCE_MS = 200;
258
248
  * - 非法值(非有限数/≤0)→ 不清理(显式 opt-out 通道:用户意图不明时不动
259
249
  * 磁盘,对齐 prune 内部「任何失败都不抛」的保守哲学)。
260
250
  *
261
- * 用 XYZ_ 前缀而非 PI_:本 env 是 pi 进程内读的配置 env,xyz-agent 桌面 spawn 链按
262
- * ENV_WHITELIST_PREFIXES(只有 XYZ_ 等)过滤,PI_ 前缀在桌面场景被静默丢弃——
263
- * 同 XYZ_SUBAGENT_IDLE_TIMEOUT_MS 的改名教训(lifecycle-manager.ts)。
251
+ * 用 TAIJI_ 前缀而非 PI_:本 env 是 pi 进程内读的配置 env,taiji 桌面 spawn 链按
252
+ * ENV_WHITELIST_PREFIXES(只有 TAIJI_ 等)过滤,PI_ 前缀在桌面场景被静默丢弃——
253
+ * 同 TAIJI_SUBAGENT_IDLE_TIMEOUT_MS 的改名教训(lifecycle-manager.ts)。
264
254
  */
265
- export const STATE_MAX_RUNS_ENV = "XYZ_SUBAGENT_STATE_MAX_RUNS";
255
+ export const STATE_MAX_RUNS_ENV = "TAIJI_SUBAGENT_STATE_MAX_RUNS";
266
256
 
267
257
  /** 解析保留上限;env 未设/空 → 默认上限,显式非法/≤0 → undefined(不清理)。 */
268
258
  function getEnvStateMaxRuns(): number | undefined {
@@ -230,7 +230,7 @@ function createOrReuseServices(pi: ExtensionAPI, ctx: ExtensionContext): Service
230
230
  // 注入 ctx.ui.setWidget 作为 streaming sink(只绑方法,不持有整个 ctx)。
231
231
  // background subagent 执行期间,text_delta 经 SubagentStream 合并后由此通道转发。
232
232
  // [W1 修复] ctx.mode === 'rpc' 守卫:TUI/json/print 下 streamSink = undefined(无 widget 噪音),
233
- // rpc mode(GUI/xyz-agent)下保持原行为(ctx.ui.setWidget → sidecar → chatStore)。
233
+ // rpc mode(GUI/taiji)下保持原行为(ctx.ui.setWidget → sidecar → chatStore)。
234
234
  // streamSink API 不变(SubagentStream.onDelta 仍可调,只是 TUI 下 stream 不会被创建)。
235
235
  streamSink: ctx.mode === "rpc"
236
236
  ? { setWidget: (key, lines) => ctx.ui.setWidget(key, lines) }
@@ -300,7 +300,6 @@ function appendSubagentIdentityEntry(pi: ExtensionAPI): void {
300
300
  process.env.PI_SUBAGENT_FORK_DEPTH !== undefined
301
301
  ? Number(process.env.PI_SUBAGENT_FORK_DEPTH)
302
302
  : undefined,
303
- chatMode: process.env.PI_SUBAGENT_CHAT_MODE === "true",
304
303
  // [review round2] worktree 隔离标志(session-runner 注入):跨重启重建路径据此
305
304
  // 拒绝续聊(handle 不可序列化,reattach 不可行)。
306
305
  worktree: process.env.PI_SUBAGENT_WORKTREE === "true",
@@ -373,7 +372,8 @@ export function bindLedgerHostAndRecover(pi: ExtensionAPI, ctx: ExtensionContext
373
372
  /**
374
373
  * 随迁块 4 的进程级维护三连(各 try-catch「失败记日志不阻断」,设计 §3.4):
375
374
  * 过期 session 文件清理 / ADR-035 manifest tmp 恢复 / ADR-035 worktree reaper 扫描。
376
- * 另含 [E1] sync 批崩溃恢复(per-session 域,不用 oncePerProcess——见块内注释)。
375
+ * ([modeless 波5] 原 [E1] sync 批崩溃恢复接线已摘除——collectMode 记录态消亡后
376
+ * core 侧 recoverSyncCollectBatch 已是 accepted-no-op,调用点随之退役。)
377
377
  */
378
378
  async function runProcessLevelMaintenance(
379
379
  agentDir: string,
@@ -381,22 +381,6 @@ async function runProcessLevelMaintenance(
381
381
  service: SubagentService,
382
382
  deps: SessionLifecycleDeps,
383
383
  ): Promise<void> {
384
- // [E1] sync 批崩溃恢复(subagent-sync-collect 设计 §3.1.5 E1,U5 接线):扫描主
385
- // session 末条 entry 重建批缓冲;全员终态未投递 → notifyBatch 补发 + 统一补
386
- // batchFinalized 标记(账本同 hash 幂等拒绝也算已投递)。须晚于 initSession(孤儿
387
- // 终态恢复先行收敛 running 成员,createOrReuseServices 内部同步完成)与 ledger
388
- // bind(补发走 notifyBatch 写账链,见上方 bindNotifyLedgerHost)。per-session 域
389
- // (主 session 文件),不用 oncePerProcess;best-effort 不阻断 session_start。
390
- // 时序约束在调用点 setupSessionLifecycle 已满足(bindLedgerHostAndRecover 与
391
- // createOrReuseServices 均先于本 helper 调用)。
392
- try {
393
- service.recoverSyncCollectBatch();
394
- } catch (err) {
395
- logger.warn("[subagents] sync collect batch recovery failed", {
396
- reason: toErrorMessage(err),
397
- });
398
- }
399
-
400
384
  try {
401
385
  // 递归扫描 <agentDir>/subagents + unlink 超 TTL 跨 session 文件属进程级维护
402
386
  // ——oncePerProcess 守卫防双跑(u-audit-fix)。