@zhushanwen/pi-subagent-workflow 9.4.1 → 9.5.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.
@@ -1,18 +1,16 @@
1
1
  /**
2
- * Workflow Extension — workflow tool(4 actions,FR-5 tool 收口)。
2
+ * Workflow Extension — workflow 工具组(tool-declaration-slimming D3 拆分终态)。
3
3
  *
4
- * 合并原 tool-workflow.ts + tool-workflow-run.ts 为单 tool。
4
+ * 拆为两工具(命名与 schedule_control 同族):
5
+ * - `workflow_run`:registry.getPath → runWorkflow(直接启动,无需用户确认;name 必填
6
+ * 由 schema 表达)
7
+ * - `workflow_control`(action: status/abort/resume):status 列出 runs(deps.runs)、
8
+ * abort 调 abortRun、resume 调 resumeRun(interrupted 态 run 断点续跑,D14 args
9
+ * 校验在入口)
5
10
  *
6
- * Actions:
7
- * - run: registry.getPath → runWorkflow(直接启动,无需用户确认)
8
- * - status: 列出 runs(deps.runs)
9
- * - abort: 调 abortRun
10
- * - resume: 调 resumeRun(interrupted 态 run 断点续跑,D14 args 校验在入口)
11
- *
12
- * **restart 不包含**(D-9 废弃);**pause 不包含**(一次性生命周期——run 不可
13
- * 挂起,提前停止用 abort,要新结果开新 run)。**resume 是中断恢复不是挂起恢复**:
14
- * 仅 interrupted 态 run 可续(崩溃收编 / terminate 被动失联后的断点续跑,
15
- * workflow-run-resume-revision U3/D14),不是把活跃 run 暂停再继续。
11
+ * **resume 是中断恢复不是挂起恢复**:仅 interrupted 态 run 可续(崩溃收编 / terminate
12
+ * 被动失联后的断点续跑,workflow-run-resume-revision U3/D14),不是把活跃 run 暂停再继续。
13
+ * 一次性生命周期:run 不可挂起(pause 已废弃),提前停止用 abort,要新结果开新 run。
16
14
  *
17
15
  * 层归属:Interface。依赖 Pi SDK + Engine lifecycle/launcher + helpers。
18
16
  */
@@ -68,74 +66,99 @@ import {
68
66
  throwPrefixed,
69
67
  } from "./tool-shared.ts";
70
68
 
71
- // ── Parameter schema ─────────────────────────────────────────
69
+ // ── Parameter schema(D3 拆分:workflow_run + workflow_control)─────────────
72
70
 
73
- /** workflow tool 的全部 action 枚举值(单一真相源)。 */
74
- export type WorkflowAction =
75
- | "run"
71
+ /** workflow_control 工具的 action 枚举值(单一真相源;run 拆出为独立工具无 action)。 */
72
+ export type WorkflowControlAction =
76
73
  | "status"
77
74
  | "abort"
78
75
  | "resume";
79
76
 
