@fieldwangai/agentflow 0.1.153 → 0.1.156

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 (193) hide show
  1. package/README.md +16 -34
  2. package/README.zh-CN.md +14 -34
  3. package/agents/agentflow-node-executor-code.md +13 -3
  4. package/agents/agentflow-node-executor-planning.md +13 -3
  5. package/agents/agentflow-node-executor-requirement.md +13 -3
  6. package/agents/agentflow-node-executor-test.md +13 -3
  7. package/agents/agentflow-node-executor-ui.md +13 -3
  8. package/agents/agentflow-node-executor.md +13 -3
  9. package/agents/en/agentflow-node-executor.md +13 -3
  10. package/agents/zh/agentflow-node-executor.md +13 -3
  11. package/bin/agentflow.mjs +3 -3
  12. package/bin/lib/admin-builtin-pipelines.mjs +3 -2
  13. package/bin/lib/agent-runners.mjs +0 -929
  14. package/bin/lib/auth.mjs +2 -1
  15. package/bin/lib/catalog-agents.mjs +2 -1
  16. package/bin/lib/catalog-flows.mjs +165 -90
  17. package/bin/lib/composer-agent.mjs +3 -793
  18. package/bin/lib/composer-skill-router.mjs +3 -323
  19. package/bin/lib/exec-buffered.mjs +32 -0
  20. package/bin/lib/flow-dsl/cli.mjs +277 -0
  21. package/bin/lib/flow-dsl/codegen.mjs +358 -0
  22. package/bin/lib/flow-dsl/defs.mjs +103 -0
  23. package/bin/lib/flow-dsl/index.mjs +77 -0
  24. package/bin/lib/flow-dsl/ir.mjs +305 -0
  25. package/bin/lib/flow-dsl/layout.mjs +115 -0
  26. package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
  27. package/bin/lib/flow-dsl/lint.mjs +241 -0
  28. package/bin/lib/flow-dsl/packages.mjs +251 -0
  29. package/bin/lib/flow-dsl/parser.mjs +444 -0
  30. package/bin/lib/flow-import.mjs +96 -26
  31. package/bin/lib/flow-write.mjs +48 -12
  32. package/bin/lib/help.mjs +8 -28
  33. package/bin/lib/html-escape.mjs +19 -0
  34. package/bin/lib/http-util.mjs +49 -0
  35. package/bin/lib/legacy-flow-execution.mjs +37 -0
  36. package/bin/lib/locales/en.json +1 -1
  37. package/bin/lib/locales/zh.json +1 -1
  38. package/bin/lib/main.mjs +152 -286
  39. package/bin/lib/marketplace.mjs +206 -61
  40. package/bin/lib/model-config.mjs +10 -0
  41. package/bin/lib/node-package-archive.mjs +240 -0
  42. package/bin/lib/node-package-bootstrap.mjs +72 -0
  43. package/bin/lib/node-package-manifest.mjs +174 -0
  44. package/bin/lib/paths.mjs +42 -19
  45. package/bin/lib/prd-workflow-routes.mjs +2949 -0
  46. package/bin/lib/prd-workflow-server.mjs +4884 -0
  47. package/bin/lib/run-events.mjs +0 -66
  48. package/bin/lib/schedule-config.mjs +4 -4
  49. package/bin/lib/skill-runtime.mjs +35 -0
  50. package/bin/lib/startup-storage-migrations.mjs +274 -0
  51. package/bin/lib/ui-server.mjs +1092 -18123
  52. package/bin/lib/user-env.mjs +16 -0
  53. package/bin/lib/workspace-auto-layout.mjs +234 -0
  54. package/bin/lib/workspace-flow-store.mjs +478 -0
  55. package/bin/lib/workspace-graph-merge.mjs +41 -34
  56. package/bin/lib/workspace-preview.mjs +12 -2
  57. package/bin/lib/workspace-routes.mjs +3088 -0
  58. package/bin/lib/workspace-server.mjs +6199 -0
  59. package/bin/lib/workspace-state.mjs +331 -0
  60. package/bin/lib/workspace.mjs +2 -1
  61. package/builtin/nodes/agent_subAgent.md +1 -0
  62. package/builtin/nodes/control_agent_toBool.md +2 -0
  63. package/builtin/nodes/control_anyOne.md +2 -0
  64. package/builtin/nodes/control_cancelled.md +2 -0
  65. package/builtin/nodes/control_cd_workspace.md +5 -11
  66. package/builtin/nodes/control_delay.md +2 -0
  67. package/builtin/nodes/control_end.md +2 -0
  68. package/builtin/nodes/control_if.md +1 -0
  69. package/builtin/nodes/control_interval_loop.md +2 -0
  70. package/builtin/nodes/control_load_mcp.md +24 -0
  71. package/builtin/nodes/control_load_skills.md +8 -23
  72. package/builtin/nodes/control_start.md +2 -0
  73. package/builtin/nodes/control_toBool.md +2 -0
  74. package/builtin/nodes/control_user_workspace.md +2 -0
  75. package/builtin/nodes/control_wait_until.md +2 -0
  76. package/builtin/nodes/display_ascii.md +1 -0
  77. package/builtin/nodes/display_chart.md +1 -0
  78. package/builtin/nodes/display_html.md +1 -0
  79. package/builtin/nodes/display_image.md +1 -0
  80. package/builtin/nodes/display_markdown.md +1 -0
  81. package/builtin/nodes/display_mermaid.md +1 -0
  82. package/builtin/nodes/display_react_app.md +1 -0
  83. package/builtin/nodes/display_table.md +1 -0
  84. package/builtin/nodes/provide_bool.md +1 -0
  85. package/builtin/nodes/provide_file.md +1 -0
  86. package/builtin/nodes/provide_password.md +1 -0
  87. package/builtin/nodes/provide_str.md +1 -0
  88. package/builtin/nodes/tool_display_share_link.md +1 -0
  89. package/builtin/nodes/tool_get_env.md +2 -0
  90. package/builtin/nodes/tool_git_checkout.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  92. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  93. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  94. package/builtin/nodes/tool_jenkins_build.md +2 -0
  95. package/builtin/nodes/tool_load_key.md +2 -0
  96. package/builtin/nodes/tool_nodejs.md +17 -19
  97. package/builtin/nodes/tool_print.md +2 -0
  98. package/builtin/nodes/tool_save_key.md +2 -0
  99. package/builtin/nodes/tool_set_run_env.md +1 -0
  100. package/builtin/nodes/tool_user_ask.md +2 -0
  101. package/builtin/nodes/tool_user_check.md +2 -0
  102. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  103. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  104. package/builtin/nodes/workspace_one_click_task.md +44 -0
  105. package/builtin/nodes/workspace_run.md +16 -0
  106. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  107. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  108. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  109. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  110. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  111. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  112. package/builtin/pipelines/new/workspace.flow.js +91 -0
  113. package/builtin/pipelines/new/workspace.layout.json +70 -0
  114. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
  115. package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
  116. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  117. package/builtin/web-ui/dist/index.html +2 -2
  118. package/package.json +3 -1
  119. package/reference/flow-control-capabilities.md +77 -158
  120. package/reference/flow-layout.md +1 -1
  121. package/reference/flow-prompt-handler-check.md +2 -2
  122. package/skills/agentflow-author-flow/SKILL.md +1 -1
  123. package/skills/agentflow-cli/SKILL.md +139 -24
  124. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  125. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  126. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  127. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  128. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  129. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  130. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  131. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  132. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  133. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  134. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  135. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  136. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  137. package/skills/agentflow-node-reference/SKILL.md +2 -2
  138. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  139. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  140. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  141. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  142. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  143. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  144. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  145. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  146. package/bin/lib/api-runner.mjs +0 -387
  147. package/bin/lib/apply.mjs +0 -903
  148. package/bin/lib/composer-flow-instances.mjs +0 -68
  149. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  150. package/bin/lib/composer-flow-validate.mjs +0 -47
  151. package/bin/lib/composer-model-router.mjs +0 -185
  152. package/bin/lib/composer-node-schema.mjs +0 -303
  153. package/bin/lib/composer-planner.mjs +0 -751
  154. package/bin/lib/composer-script-ops.mjs +0 -233
  155. package/bin/lib/flow-static-preview.mjs +0 -104
  156. package/bin/lib/hub-login.mjs +0 -54
  157. package/bin/lib/hub-publish.mjs +0 -159
  158. package/bin/lib/hub-remote.mjs +0 -189
  159. package/bin/lib/hub.mjs +0 -299
  160. package/bin/lib/jenkins.mjs +0 -380
  161. package/bin/lib/node-execute.mjs +0 -539
  162. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  163. package/bin/lib/runtime-context.mjs +0 -243
  164. package/bin/lib/scheduler.mjs +0 -601
  165. package/bin/lib/ui-print.mjs +0 -94
  166. package/bin/pipeline/build-node-prompt.mjs +0 -271
  167. package/bin/pipeline/check-cache.mjs +0 -191
  168. package/bin/pipeline/check-flow.mjs +0 -543
  169. package/bin/pipeline/collect-nodes.mjs +0 -212
  170. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  171. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  172. package/bin/pipeline/gc.mjs +0 -129
  173. package/bin/pipeline/get-env.mjs +0 -59
  174. package/bin/pipeline/get-resolved-values.mjs +0 -344
  175. package/bin/pipeline/load-key.mjs +0 -62
  176. package/bin/pipeline/parse-flow.mjs +0 -708
  177. package/bin/pipeline/post-process-control-if.mjs +0 -23
  178. package/bin/pipeline/post-process-node.mjs +0 -490
  179. package/bin/pipeline/pre-process-node.mjs +0 -1430
  180. package/bin/pipeline/resolve-inputs.mjs +0 -201
  181. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  182. package/bin/pipeline/save-key.mjs +0 -93
  183. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  184. package/bin/pipeline/validate-for-ui.mjs +0 -234
  185. package/bin/pipeline/validate-script-output.mjs +0 -130
  186. package/bin/pipeline/write-result.mjs +0 -182
  187. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  188. package/builtin/pipelines/new/flow.yaml +0 -545
  189. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  190. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  191. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  192. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  193. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -3,11 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import chalk from "chalk";
