@fieldwangai/agentflow 0.1.26 → 0.1.27
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/apply.mjs +18 -1
- package/bin/lib/catalog-flows.mjs +65 -0
- package/bin/lib/help.mjs +8 -0
- package/bin/lib/locales/en.json +21 -1
- package/bin/lib/locales/zh.json +21 -1
- package/bin/lib/main.mjs +56 -3
- package/bin/lib/marketplace.mjs +542 -0
- package/bin/lib/paths.mjs +7 -0
- package/bin/lib/scheduler.mjs +246 -0
- package/bin/lib/ui-server.mjs +72 -0
- package/bin/pipeline/build-node-prompt.mjs +43 -2
- package/bin/pipeline/get-ready-nodes.mjs +2 -2
- package/bin/pipeline/get-resolved-values.mjs +6 -0
- package/bin/pipeline/parse-flow.mjs +15 -5
- package/bin/pipeline/pre-process-node.mjs +336 -1
- package/builtin/nodes/control_cancelled.md +20 -0
- package/builtin/nodes/control_deadline.md +32 -0
- package/builtin/nodes/control_delay.md +20 -0
- package/builtin/nodes/control_interval_loop.md +53 -0
- package/builtin/nodes/control_wait_until.md +23 -0
- package/builtin/web-ui/dist/assets/index-C3PT7ICx.js +190 -0
- package/builtin/web-ui/dist/assets/index-CFuFD_86.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-CZkUPcXE.css +0 -1
- package/builtin/web-ui/dist/assets/index-D4949pHe.js +0 -190
package/bin/lib/apply.mjs
CHANGED
|
@@ -32,6 +32,18 @@ const PARALLEL_PREFIX_COLORS = [
|
|
|
32
32
|
(s) => chalk.blue(s),
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
+
function readExistingResultBranch(workspaceRoot, flowName, uuid, instanceId) {
|
|
36
|
+
const resultPath = path.join(getRunDir(workspaceRoot, flowName, uuid), "intermediate", instanceId, `${instanceId}.result.md`);
|
|
37
|
+
if (!fs.existsSync(resultPath)) return null;
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs.readFileSync(resultPath, "utf-8");
|
|
40
|
+
const m = raw.match(/^\s*branch:\s*["']?([^"'\s]+)["']?/m);
|
|
41
|
+
return m ? m[1] : null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
/** parallel 默认 false */
|
|
36
48
|
export async function apply(workspaceRoot, flowName, uuidArg, dryRun, agentModel = null, force = true, parallel = false, cliInputs = {}) {
|
|
37
49
|
ensureReference(workspaceRoot);
|
|
@@ -764,8 +776,13 @@ export async function resume(workspaceRoot, flowName, uuid, instanceIdOptional,
|
|
|
764
776
|
const failedNodes = Object.keys(instanceStatus).filter((id) => instanceStatus[id] === "failed");
|
|
765
777
|
nodesToResume = [...new Set([...pendingNodes, ...failedNodes])];
|
|
766
778
|
}
|
|
767
|
-
const payload = JSON.stringify({ status: "success", message: t("apply.user_confirmed") });
|
|
768
779
|
for (const instanceId of nodesToResume) {
|
|
780
|
+
const existingBranch = readExistingResultBranch(workspaceRoot, flowName, uuid, instanceId);
|
|
781
|
+
const payload = JSON.stringify({
|
|
782
|
+
status: "success",
|
|
783
|
+
message: t("apply.user_confirmed"),
|
|
784
|
+
...(existingBranch ? { branch: existingBranch } : {}),
|
|
785
|
+
});
|
|
769
786
|
const wr = runNodeScript(
|
|
770
787
|
workspaceRoot,
|
|
771
788
|
"write-result.mjs",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getUserPipelinesRoot,
|
|
17
17
|
} from "./paths.mjs";
|
|
18
18
|
import { Table } from "./table.mjs";
|
|
19
|
+
import { listMarketplaceNodes, parseMarketplaceDefinitionId, resolveMarketplaceNodePackage } from "./marketplace.mjs";
|
|
19
20
|
|
|
20
21
|
/** 从指定目录收集含 flow.yaml 的子目录名。 */
|
|
21
22
|
export function collectPipelineNamesFromDir(dirPath) {
|
|
@@ -201,6 +202,16 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
|
201
202
|
const archived = Boolean(opts.archived);
|
|
202
203
|
const byId = new Map();
|
|
203
204
|
const pipelineTranslations = {};
|
|
205
|
+
let marketplaceFlowData = null;
|
|
206
|
+
if (flowId && flowSource) {
|
|
207
|
+
const flowPath = getFlowYamlAbs(workspaceRoot, flowId, flowSource, opts);
|
|
208
|
+
if (flowPath.path && fs.existsSync(flowPath.path)) {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = yaml.load(fs.readFileSync(flowPath.path, "utf-8"));
|
|
211
|
+
if (parsed && typeof parsed === "object") marketplaceFlowData = parsed;
|
|
212
|
+
} catch (_) {}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
204
215
|
const addFromDir = (dir, source, flowIdOpt) => {
|
|
205
216
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return;
|
|
206
217
|
const files = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md"));
|
|
@@ -235,6 +246,26 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
|
235
246
|
addFromDir(PACKAGE_BUILTIN_NODES_DIR, "project");
|
|
236
247
|
addFromDir(path.join(root, LEGACY_NODES_DIR), "project");
|
|
237
248
|
addFromDir(path.join(root, PROJECT_NODES_DIR), "project");
|
|
249
|
+
for (const manifest of listMarketplaceNodes(root, marketplaceFlowData)) {
|
|
250
|
+
let type = "agent";
|
|
251
|
+
const runtimeType = String(manifest.runtime?.type || manifest.type || "").toLowerCase();
|
|
252
|
+
if (runtimeType.startsWith("control")) type = "control";
|
|
253
|
+
else if (runtimeType.startsWith("provide")) type = "provide";
|
|
254
|
+
byId.set(manifest.definitionId, {
|
|
255
|
+
id: manifest.definitionId,
|
|
256
|
+
packageId: manifest.id,
|
|
257
|
+
version: manifest.version,
|
|
258
|
+
type,
|
|
259
|
+
label: manifest.displayName,
|
|
260
|
+
displayName: manifest.displayName,
|
|
261
|
+
description: manifest.description,
|
|
262
|
+
inputs: manifest.input,
|
|
263
|
+
outputs: manifest.output,
|
|
264
|
+
source: manifest.source || "marketplace",
|
|
265
|
+
packageDir: manifest.packageDir,
|
|
266
|
+
runtime: manifest.runtime,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
238
269
|
if (flowId && flowSource) {
|
|
239
270
|
if (flowSource === "builtin") {
|
|
240
271
|
addFromDir(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
|
|
@@ -443,6 +474,40 @@ export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {})
|
|
|
443
474
|
export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {}) {
|
|
444
475
|
const root = path.resolve(workspaceRoot);
|
|
445
476
|
const archived = Boolean(opts.archived);
|
|
477
|
+
const marketSpec = parseMarketplaceDefinitionId(nodeId);
|
|
478
|
+
if (marketSpec) {
|
|
479
|
+
let flowDir = root;
|
|
480
|
+
if (flowId && flowSource) {
|
|
481
|
+
const flowPath = getFlowYamlAbs(workspaceRoot, flowId, flowSource, opts);
|
|
482
|
+
if (flowPath.path) flowDir = path.dirname(flowPath.path);
|
|
483
|
+
if (flowPath.path && fs.existsSync(flowPath.path)) {
|
|
484
|
+
try {
|
|
485
|
+
const parsed = yaml.load(fs.readFileSync(flowPath.path, "utf-8"));
|
|
486
|
+
if (parsed && typeof parsed === "object") opts.flowData = parsed;
|
|
487
|
+
} catch (_) {}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const resolved = resolveMarketplaceNodePackage(root, flowDir, nodeId, opts.flowData || null);
|
|
491
|
+
if (!resolved) return { error: "Node not found: " + nodeId };
|
|
492
|
+
const readmePath = path.join(resolved.packageDir, "README.md");
|
|
493
|
+
let type = "agent";
|
|
494
|
+
const runtimeType = String(resolved.runtime?.type || resolved.type || "").toLowerCase();
|
|
495
|
+
if (runtimeType.startsWith("control")) type = "control";
|
|
496
|
+
else if (runtimeType.startsWith("provide")) type = "provide";
|
|
497
|
+
return {
|
|
498
|
+
type,
|
|
499
|
+
label: resolved.displayName,
|
|
500
|
+
displayName: resolved.displayName,
|
|
501
|
+
inputs: resolved.input,
|
|
502
|
+
outputs: resolved.output,
|
|
503
|
+
executionLogic: fs.existsSync(readmePath) ? fs.readFileSync(readmePath, "utf-8").trim() : undefined,
|
|
504
|
+
description: resolved.description,
|
|
505
|
+
packageId: resolved.id,
|
|
506
|
+
version: resolved.version,
|
|
507
|
+
packageDir: resolved.packageDir,
|
|
508
|
+
runtime: resolved.runtime,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
446
511
|
const fileName = nodeId.endsWith(".md") ? nodeId : `${nodeId}.md`;
|
|
447
512
|
const pathsToTry = [];
|
|
448
513
|
if (flowId && flowSource) {
|
package/bin/lib/help.mjs
CHANGED
|
@@ -20,6 +20,10 @@ AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code CLI 流式输出驱动
|
|
|
20
20
|
agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] 本地 HTTP:流水线列表 + React Flow 节点流程图编辑保存(默认 127.0.0.1:8765;可用 AGENTFLOW_UI_HOST)
|
|
21
21
|
agentflow scheduler start [--poll-ms <ms>] 启动定时执行调度器(读取各流水线 schedule.json)
|
|
22
22
|
agentflow scheduler status [--json] 查看定时执行配置与状态
|
|
23
|
+
agentflow scheduler cancel <FlowName> <uuid> 取消某次等待中的 watch/run
|
|
24
|
+
agentflow marketplace list [--json] 查看 workspace 本地节点市场
|
|
25
|
+
agentflow marketplace publish-node <dir> 发布本地节点包到 workspace market
|
|
26
|
+
agentflow marketplace install-node <FlowName> <nodeSpec> 将 market 节点依赖写入 flow
|
|
23
27
|
agentflow apply <FlowName> [uuid] 或 agentflow apply <uuid>(由 uuid 反查 pipeline)
|
|
24
28
|
agentflow validate <FlowName> [uuid] 校验流程;终端下输出易读结果,--json 或管道时输出 JSON;传 uuid 时写入 runDir/intermediate/validation.json
|
|
25
29
|
agentflow resume <FlowName> <uuid> [instanceId] 将 pending 与 failed 节点标为已确认并继续 apply
|
|
@@ -85,6 +89,10 @@ Usage:
|
|
|
85
89
|
agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] Local HTTP: pipeline list + React Flow node diagram editor (default 127.0.0.1:8765; AGENTFLOW_UI_HOST supported)
|
|
86
90
|
agentflow scheduler start [--poll-ms <ms>] Start the scheduled-run scheduler (reads each pipeline schedule.json)
|
|
87
91
|
agentflow scheduler status [--json] Show scheduled-run configuration and state
|
|
92
|
+
agentflow scheduler cancel <FlowName> <uuid> Cancel a waiting watch/run
|
|
93
|
+
agentflow marketplace list [--json] Show workspace local marketplace packages
|
|
94
|
+
agentflow marketplace publish-node <dir> Publish a local node package to the workspace market
|
|
95
|
+
agentflow marketplace install-node <FlowName> <nodeSpec> Add a marketplace node dependency to a flow
|
|
88
96
|
agentflow apply <FlowName> [uuid] Or agentflow apply <uuid> (resolve pipeline from uuid)
|
|
89
97
|
agentflow validate <FlowName> [uuid] Validate flow; readable output in terminal, JSON with --json or pipe; writes to runDir/intermediate/validation.json when uuid provided
|
|
90
98
|
agentflow resume <FlowName> <uuid> [instanceId] Mark pending and failed nodes as acknowledged and continue apply
|
package/bin/lib/locales/en.json
CHANGED
|
@@ -241,6 +241,26 @@
|
|
|
241
241
|
"displayName": "To Bool",
|
|
242
242
|
"description": "Execute script to produce true/false prediction. Like tool_nodejs but enforces bool output. Extensible inputs."
|
|
243
243
|
},
|
|
244
|
+
"control_delay": {
|
|
245
|
+
"displayName": "Delay",
|
|
246
|
+
"description": "Persistently wait for a relative duration, then scheduler resumes this run."
|
|
247
|
+
},
|
|
248
|
+
"control_wait_until": {
|
|
249
|
+
"displayName": "Wait Until",
|
|
250
|
+
"description": "Persistently wait until an absolute time, then scheduler resumes this run."
|
|
251
|
+
},
|
|
252
|
+
"control_deadline": {
|
|
253
|
+
"displayName": "Deadline",
|
|
254
|
+
"description": "Check whether the deadline has expired and output expired as bool."
|
|
255
|
+
},
|
|
256
|
+
"control_cancelled": {
|
|
257
|
+
"displayName": "Cancelled",
|
|
258
|
+
"description": "Check whether the current run/watch has been cancelled and output cancelled as bool."
|
|
259
|
+
},
|
|
260
|
+
"control_interval_loop": {
|
|
261
|
+
"displayName": "Interval Loop",
|
|
262
|
+
"description": "Persistently wait by interval and branch to continue, done, timeout, or cancelled based on done, cancelled, and deadline inputs."
|
|
263
|
+
},
|
|
244
264
|
"control_agent_toBool": {
|
|
245
265
|
"displayName": "Agent ToBool",
|
|
246
266
|
"description": "AI-powered boolean judgment for non-deterministic scenarios"
|
|
@@ -341,4 +361,4 @@
|
|
|
341
361
|
"control_toBool_check": { "label": "Check Result" }
|
|
342
362
|
}
|
|
343
363
|
}
|
|
344
|
-
}
|
|
364
|
+
}
|
package/bin/lib/locales/zh.json
CHANGED
|
@@ -241,6 +241,26 @@
|
|
|
241
241
|
"displayName": "转布尔",
|
|
242
242
|
"description": "执行 script 脚本输出 true/false 到 prediction,类似 tool_nodejs 但强制 bool 输出,可扩展输入"
|
|
243
243
|
},
|
|
244
|
+
"control_delay": {
|
|
245
|
+
"displayName": "延迟等待",
|
|
246
|
+
"description": "持久化等待一段时间,到点后由 scheduler 唤醒当前 run 继续执行"
|
|
247
|
+
},
|
|
248
|
+
"control_wait_until": {
|
|
249
|
+
"displayName": "等待到时间",
|
|
250
|
+
"description": "持久化等待到指定时间,到点后由 scheduler 唤醒当前 run 继续执行"
|
|
251
|
+
},
|
|
252
|
+
"control_deadline": {
|
|
253
|
+
"displayName": "截止判断",
|
|
254
|
+
"description": "判断当前时间是否已超过截止时间,输出 expired 布尔值"
|
|
255
|
+
},
|
|
256
|
+
"control_cancelled": {
|
|
257
|
+
"displayName": "取消判断",
|
|
258
|
+
"description": "判断当前 run/watch 是否已被取消,输出 cancelled 布尔值"
|
|
259
|
+
},
|
|
260
|
+
"control_interval_loop": {
|
|
261
|
+
"displayName": "间隔循环",
|
|
262
|
+
"description": "按固定间隔持久化等待,并根据 done、cancelled、deadline 分支到继续、完成、超时或取消"
|
|
263
|
+
},
|
|
244
264
|
"control_agent_toBool": {
|
|
245
265
|
"displayName": "AI 转布尔",
|
|
246
266
|
"description": "由 AI 判断输入内容的布尔含义,适用于不确定性场景"
|
|
@@ -341,4 +361,4 @@
|
|
|
341
361
|
"control_toBool_check": { "label": "检查结果" }
|
|
342
362
|
}
|
|
343
363
|
}
|
|
344
|
-
}
|
|
364
|
+
}
|
package/bin/lib/main.mjs
CHANGED
|
@@ -30,7 +30,8 @@ import { startUiServer } from "./ui-server.mjs";
|
|
|
30
30
|
import { hubLogin, hubLogout } from "./hub-login.mjs";
|
|
31
31
|
import { hubPublish } from "./hub-publish.mjs";
|
|
32
32
|
import { hubListRemote, hubDownload } from "./hub-remote.mjs";
|
|
33
|
-
import { listScheduleStatuses, startScheduler } from "./scheduler.mjs";
|
|
33
|
+
import { cancelScheduledRun, listScheduleStatuses, startScheduler } from "./scheduler.mjs";
|
|
34
|
+
import { installFlowDependency, listMarketplacePackages, publishNodePackage } from "./marketplace.mjs";
|
|
34
35
|
|
|
35
36
|
async function readStdin() {
|
|
36
37
|
const chunks = [];
|
|
@@ -210,6 +211,43 @@ export async function main() {
|
|
|
210
211
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
211
212
|
process.exit(result.error ? 1 : 0);
|
|
212
213
|
}
|
|
214
|
+
if (sub === "marketplace") {
|
|
215
|
+
const action = shift();
|
|
216
|
+
if (action === "list") {
|
|
217
|
+
const result = listMarketplacePackages(workspaceRoot);
|
|
218
|
+
if (jsonMode) {
|
|
219
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
220
|
+
} else {
|
|
221
|
+
const table = new Table({ head: ["type", "id", "version", "name", "path"], style: { head: [] } });
|
|
222
|
+
for (const n of result.nodes) table.push(["node", n.id, n.version, n.displayName || "", n.packageDir]);
|
|
223
|
+
for (const c of result.collections) table.push(["collection", c.id, c.version, c.displayName || "", c.packageDir]);
|
|
224
|
+
process.stdout.write(table.toString() + "\n");
|
|
225
|
+
}
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
if (action === "publish-node") {
|
|
229
|
+
const sourceDir = shift();
|
|
230
|
+
if (!sourceDir) throw new Error("Usage: agentflow marketplace publish-node <packageDir> [--json]");
|
|
231
|
+
const result = publishNodePackage(workspaceRoot, sourceDir);
|
|
232
|
+
if (jsonMode) process.stdout.write(JSON.stringify(result) + "\n");
|
|
233
|
+
else if (result.ok) process.stdout.write(`Published node ${result.id}@${result.version}: ${result.definitionId}\n`);
|
|
234
|
+
else throw new Error(result.error || "publish-node failed");
|
|
235
|
+
process.exit(result.ok ? 0 : 1);
|
|
236
|
+
}
|
|
237
|
+
if (action === "install-node") {
|
|
238
|
+
const flowId = shift();
|
|
239
|
+
const spec = shift();
|
|
240
|
+
if (!flowId || !spec) throw new Error("Usage: agentflow marketplace install-node <flow> <nodeSpec> [--json]");
|
|
241
|
+
const flowDir = getFlowDir(workspaceRoot, flowId);
|
|
242
|
+
if (!flowDir) throw new Error(`Flow not found: ${flowId}`);
|
|
243
|
+
const result = installFlowDependency(workspaceRoot, flowDir, spec);
|
|
244
|
+
if (jsonMode) process.stdout.write(JSON.stringify(result) + "\n");
|
|
245
|
+
else if (result.ok) process.stdout.write(`Installed ${result.definitionId} into ${flowId}\n`);
|
|
246
|
+
else throw new Error(result.error || "install-node failed");
|
|
247
|
+
process.exit(result.ok ? 0 : 1);
|
|
248
|
+
}
|
|
249
|
+
throw new Error("Usage: agentflow marketplace <list|publish-node|install-node> [--json]");
|
|
250
|
+
}
|
|
213
251
|
if (sub === "copy-builtin" && jsonMode) {
|
|
214
252
|
const flowId = shift();
|
|
215
253
|
let targetFlowId;
|
|
@@ -382,7 +420,7 @@ export async function main() {
|
|
|
382
420
|
process.exit(0);
|
|
383
421
|
}
|
|
384
422
|
const rows = listScheduleStatuses(workspaceRoot);
|
|
385
|
-
const table = new Table({ head: ["flow", "source", "enabled", "cron", "timezone", "next", "running", "lastRun", "error"], style: { head: [] } });
|
|
423
|
+
const table = new Table({ head: ["flow", "source", "enabled", "cron", "timezone", "next", "running", "waiting", "lastRun", "error"], style: { head: [] } });
|
|
386
424
|
for (const r of rows) {
|
|
387
425
|
table.push([
|
|
388
426
|
r.flowId,
|
|
@@ -392,6 +430,7 @@ export async function main() {
|
|
|
392
430
|
r.timezone || "",
|
|
393
431
|
r.nextRunAt || "",
|
|
394
432
|
r.running ? "yes" : "no",
|
|
433
|
+
String(r.waiting || 0),
|
|
395
434
|
r.lastRunUuid || "",
|
|
396
435
|
r.lastError || "",
|
|
397
436
|
]);
|
|
@@ -399,7 +438,21 @@ export async function main() {
|
|
|
399
438
|
process.stdout.write(table.toString() + "\n");
|
|
400
439
|
process.exit(0);
|
|
401
440
|
}
|
|
402
|
-
|
|
441
|
+
if (action === "cancel") {
|
|
442
|
+
const flowId = shift();
|
|
443
|
+
const uuid = shift();
|
|
444
|
+
if (!flowId || !uuid) throw new Error("Usage: agentflow scheduler cancel <flow> <uuid> [--json]");
|
|
445
|
+
const result = cancelScheduledRun(workspaceRoot, flowId, uuid);
|
|
446
|
+
if (jsonMode) {
|
|
447
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
448
|
+
} else if (result.ok) {
|
|
449
|
+
process.stdout.write(`Cancelled ${flowId}/${uuid}; updated waits: ${result.updatedWaits}\n`);
|
|
450
|
+
} else {
|
|
451
|
+
throw new Error(result.error || "cancel failed");
|
|
452
|
+
}
|
|
453
|
+
process.exit(result.ok ? 0 : 1);
|
|
454
|
+
}
|
|
455
|
+
throw new Error("Usage: agentflow scheduler <start|status|cancel> [--once] [--poll-ms <ms>] [--json]");
|
|
403
456
|
}
|
|
404
457
|
// ──── Hub commands ────
|
|
405
458
|
if (sub === "login") {
|