80
- const WORKFLOW_ACTIONS: readonly WorkflowAction[] = [
81
- "run",
77
+ const WORKFLOW_CONTROL_ACTIONS: readonly WorkflowControlAction[] = [
82
78
  "status",
83
79
  "abort",
84
80
  "resume",
85
81
  ];
86
82
 
87
- const WorkflowParams = Type.Object({
88
- action: StringEnum(WORKFLOW_ACTIONS, { description: "Workflow action to execute" }),
89
- name: Type.Optional(
90
- Type.String({ description: "Workflow ref: absolute path to a .js script — normally the <location> of a workflow from <available_workflows>, but ANY absolute .js path works if the file declares `@pi-meta kind: \"workflow\"` (the list is not required). Relative paths and bare names are rejected (run action)" }),
83
+ const WorkflowRunParams = Type.Object({
84
+ name: Type.String(
85
+ { description: "Absolute path to a .js script — normally a <location> from <available_workflows>; ANY absolute .js path declaring @pi-meta kind:\"workflow\" works. Relative paths and bare names are rejected." },
91
86
  ),
92
87
  slug: Type.Optional(
93
88
  Type.String({
94
- description:
95
- "Short label (max 35 chars) for this run, shown in the TUI to distinguish concurrent runs. " +
96
- "If omitted, defaults to the script name.",
89
+ description: "Short label (max 35 chars) shown in the TUI to distinguish concurrent runs. Defaults to the script name.",
97
90
  maxLength: SLUG_MAX_LENGTH,
98
91
  }),
99
92
  ),
93
+ args: Type.Optional(
94
+ Type.Record(Type.String(), Type.Unknown(), {
95
+ description: "Arguments passed to the workflow.",
96
+ }),
97
+ ),
98
+ tokens: Type.Optional(Type.Number({
99
+ // 预算纪律句已删(tool-declaration-slimming D1 单源化→注入段
100
+ // SUBAGENT_DISPATCH_SHARED_RULES,原文见 git 历史)。
101
+ description: "Max token budget.",
102
+ })),
103
+ time: Type.Optional(Type.Number({
104
+ description: `Max time budget in ms (hard ceiling ${MAX_TIMER_DELAY_MS} ms — larger values fail fast).`,
105
+ })),
106
+ model: Type.Optional(Type.String({
107
+ // 缺省继承主 agent 的纪律句已删(D1 单源化→注入段);
108
+ // run 级 override 作用域 / user override 优先级是本参数功能语义,保留。
109
+ description:
110
+ "Run-level model override in 'provider/modelId' format — all agents spawned by this run inherit it (unless a per-call agent() opts.model is set). " +
111
+ "A user-issued runtime override (panel switch / /subagent-model) wins over this parameter and over any explicitly scripted model.",
112
+ })),
113
+ thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
114
+ // 枚举值由 schema 自身的 enum 承载(不重复列举);「user override 不改显式 scripted
115
+ // level」的优先级事实随 D1 单源化从 promptGuidelines Note 条迁入本字段说明。
116
+ description: "Run-level thinkingLevel override — all agents in the run inherit it. Omit for each agent's model's highest available level. A user-issued runtime override (panel switch / /subagent-model) does not change an explicitly scripted level (explicit levels outrank the override's inline level).",
117
+ })),
118
+ });
119
+
120
+ const WorkflowControlParams = Type.Object({
121
+ action: StringEnum(WORKFLOW_CONTROL_ACTIONS, { description: "Control operation: 'status' lists runs, 'abort' stops one, 'resume' continues an interrupted run" }),
100
122
  runId: Type.Optional(
101
- Type.String({ description: "Workflow run ID (abort / resume action; find it via action:status)" }),
123
+ Type.String({ description: "Workflow run ID (abort / resume; find via action:status)" }),
102
124
  ),
103
125
  args: Type.Optional(
104
126
  Type.Record(Type.String(), Type.Unknown(), {
105
- description:
106
- "Arguments passed to workflow as key-value pairs (run action). For resume: optional — when passed, " +
107
- "must deep-equal the run's original args (except the internal _runId key); mismatch is rejected with " +
108
- "the differing fields listed. Omit to reuse the original args.",
127
+ description: "For resume: optional — when passed, must deep-equal the original args (except internal _runId); mismatch rejected with the differing fields listed. Omit to reuse the original args.",
109
128
  }),
110
129
  ),
111
- tokens: Type.Optional(Type.Number({ description: "Max token budget — ONLY set when user explicitly requests a limit; omit = unlimited (default). For resume: the token budget applied to the resumed execution (tokens already spent by the run are counted against it)" })),
112
- time: Type.Optional(Type.Number({ description: `Max time budget in ms — ONLY set when user explicitly requests a limit; omit = unlimited (default; hard ceiling ${MAX_TIMER_DELAY_MS} ms — larger values fail fast at entry). For resume: the budget applied to the resumed execution (active time already spent by the run is counted against it; suspended time is not)` })),
130
+ tokens: Type.Optional(Type.Number({
131
+ // 预算纪律句已删(D1 单源化→注入段);
132
+ // resume 分支是本参数的功能语义,保留。
133
+ description: "Max token budget. For resume: applied to the resumed execution (tokens already spent count against it)",
134
+ })),
135
+ time: Type.Optional(Type.Number({
136
+ description: `Max time budget in ms (hard ceiling ${MAX_TIMER_DELAY_MS} ms — larger values fail fast). For resume: applied to the resumed execution (active spent time counts; suspended does not)`,
137
+ })),
113
138
  error: Type.Optional(
114
- Type.String({ description: "Error/reason message (optional, used with abort)" }),
139
+ Type.String({ description: "Error/reason message (optional, with abort)" }),
115
140
  ),
116
141
  model: Type.Optional(Type.String({
142
+ // 缺省继承纪律句已删(D1 单源化→注入段);user override 优先级在 workflow_run 的
143
+ // model 字段说明一处(跨工具通用规则单源),此处只留 resume 分支功能语义。
117
144
  description:
118
- "Run-level model override in 'provider/modelId' format. When set, all agents spawned by this run inherit it by default (unless a per-call agent() opts.model is set). " +
119
- "Also accepted on action:\"resume\" — recorded as the run's user override (same channel as a panel switch) and drives all re-dispatched steps. " +
120
- "A user-issued runtime model override (panel model switch / /subagent-model command) takes precedence over this parameter — user override wins over any explicitly scripted model. " +
121
- "Omit to inherit the main agent's model.",
122
- })),
123
- thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
124
- description: "Run-level thinkingLevel override (off/minimal/low/medium/high/xhigh/max). All agents in this run inherit it by default. Omit to default each agent to its model's highest available level.",
145
+ "Resumed run's model override (same channel as a panel switch), driving all re-dispatched steps.",
125
146
  })),
