@fieldwangai/agentflow 0.1.25

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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/README.zh-CN.md +201 -0
  4. package/agents/agentflow-node-executor-code.md +32 -0
  5. package/agents/agentflow-node-executor-planning.md +32 -0
  6. package/agents/agentflow-node-executor-requirement.md +32 -0
  7. package/agents/agentflow-node-executor-test.md +32 -0
  8. package/agents/agentflow-node-executor-ui.md +32 -0
  9. package/agents/agentflow-node-executor.md +32 -0
  10. package/agents/agents.json +8 -0
  11. package/agents/en/agentflow-node-executor.md +32 -0
  12. package/agents/zh/agentflow-node-executor.md +32 -0
  13. package/bin/agentflow.mjs +52 -0
  14. package/bin/ensure-workspace-reference.mjs +35 -0
  15. package/bin/lib/agent-runners.mjs +1199 -0
  16. package/bin/lib/agents-path.mjs +61 -0
  17. package/bin/lib/api-runner.mjs +361 -0
  18. package/bin/lib/apply.mjs +852 -0
  19. package/bin/lib/catalog-agents.mjs +300 -0
  20. package/bin/lib/catalog-flows.mjs +532 -0
  21. package/bin/lib/composer-agent.mjs +884 -0
  22. package/bin/lib/composer-flow-instances.mjs +68 -0
  23. package/bin/lib/composer-flow-skeleton.mjs +334 -0
  24. package/bin/lib/composer-flow-validate.mjs +47 -0
  25. package/bin/lib/composer-log.mjs +197 -0
  26. package/bin/lib/composer-model-router.mjs +160 -0
  27. package/bin/lib/composer-node-schema.mjs +299 -0
  28. package/bin/lib/composer-planner.mjs +749 -0
  29. package/bin/lib/composer-script-ops.mjs +233 -0
  30. package/bin/lib/composer-skill-router.mjs +384 -0
  31. package/bin/lib/flow-import.mjs +305 -0
  32. package/bin/lib/flow-normalize.mjs +71 -0
  33. package/bin/lib/flow-write.mjs +395 -0
  34. package/bin/lib/help.mjs +139 -0
  35. package/bin/lib/hub-login.mjs +54 -0
  36. package/bin/lib/hub-publish.mjs +159 -0
  37. package/bin/lib/hub-remote.mjs +189 -0
  38. package/bin/lib/hub.mjs +299 -0
  39. package/bin/lib/i18n.mjs +233 -0
  40. package/bin/lib/locales/en.json +344 -0
  41. package/bin/lib/locales/zh.json +344 -0
  42. package/bin/lib/log.mjs +37 -0
  43. package/bin/lib/main.mjs +611 -0
  44. package/bin/lib/model-config.mjs +118 -0
  45. package/bin/lib/model-lists.mjs +188 -0
  46. package/bin/lib/node-exec-context.mjs +336 -0
  47. package/bin/lib/node-execute.mjs +513 -0
  48. package/bin/lib/normalize-node-tool-command.mjs +97 -0
  49. package/bin/lib/paths.mjs +216 -0
  50. package/bin/lib/pipeline-scripts.mjs +41 -0
  51. package/bin/lib/recent-runs.mjs +173 -0
  52. package/bin/lib/run-apply-active-lock.mjs +82 -0
  53. package/bin/lib/run-events.mjs +85 -0
  54. package/bin/lib/run-node-statuses-from-disk.mjs +85 -0
  55. package/bin/lib/schedule-config.mjs +227 -0
  56. package/bin/lib/scheduler.mjs +312 -0
  57. package/bin/lib/table.mjs +4 -0
  58. package/bin/lib/terminal.mjs +42 -0
  59. package/bin/lib/ui-print.mjs +94 -0
  60. package/bin/lib/ui-server.mjs +2113 -0
  61. package/bin/lib/workspace-tree.mjs +266 -0
  62. package/bin/lib/workspace.mjs +180 -0
  63. package/bin/pipeline/build-node-prompt.mjs +179 -0
  64. package/bin/pipeline/check-cache.mjs +191 -0
  65. package/bin/pipeline/check-flow.mjs +543 -0
  66. package/bin/pipeline/collect-nodes.mjs +212 -0
  67. package/bin/pipeline/compute-cache-md5.mjs +177 -0
  68. package/bin/pipeline/ensure-run-dir.mjs +71 -0
  69. package/bin/pipeline/extract-thinking.mjs +308 -0
  70. package/bin/pipeline/gc.mjs +129 -0
  71. package/bin/pipeline/get-env.mjs +83 -0
  72. package/bin/pipeline/get-exec-id.mjs +145 -0
  73. package/bin/pipeline/get-ready-nodes.mjs +435 -0
  74. package/bin/pipeline/get-resolved-values.mjs +337 -0
  75. package/bin/pipeline/load-key.mjs +62 -0
  76. package/bin/pipeline/parse-bool.mjs +33 -0
  77. package/bin/pipeline/parse-flow.mjs +698 -0
  78. package/bin/pipeline/post-process-control-if.mjs +23 -0
  79. package/bin/pipeline/post-process-node.mjs +490 -0
  80. package/bin/pipeline/pre-process-node.mjs +449 -0
  81. package/bin/pipeline/resolve-inputs.mjs +201 -0
  82. package/bin/pipeline/run-log.mjs +34 -0
  83. package/bin/pipeline/run-tool-nodejs.mjs +160 -0
  84. package/bin/pipeline/save-key.mjs +93 -0
  85. package/bin/pipeline/snapshot-prior-round.mjs +70 -0
  86. package/bin/pipeline/validate-flow.mjs +825 -0
  87. package/bin/pipeline/validate-for-ui.mjs +226 -0
  88. package/bin/pipeline/validate-script-output.mjs +130 -0
  89. package/bin/pipeline/write-result.mjs +182 -0
  90. package/builtin/nodes/agent_subAgent.md +14 -0
  91. package/builtin/nodes/control_agent_toBool.md +20 -0
  92. package/builtin/nodes/control_anyOne.md +17 -0
  93. package/builtin/nodes/control_end.md +11 -0
  94. package/builtin/nodes/control_if.md +20 -0
  95. package/builtin/nodes/control_start.md +11 -0
  96. package/builtin/nodes/control_toBool.md +21 -0
  97. package/builtin/nodes/provide_file.md +11 -0
  98. package/builtin/nodes/provide_str.md +11 -0
  99. package/builtin/nodes/tool_get_env.md +14 -0
  100. package/builtin/nodes/tool_load_key.md +20 -0
  101. package/builtin/nodes/tool_nodejs.md +40 -0
  102. package/builtin/nodes/tool_print.md +14 -0
  103. package/builtin/nodes/tool_save_key.md +20 -0
  104. package/builtin/nodes/tool_user_ask.md +23 -0
  105. package/builtin/nodes/tool_user_check.md +22 -0
  106. package/builtin/pipelines/module-migrate/flow.yaml +819 -0
  107. package/builtin/pipelines/module-migrate/scripts/check_imports.mjs +700 -0
  108. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Makefile +362 -0
  109. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
  110. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o.d +17 -0
  111. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o.d +5 -0
  112. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o.d +8 -0
  113. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/tree_sitter_kotlin_binding.node.d +1 -0
  114. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  115. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o +0 -0
  116. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o +0 -0
  117. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o +0 -0
  118. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/tree_sitter_kotlin_binding.node +0 -0
  119. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/binding.Makefile +6 -0
  120. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/gyp-mac-tool +768 -0
  121. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
  122. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
  123. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_except.target.mk +126 -0
  124. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_maybe.target.mk +122 -0
  125. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/tree_sitter_kotlin_binding.target.mk +203 -0
  126. package/builtin/pipelines/new/flow.yaml +545 -0
  127. package/builtin/pipelines/new/scripts/check-flow.mjs +9 -0
  128. package/builtin/pipelines/new/scripts/collect-nodes.mjs +211 -0
  129. package/builtin/pipelines/scripts/adjust-node-positions.mjs +113 -0
  130. package/builtin/web-ui/dist/agentflow-icon.svg +23 -0
  131. package/builtin/web-ui/dist/assets/index-CZkUPcXE.css +1 -0
  132. package/builtin/web-ui/dist/assets/index-DkkhNESc.js +190 -0
  133. package/builtin/web-ui/dist/index.html +24 -0
  134. package/package.json +67 -0
  135. package/reference/flow-control-capabilities.md +274 -0
  136. package/reference/flow-layout.md +84 -0
  137. package/reference/flow-prompt-handler-check.md +12 -0
  138. package/reference/flow-result-semantics.md +14 -0
