@fieldwangai/agentflow 0.1.85 → 0.1.86

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.
@@ -0,0 +1,328 @@
1
+ const DEFAULT_BASE_URL = "http://127.0.0.1:8875";
2
+
3
+ function envBaseUrl() {
4
+ return String(process.env.AGENTFLOW_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
5
+ }
6
+
7
+ function envToken() {
8
+ return String(process.env.AGENTFLOW_TOKEN || process.env.AGENTFLOW_SESSION_TOKEN || "").trim();
9
+ }
10
+
11
+ function toolText(data) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
17
+ },
18
+ ],
19
+ };
20
+ }
21
+
22
+ function toolError(error) {
23
+ return {
24
+ isError: true,
25
+ content: [{ type: "text", text: String(error?.message || error || "Unknown error") }],
26
+ };
27
+ }
28
+
29
+ async function httpJson(pathname, { method = "GET", body } = {}) {
30
+ const baseUrl = envBaseUrl();
31
+ const token = envToken();
32
+ const url = new URL(pathname, baseUrl);
33
+ const headers = {
34
+ Accept: "application/json",
35
+ };
36
+ if (body !== undefined) headers["Content-Type"] = "application/json";
37
+ if (token) {
38
+ headers.Authorization = `Bearer ${token}`;
39
+ headers.Cookie = `af_session=${encodeURIComponent(token)}`;
40
+ }
41
+ const response = await fetch(url, {
42
+ method,
43
+ headers,
44
+ body: body === undefined ? undefined : JSON.stringify(body),
45
+ });
46
+ const text = await response.text();
47
+ let data = null;
48
+ try {
49
+ data = text ? JSON.parse(text) : null;
50
+ } catch {
51
+ data = { text };
52
+ }
53
+ if (!response.ok) {
54
+ const message = data?.error || data?.message || text || `HTTP ${response.status}`;
55
+ throw new Error(message);
56
+ }
57
+ return data;
58
+ }
59
+
60
+ function query(params = {}) {
61
+ const search = new URLSearchParams();
62
+ for (const [key, value] of Object.entries(params || {})) {
63
+ if (value === undefined || value === null || value === "") continue;
64
+ search.set(key, String(value));
65
+ }
66
+ const s = search.toString();
67
+ return s ? `?${s}` : "";
68
+ }
69
+
70
+ function displayKind(definitionId) {
71
+ const id = String(definitionId || "");
72
+ if (id === "display_markdown") return "markdown";
73
+ if (id === "display_mermaid") return "mermaid";
74
+ if (id === "display_ascii") return "ascii";
75
+ if (id === "display_html") return "html";
76
+ if (id === "display_react_app") return "react";
77
+ if (id === "display_image") return "image";
78
+ if (id === "display_chart") return "chart";
79
+ if (id === "display_table") return "table";
80
+ return "";
81
+ }
82
+
83
+ function slotText(slots, names = []) {
84
+ const wanted = new Set(names.map(String));
85
+ for (const slot of Array.isArray(slots) ? slots : []) {
86
+ const name = String(slot?.name || "");
87
+ if (wanted.size && !wanted.has(name)) continue;
88
+ const value = slot?.value ?? slot?.default ?? "";
89
+ if (String(value || "").trim()) return String(value);
90
+ }
91
+ return "";
92
+ }
93
+
94
+ function extractDisplayOutputs(graph) {
95
+ const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
96
+ const outputs = [];
97
+ for (const [nodeId, instance] of Object.entries(instances)) {
98
+ const kind = displayKind(instance?.definitionId);
99
+ if (!kind) continue;
100
+ const primary = kind === "image" ? "src" : "content";
101
+ const content = String(instance?.body || "") ||
102
+ slotText(instance?.input, [primary, "content", "markdown", "html", "src"]) ||
103
+ slotText(instance?.output, [primary, "content", "markdown", "html", "src"]);
104
+ outputs.push({
105
+ nodeId,
106
+ label: String(instance?.label || instance?.displayName || nodeId),
107
+ definitionId: String(instance?.definitionId || ""),
108
+ kind,
109
+ content,
110
+ hasContent: Boolean(String(content || "").trim()),
111
+ });
112
+ }
113
+ return outputs;
114
+ }
115
+
116
+ const tools = [
117
+ {
118
+ name: "agentflow_list_flows",
119
+ description: "List AgentFlow flows visible to the configured user.",
120
+ inputSchema: {
121
+ type: "object",
122
+ properties: {},
123
+ additionalProperties: false,
124
+ },
125
+ },
126
+ {
127
+ name: "agentflow_get_workspace_graph",
128
+ description: "Read a flow as a workspace graph before running or inspecting display nodes.",
129
+ inputSchema: {
130
+ type: "object",
131
+ required: ["flowId"],
132
+ properties: {
133
+ flowId: { type: "string" },
134
+ flowSource: { type: "string", default: "user" },
135
+ },
136
+ additionalProperties: false,
137
+ },
138
+ },
139
+ {
140
+ name: "agentflow_run_flow",
141
+ description: "Run an AgentFlow workspace flow and return the merged graph plus display outputs.",
142
+ inputSchema: {
143
+ type: "object",
144
+ required: ["flowId"],
145
+ properties: {
146
+ flowId: { type: "string" },
147
+ flowSource: { type: "string", default: "user" },
148
+ runNodeId: { type: "string", description: "Optional run/schedule node id. Empty runs from the graph start." },
149
+ inputs: { type: "object", description: "Optional flow input values." },
150
+ },
151
+ additionalProperties: false,
152
+ },
153
+ },
154
+ {
155
+ name: "agentflow_get_run_status",
156
+ description: "Get active workspace run status for a flow.",
157
+ inputSchema: {
158
+ type: "object",
159
+ required: ["flowId"],
160
+ properties: {
161
+ flowId: { type: "string" },
162
+ flowSource: { type: "string", default: "user" },
163
+ },
164
+ additionalProperties: false,
165
+ },
166
+ },
167
+ {
168
+ name: "agentflow_get_run_logs",
169
+ description: "Read workspace run logs. Provide runId for a specific run, or flowId to list recent runs.",
170
+ inputSchema: {
171
+ type: "object",
172
+ properties: {
173
+ flowId: { type: "string" },
174
+ flowSource: { type: "string" },
175
+ runId: { type: "string" },
176
+ limit: { type: "number", default: 20 },
177
+ },
178
+ additionalProperties: false,
179
+ },
180
+ },
181
+ {
182
+ name: "agentflow_get_display_outputs",
183
+ description: "Extract display node outputs from a workspace graph.",
184
+ inputSchema: {
185
+ type: "object",
186
+ required: ["flowId"],
187
+ properties: {
188
+ flowId: { type: "string" },
189
+ flowSource: { type: "string", default: "user" },
190
+ },
191
+ additionalProperties: false,
192
+ },
193
+ },
194
+ ];
195
+
196
+ async function callTool(name, args = {}) {
197
+ if (name === "agentflow_list_flows") {
198
+ return toolText(await httpJson("/api/flows"));
199
+ }
200
+ if (name === "agentflow_get_workspace_graph") {
201
+ const flowSource = args.flowSource || "user";
202
+ return toolText(await httpJson(`/api/workspace/graph${query({ flowId: args.flowId, flowSource })}`));
203
+ }
204
+ if (name === "agentflow_run_flow") {
205
+ const flowSource = args.flowSource || "user";
206
+ const graphPayload = await httpJson(`/api/workspace/graph${query({ flowId: args.flowId, flowSource })}`);
207
+ const runPayload = {
208
+ flowId: args.flowId,
209
+ flowSource,
210
+ runNodeId: args.runNodeId || "",
211
+ graph: graphPayload.graph,
212
+ inputs: args.inputs && typeof args.inputs === "object" ? args.inputs : {},
213
+ };
214
+ const result = await httpJson("/api/workspace/run", { method: "POST", body: runPayload });
215
+ return toolText({
216
+ ok: result?.ok === true,
217
+ flowId: args.flowId,
218
+ flowSource,
219
+ runNodeId: args.runNodeId || "",
220
+ order: result?.order || [],
221
+ pauseNodeIds: result?.pauseNodeIds || [],
222
+ touchedNodeIds: result?.touchedNodeIds || [],
223
+ displayOutputs: extractDisplayOutputs(result?.graph),
224
+ graph: result?.graph || null,
225
+ });
226
+ }
227
+ if (name === "agentflow_get_run_status") {
228
+ return toolText(await httpJson(`/api/workspace/run/status${query({ flowId: args.flowId, flowSource: args.flowSource || "user" })}`));
229
+ }
230
+ if (name === "agentflow_get_run_logs") {
231
+ if (args.runId) {
232
+ return toolText(await httpJson(`/api/workspace/run-logs/${encodeURIComponent(String(args.runId))}`));
233
+ }
234
+ return toolText(await httpJson(`/api/workspace/run-logs${query({
235
+ flowId: args.flowId || "",
236
+ flowSource: args.flowSource || "",
237
+ limit: args.limit || 20,
238
+ })}`));
239
+ }
240
+ if (name === "agentflow_get_display_outputs") {
241
+ const flowSource = args.flowSource || "user";
242
+ const graphPayload = await httpJson(`/api/workspace/graph${query({ flowId: args.flowId, flowSource })}`);
243
+ return toolText({
244
+ flowId: args.flowId,
245
+ flowSource,
246
+ displayOutputs: extractDisplayOutputs(graphPayload.graph),
247
+ });
248
+ }
249
+ return toolError(`Unknown tool: ${name}`);
250
+ }
251
+
252
+ function response(id, result) {
253
+ return { jsonrpc: "2.0", id, result };
254
+ }
255
+
256
+ function errorResponse(id, error) {
257
+ return {
258
+ jsonrpc: "2.0",
259
+ id,
260
+ error: {
261
+ code: -32000,
262
+ message: String(error?.message || error || "Unknown error"),
263
+ },
264
+ };
265
+ }
266
+
267
+ function writeMessage(message) {
268
+ process.stdout.write(JSON.stringify(message) + "\n");
269
+ }
270
+
271
+ async function handleMessage(message) {
272
+ if (!message || typeof message !== "object") return;
273
+ const id = message.id;
274
+ const method = String(message.method || "");
275
+ try {
276
+ if (method === "initialize") {
277
+ writeMessage(response(id, {
278
+ protocolVersion: "2024-11-05",
279
+ capabilities: { tools: {} },
280
+ serverInfo: { name: "agentflow", version: "0.1.0" },
281
+ }));
282
+ return;
283
+ }
284
+ if (method === "notifications/initialized") return;
285
+ if (method === "tools/list") {
286
+ writeMessage(response(id, { tools }));
287
+ return;
288
+ }
289
+ if (method === "tools/call") {
290
+ const params = message.params || {};
291
+ const result = await callTool(String(params.name || ""), params.arguments || {});
292
+ writeMessage(response(id, result));
293
+ return;
294
+ }
295
+ if (id !== undefined) writeMessage(errorResponse(id, `Unknown method: ${method}`));
296
+ } catch (e) {
297
+ if (id !== undefined) writeMessage(errorResponse(id, e));
298
+ }
299
+ }
300
+
301
+ function feedLine(line) {
302
+ const text = String(line || "").trim();
303
+ if (!text) return;
304
+ try {
305
+ void handleMessage(JSON.parse(text));
306
+ } catch (e) {
307
+ writeMessage(errorResponse(null, e));
308
+ }
309
+ }
310
+
311
+ export async function startMcpServer() {
312
+ process.stdin.setEncoding("utf8");
313
+ let buffer = "";
314
+ process.stdin.on("data", (chunk) => {
315
+ buffer += chunk;
316
+ let idx = buffer.indexOf("\n");
317
+ while (idx >= 0) {
318
+ const line = buffer.slice(0, idx);
319
+ buffer = buffer.slice(idx + 1);
320
+ feedLine(line);
321
+ idx = buffer.indexOf("\n");
322
+ }
323
+ });
324
+ process.stdin.on("end", () => {
325
+ if (buffer.trim()) feedLine(buffer);
326
+ });
327
+ await new Promise(() => {});
328
+ }
@@ -45,6 +45,14 @@ export function resolveCliAndModel(workspaceRoot, nodeModel, agentModelOverride)
45
45
  if (raw.startsWith("api:")) {
46
46
  return { cli: "api", model: raw, label: raw };
47
47
  }
