@fieldwangai/agentflow 0.1.29 → 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 +1 -1
- package/skills/agentflow-flow-recipes/SKILL.md +24 -0
- package/skills/agentflow-flow-recipes/references/recipes.md +63 -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-0vJxkTJz.css +0 -1
- package/builtin/web-ui/dist/assets/index-h69bpxLI.js +0 -190
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
以代码实现为核心:理清需求与接口,编写或修改代码,确保可运行、通过类型检查与项目规范,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
先做规划(目标拆解、步骤与依赖、可选方案),再按规划执行并产出结果,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
侧重需求理解与拆解,按上述任务完成执行,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
侧重测试与验证,执行并产出结果,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
理解设计稿或规格(Figma、标注、描述),实现或调整组件与样式,保证布局、间距、层级、断点与 RTL 等与设计一致;必要时做走查与修正,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
按上述任务完成执行,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ You are a flow node executor. Complete the work described in the node context an
|
|
|
11
11
|
|
|
12
12
|
**Only reference the variables in this section** during execution. Do not derive or concatenate paths on your own:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot: ${workspaceRoot} (workspace root
|
|
14
|
+
- workspaceRoot: ${workspaceRoot} (current execution workspace root; may be switched by a CD Workspace node)
|
|
15
|
+
- pipelineWorkspace: ${pipelineWorkspace} (pipeline workspace; use this when writing AgentFlow results)
|
|
15
16
|
- flowName: ${flowName}
|
|
16
17
|
- uuid: ${uuid}
|
|
17
18
|
- instanceId: ${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
Complete the task as described above. If the node involves file writing operations, they can be executed. Exit when done — the system automatically marks the result as success. **Only if the task explicitly fails**, run the following command to report failure (`agentflow` is a CLI command available directly in the terminal):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"reason for failure"}'
|
|
32
33
|
```
|
|
@@ -11,7 +11,8 @@ readonly: true
|
|
|
11
11
|
|
|
12
12
|
执行时**只引用本节的变量**,勿自行推导或拼接路径:
|
|
13
13
|
|
|
14
|
-
- workspaceRoot:${workspaceRoot}
|
|
14
|
+
- workspaceRoot:${workspaceRoot}(当前执行工作区根目录,可能由 CD Workspace 节点切换)
|
|
15
|
+
- pipelineWorkspace:${pipelineWorkspace}(流水线所在工作区,写 AgentFlow 结果时使用)
|
|
15
16
|
- flowName:${flowName}
|
|
16
17
|
- uuid:${uuid}
|
|
17
18
|
- instanceId:${instanceId}
|
|
@@ -28,5 +29,5 @@ ${taskBody}
|
|
|
28
29
|
|
|
29
30
|
按上述任务完成执行,节点中如有写入文件的操作可以执行。任务完成后直接退出,结果由系统自动标记成功。**仅当任务明确失败时**,执行以下命令报告失败(`agentflow` 是可直接在终端运行的 CLI 命令):
|
|
30
31
|
```bash
|
|
31
|
-
agentflow apply -ai write-result ${
|
|
32
|
+
agentflow apply -ai write-result ${pipelineWorkspace} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
|
|
32
33
|
```
|
|
@@ -10,6 +10,16 @@ import { appendRunLogLine } from "./run-events.mjs";
|
|
|
10
10
|
import { writeWithPrefix } from "./terminal.mjs";
|
|
11
11
|
import { t } from "./i18n.mjs";
|
|
12
12
|
|
|
13
|
+
function shouldPassCursorModelArg(model) {
|
|
14
|
+
const text = String(model || "").trim();
|
|
15
|
+
return text !== "" && !/^auto$/i.test(text);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function childEnv(options = {}, extra = {}) {
|
|
19
|
+
const optEnv = options && options.env && typeof options.env === "object" ? options.env : {};
|
|
20
|
+
return { ...process.env, ...optEnv, ...extra };
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
/**
|
|
14
24
|
* Run Cursor CLI with stream-json, forward events to stdout, return success/failure.
|
|
15
25
|
*/
|
|
@@ -25,8 +35,11 @@ export function runCursorAgentForNode(
|
|
|
25
35
|
const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
|
|
26
36
|
if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
|
|
27
37
|
const absWorkspaceRoot = path.resolve(workspaceRoot);
|
|
38
|
+
const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
28
39
|
const replacements = {
|
|
29
|
-
workspaceRoot:
|
|
40
|
+
workspaceRoot: execWorkspaceRoot,
|
|
41
|
+
executionWorkspaceRoot: execWorkspaceRoot,
|
|
42
|
+
pipelineWorkspace: absWorkspaceRoot,
|
|
30
43
|
promptPath: absPromptPath,
|
|
31
44
|
nodeContext: nodeContext ?? "",
|
|
32
45
|
taskBody: taskBody ?? "",
|
|
@@ -61,11 +74,11 @@ export function runCursorAgentForNode(
|
|
|
61
74
|
|
|
62
75
|
return new Promise((resolve, reject) => {
|
|
63
76
|
const agentCmd = process.env.CURSOR_AGENT_CMD || "agent";
|
|
64
|
-
const args = ["--print", "--output-format", "stream-json", "--trust", "--workspace",
|
|
77
|
+
const args = ["--print", "--output-format", "stream-json", "--trust", "--workspace", execWorkspaceRoot];
|
|
65
78
|
const approveMcps = process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "0" && process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "false";
|
|
66
79
|
if (approveMcps) args.push("--approve-mcps");
|
|
67
80
|
if (options.force) args.push("--force");
|
|
68
|
-
args.push("--model", model);
|
|
81
|
+
if (shouldPassCursorModelArg(model)) args.push("--model", model);
|
|
69
82
|
args.push(promptText);
|
|
70
83
|
if (options.flowName && options.uuid) {
|
|
71
84
|
const argvLog = args.slice(0, -1).concat([`(prompt ${args[args.length - 1].length} chars)`]);
|
|
@@ -81,9 +94,10 @@ export function runCursorAgentForNode(
|
|
|
81
94
|
}
|
|
82
95
|
const useStderrInherit = process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "1" || process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "true";
|
|
83
96
|
const child = spawn(agentCmd, args, {
|
|
84
|
-
cwd:
|
|
97
|
+
cwd: execWorkspaceRoot,
|
|
85
98
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
86
99
|
shell: false,
|
|
100
|
+
env: childEnv(options),
|
|
87
101
|
});
|
|
88
102
|
|
|
89
103
|
let lastResult = null;
|
|
@@ -286,8 +300,11 @@ export function runOpenCodeAgentForNode(
|
|
|
286
300
|
const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
|
|
287
301
|
if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
|
|
288
302
|
const absWorkspaceRoot = path.resolve(workspaceRoot);
|
|
303
|
+
const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
289
304
|
const replacements = {
|
|
290
|
-
workspaceRoot:
|
|
305
|
+
workspaceRoot: execWorkspaceRoot,
|
|
306
|
+
executionWorkspaceRoot: execWorkspaceRoot,
|
|
307
|
+
pipelineWorkspace: absWorkspaceRoot,
|
|
291
308
|
promptPath: absPromptPath,
|
|
292
309
|
nodeContext: nodeContext ?? "",
|
|
293
310
|
taskBody: taskBody ?? "",
|
|
@@ -325,16 +342,17 @@ export function runOpenCodeAgentForNode(
|
|
|
325
342
|
if (model) {
|
|
326
343
|
args.push("--model", model);
|
|
327
344
|
}
|
|
328
|
-
args.push("--dir",
|
|
345
|
+
args.push("--dir", execWorkspaceRoot);
|
|
329
346
|
args.push("--", promptText);
|
|
330
347
|
const spawnOpts = {
|
|
331
|
-
cwd:
|
|
348
|
+
cwd: execWorkspaceRoot,
|
|
332
349
|
stdio: ["ignore", "pipe", "pipe"],
|
|
333
350
|
shell: false,
|
|
351
|
+
env: childEnv(options),
|
|
334
352
|
};
|
|
335
353
|
if (options.force) {
|
|
336
354
|
spawnOpts.env = {
|
|
337
|
-
...
|
|
355
|
+
...spawnOpts.env,
|
|
338
356
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
339
357
|
permission: { external_directory: "allow" },
|
|
340
358
|
}),
|
|
@@ -426,8 +444,11 @@ export function runClaudeCodeAgentForNode(
|
|
|
426
444
|
const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
|
|
427
445
|
if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
|
|
428
446
|
const absWorkspaceRoot = path.resolve(workspaceRoot);
|
|
447
|
+
const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
429
448
|
const replacements = {
|
|
430
|
-
workspaceRoot:
|
|
449
|
+
workspaceRoot: execWorkspaceRoot,
|
|
450
|
+
executionWorkspaceRoot: execWorkspaceRoot,
|
|
451
|
+
pipelineWorkspace: absWorkspaceRoot,
|
|
431
452
|
promptPath: absPromptPath,
|
|
432
453
|
nodeContext: nodeContext ?? "",
|
|
433
454
|
taskBody: taskBody ?? "",
|
|
@@ -464,7 +485,7 @@ export function runClaudeCodeAgentForNode(
|
|
|
464
485
|
const bypassPermissions =
|
|
465
486
|
process.env.AGENTFLOW_CLAUDE_CODE_BYPASS_PERMISSIONS !== "0" &&
|
|
466
487
|
process.env.AGENTFLOW_CLAUDE_CODE_BYPASS_PERMISSIONS !== "false";
|
|
467
|
-
const args = ["-p", "--output-format", "stream-json", "--verbose", "--add-dir",
|
|
488
|
+
const args = ["-p", "--output-format", "stream-json", "--verbose", "--add-dir", execWorkspaceRoot, "--add-dir", absWorkspaceRoot];
|
|
468
489
|
if (bypassPermissions) args.push("--dangerously-skip-permissions");
|
|
469
490
|
if (model) args.push("--model", model);
|
|
470
491
|
args.push(promptText);
|
|
@@ -490,9 +511,10 @@ export function runClaudeCodeAgentForNode(
|
|
|
490
511
|
process.env.AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT === "1" ||
|
|
491
512
|
process.env.AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT === "true";
|
|
492
513
|
const child = spawn(claudeCmd, args, {
|
|
493
|
-
cwd:
|
|
514
|
+
cwd: execWorkspaceRoot,
|
|
494
515
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
495
516
|
shell: false,
|
|
517
|
+
env: childEnv(options),
|
|
496
518
|
});
|
|
497
519
|
|
|
498
520
|
let lastResult = null;
|
|
@@ -743,7 +765,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
743
765
|
const approveMcps = process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "0" && process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "false";
|
|
744
766
|
if (approveMcps) args.push("--approve-mcps");
|
|
745
767
|
args.push("--force");
|
|
746
|
-
args.push("--model", model);
|
|
768
|
+
if (shouldPassCursorModelArg(model)) args.push("--model", model);
|
|
747
769
|
args.push(promptText);
|
|
748
770
|
|
|
749
771
|
const useStderrInherit = process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "1" || process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "true";
|
|
@@ -751,6 +773,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
751
773
|
cwd: ws,
|
|
752
774
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
753
775
|
shell: false,
|
|
776
|
+
env: childEnv(options),
|
|
754
777
|
});
|
|
755
778
|
|
|
756
779
|
let lastResult = null;
|
|
@@ -939,10 +962,11 @@ export function runOpenCodeAgentWithPrompt(cliWorkspace, promptText, options = {
|
|
|
939
962
|
cwd: ws,
|
|
940
963
|
stdio: ["ignore", "pipe", "pipe"],
|
|
941
964
|
shell: false,
|
|
965
|
+
env: childEnv(options),
|
|
942
966
|
};
|
|
943
967
|
if (options.force) {
|
|
944
968
|
spawnOpts.env = {
|
|
945
|
-
...
|
|
969
|
+
...spawnOpts.env,
|
|
946
970
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
947
971
|
permission: { external_directory: "allow" },
|
|
948
972
|
}),
|
|
@@ -1047,6 +1071,7 @@ export function runClaudeCodeAgentWithPrompt(cliWorkspace, promptText, options =
|
|
|
1047
1071
|
cwd: ws,
|
|
1048
1072
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
1049
1073
|
shell: false,
|
|
1074
|
+
env: childEnv(options),
|
|
1050
1075
|
});
|
|
1051
1076
|
|
|
1052
1077
|
let lastResult = null;
|
package/bin/lib/api-runner.mjs
CHANGED
|
@@ -315,6 +315,7 @@ export function parseApiModel(str) {
|
|
|
315
315
|
*/
|
|
316
316
|
export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContext, taskBody, subagent, instanceId }, options = {}) {
|
|
317
317
|
const absRoot = path.resolve(workspaceRoot);
|
|
318
|
+
const execRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
318
319
|
const flowName = options.flowName ?? null;
|
|
319
320
|
const uuid = options.uuid ?? null;
|
|
320
321
|
|
|
@@ -329,7 +330,9 @@ export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContex
|
|
|
329
330
|
|
|
330
331
|
// ── 读取 Agent 角色提示,注入 nodeContext/taskBody ────────────────────────
|
|
331
332
|
const replacements = {
|
|
332
|
-
workspaceRoot:
|
|
333
|
+
workspaceRoot: execRoot,
|
|
334
|
+
executionWorkspaceRoot: execRoot,
|
|
335
|
+
pipelineWorkspace: absRoot,
|
|
333
336
|
nodeContext: nodeContext ?? "",
|
|
334
337
|
taskBody: taskBody ?? "",
|
|
335
338
|
flowName: flowName ?? "",
|
|
@@ -349,12 +352,12 @@ export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContex
|
|
|
349
352
|
if (provider === "anthropic") {
|
|
350
353
|
const key = process.env.ANTHROPIC_API_KEY;
|
|
351
354
|
if (!key) throw new Error("[api-runner] ANTHROPIC_API_KEY is required for api:anthropic/* models");
|
|
352
|
-
await runAnthropicLoop(key, model, systemPrompt, userContent,
|
|
355
|
+
await runAnthropicLoop(key, model, systemPrompt, userContent, execRoot, log, options);
|
|
353
356
|
} else {
|
|
354
357
|
const key = process.env.OPENAI_API_KEY;
|
|
355
358
|
if (!key) throw new Error("[api-runner] OPENAI_API_KEY is required for api:openai/* models");
|
|
356
359
|
const baseUrl = (process.env.OPENAI_BASE_URL ?? DEFAULT_OPENAI_BASE).trim();
|
|
357
|
-
await runOpenAiLoop(key, baseUrl, model, systemPrompt, userContent,
|
|
360
|
+
await runOpenAiLoop(key, baseUrl, model, systemPrompt, userContent, execRoot, log, options);
|
|
358
361
|
}
|
|
359
362
|
|
|
360
363
|
log(`done instanceId=${instanceId ?? "-"}`);
|
package/bin/lib/auth.mjs
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import {
|
|
5
|
+
ARCHIVED_PIPELINES_DIR_NAME,
|
|
6
|
+
getAgentflowDataRoot,
|
|
7
|
+
getUserPipelinesRoot,
|
|
8
|
+
sanitizeAgentflowUserId,
|
|
9
|
+
} from "./paths.mjs";
|
|
10
|
+
|
|
11
|
+
const SESSION_COOKIE = "af_session";
|
|
12
|
+
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
function authRoot() {
|
|
15
|
+
return path.join(getAgentflowDataRoot(), "auth");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function usersPath() {
|
|
19
|
+
return path.join(authRoot(), "users.json");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sessionsPath() {
|
|
23
|
+
return path.join(authRoot(), "sessions.json");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readJsonObject(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(filePath)) return {};
|
|
29
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
30
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
31
|
+
} catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeJsonObject(filePath, data) {
|
|
37
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
+
fs.writeFileSync(filePath, JSON.stringify(data && typeof data === "object" ? data : {}, null, 2) + "\n", "utf-8");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hashPassword(password, salt = crypto.randomBytes(16).toString("hex")) {
|
|
42
|
+
const hash = crypto.scryptSync(String(password), salt, 64).toString("hex");
|
|
43
|
+
return { salt, hash };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function verifyPassword(password, record) {
|
|
47
|
+
if (!record || typeof record.salt !== "string" || typeof record.hash !== "string") return false;
|
|
48
|
+
const next = hashPassword(password, record.salt).hash;
|
|
49
|
+
try {
|
|
50
|
+
return crypto.timingSafeEqual(Buffer.from(next, "hex"), Buffer.from(record.hash, "hex"));
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hashToken(token) {
|
|
57
|
+
return crypto.createHash("sha256").update(String(token)).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseCookies(header) {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const part of String(header || "").split(";")) {
|
|
63
|
+
const idx = part.indexOf("=");
|
|
64
|
+
if (idx <= 0) continue;
|
|
65
|
+
const key = part.slice(0, idx).trim();
|
|
66
|
+
const value = part.slice(idx + 1).trim();
|
|
67
|
+
if (!key) continue;
|
|
68
|
+
try {
|
|
69
|
+
out[key] = decodeURIComponent(value);
|
|
70
|
+
} catch {
|
|
71
|
+
out[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function readAuthUsers() {
|
|
78
|
+
return readJsonObject(usersPath());
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function authSetupRequired() {
|
|
82
|
+
return Object.keys(readAuthUsers()).length === 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function listFlowDirs(root) {
|
|
86
|
+
try {
|
|
87
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) return [];
|
|
88
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
89
|
+
.filter((entry) => entry.isDirectory())
|
|
90
|
+
.filter((entry) => entry.name !== ARCHIVED_PIPELINES_DIR_NAME)
|
|
91
|
+
.filter((entry) => fs.existsSync(path.join(root, entry.name, "flow.yaml")))
|
|
92
|
+
.map((entry) => entry.name)
|
|
93
|
+
.sort((a, b) => a.localeCompare(b));
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function copyMissingFlowDirs(sourceRoot, targetRoot, relativeRoot = "") {
|
|
100
|
+
const fromRoot = path.join(sourceRoot, relativeRoot);
|
|
101
|
+
const toRoot = path.join(targetRoot, relativeRoot);
|
|
102
|
+
const copied = [];
|
|
103
|
+
const skipped = [];
|
|
104
|
+
for (const name of listFlowDirs(fromRoot)) {
|
|
105
|
+
const fromDir = path.join(fromRoot, name);
|
|
106
|
+
const toDir = path.join(toRoot, name);
|
|
107
|
+
if (fs.existsSync(toDir)) {
|
|
108
|
+
skipped.push(path.join(relativeRoot, name).replace(/\\/g, "/"));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
fs.mkdirSync(path.dirname(toDir), { recursive: true });
|
|
112
|
+
fs.cpSync(fromDir, toDir, { recursive: true });
|
|
113
|
+
copied.push(path.join(relativeRoot, name).replace(/\\/g, "/"));
|
|
114
|
+
}
|
|
115
|
+
return { copied, skipped };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function migrateLegacyPipelinesToAdminUser(userId) {
|
|
119
|
+
const safeUserId = sanitizeAgentflowUserId(userId);
|
|
120
|
+
if (!safeUserId) return { copied: [], skipped: [], source: "", target: "", error: "invalid userId" };
|
|
121
|
+
|
|
122
|
+
const source = getUserPipelinesRoot("");
|
|
123
|
+
const target = getUserPipelinesRoot(safeUserId);
|
|
124
|
+
if (path.resolve(source) === path.resolve(target)) {
|
|
125
|
+
return { copied: [], skipped: [], source, target };
|
|
126
|
+
}
|
|
127
|
+
if (!fs.existsSync(source)) {
|
|
128
|
+
return { copied: [], skipped: [], source, target };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const active = copyMissingFlowDirs(source, target);
|
|
132
|
+
const archived = copyMissingFlowDirs(source, target, ARCHIVED_PIPELINES_DIR_NAME);
|
|
133
|
+
return {
|
|
134
|
+
copied: [...active.copied, ...archived.copied],
|
|
135
|
+
skipped: [...active.skipped, ...archived.skipped],
|
|
136
|
+
source,
|
|
137
|
+
target,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getSessionCookieName() {
|
|
142
|
+
return SESSION_COOKIE;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function buildSessionCookie(token) {
|
|
146
|
+
const attrs = [
|
|
147
|
+
`${SESSION_COOKIE}=${encodeURIComponent(token)}`,
|
|
148
|
+
"Path=/",
|
|
149
|
+
"HttpOnly",
|
|
150
|
+
"SameSite=Lax",
|
|
151
|
+
`Max-Age=${Math.floor(SESSION_TTL_MS / 1000)}`,
|
|
152
|
+
];
|
|
153
|
+
return attrs.join("; ");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function buildClearSessionCookie() {
|
|
157
|
+
return `${SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getAuthUserFromRequest(req) {
|
|
161
|
+
const token = parseCookies(req.headers.cookie || "")[SESSION_COOKIE];
|
|
162
|
+
if (!token) return null;
|
|
163
|
+
const sessions = readJsonObject(sessionsPath());
|
|
164
|
+
const key = hashToken(token);
|
|
165
|
+
const session = sessions[key];
|
|
166
|
+
if (!session || typeof session.userId !== "string") return null;
|
|
167
|
+
if (Number(session.expiresAt) <= Date.now()) {
|
|
168
|
+
delete sessions[key];
|
|
169
|
+
writeJsonObject(sessionsPath(), sessions);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const users = readAuthUsers();
|
|
173
|
+
const user = users[session.userId];
|
|
174
|
+
if (!user) return null;
|
|
175
|
+
return {
|
|
176
|
+
userId: session.userId,
|
|
177
|
+
username: user.username || session.userId,
|
|
178
|
+
isAdmin: Boolean(user.isAdmin),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function loginOrCreateUser(username, password) {
|
|
183
|
+
const userId = sanitizeAgentflowUserId(username);
|
|
184
|
+
if (!userId) {
|
|
185
|
+
return { ok: false, error: "用户名须以字母开头,仅可使用字母、数字、下划线与连字符,最多 64 字符" };
|
|
186
|
+
}
|
|
187
|
+
const pwd = String(password || "");
|
|
188
|
+
if (pwd.length < 4) return { ok: false, error: "密码至少 4 位" };
|
|
189
|
+
|
|
190
|
+
const users = readAuthUsers();
|
|
191
|
+
const firstUser = Object.keys(users).length === 0;
|
|
192
|
+
let user = users[userId];
|
|
193
|
+
if (!user) {
|
|
194
|
+
const hashed = hashPassword(pwd);
|
|
195
|
+
user = {
|
|
196
|
+
userId,
|
|
197
|
+
username: String(username).trim(),
|
|
198
|
+
salt: hashed.salt,
|
|
199
|
+
hash: hashed.hash,
|
|
200
|
+
isAdmin: firstUser,
|
|
201
|
+
createdAt: new Date().toISOString(),
|
|
202
|
+
};
|
|
203
|
+
users[userId] = user;
|
|
204
|
+
writeJsonObject(usersPath(), users);
|
|
205
|
+
} else if (!verifyPassword(pwd, user)) {
|
|
206
|
+
return { ok: false, error: "用户名或密码错误" };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let migration = null;
|
|
210
|
+
if (Boolean(user.isAdmin)) {
|
|
211
|
+
try {
|
|
212
|
+
migration = migrateLegacyPipelinesToAdminUser(userId);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
migration = { copied: [], skipped: [], error: (e && e.message) || String(e) };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const token = crypto.randomBytes(32).toString("base64url");
|
|
219
|
+
const sessions = readJsonObject(sessionsPath());
|
|
220
|
+
sessions[hashToken(token)] = {
|
|
221
|
+
userId,
|
|
222
|
+
createdAt: Date.now(),
|
|
223
|
+
expiresAt: Date.now() + SESSION_TTL_MS,
|
|
224
|
+
};
|
|
225
|
+
writeJsonObject(sessionsPath(), sessions);
|
|
226
|
+
return {
|
|
227
|
+
ok: true,
|
|
228
|
+
token,
|
|
229
|
+
user: { userId, username: user.username || userId, isAdmin: Boolean(user.isAdmin) },
|
|
230
|
+
migration,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function logoutRequest(req) {
|
|
235
|
+
const token = parseCookies(req.headers.cookie || "")[SESSION_COOKIE];
|
|
236
|
+
if (!token) return;
|
|
237
|
+
const sessions = readJsonObject(sessionsPath());
|
|
238
|
+
delete sessions[hashToken(token)];
|
|
239
|
+
writeJsonObject(sessionsPath(), sessions);
|
|
240
|
+
}
|
|
@@ -278,10 +278,10 @@ description: ${description != null ? String(description).replace(/\n/g, " ") : "
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
export function copyBuiltinJson(workspaceRoot, flowId, targetFlowId) {
|
|
281
|
+
export function copyBuiltinJson(workspaceRoot, flowId, targetFlowId, opts = {}) {
|
|
282
282
|
const destId = (targetFlowId && targetFlowId.trim()) || flowId;
|
|
283
283
|
const srcDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
|
|
284
|
-
const pipelinesRoot = getUserPipelinesRoot();
|
|
284
|
+
const pipelinesRoot = getUserPipelinesRoot(opts.userId);
|
|
285
285
|
const destDir = path.join(pipelinesRoot, destId);
|
|
286
286
|
if (!fs.existsSync(srcDir) || !fs.existsSync(path.join(srcDir, "flow.yaml"))) {
|
|
287
287
|
return { success: false, error: t("catalog.builtin_flow_not_found") };
|