@zhushanwen/pi-subagent-workflow 9.4.0 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/package.json +10 -10
- package/src/host/pi-host.ts +9 -6
- package/src/index.ts +9 -2
- package/src/injectors/__tests__/workflow-list-injector.test.ts +11 -4
- package/src/injectors/resource-list-injector.ts +17 -7
- package/src/injectors/subagent-list-injector.ts +27 -2
- package/src/injectors/workflow-list-injector.ts +1 -1
- package/src/interface/command/commands.ts +126 -66
- package/src/interface/gui/subagent-actions.ts +4 -2
- package/src/interface/tool/__tests__/capture-tool.ts +34 -13
- package/src/interface/tool/__tests__/subagent-tool-actions.test.ts +105 -41
- package/src/interface/tool/__tests__/subagent-tool-path-guard.test.ts +16 -12
- package/src/interface/tool/__tests__/tool-prompt-contract.test.ts +359 -175
- package/src/interface/tool/__tests__/tool-subagents.test.ts +28 -13
- package/src/interface/tool/__tests__/tool-workflow.test.ts +80 -59
- package/src/interface/tool/subagent-tool-schema.ts +79 -70
- package/src/interface/tool/subagent-tool.ts +205 -136
- package/src/interface/tool/tool-render.ts +7 -5
- package/src/interface/tool/tool-result.ts +3 -2
- package/src/interface/tool/tool-subagents.ts +39 -40
- package/src/interface/tool/tool-workflow-script.ts +7 -5
- package/src/interface/tool/tool-workflow.ts +164 -116
- package/src/jsonl-run-store.ts +126 -48
- package/src/session-lifecycle.ts +11 -7
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
* 走 record 回放)、注入安全(workerData.scriptPath 定位 _shared,不回退当前目录)
|
|
17
17
|
* 与脚本可探索性(@pi-meta 进可用 workflow 清单)——走 workflow 管道使批量派发
|
|
18
18
|
* 继承持久化、journal、GUI 投影与断点恢复全套能力。
|
|
19
|
-
* - 无 action 分发:status/abort 不复制,直接指路
|
|
19
|
+
* - 无 action 分发:status/abort 不复制,直接指路 workflow_control(runId 同体系)。
|
|
20
20
|
* - 无 args 嵌套:tasks/agents/aggregate/... 全在顶层(弱模型信任 schema 结构信号,
|
|
21
|
-
* 两跳转译是事故高发区——对照
|
|
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 裁决;
|
|
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
|
-
* (内置名优先链与
|
|
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
|
|
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
|
|
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({
|
|
110
|
-
|
|
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:
|
|
160
|
-
`To abort:
|
|
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 运行时护栏(与
|
|
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 而非用户参数——
|
|
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。**与
|
|
253
|
-
* factory
|
|
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
|
|
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
|
-
"
|
|
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
|
|
272
|
-
"One-shot batch members: message/fork-from
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
"
|
|
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:与
|
|
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 惯例同
|
|
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.
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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" },
|