@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.
- package/README.md +16 -34
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +165 -90
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +277 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +8 -28
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +37 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +152 -286
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1092 -18123
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +12 -2
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +3 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +1 -1
- package/skills/agentflow-cli/SKILL.md +139 -24
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/SKILL.md +7 -4
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +2 -2
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
- 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
|
/**
|