48
+ if (raw.startsWith("codex:")) {
49
+ const m = raw.slice("codex:".length).trim();
50
+ return {
51
+ cli: "codex",
52
+ model: m || null,
53
+ label: m ? `codex: ${m}` : "codex (default)",
54
+ };
55
+ }
48
56
  if (raw.startsWith("claude-code:")) {
49
57
  const m = raw.slice("claude-code:".length).trim();
50
58
  return {
@@ -75,9 +83,11 @@ export function resolveCliAndModel(workspaceRoot, nodeModel, agentModelOverride)
75
83
  ? "opencode"
76
84
  : cfg.cli === "api"
77
85
  ? "api"
78
- : cfg.cli === "claude-code"
79
- ? "claude-code"
80
- : "cursor";
86
+ : cfg.cli === "codex"
87
+ ? "codex"
88
+ : cfg.cli === "claude-code"
89
+ ? "claude-code"
90
+ : "cursor";
81
91
  const model = String(cfg.model).trim();
82
92
  return { cli, model, label: `${cli}: ${model}` };
83
93
  }
@@ -87,6 +97,15 @@ export function resolveCliAndModel(workspaceRoot, nodeModel, agentModelOverride)
87
97
  return { cli: "api", model: key, label: key };
88
98
  }
89
99
 
