@fieldwangai/agentflow 0.1.83 → 0.1.85
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/bin/lib/ui-server.mjs +857 -32
- package/bin/lib/workspace-run-logs.mjs +181 -0
- package/builtin/nodes/tool_git_worktree_load.md +2 -1
- package/builtin/web-ui/dist/assets/index-Lb9rIMqs.js +250 -0
- package/builtin/web-ui/dist/assets/index-aqphFZlG.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-CVdxKCZm.css +0 -1
- package/builtin/web-ui/dist/assets/index-DkW7IrCF.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
|
+
}
|
|
@@ -8,7 +8,8 @@ description: |
|
|
|
8
8
|
- `branch` is optional. When empty, AgentFlow creates a detached worktree at the current HEAD.
|
|
9
9
|
- `worktreePath` is optional. When empty, AgentFlow creates a temporary worktree under the current run temp directory.
|
|
10
10
|
- A worktree created by this node during the current Workspace run is removed when the run finishes or is stopped, even when `worktreePath` is explicitly set.
|
|
11
|
-
- Existing registered worktrees
|
|
11
|
+
- Existing registered worktrees under the current flow workspace are also removed after the run, covering leftovers from previous interrupted runs.
|
|
12
|
+
- Existing registered worktrees outside the current flow workspace are reused and not removed automatically unless this run created them.
|
|
12
13
|
- Existing worktree paths are reused only when they are registered by `git worktree list` for the given repo.
|
|
13
14
|
- `pruneMissing` defaults to true. When Git has a registered worktree whose directory is missing, AgentFlow runs `git worktree prune` before adding it again.
|
|
14
15
|
- `force` defaults to false. When true, AgentFlow passes `--force` to `git worktree add`.
|