@fieldwangai/agentflow 0.1.29 → 0.1.31
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 +63 -14
- package/bin/lib/api-runner.mjs +30 -4
- package/bin/lib/apply.mjs +6 -5
- package/bin/lib/auth.mjs +240 -0
- package/bin/lib/catalog-agents.mjs +2 -2
- package/bin/lib/catalog-flows.mjs +196 -17
- package/bin/lib/composer-agent.mjs +22 -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 +29 -1
- package/bin/lib/locales/zh.json +31 -3
- package/bin/lib/main.mjs +6 -1
- package/bin/lib/node-exec-context.mjs +5 -5
- package/bin/lib/node-execute.mjs +15 -10
- package/bin/lib/paths.mjs +69 -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 +42 -38
- package/bin/lib/skill-registry.mjs +145 -0
- package/bin/lib/ui-server.mjs +1517 -57
- package/bin/lib/user-env.mjs +83 -0
- 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-env.mjs +5 -29
- 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 +328 -6
- package/bin/pipeline/run-tool-nodejs.mjs +7 -0
- package/bin/pipeline/validate-flow.mjs +2 -0
- package/builtin/nodes/agent_subAgent.md +12 -3
- package/builtin/nodes/control_cd_workspace.md +45 -0
- package/builtin/nodes/control_load_skills.md +50 -0
- package/builtin/nodes/control_user_workspace.md +20 -0
- package/builtin/nodes/display_ascii.md +22 -0
- package/builtin/nodes/display_markdown.md +22 -0
- package/builtin/nodes/display_mermaid.md +22 -0
- package/builtin/nodes/tool_git_checkout.md +57 -0
- package/builtin/nodes/tool_nodejs.md +8 -1
- package/builtin/nodes/tool_print.md +4 -1
- package/builtin/web-ui/dist/assets/index-BVWwQpvg.css +1 -0
- package/builtin/web-ui/dist/assets/index-CvNy1n3f.js +197 -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/skills/agentflow-workspace-ascii/SKILL.md +42 -0
- package/skills/agentflow-workspace-graph/SKILL.md +67 -0
- package/skills/agentflow-workspace-markdown/SKILL.md +44 -0
- package/skills/agentflow-workspace-mermaid/SKILL.md +43 -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
|
```
|
|
@@ -9,6 +9,30 @@ import { normalizeCursorModelForCli } from "./model-config.mjs";
|
|
|
9
9
|
import { appendRunLogLine } from "./run-events.mjs";
|
|
10
10
|
import { writeWithPrefix } from "./terminal.mjs";
|
|
11
11
|
import { t } from "./i18n.mjs";
|
|
12
|
+
import { readUserEnvObject } from "./user-env.mjs";
|
|
13
|
+
import { outputNodeBasename } from "../pipeline/get-exec-id.mjs";
|
|
14
|
+
|
|
15
|
+
function shouldPassCursorModelArg(model) {
|
|
16
|
+
const text = String(model || "").trim();
|
|
17
|
+
return text !== "" && !/^auto$/i.test(text);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function childEnv(options = {}, extra = {}) {
|
|
21
|
+
const optEnv = options && options.env && typeof options.env === "object" ? options.env : {};
|
|
22
|
+
const userId = optEnv.AGENTFLOW_USER_ID || process.env.AGENTFLOW_USER_ID || "";
|
|
23
|
+
return { ...process.env, ...readUserEnvObject(userId), ...optEnv, ...extra };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, text) {
|
|
27
|
+
const body = String(text ?? "").trim();
|
|
28
|
+
if (!body) return;
|
|
29
|
+
fs.mkdirSync(path.dirname(absResultPath), { recursive: true });
|
|
30
|
+
fs.writeFileSync(absResultPath, body + "\n", "utf-8");
|
|
31
|
+
if (!instanceId) return;
|
|
32
|
+
const slotPath = path.join(absRunDir, "output", instanceId, outputNodeBasename(instanceId, 1, "result"));
|
|
33
|
+
fs.mkdirSync(path.dirname(slotPath), { recursive: true });
|
|
34
|
+
fs.writeFileSync(slotPath, body + "\n", "utf-8");
|
|
35
|
+
}
|
|
12
36
|
|
|
13
37
|
/**
|
|
14
38
|
* Run Cursor CLI with stream-json, forward events to stdout, return success/failure.
|
|
@@ -25,8 +49,11 @@ export function runCursorAgentForNode(
|
|
|
25
49
|
const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
|
|
26
50
|
if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
|
|
27
51
|
const absWorkspaceRoot = path.resolve(workspaceRoot);
|
|
52
|
+
const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
28
53
|
const replacements = {
|
|
29
|
-
workspaceRoot:
|
|
54
|
+
workspaceRoot: execWorkspaceRoot,
|
|
55
|
+
executionWorkspaceRoot: execWorkspaceRoot,
|
|
56
|
+
pipelineWorkspace: absWorkspaceRoot,
|
|
30
57
|
promptPath: absPromptPath,
|
|
31
58
|
nodeContext: nodeContext ?? "",
|
|
32
59
|
taskBody: taskBody ?? "",
|
|
@@ -61,11 +88,11 @@ export function runCursorAgentForNode(
|
|
|
61
88
|
|
|
62
89
|
return new Promise((resolve, reject) => {
|
|
63
90
|
const agentCmd = process.env.CURSOR_AGENT_CMD || "agent";
|
|
64
|
-
const args = ["--print", "--output-format", "stream-json", "--trust", "--workspace",
|
|
91
|
+
const args = ["--print", "--output-format", "stream-json", "--trust", "--workspace", execWorkspaceRoot];
|
|
65
92
|
const approveMcps = process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "0" && process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "false";
|
|
66
93
|
if (approveMcps) args.push("--approve-mcps");
|
|
67
94
|
if (options.force) args.push("--force");
|
|
68
|
-
args.push("--model", model);
|
|
95
|
+
if (shouldPassCursorModelArg(model)) args.push("--model", model);
|
|
69
96
|
args.push(promptText);
|
|
70
97
|
if (options.flowName && options.uuid) {
|
|
71
98
|
const argvLog = args.slice(0, -1).concat([`(prompt ${args[args.length - 1].length} chars)`]);
|
|
@@ -81,13 +108,15 @@ export function runCursorAgentForNode(
|
|
|
81
108
|
}
|
|
82
109
|
const useStderrInherit = process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "1" || process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "true";
|
|
83
110
|
const child = spawn(agentCmd, args, {
|
|
84
|
-
cwd:
|
|
111
|
+
cwd: execWorkspaceRoot,
|
|
85
112
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
86
113
|
shell: false,
|
|
114
|
+
env: childEnv(options),
|
|
87
115
|
});
|
|
88
116
|
|
|
89
117
|
let lastResult = null;
|
|
90
118
|
let hadError = false;
|
|
119
|
+
const assistantTextChunks = [];
|
|
91
120
|
const STDERR_CAP_BYTES = 1024 * 1024;
|
|
92
121
|
const stderrChunks = [];
|
|
93
122
|
let stderrTotalBytes = 0;
|
|
@@ -172,6 +201,7 @@ export function runCursorAgentForNode(
|
|
|
172
201
|
.join("");
|
|
173
202
|
if (text) {
|
|
174
203
|
text = text.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
204
|
+
assistantTextChunks.push(text);
|
|
175
205
|
const out = mdStreamer.push(text);
|
|
176
206
|
if (out) writeStdout(out);
|
|
177
207
|
}
|
|
@@ -266,6 +296,7 @@ export function runCursorAgentForNode(
|
|
|
266
296
|
reject(new Error(lastResult?.result || "Agent reported error."));
|
|
267
297
|
return;
|
|
268
298
|
}
|
|
299
|
+
writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, assistantTextChunks.join("") || lastResult?.result || "");
|
|
269
300
|
resolve();
|
|
270
301
|
});
|
|
271
302
|
});
|
|
@@ -286,8 +317,11 @@ export function runOpenCodeAgentForNode(
|
|
|
286
317
|
const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
|
|
287
318
|
if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
|
|
288
319
|
const absWorkspaceRoot = path.resolve(workspaceRoot);
|
|
320
|
+
const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
289
321
|
const replacements = {
|
|
290
|
-
workspaceRoot:
|
|
322
|
+
workspaceRoot: execWorkspaceRoot,
|
|
323
|
+
executionWorkspaceRoot: execWorkspaceRoot,
|
|
324
|
+
pipelineWorkspace: absWorkspaceRoot,
|
|
291
325
|
promptPath: absPromptPath,
|
|
292
326
|
nodeContext: nodeContext ?? "",
|
|
293
327
|
taskBody: taskBody ?? "",
|
|
@@ -325,16 +359,17 @@ export function runOpenCodeAgentForNode(
|
|
|
325
359
|
if (model) {
|
|
326
360
|
args.push("--model", model);
|
|
327
361
|
}
|
|
328
|
-
args.push("--dir",
|
|
362
|
+
args.push("--dir", execWorkspaceRoot);
|
|
329
363
|
args.push("--", promptText);
|
|
330
364
|
const spawnOpts = {
|
|
331
|
-
cwd:
|
|
365
|
+
cwd: execWorkspaceRoot,
|
|
332
366
|
stdio: ["ignore", "pipe", "pipe"],
|
|
333
367
|
shell: false,
|
|
368
|
+
env: childEnv(options),
|
|
334
369
|
};
|
|
335
370
|
if (options.force) {
|
|
336
371
|
spawnOpts.env = {
|
|
337
|
-
...
|
|
372
|
+
...spawnOpts.env,
|
|
338
373
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
339
374
|
permission: { external_directory: "allow" },
|
|
340
375
|
}),
|
|
@@ -346,6 +381,7 @@ export function runOpenCodeAgentForNode(
|
|
|
346
381
|
|
|
347
382
|
let stdoutLogBuf = "";
|
|
348
383
|
let stderrLogBuf = "";
|
|
384
|
+
let stdoutCaptured = "";
|
|
349
385
|
|
|
350
386
|
function drainLogBuf(buf, tag) {
|
|
351
387
|
let idx;
|
|
@@ -372,7 +408,9 @@ export function runOpenCodeAgentForNode(
|
|
|
372
408
|
child.stdout.on("data", (chunk) => {
|
|
373
409
|
if (coloredPrefix) writeWithPrefix(process.stdout, chunk, coloredPrefix, agentContentColor);
|
|
374
410
|
else process.stdout.write(agentContentColor(chunk));
|
|
375
|
-
|
|
411
|
+
const normalizedChunk = String(chunk).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
412
|
+
stdoutCaptured += normalizedChunk;
|
|
413
|
+
stdoutLogBuf += normalizedChunk;
|
|
376
414
|
stdoutLogBuf = drainLogBuf(stdoutLogBuf, "opencode-stdout");
|
|
377
415
|
});
|
|
378
416
|
|
|
@@ -404,6 +442,7 @@ export function runOpenCodeAgentForNode(
|
|
|
404
442
|
reject(new Error(`OpenCode CLI exited ${code}.`));
|
|
405
443
|
return;
|
|
406
444
|
}
|
|
445
|
+
writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, stripAnsi(stdoutCaptured));
|
|
407
446
|
resolve();
|
|
408
447
|
});
|
|
409
448
|
});
|
|
@@ -426,8 +465,11 @@ export function runClaudeCodeAgentForNode(
|
|
|
426
465
|
const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
|
|
427
466
|
if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
|
|
428
467
|
const absWorkspaceRoot = path.resolve(workspaceRoot);
|
|
468
|
+
const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
429
469
|
const replacements = {
|
|
430
|
-
workspaceRoot:
|
|
470
|
+
workspaceRoot: execWorkspaceRoot,
|
|
471
|
+
executionWorkspaceRoot: execWorkspaceRoot,
|
|
472
|
+
pipelineWorkspace: absWorkspaceRoot,
|
|
431
473
|
promptPath: absPromptPath,
|
|
432
474
|
nodeContext: nodeContext ?? "",
|
|
433
475
|
taskBody: taskBody ?? "",
|
|
@@ -464,7 +506,7 @@ export function runClaudeCodeAgentForNode(
|
|
|
464
506
|
const bypassPermissions =
|
|
465
507
|
process.env.AGENTFLOW_CLAUDE_CODE_BYPASS_PERMISSIONS !== "0" &&
|
|
466
508
|
process.env.AGENTFLOW_CLAUDE_CODE_BYPASS_PERMISSIONS !== "false";
|
|
467
|
-
const args = ["-p", "--output-format", "stream-json", "--verbose", "--add-dir",
|
|
509
|
+
const args = ["-p", "--output-format", "stream-json", "--verbose", "--add-dir", execWorkspaceRoot, "--add-dir", absWorkspaceRoot];
|
|
468
510
|
if (bypassPermissions) args.push("--dangerously-skip-permissions");
|
|
469
511
|
if (model) args.push("--model", model);
|
|
470
512
|
args.push(promptText);
|
|
@@ -490,14 +532,16 @@ export function runClaudeCodeAgentForNode(
|
|
|
490
532
|
process.env.AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT === "1" ||
|
|
491
533
|
process.env.AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT === "true";
|
|
492
534
|
const child = spawn(claudeCmd, args, {
|
|
493
|
-
cwd:
|
|
535
|
+
cwd: execWorkspaceRoot,
|
|
494
536
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
495
537
|
shell: false,
|
|
538
|
+
env: childEnv(options),
|
|
496
539
|
});
|
|
497
540
|
|
|
498
541
|
let lastResult = null;
|
|
499
542
|
let hadError = false;
|
|
500
543
|
let sessionId = null;
|
|
544
|
+
const assistantTextChunks = [];
|
|
501
545
|
const STDERR_CAP_BYTES = 1024 * 1024;
|
|
502
546
|
const stderrChunks = [];
|
|
503
547
|
let stderrTotalBytes = 0;
|
|
@@ -574,6 +618,7 @@ export function runClaudeCodeAgentForNode(
|
|
|
574
618
|
if (!block || typeof block !== "object") continue;
|
|
575
619
|
if (block.type === "text" && block.text) {
|
|
576
620
|
const text = normalizeStreamTextChunk(block.text);
|
|
621
|
+
assistantTextChunks.push(text);
|
|
577
622
|
const out = mdStreamer.push(text);
|
|
578
623
|
if (out) writeStdout(out);
|
|
579
624
|
} else if (block.type === "thinking") {
|
|
@@ -642,6 +687,7 @@ export function runClaudeCodeAgentForNode(
|
|
|
642
687
|
reject(new Error(String(msg)));
|
|
643
688
|
return;
|
|
644
689
|
}
|
|
690
|
+
writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, assistantTextChunks.join("") || lastResult?.result || "");
|
|
645
691
|
resolve();
|
|
646
692
|
});
|
|
647
693
|
});
|
|
@@ -743,7 +789,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
743
789
|
const approveMcps = process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "0" && process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "false";
|
|
744
790
|
if (approveMcps) args.push("--approve-mcps");
|
|
745
791
|
args.push("--force");
|
|
746
|
-
args.push("--model", model);
|
|
792
|
+
if (shouldPassCursorModelArg(model)) args.push("--model", model);
|
|
747
793
|
args.push(promptText);
|
|
748
794
|
|
|
749
795
|
const useStderrInherit = process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "1" || process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "true";
|
|
@@ -751,6 +797,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
751
797
|
cwd: ws,
|
|
752
798
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
753
799
|
shell: false,
|
|
800
|
+
env: childEnv(options),
|
|
754
801
|
});
|
|
755
802
|
|
|
756
803
|
let lastResult = null;
|
|
@@ -939,10 +986,11 @@ export function runOpenCodeAgentWithPrompt(cliWorkspace, promptText, options = {
|
|
|
939
986
|
cwd: ws,
|
|
940
987
|
stdio: ["ignore", "pipe", "pipe"],
|
|
941
988
|
shell: false,
|
|
989
|
+
env: childEnv(options),
|
|
942
990
|
};
|
|
943
991
|
if (options.force) {
|
|
944
992
|
spawnOpts.env = {
|
|
945
|
-
...
|
|
993
|
+
...spawnOpts.env,
|
|
946
994
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
947
995
|
permission: { external_directory: "allow" },
|
|
948
996
|
}),
|
|
@@ -1047,6 +1095,7 @@ export function runClaudeCodeAgentWithPrompt(cliWorkspace, promptText, options =
|
|
|
1047
1095
|
cwd: ws,
|
|
1048
1096
|
stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
|
|
1049
1097
|
shell: false,
|
|
1098
|
+
env: childEnv(options),
|
|
1050
1099
|
});
|
|
1051
1100
|
|
|
1052
1101
|
let lastResult = null;
|
package/bin/lib/api-runner.mjs
CHANGED
|
@@ -19,11 +19,23 @@ import { spawnSync } from "child_process";
|
|
|
19
19
|
|
|
20
20
|
import { loadAgentPromptWithReplacements, stripYamlFrontmatter } from "./agents-path.mjs";
|
|
21
21
|
import { appendRunLogLine } from "./run-events.mjs";
|
|
22
|
+
import { outputNodeBasename } from "../pipeline/get-exec-id.mjs";
|
|
22
23
|
|
|
23
24
|
const DEFAULT_OPENAI_BASE = "https://api.openai.com/v1";
|
|
24
25
|
const MAX_TOOL_ROUNDS = parseInt(process.env.AGENTFLOW_API_MAX_ROUNDS ?? "30", 10) || 30;
|
|
25
26
|
const MAX_TOKENS = parseInt(process.env.AGENTFLOW_API_MAX_TOKENS ?? "8192", 10) || 8192;
|
|
26
27
|
|
|
28
|
+
function writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, text) {
|
|
29
|
+
const body = String(text ?? "").trim();
|
|
30
|
+
if (!body || !absResultPath || !absRunDir) return;
|
|
31
|
+
fs.mkdirSync(path.dirname(absResultPath), { recursive: true });
|
|
32
|
+
fs.writeFileSync(absResultPath, body + "\n", "utf-8");
|
|
33
|
+
if (!instanceId) return;
|
|
34
|
+
const slotPath = path.join(absRunDir, "output", instanceId, outputNodeBasename(instanceId, 1, "result"));
|
|
35
|
+
fs.mkdirSync(path.dirname(slotPath), { recursive: true });
|
|
36
|
+
fs.writeFileSync(slotPath, body + "\n", "utf-8");
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
// ─── 工具定义 ────────────────────────────────────────────────────────────────
|
|
28
40
|
|
|
29
41
|
const TOOL_DEFS = [
|
|
@@ -203,6 +215,7 @@ async function runOpenAiLoop(apiKey, baseUrl, model, systemPrompt, userContent,
|
|
|
203
215
|
...(systemPrompt ? [{ role: "system", content: systemPrompt }] : []),
|
|
204
216
|
{ role: "user", content: userContent },
|
|
205
217
|
];
|
|
218
|
+
let finalText = "";
|
|
206
219
|
|
|
207
220
|
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
208
221
|
log(`[api/openai] round ${round + 1}`);
|
|
@@ -218,6 +231,7 @@ async function runOpenAiLoop(apiKey, baseUrl, model, systemPrompt, userContent,
|
|
|
218
231
|
const txt = typeof msg.content === "string" ? msg.content : "";
|
|
219
232
|
if (txt.trim()) options.onToolCall("assistant", txt.slice(0, 200));
|
|
220
233
|
}
|
|
234
|
+
if (typeof msg.content === "string" && msg.content.trim()) finalText = msg.content;
|
|
221
235
|
|
|
222
236
|
if (choice.finish_reason === "stop" || choice.finish_reason === "end_turn" || !msg.tool_calls?.length) {
|
|
223
237
|
log(`[api/openai] finished (${choice.finish_reason ?? "no-tool-calls"})`);
|
|
@@ -240,10 +254,12 @@ async function runOpenAiLoop(apiKey, baseUrl, model, systemPrompt, userContent,
|
|
|
240
254
|
}
|
|
241
255
|
messages.push(...toolResults);
|
|
242
256
|
}
|
|
257
|
+
return finalText;
|
|
243
258
|
}
|
|
244
259
|
|
|
245
260
|
async function runAnthropicLoop(apiKey, model, systemPrompt, userContent, workspaceRoot, log, options) {
|
|
246
261
|
const messages = [{ role: "user", content: userContent }];
|
|
262
|
+
let finalText = "";
|
|
247
263
|
|
|
248
264
|
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
249
265
|
log(`[api/anthropic] round ${round + 1}`);
|
|
@@ -256,6 +272,8 @@ async function runAnthropicLoop(apiKey, model, systemPrompt, userContent, worksp
|
|
|
256
272
|
const textBlock = resp.content?.find((b) => b.type === "text");
|
|
257
273
|
if (textBlock?.text) options.onToolCall("assistant", textBlock.text.slice(0, 200));
|
|
258
274
|
}
|
|
275
|
+
const textBlock = resp.content?.find((b) => b.type === "text");
|
|
276
|
+
if (textBlock?.text?.trim()) finalText = textBlock.text;
|
|
259
277
|
|
|
260
278
|
if (resp.stop_reason === "end_turn" || resp.stop_reason === "stop_sequence") {
|
|
261
279
|
log(`[api/anthropic] finished (${resp.stop_reason})`);
|
|
@@ -280,6 +298,7 @@ async function runAnthropicLoop(apiKey, model, systemPrompt, userContent, worksp
|
|
|
280
298
|
}
|
|
281
299
|
messages.push({ role: "user", content: toolResults });
|
|
282
300
|
}
|
|
301
|
+
return finalText;
|
|
283
302
|
}
|
|
284
303
|
|
|
285
304
|
// ─── 公共解析函数 ─────────────────────────────────────────────────────────────
|
|
@@ -313,8 +332,11 @@ export function parseApiModel(str) {
|
|
|
313
332
|
* uuid — 用于日志
|
|
314
333
|
* onToolCall — (subtype: string, name: string) => void 供 spinner 展示
|
|
315
334
|
*/
|
|
316
|
-
export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContext, taskBody, subagent, instanceId }, options = {}) {
|
|
335
|
+
export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId }, options = {}) {
|
|
317
336
|
const absRoot = path.resolve(workspaceRoot);
|
|
337
|
+
const execRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
|
|
338
|
+
const absRunDir = intermediatePath ? path.resolve(workspaceRoot, intermediatePath) : "";
|
|
339
|
+
const absResultPath = absRunDir && resultPathRel ? path.join(absRunDir, resultPathRel) : "";
|
|
318
340
|
const flowName = options.flowName ?? null;
|
|
319
341
|
const uuid = options.uuid ?? null;
|
|
320
342
|
|
|
@@ -329,7 +351,9 @@ export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContex
|
|
|
329
351
|
|
|
330
352
|
// ── 读取 Agent 角色提示,注入 nodeContext/taskBody ────────────────────────
|
|
331
353
|
const replacements = {
|
|
332
|
-
workspaceRoot:
|
|
354
|
+
workspaceRoot: execRoot,
|
|
355
|
+
executionWorkspaceRoot: execRoot,
|
|
356
|
+
pipelineWorkspace: absRoot,
|
|
333
357
|
nodeContext: nodeContext ?? "",
|
|
334
358
|
taskBody: taskBody ?? "",
|
|
335
359
|
flowName: flowName ?? "",
|
|
@@ -349,12 +373,14 @@ export async function runApiAgentForNode(workspaceRoot, { promptPath, nodeContex
|
|
|
349
373
|
if (provider === "anthropic") {
|
|
350
374
|
const key = process.env.ANTHROPIC_API_KEY;
|
|
351
375
|
if (!key) throw new Error("[api-runner] ANTHROPIC_API_KEY is required for api:anthropic/* models");
|
|
352
|
-
await runAnthropicLoop(key, model, systemPrompt, userContent,
|
|
376
|
+
const finalText = await runAnthropicLoop(key, model, systemPrompt, userContent, execRoot, log, options);
|
|
377
|
+
writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, finalText);
|
|
353
378
|
} else {
|
|
354
379
|
const key = process.env.OPENAI_API_KEY;
|
|
355
380
|
if (!key) throw new Error("[api-runner] OPENAI_API_KEY is required for api:openai/* models");
|
|
356
381
|
const baseUrl = (process.env.OPENAI_BASE_URL ?? DEFAULT_OPENAI_BASE).trim();
|
|
357
|
-
await runOpenAiLoop(key, baseUrl, model, systemPrompt, userContent,
|
|
382
|
+
const finalText = await runOpenAiLoop(key, baseUrl, model, systemPrompt, userContent, execRoot, log, options);
|
|
383
|
+
writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, finalText);
|
|
358
384
|
}
|
|
359
385
|
|
|
360
386
|
log(`done instanceId=${instanceId ?? "-"}`);
|
package/bin/lib/apply.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { formatDuration } from "./terminal.mjs";
|
|
|
23
23
|
import { printEntryAndFlowFiles, printNodeStatusTable, runValidateFlowAndExitIfInvalid } from "./ui-print.mjs";
|
|
24
24
|
import { clearApplyActiveLock, writeApplyActiveLock } from "./run-apply-active-lock.mjs";
|
|
25
25
|
import { ensureReference, findFlowNameByUuid, getFlowDir, getRunDir } from "./workspace.mjs";
|
|
26
|
+
import { readUserEnvObject } from "./user-env.mjs";
|
|
26
27
|
|
|
27
28
|
const PARALLEL_PREFIX_COLORS = [
|
|
28
29
|
(s) => chalk.cyan(s),
|
|
@@ -344,11 +345,11 @@ ${currentContent}
|
|
|
344
345
|
fs.mkdirSync(path.dirname(tmpPromptFile), { recursive: true });
|
|
345
346
|
fs.writeFileSync(tmpPromptFile, fullPrompt, "utf-8");
|
|
346
347
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
const result = spawnSync(opencodeCmd, ["--prompt-file", tmpPromptFile, "--print"], {
|
|
349
|
+
cwd: workspaceRoot,
|
|
350
|
+
env: { ...process.env, ...readUserEnvObject(process.env.AGENTFLOW_USER_ID || ""), OPENCODE_NON_INTERACTIVE: "1" },
|
|
351
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
352
|
+
});
|
|
352
353
|
|
|
353
354
|
try { fs.unlinkSync(tmpPromptFile); } catch (_) {}
|
|
354
355
|
|