@zhushanwen/pi-subagent-workflow 0.1.0 → 0.2.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.
Files changed (76) hide show
  1. package/agents/context-builder.md +1 -3
  2. package/agents/oracle.md +2 -2
  3. package/agents/planner.md +1 -3
  4. package/agents/researcher.md +0 -2
  5. package/agents/reviewer.md +2 -2
  6. package/agents/scout.md +13 -3
  7. package/agents/worker.md +0 -2
  8. package/package.json +5 -3
  9. package/skills/workflow-script-format/SKILL.md +6 -6
  10. package/src/execution/__tests__/agent-result-mapper.test.ts +24 -2
  11. package/src/execution/__tests__/concurrency-pool.test.ts +33 -0
  12. package/src/execution/__tests__/execute-options-mapper.test.ts +40 -8
  13. package/src/execution/__tests__/gui-mode-dispatch.test.ts +60 -0
  14. package/src/execution/__tests__/sdk-contract.test.ts +5 -2
  15. package/src/execution/__tests__/session-reconstructor.test.ts +20 -0
  16. package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
  17. package/src/execution/__tests__/subprocess-agent-runner.test.ts +72 -3
  18. package/src/execution/__tests__/tool-action.test.ts +26 -4
  19. package/src/execution/agent-result-mapper.ts +4 -1
  20. package/src/execution/concurrency-pool.ts +38 -6
  21. package/src/execution/execute-options-mapper.ts +21 -4
  22. package/src/execution/execution-record.ts +5 -0
  23. package/src/execution/record-store.ts +2 -0
  24. package/src/execution/session-reconstructor.ts +11 -0
  25. package/src/execution/session-runner.ts +12 -0
  26. package/src/execution/stream-sink.ts +83 -0
  27. package/src/execution/subagent-service.ts +68 -43
  28. package/src/execution/subprocess-agent-runner.ts +16 -4
  29. package/src/execution/types.ts +23 -3
  30. package/src/index.ts +15 -2
  31. package/src/interface/__tests__/subagent-tool-prompt.test.ts +84 -0
  32. package/src/interface/__tests__/workflow-state-file-exposure.test.ts +38 -0
  33. package/src/interface/__tests__/workflow-tool-prompt.test.ts +50 -0
  34. package/src/interface/command-actions.ts +77 -0
  35. package/src/interface/commands.ts +40 -4
  36. package/src/interface/gui-mappers.ts +83 -0
  37. package/src/interface/helpers.ts +52 -9
  38. package/src/interface/list-component.ts +3 -1
  39. package/src/interface/subagent-actions.ts +35 -22
  40. package/src/interface/subagent-tool.ts +54 -23
  41. package/src/interface/subagents.ts +45 -5
  42. package/src/interface/tool-render.ts +16 -5
  43. package/src/interface/tool-workflow-script.ts +113 -15
  44. package/src/interface/tool-workflow.ts +92 -34
  45. package/src/interface/views/WorkflowsView.ts +13 -4
  46. package/src/interface/views/__tests__/detail-content-session-file.test.ts +70 -0
  47. package/src/interface/views/detail-content.ts +20 -0
  48. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +208 -0
  49. package/src/orchestration/__tests__/agent-call-stream.test.ts +157 -0
  50. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +2 -0
  51. package/src/orchestration/__tests__/execute-agent-call.test.ts +171 -0
  52. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +177 -0
  53. package/src/orchestration/__tests__/worker-script-builder.test.ts +15 -0
  54. package/src/orchestration/agent-opts-resolver.ts +11 -2
  55. package/src/orchestration/error-recovery.ts +131 -23
  56. package/src/orchestration/execute-agent-call.ts +12 -3
  57. package/src/orchestration/jsonl-run-store.ts +10 -0
  58. package/src/orchestration/lifecycle.ts +1 -1
  59. package/src/orchestration/models/agent-call.ts +7 -0
  60. package/src/orchestration/models/ports.ts +15 -2
  61. package/src/orchestration/models/run-spec.ts +6 -0
  62. package/src/orchestration/models/trace.ts +1 -0
  63. package/src/orchestration/models/types.ts +19 -0
  64. package/src/orchestration/node-ops.ts +2 -0
  65. package/src/orchestration/worker-script-builder.ts +1 -0
  66. package/workflows/README.md +58 -0
  67. package/workflows/chain.js +107 -0
  68. package/workflows/map-reduce.js +142 -0
  69. package/workflows/parallel.js +131 -0
  70. package/workflows/scatter-gather.js +146 -0
  71. package/examples/README.md +0 -43
  72. package/examples/chain.example.js +0 -92
  73. package/examples/map-reduce.example.js +0 -99
  74. package/examples/parallel.example.js +0 -82
  75. package/examples/scatter-gather.example.js +0 -106
  76. package/src/interface/gui-adapter.ts +0 -136
