@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.
Files changed (69) hide show
  1. package/agents/agentflow-node-executor-code.md +3 -2
  2. package/agents/agentflow-node-executor-planning.md +3 -2
  3. package/agents/agentflow-node-executor-requirement.md +3 -2
  4. package/agents/agentflow-node-executor-test.md +3 -2
  5. package/agents/agentflow-node-executor-ui.md +3 -2
  6. package/agents/agentflow-node-executor.md +3 -2
  7. package/agents/en/agentflow-node-executor.md +3 -2
  8. package/agents/zh/agentflow-node-executor.md +3 -2
  9. package/bin/lib/agent-runners.mjs +63 -14
  10. package/bin/lib/api-runner.mjs +30 -4
  11. package/bin/lib/apply.mjs +6 -5
  12. package/bin/lib/auth.mjs +240 -0
  13. package/bin/lib/catalog-agents.mjs +2 -2
  14. package/bin/lib/catalog-flows.mjs +196 -17
  15. package/bin/lib/composer-agent.mjs +22 -1
  16. package/bin/lib/composer-skill-router.mjs +10 -78
  17. package/bin/lib/flow-import.mjs +2 -2
  18. package/bin/lib/flow-write.mjs +20 -20
  19. package/bin/lib/help.mjs +2 -2
  20. package/bin/lib/locales/en.json +29 -1
  21. package/bin/lib/locales/zh.json +31 -3
  22. package/bin/lib/main.mjs +6 -1
  23. package/bin/lib/node-exec-context.mjs +5 -5
  24. package/bin/lib/node-execute.mjs +15 -10
  25. package/bin/lib/paths.mjs +69 -13
  26. package/bin/lib/recent-runs.mjs +2 -2
  27. package/bin/lib/run-node-statuses-from-disk.mjs +3 -3
  28. package/bin/lib/runtime-context.mjs +225 -0
  29. package/bin/lib/scheduler.mjs +42 -38
  30. package/bin/lib/skill-registry.mjs +145 -0
  31. package/bin/lib/ui-server.mjs +1517 -57
  32. package/bin/lib/user-env.mjs +83 -0
  33. package/bin/lib/workspace-tree.mjs +4 -3
  34. package/bin/lib/workspace.mjs +9 -11
  35. package/bin/pipeline/build-node-prompt.mjs +29 -4
  36. package/bin/pipeline/get-env.mjs +5 -29
  37. package/bin/pipeline/get-exec-id.mjs +2 -2
  38. package/bin/pipeline/get-resolved-values.mjs +1 -0
  39. package/bin/pipeline/pre-process-node.mjs +328 -6
  40. package/bin/pipeline/run-tool-nodejs.mjs +7 -0
  41. package/bin/pipeline/validate-flow.mjs +2 -0
  42. package/builtin/nodes/agent_subAgent.md +12 -3
  43. package/builtin/nodes/control_cd_workspace.md +45 -0
  44. package/builtin/nodes/control_load_skills.md +50 -0
  45. package/builtin/nodes/control_user_workspace.md +20 -0
  46. package/builtin/nodes/display_ascii.md +22 -0
  47. package/builtin/nodes/display_markdown.md +22 -0
  48. package/builtin/nodes/display_mermaid.md +22 -0
  49. package/builtin/nodes/tool_git_checkout.md +57 -0
  50. package/builtin/nodes/tool_nodejs.md +8 -1
  51. package/builtin/nodes/tool_print.md +4 -1
  52. package/builtin/web-ui/dist/assets/index-BVWwQpvg.css +1 -0
  53. package/builtin/web-ui/dist/assets/index-CvNy1n3f.js +197 -0
  54. package/builtin/web-ui/dist/index.html +2 -2
  55. package/package.json +1 -1
  56. package/skills/agentflow-flow-recipes/SKILL.md +24 -0
  57. package/skills/agentflow-flow-recipes/references/recipes.md +63 -0
  58. package/skills/agentflow-node-reference/SKILL.md +25 -0
  59. package/skills/agentflow-node-reference/references/builtin-nodes.md +210 -0
  60. package/skills/agentflow-placeholder-reference/SKILL.md +24 -0
  61. package/skills/agentflow-placeholder-reference/references/placeholders.md +20 -0
  62. package/skills/agentflow-runtime-reference/SKILL.md +25 -0
  63. package/skills/agentflow-runtime-reference/references/runtime.md +64 -0
  64. package/skills/agentflow-workspace-ascii/SKILL.md +42 -0
  65. package/skills/agentflow-workspace-graph/SKILL.md +67 -0
  66. package/skills/agentflow-workspace-markdown/SKILL.md +44 -0
  67. package/skills/agentflow-workspace-mermaid/SKILL.md +43 -0
  68. package/builtin/web-ui/dist/assets/index-0vJxkTJz.css +0 -1
  69. 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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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 directory)
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"reason for failure"}'
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 ${workspaceRoot} ${flowName} ${uuid} ${instanceId} --json '{"status":"failed","message":"失败原因"}'
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: absWorkspaceRoot,
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", workspaceRoot];
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: workspaceRoot,
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: absWorkspaceRoot,
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", workspaceRoot);
362
+ args.push("--dir", execWorkspaceRoot);
329
363
  args.push("--", promptText);
330
364
  const spawnOpts = {
331
- cwd: workspaceRoot,
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
- ...process.env,
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
- stdoutLogBuf += String(chunk).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
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: absWorkspaceRoot,
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", workspaceRoot];
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: workspaceRoot,
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
- ...process.env,
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;
@@ -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: absRoot,
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, absRoot, log, options);
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, absRoot, log, options);
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
- const result = spawnSync(opencodeCmd, ["--prompt-file", tmpPromptFile, "--print"], {
348
- cwd: workspaceRoot,
349
- env: { ...process.env, OPENCODE_NON_INTERACTIVE: "1" },
350
- stdio: ["ignore", "pipe", "pipe"],
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