@zhushanwen/pi-subagent-workflow 9.4.1 → 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 +7 -7
- 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/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
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow Extension — workflow tool
|
|
2
|
+
* Workflow Extension — workflow 工具组(tool-declaration-slimming D3 拆分终态)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
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
|
-
/**
|
|
74
|
-
export type
|
|
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
|
|
81
|
-
"run",
|
|
77
|
+
const WORKFLOW_CONTROL_ACTIONS: readonly WorkflowControlAction[] = [
|
|
82
78
|
"status",
|
|
83
79
|
"abort",
|
|
84
80
|
"resume",
|
|
85
81
|
];
|
|
86
82
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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({
|
|
112
|
-
|
|
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,
|
|
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
|
-
"
|
|
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
|
|
149
|
+
type WorkflowRunToolParams = Static<typeof WorkflowRunParams>;
|
|
150
|
+
type WorkflowControlToolParams = Static<typeof WorkflowControlParams>;
|
|
129
151
|
|
|
130
152
|
// ── Constants ────────────────────────────────────────────────
|
|
131
153
|
|
|
132
154
|
/**
|
|
133
|
-
* tool 自身顶层键(
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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 调用顶层必有
|
|
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
|
|
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
|
|
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
|
|
202
|
-
*
|
|
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
|
|
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: "
|
|
221
|
-
label: "Workflow",
|
|
244
|
+
name: "workflow_run",
|
|
245
|
+
label: "Workflow Run",
|
|
222
246
|
description:
|
|
223
|
-
"Execute
|
|
224
|
-
"
|
|
225
|
-
promptSnippet: "Run
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
"workflow
|
|
232
|
-
"
|
|
233
|
-
"
|
|
234
|
-
"
|
|
235
|
-
"
|
|
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:
|
|
261
|
+
parameters: WorkflowRunParams,
|
|
260
262
|
|
|
261
263
|
async execute(
|
|
262
264
|
_toolCallId: string,
|
|
263
|
-
params:
|
|
265
|
+
params: WorkflowRunToolParams,
|
|
264
266
|
signal: AbortSignal | undefined,
|
|
265
267
|
_onUpdate: unknown,
|
|
266
268
|
_ctx: ExtensionContext,
|
|
267
269
|
): Promise<WorkflowExecuteResult> {
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
// 断言为
|
|
280
|
-
// 分支的 never 穷尽检查生效(新增 action 时 tsc 报错强制补 case)。
|
|
281
|
-
const action = params.action as
|
|
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 — 新增
|
|
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("
|
|
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:
|
|
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: {\"
|
|
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: {"
|
|
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:
|
|
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:
|
|
615
|
+
params: WorkflowControlToolParams,
|
|
568
616
|
deps: LauncherDeps,
|
|
569
617
|
): Promise<WorkflowExecuteResult> {
|
|
570
618
|
const runId = params.runId;
|