@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.
@@ -16,15 +16,15 @@
16
16
  * 走 record 回放)、注入安全(workerData.scriptPath 定位 _shared,不回退当前目录)
17
17
  * 与脚本可探索性(@pi-meta 进可用 workflow 清单)——走 workflow 管道使批量派发
18
18
  * 继承持久化、journal、GUI 投影与断点恢复全套能力。
19
- * - 无 action 分发:status/abort 不复制,直接指路 workflow tool(runId 同体系)。
19
+ * - 无 action 分发:status/abort 不复制,直接指路 workflow_control(runId 同体系)。
20
20
  * - 无 args 嵌套:tasks/agents/aggregate/... 全在顶层(弱模型信任 schema 结构信号,
21
- * 两跳转译是事故高发区——对照 workflow tool 的 name+args 形态)。
21
+ * 两跳转译是事故高发区——对照 workflow_run 的 name+args 形态)。
22
22
  * - 批量成员是一次性成员:不可 message/续聊(workflow-origin record 由 messageHandler
23
23
  * 拒绝),结果在 run 收口时以一条通知(notifyDone)到达。
24
24
  * - 不构造 `details.__gui__`:GUI 挂载按 WORKFLOW_TOOL_NAMES 集合分流,批量块走
25
25
  * workflow 块分支(恒折叠单行 + openWorkflowDrawer);`__gui__` 的渲染点在普通
26
26
  * tool 分支(v-else)的展开区内,isWorkflow 分支无展开路径不消费——构造即死代码
27
- * (D8 裁决;workflow tool 的 `__gui__` 死构造已随之删除,isWorkflow 集合内工具零消费)。
27
+ * (D8 裁决;workflow_run 的 `__gui__` 死构造已随之删除,isWorkflow 集合内工具零消费)。
28
28
  *
29
29
  * 层归属:Interface。依赖 Pi SDK + core lifecycle/registry + reentry-guard。
30
30
  */
@@ -64,7 +64,7 @@ import type { RunStartDetails, WorkflowToolResult } from "./tool-result.ts";
64
64
  *
65
65
  * 常量而非参数:subagents tool 的契约就是「把 tasks 透传给 fan-out 模板」——参数化会让
66
66
  * 工具的语义随脚本漂移(对照 D3「执行管道唯一」)。脚本按名经 registry 解析
67
- * (内置名优先链与 workflow tool actionRun 同一条,见 plan U2「复刻既有加载路径」)。
67
+ * (内置名优先链与 workflow_run actionRun 同一条,见 plan U2「复刻既有加载路径」)。
68
68
  */
69
69
  export const FAN_OUT_SCRIPT_NAME = "fan-out";
70
70
 
@@ -76,38 +76,37 @@ const SLUG_TIME_RADIX = 36;
76
76
  const SubagentsParams = Type.Object({
77
77
  tasks: Type.Array(Type.String(), {
78
78
  description:
79
- "N complete, self-contained task descriptions — one array element = one dispatchable task prompt. " +
80
- "Members run as independent one-shot subagents (they do NOT see your conversation), so each element must carry its own context and acceptance criteria. " +
81
- "All tasks are dispatched in parallel; results arrive together.",
79
+ "N complete, self-contained task prompts — one element = one dispatch. Members run as independent one-shot subagents (they do NOT see your conversation), so each must carry its own context and acceptance criteria. All run in parallel.",
82
80
  }),
83
81
  agents: Type.Optional(Type.String({
84
82
  description:
85
- "Comma-separated absolute paths to agent .md files (usually <location> values from <available_subagents>; arbitrary absolute .md paths also work). " +
86
- "One path applies to every member; N paths map one-to-one onto tasks in the same order. " +
87
- "When more than one path is given it MUST equal the number of tasks — a mismatch fails the run fast (never a silent persona swap). " +
88
- "Omit for the default (general-purpose) executor.",
83
+ "Comma-separated absolute paths to agent .md files (usually <location> values from <available_subagents>). One path applies to every member; N paths map one-to-one onto tasks in order and MUST equal the tasks count (mismatch fails the run). Omit for the default general-purpose executor.",
89
84
  })),
90
85
  aggregate: Type.Optional(Type.Boolean({
91
- description:
92
- "Default false. When true, one extra agent is appended at the end to reduce all results into a single conclusion (results are still returned in full).",
86
+ description: "Default false. When true, one extra agent is appended to reduce all results into a single conclusion (results are still returned in full).",
93
87
  })),
94
88
  slug: Type.Optional(Type.String({
95
- description:
96
- "Batch label (max 35 chars, kebab-case) shown on the conversation block and run list — provide a short label for multi-batch scenarios. " +
97
- "If omitted, one is generated for the run state face only.",
89
+ description: "Batch label (max 35 chars, kebab-case) shown on the conversation block and run list. Omit to auto-generate.",
98
90
  maxLength: SLUG_MAX_LENGTH,
99
91
  })),
100
92
  model: Type.Optional(Type.String({
93
+ // 缺省继承主 agent 的纪律句已删(D1 单源化→注入段,见
94
+ // subagent-list-injector.ts SUBAGENT_DISPATCH_SHARED_RULES);批内继承 run 级值是
95
+ // 本参数的功能语义,保留。
101
96
  description:
102
- "Run-level model override in 'provider/modelId' format; every member of the batch inherits it. " +
103
- "Omit to inherit the main agent's model.",
97
+ "Run-level model override in 'provider/modelId' format; every member of the batch inherits it.",
104
98
  })),
105
99
  thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
106
100
  description:
107
101
  "Run-level thinking depth for every member. Omit to default each agent to its model's highest available level.",
108
102
  })),