126
147
  });
127
148
 
128
- type WorkflowToolParams = Static<typeof WorkflowParams>;
149
+ type WorkflowRunToolParams = Static<typeof WorkflowRunParams>;
150
+ type WorkflowControlToolParams = Static<typeof WorkflowControlParams>;
129
151
 
130
152
  // ── Constants ────────────────────────────────────────────────
131
153
 
132
154
  /**
133
- * tool 自身顶层键(workflow params schema 键)——workflow 参数名与 tool 键撞名时
134
- * (如 workflow 声明参数 name),顶层同名键是 tool 参数而非平铺(m6 评审 M-3)。
135
- * 未来新增 tool 顶层键需同步此集合。
155
+ * tool 自身顶层键(workflow_run / workflow_control 两 schema 键的合集)——workflow
156
+ * 参数名与 tool 键撞名时(如 workflow 声明参数 name),顶层同名键是 tool 参数而非
157
+ * 平铺(m6 评审 M-3)。合集形态:run 与 control 各自的键集都是本集合子集,两工具
158
+ * 共享同一 reservedKeys 注入(超集无害——reservedKeys 只做撞名豁免,不做校验)。
136
159
  *
137
160
  * D9 下沉后作为 core args-meta 的 reservedKeys 注入(ArgMetaOptions)——core 缺省
138
- * 空集,不注入则撞名保护失效(run 调用顶层必有 action/name,会被误判平铺)。
161
+ * 空集,不注入则撞名保护失效(run 调用顶层必有 name,会被误判平铺)。
139
162
  * export 供 detectors.test 复用同一集合防漂移。
140
163
  */