100
+ if (key && key.startsWith("codex:")) {
101
+ const model = key.slice("codex:".length) || "";
102
+ return {
103
+ cli: "codex",
104
+ model: model || null,
105
+ label: model ? `codex: ${model}` : "codex (default)",
106
+ };
107
+ }
108
+
90
109
  if (key && key.startsWith("claude-code:")) {
91
110
  const model = key.slice("claude-code:".length) || "";
92
111
  return {
@@ -109,6 +128,16 @@ export function resolveCliAndModel(workspaceRoot, nodeModel, agentModelOverride)
109
128
  if (envModel && envModel.startsWith("api:")) {
110
129
  return { cli: "api", model: envModel, label: envModel };
111
130
  }
131
+ if (!key) {
132
+ const codexModel = process.env.CODEX_MODEL && String(process.env.CODEX_MODEL).trim();
133
+ if (codexModel) {
134
+ return {
135
+ cli: "codex",
136
+ model: codexModel,
137
+ label: `codex: ${codexModel}`,
138
+ };
139
+ }
140
+ }
112
141
  const model = normalizeCursorModelForCli(key || envModel || "Auto");
113
142
  return {
114
143
  cli: "cursor",
@@ -125,8 +125,68 @@ export function runOpencodeModels(workspaceRoot, provider) {
125
125
  });
126
126
  }
127
127
 
128
+ function parseCodexModelList(stdout) {
129
+ const cleaned = stripAnsiModelList(stdout);
130
+ const start = cleaned.indexOf("{");
131
+ const end = cleaned.lastIndexOf("}");
132
+ if (start < 0 || end <= start) return [];
133
+ try {
134
+ const parsed = JSON.parse(cleaned.slice(start, end + 1));
135
+ const rows = Array.isArray(parsed?.models) ? parsed.models : [];
136
+ const seen = new Set();
137
+ const models = [];
138
+ for (const row of rows) {
139
+ if (!row || typeof row !== "object") continue;
140
+ const slug = typeof row.slug === "string" ? row.slug.trim() : "";
141
+ if (!slug || seen.has(slug)) continue;
142
+ seen.add(slug);
143
+ const display = typeof row.display_name === "string" ? row.display_name.trim() : "";
144
+ models.push(display && display !== slug ? `${slug} - ${display}` : slug);
145
+ }
146
+ return models;
147
+ } catch (_) {
148
+ return [];
149
+ }
150
+ }
151
+
152
+ export function runCodexModels(workspaceRoot) {
153
+ return new Promise((resolve) => {
154
+ const codexCmd = process.env.CODEX_CMD || "codex";
155
+ const child = spawn(codexCmd, ["debug", "models", "--bundled"], {
156
+ cwd: workspaceRoot,
157
+ shell: false,
158
+ env: envWithCommonBinPaths(),
159
+ });
160
+ let stdout = "";
161
+ let stderr = "";
162
+ let settled = false;
163
+ const finish = (models) => {
164
+ if (settled) return;
165
+ settled = true;
166
+ resolve(models);
167
+ };
168
+ child.stdout?.on("data", (chunk) => {
169
+ stdout += chunk.toString("utf-8");
170
+ });
171
+ child.stderr?.on("data", (chunk) => {
172
+ stderr += chunk.toString("utf-8");
173
+ });
174
+ child.on("close", (code) => {
175
+ if (code === 0) finish(parseCodexModelList(stdout || stderr));
176
+ else finish([]);
177
+ });
178
+ child.on("error", () => finish([]));
179
+ setTimeout(() => {
180
+ try {
181
+ child.kill("SIGTERM");
182
+ } catch {}
183
+ finish([]);
184
+ }, 10_000);
185
+ });
186
+ }
187
+
128
188
  /**
129
- * 拉取 Cursor / OpenCode 模型列表并写入 ~/agentflow/model-lists.json
189
+ * 拉取 Cursor / OpenCode / Claude Code / Codex 模型列表并写入 ~/agentflow/model-lists.json
130
190
  * @param {string} workspaceRoot
131
191
  * @param {{ opencodeProviderOverride?: string }} [opts]
132
192
  */
@@ -150,9 +210,11 @@ export async function updateModelLists(workspaceRoot, opts = {}) {
150
210
  cursor: [],
151
211
  opencode: [],
152
212
  claudeCode: [],
213
+ codex: [],
153
214
  cursorFetchedAt: null,
154
215
  opencodeFetchedAt: null,
155
216
  claudeCodeFetchedAt: null,
217
+ codexFetchedAt: null,
156
218
  };
157
219
  try {
158
220
  if (fs.existsSync(cachePath)) {
@@ -161,10 +223,11 @@ export async function updateModelLists(workspaceRoot, opts = {}) {
161
223
  }
162
224
  } catch (_) {}
163
225
 
164
- const [cursorRaw, opencode, claudeCodeAvailable] = await Promise.all([
226
+ const [cursorRaw, opencode, claudeCodeAvailable, codex] = await Promise.all([
165
227
  runCursorModels(root),
166
228
  runOpencodeModels(root, opencodeProvider),
167
229
  probeClaudeCodeAvailable(),
230
+ runCodexModels(root),
168
231
  ]);
169
232
  const cursor = cursorRaw.filter(isCursorModelLine);
170
233
  const claudeCode = claudeCodeAvailable ? getBuiltinClaudeCodeModels() : [];
@@ -174,9 +237,11 @@ export async function updateModelLists(workspaceRoot, opts = {}) {
174
237
  cursor: cursor.length > 0 ? cursor : prev.cursor ?? [],
175
238
  opencode: opencode.length > 0 ? opencode : prev.opencode ?? [],
176
239
  claudeCode: claudeCode.length > 0 ? claudeCode : prev.claudeCode ?? [],
240
+ codex: codex.length > 0 ? codex : prev.codex ?? [],
177
241
  cursorFetchedAt: cursor.length > 0 ? now : prev.cursorFetchedAt ?? null,
178
242
  opencodeFetchedAt: opencode.length > 0 ? now : prev.opencodeFetchedAt ?? null,
179
243
  claudeCodeFetchedAt: claudeCode.length > 0 ? now : prev.claudeCodeFetchedAt ?? null,
244
+ codexFetchedAt: codex.length > 0 ? now : prev.codexFetchedAt ?? null,
180
245
  };
181
246
 
182
247
  try {
@@ -184,5 +249,5 @@ export async function updateModelLists(workspaceRoot, opts = {}) {
184
249
  fs.writeFileSync(cachePath, JSON.stringify(data, null, 2), "utf-8");
185
250
  } catch (_) {}
186
251
 
187
- return { cursor: data.cursor, opencode: data.opencode, claudeCode: data.claudeCode };
252
+ return { cursor: data.cursor, opencode: data.opencode, claudeCode: data.claudeCode, codex: data.codex };
188
253
  }
@@ -4,6 +4,8 @@ import path from "path";
4
4
  import { outputNodeBasename, outputDirForNode } from "../pipeline/get-exec-id.mjs";
5
5
  import { writeResult } from "../pipeline/write-result.mjs";
6
6
  import {
7
+ runCodexAgentForNode,
8
+ runCodexAgentWithPrompt,
7
9
  runClaudeCodeAgentForNode,
8
10
  runClaudeCodeAgentWithPrompt,
9
11
  runCursorAgentForNode,
@@ -100,7 +102,7 @@ async function healToolNodejsWithAI(workspaceRoot, flowName, uuid, instanceId, r
100
102
 
101
103
  const prompt = buildHealPrompt(scriptPath, resolvedScript, errorInfo, scriptContent);
102
104
  const healCli =
103
- cli === "opencode" ? "opencode" : cli === "claude-code" ? "claude-code" : "cursor";
105
+ cli === "opencode" ? "opencode" : cli === "codex" ? "codex" : cli === "claude-code" ? "claude-code" : "cursor";
104
106
 
105
107
  log.info(`[tool_nodejs AI 自愈] ${instanceId} 调用 ${healCli}${model ? ` (${model})` : ""} 修复 ${path.basename(scriptPath)}`);
106
108
  emitEvent(workspaceRoot, flowName, uuid, {
@@ -115,6 +117,9 @@ async function healToolNodejsWithAI(workspaceRoot, flowName, uuid, instanceId, r
115
117
  if (healCli === "opencode") {
116
118
  const { finished } = runOpenCodeAgentWithPrompt(workspaceRoot, prompt, { model: model || undefined, force: true });
117
119
  await finished;
120
+ } else if (healCli === "codex") {
121
+ const { finished } = runCodexAgentWithPrompt(workspaceRoot, prompt, { model: model || undefined, force: true });
122
+ await finished;
118
123
  } else if (healCli === "claude-code") {
119
124
  const { finished } = runClaudeCodeAgentWithPrompt(workspaceRoot, prompt, { model: model || undefined });
120
125
  await finished;
@@ -429,6 +434,22 @@ export async function executeNode(workspaceRoot, flowName, uuid, instanceId, pre
429
434
  execWorkspaceRoot,
430
435
  },
431
436
  );
437
+ } else if (cli === "codex") {
438
+ await runCodexAgentForNode(
439
+ workspaceRoot,
440
+ { promptPath, nodeContext: nodeContext ?? "", taskBody: taskBody ?? "", intermediatePath, resultPathRel: resultPath, subagent, instanceId },
441
+ {
442
+ model,
443
+ stderrBuffer: options.stderrBuffer,
444
+ force: options.force,
445
+ outputPrefix: options.outputPrefix,
446
+ prefixColor: options.prefixColor,
447
+ onToolCall: options.onToolCall,
448
+ flowName,
449
+ uuid,
450
+ execWorkspaceRoot,
451
+ },
452
+ );
432
453
  } else if (cli === "claude-code") {
433
454
  await runClaudeCodeAgentForNode(
434
455
  workspaceRoot,