@fieldwangai/agentflow 0.1.32 → 0.1.34

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 (45) hide show
  1. package/bin/lib/catalog-flows.mjs +7 -2
  2. package/bin/lib/composer-node-schema.mjs +7 -7
  3. package/bin/lib/composer-planner.mjs +5 -5
  4. package/bin/lib/git-worktree.mjs +263 -0
  5. package/bin/lib/gitlab-mr.mjs +174 -0
  6. package/bin/lib/locales/en.json +27 -7
  7. package/bin/lib/locales/zh.json +26 -6
  8. package/bin/lib/marketplace.mjs +106 -2
  9. package/bin/lib/paths.mjs +5 -1
  10. package/bin/lib/scheduler.mjs +8 -3
  11. package/bin/lib/ui-server.mjs +284 -13
  12. package/bin/pipeline/build-node-prompt.mjs +27 -1
  13. package/bin/pipeline/pre-process-node.mjs +216 -39
  14. package/bin/pipeline/validate-flow.mjs +7 -17
  15. package/builtin/nodes/agent_subAgent.md +2 -0
  16. package/builtin/nodes/control_agent_toBool.md +4 -0
  17. package/builtin/nodes/control_cancelled.md +8 -4
  18. package/builtin/nodes/control_cd_workspace.md +2 -0
  19. package/builtin/nodes/control_delay.md +9 -0
  20. package/builtin/nodes/control_toBool.md +6 -2
  21. package/builtin/nodes/control_user_workspace.md +2 -0
  22. package/builtin/nodes/control_wait_until.md +9 -0
  23. package/builtin/nodes/display_html.md +31 -0
  24. package/builtin/nodes/display_image.md +35 -0
  25. package/builtin/nodes/provide_bool.md +13 -0
  26. package/builtin/nodes/provide_file.md +2 -0
  27. package/builtin/nodes/provide_str.md +2 -0
  28. package/builtin/nodes/tool_get_env.md +4 -0
  29. package/builtin/nodes/tool_git_checkout.md +14 -2
  30. package/builtin/nodes/tool_git_worktree_load.md +59 -0
  31. package/builtin/nodes/tool_git_worktree_unload.md +51 -0
  32. package/builtin/nodes/tool_gitlab_create_mr.md +113 -0
  33. package/builtin/nodes/tool_load_key.md +4 -0
  34. package/builtin/nodes/tool_nodejs.md +4 -0
  35. package/builtin/nodes/tool_print.md +2 -0
  36. package/builtin/nodes/tool_save_key.md +4 -0
  37. package/builtin/nodes/tool_user_ask.md +3 -1
  38. package/builtin/nodes/tool_user_check.md +3 -1
  39. package/builtin/web-ui/dist/assets/index-B1j_UaHw.js +202 -0
  40. package/builtin/web-ui/dist/assets/index-ChiTnW0H.css +1 -0
  41. package/builtin/web-ui/dist/index.html +2 -2
  42. package/package.json +1 -1
  43. package/builtin/nodes/control_deadline.md +0 -32
  44. package/builtin/web-ui/dist/assets/index-D0Tkhqr6.css +0 -1
  45. package/builtin/web-ui/dist/assets/index-DyhW5chp.js +0 -197
@@ -238,7 +238,7 @@
238
238
  "description": "当任一上游输入就绪时,沿 next 继续执行"
239
239
  },
240
240
  "control_toBool": {
241
- "displayName": "转布尔",
241
+ "displayName": "代码转布尔",
242
242
  "description": "执行 script 脚本输出 true/false 到 prediction,类似 tool_nodejs 但强制 bool 输出,可扩展输入"
243
243
  },
244
244
  "control_delay": {
@@ -249,13 +249,9 @@
249
249
  "displayName": "等待到时间",
250
250
  "description": "持久化等待到指定时间,到点后由 scheduler 唤醒当前 run 继续执行"
251
251
  },
252
- "control_deadline": {
253
- "displayName": "截止判断",
254
- "description": "判断当前时间是否已超过截止时间,输出 expired 布尔值"
255
- },
256
252
  "control_cancelled": {
257
253
  "displayName": "取消判断",
258
- "description": "判断当前 run/watch 是否已被取消,输出 cancelled 布尔值"
254
+ "description": "判断当前 wait/run 是否已被取消,输出 cancelled 布尔值"
259
255
  },
260
256
  "control_interval_loop": {
261
257
  "displayName": "间隔循环",
@@ -281,6 +277,18 @@
281
277
  "displayName": "Git 拉取",
282
278
  "description": "克隆或更新 Git 仓库,并输出可供下游使用的工作区上下文"
283
279
  },