141
164
  export const TOOL_TOP_LEVEL = new Set([
@@ -181,7 +204,7 @@ interface RunSummary {
181
204
  // ── Tool result types ──
182
205
 
183
206
  /**
184
- * Discriminated union of `workflow` tool `details` payloads.
207
+ * Discriminated union of the workflow tools'(workflow_run / workflow_control)`details` payloads.
185
208
  *
186
209
  * Discriminant: `action`. Each action's details shape is explicitly typed so
187
210
  * downstream consumers (structured-output) can narrow without unsafe casts.
@@ -192,24 +215,25 @@ export type WorkflowToolDetails =
192
215
  | { action: "abort"; runId: string; status: string; reason?: string }
193
216
  | ({ action: "resume"; runId: string } & RunStartDetails);
194
217
 
195
- /** Result returned by the `workflow` tool's execute(公共骨架见 tool-result.ts)。 */
218
+ /** Result returned by the workflow tools' execute(公共骨架见 tool-result.ts)。 */
196
219
  type WorkflowExecuteResult = WorkflowToolResult<WorkflowToolDetails | undefined>;
197
220
 
198
221
  // ── Tool registration ────────────────────────────────────────
199
222
 
200
223
  /**
201
- * 注册 workflow tool(4 actions: run / status / abort / resume;pause 已随一次性
202
- * 生命周期移除——enum 拒绝由 pi 核心校验拦截,见 F3。resume = interrupted 态断点
203
- * 续跑,不是挂起恢复)。
224
+ * 注册 workflow 工具组(tool-declaration-slimming D3 拆分:workflow_run +
225
+ * workflow_control,命名与 schedule_control 同族)。签名不变、内部注册两工具——
226
+ * 组合根(index.ts)零改动。
227
+ *
228
+ * run 与 control 共用同一 reentryRef(与拆分前单工具 4 action 共用一 guard 语义
229
+ * 一致:abort/resume 与 run 同占守卫,双 guard 语义漂移不引入)。reentryRef 是
230
+ * factory 级单例(index.ts 在 factory 内创建),跨 session 共享。当前 Pi 运行时
231
+ * 单 session 串行(同一时刻只有一个 active session),跨 session 不会并发触发
232
+ * workflow action,故共享无竞态。若未来支持多 session 并发,需改为 per-session guard。
204
233
  *
205
234
  * @param pi ExtensionAPI
206
235
  * @param deps LauncherDeps(LifecycleDeps + registry)
207
- * @param reentryRef reentry guard。仅 workflow tool 使用——workflow-script tool
208
- * 有独立的 isScriptRunning flag(见 registerWorkflowScriptTool),不共用此 guard。
209
- * 注意:reentryRef 是 factory 级单例(index.ts 在 factory 内创建),跨 session 共享。
210
- * 当前 Pi 运行时单 session 串行(同一时刻只有一个 active session),跨 session
211
- * 不会并发触发 workflow action,故共享无竞态。若未来支持多 session 并发,需改为
212
- * per-session guard。
236
+ * @param reentryRef reentry guard
213
237
  */
214
238
  export function registerWorkflowTool(
215
239
  pi: ExtensionAPI,
@@ -217,72 +241,103 @@ export function registerWorkflowTool(
217
241
  reentryRef: ReentryGuardRef,
218
242
  ): void {
219
243
  pi.registerTool({
220
- name: "workflow",
221
- label: "Workflow",
244
+ name: "workflow_run",
245
+ label: "Workflow Run",
222
246
  description:
223
- "Execute and control workflows: run (start), status, abort, resume (continue an interrupted run).\n" +
224
- "Replaces workflow + workflow-run tools.",
225
- promptSnippet: "Run, abort, resume, or check workflow status",
247
+ "Execute a workflow: start a listed workflow script directly (runs in background; results auto-delivered).\n" +
248
+ "Run control (status/abort/resume) lives in the `workflow_control` tool.",
249
+ promptSnippet: "Run a workflow",
226
250
  promptGuidelines: [
227
- "PRIORITY: When user says 'workflow', 'run workflow', try run action FIRST.",
228
- "All listed workflows run DIRECTLY with action:run — refs/descriptions come from " +
229
- "<available_workflows> (injected each turn). For parameter details, read the <location> " +
230
- "script file (script header has @pi-meta parameters + usage + phases). Do NOT use " +
231
- "workflow-script generate for patterns already covered by available workflows.",
232
- "run: pass the workflow ref as name — normally the <location> absolute .js path from <available_workflows>, but any absolute .js path with a valid @pi-meta also works (the list is not required). Bare names and relative paths are rejected with a not-found error listing locations.",
233
- "DO NOT bash sleep or poll status after starting — results appear automatically via notifyDone.",
234
- "Runs are one-shot: there is no pause — to stop a run early use abort; for a fresh result start a new run. " +
235
- "resume is ONLY for runs whose status is interrupted (crash or session-switch during execution): it replays " +
236
- "completed calls from the record at zero token cost and continues where the run stopped. " +
237
- "Do NOT resume a settled (done/failed/cancelled) run — start a new run instead.",
238
- "resume: pass the SAME args as the original run (they are verified field-by-field; a mismatch is rejected " +
239
- "with the differing fields listed). Omit args to reuse the original ones. Changed args = different intent = new run. " +
240
- "To resume with a specific model: pass model ('provider/modelId') on the resume itself — it is recorded as the " +
241
- "run's user override (same channel as a panel switch), drives all re-dispatched steps, and persists for later " +
242
- "resumes without the parameter. Alternatively switch the run's model first (panel selector or /subagent-model) " +
243
- "then resume without model — both are the same override channel.",
244
- "Call shapes (JSON): " +
245
- "- run: {\"action\":\"run\",\"name\":\"<script>\",\"args\":{...},\"tokens\":N,\"time\":N,\"model\":\"<provider/modelId>\",\"thinkingLevel\":\"<level>\"}. " +
246
- "- status: {\"action\":\"status\"}. " +
247
- "- abort: {\"action\":\"abort\",\"runId\":\"<id>\"} (optional: {\"error\":\"<reason>\"}). " +
248
- "- resume: {\"action\":\"resume\",\"runId\":\"<id>\",\"args\":{...},\"tokens\":N,\"time\":N,\"model\":\"<provider/modelId>\"} — args/tokens/time/model optional.",
249
- "Budget: Do NOT set tokens/time unless the user explicitly requests a limit. Built-in workflows run unlimited by default.",
250
- "Model/thinkingLevel: omit by default (inherit main agent's model). Only set model/thinkingLevel when the user explicitly requests a specific model or thinking depth for this run. " +
251
- "Note: a user-issued runtime model override (panel switch / /subagent-model) takes precedence over the model parameter — an explicitly scripted thinkingLevel still applies " +
252
- "(explicit levels outrank the override's inline level). If a run's explicitly scripted model seems ignored, " +
253
- "check for an active user override (run journal model-override frame) before assuming a resolution bug.",
254
- "Anti-patterns: Flattening args sub-fields (task/items/...) to the top level — they belong inside args. Calling {\"action\":\"run\"} without name.",
255
- "CRITICAL: For orchestration patterns, ALWAYS use action:run with the <location> absolute " +
256
- "path of the matching listed workflow — NEVER use workflow-script action:generate to recreate patterns " +
257
- "already covered by available workflows. workflow-script generate is ONLY for novel patterns.",
251
+ // tool-declaration-slimming D1 终态:使用纪律单点。勿等待条/预算纪律条/模型缺省继承条
252
+ // 已删(收敛点 = <available_subagents> 注入段 SUBAGENT_DISPATCH_SHARED_RULES);args
253
+ // 平铺检测 / model override 通道的功能语义在 schema 字段说明一处,不在此复述。
254
+ // 锚词保留清单见 __tests__/tool-prompt-contract.test.ts 头注。
255
+ "PRIORITY: 'workflow' / 'run workflow' → call workflow_run FIRST.",
256
+ "Refs come from <available_workflows>; for parameter details read the <location> script file (@pi-meta header: parameters + usage + phases).",
257
+ "Call shape (JSON): {\"name\":\"<script>\",\"args\":{...}} (+ optional tokens/time/model/thinkingLevel).",
258
+ "Anti-patterns: flattening args sub-fields (task/items/...) to the top level — they belong inside args.",
259
+ "CRITICAL: for orchestration patterns ALWAYS use workflow_run with the listed workflow's <location> path — NEVER use workflow-script action:generate to recreate covered patterns; generate is ONLY for novel patterns.",
258
260
  ],
259
- parameters: WorkflowParams,
261
+ parameters: WorkflowRunParams,
260
262
 
261
263
  async execute(
262
264
  _toolCallId: string,
263
- params: WorkflowToolParams,
265
+ params: WorkflowRunToolParams,
264
266
  signal: AbortSignal | undefined,
265
267
  _onUpdate: unknown,
266
268
  _ctx: ExtensionContext,
267
269
  ): Promise<WorkflowExecuteResult> {
268
- // P1-2: Honor abort signal up-front
270
+ // P1-2: Honor abort signal up-front
269
271
  // throw(W4b):pi 只对 execute throw 置 isError:true,返回值里的 isError
270
272
  // 被 agent-loop 丢弃(agent-loop.js:453-483)——文案原样进 toolResult。
271
273
  // abort 前置判定收敛在 tool-shared(三处 tool 同源)。
272
274
  assertNotAborted(signal);
273
- // P1-6: Reentry guard(acquire 失败时尚未持有 guard,throw 前无需 release)
275
+ // P1-6: Reentry guard(acquire 失败时尚未持有 guard,throw 前无需 release)
276
+ if (!acquireReentryGuard(reentryRef)) {
277
+ throw new Error(REENTRY_BUSY_MESSAGE);
278
+ }
279
+ try {
280
+ // workflow 块分支(WORKFLOW_TOOL_NAMES 集合分流)恒折叠单行不消费 __gui__
281
+ // (D8 裁决),details 不构造 GUI 描述符。
282
+ return await actionRun(params, deps, signal);
283
+ } finally {
284
+ releaseReentryGuard(reentryRef);
285
+ }
286
+ },
287
+
288
+ renderCall(args: Record<string, unknown>, theme: Theme, _context?: unknown) {
289
+ // workflow ref 是绝对路径,标题行显示取 basename 短名(displayWorkflowName,
290
+ // 与 subagent 标题行的 displayAgentName 对称);slug 不受影响。
291
+ const name = args.name ? ` ${displayWorkflowName(String(args.name))}` : "";
292
+ // run 可选 slug:在 name 后追加 · slug(accent 色)
293
+ const slug = optionSlugSuffix(args.slug, theme);
294
+ return new Text(
295
+ theme.fg("toolTitle", theme.bold("workflow_run ")) +
296
+ theme.fg("accent", name) +
297
+ slug,
298
+ 0,
299
+ 0,
300
+ );
301
+ },
302
+
303
+ renderResult: renderTextResult,
304
+ });
305
+
306
+ pi.registerTool({
307
+ name: "workflow_control",
308
+ label: "Workflow Control",
309
+ description:
310
+ "Control workflow runs: status (list), abort (stop a run), resume (continue an interrupted run).\n" +
311
+ "Runs are one-shot (no pause): stop early with abort; for a settled run start a new one via `workflow_run`.",
312
+ promptSnippet: "Check, abort, or resume workflow runs",
313
+ promptGuidelines: [
314
+ // D1 单源化终态:一次性生命周期与 resume 资格语义已在 description 说一遍,此处只留
315
+ // 调用形状与「不得 resume 已终态 run」的纪律条(锚词清单见 tool-prompt-contract.test.ts)。
316
+ "resume is ONLY for interrupted runs (crash or session-switch): it replays completed calls at zero token cost and continues. Do NOT resume a settled (done/failed/cancelled) run — start a new run via `workflow_run`.",
317
+ "Call shapes (JSON): " +
318
+ "- status: {\"action\":\"status\"}. " +
319
+ "- abort: {\"action\":\"abort\",\"runId\":\"<id>\"}. " +
320
+ "- resume: {\"action\":\"resume\",\"runId\":\"<id>\"} (+ optional args/tokens/time/model).",
321
+ ],
322
+ parameters: WorkflowControlParams,
323
+
324
+ async execute(
325
+ _toolCallId: string,
326
+ params: WorkflowControlToolParams,
327
+ signal: AbortSignal | undefined,
328
+ _onUpdate: unknown,
329
+ _ctx: ExtensionContext,
330
+ ): Promise<WorkflowExecuteResult> {
331
+ assertNotAborted(signal);
274
332
  if (!acquireReentryGuard(reentryRef)) {
275
333
  throw new Error(REENTRY_BUSY_MESSAGE);
276
334
  }
277
335
  try {
278
336
  let result: WorkflowExecuteResult;
279
- // 断言为 WorkflowAction 联合——typebox Static 推断为 any,显式标注让 default
280
- // 分支的 never 穷尽检查生效(新增 action 时 tsc 报错强制补 case)。
281
- const action = params.action as WorkflowAction;
337
+ // 断言为 WorkflowControlAction 联合——typebox Static 推断退化为 string,显式
338
+ // 标注让 default 分支的 never 穷尽检查生效(新增 action 时 tsc 报错强制补 case)。
339
+ const action = params.action as WorkflowControlAction;
282
340
  switch (action) {
283
- case "run":
284
- result = await actionRun(params, deps, signal);
285
- break;
286
341
  case "status":
287
342
  result = actionStatus(deps);
288
343
  break;
@@ -293,7 +348,7 @@ export function registerWorkflowTool(
293
348
  result = await actionResume(params, deps);
294
349
  break;
295
350
  default: {
296
- // Exhaustiveness check — 新增 WorkflowAction 成员时未补 case,tsc 在此报错。
351
+ // Exhaustiveness check — 新增 WorkflowControlAction 成员时未补 case,tsc 在此报错。
297
352
  const _exhaustive: never = action;
298
353
  throw new Error(`Unknown action: ${String(_exhaustive)}`);
299
354
  }
@@ -308,17 +363,10 @@ export function registerWorkflowTool(
308
363
 
309
364
  renderCall(args: Record<string, unknown>, theme: Theme, _context?: unknown) {
310
365
  const action = String(args.action ?? "");
311
- // workflow ref 是绝对路径,标题行显示取 basename 短名(displayWorkflowName,
312
- // 与 subagent 标题行的 displayAgentName 对称);runId/slug 不受影响。
313
- const name = args.name ? ` ${displayWorkflowName(String(args.name))}` : "";
314
- // run action 可选 slug:在 name 后追加 · slug(accent 色)
315
- const slug = optionSlugSuffix(args.slug, theme);
316
366
  const runId = args.runId ? ` ${String(args.runId).slice(0, ID_PREVIEW_LENGTH)}` : "";
317
367
  return new Text(
318
- theme.fg("toolTitle", theme.bold("workflow ")) +
368
+ theme.fg("toolTitle", theme.bold("workflow_control ")) +
319
369
  theme.fg("muted", action) +
320
- theme.fg("accent", name) +
321
- slug +
322
370
  theme.fg("dim", runId),
323
371
  0,
324
372
  0,
@@ -332,14 +380,14 @@ export function registerWorkflowTool(
332
380
  // ── run action ───────────────────────────────────────────────
333
381
 
334
382
  export async function actionRun(
335
- params: WorkflowToolParams,
383
+ params: WorkflowRunToolParams,
336
384
  deps: LauncherDeps,
337
385
  signal: AbortSignal | undefined,
338
386
  ): Promise<WorkflowExecuteResult> {
339
387
  const name = params.name;
340
388
  if (!name) {
341
389
  throw new Error(
342
- "run requires 'name' parameter (an absolute .js path, e.g. a <location> from <available_workflows>). Correct: {\"action\":\"run\",\"name\":\"<ref>\"}",
390
+ "run requires 'name' parameter (an absolute .js path, e.g. a <location> from <available_workflows>). Correct: {\"name\":\"<ref>\"}",
343
391
  );
344
392
  }
345
393
  // 弱模型常见误用(P0 静默失败):把 task/items 等 args 子字段平铺到 workflow params
@@ -387,7 +435,7 @@ export async function actionRun(
387
435
  if (flattened.length > 0) {
388
436
  throw new Error(
389
437
  `Detected ${flattened.join(", ")} at top level — they belong inside 'args'. ` +
390
- `Correct: {"action":"run","name":"${name}","args":{${flattened.map((k) => `"${k}": "<value>"`).join(", ")}}}`,
438
+ `Correct: {"name":"${name}","args":{${flattened.map((k) => `"${k}": "<value>"`).join(", ")}}}`,
391
439
  );
392
440
  }
393
441
  // slug 运行时护栏(与 subagent startHandler 对称的纵深防御;schema maxLength 是第一道关卡)
@@ -502,7 +550,7 @@ export function displayStatusOf(run: WorkflowRun): "running" | "interrupted" | "
502
550
 
503
551
  // 一次性生命周期:abort 是唯一的提前停止方式(pause/resume 已随 D-2 移除)。
504
552
  async function actionAbort(
505
- params: WorkflowToolParams,
553
+ params: WorkflowControlToolParams,
506
554
  deps: LauncherDeps,
507
555
  ): Promise<WorkflowExecuteResult> {
508
556
  const runId = params.runId;
@@ -564,7 +612,7 @@ async function actionAbort(
564
612
  * pi 置 isError:true)。
565
613
  */
566
614
  export async function actionResume(
567
- params: WorkflowToolParams,
615
+ params: WorkflowControlToolParams,
568
616
  deps: LauncherDeps,
569
617
  ): Promise<WorkflowExecuteResult> {
570
618
  const runId = params.runId;