@fieldwangai/agentflow 0.1.28 → 0.1.30
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/agents/agentflow-node-executor-code.md +3 -2
- package/agents/agentflow-node-executor-planning.md +3 -2
- package/agents/agentflow-node-executor-requirement.md +3 -2
- package/agents/agentflow-node-executor-test.md +3 -2
- package/agents/agentflow-node-executor-ui.md +3 -2
- package/agents/agentflow-node-executor.md +3 -2
- package/agents/en/agentflow-node-executor.md +3 -2
- package/agents/zh/agentflow-node-executor.md +3 -2
- package/bin/lib/agent-runners.mjs +38 -13
- package/bin/lib/api-runner.mjs +6 -3
- package/bin/lib/auth.mjs +240 -0
- package/bin/lib/catalog-agents.mjs +2 -2
- package/bin/lib/catalog-flows.mjs +192 -16
- package/bin/lib/composer-agent.mjs +21 -1
- package/bin/lib/composer-skill-router.mjs +10 -78
- package/bin/lib/flow-import.mjs +2 -2
- package/bin/lib/flow-write.mjs +20 -20
- package/bin/lib/help.mjs +2 -2
- package/bin/lib/locales/en.json +25 -1
- package/bin/lib/locales/zh.json +25 -1
- package/bin/lib/main.mjs +6 -1
- package/bin/lib/node-exec-context.mjs +5 -5
- package/bin/lib/node-execute.mjs +14 -9
- package/bin/lib/paths.mjs +64 -13
- package/bin/lib/recent-runs.mjs +2 -2
- package/bin/lib/run-node-statuses-from-disk.mjs +3 -3
- package/bin/lib/runtime-context.mjs +225 -0
- package/bin/lib/scheduler.mjs +41 -38
- package/bin/lib/skill-registry.mjs +145 -0
- package/bin/lib/ui-server.mjs +902 -57
- package/bin/lib/workspace-tree.mjs +4 -3
- package/bin/lib/workspace.mjs +9 -11
- package/bin/pipeline/build-node-prompt.mjs +29 -4
- package/bin/pipeline/get-exec-id.mjs +2 -2
- package/bin/pipeline/get-resolved-values.mjs +1 -0
- package/bin/pipeline/pre-process-node.mjs +306 -6
- package/bin/pipeline/validate-flow.mjs +2 -0
- package/builtin/nodes/agent_subAgent.md +7 -1
- package/builtin/nodes/control_cd_workspace.md +43 -0
- package/builtin/nodes/control_load_skills.md +48 -0
- package/builtin/nodes/display_ascii.md +17 -0
- package/builtin/nodes/display_markdown.md +17 -0
- package/builtin/nodes/display_mermaid.md +17 -0
- package/builtin/nodes/tool_git_checkout.md +54 -0
- package/builtin/nodes/tool_nodejs.md +8 -1
- package/builtin/nodes/tool_print.md +4 -1
- package/builtin/web-ui/dist/assets/index-NdVOJLL9.js +196 -0
- package/builtin/web-ui/dist/assets/index-naVI6LZj.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +2 -1
- package/skills/agentflow-flow-add-instances/SKILL.md +257 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +79 -0
- package/skills/agentflow-flow-recipes/SKILL.md +24 -0
- package/skills/agentflow-flow-recipes/references/recipes.md +63 -0
- package/skills/agentflow-flow-sync-ui/SKILL.md +59 -0
- package/skills/agentflow-node-reference/SKILL.md +25 -0
- package/skills/agentflow-node-reference/references/builtin-nodes.md +210 -0
- package/skills/agentflow-placeholder-reference/SKILL.md +24 -0
- package/skills/agentflow-placeholder-reference/references/placeholders.md +20 -0
- package/skills/agentflow-runtime-reference/SKILL.md +25 -0
- package/skills/agentflow-runtime-reference/references/runtime.md +64 -0
- package/builtin/web-ui/dist/assets/index-BeUBxIj1.js +0 -190
- package/builtin/web-ui/dist/assets/index-BzhdjOzb.css +0 -1
package/bin/lib/flow-write.mjs
CHANGED
|
@@ -68,11 +68,11 @@ export function buildEmptyUserFlowYaml(options = {}) {
|
|
|
68
68
|
* @param {string} workspaceRoot
|
|
69
69
|
* @param {FlowWriteSource} source
|
|
70
70
|
*/
|
|
71
|
-
function getPipelinesRootByWriteSource(workspaceRoot, source) {
|
|
71
|
+
function getPipelinesRootByWriteSource(workspaceRoot, source, opts = {}) {
|
|
72
72
|
if (source === "workspace") {
|
|
73
73
|
return path.join(path.resolve(workspaceRoot), PIPELINES_DIR);
|
|
74
74
|
}
|
|
75
|
-
return getUserPipelinesRoot();
|
|
75
|
+
return getUserPipelinesRoot(opts.userId);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
@@ -94,7 +94,7 @@ function resolveExistingWorkspaceFlowDir(workspaceRoot, flowId) {
|
|
|
94
94
|
* @param {FlowWriteSource} flowSource
|
|
95
95
|
* @returns {{ flowDir: string, error?: string }}
|
|
96
96
|
*/
|
|
97
|
-
export function resolveFlowDirForWrite(workspaceRoot, flowId, flowSource) {
|
|
97
|
+
export function resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
98
98
|
if (!workspaceRoot || !flowId) {
|
|
99
99
|
return { flowDir: "", error: "workspaceRoot and flowId are required" };
|
|
100
100
|
}
|
|
@@ -125,7 +125,7 @@ export function resolveFlowDirForWrite(workspaceRoot, flowId, flowSource) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
const pipelinesRoot = getPipelinesRootByWriteSource(workspaceRoot, flowSource);
|
|
128
|
+
const pipelinesRoot = getPipelinesRootByWriteSource(workspaceRoot, flowSource, opts);
|
|
129
129
|
const flowDir = path.join(pipelinesRoot, flowId);
|
|
130
130
|
const resolvedFlowDir = path.resolve(flowDir);
|
|
131
131
|
const baseWithSep = boundariesBase.endsWith(path.sep) ? boundariesBase : boundariesBase + path.sep;
|
|
@@ -148,7 +148,7 @@ export function resolveFlowDirForWrite(workspaceRoot, flowId, flowSource) {
|
|
|
148
148
|
* @param {FlowWriteSource} flowSource
|
|
149
149
|
* @returns {{ flowDir: string, error?: string }}
|
|
150
150
|
*/
|
|
151
|
-
export function resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource) {
|
|
151
|
+
export function resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
152
152
|
if (!workspaceRoot || !flowId) {
|
|
153
153
|
return { flowDir: "", error: "workspaceRoot and flowId are required" };
|
|
154
154
|
}
|
|
@@ -176,7 +176,7 @@ export function resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
const pipelinesRoot = getPipelinesRootByWriteSource(workspaceRoot, flowSource);
|
|
179
|
+
const pipelinesRoot = getPipelinesRootByWriteSource(workspaceRoot, flowSource, opts);
|
|
180
180
|
const flowDir = path.join(pipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId);
|
|
181
181
|
const resolvedFlowDir = path.resolve(flowDir);
|
|
182
182
|
const baseWithSep = boundariesBase.endsWith(path.sep) ? boundariesBase : boundariesBase + path.sep;
|
|
@@ -197,8 +197,8 @@ export function resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource
|
|
|
197
197
|
export function writeFlowYaml(workspaceRoot, flowId, flowSource, flowYaml, opts = {}) {
|
|
198
198
|
const archived = Boolean(opts.archived);
|
|
199
199
|
const { flowDir, error } = archived
|
|
200
|
-
? resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource)
|
|
201
|
-
: resolveFlowDirForWrite(workspaceRoot, flowId, flowSource);
|
|
200
|
+
? resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource, opts)
|
|
201
|
+
: resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
|
|
202
202
|
if (error) return { success: false, error };
|
|
203
203
|
try {
|
|
204
204
|
fs.mkdirSync(flowDir, { recursive: true });
|
|
@@ -217,11 +217,11 @@ export function writeFlowYaml(workspaceRoot, flowId, flowSource, flowYaml, opts
|
|
|
217
217
|
* @param {FlowWriteSource} flowSource
|
|
218
218
|
* @returns {{ success: true } | { success: false, error: string }}
|
|
219
219
|
*/
|
|
220
|
-
export function archiveFlowPipeline(workspaceRoot, flowId, flowSource) {
|
|
220
|
+
export function archiveFlowPipeline(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
221
221
|
if (flowSource !== "user" && flowSource !== "workspace") {
|
|
222
222
|
return { success: false, error: "仅支持用户目录或工作区流水线归档" };
|
|
223
223
|
}
|
|
224
|
-
const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, { archived: false });
|
|
224
|
+
const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, { archived: false, userId: opts.userId });
|
|
225
225
|
if (yamlRes.error || !yamlRes.path) {
|
|
226
226
|
return { success: false, error: yamlRes.error || "找不到流水线" };
|
|
227
227
|
}
|
|
@@ -230,7 +230,7 @@ export function archiveFlowPipeline(workspaceRoot, flowId, flowSource) {
|
|
|
230
230
|
if (fromDir.split(sep).includes(ARCHIVED_PIPELINES_DIR_NAME)) {
|
|
231
231
|
return { success: false, error: "该流水线已在归档目录中" };
|
|
232
232
|
}
|
|
233
|
-
const toRes = resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource);
|
|
233
|
+
const toRes = resolveArchivedFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
|
|
234
234
|
if (toRes.error || !toRes.flowDir) {
|
|
235
235
|
return { success: false, error: toRes.error || "无法解析归档路径" };
|
|
236
236
|
}
|
|
@@ -255,7 +255,7 @@ export function archiveFlowPipeline(workspaceRoot, flowId, flowSource) {
|
|
|
255
255
|
* @param {"user" | "workspace"} toSource
|
|
256
256
|
* @returns {{ success: true, flowSource: "user" | "workspace" } | { success: false, error: string }}
|
|
257
257
|
*/
|
|
258
|
-
export function moveFlowDirectory(workspaceRoot, flowId, fromSource, toSource) {
|
|
258
|
+
export function moveFlowDirectory(workspaceRoot, flowId, fromSource, toSource, opts = {}) {
|
|
259
259
|
if (fromSource === toSource) {
|
|
260
260
|
return { success: false, error: "fromSource and toSource must differ" };
|
|
261
261
|
}
|
|
@@ -267,16 +267,16 @@ export function moveFlowDirectory(workspaceRoot, flowId, fromSource, toSource) {
|
|
|
267
267
|
}
|
|
268
268
|
let fromDir;
|
|
269
269
|
if (fromSource === "workspace") {
|
|
270
|
-
const w = resolveFlowDirForWrite(workspaceRoot, flowId, "workspace");
|
|
270
|
+
const w = resolveFlowDirForWrite(workspaceRoot, flowId, "workspace", opts);
|
|
271
271
|
if (w.error || !w.flowDir) return { success: false, error: w.error || "invalid source path" };
|
|
272
272
|
fromDir = resolveExistingWorkspaceFlowDir(workspaceRoot, flowId);
|
|
273
273
|
if (!fromDir) return { success: false, error: "source flow not found" };
|
|
274
274
|
} else {
|
|
275
|
-
const fromRes = resolveFlowDirForWrite(workspaceRoot, flowId, fromSource);
|
|
275
|
+
const fromRes = resolveFlowDirForWrite(workspaceRoot, flowId, fromSource, opts);
|
|
276
276
|
if (fromRes.error || !fromRes.flowDir) return { success: false, error: fromRes.error || "invalid source path" };
|
|
277
277
|
fromDir = fromRes.flowDir;
|
|
278
278
|
}
|
|
279
|
-
const toRes = resolveFlowDirForWrite(workspaceRoot, flowId, toSource);
|
|
279
|
+
const toRes = resolveFlowDirForWrite(workspaceRoot, flowId, toSource, opts);
|
|
280
280
|
if (toRes.error || !toRes.flowDir) return { success: false, error: toRes.error || "invalid target path" };
|
|
281
281
|
const toDir = toRes.flowDir;
|
|
282
282
|
if (!fs.existsSync(path.join(fromDir, FLOW_YAML_FILENAME))) {
|
|
@@ -301,7 +301,7 @@ export function moveFlowDirectory(workspaceRoot, flowId, fromSource, toSource) {
|
|
|
301
301
|
* @param {string} flowId
|
|
302
302
|
* @returns {{ ok: true } | { ok: false, error: string }}
|
|
303
303
|
*/
|
|
304
|
-
function assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId) {
|
|
304
|
+
function assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId, opts = {}) {
|
|
305
305
|
let realDir;
|
|
306
306
|
try {
|
|
307
307
|
realDir = fs.realpathSync(flowDir);
|
|
@@ -316,9 +316,9 @@ function assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId)
|
|
|
316
316
|
const allowedRoots = [];
|
|
317
317
|
if (flowSource === "user") {
|
|
318
318
|
try {
|
|
319
|
-
allowedRoots.push(fs.realpathSync(getUserPipelinesRoot()));
|
|
319
|
+
allowedRoots.push(fs.realpathSync(getUserPipelinesRoot(opts.userId)));
|
|
320
320
|
} catch {
|
|
321
|
-
allowedRoots.push(path.resolve(getUserPipelinesRoot()));
|
|
321
|
+
allowedRoots.push(path.resolve(getUserPipelinesRoot(opts.userId)));
|
|
322
322
|
}
|
|
323
323
|
for (const rel of [PIPELINES_DIR, LEGACY_PIPELINES_DIR]) {
|
|
324
324
|
const base = path.join(root, rel);
|
|
@@ -379,12 +379,12 @@ export function deleteFlowPipeline(workspaceRoot, flowId, flowSource, opts = {})
|
|
|
379
379
|
return { success: false, error: "invalid flowId" };
|
|
380
380
|
}
|
|
381
381
|
const archived = Boolean(opts.archived);
|
|
382
|
-
const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, { archived });
|
|
382
|
+
const yamlRes = getFlowYamlAbs(workspaceRoot, flowId, flowSource, { archived, userId: opts.userId });
|
|
383
383
|
if (yamlRes.error || !yamlRes.path) {
|
|
384
384
|
return { success: false, error: yamlRes.error || "找不到流水线" };
|
|
385
385
|
}
|
|
386
386
|
const flowDir = path.dirname(yamlRes.path);
|
|
387
|
-
const guard = assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId);
|
|
387
|
+
const guard = assertFlowDirIsSafeToDelete(flowDir, workspaceRoot, flowSource, flowId, opts);
|
|
388
388
|
if (!guard.ok) return { success: false, error: guard.error };
|
|
389
389
|
try {
|
|
390
390
|
fs.rmSync(flowDir, { recursive: true, force: true });
|
package/bin/lib/help.mjs
CHANGED
|
@@ -17,7 +17,7 @@ AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code CLI 流式输出驱动
|
|
|
17
17
|
agentflow list-remote [--search <q>] [--sort popular|trending] [--json] 浏览 Hub 上的流程
|
|
18
18
|
agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] 从 Hub 下载流程(默认 --user 安装到 ~/agentflow/pipelines/<id>;--workspace 安装到当前工程 .workspace/agentflow/pipelines/<id>;--raw 仅保留压缩包)
|
|
19
19
|
agentflow list 列出所有流水线
|
|
20
|
-
agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] 本地 HTTP:流水线列表 + React Flow 节点流程图编辑保存(默认 127.0.0.1:8765;可用 AGENTFLOW_UI_HOST)
|
|
20
|
+
agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] [--hide-community-links] 本地 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
23
|
agentflow scheduler cancel <FlowName> <uuid> 取消某次等待中的 watch/run
|
|
@@ -86,7 +86,7 @@ Usage:
|
|
|
86
86
|
agentflow list-remote [--search <q>] [--sort popular|trending] [--json] Browse flows on Hub
|
|
87
87
|
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)
|
|
88
88
|
agentflow list List all pipelines
|
|
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)
|
|
89
|
+
agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] [--hide-community-links] Local HTTP: pipeline list + React Flow node diagram editor (default 127.0.0.1:8765; AGENTFLOW_UI_HOST supported)
|
|
90
90
|
agentflow scheduler start [--poll-ms <ms>] Start the scheduled-run scheduler (reads each pipeline schedule.json)
|
|
91
91
|
agentflow scheduler status [--json] Show scheduled-run configuration and state
|
|
92
92
|
agentflow scheduler cancel <FlowName> <uuid> Cancel a waiting watch/run
|
package/bin/lib/locales/en.json
CHANGED
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"composer": {
|
|
112
112
|
"edit_context": "## AgentFlow Edit Context",
|
|
113
113
|
"tool_nodejs_rules_title": "## tool_nodejs Node Writing Rules (Must Follow)",
|
|
114
|
-
"tool_nodejs_rules_body": "The core of `definitionId: tool_nodejs` is the **`script` field**—must write a complete executable shell/node command.\n- `script`: Pipeline spawns directly, stdout becomes result, exit code determines success/failure\n- `body`: When `script` exists, it serves only as human-readable comment, **will NOT be executed**\n- `script` supports `${}` placeholders referencing input slots and system variables (workspaceRoot, runDir, etc.), values are auto shell-quoted\n- **Do NOT** wrap `${workspaceRoot}` etc. with extra double quotes (e.g., `node \"${workspaceRoot}/...\"`); write `node ${workspaceRoot}/...` instead, otherwise path will contain extra quotes causing execution failure\n- **Forbidden** to only write natural language description in `body` without `script`—this leads to no executable code at runtime\n- If logic is too complex to write complete `script`, use `agent_subAgent` instead (also change definitionId)",
|
|
114
|
+
"tool_nodejs_rules_body": "The core of `definitionId: tool_nodejs` is the **`script` field**—must write a complete executable shell/node command.\n- `script`: Pipeline spawns directly, stdout becomes result, exit code determines success/failure\n- `body`: When `script` exists, it serves only as human-readable comment, **will NOT be executed**\n- `script` supports `${}` placeholders referencing input slots and system variables (workspaceRoot, pipelineWorkspace, runDir, etc.), values are auto shell-quoted; after CD Workspace, `${workspaceRoot}` is the current execution workspace and `${pipelineWorkspace}` is the pipeline workspace\n- **Do NOT** wrap `${workspaceRoot}` etc. with extra double quotes (e.g., `node \"${workspaceRoot}/...\"`); write `node ${workspaceRoot}/...` instead, otherwise path will contain extra quotes causing execution failure\n- **Forbidden** to only write natural language description in `body` without `script`—this leads to no executable code at runtime\n- If logic is too complex to write complete `script`, use `agent_subAgent` instead (also change definitionId)",
|
|
115
115
|
"task_title": "## Task",
|
|
116
116
|
"task_instruction": "Please complete only the above single task, no extra modifications. After completion, sync UI per context instructions.",
|
|
117
117
|
"validation_passed": "flow validation passed",
|
|
@@ -265,6 +265,18 @@
|
|
|
265
265
|
"displayName": "Agent ToBool",
|
|
266
266
|
"description": "AI-powered boolean judgment for non-deterministic scenarios"
|
|
267
267
|
},
|
|
268
|
+
"control_cd_workspace": {
|
|
269
|
+
"displayName": "CD Workspace",
|
|
270
|
+
"description": "Switch downstream execution to another workspace context without changing the pipeline workspace"
|
|
271
|
+
},
|
|
272
|
+
"control_load_skills": {
|
|
273
|
+
"displayName": "Load Skills",
|
|
274
|
+
"description": "Load SKILL.md files from the current workspace, pipeline workspace, explicit paths, or both"
|
|
275
|
+
},
|
|
276
|
+
"tool_git_checkout": {
|
|
277
|
+
"displayName": "Git Checkout",
|
|
278
|
+
"description": "Clone or update a Git repository and expose it as a workspace context"
|
|
279
|
+
},
|
|
268
280
|
"tool_nodejs": {
|
|
269
281
|
"displayName": "Node.js Script",
|
|
270
282
|
"description": "Execute Node.js script, success determined by exit code, stdout as result"
|
|
@@ -285,6 +297,18 @@
|
|
|
285
297
|
"displayName": "Load Key",
|
|
286
298
|
"description": "Load key-value from global storage"
|
|
287
299
|
},
|
|
300
|
+
"display_markdown": {
|
|
301
|
+
"displayName": "Markdown Display",
|
|
302
|
+
"description": "Render Markdown content on the Workspace canvas and pass the text downstream"
|
|
303
|
+
},
|
|
304
|
+
"display_mermaid": {
|
|
305
|
+
"displayName": "Mermaid Display",
|
|
306
|
+
"description": "Render Mermaid diagram source on the Workspace canvas and pass the source downstream"
|
|
307
|
+
},
|
|
308
|
+
"display_ascii": {
|
|
309
|
+
"displayName": "ASCII Display",
|
|
310
|
+
"description": "Render ASCII diagram text on the Workspace canvas and pass the text downstream"
|
|
311
|
+
},
|
|
288
312
|
"provide_str": {
|
|
289
313
|
"displayName": "Text",
|
|
290
314
|
"description": "Provide a text value directly, value will be passed to downstream as-is"
|
package/bin/lib/locales/zh.json
CHANGED
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"composer": {
|
|
112
112
|
"edit_context": "## AgentFlow 编辑上下文",
|
|
113
113
|
"tool_nodejs_rules_title": "## tool_nodejs 节点编写规则(必须遵守)",
|
|
114
|
-
"tool_nodejs_rules_body": "`definitionId: tool_nodejs` 的核心是 **`script` 字段**——必须写完整可执行的 shell/node 命令。\n- **类型选择自检**(写 script 前先问自己):这事**确定性**吗?相同输入永远产出相同输出、可用普通代码完整描述?是 → 继续写 script;否(需要语义理解/代码翻译/LLM 推理)→ **该节点选错了类型**,应改 `definitionId: agent_subAgent`,删 `script` 字段,任务描述写到 `body`。反例:『Android→RN 页面转换』『代码 review』『生成测试用例』必须 agent。\n- `script`:流水线直接 spawn 执行,stdout 作为 result,exit code 决定成败\n- `body`:有 `script` 时仅作人类可读注释,**不会被执行**\n- `script` 支持 `${}` 占位符引用 input 槽位和系统变量(workspaceRoot、runDir 等),值自动 shell-quote
|
|
114
|
+
"tool_nodejs_rules_body": "`definitionId: tool_nodejs` 的核心是 **`script` 字段**——必须写完整可执行的 shell/node 命令。\n- **类型选择自检**(写 script 前先问自己):这事**确定性**吗?相同输入永远产出相同输出、可用普通代码完整描述?是 → 继续写 script;否(需要语义理解/代码翻译/LLM 推理)→ **该节点选错了类型**,应改 `definitionId: agent_subAgent`,删 `script` 字段,任务描述写到 `body`。反例:『Android→RN 页面转换』『代码 review』『生成测试用例』必须 agent。\n- `script`:流水线直接 spawn 执行,stdout 作为 result,exit code 决定成败\n- `body`:有 `script` 时仅作人类可读注释,**不会被执行**\n- `script` 支持 `${}` 占位符引用 input 槽位和系统变量(workspaceRoot、pipelineWorkspace、runDir 等),值自动 shell-quote;接了 CD Workspace 后 `${workspaceRoot}` 表示当前执行工作区,`${pipelineWorkspace}` 表示流水线工作区\n- **不要**再对 `${workspaceRoot}` 等占位符外包双引号(如 `node \"${workspaceRoot}/...\"`);应写 `node ${workspaceRoot}/...`,否则路径会含多余引号导致执行失败\n- **禁止**只在 `body` 中写自然语言描述而不写 `script`——这会导致节点运行时无代码可执行",
|
|
115
115
|
"task_title": "## 任务",
|
|
116
116
|
"task_instruction": "请只完成上述单一任务,不要做额外修改。完成后按上下文中的指引同步 UI。",
|
|
117
117
|
"validation_passed": "flow 校验已通过",
|
|
@@ -265,6 +265,18 @@
|
|
|
265
265
|
"displayName": "AI 转布尔",
|
|
266
266
|
"description": "由 AI 判断输入内容的布尔含义,适用于不确定性场景"
|
|
267
267
|
},
|
|
268
|
+
"control_cd_workspace": {
|
|
269
|
+
"displayName": "CD 工作区",
|
|
270
|
+
"description": "切换下游节点的执行工作区上下文,不改变流水线所在工作区"
|
|
271
|
+
},
|
|
272
|
+
"control_load_skills": {
|
|
273
|
+
"displayName": "加载 Skills",
|
|
274
|
+
"description": "从当前工作区、流水线工作区、显式路径或组合来源加载 SKILL.md"
|
|
275
|
+
},
|
|
276
|
+
"tool_git_checkout": {
|
|
277
|
+
"displayName": "Git 拉取",
|
|
278
|
+
"description": "克隆或更新 Git 仓库,并输出可供下游使用的工作区上下文"
|
|
279
|
+
},
|
|
268
280
|
"tool_nodejs": {
|
|
269
281
|
"displayName": "Node.js 脚本",
|
|
270
282
|
"description": "执行 Node.js 脚本,以 exit code 判断成败,stdout 作为结果"
|
|
@@ -285,6 +297,18 @@
|
|
|
285
297
|
"displayName": "加载键值",
|
|
286
298
|
"description": "从全局存储加载键值"
|
|
287
299
|
},
|
|
300
|
+
"display_markdown": {
|
|
301
|
+
"displayName": "Markdown 展示",
|
|
302
|
+
"description": "在 Workspace 画布中渲染 Markdown 内容,并将文本继续传给下游"
|
|
303
|
+
},
|
|
304
|
+
"display_mermaid": {
|
|
305
|
+
"displayName": "Mermaid 展示",
|
|
306
|
+
"description": "在 Workspace 画布中渲染 Mermaid 图,并将源码继续传给下游"
|
|
307
|
+
},
|
|
308
|
+
"display_ascii": {
|
|
309
|
+
"displayName": "ASCII 图展示",
|
|
310
|
+
"description": "在 Workspace 画布中渲染等宽 ASCII 图,并将文本继续传给下游"
|
|
311
|
+
},
|
|
288
312
|
"provide_str": {
|
|
289
313
|
"displayName": "文本",
|
|
290
314
|
"description": "直接提供一段文本,value 会原样供下游引用"
|
package/bin/lib/main.mjs
CHANGED
|
@@ -346,6 +346,7 @@ export async function main() {
|
|
|
346
346
|
let host = process.env.AGENTFLOW_UI_HOST || "127.0.0.1";
|
|
347
347
|
let schedulerEnabled = false;
|
|
348
348
|
let schedulerPollMs;
|
|
349
|
+
let hideCommunityLinks = /^(1|true|yes|on)$/i.test(String(process.env.AGENTFLOW_HIDE_COMMUNITY_LINKS || ""));
|
|
349
350
|
const portIdx = argv.indexOf("--port");
|
|
350
351
|
if (portIdx >= 0 && argv[portIdx + 1]) {
|
|
351
352
|
port = parseInt(argv[portIdx + 1], 10);
|
|
@@ -371,13 +372,17 @@ export async function main() {
|
|
|
371
372
|
}
|
|
372
373
|
const noOpen = argv.includes("--no-open");
|
|
373
374
|
if (noOpen) argv.splice(argv.indexOf("--no-open"), 1);
|
|
375
|
+
if (argv.includes("--hide-community-links")) {
|
|
376
|
+
hideCommunityLinks = true;
|
|
377
|
+
argv.splice(argv.indexOf("--hide-community-links"), 1);
|
|
378
|
+
}
|
|
374
379
|
if (Number.isNaN(port) || port <= 0 || port > 65535) {
|
|
375
380
|
throw new Error("Invalid --port (use 1–65535)");
|
|
376
381
|
}
|
|
377
382
|
if (!host) {
|
|
378
383
|
throw new Error("Invalid --host");
|
|
379
384
|
}
|
|
380
|
-
await startUiServer({ workspaceRoot, port, host });
|
|
385
|
+
await startUiServer({ workspaceRoot, port, host, hideCommunityLinks });
|
|
381
386
|
if (schedulerEnabled) {
|
|
382
387
|
startScheduler(workspaceRoot, { pollMs: schedulerPollMs }).catch((e) => {
|
|
383
388
|
log.error("Scheduler failed: " + ((e && e.message) || String(e)));
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
* @param {string} [runId] - specific uuid; if empty, picks the latest run
|
|
19
19
|
* @returns {{ ok: boolean, rounds: Array, runId?: string, error?: string }}
|
|
20
20
|
*/
|
|
21
|
-
export function getNodeExecContext(workspaceRoot, flowId, instanceId, runId) {
|
|
21
|
+
export function getNodeExecContext(workspaceRoot, flowId, instanceId, runId, opts = {}) {
|
|
22
22
|
let uuid = runId;
|
|
23
23
|
let runDir = "";
|
|
24
24
|
if (uuid) {
|
|
25
|
-
runDir = getRunDirCandidates(workspaceRoot, flowId, uuid).find((p) => fs.existsSync(p)) || "";
|
|
25
|
+
runDir = getRunDirCandidates(workspaceRoot, flowId, uuid, opts).find((p) => fs.existsSync(p)) || "";
|
|
26
26
|
} else {
|
|
27
|
-
const latest = findLatestRunDir(workspaceRoot, flowId);
|
|
27
|
+
const latest = findLatestRunDir(workspaceRoot, flowId, opts);
|
|
28
28
|
uuid = latest.uuid;
|
|
29
29
|
runDir = latest.runDir;
|
|
30
30
|
}
|
|
@@ -38,9 +38,9 @@ export function getNodeExecContext(workspaceRoot, flowId, instanceId, runId) {
|
|
|
38
38
|
return { ok: true, rounds, runId: uuid };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function findLatestRunDir(workspaceRoot, flowId) {
|
|
41
|
+
function findLatestRunDir(workspaceRoot, flowId, opts = {}) {
|
|
42
42
|
const roots = [
|
|
43
|
-
path.join(getFlowRuntimeRoot(workspaceRoot, flowId), "runBuild"),
|
|
43
|
+
path.join(getFlowRuntimeRoot(workspaceRoot, flowId, opts), "runBuild"),
|
|
44
44
|
path.join(getWorkspaceRunBuildRoot(workspaceRoot), flowId),
|
|
45
45
|
path.join(getLegacyUserRunBuildRoot(), flowId),
|
|
46
46
|
];
|
package/bin/lib/node-execute.mjs
CHANGED
|
@@ -152,11 +152,11 @@ async function healToolNodejsWithAI(workspaceRoot, flowName, uuid, instanceId, r
|
|
|
152
152
|
* 协议:与 run-tool-nodejs.mjs 一致——validate-script-output 解析 stdout;
|
|
153
153
|
* JSON 时 message 的每个键写入同名 output 槽位(含 result);纯文本则等价于 message.result。
|
|
154
154
|
*/
|
|
155
|
-
async function executeToolNodejsInline(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId, healOptions) {
|
|
155
|
+
async function executeToolNodejsInline(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId, healOptions, execWorkspaceRoot = workspaceRoot) {
|
|
156
156
|
let lastError;
|
|
157
157
|
for (let attempt = 1; attempt <= TOOL_NODEJS_MAX_RETRIES + 1; attempt++) {
|
|
158
158
|
try {
|
|
159
|
-
executeToolNodejsOnce(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId);
|
|
159
|
+
executeToolNodejsOnce(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId, execWorkspaceRoot);
|
|
160
160
|
return;
|
|
161
161
|
} catch (err) {
|
|
162
162
|
lastError = err;
|
|
@@ -200,21 +200,22 @@ function persistToolNodejsStderr(outputDir, instanceId, execId, stderr) {
|
|
|
200
200
|
} catch (_) {}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
function executeToolNodejsOnce(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId) {
|
|
203
|
+
function executeToolNodejsOnce(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId, execWorkspaceRoot = workspaceRoot) {
|
|
204
204
|
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
205
205
|
const outputDir = path.join(runDir, outputDirForNode(instanceId));
|
|
206
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
206
207
|
|
|
207
208
|
const { argv, commandLine: normalized } = nodeToolCommandToArgv(resolvedScript);
|
|
208
209
|
let child;
|
|
209
210
|
if (/^node\s/i.test(String(normalized).trim()) && argv.length >= 1) {
|
|
210
211
|
child = spawnSync(process.execPath, argv, {
|
|
211
|
-
cwd:
|
|
212
|
+
cwd: execWorkspaceRoot,
|
|
212
213
|
shell: false,
|
|
213
214
|
stdio: ["inherit", "pipe", "pipe"],
|
|
214
215
|
});
|
|
215
216
|
} else {
|
|
216
217
|
child = spawnSync(normalized, [], {
|
|
217
|
-
cwd:
|
|
218
|
+
cwd: execWorkspaceRoot,
|
|
218
219
|
shell: true,
|
|
219
220
|
stdio: ["inherit", "pipe", "pipe"],
|
|
220
221
|
});
|
|
@@ -224,8 +225,6 @@ function executeToolNodejsOnce(workspaceRoot, flowName, uuid, instanceId, resolv
|
|
|
224
225
|
const stderr = child.stderr?.toString("utf-8") ?? "";
|
|
225
226
|
const exitCode = child.status ?? 1;
|
|
226
227
|
|
|
227
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
228
|
-
|
|
229
228
|
// 直接写文件模式:stdout 为空 + exit 0 → 脚本已自行写入 output 文件,无需解析
|
|
230
229
|
if (!stdout.trim() && exitCode === 0) {
|
|
231
230
|
persistToolNodejsStderr(outputDir, instanceId, execId, stderr);
|
|
@@ -310,6 +309,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
310
309
|
const { definitionId, directCommand, resolvedScript, promptPath, nodeContext, taskBody, resultPath, subagent } = preOutput;
|
|
311
310
|
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
312
311
|
const intermediatePath = runDir;
|
|
312
|
+
const execWorkspaceRoot = path.resolve(preOutput.workspaceContext?.workspaceRoot || preOutput.workspaceContext?.cwd || workspaceRoot);
|
|
313
313
|
|
|
314
314
|
if (definitionId && LOCAL_ONLY_DEFINITION_IDS.has(definitionId)) {
|
|
315
315
|
return;
|
|
@@ -331,7 +331,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
331
331
|
directCommand: resolvedScript,
|
|
332
332
|
});
|
|
333
333
|
try {
|
|
334
|
-
await executeToolNodejsInline(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId, healOptions);
|
|
334
|
+
await executeToolNodejsInline(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId, healOptions, execWorkspaceRoot);
|
|
335
335
|
emitEvent(workspaceRoot, flowName, uuid, {
|
|
336
336
|
event: "direct-command-done",
|
|
337
337
|
instanceId,
|
|
@@ -356,7 +356,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
356
356
|
directCommand,
|
|
357
357
|
});
|
|
358
358
|
try {
|
|
359
|
-
const result = spawnSync(directCommand, [], { cwd:
|
|
359
|
+
const result = spawnSync(directCommand, [], { cwd: execWorkspaceRoot, shell: true, stdio: "inherit" });
|
|
360
360
|
if (result.status !== 0) {
|
|
361
361
|
emitEvent(workspaceRoot, flowName, uuid, {
|
|
362
362
|
event: "direct-command-failed",
|
|
@@ -398,6 +398,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
398
398
|
resultPathRel: resultPath ?? null,
|
|
399
399
|
modelCli: cli,
|
|
400
400
|
model: model ?? null,
|
|
401
|
+
execWorkspaceRoot,
|
|
401
402
|
});
|
|
402
403
|
try {
|
|
403
404
|
if (cli === "api") {
|
|
@@ -409,6 +410,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
409
410
|
onToolCall: options.onToolCall,
|
|
410
411
|
flowName,
|
|
411
412
|
uuid,
|
|
413
|
+
execWorkspaceRoot,
|
|
412
414
|
},
|
|
413
415
|
);
|
|
414
416
|
} else if (cli === "opencode") {
|
|
@@ -424,6 +426,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
424
426
|
onToolCall: options.onToolCall,
|
|
425
427
|
flowName,
|
|
426
428
|
uuid,
|
|
429
|
+
execWorkspaceRoot,
|
|
427
430
|
},
|
|
428
431
|
);
|
|
429
432
|
} else if (cli === "claude-code") {
|
|
@@ -439,6 +442,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
439
442
|
onToolCall: options.onToolCall,
|
|
440
443
|
flowName,
|
|
441
444
|
uuid,
|
|
445
|
+
execWorkspaceRoot,
|
|
442
446
|
},
|
|
443
447
|
);
|
|
444
448
|
} else {
|
|
@@ -454,6 +458,7 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
|
|
|
454
458
|
onToolCall: options.onToolCall,
|
|
455
459
|
flowName,
|
|
456
460
|
uuid,
|
|
461
|
+
execWorkspaceRoot,
|
|
457
462
|
},
|
|
458
463
|
);
|
|
459
464
|
}
|
package/bin/lib/paths.mjs
CHANGED
|
@@ -34,6 +34,56 @@ export function getAgentflowDataRoot() {
|
|
|
34
34
|
return path.join(os.homedir(), "agentflow");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export const AGENTFLOW_DEFAULT_USER_ID = "";
|
|
38
|
+
|
|
39
|
+
export function sanitizeAgentflowUserId(userId) {
|
|
40
|
+
const raw = userId == null ? "" : String(userId).trim();
|
|
41
|
+
if (!raw) return AGENTFLOW_DEFAULT_USER_ID;
|
|
42
|
+
const normalized = raw.toLowerCase();
|
|
43
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(normalized)) return AGENTFLOW_DEFAULT_USER_ID;
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getAgentflowUserDataRoot(userId) {
|
|
48
|
+
const safe = sanitizeAgentflowUserId(userId ?? process.env.AGENTFLOW_USER_ID);
|
|
49
|
+
if (!safe) return getAgentflowDataRoot();
|
|
50
|
+
return path.join(getAgentflowDataRoot(), "users", safe);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function listAgentflowUserIds() {
|
|
54
|
+
const usersRoot = path.join(getAgentflowDataRoot(), "users");
|
|
55
|
+
try {
|
|
56
|
+
if (!fs.existsSync(usersRoot) || !fs.statSync(usersRoot).isDirectory()) return [];
|
|
57
|
+
return fs.readdirSync(usersRoot, { withFileTypes: true })
|
|
58
|
+
.filter((entry) => entry.isDirectory())
|
|
59
|
+
.map((entry) => sanitizeAgentflowUserId(entry.name))
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.sort((a, b) => a.localeCompare(b));
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getAgentflowUserContexts() {
|
|
68
|
+
const ids = listAgentflowUserIds();
|
|
69
|
+
return [{}, ...ids.map((userId) => ({ userId }))];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveUniqueUserPipelineDir(flowName) {
|
|
73
|
+
const name = flowName == null ? "" : String(flowName).trim();
|
|
74
|
+
if (!name) return null;
|
|
75
|
+
const matches = [];
|
|
76
|
+
for (const userId of listAgentflowUserIds()) {
|
|
77
|
+
const dir = path.join(getAgentflowDataRoot(), "users", userId, "pipelines", name);
|
|
78
|
+
try {
|
|
79
|
+
if (fs.existsSync(path.join(dir, "flow.yaml"))) matches.push(dir);
|
|
80
|
+
} catch {
|
|
81
|
+
/* ignore unreadable user dirs */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return matches.length === 1 ? matches[0] : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
37
87
|
/** 项目内 runBuild 根目录:`<workspaceRoot>/.workspace/agentflow/runBuild`(legacy:写入路径已迁至 `<flowDir>/runBuild`,仅用于兼容读取) */
|
|
38
88
|
export function getWorkspaceRunBuildRoot(workspaceRoot) {
|
|
39
89
|
const root =
|
|
@@ -52,23 +102,21 @@ export function getLegacyUserRunBuildRoot() {
|
|
|
52
102
|
* 统一 runtime root:每个 flow 的 pipeline 源、scripts、runBuild 共用一个根目录。
|
|
53
103
|
* - 若 `~/agentflow/pipelines/<name>/flow.yaml` 存在 → user-scope:`~/agentflow/pipelines/<name>`
|
|
54
104
|
* - 若 `<ws>/.workspace/agentflow/pipelines/<name>/flow.yaml` 存在 → workspace-scope:`<ws>/.workspace/agentflow/pipelines/<name>`
|
|
55
|
-
* - archived(`_archived/<name>`)按对应 scope 返回
|
|
56
105
|
* - 其他(builtin 只读 / 不存在)→ 默认 user-scope 路径(首次 run 时自动创建,builtin 源仍从包内读取但 runBuild 落到用户目录)
|
|
106
|
+
* 归档 flow 不参与普通 runtime root 解析,避免同名 archived flow 被误当作活跃 flow。
|
|
57
107
|
*/
|
|
58
|
-
export function getFlowRuntimeRoot(workspaceRoot, flowName) {
|
|
108
|
+
export function getFlowRuntimeRoot(workspaceRoot, flowName, opts = {}) {
|
|
59
109
|
const root =
|
|
60
110
|
workspaceRoot != null && String(workspaceRoot).trim() !== ""
|
|
61
111
|
? path.resolve(String(workspaceRoot))
|
|
62
112
|
: process.cwd();
|
|
63
|
-
const userRoot = getUserPipelinesRoot();
|
|
113
|
+
const userRoot = getUserPipelinesRoot(opts.userId);
|
|
64
114
|
const userDir = path.join(userRoot, flowName);
|
|
65
115
|
if (fs.existsSync(path.join(userDir, "flow.yaml"))) return userDir;
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
116
|
+
const inferredUserDir = resolveUniqueUserPipelineDir(flowName);
|
|
117
|
+
if (inferredUserDir) return inferredUserDir;
|
|
68
118
|
const wsDir = path.join(root, PIPELINES_DIR, flowName);
|
|
69
119
|
if (fs.existsSync(path.join(wsDir, "flow.yaml"))) return wsDir;
|
|
70
|
-
const wsArchived = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowName);
|
|
71
|
-
if (fs.existsSync(path.join(wsArchived, "flow.yaml"))) return wsArchived;
|
|
72
120
|
// builtin / legacy / 尚未落盘 → 默认 user 目录,runBuild 首次写入时创建
|
|
73
121
|
return userDir;
|
|
74
122
|
}
|
|
@@ -78,8 +126,8 @@ export function getFlowRuntimeRoot(workspaceRoot, flowName) {
|
|
|
78
126
|
* 新运行走 `<flowRuntimeRoot>/runBuild/<uuid>`;
|
|
79
127
|
* 若该 uuid 在旧位置(legacy workspace/user runBuild 根)已存在,则返回旧位置,保留 resume 兼容。
|
|
80
128
|
*/
|
|
81
|
-
export function getRunDir(workspaceRoot, flowName, uuid) {
|
|
82
|
-
const candidates = getRunDirCandidates(workspaceRoot, flowName, uuid);
|
|
129
|
+
export function getRunDir(workspaceRoot, flowName, uuid, opts = {}) {
|
|
130
|
+
const candidates = getRunDirCandidates(workspaceRoot, flowName, uuid, opts);
|
|
83
131
|
for (const c of candidates) {
|
|
84
132
|
if (fs.existsSync(c)) return c;
|
|
85
133
|
}
|
|
@@ -93,9 +141,9 @@ export function getRunDir(workspaceRoot, flowName, uuid) {
|
|
|
93
141
|
* 2. 旧 workspace:<ws>/.workspace/agentflow/runBuild/<flow>/<uuid>
|
|
94
142
|
* 3. 旧 user:~/agentflow/runBuild/<flow>/<uuid>
|
|
95
143
|
*/
|
|
96
|
-
export function getRunDirCandidates(workspaceRoot, flowName, uuid) {
|
|
144
|
+
export function getRunDirCandidates(workspaceRoot, flowName, uuid, opts = {}) {
|
|
97
145
|
const candidates = [
|
|
98
|
-
path.join(getFlowRuntimeRoot(workspaceRoot, flowName), "runBuild", uuid),
|
|
146
|
+
path.join(getFlowRuntimeRoot(workspaceRoot, flowName, opts), "runBuild", uuid),
|
|
99
147
|
path.join(getWorkspaceRunBuildRoot(workspaceRoot), flowName, uuid),
|
|
100
148
|
path.join(getLegacyUserRunBuildRoot(), flowName, uuid),
|
|
101
149
|
];
|
|
@@ -110,8 +158,8 @@ export function getRunDirCandidates(workspaceRoot, flowName, uuid) {
|
|
|
110
158
|
return out;
|
|
111
159
|
}
|
|
112
160
|
|
|
113
|
-
export function getUserPipelinesRoot() {
|
|
114
|
-
return path.join(
|
|
161
|
+
export function getUserPipelinesRoot(userId) {
|
|
162
|
+
return path.join(getAgentflowUserDataRoot(userId), "pipelines");
|
|
115
163
|
}
|
|
116
164
|
|
|
117
165
|
export function getReferenceRootAbs() {
|
|
@@ -204,8 +252,11 @@ export const LOCAL_ONLY_DEFINITION_IDS = new Set([
|
|
|
204
252
|
"control_deadline",
|
|
205
253
|
"control_cancelled",
|
|
206
254
|
"control_interval_loop",
|
|
255
|
+
"control_cd_workspace",
|
|
256
|
+
"control_load_skills",
|
|
207
257
|
"control_start",
|
|
208
258
|
"control_end",
|
|
259
|
+
"tool_git_checkout",
|
|
209
260
|
"tool_print",
|
|
210
261
|
"tool_user_check",
|
|
211
262
|
"tool_user_ask",
|
package/bin/lib/recent-runs.mjs
CHANGED
|
@@ -151,9 +151,9 @@ function mapFlowSource(src) {
|
|
|
151
151
|
* @param {string} workspaceRoot
|
|
152
152
|
* @returns {Array<{ flowId: string, flowSource: 'workspace'|'user', runId: string, at: number, durationMs: number, endedAt: number|null, status: 'success'|'failed'|'running'|'stopped'|'interrupted'|'unknown' }>}
|
|
153
153
|
*/
|
|
154
|
-
export function listRecentRunsFromDisk(workspaceRoot) {
|
|
154
|
+
export function listRecentRunsFromDisk(workspaceRoot, opts = {}) {
|
|
155
155
|
const out = [];
|
|
156
|
-
for (const { flowName, uuid, runDir, source } of listAllRunDirs(workspaceRoot)) {
|
|
156
|
+
for (const { flowName, uuid, runDir, source } of listAllRunDirs(workspaceRoot, opts)) {
|
|
157
157
|
let at = readKeyFromMemory(runDir, "runStartTime");
|
|
158
158
|
if (at == null) {
|
|
159
159
|
try {
|
|
@@ -36,8 +36,8 @@ function parseElapsedMsLine(filePath) {
|
|
|
36
36
|
* @param {string} uuid
|
|
37
37
|
* @returns {Record<string, { status: string, elapsed?: string }>}
|
|
38
38
|
*/
|
|
39
|
-
export function getRunNodeStatusesFromDisk(workspaceRoot, flowName, uuid) {
|
|
40
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
39
|
+
export function getRunNodeStatusesFromDisk(workspaceRoot, flowName, uuid, opts = {}) {
|
|
40
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid, opts);
|
|
41
41
|
const flowJsonPath = path.join(runDir, "intermediate", "flow.json");
|
|
42
42
|
if (!fs.existsSync(flowJsonPath)) return {};
|
|
43
43
|
|
|
@@ -51,7 +51,7 @@ export function getRunNodeStatusesFromDisk(workspaceRoot, flowName, uuid) {
|
|
|
51
51
|
|
|
52
52
|
const order = Array.isArray(flow.order) ? flow.order : [];
|
|
53
53
|
const nodeDefinitions = flow.nodeDefinitions && typeof flow.nodeDefinitions === "object" ? flow.nodeDefinitions : {};
|
|
54
|
-
const execIdMap = loadAllExecIds(workspaceRoot, flowName, uuid, order);
|
|
54
|
+
const execIdMap = loadAllExecIds(workspaceRoot, flowName, uuid, order, opts);
|
|
55
55
|
const intermediateDir = path.join(runDir, "intermediate");
|
|
56
56
|
/** @type {Record<string, { status: string, elapsed?: string }>} */
|
|
57
57
|
const out = {};
|