109
- tokens: Type.Optional(Type.Number({ description: "Max token budget for the whole batch — ONLY set when the user explicitly requests a limit; omit = unlimited (default)" })),
110
- time: Type.Optional(Type.Number({ description: `Max time budget in ms for the whole batch — ONLY set when the user explicitly requests a limit; omit = unlimited (default; hard ceiling ${MAX_TIMER_DELAY_MS} ms — larger values fail fast at entry)` })),
103
+ tokens: Type.Optional(Type.Number({
104
+ // 预算纪律句已删(D1 单源化→注入段):跨工具预算纪律说一次,原文见 git 历史。
105
+ description: "Max token budget for the whole batch.",
106
+ })),
107
+ time: Type.Optional(Type.Number({
108
+ description: `Max time budget in ms for the whole batch (hard ceiling ${MAX_TIMER_DELAY_MS} ms — larger values fail fast at entry).`,
109
+ })),
111
110
  });
112
111
 
113
112
  export type SubagentsToolParams = Static<typeof SubagentsParams>;
@@ -156,8 +155,8 @@ function subagentsStartupText(
156
155
  return [
157
156
  `Started batch '${slug}' (${runId}) as workflow run '${scriptName}' — ${taskCount} subagents dispatched in parallel (allSettled).`,
158
157
  "Results arrive as ONE notification when the run settles. Do NOT poll.",
159
- `If no notification arrives well past the expected duration, make a SINGLE status check: workflow tool with runId ${runId} (recovery exit, not a poll loop).`,
160
- `To abort: workflow tool, action abort, runId ${runId}`,
158
+ `If no notification arrives well past the expected duration, make a SINGLE status check: workflow_control {"action":"status"} with runId ${runId} (recovery exit, not a poll loop).`,
159
+ `To abort: workflow_control {"action":"abort","runId":"${runId}"}`,
161
160
  ].join("\n");
162
161
  }
163
162
 
@@ -183,7 +182,7 @@ export async function runSubagentsBatch(
183
182
  );
184
183
  }
185
184
 
186
- // slug 运行时护栏(与 workflow tool actionRun 对称的纵深防御;schema maxLength 是第一道关卡)
185
+ // slug 运行时护栏(与 workflow_run actionRun 对称的纵深防御;schema maxLength 是第一道关卡)
187
186
  const providedSlug = params.slug?.trim();
188
187
  assertSlugWithinLimit(providedSlug, ["tri-review", "scan-docs"]);
189
188
  // 缺省(或空白)时 handler 生成 fan-out-<时间短码>:受益面 = 状态面(drawer run header /
