@fieldwangai/agentflow 0.1.153 → 0.1.154

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 (192) hide show
  1. package/README.md +16 -34
  2. package/README.zh-CN.md +14 -34
  3. package/agents/agentflow-node-executor-code.md +13 -3
  4. package/agents/agentflow-node-executor-planning.md +13 -3
  5. package/agents/agentflow-node-executor-requirement.md +13 -3
  6. package/agents/agentflow-node-executor-test.md +13 -3
  7. package/agents/agentflow-node-executor-ui.md +13 -3
  8. package/agents/agentflow-node-executor.md +13 -3
  9. package/agents/en/agentflow-node-executor.md +13 -3
  10. package/agents/zh/agentflow-node-executor.md +13 -3
  11. package/bin/agentflow.mjs +3 -3
  12. package/bin/lib/admin-builtin-pipelines.mjs +3 -2
  13. package/bin/lib/agent-runners.mjs +0 -929
  14. package/bin/lib/auth.mjs +2 -1
  15. package/bin/lib/catalog-agents.mjs +2 -1
  16. package/bin/lib/catalog-flows.mjs +165 -90
  17. package/bin/lib/composer-agent.mjs +3 -793
  18. package/bin/lib/composer-skill-router.mjs +3 -323
  19. package/bin/lib/exec-buffered.mjs +32 -0
  20. package/bin/lib/flow-dsl/cli.mjs +276 -0
  21. package/bin/lib/flow-dsl/codegen.mjs +358 -0
  22. package/bin/lib/flow-dsl/defs.mjs +103 -0
  23. package/bin/lib/flow-dsl/index.mjs +77 -0
  24. package/bin/lib/flow-dsl/ir.mjs +305 -0
  25. package/bin/lib/flow-dsl/layout.mjs +115 -0
  26. package/bin/lib/flow-dsl/legacy-yaml.mjs +268 -0
  27. package/bin/lib/flow-dsl/lint.mjs +241 -0
  28. package/bin/lib/flow-dsl/packages.mjs +251 -0
  29. package/bin/lib/flow-dsl/parser.mjs +444 -0
  30. package/bin/lib/flow-import.mjs +96 -26
  31. package/bin/lib/flow-write.mjs +48 -12
  32. package/bin/lib/help.mjs +8 -28
  33. package/bin/lib/html-escape.mjs +19 -0
  34. package/bin/lib/http-util.mjs +49 -0
  35. package/bin/lib/legacy-flow-execution.mjs +37 -0
  36. package/bin/lib/locales/en.json +1 -1
  37. package/bin/lib/locales/zh.json +1 -1
  38. package/bin/lib/main.mjs +152 -286
  39. package/bin/lib/marketplace.mjs +206 -61
  40. package/bin/lib/model-config.mjs +10 -0
  41. package/bin/lib/node-package-archive.mjs +240 -0
  42. package/bin/lib/node-package-bootstrap.mjs +72 -0
  43. package/bin/lib/node-package-manifest.mjs +174 -0
  44. package/bin/lib/paths.mjs +42 -19
  45. package/bin/lib/prd-workflow-routes.mjs +2949 -0
  46. package/bin/lib/prd-workflow-server.mjs +4884 -0
  47. package/bin/lib/run-events.mjs +0 -66
  48. package/bin/lib/schedule-config.mjs +4 -4
  49. package/bin/lib/skill-runtime.mjs +35 -0
  50. package/bin/lib/startup-storage-migrations.mjs +274 -0
  51. package/bin/lib/ui-server.mjs +1087 -18122
  52. package/bin/lib/user-env.mjs +16 -0
  53. package/bin/lib/workspace-auto-layout.mjs +234 -0
  54. package/bin/lib/workspace-flow-store.mjs +478 -0
  55. package/bin/lib/workspace-graph-merge.mjs +41 -34
  56. package/bin/lib/workspace-routes.mjs +3088 -0
  57. package/bin/lib/workspace-server.mjs +6199 -0
  58. package/bin/lib/workspace-state.mjs +331 -0
  59. package/bin/lib/workspace.mjs +2 -1
  60. package/builtin/nodes/agent_subAgent.md +1 -0
  61. package/builtin/nodes/control_agent_toBool.md +2 -0
  62. package/builtin/nodes/control_anyOne.md +2 -0
  63. package/builtin/nodes/control_cancelled.md +2 -0
  64. package/builtin/nodes/control_cd_workspace.md +5 -11
  65. package/builtin/nodes/control_delay.md +2 -0
  66. package/builtin/nodes/control_end.md +2 -0
  67. package/builtin/nodes/control_if.md +1 -0
  68. package/builtin/nodes/control_interval_loop.md +2 -0
  69. package/builtin/nodes/control_load_mcp.md +24 -0
  70. package/builtin/nodes/control_load_skills.md +8 -23
  71. package/builtin/nodes/control_start.md +2 -0
  72. package/builtin/nodes/control_toBool.md +2 -0
  73. package/builtin/nodes/control_user_workspace.md +2 -0
  74. package/builtin/nodes/control_wait_until.md +2 -0
  75. package/builtin/nodes/display_ascii.md +1 -0
  76. package/builtin/nodes/display_chart.md +1 -0
  77. package/builtin/nodes/display_html.md +1 -0
  78. package/builtin/nodes/display_image.md +1 -0
  79. package/builtin/nodes/display_markdown.md +1 -0
  80. package/builtin/nodes/display_mermaid.md +1 -0
  81. package/builtin/nodes/display_react_app.md +1 -0
  82. package/builtin/nodes/display_table.md +1 -0
  83. package/builtin/nodes/provide_bool.md +1 -0
  84. package/builtin/nodes/provide_file.md +1 -0
  85. package/builtin/nodes/provide_password.md +1 -0
  86. package/builtin/nodes/provide_str.md +1 -0
  87. package/builtin/nodes/tool_display_share_link.md +1 -0
  88. package/builtin/nodes/tool_get_env.md +2 -0
  89. package/builtin/nodes/tool_git_checkout.md +1 -0
  90. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  92. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  93. package/builtin/nodes/tool_jenkins_build.md +2 -0
  94. package/builtin/nodes/tool_load_key.md +2 -0
  95. package/builtin/nodes/tool_nodejs.md +17 -19
  96. package/builtin/nodes/tool_print.md +2 -0
  97. package/builtin/nodes/tool_save_key.md +2 -0
  98. package/builtin/nodes/tool_set_run_env.md +1 -0
  99. package/builtin/nodes/tool_user_ask.md +2 -0
  100. package/builtin/nodes/tool_user_check.md +2 -0
  101. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  102. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  103. package/builtin/nodes/workspace_one_click_task.md +44 -0
  104. package/builtin/nodes/workspace_run.md +16 -0
  105. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  106. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  107. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  108. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  109. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  110. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  111. package/builtin/pipelines/new/workspace.flow.js +91 -0
  112. package/builtin/pipelines/new/workspace.layout.json +70 -0
  113. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
  114. package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +870 -0
  115. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  116. package/builtin/web-ui/dist/index.html +2 -2
  117. package/package.json +2 -1
  118. package/reference/flow-control-capabilities.md +77 -158
  119. package/reference/flow-layout.md +1 -1
  120. package/reference/flow-prompt-handler-check.md +2 -2
  121. package/skills/agentflow-author-flow/SKILL.md +1 -1
  122. package/skills/agentflow-cli/SKILL.md +139 -24
  123. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  124. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  125. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  126. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  127. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  128. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  129. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  130. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  131. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  132. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  133. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  134. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  135. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  136. package/skills/agentflow-node-reference/SKILL.md +2 -2
  137. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  138. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  139. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  140. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  141. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  142. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  143. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  144. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  145. package/bin/lib/api-runner.mjs +0 -387
  146. package/bin/lib/apply.mjs +0 -903
  147. package/bin/lib/composer-flow-instances.mjs +0 -68
  148. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  149. package/bin/lib/composer-flow-validate.mjs +0 -47
  150. package/bin/lib/composer-model-router.mjs +0 -185
  151. package/bin/lib/composer-node-schema.mjs +0 -303
  152. package/bin/lib/composer-planner.mjs +0 -751
  153. package/bin/lib/composer-script-ops.mjs +0 -233
  154. package/bin/lib/flow-static-preview.mjs +0 -104
  155. package/bin/lib/hub-login.mjs +0 -54
  156. package/bin/lib/hub-publish.mjs +0 -159
  157. package/bin/lib/hub-remote.mjs +0 -189
  158. package/bin/lib/hub.mjs +0 -299
  159. package/bin/lib/jenkins.mjs +0 -380
  160. package/bin/lib/node-execute.mjs +0 -539
  161. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  162. package/bin/lib/runtime-context.mjs +0 -243
  163. package/bin/lib/scheduler.mjs +0 -601
  164. package/bin/lib/ui-print.mjs +0 -94
  165. package/bin/pipeline/build-node-prompt.mjs +0 -271
  166. package/bin/pipeline/check-cache.mjs +0 -191
  167. package/bin/pipeline/check-flow.mjs +0 -543
  168. package/bin/pipeline/collect-nodes.mjs +0 -212
  169. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  170. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  171. package/bin/pipeline/gc.mjs +0 -129
  172. package/bin/pipeline/get-env.mjs +0 -59
  173. package/bin/pipeline/get-resolved-values.mjs +0 -344
  174. package/bin/pipeline/load-key.mjs +0 -62
  175. package/bin/pipeline/parse-flow.mjs +0 -708
  176. package/bin/pipeline/post-process-control-if.mjs +0 -23
  177. package/bin/pipeline/post-process-node.mjs +0 -490
  178. package/bin/pipeline/pre-process-node.mjs +0 -1430
  179. package/bin/pipeline/resolve-inputs.mjs +0 -201
  180. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  181. package/bin/pipeline/save-key.mjs +0 -93
  182. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  183. package/bin/pipeline/validate-for-ui.mjs +0 -234
  184. package/bin/pipeline/validate-script-output.mjs +0 -130
  185. package/bin/pipeline/write-result.mjs +0 -182
  186. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  187. package/builtin/pipelines/new/flow.yaml +0 -545
  188. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  189. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  190. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  191. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  192. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -1,68 +0,0 @@
