@fieldwangai/agentflow 0.1.25 → 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/composer-agent.mjs +20 -84
- package/bin/lib/composer-skill-router.mjs +0 -18
- 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/node-exec-context.mjs +39 -14
- package/bin/lib/paths.mjs +7 -0
- package/bin/lib/scheduler.mjs +246 -0
- package/bin/lib/ui-server.mjs +88 -69
- 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-DkkhNESc.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) {
|
|
@@ -89,49 +89,6 @@ export function buildScriptContentBlockForInstances(flowYamlAbs, instanceIds) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/**
|
|
93
|
-
* 为 query 意图构建选中节点的完整上下文块(YAML excerpt + 脚本内容)。
|
|
94
|
-
* 供单步轻量 prompt 使用,不注入编辑规则。
|
|
95
|
-
*/
|
|
96
|
-
export function buildQueryContextBlock(flowYamlAbs, instanceIds) {
|
|
97
|
-
if (!flowYamlAbs || !instanceIds?.length) return "";
|
|
98
|
-
try {
|
|
99
|
-
const flowDir = path.dirname(flowYamlAbs);
|
|
100
|
-
const scriptsDirAbs = path.join(flowDir, "scripts");
|
|
101
|
-
const flowRaw = fs.readFileSync(flowYamlAbs, "utf-8");
|
|
102
|
-
const flowDoc = yaml.load(flowRaw);
|
|
103
|
-
const instances = flowDoc?.instances || {};
|
|
104
|
-
const parts = [];
|
|
105
|
-
let totalBytes = 0;
|
|
106
|
-
for (const id of instanceIds) {
|
|
107
|
-
const inst = instances[id];
|
|
108
|
-
if (!inst) continue;
|
|
109
|
-
// YAML excerpt
|
|
110
|
-
const instYaml = yaml.dump({ [id]: inst }, { lineWidth: -1 });
|
|
111
|
-
if (totalBytes + instYaml.length > MAX_SCRIPT_INJECT_BYTES) break;
|
|
112
|
-
totalBytes += instYaml.length;
|
|
113
|
-
parts.push(`### 节点 \`${id}\`(${inst.definitionId || "unknown"})\n\`\`\`yaml\n${instYaml.trimEnd()}\n\`\`\``);
|
|
114
|
-
|
|
115
|
-
// Script content for tool_nodejs
|
|
116
|
-
if (inst.definitionId === "tool_nodejs" && inst.script && fs.existsSync(scriptsDirAbs)) {
|
|
117
|
-
const filenames = extractScriptFilenames(String(inst.script));
|
|
118
|
-
for (const fn of filenames) {
|
|
119
|
-
try {
|
|
120
|
-
const content = fs.readFileSync(path.join(scriptsDirAbs, fn), "utf-8");
|
|
121
|
-
if (totalBytes + content.length > MAX_SCRIPT_INJECT_BYTES) break;
|
|
122
|
-
totalBytes += content.length;
|
|
123
|
-
parts.push(`### 脚本 \`${fn}\`\n\`\`\`javascript\n${content.trimEnd()}\n\`\`\``);
|
|
124
|
-
} catch { /* skip */ }
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (!parts.length) return "";
|
|
129
|
-
return `## 选中节点上下文\n\n${parts.join("\n\n")}`;
|
|
130
|
-
} catch {
|
|
131
|
-
return "";
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
92
|
// ─── 单步模式(向后兼容) ──────────────────────────────────────────────────
|
|
136
93
|
|
|
137
94
|
/**
|
|
@@ -206,8 +163,6 @@ function extractInstanceYamlExcerpt(flowYamlAbs, instanceId) {
|
|
|
206
163
|
|
|
207
164
|
function buildAgentStepPrompt(step, flowContext) {
|
|
208
165
|
const parts = [];
|
|
209
|
-
const intentCategory = flowContext?.intentCategory || "generic";
|
|
210
|
-
const isQuery = intentCategory === "query";
|
|
211
166
|
|
|
212
167
|
const nodeRole = step.nodeRole != null ? String(step.nodeRole).trim() : "";
|
|
213
168
|
if (nodeRole) {
|
|
@@ -215,8 +170,7 @@ function buildAgentStepPrompt(step, flowContext) {
|
|
|
215
170
|
parts.push("");
|
|
216
171
|
}
|
|
217
172
|
|
|
218
|
-
|
|
219
|
-
if (flowContext && !isQuery) {
|
|
173
|
+
if (flowContext) {
|
|
220
174
|
parts.push(t("composer.edit_context"));
|
|
221
175
|
if (flowContext.flowYamlAbs) {
|
|
222
176
|
parts.push(`- 图定义文件:${flowContext.flowYamlAbs}`);
|
|
@@ -243,16 +197,12 @@ function buildAgentStepPrompt(step, flowContext) {
|
|
|
243
197
|
parts.push(flowContext.skillInjectionBlock);
|
|
244
198
|
parts.push("");
|
|
245
199
|
}
|
|
246
|
-
} else if (flowContext && isQuery) {
|
|
247
|
-
parts.push("## AgentFlow 问答上下文");
|
|
248
|
-
if (flowContext.flowYamlAbs) parts.push(`- 图定义文件:${flowContext.flowYamlAbs}`);
|
|
249
|
-
parts.push("");
|
|
250
200
|
}
|
|
251
201
|
|
|
252
202
|
const sid = step.instanceId != null ? String(step.instanceId).trim() : "";
|
|
253
203
|
const instMap = flowContext?._instanceMap;
|
|
254
204
|
const targetInst = sid && instMap && instMap[sid];
|
|
255
|
-
if (
|
|
205
|
+
if (targetInst && targetInst.definitionId === "tool_nodejs") {
|
|
256
206
|
parts.push(t("composer.tool_nodejs_rules_title"));
|
|
257
207
|
parts.push(t("composer.tool_nodejs_rules_body"));
|
|
258
208
|
parts.push("");
|
|
@@ -263,21 +213,19 @@ function buildAgentStepPrompt(step, flowContext) {
|
|
|
263
213
|
parts.push("");
|
|
264
214
|
|
|
265
215
|
// 节点 schema 与目标 instance 上下文
|
|
266
|
-
// query 模式:只注入 instance excerpt + script,跳过 schema
|
|
267
216
|
try {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
parts.push("");
|
|
279
|
-
}
|
|
217
|
+
const targetIsExtensible = targetInst && EXTENSIBLE_DEFINITIONS.has(targetInst.definitionId);
|
|
218
|
+
const promptText = String(step.prompt || step.description || "");
|
|
219
|
+
const promptMentionsSlots = /input\s*:|output\s*:|追加|扩展槽|business\s*slot|业务槽/i.test(promptText);
|
|
220
|
+
const useFullSchema = Boolean(targetIsExtensible || promptMentionsSlots);
|
|
221
|
+
const schemaSection = useFullSchema
|
|
222
|
+
? buildNodeSchemaPromptSection()
|
|
223
|
+
: buildNodeSchemaCompactSection();
|
|
224
|
+
if (schemaSection) {
|
|
225
|
+
parts.push(schemaSection);
|
|
226
|
+
parts.push("");
|
|
280
227
|
}
|
|
228
|
+
|
|
281
229
|
// Inject YAML excerpt + script content for target instance (or canvas fallback)
|
|
282
230
|
const idsToInject = sid ? [sid] : (flowContext?.canvasInstanceIds || []);
|
|
283
231
|
if (idsToInject.length > 0 && flowContext?.flowYamlAbs) {
|
|
@@ -304,30 +252,18 @@ function buildAgentStepPrompt(step, flowContext) {
|
|
|
304
252
|
}
|
|
305
253
|
}
|
|
306
254
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
} else {
|
|
314
|
-
parts.push(
|
|
315
|
-
"## 上下文已就绪(禁止 forage)\n" +
|
|
316
|
-
"- 节点定义见上方 schema 表,**禁止** Glob/Read `builtin/nodes/`、`.workspace/agentflow/nodes/`、历史 `runBuild/` 来推断节点结构。\n" +
|
|
317
|
-
"- 目标 instance 的当前 YAML 已附上(若 instanceId 已知);tool_nodejs 节点引用的 .mjs 脚本内容也已附上(若存在)。\n" +
|
|
318
|
-
"- 如需查看整份 flow,仅在确实需要时读取一次。"
|
|
319
|
-
);
|
|
320
|
-
}
|
|
255
|
+
parts.push(
|
|
256
|
+
"## 上下文已就绪(禁止 forage)\n" +
|
|
257
|
+
"- 节点定义见上方 schema 表,**禁止** Glob/Read `builtin/nodes/`、`.workspace/agentflow/nodes/`、历史 `runBuild/` 来推断节点结构。\n" +
|
|
258
|
+
"- 目标 instance 的当前 YAML 已附上(若 instanceId 已知);tool_nodejs 节点引用的 .mjs 脚本内容也已附上(若存在)。\n" +
|
|
259
|
+
"- 如需查看整份 flow,仅在确实需要时读取一次。"
|
|
260
|
+
);
|
|
321
261
|
parts.push("");
|
|
322
262
|
} catch {
|
|
323
263
|
/* schema 注入失败不影响主流程 */
|
|
324
264
|
}
|
|
325
265
|
|
|
326
|
-
|
|
327
|
-
parts.push("请基于上方注入的节点 YAML 与脚本内容回答用户的问题。**不要修改任何文件。**");
|
|
328
|
-
} else {
|
|
329
|
-
parts.push(t("composer.task_instruction"));
|
|
330
|
-
}
|
|
266
|
+
parts.push(t("composer.task_instruction"));
|
|
331
267
|
return parts.join("\n");
|
|
332
268
|
}
|
|
333
269
|
|
|
@@ -149,24 +149,6 @@ export function detectIntents(userPrompt) {
|
|
|
149
149
|
return [...new Set(matched)];
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
/**
|
|
153
|
-
* 将意图列表归类为注入策略类别。
|
|
154
|
-
* @param {string[]} intents
|
|
155
|
-
* @returns {"query" | "edit-node" | "add-node" | "add-flow" | "edit-flow" | "generic"}
|
|
156
|
-
*/
|
|
157
|
-
export function classifyIntentCategory(intents) {
|
|
158
|
-
if (!intents || intents.length === 0) return "generic";
|
|
159
|
-
// 纯 query(无任何编辑意图混入)
|
|
160
|
-
const editIntents = intents.filter(i => i !== "query-explain");
|
|
161
|
-
if (editIntents.length === 0) return "query";
|
|
162
|
-
// 混合 query + 编辑 → 按编辑侧分类
|
|
163
|
-
if (editIntents.includes("create-flow")) return "add-flow";
|
|
164
|
-
if (editIntents.includes("optimize-flow")) return "edit-flow";
|
|
165
|
-
if (editIntents.includes("add-instances")) return "add-node";
|
|
166
|
-
if (editIntents.includes("edit-fields") || editIntents.includes("optimize-nodes")) return "edit-node";
|
|
167
|
-
return "generic";
|
|
168
|
-
}
|
|
169
|
-
|
|
170
152
|
// ─── 加载 skill 和 reference 内容 ─────────────────────────────────────────
|
|
171
153
|
|
|
172
154
|
/**
|
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") {
|