@zhushanwen/pi-subagent-workflow 8.14.7 → 9.0.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 +6 -5
- package/package.json +11 -7
- package/relay/relay.mjs +45 -2
- package/skills/workflow-script-format/SKILL.md +2 -56
- package/src/host/__tests__/inflight-reporter.test.ts +26 -1
- package/src/host/__tests__/pi-host.test.ts +418 -4
- package/src/host/inflight-reporter.ts +13 -10
- package/src/host/pi-host.ts +205 -17
- package/src/index.ts +21 -72
- package/src/injectors/__tests__/engine-awareness.test.ts +236 -33
- package/src/injectors/__tests__/helpers/injector-test-mocks.ts +15 -7
- package/src/injectors/__tests__/workflow-list-injector.test.ts +447 -40
- package/src/injectors/resource-list-injector.ts +103 -32
- package/src/injectors/subagent-list-injector.ts +4 -28
- package/src/injectors/workflow-list-injector.ts +8 -4
- package/src/interface/__tests__/capture-tool.ts +56 -0
- package/src/interface/__tests__/commands-resume.test.ts +103 -0
- package/src/interface/__tests__/tool-prompt-contract.test.ts +361 -0
- package/src/interface/__tests__/tool-render.test.ts +105 -2
- package/src/interface/__tests__/tool-subagents.test.ts +24 -26
- package/src/interface/__tests__/tool-workflow-resume.test.ts +227 -0
- package/src/interface/__tests__/tool-workflow-script.test.ts +557 -0
- package/src/interface/__tests__/tool-workflow.test.ts +884 -0
- package/src/interface/bg-notify-render.ts +18 -9
- package/src/interface/command-actions.ts +22 -17
- package/src/interface/commands.ts +58 -12
- package/src/interface/format.ts +50 -16
- package/src/interface/id-preview.ts +4 -4
- package/src/interface/subagent-actions.ts +18 -50
- package/src/interface/subagent-tool.ts +26 -18
- package/src/interface/subagents.ts +5 -11
- package/src/interface/tool-render.ts +14 -30
- package/src/interface/tool-shared.ts +55 -12
- package/src/interface/tool-subagents.ts +21 -11
- package/src/interface/tool-workflow-script.ts +52 -72
- package/src/interface/tool-workflow.ts +345 -139
- package/src/interface/views/WorkflowsView.ts +10 -7
- package/src/interface/views/__tests__/{WorkflowsView-signature.test.ts → WorkflowsView.test.ts} +155 -11
- package/src/interface/views/detail-content.ts +3 -5
- package/src/jsonl-run-store.ts +656 -544
- package/src/model-events.ts +35 -0
- package/src/session-lifecycle.ts +376 -85
- package/src/subagents-events.ts +56 -0
- package/src/workflow-events.ts +285 -200
- package/src/workflow-notify.ts +456 -0
- package/src/injectors/__tests__/engine-section-stability.test.ts +0 -353
- package/src/injectors/__tests__/model-list-injector.test.ts +0 -136
- package/src/injectors/__tests__/subagent-list-injector.test.ts +0 -376
- package/src/interface/__tests__/detectors.test.ts +0 -161
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +0 -183
- package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +0 -219
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +0 -251
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +0 -215
- package/src/interface/__tests__/workflow-state-file-exposure.test.ts +0 -38
- package/src/interface/__tests__/workflow-tool-prompt.test.ts +0 -129
- package/src/interface/helpers.ts +0 -272
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +0 -73
- package/src/interface/views/__tests__/record-progress.test.ts +0 -235
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
// `subagent` LLM 工具。薄壳——参数解析 + 调 runtime.execute。
|
|
4
4
|
// 不创建 state、不节流 onUpdate、不持久化(全部在 runtime 层统一)。
|
|
5
5
|
//
|
|
6
|
-
// 设计说明:
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
6
|
+
// 设计说明:execute / renderCall 两个回调抽成模块级 const + 顶层 type alias。
|
|
7
|
+
// 原因:stub 的 registerTool(tool: unknown) 参数是 unknown,在其对象字面量内直接
|
|
8
|
+
// 标注从 pi-coding-agent 导入的泛型(AgentToolResult<X>、Theme、ExtensionContext)
|
|
9
|
+
// 会触发 TS2307 误报(probe5d/5f 验证)。抽到顶层后参数类型由 alias 提供,绕过
|
|
10
|
+
// 该 quirk。renderResult 直接引用 tool-render.ts 导出的 renderSubagentResult
|
|
11
|
+
// 函数值——值引用不涉及类型标注,不受该 quirk 影响。
|
|
11
12
|
|
|
12
13
|
import { isAbsolute } from "node:path";
|
|
13
14
|
|
|
@@ -18,9 +19,17 @@ import type { Static } from "typebox";
|
|
|
18
19
|
|
|
19
20
|
import { getSubagentService } from "@zhushanwen/subagent-core";
|
|
20
21
|
import type { SubagentToolResult } from "@zhushanwen/subagent-core";
|
|
22
|
+
import {
|
|
23
|
+
cancelHandler,
|
|
24
|
+
closeHandler,
|
|
25
|
+
forkFromHandler,
|
|
26
|
+
listHandler,
|
|
27
|
+
messageHandler,
|
|
28
|
+
startHandler,
|
|
29
|
+
} from "@zhushanwen/subagent-core";
|
|
21
30
|
import { extractAgentName } from "./format.ts";
|
|
22
31
|
import { toGuiCtx } from "./gui-mappers.ts";
|
|
23
|
-
import { adapter
|
|
32
|
+
import { adapter } from "./subagent-actions.ts";
|
|
24
33
|
import { SubagentParams } from "./subagent-tool-schema.ts";
|
|
25
34
|
import { type RenderContext,renderSubagentCall, renderSubagentResult } from "./tool-render.ts";
|
|
26
35
|
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
@@ -48,13 +57,6 @@ type SubagentExecuteCb = (
|
|
|
48
57
|
|
|
49
58
|
type SubagentRenderCallCb = (args: unknown, theme: Theme, ctx: RenderContext) => Component;
|
|
50
59
|
|
|
51
|
-
type SubagentRenderResultCb = (
|
|
52
|
-
result: AgentToolResult<SubagentToolResult>,
|
|
53
|
-
options: { expanded: boolean; isPartial: boolean },
|
|
54
|
-
theme: Theme,
|
|
55
|
-
ctx: RenderContext,
|
|
56
|
-
) => Component;
|
|
57
|
-
|
|
58
60
|
// ============================================================
|
|
59
61
|
// renderCall 预解析 helper
|
|
60
62
|
// ============================================================
|
|
@@ -221,7 +223,7 @@ Do NOT recurse when: the work is linear/flat; the child needs your context; or y
|
|
|
221
223
|
executionMode: "sequential",
|
|
222
224
|
parameters: SubagentParams,
|
|
223
225
|
renderCall: subagentRenderCall,
|
|
224
|
-
renderResult:
|
|
226
|
+
renderResult: renderSubagentResult,
|
|
225
227
|
execute: executeSubagent,
|
|
226
228
|
});
|
|
227
229
|
}
|
|
@@ -260,9 +262,6 @@ const subagentRenderCall: SubagentRenderCallCb = (args, theme, ctx) => {
|
|
|
260
262
|
return renderSubagentCall(args, theme, ctx, resolved);
|
|
261
263
|
};
|
|
262
264
|
|
|
263
|
-
const subagentRenderResult: SubagentRenderResultCb = (result, options, theme, ctx) =>
|
|
264
|
-
renderSubagentResult(result, options, theme, ctx);
|
|
265
|
-
|
|
266
265
|
/**
|
|
267
266
|
* execute 实现(action 路由 + adapter)。
|
|
268
267
|
*
|
|
@@ -295,7 +294,16 @@ const executeSubagent: SubagentExecuteCb = async (
|
|
|
295
294
|
// background 模式:execute 立即返回,detached 运行不向 tool 层回流 onUpdate
|
|
296
295
|
//(完成由 notify 驱动新 turn)。onUpdate 参数保留以兼容 SDK 回调签名,但不消费。
|
|
297
296
|
const service = getSubagentService();
|
|
298
|
-
if (!service)
|
|
297
|
+
if (!service) {
|
|
298
|
+
// [C2] 错误带恢复动作(对齐 workflow-events getWorkflowDeps 的 store unavailable
|
|
299
|
+
// 闭环风格):service 缺席的 root cause 是 session_start 装配链失败(围栏 catch
|
|
300
|
+
// 只留 extension 日志),此处指引 reload + 查日志,接通「现象 → 根因」链路。
|
|
301
|
+
throw new Error(
|
|
302
|
+
"subagents runtime not initialized (session_start assembly failed). " +
|
|
303
|
+
"Recovery: reload this session or restart pi to re-run initialization, " +
|
|
304
|
+
"and check the subagents extension logs (session_start failure) for the root cause.",
|
|
305
|
+
);
|
|
306
|
+
}
|
|
299
307
|
|
|
300
308
|
// typebox v1 的 StringEnum 在 Static 投影下退化为 string,此处类型守卫收窄回
|
|
301
309
|
// 字面量联合,恢复 switch 的 exhaustiveness 约束(default 分支 = never)。
|
|
@@ -13,23 +13,17 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
|
|
|
13
13
|
import { getSubagentService } from "@zhushanwen/subagent-core";
|
|
14
14
|
import type { SubagentService } from "@zhushanwen/subagent-core";
|
|
15
15
|
import { displayAgentName } from "@zhushanwen/subagent-core";
|
|
16
|
-
import { messageHandler, startHandler } from "
|
|
16
|
+
import { messageHandler, startHandler } from "@zhushanwen/subagent-core";
|
|
17
|
+
import { SUBAGENT_DIRECTIVE_CUSTOM_TYPE } from "@zhushanwen/extension-protocol";
|
|
17
18
|
import { parseSubagentRpcCommand } from "./command-actions.ts";
|
|
18
19
|
import type { SubagentRpcAction } from "./command-actions.ts";
|
|
19
20
|
import { LIST_LIMIT } from "./list-shared.ts";
|
|
20
21
|
import { createSubagentsView } from "./list-view.ts";
|
|
21
22
|
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* 定向消息留痕载体(设计 §3.3.3):message/start 成功派发后落主 session 的
|
|
27
|
-
* custom_message entry,一 entry 双消费——
|
|
28
|
-
* 1. 主 agent 上下文(custom_message 进 context,主 agent 下次 turn 可见定向对话)
|
|
29
|
-
* 2. renderer 定向气泡渲染源(§3.3.3a live/reload 双链路,后续 wave 消费)
|
|
30
|
-
* 字段形状是 GUI 契约,改动需与 renderer 侧同步。
|
|
31
|
-
*/
|
|
32
|
-
export const SUBAGENT_DIRECTIVE_CUSTOM_TYPE = "subagent-directive";
|
|
24
|
+
// subagent-directive customType 经 extension-protocol 单源(与 shared/runtime 消费侧
|
|
25
|
+
// 同源;等值锁 = src/__tests__/contract.notify-custom-types.test.ts)。定向消息留痕
|
|
26
|
+
// 载体语义见下方 emitSubagentDirective 注释。
|
|
33
27
|
|
|
34
28
|
/** subagent-directive entry 的 details 形状(GUI 定向气泡渲染契约)。 */
|
|
35
29
|
export interface SubagentDirectiveDetails {
|
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
formatElapsedSeconds,
|
|
33
33
|
statusGlyph,
|
|
34
34
|
type ThemeLike,
|
|
35
|
-
truncLine,
|
|
36
35
|
} from "./format.ts";
|
|
36
|
+
import { truncLine } from "./tui-kit.ts";
|
|
37
37
|
|
|
38
38
|
// ============================================================
|
|
39
39
|
// 常量
|
|
@@ -65,14 +65,14 @@ function getTermWidth(): number {
|
|
|
65
65
|
// ============================================================
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* renderResult 的 context(SDK ToolRenderContext
|
|
69
|
-
* SDK 实际传入更完整的 { args, toolCallId, cwd,
|
|
70
|
-
*
|
|
68
|
+
* renderResult 的 context(SDK ToolRenderContext 的有意子集——结构兼容只声明
|
|
69
|
+
* 本组件需要的字段)。SDK 实际传入更完整的 { args, toolCallId, cwd,
|
|
70
|
+
* executionStarted, argsComplete, isPartial, expanded, showImages, isError,
|
|
71
|
+
* lastComponent, ... }。
|
|
71
72
|
*
|
|
72
73
|
* 注意:不使用 lastComponent。每次 renderResult 返回新 Container(参照 nicobailon)。
|
|
73
74
|
*/
|
|
74
75
|
export interface RenderContext {
|
|
75
|
-
state: Record<string, never>;
|
|
76
76
|
invalidate(): void;
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -228,7 +228,10 @@ function buildCompactLines(d: SubagentToolResult, theme: ThemeLike): string[] {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
231
|
-
*
|
|
231
|
+
* 展开视图行内容生成。除下列两个特例外与 compact 逐字节相同(一次性 block 无
|
|
232
|
+
* 细节可展开),测试锁字节等价:
|
|
233
|
+
* - list:compact 结果之上每 item 追加 sessionFile 路径行(renderListExpanded)
|
|
234
|
+
* - fork-from:源文件用完整路径(compact 用 basename 短标签)
|
|
232
235
|
*/
|
|
233
236
|
function buildExpandedLines(d: SubagentToolResult, theme: ThemeLike): string[] {
|
|
234
237
|
const width = getTermWidth();
|
|
@@ -236,34 +239,15 @@ function buildExpandedLines(d: SubagentToolResult, theme: ThemeLike): string[] {
|
|
|
236
239
|
if (d.action === "list" && d.listResponse) {
|
|
237
240
|
return renderListExpanded(d.listResponse, theme, width);
|
|
238
241
|
}
|
|
239
|
-
if (d.action === "
|
|
242
|
+
if (d.action === "fork-from" && d.forkFromResponse) {
|
|
240
243
|
return [truncLine(
|
|
241
|
-
`${theme.fg("
|
|
244
|
+
`${theme.fg("accent", "⑂")} ${theme.fg("dim", "forked-from ")}${theme.fg("accent", d.forkFromResponse.sourceSessionFile)}` +
|
|
245
|
+
` ${theme.fg("dim", "→ new: ")}${theme.fg("accent", d.subagentId ?? "?")}`,
|
|
242
246
|
width,
|
|
243
247
|
)];
|
|
244
248
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return [
|
|
248
|
-
truncLine(
|
|
249
|
-
`${theme.fg("accent", "⑂")} ${theme.fg("dim", "forked-from ")}${theme.fg("accent", d.forkFromResponse.sourceSessionFile)}` +
|
|
250
|
-
` ${theme.fg("dim", "→ new: ")}${theme.fg("accent", d.subagentId ?? "?")}`,
|
|
251
|
-
width,
|
|
252
|
-
),
|
|
253
|
-
];
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const lines: string[] = [];
|
|
257
|
-
// bg 占位 expanded 与 compact 同(一次性 block 无细节可展开)
|
|
258
|
-
if ("bgResponse" in d) {
|
|
259
|
-
lines.push(truncLine(
|
|
260
|
-
`${theme.fg("accent", "●")} ${theme.fg("dim", "background: ")}${theme.fg("accent", d.subagentId ?? "?")}`
|
|
261
|
-
+ ` ${theme.fg("dim", "· detached")}`,
|
|
262
|
-
width,
|
|
263
|
-
));
|
|
264
|
-
return lines;
|
|
265
|
-
}
|
|
266
|
-
return [truncLine(theme.fg("warning", "(subagent: no response)"), width)];
|
|
249
|
+
// cancel / start(bg) / fallback:expanded 与 compact 逐字节相同
|
|
250
|
+
return buildCompactLines(d, theme);
|
|
267
251
|
}
|
|
268
252
|
|
|
269
253
|
// ============================================================
|
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
* 抽取边界(findings g11a-F1/F2/F3):
|
|
5
5
|
* - `assertNotAborted` / `optionSlugSuffix` / `renderTextResult`:三处逐字重复的
|
|
6
6
|
* 入口前置与渲染片段(同粒度小函数)。
|
|
7
|
-
* - `buildRunSpecFromScript
|
|
8
|
-
*
|
|
7
|
+
* - `buildRunSpecFromScript`:RunSpec 组装字面量——RunSpec 是 core 启动契约,
|
|
8
|
+
* 字面量漏改即静默丢字段。
|
|
9
|
+
* - `withGuiAttach`:RPC 模式给 details 附加 `__gui__` 的唯一实现(workflow-script /
|
|
10
|
+
* subagent 两个 tool 的 attach 点收敛于此,组件构造回调由调用方提供;workflow /
|
|
11
|
+
* subagents 走 isWorkflow 块分支不消费 `__gui__`,不构造——D8)。
|
|
12
|
+
* - `throwPrefixed`:catch 内 `<前缀>: <msg>` 重抛(pi 的 execute-throw 契约,
|
|
13
|
+
* toErrorMessage 规整单点)。
|
|
14
|
+
*
|
|
15
|
+
* 「可用脚本清单」串不在本文件:已并入 core 单源
|
|
16
|
+
*(@zhushanwen/subagent-core 的 formatAvailableWorkflowRefs,经 barrel 消费)。
|
|
9
17
|
*
|
|
10
18
|
* **不**把 execute 包成 HOF:reentry-guard.ts 文件头已裁决(HOF 包装会破坏 union
|
|
11
19
|
* 返回类型推断),本文件只放同粒度小函数,guard 的 check → try/finally release
|
|
@@ -17,7 +25,10 @@
|
|
|
17
25
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
18
26
|
import { Text } from "@earendil-works/pi-tui";
|
|
19
27
|
|
|
28
|
+
import type { GuiComponent, GuiContext, GuiRenderResult } from "@zhushanwen/extension-protocol";
|
|
29
|
+
import { guiResult, isGuiCapable } from "@zhushanwen/extension-protocol";
|
|
20
30
|
import type { RunSpec, WorkflowScript } from "@zhushanwen/subagent-core";
|
|
31
|
+
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
21
32
|
import { renderTextFallback } from "./format.ts";
|
|
22
33
|
|
|
23
34
|
/** renderResult 回调的宽入参形态(content 可缺省,由 renderTextFallback 兜底)。 */
|
|
@@ -93,17 +104,49 @@ export function buildRunSpecFromScript(
|
|
|
93
104
|
};
|
|
94
105
|
}
|
|
95
106
|
|
|
107
|
+
/** withGuiAttach 的 details 约束:已声明 `__gui__?`(消费 attach 的 details union 均满足)。 */
|
|
108
|
+
interface GuiAttachableDetails {
|
|
109
|
+
__gui__?: GuiRenderResult;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* GUI attach 单点:RPC 模式(isGuiCapable,仅 mode==="rpc")下为 details 附加
|
|
114
|
+
* `__gui__`,其余模式原样返回;details 为 undefined 时原样返回(过载签名区分
|
|
115
|
+
* 可空/非空入参,非空入参的返回不带 undefined——调用方无需收窄)。
|
|
116
|
+
*
|
|
117
|
+
* 组件构造是各 tool 特有逻辑,经 build 回调注入——回调只在 attach 分支被调用
|
|
118
|
+
* (非 RPC 模式零构造成本)。union 各成员已声明 `__gui__?`,spread + 补字段类型
|
|
119
|
+
* 安全,无需强转。
|
|
120
|
+
*/
|
|
121
|
+
export function withGuiAttach<Details extends GuiAttachableDetails>(
|
|
122
|
+
details: Details,
|
|
123
|
+
ctx: GuiContext | undefined,
|
|
124
|
+
build: (details: Details) => GuiComponent,
|
|
125
|
+
): Details;
|
|
126
|
+
export function withGuiAttach<Details extends GuiAttachableDetails>(
|
|
127
|
+
details: Details | undefined,
|
|
128
|
+
ctx: GuiContext | undefined,
|
|
129
|
+
build: (details: Details) => GuiComponent,
|
|
130
|
+
): Details | undefined;
|
|
131
|
+
export function withGuiAttach<Details extends GuiAttachableDetails>(
|
|
132
|
+
details: Details | undefined,
|
|
133
|
+
ctx: GuiContext | undefined,
|
|
134
|
+
build: (details: Details) => GuiComponent,
|
|
135
|
+
): Details | undefined {
|
|
136
|
+
if (!details) return details;
|
|
137
|
+
if (ctx && isGuiCapable(ctx)) {
|
|
138
|
+
return { ...details, __gui__: guiResult(build(details)) };
|
|
139
|
+
}
|
|
140
|
+
return details;
|
|
141
|
+
}
|
|
142
|
+
|
|
96
143
|
/**
|
|
97
|
-
*
|
|
144
|
+
* catch 内重抛单点:`<prefix>: <msg>`(msg 经 toErrorMessage 规整)。
|
|
98
145
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
146
|
+
* pi 只对 execute throw 置 isError:true——返回值里的 isError 被 agent-loop 丢弃
|
|
147
|
+
* (agent-loop.js:453-483),错误一律 throw,本函数是四个 catch 点共享的唯一包装
|
|
148
|
+
* 形态;前缀是 LLM 可见文案,调用方逐字保持既有形态。
|
|
101
149
|
*/
|
|
102
|
-
export function
|
|
103
|
-
|
|
104
|
-
.filter((wf) => wf.available)
|
|
105
|
-
.map(
|
|
106
|
-
(wf) => ` - ${wf.name}: ${wf.meta.description || "(no description)"}\n location: ${wf.path}`,
|
|
107
|
-
)
|
|
108
|
-
.join("\n");
|
|
150
|
+
export function throwPrefixed(prefix: string, err: unknown): never {
|
|
151
|
+
throw new Error(`${prefix}: ${toErrorMessage(err)}`);
|
|
109
152
|
}
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* - 唯一批量入口:N 个已知独立任务一次派发;handler 确定性转译
|
|
11
11
|
* runWorkflow("fan-out")(执行管道唯一——collect 时代的批协调状态已退役,
|
|
12
12
|
* 不存在第二套批机制)。
|
|
13
|
+
* - 批量派发的等价性契约:tasks[] 经 handler 确定性转译为内置 fan-out 模板脚本,
|
|
14
|
+
* 走 runWorkflow 完整管道执行——模板仅由 tasks[] 确定性生成、不含任何批协调
|
|
15
|
+
* 状态,执行管道唯一(不存在第二套批机制);走管道使批量派发继承 workflow 的
|
|
16
|
+
* 持久化、journal、GUI 投影与断点恢复(resume)全套能力,这是刻意保留间接层
|
|
17
|
+
* 的原因。
|
|
13
18
|
* - 无 action 分发:status/abort 不复制,直接指路 workflow tool(runId 同体系)。
|
|
14
19
|
* - 无 args 嵌套:tasks/agents/aggregate/... 全在顶层(弱模型信任 schema 结构信号,
|
|
15
20
|
* 两跳转译是事故高发区——对照 workflow tool 的 name+args 形态)。
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
* - 不构造 `details.__gui__`:GUI 挂载按 WORKFLOW_TOOL_NAMES 集合分流,批量块走
|
|
19
24
|
* workflow 块分支(恒折叠单行 + openWorkflowDrawer);`__gui__` 的渲染点在普通
|
|
20
25
|
* tool 分支(v-else)的展开区内,isWorkflow 分支无展开路径不消费——构造即死代码
|
|
21
|
-
* (D8 裁决;workflow tool
|
|
26
|
+
* (D8 裁决;workflow tool 的 `__gui__` 死构造已随之删除,isWorkflow 集合内工具零消费)。
|
|
22
27
|
*
|
|
23
28
|
* 层归属:Interface。依赖 Pi SDK + core lifecycle/registry + reentry-guard。
|
|
24
29
|
*/
|
|
@@ -30,7 +35,13 @@ import { type Static, Type } from "typebox";
|
|
|
30
35
|
|
|
31
36
|
import { MAX_TIMER_DELAY_MS, SLUG_MAX_LENGTH, THINKING_ORDER } from "@zhushanwen/subagent-core";
|
|
32
37
|
import type { LauncherDeps } from "@zhushanwen/subagent-core";
|
|
33
|
-
|
|
38
|
+
// formatAvailableWorkflowRefs:拒单可用清单单源(core launcher,副本已并入)。
|
|
39
|
+
import {
|
|
40
|
+
assertEntryTimeBudget,
|
|
41
|
+
assertSlugWithinLimit,
|
|
42
|
+
formatAvailableWorkflowRefs,
|
|
43
|
+
runWorkflow,
|
|
44
|
+
} from "@zhushanwen/subagent-core";
|
|
34
45
|
import {
|
|
35
46
|
acquireReentryGuard,
|
|
36
47
|
REENTRY_BUSY_MESSAGE,
|
|
@@ -40,7 +51,6 @@ import {
|
|
|
40
51
|
import {
|
|
41
52
|
assertNotAborted,
|
|
42
53
|
buildRunSpecFromScript,
|
|
43
|
-
formatAvailableWorkflowList,
|
|
44
54
|
optionSlugSuffix,
|
|
45
55
|
renderTextResult,
|
|
46
56
|
} from "./tool-shared.ts";
|
|
@@ -163,11 +173,12 @@ export async function runSubagentsBatch(
|
|
|
163
173
|
signal: AbortSignal | undefined,
|
|
164
174
|
): Promise<SubagentsExecuteResult> {
|
|
165
175
|
// D9:tasks 缺失/空数组 → 入口 throw(pi 只对 execute throw 置 isError:true)。
|
|
166
|
-
//
|
|
176
|
+
// 文案与其他 tool 的必填参数拒单同模板(<subject> requires '<param>' parameter.
|
|
177
|
+
// Correct: <最小正确调用例>):本工具无 action,主语用工具名。
|
|
167
178
|
const tasks = params.tasks;
|
|
168
179
|
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
169
180
|
throw new Error(
|
|
170
|
-
'tasks
|
|
181
|
+
'subagents requires \'tasks\' parameter (non-empty string array). Correct: {"tasks":["...","..."]}',
|
|
171
182
|
);
|
|
172
183
|
}
|
|
173
184
|
|
|
@@ -184,12 +195,13 @@ export async function runSubagentsBatch(
|
|
|
184
195
|
const time = params.time;
|
|
185
196
|
assertEntryTimeBudget(time);
|
|
186
197
|
|
|
187
|
-
//
|
|
188
|
-
//
|
|
198
|
+
// 执行体脚本按固定内置名解析(registry.get 精确名匹配;脚本名是工具自带常量
|
|
199
|
+
// FAN_OUT_SCRIPT_NAME 而非用户参数——workflow tool actionRun 的按名解析已退役
|
|
200
|
+
// 走 getPath 单通道,本工具不允许换脚本,无 getPath 回落需求)。
|
|
189
201
|
const script = await deps.registry.get(FAN_OUT_SCRIPT_NAME);
|
|
190
202
|
if (!script || !script.available) {
|
|
191
203
|
const all = await deps.registry.loadAll();
|
|
192
|
-
const available =
|
|
204
|
+
const available = formatAvailableWorkflowRefs(all);
|
|
193
205
|
throw new Error(
|
|
194
206
|
`Built-in workflow '${FAN_OUT_SCRIPT_NAME}' is not available — the subagents tool runs it as its batch body. ` +
|
|
195
207
|
`Recovery: verify the @zhushanwen/subagent-core package ships workflows/${FAN_OUT_SCRIPT_NAME}.js (reinstall/repair it), then retry. ` +
|
|
@@ -303,8 +315,6 @@ export function registerSubagentsTool(
|
|
|
303
315
|
);
|
|
304
316
|
},
|
|
305
317
|
|
|
306
|
-
renderResult
|
|
307
|
-
return renderTextResult(result);
|
|
308
|
-
},
|
|
318
|
+
renderResult: renderTextResult,
|
|
309
319
|
});
|
|
310
320
|
}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* generateWorkflowScript(报错文案逐字平移,CA2 前提);save/delete 改调 core
|
|
15
15
|
* barrel(第三可选目录参数缺省即 pi 布局 .pi/workflows——pi 现两参调用形态零变化)。
|
|
16
16
|
* 结构化 {ok}|{error} → pi 的 execute-throw 契约转换在本层(pi 只对 execute throw
|
|
17
|
-
* 置 isError:true);AbortSignal 的 aborted
|
|
17
|
+
* 置 isError:true);AbortSignal 的 aborted 检查在 execute 入口统一拦截(与
|
|
18
|
+
* workflow / subagents 两 tool 同源,tool-shared assertNotAborted;C4 偏差 #4)。
|
|
18
19
|
*
|
|
19
20
|
* 层归属:Interface。依赖 Pi SDK + engine script-lint + infra workflow-files。
|
|
20
21
|
*/
|
|
@@ -26,23 +27,21 @@ import { type Static, Type } from "typebox";
|
|
|
26
27
|
|
|
27
28
|
import {
|
|
28
29
|
guiComponent,
|
|
29
|
-
type GuiContext,
|
|
30
30
|
type GuiRenderResult,
|
|
31
|
-
guiResult,
|
|
32
|
-
isGuiCapable,
|
|
33
31
|
} from "@zhushanwen/extension-protocol";
|
|
34
32
|
// C5②/C5⑦:创作闭环统一走 core barrel(generateWorkflowScript/saveWorkflow/
|
|
35
33
|
// deleteWorkflow/lintScript 均为 barrel 导出面;深路径在 npm/vendored 形态不可达)
|
|
36
34
|
import {
|
|
37
35
|
deleteWorkflow,
|
|
36
|
+
formatAvailableWorkflowRefs,
|
|
38
37
|
generateWorkflowScript,
|
|
39
38
|
lintScript,
|
|
40
39
|
saveWorkflow,
|
|
41
40
|
} from "@zhushanwen/subagent-core";
|
|
42
41
|
import type { WorkflowScriptRegistry } from "@zhushanwen/subagent-core";
|
|
43
42
|
import { toGuiCtx } from "./gui-mappers.ts";
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
43
|
+
import type { WorkflowToolResult } from "./tool-result.ts";
|
|
44
|
+
import { assertNotAborted, renderTextResult, throwPrefixed, withGuiAttach } from "./tool-shared.ts";
|
|
46
45
|
|
|
47
46
|
// ── Parameter schema ─────────────────────────────────────────
|
|
48
47
|
|
|
@@ -82,17 +81,13 @@ export type WorkflowScriptToolDetails =
|
|
|
82
81
|
| { action: "save"; name: string; ok: boolean; __gui__?: GuiRenderResult }
|
|
83
82
|
| { action: "delete"; name: string; ok: boolean; __gui__?: GuiRenderResult };
|
|
84
83
|
|
|
85
|
-
/** Result returned by the `workflow-script` tool's execute. */
|
|
86
|
-
export
|
|
87
|
-
content: Array<{ type: "text"; text: string }>;
|
|
88
|
-
details: WorkflowScriptToolDetails | undefined;
|
|
89
|
-
isError?: boolean;
|
|
90
|
-
}
|
|
84
|
+
/** Result returned by the `workflow-script` tool's execute(公共骨架见 tool-result.ts)。 */
|
|
85
|
+
export type WorkflowScriptExecuteResult = WorkflowToolResult<WorkflowScriptToolDetails | undefined>;
|
|
91
86
|
|
|
92
87
|
// ── GUI 协议 helpers ───────────────────────────────────────
|
|
93
88
|
|
|
94
89
|
/**
|
|
95
|
-
*
|
|
90
|
+
* 按 WorkflowScriptToolDetails 构造 stats-line GuiComponent。
|
|
96
91
|
*
|
|
97
92
|
* 所有 5 个 action 都映射到 stats-line(单行统计,无复杂结构):
|
|
98
93
|
* - generate: 显示生成的脚本名
|
|
@@ -100,20 +95,6 @@ export interface TextContent {
|
|
|
100
95
|
* - list: 脚本数量
|
|
101
96
|
* - save/delete: ok/warn
|
|
102
97
|
*/
|
|
103
|
-
function withScriptGui(
|
|
104
|
-
result: TextContent,
|
|
105
|
-
ctx?: GuiContext,
|
|
106
|
-
): TextContent {
|
|
107
|
-
if (!ctx || !isGuiCapable(ctx) || !result.details) return result;
|
|
108
|
-
const details = result.details;
|
|
109
|
-
// union 各成员已声明 __gui__?,spread + 补字段类型安全,无需强转
|
|
110
|
-
return {
|
|
111
|
-
...result,
|
|
112
|
-
details: { ...details, __gui__: guiResult(buildScriptGui(details)) },
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** 按 WorkflowScriptToolDetails 构造 stats-line GuiComponent。 */
|
|
117
98
|
export function buildScriptGui(details: WorkflowScriptToolDetails) {
|
|
118
99
|
switch (details.action) {
|
|
119
100
|
case "generate":
|
|
@@ -199,11 +180,15 @@ export function registerWorkflowScriptTool(
|
|
|
199
180
|
signal: AbortSignal | undefined,
|
|
200
181
|
_onUpdate: unknown,
|
|
201
182
|
ctx: ExtensionContext,
|
|
202
|
-
): Promise<
|
|
203
|
-
|
|
183
|
+
): Promise<WorkflowScriptExecuteResult> {
|
|
184
|
+
// P1-2 abort 前置(三 tool 对称):5 个 action 统一在 execute 入口拦截——
|
|
185
|
+
// 原先只在 generate 内检查,lint/save/delete/list 四路径漏拦。throw(W4b):
|
|
186
|
+
// pi 只对 execute throw 置 isError:true,返回值里的 isError 被 agent-loop 丢弃。
|
|
187
|
+
assertNotAborted(signal);
|
|
188
|
+
let result: WorkflowScriptExecuteResult;
|
|
204
189
|
switch (params.action) {
|
|
205
190
|
case "generate":
|
|
206
|
-
result = actionGenerate(params
|
|
191
|
+
result = actionGenerate(params);
|
|
207
192
|
break;
|
|
208
193
|
case "lint":
|
|
209
194
|
result = await actionLint(params, registry);
|
|
@@ -222,8 +207,11 @@ export function registerWorkflowScriptTool(
|
|
|
222
207
|
// isError:true,返回值里的 isError 被 agent-loop 丢弃(agent-loop.js:453-483)。
|
|
223
208
|
throw new Error(`Unknown action: ${String(params.action)}`);
|
|
224
209
|
}
|
|
225
|
-
// GUI 协议:RPC 模式下附加 __gui__ 到 details
|
|
226
|
-
return
|
|
210
|
+
// GUI 协议:RPC 模式下附加 __gui__ 到 details(attach 单点在 tool-shared)
|
|
211
|
+
return {
|
|
212
|
+
...result,
|
|
213
|
+
details: withGuiAttach(result.details, toGuiCtx(ctx), buildScriptGui),
|
|
214
|
+
};
|
|
227
215
|
},
|
|
228
216
|
|
|
229
217
|
renderCall(args: ScriptParams, theme: Theme, _context?: unknown) {
|
|
@@ -234,23 +222,16 @@ export function registerWorkflowScriptTool(
|
|
|
234
222
|
return new Text(text, 0, 0);
|
|
235
223
|
},
|
|
236
224
|
|
|
237
|
-
renderResult
|
|
238
|
-
result: { content?: Array<{ type: string; text?: string }> },
|
|
239
|
-
_options: unknown,
|
|
240
|
-
_theme: Theme,
|
|
241
|
-
_context?: unknown,
|
|
242
|
-
) {
|
|
243
|
-
return renderTextResult(result);
|
|
244
|
-
},
|
|
225
|
+
renderResult: renderTextResult,
|
|
245
226
|
});
|
|
246
227
|
}
|
|
247
228
|
|
|
248
229
|
// ── generate action ──────────────────────────────────────────
|
|
249
230
|
|
|
250
|
-
export function actionGenerate(params: ScriptParams
|
|
231
|
+
export function actionGenerate(params: ScriptParams): WorkflowScriptExecuteResult {
|
|
251
232
|
// throw(W4b):pi 只对 execute throw 置 isError:true(返回值 isError 被丢弃)。
|
|
252
|
-
//
|
|
253
|
-
|
|
233
|
+
// abort 前置在 execute 入口统一拦截(三 tool 同源,tool-shared assertNotAborted),
|
|
234
|
+
// 本函数不再自带 signal 检查。
|
|
254
235
|
const name = params.name ?? "";
|
|
255
236
|
const script = params.script ?? "";
|
|
256
237
|
|
|
@@ -281,18 +262,20 @@ export function actionGenerate(params: ScriptParams, signal: AbortSignal | undef
|
|
|
281
262
|
async function actionLint(
|
|
282
263
|
params: ScriptParams,
|
|
283
264
|
registry: WorkflowScriptRegistry,
|
|
284
|
-
): Promise<
|
|
265
|
+
): Promise<WorkflowScriptExecuteResult> {
|
|
285
266
|
const name = params.name;
|
|
286
267
|
if (!name) {
|
|
287
|
-
throw new Error(
|
|
268
|
+
throw new Error(
|
|
269
|
+
"lint requires 'name' parameter. Correct: {\"action\":\"lint\",\"name\":\"<script>\"}",
|
|
270
|
+
);
|
|
288
271
|
}
|
|
289
272
|
const source = await loadScriptSource(name, registry);
|
|
290
273
|
if (!source) {
|
|
274
|
+
// 清单 item 走 core formatAvailableWorkflowRefs 缺省形态(有意统一:与 run 拒单
|
|
275
|
+
// 一致带 location 行,自纠指引同构);"Available:" 标题与 (none) 空态是本调用点
|
|
276
|
+
// 的语境文案。
|
|
291
277
|
const all = await registry.loadAll();
|
|
292
|
-
const
|
|
293
|
-
const suggestions = available
|
|
294
|
-
.map((wf) => ` - ${wf.name}: ${wf.meta.description || "(no description)"}`)
|
|
295
|
-
.join("\n");
|
|
278
|
+
const suggestions = formatAvailableWorkflowRefs(all);
|
|
296
279
|
throw new Error(
|
|
297
280
|
`Workflow '${name}' not found or not available.\nAvailable:\n${suggestions || " (none)"}`,
|
|
298
281
|
);
|
|
@@ -334,10 +317,12 @@ async function loadScriptSource(
|
|
|
334
317
|
|
|
335
318
|
// ── save action ──────────────────────────────────────────────
|
|
336
319
|
|
|
337
|
-
async function actionSave(params: ScriptParams): Promise<
|
|
320
|
+
async function actionSave(params: ScriptParams): Promise<WorkflowScriptExecuteResult> {
|
|
338
321
|
const name = params.name;
|
|
339
322
|
if (!name) {
|
|
340
|
-
throw new Error(
|
|
323
|
+
throw new Error(
|
|
324
|
+
"save requires 'name' parameter (tmp script name). Correct: {\"action\":\"save\",\"name\":\"<tmp-script>\"}",
|
|
325
|
+
);
|
|
341
326
|
}
|
|
342
327
|
try {
|
|
343
328
|
const result = await saveWorkflow(name, params.newName);
|
|
@@ -346,10 +331,7 @@ async function actionSave(params: ScriptParams): Promise<TextContent> {
|
|
|
346
331
|
details: { action: "save", name, ok: true },
|
|
347
332
|
};
|
|
348
333
|
} catch (err: unknown) {
|
|
349
|
-
|
|
350
|
-
// agent-loop 丢弃,agent-loop.js:453-483)——文案原样进 toolResult。
|
|
351
|
-
const msg = toErrorMessage(err);
|
|
352
|
-
throw new Error(`Save failed: ${msg}`);
|
|
334
|
+
throwPrefixed("Save failed", err);
|
|
353
335
|
}
|
|
354
336
|
}
|
|
355
337
|
|
|
@@ -359,10 +341,12 @@ function actionDelete(
|
|
|
359
341
|
params: ScriptParams,
|
|
360
342
|
registry: WorkflowScriptRegistry,
|
|
361
343
|
isRunning: (name: string) => boolean,
|
|
362
|
-
):
|
|
344
|
+
): WorkflowScriptExecuteResult {
|
|
363
345
|
const name = params.name;
|
|
364
346
|
if (!name) {
|
|
365
|
-
throw new Error(
|
|
347
|
+
throw new Error(
|
|
348
|
+
"delete requires 'name' parameter. Correct: {\"action\":\"delete\",\"name\":\"<script>\"}",
|
|
349
|
+
);
|
|
366
350
|
}
|
|
367
351
|
// deleteWorkflow 内部检查 isRunning(防止删运行中脚本)
|
|
368
352
|
try {
|
|
@@ -374,32 +358,28 @@ function actionDelete(
|
|
|
374
358
|
details: { action: "delete", name, ok: true },
|
|
375
359
|
};
|
|
376
360
|
} catch (err: unknown) {
|
|
377
|
-
|
|
378
|
-
const msg = toErrorMessage(err);
|
|
379
|
-
throw new Error(`Delete failed: ${msg}`);
|
|
361
|
+
throwPrefixed("Delete failed", err);
|
|
380
362
|
}
|
|
381
363
|
}
|
|
382
364
|
|
|
383
365
|
// ── list action ──────────────────────────────────────────────
|
|
384
366
|
|
|
385
|
-
async function actionList(registry: WorkflowScriptRegistry): Promise<
|
|
367
|
+
async function actionList(registry: WorkflowScriptRegistry): Promise<WorkflowScriptExecuteResult> {
|
|
386
368
|
try {
|
|
387
369
|
const all = await registry.loadAll();
|
|
388
|
-
|
|
389
|
-
|
|
370
|
+
// item 行走 core formatAvailableWorkflowRefs(includeSource 保留 [saved]/[tmp]
|
|
371
|
+
// 标签、去 location 行、分隔符统一为 ":"——与拒单 item 模板单源);标题与空态
|
|
372
|
+
// 是本调用点的语境文案。
|
|
373
|
+
const lines = formatAvailableWorkflowRefs(all, { includeSource: true, includeLocation: false });
|
|
374
|
+
if (lines === "") {
|
|
390
375
|
return textResult("No workflow scripts available.");
|
|
391
376
|
}
|
|
392
|
-
const lines = available.map(
|
|
393
|
-
(wf) => ` [${wf.source}] ${wf.name} — ${wf.meta.description || "(no description)"}`,
|
|
394
|
-
);
|
|
395
377
|
return {
|
|
396
|
-
content: [{ type: "text", text: `Available workflows:\n${lines
|
|
397
|
-
details: { action: "list", count: available.length },
|
|
378
|
+
content: [{ type: "text", text: `Available workflows:\n${lines}` }],
|
|
379
|
+
details: { action: "list", count: all.filter((wf) => wf.available).length },
|
|
398
380
|
};
|
|
399
381
|
} catch (err: unknown) {
|
|
400
|
-
|
|
401
|
-
const msg = toErrorMessage(err);
|
|
402
|
-
throw new Error(`List failed: ${msg}`);
|
|
382
|
+
throwPrefixed("List failed", err);
|
|
403
383
|
}
|
|
404
384
|
}
|
|
405
385
|
|
|
@@ -410,7 +390,7 @@ async function actionList(registry: WorkflowScriptRegistry): Promise<TextContent
|
|
|
410
390
|
* isError:true,返回值里的 isError 被 agent-loop 丢弃(agent-loop.js:453-483),
|
|
411
391
|
* 错误一律 throw,编译器兜底防回潮)。
|
|
412
392
|
*/
|
|
413
|
-
function textResult(text: string):
|
|
393
|
+
function textResult(text: string): WorkflowScriptExecuteResult {
|
|
414
394
|
return {
|
|
415
395
|
content: [{ type: "text", text }],
|
|
416
396
|
details: undefined,
|