1
- /**
2
- * 从 flow.yaml 解析实例的 role / model,供 Composer 多步规划与路由使用。
3
- */
4
- import yaml from "js-yaml";
5
- import { translateRole, normalizeRoleToKey } from "./i18n.mjs";
6
-
7
- const VALID_ROLES = ["requirement", "planning", "code", "test", "normal"];
8
-
9
- /**
10
- * @param {string} [flowYaml]
11
- * @returns {Record<string, { role: string, model?: string, label: string, definitionId?: string, hasScript?: boolean }>}
12
- */
13
- export function parseInstanceRoleModelMap(flowYaml) {
14
- const map = {};
15
- if (!flowYaml || typeof flowYaml !== "string" || !flowYaml.trim()) return map;
16
- try {
17
- const raw = yaml.load(flowYaml);
18
- const instances = raw?.instances && typeof raw.instances === "object" ? raw.instances : {};
19
- for (const [id, inst] of Object.entries(instances)) {
20
- if (!inst || typeof inst !== "object") continue;
21
- const rawRole = inst.role != null ? String(inst.role).trim() : "";
22
- const normalizedRole = normalizeRoleToKey(rawRole);
23
- const role = VALID_ROLES.includes(normalizedRole) ? normalizedRole : "normal";
24
- let model = inst.model != null ? String(inst.model).trim() : "";
25
- if (model === "default") model = "";
26
- const label = inst.label != null ? String(inst.label) : id;
27
- const definitionId = inst.definitionId != null ? String(inst.definitionId).trim() : undefined;
28
- const hasScript = definitionId === "tool_nodejs" && inst.script != null && String(inst.script).trim() !== "";
29
- map[id] = { role, model: model || undefined, label, definitionId, hasScript };
30
- }
31
- } catch {
32
- /* ignore */
33
- }
34
- return map;
35
- }
36
-
37
- /**
38
- * 供规划器 user 消息附加:明确实例上的角色、模型与节点类型,便于输出带 instanceId / nodeRole 的步骤。
39
- * @param {string} [flowYaml]
40
- * @returns {string}
41
- */
42
- export function formatInstancePlannerHint(flowYaml) {
43
- const map = parseInstanceRoleModelMap(flowYaml);
44
- const entries = Object.entries(map);
45
- if (entries.length === 0) return "";
46
-
47
- const nodejsMissing = [];
48
- const lines = entries.map(([id, v]) => {
49
- const m = v.model ? ` · 模型 \`${v.model}\`` : "";
50
- const defHint = v.definitionId ? ` · \`${v.definitionId}\`` : "";
51
- const scriptHint = v.definitionId === "tool_nodejs" && !v.hasScript ? " ⚠️ **缺 script**" : "";
52
- if (v.definitionId === "tool_nodejs" && !v.hasScript) nodejsMissing.push(id);
53
- return `- \`${id}\`(${v.label}):角色 **${translateRole(v.role)}**${defHint}${m}${scriptHint}`;
54
- });
55
-
56
- let warn = "";
57
- if (nodejsMissing.length > 0) {
58
- warn = `\n\n⚠️ **以下 tool_nodejs 节点缺少 script 字段**(无 script 则节点无法执行):${nodejsMissing.map(id => `\`${id}\``).join("、")}。` +
59
- "必须为它们写入可执行的 `script`(shell/node 命令),或改用 `agent_subAgent`。`body` 不会被执行。";
60
- }
61
-
62
- return (
63
- "\n## 画布实例(类型 / 角色 / 模型)\n" +
64
- "agent 步骤请尽量填写 `instanceId`(本步主要操作的实例),`nodeRole` 应与该实例的 role 一致;可选用 `executorModel` 覆盖执行模型(否则使用该实例在 YAML 中的 model,再否则用用户全局模型)。\n" +
65
- lines.join("\n") +
66
- warn
67
- );
68
- }
@@ -1,334 +0,0 @@
1
- /**
2
- * Composer 阶段一 skeleton 生成器:在 AI 真正执行前,由脚本写入:
3
- * 1. flow.yaml — 含 control_start + control_end + 一条主链 edge + nodePositions
4
- * 2. composer-node-spec.md — 含三段 section 模板(整体框架/节点职责/计划数据槽)
5
- *
6
- * 设计原则:
7
- * - **幂等**:只有当目标文件不存在 / 完全空 / instances 为空时才写入。
8
- * 如已有内容,**不动**(避免覆盖用户工作)。
9
- * - **最小骨架**:让 AI 只做"插入节点 + 调整边"而非"从零写 YAML"。
10
- * - **正确 YAML**:start/end 槽位结构、type、name 严格对齐 schema。
11
- */
12
- import fs from "fs";
13
- import path from "path";
14
- import yaml from "js-yaml";
15
-
16
- /**
17
- * 写入 flow.yaml skeleton(如已有非空 instances 则跳过)。
18
- * @param {string} flowYamlAbs flow.yaml 绝对路径
19
- * @returns {{ created: boolean, reason: string }}
20
- */
21
- export function ensureFlowSkeleton(flowYamlAbs) {
22
- if (!flowYamlAbs) return { created: false, reason: "missing flowYamlAbs" };
23
-
24
- const exists = fs.existsSync(flowYamlAbs);
25
- if (exists) {
26
- try {
27
- const raw = fs.readFileSync(flowYamlAbs, "utf-8");
28
- const data = yaml.load(raw);
29
- const instCount = data && data.instances && typeof data.instances === "object"
30
- ? Object.keys(data.instances).length
31
- : 0;
32
- if (instCount > 0) {
33
- return { created: false, reason: `flow.yaml already has ${instCount} instances` };
34
- }
35
- } catch {
36
- /* 解析失败也覆盖(视为坏文件) */
37
- }
38
- }
39
-
40
- fs.mkdirSync(path.dirname(flowYamlAbs), { recursive: true });
41
-
42
- const skeleton = {
43
- instances: {
44
- control_start: {
45
- definitionId: "control_start",
46
- label: "Start",
47
- input: [],
48
- output: [
49
- { type: "node", name: "next", value: "" },
50
- ],
51
- body: "流程入口",
52
- },
53
- control_end: {
54
- definitionId: "control_end",
55
- label: "End",
56
- input: [
57
- { type: "node", name: "prev", value: "" },
58
- ],
59
- output: [],
60
- body: "流程出口",
61
- },
62
- },
63
- edges: [
64
- {
65
- source: "control_start",
66
- target: "control_end",
67
- sourceHandle: "output-0",
68
- targetHandle: "input-0",
69
- },
70
- ],
71
- ui: {
72
- nodePositions: {
73
- control_start: { x: 100, y: 300 },
74
- control_end: { x: 380, y: 300 },
75
- },
76
- },
77
- };
78
-
79
- const text = yaml.dump(skeleton, { lineWidth: -1, noRefs: true });
80
- fs.writeFileSync(flowYamlAbs, text, "utf-8");
81
- return { created: true, reason: "wrote start+end skeleton" };
82
- }
83
-
84
- /**
85
- * 写入 composer-node-spec.md 模板(如已存在则跳过)。
86
- * @param {string} specMdAbs
87
- * @param {{ flowId?: string, userRequest?: string }} [meta]
88
- * @returns {{ created: boolean, reason: string }}
89
- */
90
- export function ensureNodeSpecSkeleton(specMdAbs, meta = {}) {
91
- if (!specMdAbs) return { created: false, reason: "missing specMdAbs" };
92
-
93
- if (fs.existsSync(specMdAbs)) {
94
- try {
95
- const raw = fs.readFileSync(specMdAbs, "utf-8").trim();
96
- if (raw.length > 0) {
97
- return { created: false, reason: "spec.md already exists" };
98
- }
99
- } catch { /* fallthrough */ }
100
- }
101
-
102
- fs.mkdirSync(path.dirname(specMdAbs), { recursive: true });
103
-
104
- const flowId = meta.flowId || "(unknown)";
105
- const userRequest = (meta.userRequest || "").trim() || "(未提供)";
106
- const ts = new Date().toISOString();
107
-
108
- const tmpl = `# composer-node-spec — ${flowId}
109
-
110
- > 生成时间:${ts}
111
- > 用户原始需求:
112
- ${userRequest.split("\n").map((l) => "> " + l).join("\n")}
113
-
114
- ## 整体框架
115
-
116
- <!--
117
- AI 在此填写整体设计意图:
118
- - 主链节点序列(Start → ... → End)
119
- - 分支结构(control_if 的真假分支去向)
120
- - 循环结构(control_anyOne + control_toBool + control_if 的环路)
121
- - 并行结构(如有)
122
- - 全局数据(SaveKey / LoadKey 跨节点共享的 key)
123
- -->
124
-
125
- ## 节点职责
126
-
127
- <!--
128
- 按 instanceId 分小节,**每节点必须写齐 4 项**:
129
- **职责**:一句话说该节点做什么
130
- **输入**:列出每个 input 槽 → 语义 + 来源(上游 instanceId.slot)
131
- **输出**:列出每个 output 槽 → 语义 + 去向(下游 instanceId.slot)
132
- **实现要点**:tool_nodejs 写脚本路径(scripts/xxx.mjs)、agent_subAgent 写 body 关键点、control_* 写判定/汇合规则、provide_* 写固定值
133
- 说明:
134
- - **输入/输出**就是阶段三连线的依据——把每个槽位的「上游来源」和「下游去向」写清,连边 sourceHandle/targetHandle 就一目了然
135
- - 控制流槽(prev/next)也要写,标「控制流:来自 <upstream>.next」即可
136
- - 业务数据槽(text/file)必须写清语义,让阶段二节点补充时知道 body/script 该用什么 \${slot} 占位符
137
- - tool_nodejs 的可执行代码留到阶段二写,本阶段只需脚本路径与 I/O 语义
138
- -->
139
-
140
- ### control_start
141
- **职责**:流程入口
142
- **输入**:—
143
- **输出**:next:node → 给 <下一个节点>.prev(控制流,启动主链)
144
-
145
- ### control_end
146
- **职责**:流程出口
147
- **输入**:prev:node ← 来自 <最后一个节点>.next(控制流)
148
- **输出**:—
149
-
150
- <!-- 在此后追加每个新节点的小节 -->
151
-
152
- ## 计划数据槽(仅 ★ 可扩展节点需追加的业务槽)
153
-
154
- <!--
155
- 本 section 是**完整业务数据槽设计**——是 spec.md 的权威记录与阶段二/三对账依据。
156
- 仅对 ★ 可扩展节点(agent_subAgent / tool_nodejs / tool_user_check)列出需要在 input/output 数组**末尾追加**的业务数据槽。
157
- 固定槽位节点(control_*、provide_*、tool_load_key、tool_save_key、tool_get_env、tool_print、tool_user_ask)槽位结构由 schema 锁死,**不在此列出**。
158
-
159
- **重要:阶段一 AI 应该同时把这些槽落到 flow.yaml**(鼓励一次写到位)。
160
- - 已落到 yaml → 阶段二按 name 去重跳过,不影响幂等
161
- - 未落到 yaml → 阶段二脚本/agent 据此 section 补齐
162
-
163
- 格式(机器可解析):
164
- <instanceId>:
165
- input += [<name>:<text|file>, ...]
166
- output += [<name>:<text|file>, ...]
167
- type 仅可用 \`text\`(短串/路径/JSON)或 \`file\`(大块产物路径),**禁止 node**。
168
- 命名建议与上下游语义对齐(如下游 \`fromapp\` 的入参,上游就用同名 output)便于连线。
169
- 示例:
170
- agent_analyze:
171
- input += [fromapp:text, page_name:text]
172
- output += [analysis:file]
173
- agent_convert:
174
- input += [analysis:file, toapp:text]
175
- output += [converted_code:file]
176
- agent_check_compile:
177
- input += [converted_code:file]
178
- output += [compile_result:text]
179
- -->
180
- `;
181
-
182
- fs.writeFileSync(specMdAbs, tmpl, "utf-8");
183
- return { created: true, reason: "wrote spec.md template" };
184
- }
185
-
186
- /**
187
- * 阶段一前置:同时确保 flow.yaml + spec.md 两份骨架。
188
- * @param {{ flowYamlAbs: string, composerSpecAbs?: string, flowId?: string, userRequest?: string }} opts
189
- * @returns {{ flow: ReturnType<typeof ensureFlowSkeleton>, spec: ReturnType<typeof ensureNodeSpecSkeleton> }}
190
- */
191
- export function ensurePhase1Skeletons(opts) {
192
- const flow = ensureFlowSkeleton(opts.flowYamlAbs);
193
- const spec = opts.composerSpecAbs
194
- ? ensureNodeSpecSkeleton(opts.composerSpecAbs, { flowId: opts.flowId, userRequest: opts.userRequest })
195
- : { created: false, reason: "missing composerSpecAbs" };
196
- return { flow, spec };
197
- }
198
-
199
- // ─── 解析 spec.md「计划数据槽」+ 合并到 flow.yaml ──────────────────────────
200
-
201
- const VALID_BUSINESS_TYPES = new Set(["text", "file"]);
202
-
203
- /**
204
- * 从 spec.md 文本中解析「计划数据槽」section。
205
- * 期望格式:
206
- * ## 计划数据槽(...)
207
- * <instanceId>:
208
- * input += [<name>:<type>, <name>:<type>]
209
- * output += [<name>:<type>]
210
- * @param {string} specText
211
- * @returns {Record<string, { input: Array<{name,type}>, output: Array<{name,type}> }>}
212
- */
213
- export function parsePlannedSlotsFromSpec(specText) {
214
- if (!specText || typeof specText !== "string") return {};
215
- const idx = specText.search(/^##\s+计划数据槽/m);
216
- if (idx < 0) return {};
217
- const section = specText.slice(idx);
218
- // 截到下一个 ## 或 EOF
219
- const nextH = section.slice(2).search(/^##\s/m);
220
- const body = nextH >= 0 ? section.slice(0, nextH + 2) : section;
221
-
222
- const result = {};
223
- // 按 instanceId 块分割:行首非缩进的 "<id>:" 起一个块
224
- const lines = body.split("\n");
225
- let currentId = null;
226
- for (const rawLine of lines) {
227
- const line = rawLine.replace(/\r$/, "");
228
- // 跳过 HTML 注释、标题、空行
229
- if (/^\s*$/.test(line)) continue;
230
- if (/^<!--|-->\s*$|^##\s/.test(line)) continue;
231
-
232
- const idMatch = /^\s{0,2}([A-Za-z][A-Za-z0-9_]*)\s*:\s*$/.exec(line);
233
- if (idMatch) {
234
- currentId = idMatch[1];
235
- if (!result[currentId]) result[currentId] = { input: [], output: [] };
236
- continue;
237
- }
238
- // input += [...] / output += [...]
239
- const slotMatch = /^\s*(input|output)\s*\+?=\s*\[([^\]]*)\]\s*$/.exec(line);
240
- if (slotMatch && currentId) {
241
- const kind = slotMatch[1];
242
- const items = slotMatch[2].split(",").map((s) => s.trim()).filter(Boolean);
243
- for (const item of items) {
244
- const m = /^([A-Za-z][A-Za-z0-9_]*)\s*:\s*([a-z]+)$/.exec(item);
245
- if (!m) continue;
246
- const name = m[1];
247
- const type = m[2];
248
- if (!VALID_BUSINESS_TYPES.has(type)) continue;
249
- result[currentId][kind].push({ name, type });
250
- }
251
- }
252
- }
253
- return result;
254
- }
255
-
256
- /**
257
- * 把 spec.md 解析出的业务槽合并到 flow.yaml(幂等:同 name 跳过)。
258
- * 仅对 ★ 可扩展节点(agent_subAgent / tool_nodejs / tool_user_check)操作;
259
- * 其它 definitionId 上的 planned 数据**忽略**(属于 spec.md 误写,不要破坏固定槽位)。
260
- *
261
- * @param {string} flowYamlAbs
262
- * @param {string} specMdAbs
263
- * @returns {{ ok: boolean, applied: Array<{instanceId, addedInputs: string[], addedOutputs: string[]}>, skipped: Array<{instanceId, reason: string}>, error?: string }}
264
- */
265
- export function applyPlannedSlotsFromSpec(flowYamlAbs, specMdAbs) {
266
- if (!flowYamlAbs || !fs.existsSync(flowYamlAbs)) return { ok: false, applied: [], skipped: [], error: "flow.yaml not found" };
267
- if (!specMdAbs || !fs.existsSync(specMdAbs)) return { ok: false, applied: [], skipped: [], error: "spec.md not found" };
268
-
269
- const EXTENSIBLE = new Set(["agent_subAgent", "tool_nodejs", "tool_user_check"]);
270
- const applied = [];
271
- const skipped = [];
272
-
273
- let flow;
274
- try {
275
- flow = yaml.load(fs.readFileSync(flowYamlAbs, "utf-8"));
276
- } catch (e) {
277
- return { ok: false, applied: [], skipped: [], error: `failed to parse flow.yaml: ${e.message}` };
278
- }
279
- if (!flow || typeof flow !== "object" || !flow.instances) {
280
- return { ok: false, applied: [], skipped: [], error: "flow.yaml has no instances" };
281
- }
282
-
283
- let planned;
284
- try {
285
- const specText = fs.readFileSync(specMdAbs, "utf-8");
286
- planned = parsePlannedSlotsFromSpec(specText);
287
- } catch (e) {
288
- return { ok: false, applied: [], skipped: [], error: `failed to parse spec.md: ${e.message}` };
289
- }
290
-
291
- let mutated = false;
292
- for (const [instanceId, plan] of Object.entries(planned)) {
293
- const inst = flow.instances[instanceId];
294
- if (!inst || typeof inst !== "object") {
295
- skipped.push({ instanceId, reason: "instance not in flow.yaml" });
296
- continue;
297
- }
298
- if (!EXTENSIBLE.has(inst.definitionId)) {
299
- skipped.push({ instanceId, reason: `non-extensible definitionId: ${inst.definitionId}` });
300
- continue;
301
- }
302
- if (!Array.isArray(inst.input)) inst.input = [];
303
- if (!Array.isArray(inst.output)) inst.output = [];
304
-
305
- const existingInputNames = new Set(inst.input.map((s) => s && s.name).filter(Boolean));
306
- const existingOutputNames = new Set(inst.output.map((s) => s && s.name).filter(Boolean));
307
- const addedInputs = [];
308
- const addedOutputs = [];
309
-
310
- for (const { name, type } of plan.input) {
311
- if (existingInputNames.has(name)) continue;
312
- inst.input.push({ type, name, value: "" });
313
- existingInputNames.add(name);
314
- addedInputs.push(`${name}:${type}`);
315
- mutated = true;
316
- }
317
- for (const { name, type } of plan.output) {
318
- if (existingOutputNames.has(name)) continue;
319
- inst.output.push({ type, name, value: "" });
320
- existingOutputNames.add(name);
321
- addedOutputs.push(`${name}:${type}`);
322
- mutated = true;
323
- }
324
-
325
- if (addedInputs.length > 0 || addedOutputs.length > 0) {
326
- applied.push({ instanceId, addedInputs, addedOutputs });
327
- }
328
- }
329
-
330
- if (mutated) {
331
- fs.writeFileSync(flowYamlAbs, yaml.dump(flow, { lineWidth: -1, noRefs: true }), "utf-8");
332
- }
333
- return { ok: true, applied, skipped };
334
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * Web UI Composer:对当前 flow.yaml 调用与 CLI 一致的 validate-flow 逻辑。
3
- * agent 有时会把 flow.yaml 写到 workspace 路径而非 user 路径(或反之),
4
- * 当原路径找不到时,尝试在 workspace 下的 .workspace/agentflow/pipelines/ 和
5
- * agentflow/pipelines/ 中查找同名目录。
6
- */
7
- import fs from "fs";
8
- import path from "path";
9
- import { runValidateFlow } from "../pipeline/validate-flow.mjs";
10
-
11
- /**
12
- * @param {string} flowYamlAbs
13
- * @param {string} workspaceRoot
14
- * @returns {{ ok: boolean, errors: string[], warnings: string[] }}
15
- */
16
- export function validateComposerFlowYaml(flowYamlAbs, workspaceRoot) {
17
- let flowDir = path.dirname(path.resolve(flowYamlAbs));
18
- const root = path.resolve(workspaceRoot);
19
-
20
- if (!fs.existsSync(path.join(flowDir, "flow.yaml"))) {
21
- const flowId = path.basename(flowDir);
22
- const candidates = [
23
- path.join(root, ".workspace", "agentflow", "pipelines", flowId),
24
- path.join(root, "agentflow", "pipelines", flowId),
25
- path.join(root, ".cursor", "agentflow", "pipelines", flowId),
26
- ];
27
- for (const c of candidates) {
28
- if (fs.existsSync(path.join(c, "flow.yaml"))) {
29
- flowDir = c;
30
- break;
31
- }
32
- }
33
- }
34
-
35
- return runValidateFlow(flowDir, root);
36
- }
37
-
38
- /**
39
- * @param {{ ok?: boolean, errors?: string[] }} result
40
- * @returns {string}
41
- */
42
- export function formatValidationErrorsBlock(result) {
43
- if (!result) return "(校验无返回)";
44
- const errs = Array.isArray(result.errors) ? result.errors : [];
45
- if (errs.length === 0) return "(无 errors 字段,请检查 flow.yaml 结构)";
46
- return errs.map((e, i) => `${i + 1}. ${e}`).join("\n");
47
- }
@@ -1,185 +0,0 @@
1
- /**
2
- * Composer 模型路由器:基于 model-lists.json 按任务复杂度分配合适的模型。
3
- *
4
- * 模型分三个档次:
5
- * - fast — 简单/一般任务(改文案、添加节点、日常编辑)
6
- * - balanced — 中等任务(多步推理、较复杂设计)
7
- * - capable — 复杂任务(流程重构、深度分析)
8
- *
9
- * 路由策略:simple → fast,medium → fast,complex → capable
10
- * "auto" 条目不参与自动路由(auto 本身就是让 Cursor 自己选,不是明确的模型选择)。
11
- */
12
- import fs from "fs";
13
- import { getModelListsAbs } from "./paths.mjs";
14
- import { loadModelConfig, normalizeCursorModelForCli } from "./model-config.mjs";
15
-
16
- // ─── 模型分类规则 ──────────────────────────────────────────────────────────
17
-
18
- const FAST_PATTERNS = [
19
- /\bflash\b/i,
20
- /\bmini\b/i,
21
- /\bhaiku\b/i,
22
- /\blite\b/i,
23
- /\binstant\b/i,
24
- /\bfast\b/i,
25
- /\b4o-mini\b/i,
26
- /\bgpt-4\.1-mini\b/i,
27
- /\bgpt-4\.1-nano\b/i,
28
- /\bclaude-3\.5-haiku\b/i,
29
- /\bgemini.*flash\b/i,
30
- /\bdeepseek-v3\b/i,
31
- ];
32
-
33
- const CAPABLE_PATTERNS = [
34
- /\bopus\b/i,
35
- /\bo1\b/i,
36
- /\bo3\b/i,
37
- /\bo4-mini\b/i,
38
- /\bsonnet.*think/i,
39
- /\bthinking\b/i,
40
- /\bmax\b/i,
41
- /\bcodex\b/i,
42
- /\bgpt-5/i,
43
- /\bclaude-4/i,
44
- /\bdeepseek-r1\b/i,
45
- /\bgemini.*pro.*think/i,
46
- ];
47
-
48
- function classifyModel(modelId) {
49
- const s = String(modelId || "");
50
- for (const re of FAST_PATTERNS) {
51
- if (re.test(s)) return "fast";
52
- }
53
- for (const re of CAPABLE_PATTERNS) {
54
- if (re.test(s)) return "capable";
55
- }
56
- return "balanced";
57
- }
58
-
59
- // ─── 读取可用模型列表 ──────────────────────────────────────────────────────
60
-
61
- function loadModelLists() {
62
- try {
63
- const p = getModelListsAbs();
64
- if (!fs.existsSync(p)) return { cursor: [], opencode: [], claudeCode: [], codex: [] };
65
- const data = JSON.parse(fs.readFileSync(p, "utf-8"));
66
- return {
67
- cursor: Array.isArray(data.cursor) ? data.cursor.map(String) : [],
68
- opencode: Array.isArray(data.opencode) ? data.opencode.map(String) : [],
69
- claudeCode: Array.isArray(data.claudeCode) ? data.claudeCode.map(String) : [],
70
- codex: Array.isArray(data.codex) ? data.codex.map(String) : [],
71
- };
72
- } catch {
73
- return { cursor: [], opencode: [], claudeCode: [], codex: [] };
74
- }
75
- }
76
-
77
- function isAutoEntry(entry) {
78
- const id = String(entry || "").split(" - ")[0].trim().toLowerCase();
79
- return id === "auto" || id === "";
80
- }
81
-
82
- function buildModelTiers(modelList) {
83
- const tiers = { fast: [], balanced: [], capable: [] };
84
- for (const m of modelList) {
85
- if (isAutoEntry(m)) continue;
86
- const tier = classifyModel(m);
87
- tiers[tier].push(m);
88
- }
89
- return tiers;
90
- }
91
-
92
- function modelEntryId(entry) {
93
- const idx = String(entry || "").indexOf(" - ");
94
- return idx >= 0 ? entry.slice(0, idx).trim() : String(entry || "").trim();
95
- }
96
-
97
- function prefixedModels(models, prefix) {
98
- return (Array.isArray(models) ? models : [])
99
- .map((m) => modelEntryId(m))
100
- .filter(Boolean)
101
- .map((m) => `${prefix}:${m}`);
102
- }
103
-
104
- // ─── 公开接口 ──────────────────────────────────────────────────────────────
105
-
106
- /**
107
- * 根据任务复杂度从可用模型列表中选择合适的模型。
108
- *
109
- * @param {"simple" | "medium" | "complex"} complexity
110
- * @param {object} [opts]
111
- * @param {string} [opts.userPreferredModel] 用户在 Composer 下拉选择的模型(优先级最高)
112
- * @param {string} [opts.workspaceRoot] 工作区根目录(加载 models.json)
113
- * @returns {{ model: string | null, tier: string, source: string }}
114
- */
115
- export function routeModel(complexity, opts = {}) {
116
- if (opts.userPreferredModel && String(opts.userPreferredModel).trim()) {
117
- const m = String(opts.userPreferredModel).trim();
118
- return { model: m, tier: classifyModel(m), source: "user-selected" };
119
- }
120
-
121
- const lists = loadModelLists();
122
- const allModels = [
123
- ...lists.cursor,
124
- ...prefixedModels(lists.codex, "codex"),
125
- ...prefixedModels(lists.claudeCode, "claude-code"),
126
- ...prefixedModels(lists.opencode, "opencode"),
127
- ];
128
- if (allModels.length === 0) {
129
- return { model: null, tier: complexity === "complex" ? "capable" : complexity === "simple" ? "fast" : "balanced", source: "no-models-available" };
130
- }
131
-
132
- const tiers = buildModelTiers(allModels);
133
-
134
- // simple 和 medium 都优先用 fast 模型,complex 才升级到 capable
135
- const tierMap = {
136
- simple: "fast",
137
- medium: "fast",
138
- complex: "capable",
139
- };
140
- const targetTier = tierMap[complexity] || "fast";
141
-
142
- if (tiers[targetTier].length > 0) {
143
- return { model: tiers[targetTier][0], tier: targetTier, source: "auto-routed" };
144
- }
145
-
146
- // fallback 链:capable → balanced → fast → any
147
- if (targetTier === "capable") {
148
- if (tiers.balanced.length > 0) return { model: tiers.balanced[0], tier: "balanced", source: "auto-routed-fallback" };
149
- if (tiers.fast.length > 0) return { model: tiers.fast[0], tier: "fast", source: "auto-routed-fallback" };
150
- }
151
- if (targetTier === "fast") {
152
- if (tiers.balanced.length > 0) return { model: tiers.balanced[0], tier: "balanced", source: "auto-routed-fallback" };
153
- if (tiers.capable.length > 0) return { model: tiers.capable[0], tier: "capable", source: "auto-routed-fallback" };
154
- }
155
-
156
- const firstReal = allModels.find((m) => !isAutoEntry(m));
157
- if (firstReal) return { model: firstReal, tier: classifyModel(firstReal), source: "auto-routed-fallback" };
158
- return { model: allModels[0], tier: classifyModel(allModels[0]), source: "auto-routed-fallback" };
159
- }
160
-
161
- /**
162
- * 返回模型列表的分层信息(供 UI 或调试用)。
163
- */
164
- export function getModelTierInfo() {
165
- const lists = loadModelLists();
166
- const cursorTiers = buildModelTiers(lists.cursor);
167
- const opencodeTiers = buildModelTiers(lists.opencode);
168
- const claudeCodeTiers = buildModelTiers(lists.claudeCode);
169
- const codexTiers = buildModelTiers(lists.codex);
170
- return {
171
- cursor: cursorTiers,
172
- opencode: opencodeTiers,
173
- claudeCode: claudeCodeTiers,
174
- codex: codexTiers,
175
- totalCursor: lists.cursor.length,
176
- totalOpencode: lists.opencode.length,
177
- totalClaudeCode: lists.claudeCode.length,
178
- totalCodex: lists.codex.length,
179
- };
180
- }
181
-
182
- /**
183
- * 对单个模型 ID 进行分类。
184
- */
185
- export { classifyModel };