280
+ "tool_git_worktree_load": {
281
+ "displayName": "加载 Worktree",
282
+ "description": "创建或复用 Git worktree,并输出 workspaceContext 与 gitContext"
283
+ },
284
+ "tool_git_worktree_unload": {
285
+ "displayName": "卸载 Worktree",
286
+ "description": "移除 Git worktree,并恢复下游工作区上下文"
287
+ },
288
+ "tool_gitlab_create_mr": {
289
+ "displayName": "提交 GitLab MR",
290
+ "description": "为当前分支创建或复用 GitLab Merge Request,并输出 MR 链接"
291
+ },
284
292
  "tool_nodejs": {
285
293
  "displayName": "Node.js 脚本",
286
294
  "description": "执行 Node.js 脚本,以 exit code 判断成败,stdout 作为结果"
@@ -313,6 +321,14 @@
313
321
  "displayName": "ASCII 图展示",
314
322
  "description": "在 Workspace 画布中渲染等宽 ASCII 图,并将文本继续传给下游"
315
323
  },
324
+ "display_html": {
325
+ "displayName": "HTML 展示",
326
+ "description": "在 Workspace 画布中用 sandbox iframe 渲染 HTML,并将源码继续传给下游"
327
+ },
328
+ "display_image": {
329
+ "displayName": "图片展示",
330
+ "description": "在 Workspace 画布中预览图片 URL、data URL 或图片路径,并将来源继续传给下游"
331
+ },
316
332
  "provide_str": {
317
333
  "displayName": "文本",
318
334
  "description": "直接提供一段文本,value 会原样供下游引用"
@@ -320,6 +336,10 @@
320
336
  "provide_file": {
321
337
  "displayName": "文件",
322
338
  "description": "直接提供文件路径或内容,value 会原样供下游引用"
339
+ },
340
+ "provide_bool": {
341
+ "displayName": "布尔",
342
+ "description": "直接提供 true/false 布尔值,value 会供下游 bool 引脚引用"
323
343
  }
324
344
  },