@@ -40,6 +40,12 @@ export interface RunSpec {
40
40
  readonly budgetRef?: Budget;
41
41
  /** 脚本名(meta.name 或文件名 stem)。 */
42
42
  readonly scriptName: string;
43
+ /**
44
+ * Run 级简短标签(≤20 字符),区别于 scriptName(脚本身份名)。
45
+ * 区分同脚本的不同 run 实例(如 'migrate-users-batch1' vs 'migrate-users-batch2')。
46
+ * 旧持久化 run 缺失时为 undefined,渲染时回落 scriptName。
47
+ */
48
+ readonly slug?: string;
43
49
  /** 脚本文件绝对路径(用于诊断/日志)。 */
44
50
  readonly scriptPath: string;
45
51
  /** 人类可读描述(meta.description)。 */
@@ -61,6 +61,7 @@ export class Trace {
61
61
  if (patch.error !== undefined) node.error = patch.error;
62
62
  if (patch.completedAt !== undefined) node.completedAt = patch.completedAt;
63
63
  if (patch.sessionId !== undefined) node.sessionId = patch.sessionId;
64
+ if (patch.sessionFile !== undefined) node.sessionFile = patch.sessionFile;
64
65
  }
65
66
 
66
67
  /** 查找指定 stepIndex 的节点(首个匹配,trace 中 stepIndex 应唯一)。 */
@@ -84,6 +84,12 @@ export interface AgentCallOpts {
84
84
  * When omitted, pi's default model is used.
85
85
  */
86
86
  model?: string;
87
+ /**
88
+ * Thinking level override (e.g. "high", "medium", "low").
89
+ * M2: Added to align with subagent path's ExecuteOptions.thinkingLevel.
90
+ * When omitted, agent .md frontmatter thinkingLevel is used (via resolveAgentOpts).
91
+ */
92
+ thinkingLevel?: string;
87
93
  /** Scene name for model-switch advisor recommendation. */
88
94
  scene?: string;
89
95
  /**
@@ -181,6 +187,13 @@ export interface AgentResult {
181
187
  * Can be used to locate the session JSONL file for post-run inspection (G-017)。
182
188
  */
183
189
  sessionId?: string;
190
+ /**
191
+ * Session JSONL 绝对路径(不含目录的文件名在 subagents 侧 AgentResult.sessionFile)。
192
+ * 由 mapToWorkflowAgentResult 从 subagents AgentResult 透传——让 workflow 编排层
193
+ * 继承 subagent 执行管道产出的 session 文件路径,overlay/GUI 可直接定位。
194
+ * 窗口期内可能 undefined(session 尚未创建成功)。
195
+ */
196
+ sessionFile?: string;
184
197
  /** All tool calls collected from JSONL stream (FR-7). */
185
198
  toolCalls?: ToolCallEntry[];
186
199
  }
@@ -208,6 +221,11 @@ export interface ExecutionTraceNode {
208
221
  */
209
222
  sessionId?: string;
210
223
  /**
224
+ * Session JSONL 绝对路径。finalizeCall 从 result.sessionFile 透传。
225
+ * 持久化到快照(serializeRun),pause/resume + 跨 session 重水合后保留。
226
+ */
227
+ sessionFile?: string;
228
+ /**
211
229
  * Live 执行进度对象(running 时存在,done 时由 dispatchAgentCall 清除)。
212
230
  *
213
231
  * 挂在 node 上(D-10 单源延伸:AgentCall.traceNode 与 Trace.nodes 共享同一引用)。
@@ -229,6 +247,7 @@ export interface TracePatch {
229
247
  error?: string;
230
248
  completedAt?: string;
231
249
  sessionId?: string;
250
+ sessionFile?: string;
232
251
  }
233
252
 
234
253
  // ── Worker 诊断 ───────────────────────────────────────────────
@@ -96,6 +96,7 @@ export async function retryNode(
96
96
  call.attempts = 0;
97
97
  call.result = undefined;
98
98
  call.sessionId = undefined;
99
+ call.sessionFile = undefined;
99
100
 
100
101
  // 同步 trace 节点:回退到 pending
101
102
  run.state.trace.update(callId, {
@@ -104,6 +105,7 @@ export async function retryNode(
104
105
  error: undefined,
105
106
  completedAt: undefined,
106
107
  sessionId: undefined,
108
+ sessionFile: undefined,
107
109
  });
108
110
 
109
111
  // 主线程重跑(不重启 worker)——executeAgentCall 内部 markRunning + runner.run
@@ -170,6 +170,7 @@ export function buildWorkerScript(userScript: string): string {
170
170
  ' schema: firstArg.schema,',
171
171
  ' model: firstArg.model,',
172
172
  ' scene: firstArg.scene,',
173
+ ' skill: firstArg.skill,',
173
174
  ' timeoutMs: firstArg.timeoutMs,',
174
175
  ' cwd: firstArg.cwd,',
175
176
  ' };',
@@ -0,0 +1,58 @@
1
+ # 内置通用编排 Workflow
2
+
3
+ 4 个开箱即用的通用 subagent 编排 workflow,覆盖日常常见的多 agent 协作模式。每个脚本用 `agent()`/`parallel()` 自包含实现,`workflow run <name>` 直接执行,无需额外定义子 workflow。
4
+
5
+ ## 文件清单
6
+
7
+ | workflow | 模式 | 必需参数 | 适用场景 |
8
+ |----------|------|----------|----------|
9
+ | `chain.js` | analyze → transform → synthesize 顺序链 | `task` | 多阶段处理:先分析、再变换、最后综合 |
10
+ | `parallel.js` | 多视角并行分析 → 聚合汇总 | `target`(可选 `perspectives`) | 多维度评估同一目标(安全/性能/可维护性等) |
11
+ | `scatter-gather.js` | scatter 拆分 → parallel 处理 → gather 合并 | `task` | 大任务先拆成子任务再并行处理 |
12
+ | `map-reduce.js` | parallel map → reduce 归约 | `items`/`itemsJson` + `operation` | 对已知数组批量变换后归约成单一结果 |
13
+
14
+ ## 用法
15
+
16
+ ### chain — 顺序多步处理
17
+
18
+ ```
19
+ workflow run chain --args task="把这段需求文档拆成技术任务:..."
20
+ ```
21
+
22
+ 三段 agent 调用:分析任务 → 基于分析产出方案 → 综合方案输出结论。每步用 `schema` 拿结构化输出,上一步输出拼进下一步 prompt。
23
+
24
+ ### parallel — 并行多视角分析
25
+
26
+ ```
27
+ workflow run parallel --args target="src/auth/login.ts"
28
+ workflow run parallel --args target="..." --args 'perspectives=["security","readability"]'
29
+ ```
30
+
31
+ `perspectives` 默认 `["security","performance","maintainability"]`。每个视角一个并行 agent,各自返回评分+发现的问题;最后再一个 agent 汇总成总体评分+top 问题+共识。
32
+
33
+ ### scatter-gather — 分发-收集
34
+
35
+ ```
36
+ workflow run scatter-gather --args task="重构认证模块,涉及 session/jwt/oauth 三块"
37
+ ```
38
+
39
+ 三段:第一个 agent 把大任务拆成 2-4 个可并行子任务 → `parallel()` 并行处理每个子任务 → 最后一个 agent 合并所有结果。
40
+
41
+ ### map-reduce — 映射-归约
42
+
43
+ ```
44
+ workflow run map-reduce --args 'items=["file1.ts","file2.ts","file3.ts"]' --args operation="审查代码风格"
45
+ workflow run map-reduce --args itemsJson=/path/to/items.json --args operation="..."
46
+ ```
47
+
48
+ `items` 直接传 JSON 数组,或 `itemsJson` 传 JSON 文件路径(二选一)。`parallel()` 对每个 item 并行执行 `operation` → 一个 agent 把所有结果归约成单一结论。
49
+
50
+ ## 编排 API
51
+
52
+ 这些 workflow 内部使用的编排函数(`agent()` / `parallel()` / `pipeline()` / `workflow()`)由 worker 线程注入,完整 API 参考见 `skills/workflow-script-format/SKILL.md`。
53
+
54
+ ## 相关文档
55
+
56
+ - `skills/workflow-script-format/SKILL.md` — workflow script 完整 API(agent/parallel/pipeline/workflow 签名、$ARGS/$BUDGET、lint 规则)
57
+ - `docs/adr/030-subagents-workflow-merge.md` — 合并决策(决策 3 分层配额 + workflow 嵌套)
58
+ - `docs/adr/032-builtin-orchestration-workflows.md` — 从"参考模板"改为"内置通用编排 workflow"的决策
@@ -0,0 +1,107 @@
1
+ // chain.js — 顺序多步处理(通用 subagent 编排)
2
+ //
3
+ // 模式:analyze → transform → synthesize,每步 agent() 输出作下步输入。
4
+ // 适用于需要"先分析、再变换、最后综合"的多阶段任务。
5
+ //
6
+ // 用法:
7
+ // workflow run chain --args task="把这段需求文档拆成技术任务:..."
8
+ //
9
+ // ⚠️ lintScript 约束(本脚本已遵守):
10
+ // - 含 agent() 入口
11
+ // - 禁止 bare IIFE(用 top-level await)
12
+ // - 禁止用 result 作变量名
13
+
14
+ const meta = {
15
+ name: "chain",
16
+ description: "通用编排:analyze → transform → synthesize 顺序三步链",
17
+ phases: ["analyze", "transform", "synthesize"],
18
+ };
19
+
20
+ // ── 入参($ARGS)──────────────────────────────────────────────────
21
+ const task = $ARGS.task;
22
+ if (!task) {
23
+ throw new Error("chain 缺少必需参数 task。用法:workflow run chain --args task=\"<任务描述>\"");
24
+ }
25
+
26
+ log("chain 开始,task=" + task);
27
+
28
+ let currentPhase = "init";
29
+ let outcome;
30
+
31
+ try {
32
+ // ── 段 1:analyze(分析任务,提取关键点)─────────────────────────
33
+ phase("analyze");
34
+ currentPhase = "analyze";
35
+ const analysis = await agent({
36
+ prompt: "分析以下任务,提取核心洞察和关键点:\n\n" + task,
37
+ schema: {
38
+ type: "object",
39
+ properties: {
40
+ insights: { type: "string", description: "对任务的核心洞察" },
41
+ keyPoints: {
42
+ type: "array",
43
+ items: { type: "string" },
44
+ description: "关键点列表",
45
+ },
46
+ },
47
+ required: ["insights", "keyPoints"],
48
+ },
49
+ description: "chain-analyze",
50
+ });
51
+
52
+ // ── 段 2:transform(基于分析产出方案)───────────────────────────
53
+ phase("transform");
54
+ currentPhase = "transform";
55
+ const plan = await agent({
56
+ prompt:
57
+ "基于以下分析结果,产出可执行方案:\n\n洞察:" + (analysis?.insights ?? "(分析无结果)") +
58
+ "\n关键点:" + JSON.stringify(analysis?.keyPoints ?? []),
59
+ schema: {
60
+ type: "object",
61
+ properties: {
62
+ plan: { type: "string", description: "执行方案" },
63
+ actions: {
64
+ type: "array",
65
+ items: { type: "string" },
66
+ description: "具体行动步骤",
67
+ },
68
+ },
69
+ required: ["plan", "actions"],
70
+ },
71
+ description: "chain-transform",
72
+ });
73
+
74
+ // ── 段 3:synthesize(综合方案输出最终结论)─────────────────────
75
+ phase("synthesize");
76
+ currentPhase = "synthesize";
77
+ const final = await agent({
78
+ prompt:
79
+ "综合以下方案,输出最终结论和建议:\n\n方案:" + (plan?.plan ?? "(方案无结果)") +
80
+ "\n行动步骤:" + JSON.stringify(plan?.actions ?? []),
81
+ schema: {
82
+ type: "object",
83
+ properties: {
84
+ summary: { type: "string", description: "最终总结" },
85
+ recommendation: { type: "string", description: "核心建议" },
86
+ },
87
+ required: ["summary", "recommendation"],
88
+ },
89
+ description: "chain-synthesize",
90
+ });
91
+
92
+ outcome = {
93
+ status: "ok",
94
+ phases_run: ["analyze", "transform", "synthesize"],
95
+ final: { summary: (final?.summary ?? "(综合无结果)"), recommendation: (final?.recommendation ?? "(综合无结果)") },
96
+ message: "chain 完成:analyze → transform → synthesize 全绿",
97
+ };
98
+ } catch (err) {
99
+ outcome = {
100
+ status: "error",
101
+ phase: currentPhase,
102
+ error: err && err.message ? err.message : String(err),
103
+ message: "chain 在 " + currentPhase + " 段失败",
104
+ };
105
+ }
106
+
107
+ return outcome;
@@ -0,0 +1,142 @@
1
+ // map-reduce.js — 映射-归约(通用 subagent 编排)
2
+ //
3
+ // 模式(两段):
4
+ // 段 1 map: parallel() 对每个 item 并行执行 operation
5
+ // 段 2 reduce: agent() 把所有 map 结果归约成单一结果
6
+ //
7
+ // 与 scatter-gather 的区别:scatter-gather 强调"拆分"(scatter 决定子任务数);
8
+ // map-reduce 强调对"已知 items 数组"的变换+聚合(items 已有,map 变换、reduce 归约)。
9
+ //
10
+ // 用法:
11
+ // workflow run map-reduce --args 'items=["file1.ts","file2.ts","file3.ts"]' --args operation="审查代码风格"
12
+ // workflow run map-reduce --args itemsJson=/path/to/items.json --args operation="..."
13
+ //
14
+ // ⚠️ lintScript 约束(本脚本已遵守):含 parallel() 入口(兼 agent 嵌套),禁止 bare IIFE
15
+
16
+ const meta = {
17
+ name: "map-reduce",
18
+ description: "通用编排:parallel map → reduce 两段,处理已知 items 数组",
19
+ phases: ["map", "reduce"],
20
+ };
21
+
22
+ const fs = require("fs");
23
+
24
+ // ── 入参($ARGS)──────────────────────────────────────────────────
25
+ const operation = $ARGS.operation;
26
+ if (!operation) {
27
+ throw new Error(
28
+ 'map-reduce 缺少必需参数 operation。用法:workflow run map-reduce --args operation="<对每个 item 做什么>"',
29
+ );
30
+ }
31
+
32
+ // items 来源:直接数组 或 itemsJson 文件路径(二选一)
33
+ let items = $ARGS.items;
34
+ if (!items) {
35
+ const itemsPath = $ARGS.itemsJson;
36
+ if (!itemsPath) {
37
+ throw new Error(
38
+ 'map-reduce 需要 items(直接数组)或 itemsJson(JSON 文件路径)参数',
39
+ );
40
+ }
41
+ if (!fs.existsSync(itemsPath)) {
42
+ throw new Error("itemsJson 文件不存在: " + itemsPath);
43
+ }
44
+ items = JSON.parse(fs.readFileSync(itemsPath, "utf-8"));
45
+ }
46
+
47
+ if (!Array.isArray(items) || items.length === 0) {
48
+ throw new Error("items 不是数组或为空");
49
+ }
50
+
51
+ log("map-reduce 开始,items=" + items.length + " 个,operation=" + operation);
52
+
53
+ let currentPhase = "init";
54
+ let outcome;
55
+
56
+ try {
57
+ // ── 段 1:map(parallel 对每个 item 并行变换)────────────────────
58
+ phase("map");
59
+ currentPhase = "map";
60
+
61
+ // 每个 item 字符串化,便于拼进 prompt
62
+ const mappedRaw = await parallel(
63
+ items.map((item, idx) =>
64
+ agent({
65
+ prompt:
66
+ "对以下 item 执行操作:\n\noperation:" + operation +
67
+ "\nitem:" + (typeof item === "string" ? item : JSON.stringify(item)),
68
+ schema: {
69
+ type: "object",
70
+ properties: {
71
+ itemIndex: { type: "number", description: "item 序号" },
72
+ mapped: { type: "string", description: "map 后的结果" },
73
+ },
74
+ required: ["itemIndex", "mapped"],
75
+ },
76
+ description: "map-reduce-map-" + idx,
77
+ })
78
+ ),
79
+ );
80
+
81
+ const mapped = [];
82
+ let mapFailed = 0;
83
+ for (let i = 0; i < mappedRaw.length; i++) {
84
+ const r = mappedRaw[i];
85
+ if (!r || r.error) {
86
+ mapped.push({
87
+ itemIndex: i,
88
+ item: items[i],
89
+ status: "failed",
90
+ error: r ? r.error : "agent 无返回",
91
+ });
92
+ mapFailed++;
93
+ } else {
94
+ mapped.push({
95
+ itemIndex: i,
96
+ item: items[i],
97
+ status: "ok",
98
+ mapped: r.mapped,
99
+ });
100
+ }
101
+ }
102
+ if (mapFailed === items.length) {
103
+ throw new Error("全部 map 失败(" + mapFailed + "/" + items.length + ")");
104
+ }
105
+ log("map 完成:ok=" + (items.length - mapFailed) + " failed=" + mapFailed);
106
+
107
+ // ── 段 2:reduce(agent 聚合所有 map 结果)──────────────────────
108
+ phase("reduce");
109
+ currentPhase = "reduce";
110
+ const reduced = await agent({
111
+ prompt:
112
+ "以下是对 " + items.length + " 个 item 执行「" + operation + "」的结果,请归约成单一结论:\n\n" +
113
+ JSON.stringify(mapped, null, 2),
114
+ schema: {
115
+ type: "object",
116
+ properties: {
117
+ reduced: { type: "string", description: "归约后的最终结果" },
118
+ stats: { type: "string", description: "统计摘要(成功率/共性发现等)" },
119
+ },
120
+ required: ["reduced", "stats"],
121
+ },
122
+ description: "map-reduce-reduce",
123
+ });
124
+
125
+ outcome = {
126
+ status: mapFailed > 0 ? "partial" : "ok",
127
+ phases_run: ["map", "reduce"],
128
+ items_total: items.length,
129
+ items_mapped: items.length - mapFailed,
130
+ reduced: { reduced: (reduced?.reduced ?? "(归约无结果)"), stats: (reduced?.stats ?? "(归约无结果)") },
131
+ message: "map-reduce 完成:map " + items.length + " 项(失败 " + mapFailed + ")→ reduce",
132
+ };
133
+ } catch (err) {
134
+ outcome = {
135
+ status: "error",
136
+ phase: currentPhase,
137
+ error: err && err.message ? err.message : String(err),
138
+ message: "map-reduce 在 " + currentPhase + " 段失败",
139
+ };
140
+ }
141
+
142
+ return outcome;
@@ -0,0 +1,131 @@
1
+ // parallel.js — 并行多视角分析(通用 subagent 编排)
2
+ //
3
+ // 模式:N 个 agent 从不同角度并行分析同一目标 → 汇总聚合。
4
+ // 适用于需要多维度评估(安全/性能/可维护性等)的场景。
5
+ //
6
+ // 用法:
7
+ // workflow run parallel --args target="src/auth/login.ts"
8
+ // workflow run parallel --args target="..." --args 'perspectives=["security","readability"]'
9
+ //
10
+ // ⚠️ 分层配额规则(来源:ADR-030 决策 3):
11
+ // - 全局并发上限 maxConcurrent = 6
12
+ // - parallel() 内的 agent() 调用共享配额池,超出自动排队(不报错)
13
+ // - 本脚本默认 3 视角并行,配额充足
14
+ //
15
+ // ⚠️ lintScript 约束(本脚本已遵守):含 parallel() 入口,禁止 bare IIFE
16
+
17
+ const meta = {
18
+ name: "parallel",
19
+ description: "通用编排:多视角并行分析同一目标,再聚合汇总",
20
+ phases: ["parallel-analyze", "aggregate"],
21
+ };
22
+
23
+ // ── 入参($ARGS)──────────────────────────────────────────────────
24
+ const target = $ARGS.target;
25
+ if (!target) {
26
+ throw new Error("parallel 缺少必需参数 target。用法:workflow run parallel --args target=\"<分析目标>\"");
27
+ }
28
+ const perspectives = Array.isArray($ARGS.perspectives) && $ARGS.perspectives.length > 0
29
+ ? $ARGS.perspectives
30
+ : ["security", "performance", "maintainability"];
31
+
32
+ log("parallel 开始,target=" + target + " perspectives=" + JSON.stringify(perspectives));
33
+
34
+ let currentPhase = "init";
35
+ let outcome;
36
+
37
+ try {
38
+ // ── 段 1:parallel-analyze(多视角并行分析)──────────────────────
39
+ phase("parallel-analyze");
40
+ currentPhase = "parallel-analyze";
41
+
42
+ // parallel() 接受 Promise 数组;agent() 返回 Promise。allSettled 语义。
43
+ const perPerspectiveRaw = await parallel(
44
+ perspectives.map((p) =>
45
+ agent({
46
+ prompt:
47
+ "从「" + p + "」角度分析以下目标,给出评分和发现的问题:\n\n" + target,
48
+ schema: {
49
+ type: "object",
50
+ properties: {
51
+ perspective: { type: "string", description: "视角名称" },
52
+ score: { type: "number", description: "0-10 评分" },
53
+ findings: {
54
+ type: "array",
55
+ items: { type: "string" },
56
+ description: "发现的问题",
57
+ },
58
+ },
59
+ required: ["perspective", "score", "findings"],
60
+ },
61
+ description: "parallel-" + p,
62
+ })
63
+ ),
64
+ );
65
+
66
+ // 收集结果,标记成功/失败
67
+ const perPerspective = [];
68
+ let failedCount = 0;
69
+ for (let i = 0; i < perPerspectiveRaw.length; i++) {
70
+ const r = perPerspectiveRaw[i];
71
+ if (!r || r.error) {
72
+ perPerspective.push({
73
+ perspective: perspectives[i],
74
+ status: "failed",
75
+ error: r ? r.error : "agent 无返回",
76
+ });
77
+ failedCount++;
78
+ } else {
79
+ perPerspective.push({ perspective: perspectives[i], status: "ok", ...r });
80
+ }
81
+ }
82
+ if (failedCount === perspectives.length) {
83
+ throw new Error("全部视角分析失败(" + failedCount + "/" + perspectives.length + ")");
84
+ }
85
+ log("parallel-analyze 完成:ok=" + (perspectives.length - failedCount) + " failed=" + failedCount);
86
+
87
+ // ── 段 2:aggregate(汇总多视角结果)────────────────────────────
88
+ phase("aggregate");
89
+ currentPhase = "aggregate";
90
+ const aggregate = await agent({
91
+ prompt:
92
+ "以下是多视角分析结果,请综合出总体评分、top 问题和共识:\n\n" +
93
+ JSON.stringify(perPerspective, null, 2),
94
+ schema: {
95
+ type: "object",
96
+ properties: {
97
+ overallScore: { type: "number", description: "综合评分 0-10" },
98
+ topIssues: {
99
+ type: "array",
100
+ items: { type: "string" },
101
+ description: "最关键的问题(按严重度排序)",
102
+ },
103
+ consensus: { type: "string", description: "多视角共识总结" },
104
+ },
105
+ required: ["overallScore", "topIssues", "consensus"],
106
+ },
107
+ description: "parallel-aggregate",
108
+ });
109
+
110
+ outcome = {
111
+ status: failedCount > 0 ? "partial" : "ok",
112
+ phases_run: ["parallel-analyze", "aggregate"],
113
+ perspectives_analyzed: perspectives.length,
114
+ per_perspective: perPerspective,
115
+ aggregate: {
116
+ overallScore: (aggregate?.overallScore ?? "(聚合无结果)"),
117
+ topIssues: (aggregate?.topIssues ?? []),
118
+ consensus: (aggregate?.consensus ?? "(聚合无结果)"),
119
+ },
120
+ message: "parallel 完成:" + perspectives.length + " 视角(失败 " + failedCount + ")→ 聚合",
121
+ };
122
+ } catch (err) {
123
+ outcome = {
124
+ status: "error",
125
+ phase: currentPhase,
126
+ error: err && err.message ? err.message : String(err),
127
+ message: "parallel 在 " + currentPhase + " 段失败",
128
+ };
129
+ }
130
+
131
+ return outcome;
@@ -0,0 +1,146 @@
1
+ // scatter-gather.js — 分发-收集(通用 subagent 编排)
2
+ //
3
+ // 模式(三段):
4
+ // 段 1 scatter: agent() 把大任务拆成 2-4 个可并行的子任务
5
+ // 段 2 process: parallel() 并行处理每个子任务
6
+ // 段 3 gather: agent() 合并所有子任务结果
7
+ //
8
+ // 适用于"任务太大需要先拆分再并行处理"的场景。
9
+ //
10
+ // 用法:
11
+ // workflow run scatter-gather --args task="重构认证模块,涉及 session/jwt/oauth 三块"
12
+ //
13
+ // ⚠️ lintScript 约束(本脚本已遵守):含 parallel() 入口(兼 agent 嵌套),禁止 bare IIFE
14
+
15
+ const meta = {
16
+ name: "scatter-gather",
17
+ description: "通用编排:scatter 拆分 → parallel 处理 → gather 合并 三段",
18
+ phases: ["scatter", "process", "gather"],
19
+ };
20
+
21
+ // ── 入参($ARGS)──────────────────────────────────────────────────
22
+ const task = $ARGS.task;
23
+ if (!task) {
24
+ throw new Error("scatter-gather 缺少必需参数 task。用法:workflow run scatter-gather --args task=\"<大任务描述>\"");
25
+ }
26
+
27
+ log("scatter-gather 开始,task=" + task);
28
+
29
+ let currentPhase = "init";
30
+ let outcome;
31
+
32
+ try {
33
+ // ── 段 1:scatter(拆分任务)─────────────────────────────────────
34
+ phase("scatter");
35
+ currentPhase = "scatter";
36
+ const split = await agent({
37
+ prompt:
38
+ "把以下任务拆成 2-4 个可独立并行处理的子任务。每个子任务应有明确边界,不互相依赖:\n\n" +
39
+ task,
40
+ schema: {
41
+ type: "object",
42
+ properties: {
43
+ subtasks: {
44
+ type: "array",
45
+ items: {
46
+ type: "object",
47
+ properties: {
48
+ name: { type: "string", description: "子任务名称" },
49
+ description: { type: "string", description: "子任务详细描述" },
50
+ },
51
+ required: ["name", "description"],
52
+ },
53
+ description: "2-4 个可并行的子任务",
54
+ },
55
+ },
56
+ required: ["subtasks"],
57
+ },
58
+ description: "scatter-split",
59
+ });
60
+
61
+ const subtasks = Array.isArray(split?.subtasks) ? split.subtasks : [];
62
+ if (subtasks.length === 0) {
63
+ throw new Error("scatter 返回的 subtasks 为空");
64
+ }
65
+ log("scatter 出 " + subtasks.length + " 个子任务");
66
+
67
+ // ── 段 2:process(parallel 并行处理每个子任务)──────────────────
68
+ phase("process");
69
+ currentPhase = "process";
70
+ const processedRaw = await parallel(
71
+ subtasks.map((s) =>
72
+ agent({
73
+ prompt:
74
+ "处理以下子任务,输出处理结果:\n\n子任务:" + s.name + "\n描述:" + s.description,
75
+ schema: {
76
+ type: "object",
77
+ properties: {
78
+ subtask: { type: "string", description: "子任务名称" },
79
+ result: { type: "string", description: "处理结果" },
80
+ },
81
+ required: ["subtask", "result"],
82
+ },
83
+ description: "scatter-process-" + s.name,
84
+ })
85
+ ),
86
+ );
87
+
88
+ const processed = [];
89
+ let failedCount = 0;
90
+ for (let i = 0; i < processedRaw.length; i++) {
91
+ const r = processedRaw[i];
92
+ if (!r || r.error) {
93
+ processed.push({
94
+ subtask: subtasks[i].name,
95
+ status: "failed",
96
+ error: r ? r.error : "agent 无返回",
97
+ });
98
+ failedCount++;
99
+ } else {
100
+ processed.push({ subtask: subtasks[i].name, status: "ok", result: r.result });
101
+ }
102
+ }
103
+ if (failedCount === subtasks.length) {
104
+ throw new Error("全部子任务处理失败(" + failedCount + "/" + subtasks.length + ")");
105
+ }
106
+ log("process 完成:ok=" + (subtasks.length - failedCount) + " failed=" + failedCount);
107
+
108
+ // ── 段 3:gather(合并所有子任务结果)───────────────────────────
109
+ phase("gather");
110
+ currentPhase = "gather";
111
+ const gathered = await agent({
112
+ prompt:
113
+ "以下是各子任务的处理结果,请合并成一个完整、一致的最终结果:\n\n" +
114
+ JSON.stringify(processed, null, 2),
115
+ schema: {
116
+ type: "object",
117
+ properties: {
118
+ mergedResult: { type: "string", description: "合并后的最终结果" },
119
+ completeness: { type: "string", description: "完整性评估" },
120
+ },
121
+ required: ["mergedResult", "completeness"],
122
+ },
123
+ description: "scatter-gather-merge",
124
+ });
125
+
126
+ outcome = {
127
+ status: failedCount > 0 ? "partial" : "ok",
128
+ phases_run: ["scatter", "process", "gather"],
129
+ subtasks_total: subtasks.length,
130
+ subtasks_processed: subtasks.length - failedCount,
131
+ gathered: {
132
+ mergedResult: (gathered?.mergedResult ?? "(合并无结果)"),
133
+ completeness: (gathered?.completeness ?? "(合并无结果)"),
134
+ },
135
+ message: "scatter-gather 完成:split " + subtasks.length + " → process(失败 " + failedCount + ")→ merge",
136
+ };
137
+ } catch (err) {
138
+ outcome = {
139
+ status: "error",
140
+ phase: currentPhase,
141
+ error: err && err.message ? err.message : String(err),
142
+ message: "scatter-gather 在 " + currentPhase + " 段失败",
143
+ };
144
+ }
145
+
146
+ return outcome;