5
5
  import { createMarkdownStreamer, render as renderMarkdown } from "markdansi";
6
- import { getAgentPath, loadAgentPromptWithReplacements, stripYamlFrontmatter } from "./agents-path.mjs";
7
- import { machineReadable } from "./log.mjs";
8
6
  import { normalizeCursorModelForCli } from "./model-config.mjs";
9
- import { appendRunLogLine } from "./run-events.mjs";
10
- import { writeWithPrefix } from "./terminal.mjs";
11
7
  import { t } from "./i18n.mjs";
12
8
  import { readMergedEnvObject } from "./user-env.mjs";
13
9
  import {
@@ -81,17 +77,6 @@ function cursorResultErrorText(event) {
81
77
  return "";
82
78
  }
83
79
 
84
- function writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, text) {
85
- const body = String(text ?? "").trim();
86
- if (!body) return;
87
- fs.mkdirSync(path.dirname(absResultPath), { recursive: true });
88
- fs.writeFileSync(absResultPath, body + "\n", "utf-8");
89
- if (!instanceId) return;
90
- const slotPath = path.join(absRunDir, "output", instanceId, outputNodeBasename(instanceId, 1, "result"));
91
- fs.mkdirSync(path.dirname(slotPath), { recursive: true });
92
- fs.writeFileSync(slotPath, body + "\n", "utf-8");
93
- }
94
-
95
80
  function envFlag(name, defaultValue = false) {
96
81
  const raw = process.env[name];
97
82
  if (raw == null || raw === "") return defaultValue;
@@ -319,920 +304,6 @@ export function summarizeCursorStderr(stderr, maxChars = 1200) {
319
304
  /**
320
305
  * Run Cursor CLI with stream-json, forward events to stdout, return success/failure.
321
306
  */
322
- export function runCursorAgentForNode(
323
- workspaceRoot,
324
- { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId },
325
- options = {},
326
- ) {
327
- const absPromptPath = path.resolve(workspaceRoot, promptPath);
328
- const absRunDir = path.resolve(workspaceRoot, intermediatePath);
329
- const absResultPath = path.join(absRunDir, resultPathRel);
330
- const nodeIntermediateDir = path.dirname(absPromptPath);
331
- const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
332
- if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
333
- const absWorkspaceRoot = path.resolve(workspaceRoot);
334
- const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
335
- const replacements = {
336
- workspaceRoot: execWorkspaceRoot,
337
- executionWorkspaceRoot: execWorkspaceRoot,
338
- pipelineWorkspace: absWorkspaceRoot,
339
- promptPath: absPromptPath,
340
- nodeContext: nodeContext ?? "",
341
- taskBody: taskBody ?? "",
342
- resultPath: absResultPath,
343
- intermediatePath: path.join(absRunDir, "intermediate"),
344
- outputDir,
345
- flowName: options.flowName ?? "",
346
- uuid: options.uuid ?? "",
347
- instanceId: instanceId ?? "",
348
- };
349
- const agentContent = loadAgentPromptWithReplacements(workspaceRoot, subagent, replacements);
350
- let agentPathForPrompt = getAgentPath(workspaceRoot, subagent);
351
- if (agentContent) {
352
- const resolvedAgentPath = path.join(nodeIntermediateDir, `agent-${subagent}.md`);
353
- fs.mkdirSync(nodeIntermediateDir, { recursive: true });
354
- fs.writeFileSync(resolvedAgentPath, agentContent, "utf8");
355
- agentPathForPrompt = resolvedAgentPath;
356
- }
357
- const rawAgentContent =
358
- agentContent != null
359
- ? agentContent
360
- : fs.existsSync(agentPathForPrompt)
361
- ? fs.readFileSync(agentPathForPrompt, "utf8")
362
- : "";
363
- const promptText = stripYamlFrontmatter(rawAgentContent);
364
-
365
- const modelRaw = options.model ?? process.env.CURSOR_AGENT_MODEL ?? null;
366
- const model = normalizeCursorModelForCli(modelRaw);
367
- const rawPrefix = options.outputPrefix != null ? `[${options.outputPrefix}] ` : "";
368
- const coloredPrefix = rawPrefix && options.prefixColor ? options.prefixColor(rawPrefix) : rawPrefix;
369
- const agentContentColor = options.contentColor ?? ((line) => chalk.gray(line));
370
- const {
371
- baseEnv: cursorBaseEnv,
372
- attempts: cursorAttempts,
373
- attemptIndex: cursorAttemptIndex,
374
- selection: cursorSelection,
375
- } = cursorAttemptOptions(options);
376
-
377
- return new Promise((resolve, reject) => {
378
- const agentCmd = process.env.CURSOR_AGENT_CMD || "agent";
379
- const args = ["--print", "--output-format", "stream-json", "--trust", "--workspace", execWorkspaceRoot];
380
- const approveMcps = process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "0" && process.env.AGENTFLOW_CURSOR_APPROVE_MCPS !== "false";
381
- if (approveMcps) args.push("--approve-mcps");
382
- if (options.force) args.push("--force");
383
- if (shouldPassCursorModelArg(model)) args.push("--model", model);
384
- args.push(promptText);
385
- if (options.flowName && options.uuid) {
386
- const argvLog = args.slice(0, -1).concat([`(prompt ${args[args.length - 1].length} chars)`]);
387
- appendRunLogLine(workspaceRoot, options.flowName, options.uuid, "cli-raw", `Cursor CLI 完整参数: ${agentCmd} ${JSON.stringify(argvLog)}`);
388
- if (cursorAttempts.length > 1) {
389
- appendRunLogLine(
390
- workspaceRoot,
391
- options.flowName,
392
- options.uuid,
393
- "cli-raw",
394
- `Cursor API Key 尝试: ${cursorApiKeyLabel(cursorSelection)} / ${cursorAttempts.length}`,
395
- );
396
- }
397
- appendRunLogLine(
398
- workspaceRoot,
399
- options.flowName,
400
- options.uuid,
401
- "cli-raw",
402
- `Cursor CLI prompt 前 800 字:\n${promptText.slice(0, 800)}${promptText.length > 800 ? "..." : ""}`,
403
- );
404
- appendRunLogLine(workspaceRoot, options.flowName, options.uuid, "cli-raw", `Cursor CLI prompt 完整:\n${promptText}`);
405
- }
406
- const useStderrInherit = process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "1" || process.env.AGENTFLOW_CURSOR_STDERR_INHERIT === "true";
407
- const child = spawn(agentCmd, args, {
408
- cwd: execWorkspaceRoot,
409
- stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
410
- shell: false,
411
- env: childEnv(options, cursorApiKeyEnv(cursorSelection)),
412
- });
413
-
414
- let lastResult = null;
415
- let hadError = false;
416
- let hadToolActivity = false;
417
- const assistantTextChunks = [];
418
- const STDERR_CAP_BYTES = 1024 * 1024;
419
- const stderrChunks = [];
420
- let stderrTotalBytes = 0;
421
- const stderrBuffer = options.stderrBuffer || null;
422
- let stderrLineBuffer = "";
423
- const flowName = options.flowName ?? null;
424
- const uuid = options.uuid ?? null;
425
-
426
- const outStream = machineReadable ? process.stderr : process.stdout;
427
- function writeStdout(text) {
428
- if (coloredPrefix) writeWithPrefix(outStream, text, coloredPrefix, agentContentColor);
429
- else if (text) outStream.write(agentContentColor(text));
430
- if (text && flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "cursor-stdout", text);
431
- }
432
-
433
- function flushStderrLines() {
434
- if (!coloredPrefix) return;
435
- let idx;
436
- while ((idx = stderrLineBuffer.indexOf("\n")) !== -1) {
437
- const line = stderrLineBuffer.slice(0, idx + 1);
438
- stderrLineBuffer = stderrLineBuffer.slice(idx + 1);
439
- writeWithPrefix(process.stderr, line, coloredPrefix, agentContentColor);
440
- }
441
- }
442
-
443
- if (!useStderrInherit) {
444
- child.stderr.on("data", (chunk) => {
445
- const s = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
446
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8");
447
- const len = buf.length;
448
- while (stderrChunks.length > 0 && stderrTotalBytes + len > STDERR_CAP_BYTES) {
449
- const drop = stderrChunks.shift();
450
- stderrTotalBytes -= Buffer.isBuffer(drop) ? drop.length : Buffer.byteLength(drop, "utf-8");
451
- }
452
- stderrChunks.push(buf);
453
- stderrTotalBytes += len;
454
- if (stderrBuffer) {
455
- stderrBuffer.push(chunk);
456
- } else if (coloredPrefix) {
457
- stderrLineBuffer += s;
458
- flushStderrLines();
459
- } else {
460
- process.stderr.write(chunk);
461
- }
462
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "cursor-stderr", s);
463
- });
464
- }
465
-
466
- const stdoutWidth = process.stdout.columns ?? 80;
467
- const mdStreamer = createMarkdownStreamer({
468
- render: (md) => renderMarkdown(md, { width: stdoutWidth }),
469
- spacing: "single",
470
- });
471
-
472
- const STDOUT_RAW_CAP = 200;
473
- const debugStdout = process.env.AGENTFLOW_DEBUG_STDOUT === "1" || process.env.AGENTFLOW_DEBUG_STDOUT === "true";
474
-
475
- function isLikelyBase64(s) {
476
- if (!s || typeof s !== "string") return false;
477
- const t = s.trim();
478
- if (t.startsWith("data:image/") && t.includes(";base64,")) return true;
479
- if (t.length < 80) return false;
480
- return /^[A-Za-z0-9+/]+=*$/.test(t);
481
- }
482
-
483
- child.stdout.setEncoding("utf-8");
484
- let stdoutLineBuffer = "";
485
- child.stdout.on("data", (chunk) => {
486
- stdoutLineBuffer += chunk;
487
- const idx = stdoutLineBuffer.lastIndexOf("\n");
488
- const complete = idx >= 0 ? stdoutLineBuffer.slice(0, idx) : "";
489
- if (idx >= 0) stdoutLineBuffer = stdoutLineBuffer.slice(idx + 1);
490
- const lines = complete.split("\n").filter(Boolean);
491
- for (const line of lines) {
492
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "cursor-stdout-raw", line);
493
- try {
494
- const event = JSON.parse(line);
495
- if (event.type === "assistant" && event.message?.content) {
496
- let text = (event.message.content || [])
497
- .filter((c) => c.type === "text" && c.text)
498
- .map((c) => c.text)
499
- .join("");
500
- if (text) {
501
- text = text.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
502
- assistantTextChunks.push(text);
503
- const out = mdStreamer.push(text);
504
- if (out) writeStdout(out);
505
- }
506
- } else if (event.type === "tool_call") {
507
- hadToolActivity = true;
508
- const toolName =
509
- event.tool_call && typeof event.tool_call === "object" ? Object.keys(event.tool_call)[0] ?? "?" : "?";
510
- const subtype = event.subtype ?? "";
511
- if (options.onToolCall) options.onToolCall(subtype, toolName);
512
- } else if (event.type === "thinking") {
513
- if (options.onToolCall) options.onToolCall("thinking", "");
514
- } else if (event.type === "result") {
515
- lastResult = event;
516
- if (event.subtype === "success" && !event.is_error) {
517
- hadError = false;
518
- } else {
519
- hadError = true;
520
- }
521
- } else {
522
- writeStdout(`[cursor-stdout] event: ${event.type ?? "unknown"}\n`);
523
- }
524
- } catch (_) {
525
- let out;
526
- if (line.includes('"type":"tool_call"') || line.includes('"type": "tool_call"')) {
527
- hadToolActivity = true;
528
- let subtype = "?";
529
- try {
530
- const ev = JSON.parse(line);
531
- if (ev && ev.type === "tool_call") subtype = ev.subtype ?? "?";
532
- } catch {
533
- const m = line.match(/"subtype"\s*:\s*"([^"]+)"/);
534
- if (m) subtype = m[1];
535
- }
536
- out = `[cursor] tool_call ${subtype}\n`;
537
- } else if (isLikelyBase64(line)) {
538
- out = `[cursor-stdout] (base64 图片/数据, ${line.length} 字符)\n`;
539
- } else if (debugStdout || line.length <= STDOUT_RAW_CAP) {
540
- out = line + "\n";
541
- } else if (lastResult == null) {
542
- out = `[cursor-stdout] (非 JSON,可能为 Cursor 报错) ${line.slice(0, 500)}${line.length > 500 ? "..." : ""}\n`;
543
- } else {
544
- out = `[cursor-stdout] (解析失败或未处理的一行, ${line.length} 字符)\n`;
545
- }
546
- writeStdout(out);
547
- }
548
- }
549
- });
550
-
551
- child.on("error", (err) => {
552
- child.stdout?.removeAllListeners();
553
- child.stderr?.removeAllListeners();
554
- child.removeAllListeners();
555
- reject(new Error(`Cursor CLI failed to start: ${err.message}. Ensure '${agentCmd}' is in PATH.`));
556
- });
557
-
558
- child.on("close", (code) => {
559
- if (stdoutLineBuffer.trim() && flowName && uuid) {
560
- appendRunLogLine(workspaceRoot, flowName, uuid, "cursor-stdout-raw", stdoutLineBuffer.trim());
561
- }
562
- child.stdout.removeAllListeners();
563
- if (!useStderrInherit) child.stderr.removeAllListeners();
564
- child.removeAllListeners();
565
- const tail = mdStreamer.finish();
566
- if (tail) writeStdout(tail);
567
- if (coloredPrefix && stderrLineBuffer) {
568
- writeWithPrefix(process.stderr, stderrLineBuffer.endsWith("\n") ? stderrLineBuffer : stderrLineBuffer + "\n", coloredPrefix);
569
- }
570
- const retryCursorQuota = (errorText) => {
571
- if (!cursorSelection) return false;
572
- if (cursorAttemptIndex >= cursorAttempts.length - 1) return false;
573
- if (hadToolActivity) return false;
574
- if (!isCursorQuotaError(errorText)) return false;
575
- markCursorApiKeyQuotaBlocked(cursorSelection, cursorApiKeyCooldownMinutes(cursorBaseEnv));
576
- const nextOptions = nextCursorAttemptOptions(options, cursorAttempts, cursorAttemptIndex);
577
- const line = `[agentflow] Cursor API Key ${cursorApiKeyLabel(cursorSelection)} reached quota, retrying ${cursorAttemptIndex + 2}/${cursorAttempts.length}...\n`;
578
- writeStdout(line);
579
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "cli-raw", line.trim());
580
- runCursorAgentForNode(
581
- workspaceRoot,
582
- { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId },
583
- nextOptions,
584
- ).then(resolve).catch(reject);
585
- return true;
586
- };
587
- if (code !== 0 && lastResult == null) {
588
- const stderr = Buffer.concat(stderrChunks).toString("utf-8");
589
- const stderrTail = stderr ? stderr.trim().slice(-1200) : "";
590
- if (retryCursorQuota(stderrTail)) return;
591
- const stderrSummary = summarizeCursorStderr(stderr);
592
- const autoOnly =
593
- /named models unavailable/i.test(stderrTail) ||
594
- (/free plans?/i.test(stderrTail) && /only use auto/i.test(stderrTail)) ||
595
- /only use auto/i.test(stderrTail);
596
- if (autoOnly && model !== "Auto" && !options._agentflowAutoRetry) {
597
- writeStdout(t("runner.cursor_account_limit") + "\n");
598
- runCursorAgentForNode(
599
- workspaceRoot,
600
- { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId },
601
- { ...options, model: "Auto", _agentflowAutoRetry: true },
602
- ).then(resolve).catch(reject);
603
- return;
604
- }
605
- const logHint =
606
- flowName && uuid
607
- ? ` 检查 run 目录 logs/log.txt 查看完整 Cursor stderr;常见原因:未登录 Cursor、模型不可用、网络/权限。若无报错内容,可设置 AGENTFLOW_CURSOR_STDERR_INHERIT=1 后重跑,使 Cursor 的 stderr 直接输出到终端。`
608
- : "";
609
- const err = new Error(`Cursor CLI exited ${code}. ${stderrSummary || "No result event received."}${logHint}`);
610
- err.cursorStderrTail = stderrTail;
611
- reject(err);
612
- return;
613
- }
614
- if (hadError || (lastResult && lastResult.is_error)) {
615
- const errorText = cursorResultErrorText(lastResult) || "Agent reported error.";
616
- if (retryCursorQuota(errorText)) return;
617
- reject(new Error(errorText));
618
- return;
619
- }
620
- writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, assistantTextChunks.join("") || lastResult?.result || "");
621
- resolve();
622
- });
623
- });
624
- }
625
-
626
- /**
627
- * Run OpenCode CLI in non-interactive mode for a node.
628
- */
629
- export function runOpenCodeAgentForNode(
630
- workspaceRoot,
631
- { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId },
632
- options = {},
633
- ) {
634
- const absPromptPath = path.resolve(workspaceRoot, promptPath);
635
- const absRunDir = path.resolve(workspaceRoot, intermediatePath);
636
- const absResultPath = path.join(absRunDir, resultPathRel);
637
- const nodeIntermediateDir = path.dirname(absPromptPath);
638
- const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
639
- if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
640
- const absWorkspaceRoot = path.resolve(workspaceRoot);
641
- const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
642
- const replacements = {
643
- workspaceRoot: execWorkspaceRoot,
644
- executionWorkspaceRoot: execWorkspaceRoot,
645
- pipelineWorkspace: absWorkspaceRoot,
646
- promptPath: absPromptPath,
647
- nodeContext: nodeContext ?? "",
648
- taskBody: taskBody ?? "",
649
- resultPath: absResultPath,
650
- intermediatePath: path.join(absRunDir, "intermediate"),
651
- outputDir,
652
- flowName: options.flowName ?? "",
653
- uuid: options.uuid ?? "",
654
- instanceId: instanceId ?? "",
655
- };
656
- const agentContent = loadAgentPromptWithReplacements(workspaceRoot, subagent, replacements);
657
- let agentPathForPrompt = getAgentPath(workspaceRoot, subagent);
658
- if (agentContent) {
659
- const resolvedAgentPath = path.join(nodeIntermediateDir, `agent-${subagent}.md`);
660
- fs.mkdirSync(nodeIntermediateDir, { recursive: true });
661
- fs.writeFileSync(resolvedAgentPath, agentContent, "utf8");
662
- agentPathForPrompt = resolvedAgentPath;
663
- }
664
- const rawAgentContent =
665
- agentContent != null
666
- ? agentContent
667
- : fs.existsSync(agentPathForPrompt)
668
- ? fs.readFileSync(agentPathForPrompt, "utf8")
669
- : "";
670
- const promptText = stripYamlFrontmatter(rawAgentContent);
671
-
672
- const model = options.model && String(options.model).trim();
673
- const rawPrefix = options.outputPrefix != null ? `[${options.outputPrefix}] ` : "";
674
- const coloredPrefix = rawPrefix && options.prefixColor ? options.prefixColor(rawPrefix) : rawPrefix;
675
- const agentContentColor = options.contentColor ?? ((line) => chalk.gray(line));
676
-
677
- return new Promise((resolve, reject) => {
678
- const opencodeCmd = process.env.OPENCODE_CMD || "opencode";
679
- const args = ["run"];
680
- if (model) {
681
- args.push("--model", model);
682
- }
683
- args.push("--dir", execWorkspaceRoot);
684
- args.push("--", promptText);
685
- const spawnOpts = {
686
- cwd: execWorkspaceRoot,
687
- stdio: ["ignore", "pipe", "pipe"],
688
- shell: false,
689
- env: childEnv(options),
690
- };
691
- if (options.force) {
692
- spawnOpts.env = {
693
- ...spawnOpts.env,
694
- OPENCODE_CONFIG_CONTENT: JSON.stringify({
695
- permission: { external_directory: "allow" },
696
- }),
697
- };
698
- }
699
- const child = spawn(opencodeCmd, args, spawnOpts);
700
- const flowName = options.flowName ?? null;
701
- const uuid = options.uuid ?? null;
702
-
703
- let stdoutLogBuf = "";
704
- let stderrLogBuf = "";
705
- let stdoutCaptured = "";
706
-
707
- function drainLogBuf(buf, tag) {
708
- let idx;
709
- while ((idx = buf.indexOf("\n")) !== -1) {
710
- const raw = buf.slice(0, idx);
711
- buf = buf.slice(idx + 1);
712
- const line = stripAnsi(raw).trimEnd();
713
- if (line.trim() && flowName && uuid) {
714
- appendRunLogLine(workspaceRoot, flowName, uuid, tag, line);
715
- }
716
- }
717
- return buf;
718
- }
719
-
720
- function flushLogBuf(buf, tag) {
721
- if (!buf) return;
722
- const line = stripAnsi(buf).trimEnd();
723
- if (line.trim() && flowName && uuid) {
724
- appendRunLogLine(workspaceRoot, flowName, uuid, tag, line);
725
- }
726
- }
727
-
728
- child.stdout.setEncoding("utf-8");
729
- child.stdout.on("data", (chunk) => {
730
- if (coloredPrefix) writeWithPrefix(process.stdout, chunk, coloredPrefix, agentContentColor);
731
- else process.stdout.write(agentContentColor(chunk));
732
- const normalizedChunk = String(chunk).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
733
- stdoutCaptured += normalizedChunk;
734
- stdoutLogBuf += normalizedChunk;
735
- stdoutLogBuf = drainLogBuf(stdoutLogBuf, "opencode-stdout");
736
- });
737
-
738
- child.stderr.on("data", (chunk) => {
739
- const s = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
740
- if (coloredPrefix) {
741
- writeWithPrefix(process.stderr, s, coloredPrefix, agentContentColor);
742
- } else {
743
- process.stderr.write(chunk);
744
- }
745
- stderrLogBuf += s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
746
- stderrLogBuf = drainLogBuf(stderrLogBuf, "opencode-stderr");
747
- });
748
-
749
- child.on("error", (err) => {
750
- child.stdout?.removeAllListeners();
751
- child.stderr?.removeAllListeners();
752
- child.removeAllListeners();
753
- reject(new Error(`OpenCode CLI failed to start: ${err.message}. Ensure '${opencodeCmd}' is in PATH.`));
754
- });
755
-
756
- child.on("close", (code) => {
757
- child.stdout.removeAllListeners();
758
- child.stderr.removeAllListeners();
759
- child.removeAllListeners();
760
- flushLogBuf(stdoutLogBuf, "opencode-stdout");
761
- flushLogBuf(stderrLogBuf, "opencode-stderr");
762
- if (code !== 0) {
763
- reject(new Error(`OpenCode CLI exited ${code}.`));
764
- return;
765
- }
766
- writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, stripAnsi(stdoutCaptured));
767
- resolve();
768
- });
769
- });
770
- }
771
-
772
- /**
773
- * Run Claude Code CLI (`claude`) in non-interactive stream-json mode for a node.
774
- * NDJSON event schema: system(init) / assistant(message.content[]) / user(tool_result) / result.
775
- * Thinking and text both live as content blocks inside assistant events (not as top-level events).
776
- */
777
- export function runClaudeCodeAgentForNode(
778
- workspaceRoot,
779
- { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId },
780
- options = {},
781
- ) {
782
- const absPromptPath = path.resolve(workspaceRoot, promptPath);
783
- const absRunDir = path.resolve(workspaceRoot, intermediatePath);
784
- const absResultPath = path.join(absRunDir, resultPathRel);
785
- const nodeIntermediateDir = path.dirname(absPromptPath);
786
- const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
787
- if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
788
- const absWorkspaceRoot = path.resolve(workspaceRoot);
789
- const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
790
- const replacements = {
791
- workspaceRoot: execWorkspaceRoot,
792
- executionWorkspaceRoot: execWorkspaceRoot,
793
- pipelineWorkspace: absWorkspaceRoot,
794
- promptPath: absPromptPath,
795
- nodeContext: nodeContext ?? "",
796
- taskBody: taskBody ?? "",
797
- resultPath: absResultPath,
798
- intermediatePath: path.join(absRunDir, "intermediate"),
799
- outputDir,
800
- flowName: options.flowName ?? "",
801
- uuid: options.uuid ?? "",
802
- instanceId: instanceId ?? "",
803
- };
804
- const agentContent = loadAgentPromptWithReplacements(workspaceRoot, subagent, replacements);
805
- let agentPathForPrompt = getAgentPath(workspaceRoot, subagent);
806
- if (agentContent) {
807
- const resolvedAgentPath = path.join(nodeIntermediateDir, `agent-${subagent}.md`);
808
- fs.mkdirSync(nodeIntermediateDir, { recursive: true });
809
- fs.writeFileSync(resolvedAgentPath, agentContent, "utf8");
810
- agentPathForPrompt = resolvedAgentPath;
811
- }
812
- const rawAgentContent =
813
- agentContent != null
814
- ? agentContent
815
- : fs.existsSync(agentPathForPrompt)
816
- ? fs.readFileSync(agentPathForPrompt, "utf8")
817
- : "";
818
- const promptText = stripYamlFrontmatter(rawAgentContent);
819
-
820
- const model = options.model && String(options.model).trim();
821
- const rawPrefix = options.outputPrefix != null ? `[${options.outputPrefix}] ` : "";
822
- const coloredPrefix = rawPrefix && options.prefixColor ? options.prefixColor(rawPrefix) : rawPrefix;
823
- const agentContentColor = options.contentColor ?? ((line) => chalk.gray(line));
824
-
825
- return new Promise((resolve, reject) => {
826
- const claudeCmd = process.env.CLAUDE_CODE_CMD || "claude";
827
- const bypassPermissions =
828
- process.env.AGENTFLOW_CLAUDE_CODE_BYPASS_PERMISSIONS !== "0" &&
829
- process.env.AGENTFLOW_CLAUDE_CODE_BYPASS_PERMISSIONS !== "false";
830
- const args = ["-p", "--output-format", "stream-json", "--verbose", "--add-dir", execWorkspaceRoot, "--add-dir", absWorkspaceRoot];
831
- if (bypassPermissions) args.push("--dangerously-skip-permissions");
832
- if (model) args.push("--model", model);
833
- args.push(promptText);
834
- if (options.flowName && options.uuid) {
835
- const argvLog = args.slice(0, -1).concat([`(prompt ${args[args.length - 1].length} chars)`]);
836
- appendRunLogLine(
837
- workspaceRoot,
838
- options.flowName,
839
- options.uuid,
840
- "cli-raw",
841
- `Claude Code CLI 完整参数: ${claudeCmd} ${JSON.stringify(argvLog)}`,
842
- );
843
- appendRunLogLine(
844
- workspaceRoot,
845
- options.flowName,
846
- options.uuid,
847
- "cli-raw",
848
- `Claude Code CLI prompt 前 800 字:\n${promptText.slice(0, 800)}${promptText.length > 800 ? "..." : ""}`,
849
- );
850
- appendRunLogLine(workspaceRoot, options.flowName, options.uuid, "cli-raw", `Claude Code CLI prompt 完整:\n${promptText}`);
851
- }
852
- const useStderrInherit =
853
- process.env.AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT === "1" ||
854
- process.env.AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT === "true";
855
- const child = spawn(claudeCmd, args, {
856
- cwd: execWorkspaceRoot,
857
- stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
858
- shell: false,
859
- env: childEnv(options),
860
- });
861
-
862
- let lastResult = null;
863
- let hadError = false;
864
- let sessionId = null;
865
- const assistantTextChunks = [];
866
- const STDERR_CAP_BYTES = 1024 * 1024;
867
- const stderrChunks = [];
868
- let stderrTotalBytes = 0;
869
- const stderrBuffer = options.stderrBuffer || null;
870
- let stderrLineBuffer = "";
871
- const flowName = options.flowName ?? null;
872
- const uuid = options.uuid ?? null;
873
-
874
- const outStream = machineReadable ? process.stderr : process.stdout;
875
- function writeStdout(text) {
876
- if (coloredPrefix) writeWithPrefix(outStream, text, coloredPrefix, agentContentColor);
877
- else if (text) outStream.write(agentContentColor(text));
878
- if (text && flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "claude-code-stdout", text);
879
- }
880
-
881
- function flushStderrLines() {
882
- if (!coloredPrefix) return;
883
- let idx;
884
- while ((idx = stderrLineBuffer.indexOf("\n")) !== -1) {
885
- const line = stderrLineBuffer.slice(0, idx + 1);
886
- stderrLineBuffer = stderrLineBuffer.slice(idx + 1);
887
- writeWithPrefix(process.stderr, line, coloredPrefix, agentContentColor);
888
- }
889
- }
890
-
891
- if (!useStderrInherit) {
892
- child.stderr.on("data", (chunk) => {
893
- const s = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
894
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8");
895
- const len = buf.length;
896
- while (stderrChunks.length > 0 && stderrTotalBytes + len > STDERR_CAP_BYTES) {
897
- const drop = stderrChunks.shift();
898
- stderrTotalBytes -= Buffer.isBuffer(drop) ? drop.length : Buffer.byteLength(drop, "utf-8");
899
- }
900
- stderrChunks.push(buf);
901
- stderrTotalBytes += len;
902
- if (stderrBuffer) {
903
- stderrBuffer.push(chunk);
904
- } else if (coloredPrefix) {
905
- stderrLineBuffer += s;
906
- flushStderrLines();
907
- } else {
908
- process.stderr.write(chunk);
909
- }
910
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "claude-code-stderr", s);
911
- });
912
- }
913
-
914
- const stdoutWidth = process.stdout.columns ?? 80;
915
- const mdStreamer = createMarkdownStreamer({
916
- render: (md) => renderMarkdown(md, { width: stdoutWidth }),
917
- spacing: "single",
918
- });
919
-
920
- child.stdout.setEncoding("utf-8");
921
- let stdoutLineBuffer = "";
922
- child.stdout.on("data", (chunk) => {
923
- stdoutLineBuffer += chunk;
924
- const idx = stdoutLineBuffer.lastIndexOf("\n");
925
- const complete = idx >= 0 ? stdoutLineBuffer.slice(0, idx) : "";
926
- if (idx >= 0) stdoutLineBuffer = stdoutLineBuffer.slice(idx + 1);
927
- const lines = complete.split("\n").filter(Boolean);
928
- for (const line of lines) {
929
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "claude-code-stdout-raw", line);
930
- try {
931
- const event = JSON.parse(line);
932
- if (event && typeof event === "object" && event.session_id && !sessionId) {
933
- sessionId = event.session_id;
934
- }
935
- if (event.type === "system") {
936
- // init 等元事件,仅记录
937
- } else if (event.type === "assistant" && event.message && Array.isArray(event.message.content)) {
938
- for (const block of event.message.content) {
939
- if (!block || typeof block !== "object") continue;
940
- if (block.type === "text" && block.text) {
941
- const text = normalizeStreamTextChunk(block.text);
942
- assistantTextChunks.push(text);
943
- const out = mdStreamer.push(text);
944
- if (out) writeStdout(out);
945
- } else if (block.type === "thinking") {
946
- if (options.onToolCall) options.onToolCall("thinking", "");
947
- } else if (block.type === "tool_use") {
948
- const toolName = block.name || "?";
949
- if (options.onToolCall) options.onToolCall("tool_use", toolName);
950
- writeStdout(`[claude-code] tool ${toolName}\n`);
951
- }
952
- }
953
- } else if (event.type === "user" && event.message && Array.isArray(event.message.content)) {
954
- // tool_result 回传;不向用户 stdout 渲染,只记录
955
- } else if (event.type === "result") {
956
- lastResult = event;
957
- const isSuccess = event.subtype === "success" && !event.is_error;
958
- hadError = !isSuccess;
959
- } else {
960
- writeStdout(`[claude-code-stdout] event: ${event.type ?? "unknown"}\n`);
961
- }
962
- } catch (_) {
963
- writeStdout(`[claude-code-stdout] (非 JSON) ${line.slice(0, 500)}${line.length > 500 ? "..." : ""}\n`);
964
- }
965
- }
966
- });
967
-
968
- child.on("error", (err) => {
969
- child.stdout?.removeAllListeners();
970
- child.stderr?.removeAllListeners();
971
- child.removeAllListeners();
972
- reject(
973
- new Error(
974
- `Claude Code CLI failed to start: ${err.message}. Install via 'npm i -g @anthropic-ai/claude-code' and run 'claude /login', or set CLAUDE_CODE_CMD.`,
975
- ),
976
- );
977
- });
978
-
979
- child.on("close", (code) => {
980
- if (stdoutLineBuffer.trim() && flowName && uuid) {
981
- appendRunLogLine(workspaceRoot, flowName, uuid, "claude-code-stdout-raw", stdoutLineBuffer.trim());
982
- }
983
- child.stdout.removeAllListeners();
984
- if (!useStderrInherit) child.stderr.removeAllListeners();
985
- child.removeAllListeners();
986
- const tail = mdStreamer.finish();
987
- if (tail) writeStdout(tail);
988
- if (coloredPrefix && stderrLineBuffer) {
989
- writeWithPrefix(process.stderr, stderrLineBuffer.endsWith("\n") ? stderrLineBuffer : stderrLineBuffer + "\n", coloredPrefix);
990
- }
991
- if (code !== 0 && lastResult == null) {
992
- const stderr = Buffer.concat(stderrChunks).toString("utf-8");
993
- const stderrTail = stderr ? stderr.trim().slice(-1200) : "";
994
- const logHint =
995
- flowName && uuid
996
- ? ` 检查 run 目录 logs/log.txt 查看完整 Claude Code stderr;常见原因:未登录 claude /login、模型不可用、网络/权限。若无报错内容,可设置 AGENTFLOW_CLAUDE_CODE_STDERR_INHERIT=1 后重跑。`
997
- : "";
998
- const err = new Error(`Claude Code CLI exited ${code}. ${stderrTail || "No result event received."}${logHint}`);
999
- err.claudeCodeStderrTail = stderrTail;
1000
- reject(err);
1001
- return;
1002
- }
1003
- if (hadError || (lastResult && lastResult.is_error)) {
1004
- const msg =
1005
- (lastResult && typeof lastResult.result === "string" && lastResult.result) ||
1006
- (lastResult && lastResult.subtype) ||
1007
- "Agent reported error.";
1008
- reject(new Error(String(msg)));
1009
- return;
1010
- }
1011
- writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, assistantTextChunks.join("") || lastResult?.result || "");
1012
- resolve();
1013
- });
1014
- });
1015
- }
1016
-
1017
- /**
1018
- * Run Codex CLI (`codex exec`) in non-interactive JSONL mode for a node.
1019
- */
1020
- export function runCodexAgentForNode(
1021
- workspaceRoot,
1022
- { promptPath, nodeContext, taskBody, intermediatePath, resultPathRel, subagent, instanceId },
1023
- options = {},
1024
- ) {
1025
- const absPromptPath = path.resolve(workspaceRoot, promptPath);
1026
- const absRunDir = path.resolve(workspaceRoot, intermediatePath);
1027
- const absResultPath = path.join(absRunDir, resultPathRel);
1028
- const nodeIntermediateDir = path.dirname(absPromptPath);
1029
- const outputDir = instanceId ? path.join(absRunDir, "output", instanceId) : path.join(absRunDir, "output");
1030
- if (instanceId) fs.mkdirSync(outputDir, { recursive: true });
1031
- const absWorkspaceRoot = path.resolve(workspaceRoot);
1032
- const execWorkspaceRoot = path.resolve(options.execWorkspaceRoot || workspaceRoot);
1033
- const replacements = {
1034
- workspaceRoot: execWorkspaceRoot,
1035
- executionWorkspaceRoot: execWorkspaceRoot,
1036
- pipelineWorkspace: absWorkspaceRoot,
1037
- promptPath: absPromptPath,
1038
- nodeContext: nodeContext ?? "",
1039
- taskBody: taskBody ?? "",
1040
- resultPath: absResultPath,
1041
- intermediatePath: path.join(absRunDir, "intermediate"),
1042
- outputDir,
1043
- flowName: options.flowName ?? "",
1044
- uuid: options.uuid ?? "",
1045
- instanceId: instanceId ?? "",
1046
- };
1047
- const agentContent = loadAgentPromptWithReplacements(workspaceRoot, subagent, replacements);
1048
- let agentPathForPrompt = getAgentPath(workspaceRoot, subagent);
1049
- if (agentContent) {
1050
- const resolvedAgentPath = path.join(nodeIntermediateDir, `agent-${subagent}.md`);
1051
- fs.mkdirSync(nodeIntermediateDir, { recursive: true });
1052
- fs.writeFileSync(resolvedAgentPath, agentContent, "utf8");
1053
- agentPathForPrompt = resolvedAgentPath;
1054
- }
1055
- const rawAgentContent =
1056
- agentContent != null
1057
- ? agentContent
1058
- : fs.existsSync(agentPathForPrompt)
1059
- ? fs.readFileSync(agentPathForPrompt, "utf8")
1060
- : "";
1061
- const promptText = stripYamlFrontmatter(rawAgentContent);
1062
-
1063
- const model = cleanModel(options.model, "CODEX_MODEL");
1064
- const rawPrefix = options.outputPrefix != null ? `[${options.outputPrefix}] ` : "";
1065
- const coloredPrefix = rawPrefix && options.prefixColor ? options.prefixColor(rawPrefix) : rawPrefix;
1066
- const agentContentColor = options.contentColor ?? ((line) => chalk.gray(line));
1067
-
1068
- return new Promise((resolve, reject) => {
1069
- const codexCmd = process.env.CODEX_CMD || "codex";
1070
- fs.mkdirSync(nodeIntermediateDir, { recursive: true });
1071
- const outputLastMessagePath = path.join(nodeIntermediateDir, "codex-last-message.txt");
1072
- const args = buildCodexExecArgs({
1073
- workspace: execWorkspaceRoot,
1074
- addDirs: [absWorkspaceRoot],
1075
- model,
1076
- outputLastMessagePath,
1077
- promptText,
1078
- configArgs: options.codexConfigArgs,
1079
- });
1080
- if (options.flowName && options.uuid) {
1081
- const argvLog = args.slice(0, -1).concat([`(prompt ${args[args.length - 1].length} chars)`]);
1082
- appendRunLogLine(
1083
- workspaceRoot,
1084
- options.flowName,
1085
- options.uuid,
1086
- "cli-raw",
1087
- `Codex CLI 完整参数: ${codexCmd} ${JSON.stringify(argvLog)}`,
1088
- );
1089
- appendRunLogLine(
1090
- workspaceRoot,
1091
- options.flowName,
1092
- options.uuid,
1093
- "cli-raw",
1094
- `Codex CLI prompt 前 800 字:\n${promptText.slice(0, 800)}${promptText.length > 800 ? "..." : ""}`,
1095
- );
1096
- appendRunLogLine(workspaceRoot, options.flowName, options.uuid, "cli-raw", `Codex CLI prompt 完整:\n${promptText}`);
1097
- }
1098
-
1099
- const useStderrInherit =
1100
- process.env.AGENTFLOW_CODEX_STDERR_INHERIT === "1" ||
1101
- process.env.AGENTFLOW_CODEX_STDERR_INHERIT === "true";
1102
- const child = spawn(codexCmd, args, {
1103
- cwd: execWorkspaceRoot,
1104
- stdio: ["ignore", "pipe", useStderrInherit ? "inherit" : "pipe"],
1105
- shell: false,
1106
- env: childEnv(options),
1107
- });
1108
-
1109
- let hadError = false;
1110
- const assistantTextChunks = [];
1111
- const STDERR_CAP_BYTES = 1024 * 1024;
1112
- const stderrChunks = [];
1113
- let stderrTotalBytes = 0;
1114
- const stderrBuffer = options.stderrBuffer || null;
1115
- let stderrLineBuffer = "";
1116
- const flowName = options.flowName ?? null;
1117
- const uuid = options.uuid ?? null;
1118
-
1119
- const outStream = machineReadable ? process.stderr : process.stdout;
1120
- function writeStdout(text) {
1121
- if (coloredPrefix) writeWithPrefix(outStream, text, coloredPrefix, agentContentColor);
1122
- else if (text) outStream.write(agentContentColor(text));
1123
- if (text && flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "codex-stdout", text);
1124
- }
1125
-
1126
- function flushStderrLines() {
1127
- if (!coloredPrefix) return;
1128
- let idx;
1129
- while ((idx = stderrLineBuffer.indexOf("\n")) !== -1) {
1130
- const line = stderrLineBuffer.slice(0, idx + 1);
1131
- stderrLineBuffer = stderrLineBuffer.slice(idx + 1);
1132
- writeWithPrefix(process.stderr, line, coloredPrefix, agentContentColor);
1133
- }
1134
- }
1135
-
1136
- if (!useStderrInherit) {
1137
- child.stderr.on("data", (chunk) => {
1138
- const s = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
1139
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8");
1140
- const len = buf.length;
1141
- while (stderrChunks.length > 0 && stderrTotalBytes + len > STDERR_CAP_BYTES) {
1142
- const drop = stderrChunks.shift();
1143
- stderrTotalBytes -= Buffer.isBuffer(drop) ? drop.length : Buffer.byteLength(drop, "utf-8");
1144
- }
1145
- stderrChunks.push(buf);
1146
- stderrTotalBytes += len;
1147
- if (stderrBuffer) {
1148
- stderrBuffer.push(chunk);
1149
- } else if (coloredPrefix) {
1150
- stderrLineBuffer += s;
1151
- flushStderrLines();
1152
- } else {
1153
- process.stderr.write(chunk);
1154
- }
1155
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "codex-stderr", s);
1156
- });
1157
- }
1158
-
1159
- const stdoutWidth = process.stdout.columns ?? 80;
1160
- const mdStreamer = createMarkdownStreamer({
1161
- render: (md) => renderMarkdown(md, { width: stdoutWidth }),
1162
- spacing: "single",
1163
- });
1164
-
1165
- function emitNatural(_kind, text) {
1166
- assistantTextChunks.push(text);
1167
- const out = mdStreamer.push(text);
1168
- if (out) writeStdout(out);
1169
- }
1170
-
1171
- function emitStatus(line) {
1172
- if (line) writeStdout(`[codex] ${line}\n`);
1173
- }
1174
-
1175
- child.stdout.setEncoding("utf-8");
1176
- let stdoutLineBuffer = "";
1177
- child.stdout.on("data", (chunk) => {
1178
- stdoutLineBuffer += chunk;
1179
- const idx = stdoutLineBuffer.lastIndexOf("\n");
1180
- const complete = idx >= 0 ? stdoutLineBuffer.slice(0, idx) : "";
1181
- if (idx >= 0) stdoutLineBuffer = stdoutLineBuffer.slice(idx + 1);
1182
- const lines = complete.split("\n").filter(Boolean);
1183
- for (const line of lines) {
1184
- if (flowName && uuid) appendRunLogLine(workspaceRoot, flowName, uuid, "codex-stdout-raw", line);
1185
- try {
1186
- const event = JSON.parse(line);
1187
- const result = handleCodexEvent(event, { emitNatural, emitStatus, onToolCall: options.onToolCall });
1188
- if (result.hadError) hadError = true;
1189
- if (!result.handled) writeStdout(`[codex-stdout] event: ${codexEventKind(event)}\n`);
1190
- } catch (_) {
1191
- writeStdout(`[codex-stdout] (非 JSON) ${line.slice(0, 500)}${line.length > 500 ? "..." : ""}\n`);
1192
- }
1193
- }
1194
- });
1195
-
1196
- child.on("error", (err) => {
1197
- child.stdout?.removeAllListeners();
1198
- child.stderr?.removeAllListeners();
1199
- child.removeAllListeners();
1200
- reject(new Error(`Codex CLI failed to start: ${err.message}. Ensure '${codexCmd}' is in PATH and run 'codex login'.`));
1201
- });
1202
-
1203
- child.on("close", (code) => {
1204
- if (stdoutLineBuffer.trim() && flowName && uuid) {
1205
- appendRunLogLine(workspaceRoot, flowName, uuid, "codex-stdout-raw", stdoutLineBuffer.trim());
1206
- }
1207
- child.stdout.removeAllListeners();
1208
- if (!useStderrInherit) child.stderr.removeAllListeners();
1209
- child.removeAllListeners();
1210
- const tail = mdStreamer.finish();
1211
- if (tail) writeStdout(tail);
1212
- if (coloredPrefix && stderrLineBuffer) {
1213
- writeWithPrefix(process.stderr, stderrLineBuffer.endsWith("\n") ? stderrLineBuffer : stderrLineBuffer + "\n", coloredPrefix);
1214
- }
1215
- const finalMessage = fs.existsSync(outputLastMessagePath)
1216
- ? fs.readFileSync(outputLastMessagePath, "utf-8").trim()
1217
- : assistantTextChunks.join("").trim();
1218
- if (code !== 0) {
1219
- const stderr = Buffer.concat(stderrChunks).toString("utf-8");
1220
- const stderrTail = stderr ? stderr.trim().slice(-1200) : "";
1221
- const err = new Error(codexFailureMessage(code, stderrTail, { flowName, uuid }));
1222
- err.codexStderrTail = stderrTail;
1223
- reject(err);
1224
- return;
1225
- }
1226
- if (hadError) {
1227
- reject(new Error(finalMessage || "Codex reported error."));
1228
- return;
1229
- }
1230
- writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, finalMessage || assistantTextChunks.join(""));
1231
- resolve();
1232
- });
1233
- });
1234
- }
1235
-
1236
307
  const COMPOSER_STATUS_MAX = 200;
1237
308
 
1238
309
  /**