325
345
  "pipeline": {
@@ -12,6 +12,7 @@ import {
12
12
 
13
13
  const NODE_MANIFEST = "node.yaml";
14
14
  const COLLECTION_MANIFEST = "collection.yaml";
15
+ const FLOW_SNIPPET_MANIFEST = "flow-snippet.yaml";
15
16
  const LOCK_FILENAME = "agentflow.lock.json";
16
17
 
17
18
  function workspacePackageRoot(workspaceRoot) {
@@ -61,15 +62,20 @@ function readJsonObject(filePath) {
61
62
  function normalizeSlotList(value) {
62
63
  if (!Array.isArray(value)) return [];
63
64
  return value.map((slot) => {
64
- if (!slot || typeof slot !== "object") return { type: "text", name: "", default: "" };
65
+ if (!slot || typeof slot !== "object") return { type: "text", name: "", default: "", showOnNode: false };
65
66
  const type = slot.type != null ? String(slot.type).trim() : "text";
66
67
  const name = slot.name != null ? String(slot.name).trim() : "";
67
68
  const def = slot.default !== undefined ? slot.default : slot.value;
68
- return {
69
+ const normalized = {
69
70
  type,
70
71
  name,
71
72
  default: def == null ? "" : String(def),
72
73
  };
74
+ if (slot.required != null) normalized.required = Boolean(slot.required);
75
+ normalized.showOnNode = slot.showOnNode != null
76
+ ? Boolean(slot.showOnNode)
77
+ : Boolean(normalized.required) || type.toLowerCase() === "node";
78
+ return normalized;
73
79
  });
74
80
  }
75
81
 
@@ -119,6 +125,14 @@ function resolveWorkspaceNodePackageDir(workspaceRoot, id, version) {
119
125
  return target;
120
126
  }
121
127
 
128
+ function resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version) {
129
+ if (!isSafePathSegment(id) || !isSafePathSegment(version)) return null;
130
+ const base = path.resolve(workspacePackageRoot(workspaceRoot), "flow-snippets");
131
+ const target = path.resolve(base, id, version);
132
+ if (target !== base && !target.startsWith(base + path.sep)) return null;
133
+ return target;
134
+ }
135
+
122
136
  function collectFlowDirs(rootDir, source, archived = false) {
123
137
  const out = [];
124
138
  if (!fs.existsSync(rootDir)) return out;
@@ -365,6 +379,41 @@ export function listMarketplacePackages(workspaceRoot, opts = {}) {
365
379
  return { nodes, collections };
366
380
  }
367
381
 
382
+ export function listMarketplaceFlowSnippets(workspaceRoot) {
383
+ const root = workspacePackageRoot(workspaceRoot);
384
+ const snippetsRoot = path.join(root, "flow-snippets");
385
+ const snippets = [];
386
+ if (!fs.existsSync(snippetsRoot)) return { snippets };
387
+ for (const entry of fs.readdirSync(snippetsRoot, { withFileTypes: true })) {
388
+ if (!entry.isDirectory()) continue;
389
+ const base = path.join(snippetsRoot, entry.name);
390
+ for (const version of listVersionDirs(base)) {
391
+ const dir = path.join(base, version);
392
+ const manifest = readYamlObject(path.join(dir, FLOW_SNIPPET_MANIFEST));
393
+ if (!manifest) continue;
394
+ const snippet = manifest.snippet && typeof manifest.snippet === "object" ? manifest.snippet : {};
395
+ snippets.push({
396
+ id: manifest.id || entry.name,
397
+ version: manifest.version || version,
398
+ displayName: manifest.displayName || manifest.name || entry.name,
399
+ description: manifest.description || "",
400
+ tags: Array.isArray(manifest.tags) ? manifest.tags.map((x) => String(x)) : [],
401
+ nodeCount: Number(manifest.nodeCount) || Object.keys(snippet.instances || {}).length || 0,
402
+ edgeCount: Number(manifest.edgeCount) || (Array.isArray(snippet.edges) ? snippet.edges.length : 0),
403
+ createdAt: manifest.createdAt || "",
404
+ updatedAt: manifest.updatedAt || "",
405
+ packageDir: dir,
406
+ snippet,
407
+ });
408
+ }
409
+ }
410
+ snippets.sort((a, b) => {
411
+ const byTime = String(b.updatedAt || b.createdAt || "").localeCompare(String(a.updatedAt || a.createdAt || ""));
412
+ return byTime || a.id.localeCompare(b.id) || a.version.localeCompare(b.version);
413
+ });
414
+ return { snippets };
415
+ }
416
+
368
417
  export function deleteMarketplaceNodePackage(workspaceRoot, id, version, opts = {}) {
369
418
  const packageDir = resolveWorkspaceNodePackageDir(workspaceRoot, id, version);
370
419
  if (!packageDir) return { ok: false, error: "Invalid marketplace node id or version" };
@@ -587,11 +636,15 @@ export function publishNodeFromInstance(workspaceRoot, payload = {}, options = {
587
636
  type: slot.type,
588
637
  name: slot.name,
589
638
  default: slot.default,
639
+ ...(slot.required != null ? { required: Boolean(slot.required) } : {}),
640
+ ...(slot.showOnNode != null ? { showOnNode: Boolean(slot.showOnNode) } : {}),
590
641
  }));
591
642
  const outputs = normalizeSlotList(payload.outputs || payload.output).map((slot) => ({
592
643
  type: slot.type,
593
644
  name: slot.name,
594
645
  default: slot.default,
646
+ ...(slot.required != null ? { required: Boolean(slot.required) } : {}),
647
+ ...(slot.showOnNode != null ? { showOnNode: Boolean(slot.showOnNode) } : {}),
595
648
  }));
596
649
  const script = String(payload.script || "").trim();
597
650
  const body = String(payload.body || "").trim();
@@ -644,6 +697,57 @@ export function publishNodeFromInstance(workspaceRoot, payload = {}, options = {
644
697
  };
645
698
  }
646
699
 
700
+ export function publishFlowSnippet(workspaceRoot, payload = {}) {
701
+ const label = String(payload.displayName || payload.name || payload.id || "flow snippet").trim();
702
+ const id = safePackageId(payload.id || payload.packageId || label);
703
+ const version = normalizeVersion(payload.version || "1.0.0");
704
+ if (!id) return { ok: false, error: "Invalid snippet id" };
705
+
706
+ const rawSnippet = payload.snippet && typeof payload.snippet === "object" ? payload.snippet : {};
707
+ const instances = rawSnippet.instances && typeof rawSnippet.instances === "object" ? rawSnippet.instances : {};
708
+ const edges = Array.isArray(rawSnippet.edges) ? rawSnippet.edges : [];
709
+ const ui = rawSnippet.ui && typeof rawSnippet.ui === "object" ? rawSnippet.ui : {};
710
+ const nodeCount = Object.keys(instances).length;
711
+ if (nodeCount < 2) return { ok: false, error: "A flow snippet needs at least two nodes" };
712
+
713
+ const now = new Date().toISOString();
714
+ const dest = resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version);
715
+ if (!dest) return { ok: false, error: "Invalid snippet id or version" };
716
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
717
+ fs.rmSync(dest, { recursive: true, force: true });
718
+ fs.mkdirSync(dest, { recursive: true });
719
+
720
+ const manifest = {
721
+ id,
722
+ version,
723
+ name: label,
724
+ displayName: label,
725
+ description: String(payload.description || "").trim(),
726
+ tags: Array.isArray(payload.tags) ? payload.tags.map((x) => String(x).trim()).filter(Boolean) : [],
727
+ nodeCount,
728
+ edgeCount: edges.length,
729
+ createdAt: now,
730
+ updatedAt: now,
731
+ snippet: {
732
+ instances,
733
+ edges: edges.map((edge) => ({
734
+ source: String(edge.source || ""),
735
+ target: String(edge.target || ""),
736
+ sourceHandle: edge.sourceHandle ?? null,
737
+ targetHandle: edge.targetHandle ?? null,
738
+ })).filter((edge) => edge.source && edge.target),
739
+ ui,
740
+ },
741
+ };
742
+ fs.writeFileSync(path.join(dest, FLOW_SNIPPET_MANIFEST), yaml.dump(manifest, { lineWidth: -1 }), "utf-8");
743
+ fs.writeFileSync(
744
+ path.join(dest, "README.md"),
745
+ `# ${label}\n\n${manifest.description || "Published from an AgentFlow canvas selection."}\n`,
746
+ "utf-8",
747
+ );
748
+ return { ok: true, id, version, packageDir: dest, snippet: manifest.snippet };
749
+ }
750
+
647
751
  export function installFlowDependency(workspaceRoot, flowDir, spec) {
648
752
  const parsed = parseMarketplaceDefinitionId(spec.startsWith("marketplace:") ? spec : `marketplace:${spec}`);
649
753
  if (!parsed) return { ok: false, error: `Invalid marketplace node spec: ${spec}` };
package/bin/lib/paths.mjs CHANGED
@@ -253,7 +253,6 @@ export const LOCAL_ONLY_DEFINITION_IDS = new Set([
253
253
  "control_if",
254
254
  "control_delay",
255
255
  "control_wait_until",
256
- "control_deadline",
257
256
  "control_cancelled",
258
257
  "control_interval_loop",
259
258
  "control_cd_workspace",
@@ -262,11 +261,15 @@ export const LOCAL_ONLY_DEFINITION_IDS = new Set([
262
261
  "control_start",
263
262
  "control_end",
264
263
  "tool_git_checkout",
264
+ "tool_git_worktree_load",
265
+ "tool_git_worktree_unload",
266
+ "tool_gitlab_create_mr",
265
267
  "tool_print",
266
268
  "tool_user_check",
267
269
  "tool_user_ask",
268
270
  "provide_str",
269
271
  "provide_file",
272
+ "provide_bool",
270
273
  ]);
271
274
 
272
275
  /** 仅 pre+post 且由 CLI 负责写终态的节点 */
@@ -276,4 +279,5 @@ export const LOCAL_ONLY_TERMINAL_SUCCESS_IDS = new Set([
276
279
  "tool_print",
277
280
  "provide_str",
278
281
  "provide_file",
282
+ "provide_bool",
279
283
  ]);
@@ -136,7 +136,7 @@ function readJsonObject(filePath) {
136
136
  }
137
137
 
138
138
  function waitStateKey(state) {
139
- return String((state && (state.id || state.instanceId)) || "");
139
+ return String((state && (state.waitId || state.id || state.instanceId)) || "");
140
140
  }
141
141
 
142
142
  function persistedWaitState(state) {
@@ -181,17 +181,22 @@ function writeWaitState(waitState, patch = {}) {
181
181
  ...(patch && typeof patch === "object" ? patch : {}),
182
182
  updatedAt: new Date().toISOString(),
183
183
  };
184
+ const key = waitStateKey(next);
185
+ if (key) {
186
+ next.waitId = String(next.waitId || key);
187
+ next.id = String(next.id || next.waitId);
188
+ }
184
189
  const runDir = next.runDir || (next.waitPath ? path.dirname(next.waitPath) : "");
185
190
  if (!runDir) return;
186
191
 
187
192
  const legacyPath = next.legacyPath || next.waitPath || path.join(runDir, WAIT_STATE_FILENAME);
188
193
  const registryPath = next.registryPath || path.join(runDir, WAIT_STATES_FILENAME);
189
194
  const persisted = persistedWaitState(next);
190
- const key = waitStateKey(persisted);
195
+ const persistedKey = waitStateKey(persisted);
191
196
 
192
197
  const registry = readJsonObject(registryPath);
193
198
  if (registry && Array.isArray(registry.waits)) {
194
- const waits = registry.waits.filter((w) => waitStateKey(w) !== key);
199
+ const waits = registry.waits.filter((w) => waitStateKey(w) !== persistedKey);
195
200
  waits.push(persisted);
196
201
  fs.writeFileSync(registryPath, JSON.stringify({ ...registry, updatedAt: new Date().toISOString(), waits }, null, 2) + "\n", "utf-8");
197
202
  }