@fieldwangai/agentflow 0.1.149 → 0.1.154
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/README.md +20 -33
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +167 -88
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +276 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +268 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +10 -30
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +43 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +153 -283
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1106 -18013
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +74 -0
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-DPbR2UaN.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +2 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +8 -47
- package/skills/agentflow-cli/SKILL.md +158 -25
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +668 -18
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +7 -248
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +4 -73
- package/skills/agentflow-flow-recipes/SKILL.md +7 -18
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +3 -54
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +5 -4
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-placeholder-reference/SKILL.md +2 -2
- package/skills/agentflow-runtime-reference/SKILL.md +2 -2
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-BSCpd5la.js +0 -889
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/skills/agentflow-node-authoring/SKILL.md +0 -57
package/bin/lib/flow-import.mjs
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 从上传的流程文件或 zip 解压结果导入流水线目录(user / workspace)。
|
|
3
|
+
*
|
|
4
|
+
* 「包的根在哪」这件事以前只认 `flow.yaml`——代码化的流程目录里没有它,于是从平台发布通道
|
|
5
|
+
* 传上来只会得到「压缩包内未找到 flow.yaml」。判据改成和磁盘上完全一致的那一套
|
|
6
|
+
* (`FLOW_MARKER_FILENAMES`:`workspace.flow.js` / `workspace.graph.json` / `flow.yaml`),
|
|
7
|
+
* 两种格式都收。
|
|
3
8
|
*/
|
|
4
9
|
import fs from "fs";
|
|
5
10
|
import path from "path";
|
|
6
11
|
import yaml from "js-yaml";
|
|
7
12
|
import { unzipSync } from "fflate";
|
|
13
|
+
import { flowFilesToGraph } from "./flow-dsl/index.mjs";
|
|
14
|
+
import { marketplaceDependenciesFromSource } from "./flow-dsl/packages.mjs";
|
|
8
15
|
import { resolveFlowDirForWrite, validateUserPipelineId } from "./flow-write.mjs";
|
|
9
16
|
import { normalizeFlowYamlText } from "./flow-normalize.mjs";
|
|
17
|
+
import { FLOW_MARKER_FILENAMES } from "./paths.mjs";
|
|
18
|
+
|
|
19
|
+
/** `flow.yml` 是 `flow.yaml` 的历史别名,落盘时统一改名。 */
|
|
20
|
+
const YAML_ALIAS = "flow.yml";
|
|
21
|
+
const MARKERS = new Set([...FLOW_MARKER_FILENAMES, YAML_ALIAS].map((n) => n.toLowerCase()));
|
|
10
22
|
|
|
11
23
|
export const IMPORT_MAX_UNCOMPRESSED_BYTES = 8 * 1024 * 1024;
|
|
12
24
|
export const IMPORT_MAX_FILE_ENTRIES = 500;
|
|
@@ -34,15 +46,19 @@ function shouldIgnoreZipPath(norm) {
|
|
|
34
46
|
return parts.some((seg) => seg === "__MACOSX" || seg.startsWith("._"));
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
|
|
49
|
+
/**
|
|
50
|
+
* 这个文件是不是流程目录的标记文件;是的话返回它所在的目录(根目录为 `""`)。
|
|
51
|
+
*
|
|
52
|
+
* 只认根目录或一层子目录——再深的层级不是「单个流程的包」。
|
|
53
|
+
*
|
|
54
|
+
* @returns {string | null} 不是标记文件时返回 null
|
|
55
|
+
*/
|
|
56
|
+
function flowMarkerParentDir(normPath) {
|
|
38
57
|
const lower = normPath.toLowerCase();
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (lower.endsWith(suf)) parent = normPath.slice(0, -suf.length);
|
|
44
|
-
else if (lower.endsWith(sufYml)) parent = normPath.slice(0, -sufYml.length);
|
|
45
|
-
else return null;
|
|
58
|
+
if (MARKERS.has(lower)) return "";
|
|
59
|
+
const slash = lower.lastIndexOf("/");
|
|
60
|
+
if (slash < 0 || !MARKERS.has(lower.slice(slash + 1))) return null;
|
|
61
|
+
const parent = normPath.slice(0, slash);
|
|
46
62
|
if (parent.includes("/")) return null;
|
|
47
63
|
return parent;
|
|
48
64
|
}
|
|
@@ -85,6 +101,54 @@ export function validateImportedFlowYaml(content) {
|
|
|
85
101
|
}
|
|
86
102
|
}
|
|
87
103
|
|
|
104
|
+
/**
|
|
105
|
+
* 单文件上传(不是 zip)时,判断这一份是什么、能不能收。
|
|
106
|
+
*
|
|
107
|
+
* 代码化的流程只上传 `workspace.flow.js` 时,布局和节点元数据都缺席——那不影响读图,
|
|
108
|
+
* 缺省会被补齐。但源码必须**当场解析得过**:写进去一个解析不出图的文件,用户下次打开画布
|
|
109
|
+
* 才发现,而那时已经离现场很远。
|
|
110
|
+
*
|
|
111
|
+
* @param {string} content
|
|
112
|
+
* @param {string} filename 上传时的文件名,用来决定按哪种格式解析
|
|
113
|
+
* @param {{ resolvePackage?: Function }} [opts]
|
|
114
|
+
* @returns {{ ok: true, entryName: string } | { ok: false, error: string }}
|
|
115
|
+
*/
|
|
116
|
+
export function validateImportedFlowSource(content, filename = "", opts = {}) {
|
|
117
|
+
const name = String(filename || "").toLowerCase();
|
|
118
|
+
if (!name.endsWith(".js") && !name.endsWith(".mjs")) {
|
|
119
|
+
const checked = validateImportedFlowYaml(content);
|
|
120
|
+
return checked.ok ? { ok: true, entryName: "flow.yaml" } : checked;
|
|
121
|
+
}
|
|
122
|
+
if (Buffer.byteLength(String(content || ""), "utf8") > IMPORT_MAX_UNCOMPRESSED_BYTES) {
|
|
123
|
+
return { ok: false, error: "workspace.flow.js 过大" };
|
|
124
|
+
}
|
|
125
|
+
const dependencies = marketplaceDependenciesFromSource(content);
|
|
126
|
+
if (dependencies.errors.length) {
|
|
127
|
+
return { ok: false, error: dependencies.errors.join(";") };
|
|
128
|
+
}
|
|
129
|
+
if (typeof opts.resolvePackage === "function") {
|
|
130
|
+
const missing = dependencies.dependencies.filter((dependency) => !opts.resolvePackage(dependency.specifier));
|
|
131
|
+
if (missing.length) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
error: `服务端缺少节点包:${missing.map((dependency) => dependency.specifier).join(", ")};请先上传这些精确版本`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
flowFilesToGraph({
|
|
140
|
+
source: String(content || ""),
|
|
141
|
+
layout: {},
|
|
142
|
+
nodeMeta: {},
|
|
143
|
+
files: {},
|
|
144
|
+
resolvePackage: opts.resolvePackage,
|
|
145
|
+
});
|
|
146
|
+
return { ok: true, entryName: "workspace.flow.js" };
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return { ok: false, error: `workspace.flow.js 解析失败:${(e && e.message) || e}` };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
88
152
|
/**
|
|
89
153
|
* @param {Record<string, Uint8Array>} unzipped
|
|
90
154
|
* @returns {{ ok: true, files: Map<string, Buffer> } | { ok: false, error: string }}
|
|
@@ -118,15 +182,15 @@ export function normalizeZipToPipelineFiles(unzipped) {
|
|
|
118
182
|
/** @type {Set<string>} */
|
|
119
183
|
const parents = new Set();
|
|
120
184
|
for (const k of raw.keys()) {
|
|
121
|
-
const p =
|
|
185
|
+
const p = flowMarkerParentDir(k);
|
|
122
186
|
if (p !== null) parents.add(p);
|
|
123
187
|
}
|
|
124
188
|
|
|
125
189
|
if (parents.size === 0) {
|
|
126
|
-
return { ok: false, error:
|
|
190
|
+
return { ok: false, error: `压缩包内未找到流程文件(需要 ${FLOW_MARKER_FILENAMES.join(" / ")} 之一)` };
|
|
127
191
|
}
|
|
128
192
|
if (parents.size > 1) {
|
|
129
|
-
return { ok: false, error: "压缩包内存在多个 pipeline
|
|
193
|
+
return { ok: false, error: "压缩包内存在多个 pipeline,请分别打包" };
|
|
130
194
|
}
|
|
131
195
|
|
|
132
196
|
const [prefix] = [...parents];
|
|
@@ -136,7 +200,7 @@ export function normalizeZipToPipelineFiles(unzipped) {
|
|
|
136
200
|
if (!need) {
|
|
137
201
|
return {
|
|
138
202
|
ok: false,
|
|
139
|
-
error: "ZIP
|
|
203
|
+
error: "ZIP 目录结构无效:存在不属于该流水线目录的文件(请使用单文件夹,或把流程文件放在根目录)",
|
|
140
204
|
};
|
|
141
205
|
}
|
|
142
206
|
}
|
|
@@ -155,22 +219,23 @@ export function normalizeZipToPipelineFiles(unzipped) {
|
|
|
155
219
|
}
|
|
156
220
|
|
|
157
221
|
const yamlKeys = [...out.keys()].filter(
|
|
158
|
-
(k) => k.toLowerCase() === "flow.yaml" || k.toLowerCase() ===
|
|
222
|
+
(k) => k.toLowerCase() === "flow.yaml" || k.toLowerCase() === YAML_ALIAS,
|
|
159
223
|
);
|
|
160
|
-
if (yamlKeys.length === 0) {
|
|
161
|
-
return { ok: false, error: "归一化后缺少 flow.yaml" };
|
|
162
|
-
}
|
|
163
224
|
if (yamlKeys.length > 1) {
|
|
164
225
|
return { ok: false, error: "流水线目录内不能同时存在多个 flow.yaml / flow.yml" };
|
|
165
226
|
}
|
|
166
|
-
|
|
167
227
|
const yamlKey = yamlKeys[0];
|
|
168
|
-
if (yamlKey !== "flow.yaml") {
|
|
228
|
+
if (yamlKey && yamlKey !== "flow.yaml") {
|
|
169
229
|
const body = out.get(yamlKey);
|
|
170
230
|
out.delete(yamlKey);
|
|
171
231
|
out.set("flow.yaml", body);
|
|
172
232
|
}
|
|
173
233
|
|
|
234
|
+
// 归一化会剥掉外层目录,标记文件可能因此换了位置——重新确认一次
|
|
235
|
+
if (![...out.keys()].some((k) => MARKERS.has(k.toLowerCase()))) {
|
|
236
|
+
return { ok: false, error: `归一化后缺少流程文件(需要 ${FLOW_MARKER_FILENAMES.join(" / ")} 之一)` };
|
|
237
|
+
}
|
|
238
|
+
|
|
174
239
|
return { ok: true, files: out };
|
|
175
240
|
}
|
|
176
241
|
|
|
@@ -214,7 +279,7 @@ export function suggestFlowIdFromZip(zipBuffer) {
|
|
|
214
279
|
/** @type {Set<string>} */
|
|
215
280
|
const parents = new Set();
|
|
216
281
|
for (const k of raw.keys()) {
|
|
217
|
-
const p =
|
|
282
|
+
const p = flowMarkerParentDir(k);
|
|
218
283
|
if (p !== null) parents.add(p);
|
|
219
284
|
}
|
|
220
285
|
|
|
@@ -270,12 +335,17 @@ export function writePipelineTree(workspaceRoot, flowId, flowSource, filesRelati
|
|
|
270
335
|
return { success: false, error: "目标目录已存在" };
|
|
271
336
|
}
|
|
272
337
|
|
|
338
|
+
// 有 yaml 就照旧校验并规范化;代码化的包没有 yaml,认标记文件即可
|
|
273
339
|
const yamlBuf = filesRelative.get("flow.yaml");
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
340
|
+
let normalizedYaml = "";
|
|
341
|
+
if (yamlBuf) {
|
|
342
|
+
const text = yamlBuf.toString("utf8");
|
|
343
|
+
const v = validateImportedFlowYaml(text);
|
|
344
|
+
if (!v.ok) return { success: false, error: v.error };
|
|
345
|
+
normalizedYaml = normalizeFlowYamlText(text).text;
|
|
346
|
+
} else if (![...filesRelative.keys()].some((k) => MARKERS.has(k.toLowerCase()))) {
|
|
347
|
+
return { success: false, error: `缺少流程文件(需要 ${FLOW_MARKER_FILENAMES.join(" / ")} 之一)` };
|
|
348
|
+
}
|
|
279
349
|
|
|
280
350
|
try {
|
|
281
351
|
fs.mkdirSync(flowDir, { recursive: true });
|
|
@@ -290,7 +360,7 @@ export function writePipelineTree(workspaceRoot, flowId, flowSource, filesRelati
|
|
|
290
360
|
}
|
|
291
361
|
const parent = path.dirname(abs);
|
|
292
362
|
fs.mkdirSync(parent, { recursive: true });
|
|
293
|
-
const payload = safe === "flow.yaml" ? Buffer.from(normalizedYaml, "utf8") : buf;
|
|
363
|
+
const payload = safe === "flow.yaml" && normalizedYaml ? Buffer.from(normalizedYaml, "utf8") : buf;
|
|
294
364
|
fs.writeFileSync(abs, payload);
|
|
295
365
|
}
|
|
296
366
|
return { success: true };
|
package/bin/lib/flow-write.mjs
CHANGED
|
@@ -13,9 +13,11 @@ import {
|
|
|
13
13
|
getUserPipelinesRoot,
|
|
14
14
|
LEGACY_PIPELINES_DIR,
|
|
15
15
|
PIPELINES_DIR,
|
|
16
|
+
isFlowDir,
|
|
16
17
|
} from "./paths.mjs";
|
|
17
|
-
import {
|
|
18
|
+
import { resolveFlowDirAbs } from "./catalog-flows.mjs";
|
|
18
19
|
import { normalizeFlowYamlText } from "./flow-normalize.mjs";
|
|
20
|
+
import { writeWorkspaceGraphFiles } from "./workspace-flow-store.mjs";
|
|
19
21
|
|
|
20
22
|
export const FLOW_YAML_FILENAME = "flow.yaml";
|
|
21
23
|
|
|
@@ -83,7 +85,7 @@ function resolveExistingWorkspaceFlowDir(workspaceRoot, flowId) {
|
|
|
83
85
|
const root = path.resolve(workspaceRoot);
|
|
84
86
|
for (const rel of [PIPELINES_DIR, LEGACY_PIPELINES_DIR]) {
|
|
85
87
|
const d = path.join(root, rel, flowId);
|
|
86
|
-
if (
|
|
88
|
+
if (isFlowDir(d)) return d;
|
|
87
89
|
}
|
|
88
90
|
return null;
|
|
89
91
|
}
|
|
@@ -186,6 +188,40 @@ export function resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource
|
|
|
186
188
|
return { flowDir };
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
/**
|
|
192
|
+
* 新建一个空流程:目录 + 空的 `workspace.flow.js` + 承载说明的 `workspace.layout.json`。
|
|
193
|
+
*
|
|
194
|
+
* 以前新建流程写的是一个空 `flow.yaml`——那个格式的执行栈早就退休了,写它纯粹是因为
|
|
195
|
+
* 目录识别和列表说明当时只认它。两件事现在都不依赖 yaml 了(见 paths.mjs 的 `isFlowDir`
|
|
196
|
+
* 和 catalog-flows 的 `readPipelineListDescription`),所以新流程生下来就是代码。
|
|
197
|
+
*
|
|
198
|
+
* @param {string} workspaceRoot
|
|
199
|
+
* @param {string} flowId
|
|
200
|
+
* @param {FlowWriteSource} flowSource
|
|
201
|
+
* @param {{ description?: string, userId?: string }} [opts]
|
|
202
|
+
* @returns {{ success: true, flowDir: string } | { success: false, error: string }}
|
|
203
|
+
*/
|
|
204
|
+
export function createEmptyFlow(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
205
|
+
const { flowDir, error } = resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
|
|
206
|
+
if (error) return { success: false, error };
|
|
207
|
+
const description = String(opts.description || "").trim();
|
|
208
|
+
try {
|
|
209
|
+
fs.mkdirSync(flowDir, { recursive: true });
|
|
210
|
+
const result = writeWorkspaceGraphFiles(flowDir, {
|
|
211
|
+
version: 1,
|
|
212
|
+
instances: {},
|
|
213
|
+
edges: [],
|
|
214
|
+
ui: { nodePositions: {}, ...(description ? { description } : {}) },
|
|
215
|
+
});
|
|
216
|
+
if (result.format !== "dsl") {
|
|
217
|
+
return { success: false, error: `新建流程没能写成代码:${result.degradedReason || "未知原因"}` };
|
|
218
|
+
}
|
|
219
|
+
return { success: true, flowDir };
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return { success: false, error: (e && e.message) || String(e) };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
189
225
|
/**
|
|
190
226
|
* @param {string} workspaceRoot
|
|
191
227
|
* @param {string} flowId
|
|
@@ -221,11 +257,11 @@ export function archiveFlowPipeline(workspaceRoot, flowId, flowSource, opts = {}
|
|
|
221
257
|
if (flowSource !== "user" && flowSource !== "workspace") {
|
|
222
258
|
return { success: false, error: "仅支持用户目录或工作区流水线归档" };
|
|
223
259
|
}
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
return { success: false, error:
|
|
260
|
+
const dirRes = resolveFlowDirAbs(workspaceRoot, flowId, flowSource, { archived: false, userId: opts.userId });
|
|
261
|
+
if (dirRes.error || !dirRes.dir) {
|
|
262
|
+
return { success: false, error: dirRes.error || "找不到流水线" };
|
|
227
263
|
}
|
|
228
|
-
const fromDir =
|
|
264
|
+
const fromDir = dirRes.dir;
|
|
229
265
|
const sep = path.sep;
|
|
230
266
|
if (fromDir.split(sep).includes(ARCHIVED_PIPELINES_DIR_NAME)) {
|
|
231
267
|
return { success: false, error: "该流水线已在归档目录中" };
|
|
@@ -266,7 +302,7 @@ export function restoreArchivedFlowPipeline(workspaceRoot, flowId, flowSource, o
|
|
|
266
302
|
return { success: false, error: archivedRes.error || "无法解析归档路径" };
|
|
267
303
|
}
|
|
268
304
|
const fromDir = archivedRes.flowDir;
|
|
269
|
-
if (!
|
|
305
|
+
if (!isFlowDir(fromDir)) {
|
|
270
306
|
return { success: false, error: "找不到归档流水线" };
|
|
271
307
|
}
|
|
272
308
|
const activeRes = resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
|
|
@@ -317,7 +353,7 @@ export function moveFlowDirectory(workspaceRoot, flowId, fromSource, toSource, o
|
|
|
317
353
|
const toRes = resolveFlowDirForWrite(workspaceRoot, flowId, toSource, opts);
|
|
318
354
|
if (toRes.error || !toRes.flowDir) return { success: false, error: toRes.error || "invalid target path" };
|
|
319
355
|
const toDir = toRes.flowDir;
|
|
320
|
-
if (!
|
|
356
|
+
if (!isFlowDir(fromDir)) {
|
|
321
357
|
return { success: false, error: "source flow not found" };
|
|
322
358
|
}
|
|
323
359
|
if (fs.existsSync(toDir)) {
|
|
@@ -417,11 +453,11 @@ export function deleteFlowPipeline(workspaceRoot, flowId, flowSource, opts = {})
|
|
|
417
453
|
return { success: false, error: "invalid flowId" };
|
|
418
454
|
}
|
|
419
455
|
const archived = Boolean(opts.archived);
|
|
420
|
-
const
|
|
421
|
-
if (
|
|
422
|
-
return { success: false, error:
|
|
456
|
+
const dirRes = resolveFlowDirAbs(workspaceRoot, flowId, flowSource, { archived, userId: opts.userId });
|
|
457
|
+
if (dirRes.error || !dirRes.dir) {
|
|
458
|
+
return { success: false, error: dirRes.error || "找不到流水线" };
|
|
423
459
|
}
|
|
424
|
-
const flowDir =
|
|
460
|
+
const flowDir = dirRes.dir;
|
|
425
461
|
const guard = assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId, opts);
|
|
426
462
|
if (!guard.ok) return { success: false, error: guard.error };
|
|
427
463
|
try {
|
package/bin/lib/help.mjs
CHANGED
|
@@ -11,30 +11,20 @@ export function printHelp() {
|
|
|
11
11
|
AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code / Codex CLI 流式输出驱动 apply/replay。
|
|
12
12
|
|
|
13
13
|
用法:
|
|
14
|
-
agentflow login [--provider github|google] 登录 AgentFlow Hub(默认 GitHub)
|
|
15
|
-
agentflow logout 退出 Hub 登录
|
|
16
|
-
agentflow publish <FlowName> [--title <t>] [--description <d>] [--tags <t1,t2>] 发布流程到 Hub
|
|
17
|
-
agentflow list-remote [--search <q>] [--sort popular|trending] [--json] 浏览 Hub 上的流程
|
|
18
|
-
agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] 从 Hub 下载流程(默认 --user 安装到 ~/agentflow/pipelines/<id>;--workspace 安装到当前工程 .workspace/agentflow/pipelines/<id>;--raw 仅保留压缩包)
|
|
19
14
|
agentflow list 列出所有流水线
|
|
20
|
-
agentflow ui [--host <addr>] [--port <n>] [--
|
|
21
|
-
agentflow flow
|
|
15
|
+
agentflow ui [--host <addr>] [--port <n>] [--no-open] [--hide-community-links] 本地 HTTP:流水线列表 + React Flow 节点流程图编辑保存(默认 127.0.0.1:8765;可用 AGENTFLOW_UI_HOST)
|
|
16
|
+
agentflow flow dsl <export|import|lint|migrate> <FlowName|dir> [--out <dir>] 流程图与 workspace.flow.js 代码互转 / 静态校验 / 就地迁移
|
|
22
17
|
agentflow mcp 启动 AgentFlow MCP stdio server,供 Cursor/Codex 运行流程并读取 display 输出
|
|
23
|
-
agentflow scheduler start [--poll-ms <ms>] 启动定时执行调度器(读取各流水线 schedule.json)
|
|
24
|
-
agentflow scheduler status [--json] 查看定时执行配置与状态
|
|
25
|
-
agentflow scheduler cancel <FlowName> <uuid> 取消某次等待中的 watch/run
|
|
26
18
|
agentflow marketplace list [--json] 查看 workspace 本地节点市场
|
|
27
19
|
agentflow marketplace publish-node <dir> 发布本地节点包到 workspace market
|
|
28
|
-
agentflow
|
|
29
|
-
agentflow apply <FlowName> [uuid] 或 agentflow apply <uuid>(由 uuid 反查 pipeline)
|
|
20
|
+
agentflow apply <FlowName> [uuid] 已下线;请改用 Workspace Run
|
|
30
21
|
agentflow validate <FlowName> [uuid] 校验流程;终端下输出易读结果,--json 或管道时输出 JSON;传 uuid 时写入 runDir/intermediate/validation.json
|
|
31
|
-
agentflow resume <FlowName> <uuid> [instanceId]
|
|
32
|
-
agentflow replay [flowName] <uuid> <instanceId>
|
|
22
|
+
agentflow resume <FlowName> <uuid> [instanceId] 已下线;请改用 Workspace Run
|
|
23
|
+
agentflow replay [flowName] <uuid> <instanceId> 已下线;请改用 Workspace Run
|
|
33
24
|
agentflow run-status <flowName> <uuid> 输出该次运行的节点状态 JSON(供 UI 展示 success/pending 等角标)
|
|
34
25
|
agentflow extract-thinking <flowName> <uuid> 从该次 run 的 logs/log.txt 提取 thinking,写入 logs/thinking_by_session_and_nodes.md
|
|
35
26
|
agentflow extract-thinking -list 列出所有存在 logs/log.txt 的 run(可接 --json)
|
|
36
27
|
agentflow update-model-lists 拉取 Cursor / OpenCode / Claude Code / Codex 模型列表并写入 ~/agentflow/model-lists.json
|
|
37
|
-
agentflow write-flow <flowId> --json --flow-source <user|workspace> 从 stdin 读入 YAML 写入用户目录或工作区(builtin 已弃用,将视为 workspace)
|
|
38
28
|
agentflow --help
|
|
39
29
|
|
|
40
30
|
选项:
|
|
@@ -82,30 +72,20 @@ Apply/replay 脚本已打包在 agentflow 包中(bin/pipeline/)。
|
|
|
82
72
|
AgentFlow CLI — drive apply/replay with Cursor / OpenCode / Claude Code / Codex CLI streaming.
|
|
83
73
|
|
|
84
74
|
Usage:
|
|
85
|
-
agentflow login [--provider github|google] Login to AgentFlow Hub (default: GitHub)
|
|
86
|
-
agentflow logout Sign out of Hub
|
|
87
|
-
agentflow publish <FlowName> [--title <t>] [--description <d>] [--tags <t1,t2>] Publish flow to Hub
|
|
88
|
-
agentflow list-remote [--search <q>] [--sort popular|trending] [--json] Browse flows on Hub
|
|
89
|
-
agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] Download flow (default --user → ~/agentflow/pipelines/<id>; --workspace → current project's .workspace/agentflow/pipelines/<id>; --raw keeps the archive)
|
|
90
75
|
agentflow list List all pipelines
|
|
91
|
-
agentflow ui [--host <addr>] [--port <n>] [--
|
|
92
|
-
agentflow flow
|
|
76
|
+
agentflow ui [--host <addr>] [--port <n>] [--no-open] [--hide-community-links] Local HTTP: pipeline list + React Flow node diagram editor (default 127.0.0.1:8765; AGENTFLOW_UI_HOST supported)
|
|
77
|
+
agentflow flow dsl <export|import|lint|migrate> <FlowName|dir> [--out <dir>] Convert a graph to/from workspace.flow.js, lint it, or migrate in place
|
|
93
78
|
agentflow mcp Start the AgentFlow MCP stdio server for Cursor/Codex to run flows and read display outputs
|
|
94
|
-
agentflow scheduler start [--poll-ms <ms>] Start the scheduled-run scheduler (reads each pipeline schedule.json)
|
|
95
|
-
agentflow scheduler status [--json] Show scheduled-run configuration and state
|
|
96
|
-
agentflow scheduler cancel <FlowName> <uuid> Cancel a waiting watch/run
|
|
97
79
|
agentflow marketplace list [--json] Show workspace local marketplace packages
|
|
98
80
|
agentflow marketplace publish-node <dir> Publish a local node package to the workspace market
|
|
99
|
-
agentflow
|
|
100
|
-
agentflow apply <FlowName> [uuid] Or agentflow apply <uuid> (resolve pipeline from uuid)
|
|
81
|
+
agentflow apply <FlowName> [uuid] Retired; use Workspace Run instead
|
|
101
82
|
agentflow validate <FlowName> [uuid] Validate flow; readable output in terminal, JSON with --json or pipe; writes to runDir/intermediate/validation.json when uuid provided
|
|
102
|
-
agentflow resume <FlowName> <uuid> [instanceId]
|
|
103
|
-
agentflow replay [flowName] <uuid> <instanceId>
|
|
83
|
+
agentflow resume <FlowName> <uuid> [instanceId] Retired; use Workspace Run instead
|
|
84
|
+
agentflow replay [flowName] <uuid> <instanceId> Retired; use Workspace Run instead
|
|
104
85
|
agentflow run-status <flowName> <uuid> Output node status JSON for this run (for UI success/pending badges)
|
|
105
86
|
agentflow extract-thinking <flowName> <uuid> Extract thinking from run logs/log.txt, write to logs/thinking_by_session_and_nodes.md
|
|
106
87
|
agentflow extract-thinking -list List all runs with logs/log.txt (use --json)
|
|
107
88
|
agentflow update-model-lists Fetch Cursor / OpenCode / Claude Code / Codex model lists to ~/agentflow/model-lists.json
|
|
108
|
-
agentflow write-flow <flowId> --json --flow-source <user|workspace> Read YAML from stdin and write to user dir or workspace (builtin deprecated, treated as workspace)
|
|
109
89
|
agentflow --help
|
|
110
90
|
|
|
111
91
|
Options:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML 转义。
|
|
3
|
+
*
|
|
4
|
+
* 单独成模块是为了让 PRD workflow 那套服务端渲染不必反向依赖 ui-server——它和
|
|
5
|
+
* `injectHtmlBaseHref` 是仅有的两个使用者,各自 import 一份比互相牵扯干净。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 转义成可以放进属性值的文本。
|
|
10
|
+
* @param {unknown} value
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function htmlEscapeAttribute(value) {
|
|
14
|
+
return String(value || "")
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/"/g, """)
|
|
17
|
+
.replace(/</g, "<")
|
|
18
|
+
.replace(/>/g, ">");
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 两个最基础的 HTTP 工具。
|
|
3
|
+
*
|
|
4
|
+
* 独立成模块只有一个原因:路由拆分出去之后,`json(res, ...)` / `readBody(req)` 在两个文件
|
|
5
|
+
* 里都要以**同名标识符**出现——搬走的路由体一行都不改,靠的就是这个。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('http').ServerResponse} res
|
|
10
|
+
* @param {number} status
|
|
11
|
+
* @param {unknown} obj
|
|
12
|
+
*/
|
|
13
|
+
export function json(res, status, obj) {
|
|
14
|
+
const body = JSON.stringify(obj);
|
|
15
|
+
res.writeHead(status, {
|
|
16
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
17
|
+
"Content-Length": Buffer.byteLength(body),
|
|
18
|
+
});
|
|
19
|
+
res.end(body);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let total = 0;
|
|
26
|
+
let exceeded = false;
|
|
27
|
+
req.on("data", (chunk) => {
|
|
28
|
+
if (exceeded) return;
|
|
29
|
+
const value = Buffer.from(chunk);
|
|
30
|
+
total += value.length;
|
|
31
|
+
if (total > maxBytes) {
|
|
32
|
+
exceeded = true;
|
|
33
|
+
chunks.length = 0;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
chunks.push(value);
|
|
37
|
+
});
|
|
38
|
+
req.on("end", () => {
|
|
39
|
+
if (exceeded) {
|
|
40
|
+
const error = new Error(`Request body exceeds ${maxBytes} bytes`);
|
|
41
|
+
error.status = 413;
|
|
42
|
+
reject(error);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
46
|
+
});
|
|
47
|
+
req.on("error", reject);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const LEGACY_FLOW_EXECUTION_DISABLED = true;
|
|
2
|
+
|
|
3
|
+
export const LEGACY_FLOW_EXECUTION_MESSAGE =
|
|
4
|
+
"Legacy Start/End Pipeline execution has been retired. Use the Workspace graph with Run or Scheduled Run.";
|
|
5
|
+
|
|
6
|
+
export const LEGACY_FLOW_NODE_IDS = new Set(["control_start", "control_end"]);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 只有旧 Start/End 运行时实现、Workspace 运行时没有对应 handler 的节点类型。
|
|
10
|
+
*
|
|
11
|
+
* 这些定义留在 `builtin/nodes/` 只为让历史 flow.yaml / workspace.graph.json 仍能解析出
|
|
12
|
+
* 节点元数据;节点面板不再提供它们,避免用户新建出跑不了的图(拖出来只会落到通用
|
|
13
|
+
* agent 路径,拿不到文档承诺的语义)。
|
|
14
|
+
*
|
|
15
|
+
* 想恢复某个类型,就在 ui-server 的 workspace 运行循环里补上 handler,再从这里删掉。
|
|
16
|
+
*/
|
|
17
|
+
export const WORKSPACE_UNSUPPORTED_NODE_IDS = new Set([
|
|
18
|
+
// 循环 / 汇合:Workspace 运行计划是 DAG,有环会被直接拒绝
|
|
19
|
+
"control_anyOne",
|
|
20
|
+
"control_toBool",
|
|
21
|
+
"control_agent_toBool",
|
|
22
|
+
"control_interval_loop",
|
|
23
|
+
// 时序与取消
|
|
24
|
+
"control_delay",
|
|
25
|
+
"control_wait_until",
|
|
26
|
+
"control_cancelled",
|
|
27
|
+
// 人工卡点
|
|
28
|
+
"tool_user_check",
|
|
29
|
+
"tool_user_ask",
|
|
30
|
+
// 运行期键值 / 环境变量
|
|
31
|
+
"tool_load_key",
|
|
32
|
+
"tool_save_key",
|
|
33
|
+
"tool_get_env",
|
|
34
|
+
// 其它
|
|
35
|
+
"tool_print",
|
|
36
|
+
"tool_jenkins_build",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/** 节点面板与目录需要隐藏的全部 definitionId */
|
|
40
|
+
export const RETIRED_NODE_IDS = new Set([
|
|
41
|
+
...LEGACY_FLOW_NODE_IDS,
|
|
42
|
+
...WORKSPACE_UNSUPPORTED_NODE_IDS,
|
|
43
|
+
]);
|
package/bin/lib/locales/en.json
CHANGED
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"type_text": "Text",
|
|
107
107
|
"pipeline_header": "Pipeline",
|
|
108
108
|
"source_header": "Source",
|
|
109
|
-
"
|
|
109
|
+
"open_example_header": "Validate Command"
|
|
110
110
|
},
|
|
111
111
|
"composer": {
|
|
112
112
|
"edit_context": "## AgentFlow Edit Context",
|
package/bin/lib/locales/zh.json
CHANGED