@fieldwangai/agentflow 0.1.84 → 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.
- package/README.md +21 -5
- package/README.zh-CN.md +21 -5
- package/bin/agentflow.mjs +1 -1
- package/bin/lib/agent-runners.mjs +589 -0
- package/bin/lib/auth.mjs +11 -1
- package/bin/lib/catalog-flows.mjs +2 -0
- package/bin/lib/composer-agent.mjs +163 -1
- package/bin/lib/composer-model-router.mjs +28 -3
- package/bin/lib/composer-planner.mjs +3 -1
- package/bin/lib/help.mjs +16 -14
- package/bin/lib/locales/en.json +5 -1
- package/bin/lib/locales/zh.json +5 -1
- package/bin/lib/main.mjs +5 -0
- package/bin/lib/mcp-server.mjs +328 -0
- package/bin/lib/model-config.mjs +32 -3
- package/bin/lib/model-lists.mjs +68 -3
- package/bin/lib/node-execute.mjs +22 -1
- package/bin/lib/ui-server.mjs +1420 -73
- package/bin/lib/workspace-run-logs.mjs +181 -0
- package/bin/pipeline/validate-flow.mjs +19 -11
- package/bin/pipeline/validate-for-ui.mjs +19 -11
- package/builtin/nodes/display_react_app.md +52 -0
- package/builtin/web-ui/dist/assets/index-CSr7NGO4.js +322 -0
- package/builtin/web-ui/dist/assets/index-DIO0cfyb.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +3 -2
- package/builtin/web-ui/dist/assets/index-CVdxKCZm.css +0 -1
- package/builtin/web-ui/dist/assets/index-w1bX4CFG.js +0 -297
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
import { getAgentflowDataRoot } from "./paths.mjs";
|
|
5
|
+
import { runLedgerId } from "./run-ledger.mjs";
|
|
6
|
+
|
|
7
|
+
const MAX_EVENT_TEXT_CHARS = 20_000;
|
|
8
|
+
const MAX_INDEX_RECORDS = 10_000;
|
|
9
|
+
const SECRET_KEY_RE = /(token|password|passwd|secret|webhook|authorization|api[_-]?key|access[_-]?key)/i;
|
|
10
|
+
|
|
11
|
+
function safeSegment(value, fallback = "run") {
|
|
12
|
+
return String(value || fallback)
|
|
13
|
+
.trim()
|
|
14
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "_")
|
|
15
|
+
.replace(/^_+|_+$/g, "")
|
|
16
|
+
.slice(0, 160) || fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function logRoot() {
|
|
20
|
+
return path.join(getAgentflowDataRoot(), "workspace-run-logs");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function indexPath() {
|
|
24
|
+
return path.join(logRoot(), "index.jsonl");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function eventPath(runId) {
|
|
28
|
+
return path.join(logRoot(), "events", `${safeSegment(runId)}.jsonl`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function truncateText(value) {
|
|
32
|
+
const text = String(value ?? "");
|
|
33
|
+
if (text.length <= MAX_EVENT_TEXT_CHARS) return text;
|
|
34
|
+
return `${text.slice(0, MAX_EVENT_TEXT_CHARS)}\n... [truncated ${text.length - MAX_EVENT_TEXT_CHARS} chars]`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function redact(value, depth = 0) {
|
|
38
|
+
if (depth > 8) return "[MaxDepth]";
|
|
39
|
+
if (typeof value === "string") return truncateText(value);
|
|
40
|
+
if (value == null || typeof value === "number" || typeof value === "boolean") return value;
|
|
41
|
+
if (Array.isArray(value)) return value.slice(0, 200).map((item) => redact(item, depth + 1));
|
|
42
|
+
if (typeof value === "object") {
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const [key, raw] of Object.entries(value).slice(0, 200)) {
|
|
45
|
+
if (key === "graph") {
|
|
46
|
+
out[key] = "[omitted]";
|
|
47
|
+
} else {
|
|
48
|
+
out[key] = SECRET_KEY_RE.test(key) ? "***" : redact(raw, depth + 1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function appendJsonl(filePath, item) {
|
|
57
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
58
|
+
fs.appendFileSync(filePath, JSON.stringify(item) + "\n", "utf-8");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readJsonl(filePath) {
|
|
62
|
+
if (!fs.existsSync(filePath)) return [];
|
|
63
|
+
try {
|
|
64
|
+
return fs.readFileSync(filePath, "utf-8")
|
|
65
|
+
.split(/\r?\n/)
|
|
66
|
+
.filter((line) => line.trim())
|
|
67
|
+
.map((line) => {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(line);
|
|
70
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.filter(Boolean);
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function indexRecord(meta = {}, patch = {}) {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
return {
|
|
84
|
+
version: 1,
|
|
85
|
+
recordType: "summary",
|
|
86
|
+
runId: String(meta.runId || patch.runId || runLedgerId("workspace")),
|
|
87
|
+
userId: String(meta.userId || ""),
|
|
88
|
+
username: String(meta.username || meta.userId || ""),
|
|
89
|
+
flowId: String(meta.flowId || ""),
|
|
90
|
+
flowSource: String(meta.flowSource || "user"),
|
|
91
|
+
scheduleNodeId: String(meta.scheduleNodeId || ""),
|
|
92
|
+
runNodeId: String(meta.runNodeId || ""),
|
|
93
|
+
scheduled: meta.scheduled === true,
|
|
94
|
+
trigger: String(meta.trigger || (meta.scheduled === true ? "scheduled" : "manual")),
|
|
95
|
+
label: String(meta.label || ""),
|
|
96
|
+
startedAt: Number(meta.startedAt || now),
|
|
97
|
+
endedAt: meta.endedAt == null ? null : Number(meta.endedAt),
|
|
98
|
+
durationMs: Math.max(0, Number(meta.durationMs || 0)),
|
|
99
|
+
status: String(meta.status || "running"),
|
|
100
|
+
error: String(meta.error || ""),
|
|
101
|
+
updatedAt: Number(meta.updatedAt || now),
|
|
102
|
+
...patch,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createWorkspaceRunLogSession(meta = {}) {
|
|
107
|
+
const startedAt = Number(meta.startedAt || Date.now());
|
|
108
|
+
const runId = String(meta.runId || runLedgerId("workspace"));
|
|
109
|
+
const item = indexRecord({ ...meta, runId, startedAt, status: "running", updatedAt: startedAt });
|
|
110
|
+
appendJsonl(indexPath(), item);
|
|
111
|
+
appendWorkspaceRunLogEvent(runId, { type: "run-start", ...item, ts: startedAt });
|
|
112
|
+
return { ...item, eventPath: eventPath(runId) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function appendWorkspaceRunLogEvent(runId, event = {}) {
|
|
116
|
+
const id = String(runId || "");
|
|
117
|
+
if (!id) return;
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
const item = {
|
|
120
|
+
version: 1,
|
|
121
|
+
runId: id,
|
|
122
|
+
ts: Number(event.ts || event.at || now),
|
|
123
|
+
type: String(event.type || "event"),
|
|
124
|
+
...redact(event),
|
|
125
|
+
};
|
|
126
|
+
appendJsonl(eventPath(id), item);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function finishWorkspaceRunLogSession(runId, status, patch = {}) {
|
|
130
|
+
const id = String(runId || "");
|
|
131
|
+
if (!id) return null;
|
|
132
|
+
const endedAt = Number(patch.endedAt || Date.now());
|
|
133
|
+
const summaries = readWorkspaceRunLogIndex().filter((item) => item.runId === id);
|
|
134
|
+
const started = summaries[0] || {};
|
|
135
|
+
const item = indexRecord(started, {
|
|
136
|
+
...patch,
|
|
137
|
+
runId: id,
|
|
138
|
+
endedAt,
|
|
139
|
+
durationMs: Math.max(0, Number(patch.durationMs || (endedAt - Number(started.startedAt || endedAt)))),
|
|
140
|
+
status: String(status || patch.status || "finished"),
|
|
141
|
+
error: String(patch.error || ""),
|
|
142
|
+
updatedAt: endedAt,
|
|
143
|
+
});
|
|
144
|
+
appendJsonl(indexPath(), item);
|
|
145
|
+
appendWorkspaceRunLogEvent(id, { type: "run-finish", status: item.status, error: item.error, ts: endedAt, durationMs: item.durationMs });
|
|
146
|
+
return item;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function readWorkspaceRunLogIndex() {
|
|
150
|
+
const rows = readJsonl(indexPath());
|
|
151
|
+
return rows.slice(-MAX_INDEX_RECORDS);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function listWorkspaceRunLogs(filter = {}) {
|
|
155
|
+
const byRun = new Map();
|
|
156
|
+
for (const row of readWorkspaceRunLogIndex()) {
|
|
157
|
+
if (!row || !row.runId) continue;
|
|
158
|
+
const prev = byRun.get(row.runId) || {};
|
|
159
|
+
byRun.set(row.runId, { ...prev, ...row });
|
|
160
|
+
}
|
|
161
|
+
let rows = Array.from(byRun.values());
|
|
162
|
+
const userId = String(filter.userId || "");
|
|
163
|
+
const flowId = String(filter.flowId || "");
|
|
164
|
+
const flowSource = String(filter.flowSource || "");
|
|
165
|
+
const scheduleNodeId = String(filter.scheduleNodeId || "");
|
|
166
|
+
const runNodeId = String(filter.runNodeId || "");
|
|
167
|
+
const scheduled = filter.scheduled;
|
|
168
|
+
if (userId) rows = rows.filter((row) => String(row.userId || "") === userId);
|
|
169
|
+
if (flowId) rows = rows.filter((row) => String(row.flowId || "") === flowId);
|
|
170
|
+
if (flowSource) rows = rows.filter((row) => String(row.flowSource || "user") === flowSource);
|
|
171
|
+
if (scheduleNodeId) rows = rows.filter((row) => String(row.scheduleNodeId || "") === scheduleNodeId);
|
|
172
|
+
if (runNodeId) rows = rows.filter((row) => String(row.runNodeId || "") === runNodeId);
|
|
173
|
+
if (scheduled === true || scheduled === false) rows = rows.filter((row) => row.scheduled === scheduled);
|
|
174
|
+
rows.sort((a, b) => Number(b.startedAt || b.updatedAt || 0) - Number(a.startedAt || a.updatedAt || 0));
|
|
175
|
+
const limit = Math.max(1, Math.min(200, Number(filter.limit || 50)));
|
|
176
|
+
return rows.slice(0, limit);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function readWorkspaceRunLogEvents(runId) {
|
|
180
|
+
return readJsonl(eventPath(runId));
|
|
181
|
+
}
|
|
@@ -113,18 +113,26 @@ function loadFlowYaml(flowDir) {
|
|
|
113
113
|
|
|
114
114
|
function loadModelLists(_workspaceRoot) {
|
|
115
115
|
const p = getModelListsAbs();
|
|
116
|
-
if (!fs.existsSync(p)) return { cursor: [], opencode: [] };
|
|
116
|
+
if (!fs.existsSync(p)) return { cursor: [], opencode: [], claudeCode: [], codex: [] };
|
|
117
117
|
try {
|
|
118
118
|
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
119
119
|
return {
|
|
120
120
|
cursor: Array.isArray(data.cursor) ? data.cursor : [],
|
|
121
121
|
opencode: Array.isArray(data.opencode) ? data.opencode : [],
|
|
122
|
+
claudeCode: Array.isArray(data.claudeCode) ? data.claudeCode : [],
|
|
123
|
+
codex: Array.isArray(data.codex) ? data.codex : [],
|
|
122
124
|
};
|
|
123
125
|
} catch {
|
|
124
|
-
return { cursor: [], opencode: [] };
|
|
126
|
+
return { cursor: [], opencode: [], claudeCode: [], codex: [] };
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
function modelEntryId(entry) {
|
|
131
|
+
const t = String(entry || "").trim();
|
|
132
|
+
const first = t.split(/\s+-/)[0].trim();
|
|
133
|
+
return first || t;
|
|
134
|
+
}
|
|
135
|
+
|
|
128
136
|
function loadCustomRoleIds(_workspaceRoot) {
|
|
129
137
|
const agentsPath = getUserAgentsJsonAbs();
|
|
130
138
|
if (!fs.existsSync(agentsPath)) return new Set();
|
|
@@ -683,21 +691,21 @@ function computeValidation(loaded, workspaceRoot) {
|
|
|
683
691
|
}
|
|
684
692
|
|
|
685
693
|
const root = workspaceRoot ? path.resolve(workspaceRoot) : path.dirname(loaded._flowDir || ".");
|
|
686
|
-
const { cursor: cursorList, opencode: opencodeList } = loadModelLists(root);
|
|
687
|
-
const opencodeSet = new Set((opencodeList || []).map(
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const first = t.split(/\s+-/)[0].trim();
|
|
692
|
-
return first || t;
|
|
693
|
-
})
|
|
694
|
-
);
|
|
694
|
+
const { cursor: cursorList, opencode: opencodeList, claudeCode: claudeCodeList, codex: codexList } = loadModelLists(root);
|
|
695
|
+
const opencodeSet = new Set((opencodeList || []).map(modelEntryId));
|
|
696
|
+
const claudeCodeSet = new Set((claudeCodeList || []).map(modelEntryId));
|
|
697
|
+
const codexSet = new Set((codexList || []).map(modelEntryId));
|
|
698
|
+
const cursorSet = new Set((cursorList || []).map(modelEntryId));
|
|
695
699
|
for (const n of nodes) {
|
|
696
700
|
const model = (instances[n.id] && instances[n.id].model != null) ? String(instances[n.id].model).trim() : "";
|
|
697
701
|
if (!model) continue;
|
|
698
702
|
let valid = false;
|
|
699
703
|
if (model.startsWith("opencode:")) {
|
|
700
704
|
valid = opencodeSet.has(model.slice(9).trim());
|
|
705
|
+
} else if (model.startsWith("claude-code:")) {
|
|
706
|
+
valid = claudeCodeSet.size === 0 || claudeCodeSet.has(model.slice(12).trim());
|
|
707
|
+
} else if (model.startsWith("codex:")) {
|
|
708
|
+
valid = codexSet.size === 0 || codexSet.has(model.slice(6).trim());
|
|
701
709
|
} else {
|
|
702
710
|
const cursorId = model.startsWith("cursor:") ? model.slice(7).trim() : model;
|
|
703
711
|
valid = cursorSet.has(cursorId) || (cursorList || []).some((c) => String(c).trim() === model || String(c).trim().startsWith(cursorId + " "));
|
|
@@ -91,18 +91,26 @@ function loadFlowYaml(flowDir) {
|
|
|
91
91
|
|
|
92
92
|
function loadModelLists(_workspaceRoot) {
|
|
93
93
|
const p = getModelListsAbs();
|
|
94
|
-
if (!fs.existsSync(p)) return { cursor: [], opencode: [] };
|
|
94
|
+
if (!fs.existsSync(p)) return { cursor: [], opencode: [], claudeCode: [], codex: [] };
|
|
95
95
|
try {
|
|
96
96
|
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
97
97
|
return {
|
|
98
98
|
cursor: Array.isArray(data.cursor) ? data.cursor : [],
|
|
99
99
|
opencode: Array.isArray(data.opencode) ? data.opencode : [],
|
|
100
|
+
claudeCode: Array.isArray(data.claudeCode) ? data.claudeCode : [],
|
|
101
|
+
codex: Array.isArray(data.codex) ? data.codex : [],
|
|
100
102
|
};
|
|
101
103
|
} catch {
|
|
102
|
-
return { cursor: [], opencode: [] };
|
|
104
|
+
return { cursor: [], opencode: [], claudeCode: [], codex: [] };
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
function modelEntryId(entry) {
|
|
109
|
+
const t = String(entry || "").trim();
|
|
110
|
+
const first = t.split(/\s+-/)[0].trim();
|
|
111
|
+
return first || t;
|
|
112
|
+
}
|
|
113
|
+
|
|
106
114
|
/** ~/agentflow/agents.json 仅存用户角色,无 source 字段,取所有条目的 id */
|
|
107
115
|
function loadCustomRoleIds(_workspaceRoot) {
|
|
108
116
|
const agentsPath = getUserAgentsJsonAbs();
|
|
@@ -163,21 +171,21 @@ function computeValidation(flowDir, workspaceRoot) {
|
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
const root = workspaceRoot ? path.resolve(workspaceRoot) : flowDir;
|
|
166
|
-
const { cursor: cursorList, opencode: opencodeList } = loadModelLists(root);
|
|
167
|
-
const opencodeSet = new Set((opencodeList || []).map(
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const first = t.split(/\s+-/)[0].trim();
|
|
172
|
-
return first || t;
|
|
173
|
-
})
|
|
174
|
-
);
|
|
174
|
+
const { cursor: cursorList, opencode: opencodeList, claudeCode: claudeCodeList, codex: codexList } = loadModelLists(root);
|
|
175
|
+
const opencodeSet = new Set((opencodeList || []).map(modelEntryId));
|
|
176
|
+
const claudeCodeSet = new Set((claudeCodeList || []).map(modelEntryId));
|
|
177
|
+
const codexSet = new Set((codexList || []).map(modelEntryId));
|
|
178
|
+
const cursorSet = new Set((cursorList || []).map(modelEntryId));
|
|
175
179
|
for (const n of nodes) {
|
|
176
180
|
const model = (instances[n.id] && instances[n.id].model != null) ? String(instances[n.id].model).trim() : "";
|
|
177
181
|
if (!model) continue;
|
|
178
182
|
let valid = false;
|
|
179
183
|
if (model.startsWith("opencode:")) {
|
|
180
184
|
valid = opencodeSet.has(model.slice(9).trim());
|
|
185
|
+
} else if (model.startsWith("claude-code:")) {
|
|
186
|
+
valid = claudeCodeSet.size === 0 || claudeCodeSet.has(model.slice(12).trim());
|
|
187
|
+
} else if (model.startsWith("codex:")) {
|
|
188
|
+
valid = codexSet.size === 0 || codexSet.has(model.slice(6).trim());
|
|
181
189
|
} else {
|
|
182
190
|
const cursorId = model.startsWith("cursor:") ? model.slice(7).trim() : model;
|
|
183
191
|
valid = cursorSet.has(cursorId) || (cursorList || []).some((c) => String(c).trim() === model || String(c).trim().startsWith(cursorId + " "));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Built-in node: React App Display
|
|
3
|
+
description: Display a small React project in a sandboxed workspace iframe and pass the project JSON downstream
|
|
4
|
+
displayName: React App
|
|
5
|
+
input:
|
|
6
|
+
- type: node
|
|
7
|
+
name: prev
|
|
8
|
+
default: ""
|
|
9
|
+
- type: text
|
|
10
|
+
name: content
|
|
11
|
+
default: ""
|
|
12
|
+
required: true
|
|
13
|
+
showOnNode: true
|
|
14
|
+
- type: file
|
|
15
|
+
name: filePath
|
|
16
|
+
default: ""
|
|
17
|
+
showOnNode: false
|
|
18
|
+
- type: text
|
|
19
|
+
name: workspaceContext
|
|
20
|
+
default: ""
|
|
21
|
+
showOnNode: false
|
|
22
|
+
output:
|
|
23
|
+
- type: text
|
|
24
|
+
name: content
|
|
25
|
+
default: ""
|
|
26
|
+
showOnNode: true
|
|
27
|
+
- type: node
|
|
28
|
+
name: next
|
|
29
|
+
default: ""
|
|
30
|
+
---
|
|
31
|
+
{
|
|
32
|
+
"title": "React App",
|
|
33
|
+
"entry": "src/App.jsx",
|
|
34
|
+
"packageJson": {
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "vite --host 0.0.0.0",
|
|
37
|
+
"build": "vite build"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@vitejs/plugin-react": "latest",
|
|
41
|
+
"vite": "latest",
|
|
42
|
+
"react": "latest",
|
|
43
|
+
"react-dom": "latest"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {}
|
|
46
|
+
},
|
|
47
|
+
"files": {
|
|
48
|
+
"src/App.jsx": "export default function App() {\\n return (\\n <main>\\n <h1>React App</h1>\\n <p>Ask an Agent to replace this with a React project.</p>\\n </main>\\n );\\n}\\n",
|
|
49
|
+
"src/styles.css": "body { margin: 0; background: #111112; color: #f4f0ff; font-family: Inter, system-ui, sans-serif; }\\nmain { min-height: 100vh; padding: 24px; }\\nh1 { margin: 0 0 8px; font-size: 28px; }\\np { margin: 0; color: rgba(244, 240, 255, 0.68); }\\n"
|
|
50
|
+
},
|
|
51
|
+
"inputs": {}
|
|
52
|
+
}
|