@@ -197,7 +196,7 @@ export async function runSubagentsBatch(
197
196
  assertEntryTimeBudget(time);
198
197
 
199
198
  // 执行体脚本按固定内置名解析(registry.get 精确名匹配;脚本名是工具自带常量
200
- // FAN_OUT_SCRIPT_NAME 而非用户参数——workflow tool actionRun 的按名解析已退役
199
+ // FAN_OUT_SCRIPT_NAME 而非用户参数——workflow_run actionRun 的按名解析已退役
201
200
  // 走 getPath 单通道,本工具不允许换脚本,无 getPath 回落需求)。
202
201
  const script = await deps.registry.get(FAN_OUT_SCRIPT_NAME);
203
202
  if (!script || !script.available) {
@@ -249,8 +248,8 @@ export async function runSubagentsBatch(
249
248
  *
250
249
  * @param pi ExtensionAPI
251
250
  * @param deps LauncherDeps(LifecycleDeps + registry)
252
- * @param reentryRef reentry guard。**与 workflow tool 共用同一实例**(index.ts
253
- * factory 内创建的单例)——两者是同一条 runWorkflow 管道的入口,共用守卫避免
251
+ * @param reentryRef reentry guard。**与 workflow_run/workflow_control 共用同一实例**(index.ts
252
+ * factory 内创建的单例)——三者是同一条 runWorkflow 管道的入口,共用守卫避免
254
253
  * 双 guard 语义漂移;workflow-script tool 的 isScriptRunning 是另一套 flag,
255
254
  * 与本 guard 无关。
256
255
  */
@@ -262,22 +261,22 @@ export function registerSubagentsTool(
262
261
  pi.registerTool({
263
262
  name: "subagents",
264
263
  label: "Subagents (batch)",
264
+ // tool-declaration-slimming U-C1 R1 裁决:低频批量入口延迟加载(defer 名单工具)。
265
+ exposure: "deferred",
265
266
  description:
266
- "Spawn multiple subagents in ONE call and collect all results together — for N INDEPENDENT tasks.\n" +
267
+ "Spawn multiple subagents in ONE call, collect all results together — for N INDEPENDENT tasks.\n" +
267
268
  "Each member is a one-shot batch member: you cannot message or continue it (re-dispatch instead). Results arrive as ONE notification when the whole batch settles (not per member).\n" +
268
- "For a single subagent you can message later, use the `subagent` tool. For tasks that depend on each other's output (step 2 needs step 1's result), use the `workflow` tool (chain / map-reduce) instead — batch members never see each other's results.",
269
+ "Single subagent you can message later: `subagent_message` tool. Dependent tasks (step 2 needs step 1's result): `workflow_run` tool — batch members never see each other's results.",
269
270
  promptSnippet: "Dispatch N independent subagent tasks in one batch",
270
271
  promptGuidelines: [
271
- "PRIORITY: 2+ independent tasks in one dispatch (results combined by you afterwards) → call `subagents` ONCE with the tasks array — do NOT issue N separate `subagent` starts and do NOT hand-build a workflow.",
272
- "One-shot batch members: message/fork-from a batch member is not supported; to redo or extend a member's work, dispatch a new task (run the failed/extra task alone in a second batch).",
273
- "Do NOT poll after starting — the batch result arrives as a single completion notification carrying every member's summary (plus optional aggregate).",
274
- "Call shape (JSON): {\"tasks\":[\"<task 1>\",\"<task 2>\"],\"agents\":\"<abs .md path or comma-separated list>\",\"aggregate\":false,\"slug\":\"<short label>\"}. tasks is required and every element must be a complete self-contained task prompt.",
275
- "agents: one path applies to all tasks; N paths map one-to-one onto tasks and N must equal tasks.length (a mismatch fails the run). Omit for the default executor.",
276
- "slug: pass a short kebab-case label when you run more than one batch — it identifies the batch on the conversation block and in the run list.",
277
- "Budget: Do NOT set tokens/time unless the user explicitly requests a limit. Batches run unlimited by default.",
278
- "Model/thinkingLevel: omit by default (inherit the main agent's model). Only set them when the user explicitly requests a specific model or thinking depth for this batch.",
279
- "Anti-patterns: nesting the tasks under an 'args' key (they are top-level), dispatching dependent steps in one batch (chain them across messages or use the workflow tool), and treating batch results as verified without checking them.",
280
- "Run control (status/abort) lives in the workflow tool — use the runId printed in this tool's output.",
272
+ "PRIORITY: 2+ independent tasks in one dispatch (results combined by you afterwards) → call `subagents` ONCE with the tasks array — do NOT issue N separate subagent_start calls and do NOT hand-build a workflow.",
273
+ "One-shot batch members: message/fork-from is not supported; to redo or extend a member's work, dispatch a new task in a second batch.",
274
+ // 勿等待条 / 预算纪律条 / 模型缺省继承条 三条已删(tool-declaration-slimming D1
275
+ // 单源化→<available_subagents> 注入段 SUBAGENT_DISPATCH_SHARED_RULES 说一次,
276
+ // 原文见 git 历史;零残留由 tool-subagents.test.ts 单源化终态断言锁定)。
277
+ "Call shape (JSON): {\"tasks\":[\"<task 1>\",\"<task 2>\"],\"agents\":\"<abs .md path(s)>\",\"aggregate\":false,\"slug\":\"<label>\"} — tasks required, every element a complete self-contained prompt.",
278
+ "Anti-patterns: nesting tasks under an 'args' key (top-level); dispatching dependent steps in one batch (chain across messages or use the `workflow_run` tool); treating batch results as verified without checking.",
279
+ "Run control (status/abort) lives in the `workflow_control` tool — use the runId printed in this tool's output.",
281
280
  ],
282
281
  parameters: SubagentsParams,
283
282
 
@@ -291,7 +290,7 @@ export function registerSubagentsTool(
291
290
  // throw(W4b 契约):pi 只对 execute throw 置 isError:true,返回值里的 isError
292
291
  // 被 agent-loop 丢弃——错误一律 throw(abort 前置判定收敛在 tool-shared)。
293
292
  assertNotAborted(signal);
294
- // reentry guard:与 workflow tool 共用(acquire 失败时尚未持有 guard,throw 前无需 release)
293
+ // reentry guard:与 workflow_run/workflow_control 共用(acquire 失败时尚未持有 guard,throw 前无需 release)
295
294
  if (!acquireReentryGuard(reentryRef)) {
296
295
  throw new Error(REENTRY_BUSY_MESSAGE);
297
296
  }
@@ -303,7 +302,7 @@ export function registerSubagentsTool(
303
302
  },
304
303
 
305
304
  renderCall(args: Record<string, unknown>, theme: Theme, _context?: unknown) {
306
- // 单行标题:subagents <N tasks> · <slug>(TUI 惯例同 workflow tool 的 renderCall)
305
+ // 单行标题:subagents <N tasks> · <slug>(TUI 惯例同 workflow_run 的 renderCall)
307
306
  const count = Array.isArray(args.tasks) ? args.tasks.length : 0;
308
307
  const bulk = count > 0 ? ` ${count} tasks` : "";
309
308
  const slug = optionSlugSuffix(args.slug, theme);
@@ -153,11 +153,13 @@ export function registerWorkflowScriptTool(
153
153
  pi.registerTool({
154
154
  name: "workflow-script",
155
155
  label: "Workflow Script",
156
+ // tool-declaration-slimming U-C1 R1 裁决:低频脚本管理面延迟加载(defer 名单工具)。
157
+ exposure: "deferred",
156
158
  description:
157
159
  "Manage workflow scripts: generate (AI creates tmp script), lint (static check), " +
158
160
  "save (tmp→permanent), delete, list. Before generating a new script, use action:list " +
159
161
  "to check if an available workflow already " +
160
- "covers the use case. Replaces workflow-generate + workflow-lint tools.",
162
+ "covers the use case.",
161
163
  promptSnippet: "Generate, lint, save, delete, or list workflow scripts",
162
164
  promptGuidelines: [
163
165
  "generate: AI writes a tmp workflow script to .pi/workflows/.tmp/. Declare metadata as a /* @pi-meta */ YAML block comment (name/description/phases required; parameters JSON Schema + usage markdown optional). NOT a const meta variable. Generate round-trip-validates the YAML and reports line/col on error (common pitfall: patternProperties regex must use double backslash \\d, not \d).",
@@ -167,11 +169,11 @@ export function registerWorkflowScriptTool(
167
169
  "list: Show all available workflow scripts with source tags. " +
168
170
  "Use this to discover available workflows (see <available_workflows> injection) " +
169
171
  "and user-generated scripts before starting a run. After listing, start a script via " +
170
- "the workflow tool with action:run and the script name.",
172
+ "the `workflow_run` tool with the script name.",
171
173
  "CRITICAL ANTI-PATTERN: NEVER generate scripts for patterns already covered by available " +
172
- "workflows. These are BUILT-IN — use the workflow tool with action:run directly. " +
174
+ "workflows. These are BUILT-IN — run them directly via `workflow_run`. " +
173
175
  "generate is for NOVEL orchestration patterns ONLY. When in doubt, action:list first, " +
174
- "then action:run — not action:generate.",
176
+ "then run — not action:generate.",
175
177
  ],
176
178
  parameters: WorkflowScriptParams,
177
179
 
@@ -254,7 +256,7 @@ export function actionGenerate(params: ScriptParams): WorkflowScriptExecuteResul
254
256
  content: [
255
257
  {
256
258
  type: "text",
257
- text: `Generated workflow script: ${filePath}\nName: ${name}\nReady to run via the workflow tool.`,
259
+ text: `Generated workflow script: ${filePath}\nName: ${name}\nReady to run via the \`workflow_run\` tool.`,
258
260
  },
259
261
  ],
260
262
  details: { action: "generate", path: filePath, name, status: "ready" },