@fieldwangai/agentflow 0.1.84 → 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.
@@ -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
+ }