@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.
- package/bin/lib/catalog-flows.mjs +7 -2
- package/bin/lib/composer-node-schema.mjs +7 -7
- package/bin/lib/composer-planner.mjs +5 -5
- package/bin/lib/git-worktree.mjs +263 -0
- package/bin/lib/gitlab-mr.mjs +174 -0
- package/bin/lib/locales/en.json +27 -7
- package/bin/lib/locales/zh.json +26 -6
- package/bin/lib/marketplace.mjs +106 -2
- package/bin/lib/paths.mjs +5 -1
- package/bin/lib/scheduler.mjs +8 -3
- package/bin/lib/ui-server.mjs +284 -13
- package/bin/pipeline/build-node-prompt.mjs +27 -1
- package/bin/pipeline/pre-process-node.mjs +216 -39
- package/bin/pipeline/validate-flow.mjs +7 -17
- package/builtin/nodes/agent_subAgent.md +2 -0
- package/builtin/nodes/control_agent_toBool.md +4 -0
- package/builtin/nodes/control_cancelled.md +8 -4
- package/builtin/nodes/control_cd_workspace.md +2 -0
- package/builtin/nodes/control_delay.md +9 -0
- package/builtin/nodes/control_toBool.md +6 -2
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +9 -0
- package/builtin/nodes/display_html.md +31 -0
- package/builtin/nodes/display_image.md +35 -0
- package/builtin/nodes/provide_bool.md +13 -0
- package/builtin/nodes/provide_file.md +2 -0
- package/builtin/nodes/provide_str.md +2 -0
- package/builtin/nodes/tool_get_env.md +4 -0
- package/builtin/nodes/tool_git_checkout.md +14 -2
- package/builtin/nodes/tool_git_worktree_load.md +59 -0
- package/builtin/nodes/tool_git_worktree_unload.md +51 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +113 -0
- package/builtin/nodes/tool_load_key.md +4 -0
- package/builtin/nodes/tool_nodejs.md +4 -0
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +4 -0
- package/builtin/nodes/tool_user_ask.md +3 -1
- package/builtin/nodes/tool_user_check.md +3 -1
- package/builtin/web-ui/dist/assets/index-B1j_UaHw.js +202 -0
- package/builtin/web-ui/dist/assets/index-ChiTnW0H.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/nodes/control_deadline.md +0 -32
- package/builtin/web-ui/dist/assets/index-D0Tkhqr6.css +0 -1
- package/builtin/web-ui/dist/assets/index-DyhW5chp.js +0 -197
package/bin/lib/locales/zh.json
CHANGED
|
@@ -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
|
|
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": {
|
package/bin/lib/marketplace.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
]);
|
package/bin/lib/scheduler.mjs
CHANGED
|
@@ -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
|
|
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) !==
|
|
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
|
}
|