@@ -0,0 +1,160 @@
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: [] };
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
+ };
70
+ } catch {
71
+ return { cursor: [], opencode: [] };
72
+ }
73
+ }
74
+
75
+ function isAutoEntry(entry) {
76
+ const id = String(entry || "").split(" - ")[0].trim().toLowerCase();
77
+ return id === "auto" || id === "";
78
+ }
79
+
80
+ function buildModelTiers(modelList) {
81
+ const tiers = { fast: [], balanced: [], capable: [] };
82
+ for (const m of modelList) {
83
+ if (isAutoEntry(m)) continue;
84
+ const tier = classifyModel(m);
85
+ tiers[tier].push(m);
86
+ }
87
+ return tiers;
88
+ }
89
+
90
+ // ─── 公开接口 ──────────────────────────────────────────────────────────────
91
+
92
+ /**
93
+ * 根据任务复杂度从可用模型列表中选择合适的模型。
94
+ *
95
+ * @param {"simple" | "medium" | "complex"} complexity
96
+ * @param {object} [opts]
97
+ * @param {string} [opts.userPreferredModel] 用户在 Composer 下拉选择的模型(优先级最高)
98
+ * @param {string} [opts.workspaceRoot] 工作区根目录(加载 models.json)
99
+ * @returns {{ model: string | null, tier: string, source: string }}
100
+ */
101
+ export function routeModel(complexity, opts = {}) {
102
+ if (opts.userPreferredModel && String(opts.userPreferredModel).trim()) {
103
+ const m = String(opts.userPreferredModel).trim();
104
+ return { model: m, tier: classifyModel(m), source: "user-selected" };
105
+ }
106
+
107
+ const lists = loadModelLists();
108
+ const allModels = [...lists.cursor];
109
+ if (allModels.length === 0) {
110
+ return { model: null, tier: complexity === "complex" ? "capable" : complexity === "simple" ? "fast" : "balanced", source: "no-models-available" };
111
+ }
112
+
113
+ const tiers = buildModelTiers(allModels);
114
+
115
+ // simple 和 medium 都优先用 fast 模型,complex 才升级到 capable
116
+ const tierMap = {
117
+ simple: "fast",
118
+ medium: "fast",
119
+ complex: "capable",
120
+ };
121
+ const targetTier = tierMap[complexity] || "fast";
122
+
123
+ if (tiers[targetTier].length > 0) {
124
+ return { model: tiers[targetTier][0], tier: targetTier, source: "auto-routed" };
125
+ }
126
+
127
+ // fallback 链:capable → balanced → fast → any
128
+ if (targetTier === "capable") {
129
+ if (tiers.balanced.length > 0) return { model: tiers.balanced[0], tier: "balanced", source: "auto-routed-fallback" };
130
+ if (tiers.fast.length > 0) return { model: tiers.fast[0], tier: "fast", source: "auto-routed-fallback" };
131
+ }
132
+ if (targetTier === "fast") {
133
+ if (tiers.balanced.length > 0) return { model: tiers.balanced[0], tier: "balanced", source: "auto-routed-fallback" };
134
+ if (tiers.capable.length > 0) return { model: tiers.capable[0], tier: "capable", source: "auto-routed-fallback" };
135
+ }
136
+
137
+ const firstReal = allModels.find((m) => !isAutoEntry(m));
138
+ if (firstReal) return { model: firstReal, tier: classifyModel(firstReal), source: "auto-routed-fallback" };
139
+ return { model: allModels[0], tier: classifyModel(allModels[0]), source: "auto-routed-fallback" };
140
+ }
141
+
142
+ /**
143
+ * 返回模型列表的分层信息(供 UI 或调试用)。
144
+ */
145
+ export function getModelTierInfo() {
146
+ const lists = loadModelLists();
147
+ const cursorTiers = buildModelTiers(lists.cursor);
148
+ const opencodeTiers = buildModelTiers(lists.opencode);
149
+ return {
150
+ cursor: cursorTiers,
151
+ opencode: opencodeTiers,
152
+ totalCursor: lists.cursor.length,
153
+ totalOpencode: lists.opencode.length,
154
+ };
155
+ }
156
+
157
+ /**
158
+ * 对单个模型 ID 进行分类。
159
+ */
160
+ export { classifyModel };
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Composer 节点 schema 加载器:读取 builtin/nodes/*.md frontmatter,
3
+ * 输出 definitionId → input/output 槽位的紧凑 schema 表,
4
+ * 注入 composer 规划器/子 agent 的 system prompt,作为 ground truth。
5
+ *
6
+ * 目的:避免 agent 凭记忆造槽位 type/name/顺序(常见错误:provide_str output 写成 type=node、
7
+ * 槽位中英混乱、默认值漂移)。
8
+ */
9
+
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import yaml from "js-yaml";
13
+
14
+ import { PACKAGE_BUILTIN_NODES_DIR } from "./paths.mjs";
15
+
16
+ /**
17
+ * 可扩展节点:允许在基础控制槽(prev/next)之外**追加** type=text|file 的业务数据槽。
18
+ * 其它节点(control_*、provide_*、tool_load_key、tool_save_key、tool_get_env、tool_print、tool_user_ask)
19
+ * 必须严格保持 schema 默认槽位结构。
20
+ */
21
+ export const EXTENSIBLE_DEFINITIONS = new Set([
22
+ "agent_subAgent",
23
+ "tool_nodejs",
24
+ "tool_user_check",
25
+ "control_toBool",
26
+ ]);
27
+
28
+ /**
29
+ * 合法 role 值(与 bin/pipeline/validate-flow.mjs:VALID_ROLES 保持同步)。
30
+ * 推荐使用英文 key;中文别名仅为兼容旧画布。
31
+ */
32
+ export const VALID_ROLE_VALUES = [
33
+ "requirement", "planning", "code", "test", "normal",
34
+ "需求拆解", "技术规划", "代码执行", "测试回归", "普通",
35
+ "前端/UI",
36
+ "agentflow-node-executor-requirement",
37
+ "agentflow-node-executor-planning",
38
+ "agentflow-node-executor-code",
39
+ "agentflow-node-executor-test",
40
+ "agentflow-node-executor-ui",
41
+ "agentflow-node-executor",
42
+ ];
43
+
44
+ let cachedTable = null;
45
+ let cachedSummary = null;
46
+ let cachedCompact = null;
47
+
48
+ /** 解析单个节点 .md 的 frontmatter,返回 { input:[{name,type}], output:[{name,type}], description } */
49
+ function parseNodeFrontmatter(filePath) {
50
+ try {
51
+ const raw = fs.readFileSync(filePath, "utf-8");
52
+ const m = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
53
+ if (!m) return null;
54
+ const fm = yaml.load(m[1]);
55
+ if (!fm || typeof fm !== "object") return null;
56
+ const toSlots = (arr) =>
57
+ Array.isArray(arr)
58
+ ? arr.map((s) => ({
59
+ name: s && s.name != null ? String(s.name) : "",
60
+ type: s && s.type != null ? String(s.type) : "",
61
+ }))
62
+ : [];
63
+ return {
64
+ description: fm.displayName || fm.description || "",
65
+ input: toSlots(fm.input),
66
+ output: toSlots(fm.output),
67
+ };
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 读取所有内置节点定义并缓存。
75
+ * @returns {Record<string, {input:Array<{name,type}>, output:Array<{name,type}>, description:string}>}
76
+ */
77
+ export function getBuiltinNodeSchemas() {
78
+ if (cachedTable) return cachedTable;
79
+ const table = {};
80
+ if (!fs.existsSync(PACKAGE_BUILTIN_NODES_DIR)) {
81
+ cachedTable = table;
82
+ return table;
83
+ }
84
+ const files = fs.readdirSync(PACKAGE_BUILTIN_NODES_DIR).filter((f) => f.endsWith(".md"));
85
+ for (const f of files) {
86
+ const definitionId = f.replace(/\.md$/, "");
87
+ const parsed = parseNodeFrontmatter(path.join(PACKAGE_BUILTIN_NODES_DIR, f));
88
+ if (parsed) table[definitionId] = parsed;
89
+ }
90
+ cachedTable = table;
91
+ return table;
92
+ }
93
+
94
+ /** 把单条 schema 压成一行:`tool_nodejs ★ in: prev:node out: next:node, result:text`(★ 标记可扩展) */
95
+ function formatSchemaLine(definitionId, def) {
96
+ const fmt = (slot) => `${slot.name || "_"}:${slot.type || "?"}`;
97
+ const inStr = def.input.length > 0 ? def.input.map(fmt).join(", ") : "—";
98
+ const outStr = def.output.length > 0 ? def.output.map(fmt).join(", ") : "—";
99
+ const mark = EXTENSIBLE_DEFINITIONS.has(definitionId) ? " ★" : "";
100
+ return `- ${definitionId}${mark} in: ${inStr} out: ${outStr}`;
101
+ }
102
+
103
+ /**
104
+ * Compact 版:schema 表 + 5 条硬约束 + 引脚 type 决策树(约 2KB)。
105
+ * 默认所有 agent step 用此版本;只有 step 修改 ★ 扩展节点的槽位时才升级到 full 版。
106
+ */
107
+ export function buildNodeSchemaCompactSection() {
108
+ if (cachedCompact) return cachedCompact;
109
+ const table = getBuiltinNodeSchemas();
110
+ const lines = [];
111
+ lines.push("## 引脚 type 含义(设计连线前必读)");
112
+ lines.push("流水线里「连一条 edge」就是「上游 output 槽位 → 下游同 type 的 input 槽位」传一份数据。**type 决定这条线传什么、做什么**:");
113
+ lines.push("");
114
+ lines.push("- **`node`**(控制流连线):只表达「执行顺序」,**不携带业务数据**。串主链(Start→A→B→End)、汇合分支用它。槽位名通常是 `prev` / `next` / `prev1` / `next2` / `option_N`。⚠️ 业务字段绝不要标 `node`。");
115
+ lines.push("- **`text`**(短上下文 / 结论 / 路径串):上游把字符串结果(分析结论、用户输入、key 名、JSON 串)直接传给下游;下游 body / script 用 `${slotName}` 引用,apply 时原样替换。适合 < ~1KB 的内容。");
116
+ lines.push("- **`file`**(大块产物 / 上下文文件):上游把内容写到一个**文件**,下游通过 `${slotName}` 拿到的是**文件绝对路径**(不是内容)。下游需 Read 该路径取内容。适合报告 / todolist / 中间代码 / 截图等 > 1KB 或二进制。");
117
+ lines.push("- **`bool`**(仅做分支判定):只在 `control_toBool.prediction` / `control_agent_toBool.prediction`(out) → `control_if.prediction`(in) 一对位置使用,其它任何节点禁止 `bool` 槽。");
118
+ lines.push("");
119
+ lines.push("**选 type 决策**:");
120
+ lines.push("- 想表达「下一步走谁」 → `node`");
121
+ lines.push("- 想传「短串/路径/key/JSON」 → `text`");
122
+ lines.push("- 想传「整篇文档/报告/JSON 文件/代码」 → `file`");
123
+ lines.push("- 想做「if 真假分支」 → `control_toBool`(确定性)或 `control_agent_toBool`(AI 判断)→ `control_if`(用 bool 引脚)");
124
+ lines.push("");
125
+ lines.push("## 内置节点 schema(权威,必须严格遵守)");
126
+ lines.push(
127
+ "格式:`definitionId [★] in: <name>:<type>, ... out: <name>:<type>, ...` " +
128
+ "(`type` 仅允许 `node|text|file|bool`;★ 标记的是**可扩展节点**)"
129
+ );
130
+ lines.push("");
131
+ const ids = Object.keys(table).sort();
132
+ for (const id of ids) {
133
+ lines.push(formatSchemaLine(id, table[id]));
134
+ }
135
+ lines.push("");
136
+ lines.push("**硬性约束(违反则 validate-flow 失败):**");
137
+ lines.push("1. **固定槽位节点**(未带 ★):`input`/`output` 数组必须**完整复制**上表槽位(`type`、`name`、顺序、个数均不可改),仅可填写 `value`。");
138
+ lines.push("2. **可扩展节点**(带 ★):基础骨架不可删改,可在数组**末尾追加** type=`text` 或 `file` 的业务数据槽(按上方语义选 text 还是 file)。⚠️ 业务槽 type 必须 `text` 或 `file`,**绝对不能写 `node`**(node 仅控制流)。");
139
+ lines.push("3. `provide_*` 节点不得连入控制链(node→node 边),仅作数据源向下游 text/file 槽供值。");
140
+ lines.push("4. 边连接时 `sourceHandle: output-N` 与 `targetHandle: input-N` 的索引必须对应**同一 type**;type 不一致禁止连线(text 不能接 file,node 不能接 text)。");
141
+ lines.push("5. **YAML 多行字符串必须用 `|` 块标量。** 写 `script` / `body` / `value` 等字符串字段时,只要内容含 `: `、`\"`、`'`、`#`、换行、shell 操作符,**强制**使用 `|` 块。");
142
+ cachedCompact = lines.join("\n");
143
+ return cachedCompact;
144
+ }
145
+
146
+ /**
147
+ * Full 版:compact + YAML 正反对照 + role 枚举(约 5KB)。
148
+ * 仅当 step 修改 ★ 扩展节点的 input/output 结构时使用,避免误把业务槽写成 type:node。
149
+ */
150
+ export function buildNodeSchemaPromptSection() {
151
+ if (cachedSummary) return cachedSummary;
152
+ const table = getBuiltinNodeSchemas();
153
+ const lines = [];
154
+ lines.push("## 引脚 type 含义(设计连线前必读)");
155
+ lines.push("流水线里「连一条 edge」就是「上游 output 槽位 → 下游同 type 的 input 槽位」传一份数据。**type 决定这条线传什么、做什么**:");
156
+ lines.push("");
157
+ lines.push("- **`node`**(控制流连线):只表达「执行顺序」,**不携带业务数据**。串主链、汇合分支用它。槽位名通常是 `prev` / `next` / `prev1` / `next2` / `option_N`。⚠️ 业务字段绝不要标 `node`。");
158
+ lines.push("- **`text`**(短上下文 / 结论 / 路径串):上游把字符串结果(分析结论、用户输入、key 名、JSON 串)直接传给下游;下游 body / script 用 `${slotName}` 引用,apply 时原样替换。适合 < ~1KB 的内容。");
159
+ lines.push("- **`file`**(大块产物 / 上下文文件):上游把内容写到一个**文件**,下游通过 `${slotName}` 拿到的是**文件绝对路径**(不是内容)。下游需 Read 该路径取内容。适合报告 / todolist / 中间代码 / 截图等 > 1KB 或二进制。");
160
+ lines.push("- **`bool`**(仅做分支判定):只在 `control_toBool.prediction` / `control_agent_toBool.prediction`(out) → `control_if.prediction`(in) 一对位置使用,其它任何节点禁止 `bool` 槽。");
161
+ lines.push("");
162
+ lines.push("## 内置节点 schema(权威,必须严格遵守)");
163
+ lines.push(
164
+ "格式:`definitionId [★] in: <name>:<type>, ... out: <name>:<type>, ...` " +
165
+ "(`type` 仅允许 `node|text|file|bool`;★ 标记的是**可扩展节点**)"
166
+ );
167
+ lines.push("");
168
+ const ids = Object.keys(table).sort();
169
+ for (const id of ids) {
170
+ lines.push(formatSchemaLine(id, table[id]));
171
+ }
172
+ lines.push("");
173
+ lines.push("**硬性约束(违反则 validate-flow 失败):**");
174
+ lines.push(
175
+ "1. **固定槽位节点**(未带 ★):`input`/`output` 数组必须**完整复制**上表槽位(`type`、`name`、顺序、个数均不可改),仅可填写 `value`。"
176
+ );
177
+ lines.push(
178
+ "2. **可扩展节点**(带 ★:agent_subAgent / tool_nodejs / tool_user_check):" +
179
+ "上表槽位为**基础骨架不可删改**(`prev`/`next` 等控制槽与 schema 已有数据槽的 `type`/`name`/顺序保持一致);" +
180
+ "可在数组**末尾追加** type=`text` 或 `file` 的业务数据槽(`bool` 仅 control_toBool / control_agent_toBool / control_if 使用,禁止他处出现)," +
181
+ "命名应与上下游语义对齐(如 `fromapp`、`analysis`、`compile_result`、`result`),便于阶段三连线。"
182
+ );
183
+ lines.push(
184
+ " ⚠️ **追加业务数据槽时 type 必须是 `text` 或 `file`,绝对不能写 `node`。**" +
185
+ " `node` 类型**仅限**基础控制槽(`prev`/`next`/`prev1`/`prev2`/`next1`/`next2`/`option_N`),属于 schema 默认骨架。" +
186
+ " 不要因为 schema 表里 `prev:node` 就惯性给 `fromapp`/`toapp`/`page_name` 也写 `node`——那意味着「控制流连线」,下游会报「边类型不一致」。"
187
+ );
188
+ lines.push(
189
+ "3. `provide_*` 节点不得连入控制链(node→node 边),仅作数据源向下游 text/file 槽供值。"
190
+ );
191
+ lines.push(
192
+ "4. 边连接时 `sourceHandle: output-N` 与 `targetHandle: input-N` 的索引必须对应同一 type;type 不一致禁止连线。"
193
+ );
194
+ lines.push(
195
+ "5. **YAML 多行字符串必须用 `|` 块标量。** 写 `script` / `body` / `value` 等字符串字段时,只要内容含 `: `(冒号+空格)、`\"`、`'`、`#`、换行、shell 操作符(`|`/`&`/`>`/`<`),**强制**使用 `|` 块。**默认全部用 `|`**——比裸写安全且易读。"
196
+ );
197
+ lines.push(" ✅ 正确:");
198
+ lines.push(" ```yaml");
199
+ lines.push(" script: |");
200
+ lines.push(" node -e \"console.log('TODO: scripts/x.mjs')\"");
201
+ lines.push(" ```");
202
+ lines.push(" ❌ 错误(YAML 报 `bad indentation of a mapping entry`):");
203
+ lines.push(" ```yaml");
204
+ lines.push(" script: node -e \"console.log('TODO: scripts/x.mjs')\"");
205
+ lines.push(" ```");
206
+ lines.push(" 原因:YAML 解析器看到 `: ` 会试图开新 mapping key(`TODO` 会被当 key),缩进对不上就崩。");
207
+ lines.push("");
208
+ lines.push("## 引脚 type 决策树(写槽位前必看)");
209
+ lines.push("```");
210
+ lines.push("要追加的槽位代表什么?");
211
+ lines.push("├─ 上游节点的「控制流向」(prev / next / prev1 / next2 ...)");
212
+ lines.push("│ └─ 这些是 schema 默认骨架槽,**禁止你追加**,已经在基础结构里。");
213
+ lines.push("├─ 短字符串 / JSON 串 / 路径字符串(fromapp、page_name、analysis、result、compile_result …)");
214
+ lines.push("│ └─ type: text ✅");
215
+ lines.push("├─ 文件绝对路径(todolist.json、conversion_result.md、screenshot.png …)");
216
+ lines.push("│ └─ type: file ✅");
217
+ lines.push("└─ 二元判定值(仅 control_toBool / control_agent_toBool 的 prediction、control_if 的 prediction)");
218
+ lines.push(" └─ type: bool ✅(其他节点禁用)");
219
+ lines.push("");
220
+ lines.push("⛔ 任何业务数据槽都**不可**写 type: node");
221
+ lines.push("```");
222
+ lines.push("");
223
+ lines.push("## 完整 YAML 正反对照(agent_subAgent ★ 追加业务槽)");
224
+ lines.push("");
225
+ lines.push("✅ **正例**(追加 `fromapp` text 输入 + `todolist` file 输出):");
226
+ lines.push("```yaml");
227
+ lines.push("agent_plan:");
228
+ lines.push(" definitionId: agent_subAgent");
229
+ lines.push(" label: 规划");
230
+ lines.push(" input:");
231
+ lines.push(" - type: node # 基础控制槽(保留,原顺序)");
232
+ lines.push(" name: prev");
233
+ lines.push(" value: ''");
234
+ lines.push(" - type: text # 追加业务数据槽(字符串入参)");
235
+ lines.push(" name: fromapp");
236
+ lines.push(" value: ''");
237
+ lines.push(" - type: text # 追加业务数据槽");
238
+ lines.push(" name: page_name");
239
+ lines.push(" value: ''");
240
+ lines.push(" output:");
241
+ lines.push(" - type: node # 基础控制槽");
242
+ lines.push(" name: next");
243
+ lines.push(" value: ''");
244
+ lines.push(" - type: file # 追加业务数据槽(产出文件路径)");
245
+ lines.push(" name: todolist");
246
+ lines.push(" value: ''");
247
+ lines.push("```");
248
+ lines.push("");
249
+ lines.push("❌ **反例 1**(把业务槽 type 写成 node):");
250
+ lines.push("```yaml");
251
+ lines.push("input:");
252
+ lines.push(" - type: node");
253
+ lines.push(" name: prev");
254
+ lines.push(" - type: node # ❌ fromapp 是字符串入参,type 必须是 text,不是 node");
255
+ lines.push(" name: fromapp");
256
+ lines.push(" - type: node # ❌ 同上");
257
+ lines.push(" name: page_name");
258
+ lines.push("```");
259
+ lines.push("**会触发**:连边时 provide_str (output:text) → 该槽 (input:node),validate-flow 报「边类型不一致」;或 agent 把它当成「上游控制流」试图等待,运行时阻塞。");
260
+ lines.push("");
261
+ lines.push("❌ **反例 2**(删改基础 prev/next 控制槽):");
262
+ lines.push("```yaml");
263
+ lines.push("input:");
264
+ lines.push(" - type: text # ❌ prev 必须是 node,不可改 type");
265
+ lines.push(" name: prev");
266
+ lines.push("output: [] # ❌ next 不可删除,agent_subAgent 必须保留 next");
267
+ lines.push("```");
268
+ lines.push("**会触发**:上游 control_start.next (output-0:node) 无法连入;下游所有节点失去控制流入边。");
269
+ lines.push("");
270
+ lines.push("❌ **反例 3**(在固定槽位节点上追加槽):");
271
+ lines.push("```yaml");
272
+ lines.push("provide_fromapp:");
273
+ lines.push(" definitionId: provide_str # ← 未带 ★,固定槽位节点");
274
+ lines.push(" output:");
275
+ lines.push(" - type: text # ✅ schema 默认槽");
276
+ lines.push(" name: value");
277
+ lines.push(" - type: text # ❌ provide_str 不可追加任何槽");
278
+ lines.push(" name: extra");
279
+ lines.push("```");
280
+ lines.push("**会触发**:参考 schema 表,provide_str 仅有 `out: value:text`,多余槽位破坏 schema。");
281
+ lines.push("");
282
+ lines.push("## instance.role 合法枚举");
283
+ lines.push("`role` 字段**可选**——不确定时**直接省略**(缺省即合法),切勿凭印象写「分析/参数输入/循环入口」等枚举外的值(validate 会拒绝)。");
284
+ lines.push("若要写,仅可从下表选一项(推荐英文 key):");
285
+ const roleEng = VALID_ROLE_VALUES.filter((r) => /^[a-z_-]+$/i.test(r));
286
+ const roleZh = VALID_ROLE_VALUES.filter((r) => !/^[a-z_-]+$/i.test(r));
287
+ lines.push(`- 英文 key:${roleEng.map((r) => "`" + r + "`").join(" / ")}`);
288
+ lines.push(`- 中文别名:${roleZh.map((r) => "`" + r + "`").join(" / ")}`);
289
+ lines.push("约定:`agent_subAgent` 写代码相关→`code`;规划/分析→`planning`;测试→`test`;其余或不确定→`normal` 或省略。`tool_*` / `control_*` / `provide_*` 一律省略 role。");
290
+ cachedSummary = lines.join("\n");
291
+ return cachedSummary;
292
+ }
293
+
294
+ /** 用于测试/排错:清空缓存 */
295
+ export function _resetSchemaCache() {
296
+ cachedTable = null;
297
+ cachedSummary = null;
298
+ cachedCompact = null;
299
+ }