@agenr/openclaw-plugin 3.3.0 → 2026.6.3
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/dist/build-before-turn-artifact-NPUHVWFE.js +71 -0
- package/dist/build-recall-artifact-F3LS3PZX.js +62 -0
- package/dist/chunk-5AXMFBHR.js +14 -0
- package/dist/chunk-5AYIXQRF.js +4452 -0
- package/dist/chunk-5TIP2EPP.js +6944 -0
- package/dist/chunk-GAERET5Q.js +2070 -0
- package/dist/chunk-GF3PX3VM.js +41 -0
- package/dist/chunk-GKZQ5AG5.js +44 -0
- package/dist/chunk-IBPS64W3.js +1069 -0
- package/dist/chunk-MC3C2XM5.js +148 -0
- package/dist/chunk-NSLTJBUC.js +270 -0
- package/dist/chunk-OJSIZDZD.js +9 -0
- package/dist/chunk-OWGQWQUP.js +45 -0
- package/dist/chunk-SIY3JA7T.js +3062 -0
- package/dist/chunk-SOQW7356.js +2416 -0
- package/dist/chunk-U74RE3L7.js +3233 -0
- package/dist/chunk-VBPYU7GO.js +597 -0
- package/dist/chunk-VTHBPXDQ.js +1750 -0
- package/dist/chunk-XFJ4S4G2.js +1679 -0
- package/dist/chunk-Y5NB3FTH.js +106 -0
- package/dist/chunk-ZX55JBV2.js +4451 -0
- package/dist/index.js +1855 -19846
- package/dist/lifecycle-checkpoint-IAC5FCQU.js +154 -0
- package/dist/scan-6JKPOQHD.js +6 -0
- package/dist/service-EKFACEN6.js +15 -0
- package/dist/service-RHNB5AEQ.js +861 -0
- package/dist/sink-AUAAWC5O.js +8 -0
- package/openclaw.plugin.json +2 -11
- package/package.json +1 -1
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatTargetSelectorFromParams
|
|
3
|
+
} from "./chunk-U74RE3L7.js";
|
|
4
|
+
|
|
5
|
+
// src/adapters/openclaw/transcript/parser.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
import * as fs2 from "fs/promises";
|
|
8
|
+
|
|
9
|
+
// src/adapters/openclaw/session/session-id.ts
|
|
10
|
+
import path from "path";
|
|
11
|
+
function deriveOpenClawSessionIdFromFilePath(sessionFile, logger) {
|
|
12
|
+
const normalizedSessionFile = sessionFile.trim();
|
|
13
|
+
if (normalizedSessionFile.length === 0) {
|
|
14
|
+
debugLog(logger, "session-id", "cannot derive session id from empty session file path");
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
const fileName = path.basename(normalizedSessionFile);
|
|
18
|
+
const sessionId = fileName.replace(/\.jsonl(?:\..*)?$/i, "").trim();
|
|
19
|
+
debugLog(logger, "session-id", `derived session id "${sessionId || "<empty>"}" from file=${normalizedSessionFile}`);
|
|
20
|
+
return sessionId.length > 0 ? sessionId : void 0;
|
|
21
|
+
}
|
|
22
|
+
function debugLog(logger, subsystem, message) {
|
|
23
|
+
logger?.debug?.(`[agenr] ${subsystem}: ${message}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/adapters/openclaw/transcript/jsonl.ts
|
|
27
|
+
function parseJsonObjectLineWithDiagnostics(line, lineNumber = 1) {
|
|
28
|
+
if (!line || line.trim().length === 0) {
|
|
29
|
+
return {
|
|
30
|
+
record: null
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(line);
|
|
35
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
36
|
+
return {
|
|
37
|
+
record: parsed
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
record: null,
|
|
42
|
+
diagnostic: {
|
|
43
|
+
kind: "non_object_record",
|
|
44
|
+
lineNumber,
|
|
45
|
+
message: `Skipped non-object JSONL line ${lineNumber}`
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
} catch {
|
|
49
|
+
return {
|
|
50
|
+
record: null,
|
|
51
|
+
diagnostic: {
|
|
52
|
+
kind: "malformed_json",
|
|
53
|
+
lineNumber,
|
|
54
|
+
message: `Skipped malformed JSONL line ${lineNumber}`
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function parseJsonlLines(raw, onRecord) {
|
|
60
|
+
const lines = raw.split(/\r?\n/);
|
|
61
|
+
const diagnostics = [];
|
|
62
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
63
|
+
const line = lines[index]?.trim();
|
|
64
|
+
if (!line) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const parsed = parseJsonObjectLineWithDiagnostics(line, index + 1);
|
|
68
|
+
if (parsed.diagnostic) {
|
|
69
|
+
diagnostics.push(parsed.diagnostic);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (parsed.record) {
|
|
73
|
+
onRecord(parsed.record, index + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
diagnostics
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/adapters/openclaw/transcript/tool-summarization.ts
|
|
82
|
+
var AGENR_FETCH_TARGET_MAX_CHARS = 80;
|
|
83
|
+
var DEFAULT_TOOL_RESULT_DROP_NAMES = ["read", "web_fetch", "browser", "screenshot", "snapshot", "canvas", "tts"];
|
|
84
|
+
var DEFAULT_TOOL_RESULT_KEEP_NAMES = ["web_search", "memory_search", "memory_get", "image"];
|
|
85
|
+
var DEFAULT_TOOL_RESULT_DROP_NAME_SET = new Set(DEFAULT_TOOL_RESULT_DROP_NAMES);
|
|
86
|
+
var DEFAULT_TOOL_RESULT_KEEP_NAME_SET = new Set(DEFAULT_TOOL_RESULT_KEEP_NAMES);
|
|
87
|
+
function asRecord(value) {
|
|
88
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
89
|
+
}
|
|
90
|
+
function getString(value) {
|
|
91
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
92
|
+
}
|
|
93
|
+
function truncateInline(value, max) {
|
|
94
|
+
if (value.length <= max) {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
return value.slice(0, max);
|
|
98
|
+
}
|
|
99
|
+
function firstStringArgValue(args, max) {
|
|
100
|
+
for (const value of Object.values(args)) {
|
|
101
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
102
|
+
return truncateInline(value.trim(), max);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
function extractAgenrStoreEntries(args) {
|
|
108
|
+
const nestedEntries = Array.isArray(args.entries) ? args.entries.flatMap((entry) => {
|
|
109
|
+
const record = asRecord(entry);
|
|
110
|
+
return record ? [record] : [];
|
|
111
|
+
}) : [];
|
|
112
|
+
if (nestedEntries.length > 0) {
|
|
113
|
+
return nestedEntries;
|
|
114
|
+
}
|
|
115
|
+
if (getString(args.type) || getString(args.subject) || getString(args.content) || getString(args.claimKey) || getString(args.claim_key) || getString(args.supersedes)) {
|
|
116
|
+
return [args];
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function summarizeAgenrStoreEntry(entry) {
|
|
121
|
+
const type = getString(entry.type) ?? "unknown";
|
|
122
|
+
const subject = getString(entry.subject) ?? "(no subject)";
|
|
123
|
+
const claimKey = getString(entry.claimKey) ?? getString(entry.claim_key);
|
|
124
|
+
const claimKeySuffix = claimKey ? ` claim_key=${JSON.stringify(truncateInline(claimKey.trim(), 120))}` : "";
|
|
125
|
+
return `${type}: "${truncateInline(subject, 60)}"${claimKeySuffix}`;
|
|
126
|
+
}
|
|
127
|
+
function toolIdentifier(toolName, args) {
|
|
128
|
+
const normalizedToolName = toolName.trim().toLowerCase();
|
|
129
|
+
if (normalizedToolName === "read" || normalizedToolName === "edit" || normalizedToolName === "write") {
|
|
130
|
+
return getString(args.file_path) ?? getString(args.path) ?? getString(args.file) ?? "(unknown file)";
|
|
131
|
+
}
|
|
132
|
+
if (normalizedToolName === "exec") {
|
|
133
|
+
const command = getString(args.command) ?? getString(args.cmd) ?? "(unknown command)";
|
|
134
|
+
return truncateInline(command, 100);
|
|
135
|
+
}
|
|
136
|
+
if (normalizedToolName === "web_fetch") {
|
|
137
|
+
return getString(args.url) ?? "(unknown url)";
|
|
138
|
+
}
|
|
139
|
+
if (normalizedToolName === "web_search") {
|
|
140
|
+
return getString(args.query) ?? "(unknown query)";
|
|
141
|
+
}
|
|
142
|
+
if (normalizedToolName === "browser") {
|
|
143
|
+
const action = getString(args.action) ?? "(unknown action)";
|
|
144
|
+
const targetUrl = getString(args.targetUrl) ?? getString(args.url);
|
|
145
|
+
return targetUrl ? `${action} ${targetUrl}` : action;
|
|
146
|
+
}
|
|
147
|
+
if (normalizedToolName === "agenr_store") {
|
|
148
|
+
const entries = extractAgenrStoreEntries(args);
|
|
149
|
+
return `${entries.length} entr${entries.length === 1 ? "y" : "ies"}`;
|
|
150
|
+
}
|
|
151
|
+
if (normalizedToolName === "agenr_recall") {
|
|
152
|
+
const query = getString(args.query) ?? "(no query)";
|
|
153
|
+
return `"${truncateInline(query, 80)}"`;
|
|
154
|
+
}
|
|
155
|
+
if (normalizedToolName === "agenr_fetch") {
|
|
156
|
+
return formatTargetSelectorFromParams(args, { maxValueChars: AGENR_FETCH_TARGET_MAX_CHARS });
|
|
157
|
+
}
|
|
158
|
+
if (normalizedToolName === "message") {
|
|
159
|
+
const action = getString(args.action) ?? "(unknown action)";
|
|
160
|
+
const target = getString(args.target) ?? getString(args.to) ?? "(unknown target)";
|
|
161
|
+
return `${truncateInline(action, 80)} to ${truncateInline(target, 80)}`;
|
|
162
|
+
}
|
|
163
|
+
if (normalizedToolName === "sessions_spawn") {
|
|
164
|
+
return getString(args.label) ?? getString(args.task)?.slice(0, 60) ?? "(unknown task)";
|
|
165
|
+
}
|
|
166
|
+
if (normalizedToolName === "image") {
|
|
167
|
+
return getString(args.image) ?? getString(args.url) ?? getString(args.path) ?? "(unknown image)";
|
|
168
|
+
}
|
|
169
|
+
if (normalizedToolName === "canvas") {
|
|
170
|
+
return getString(args.action) ?? "(unknown action)";
|
|
171
|
+
}
|
|
172
|
+
if (normalizedToolName === "tts") {
|
|
173
|
+
const text = getString(args.text) ?? "(unknown text)";
|
|
174
|
+
return truncateInline(text, 50);
|
|
175
|
+
}
|
|
176
|
+
return firstStringArgValue(args, 80) ?? "(unknown)";
|
|
177
|
+
}
|
|
178
|
+
function extractToolCallBlocks(content) {
|
|
179
|
+
if (!Array.isArray(content)) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
const toolCalls = [];
|
|
183
|
+
for (const block of content) {
|
|
184
|
+
const record = asRecord(block);
|
|
185
|
+
if (!record) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const type = typeof record.type === "string" ? record.type.trim().toLowerCase() : "";
|
|
189
|
+
const name = getString(record.name) ?? getString(record.tool) ?? getString(record.tool_name);
|
|
190
|
+
const args = asRecord(record.arguments) ?? asRecord(record.args) ?? asRecord(record.input) ?? {};
|
|
191
|
+
const id = getString(record.id) ?? getString(record.toolCallId) ?? getString(record.tool_call_id) ?? getString(record.call_id);
|
|
192
|
+
if ((type === "toolcall" || type === "tool_call" || type === "tool_use" || type === "tooluse") && name) {
|
|
193
|
+
toolCalls.push({ name, args, id });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!type && name && ("arguments" in record || "args" in record || "input" in record)) {
|
|
197
|
+
toolCalls.push({ name, args, id });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return toolCalls;
|
|
201
|
+
}
|
|
202
|
+
function summarizeToolCall(call, options) {
|
|
203
|
+
const normalizedToolName = call.name.trim().toLowerCase();
|
|
204
|
+
const override = options?.overrides?.[normalizedToolName];
|
|
205
|
+
if (override) {
|
|
206
|
+
const summary = override(call);
|
|
207
|
+
if (summary) {
|
|
208
|
+
return summary;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const args = call.args;
|
|
212
|
+
const filePath = getString(args.file_path) ?? getString(args.path) ?? getString(args.file);
|
|
213
|
+
if (normalizedToolName === "read") {
|
|
214
|
+
return `[called Read: ${filePath ?? "(unknown file)"}]`;
|
|
215
|
+
}
|
|
216
|
+
if (normalizedToolName === "write") {
|
|
217
|
+
const content = getString(args.content) ?? getString(args.text) ?? "";
|
|
218
|
+
return `[called Write: ${filePath ?? "(unknown file)"} - ${content.length} chars]`;
|
|
219
|
+
}
|
|
220
|
+
if (normalizedToolName === "edit") {
|
|
221
|
+
const oldText = getString(args.oldText) ?? getString(args.old_string) ?? "";
|
|
222
|
+
return `[called Edit: ${filePath ?? "(unknown file)"} - replaced ${oldText.length} chars]`;
|
|
223
|
+
}
|
|
224
|
+
if (normalizedToolName === "exec") {
|
|
225
|
+
const command = getString(args.command) ?? getString(args.cmd) ?? "(unknown command)";
|
|
226
|
+
return `[called exec: ${truncateInline(command, 200)}]`;
|
|
227
|
+
}
|
|
228
|
+
if (normalizedToolName === "web_search") {
|
|
229
|
+
const query = getString(args.query) ?? "(unknown query)";
|
|
230
|
+
return `[called web_search: ${truncateInline(query, 200)}]`;
|
|
231
|
+
}
|
|
232
|
+
if (normalizedToolName === "web_fetch") {
|
|
233
|
+
const url = getString(args.url) ?? "(unknown url)";
|
|
234
|
+
return `[called web_fetch: ${truncateInline(url, 200)}]`;
|
|
235
|
+
}
|
|
236
|
+
if (normalizedToolName === "browser") {
|
|
237
|
+
const action = getString(args.action) ?? "(unknown action)";
|
|
238
|
+
return `[called browser: ${truncateInline(action, 200)}]`;
|
|
239
|
+
}
|
|
240
|
+
if (normalizedToolName === "message") {
|
|
241
|
+
const action = getString(args.action) ?? "(unknown action)";
|
|
242
|
+
const target = getString(args.target) ?? getString(args.to) ?? "(unknown target)";
|
|
243
|
+
return `[called message: ${truncateInline(action, 200)} to ${truncateInline(target, 200)}]`;
|
|
244
|
+
}
|
|
245
|
+
if (normalizedToolName === "agenr_store") {
|
|
246
|
+
const entries = extractAgenrStoreEntries(args);
|
|
247
|
+
if (entries.length === 0) {
|
|
248
|
+
return "[attempted brain store: (empty)]";
|
|
249
|
+
}
|
|
250
|
+
const summaries = entries.slice(0, 3).map(summarizeAgenrStoreEntry);
|
|
251
|
+
const countSuffix = entries.length > 3 ? ` (+${entries.length - 3} more)` : "";
|
|
252
|
+
return `[attempted brain store: ${summaries.join(", ")}${countSuffix}]`;
|
|
253
|
+
}
|
|
254
|
+
if (normalizedToolName === "agenr_recall") {
|
|
255
|
+
const query = getString(args.query) ?? "(no query)";
|
|
256
|
+
return `[recalled from brain: "${truncateInline(query, 100)}"]`;
|
|
257
|
+
}
|
|
258
|
+
if (normalizedToolName === "agenr_fetch") {
|
|
259
|
+
return `[fetched from brain: ${formatTargetSelectorFromParams(args, { maxValueChars: AGENR_FETCH_TARGET_MAX_CHARS })}]`;
|
|
260
|
+
}
|
|
261
|
+
if (normalizedToolName === "sessions_spawn") {
|
|
262
|
+
const label = getString(args.label);
|
|
263
|
+
const mode = getString(args.mode) ?? "run";
|
|
264
|
+
const model = getString(args.model);
|
|
265
|
+
const modelSuffix = model ? ` model=${model}` : "";
|
|
266
|
+
if (label) {
|
|
267
|
+
return `[spawned sub-agent: ${label} (${mode}${modelSuffix})]`;
|
|
268
|
+
}
|
|
269
|
+
const task = getString(args.task) ?? "(no task)";
|
|
270
|
+
return `[spawned sub-agent: ${truncateInline(task, 80)} (${mode}${modelSuffix})]`;
|
|
271
|
+
}
|
|
272
|
+
const relevantArgValue = firstStringArgValue(
|
|
273
|
+
Object.fromEntries(
|
|
274
|
+
Object.entries(args).filter(
|
|
275
|
+
([key]) => !["buffer", "content", "data", "newText", "new_string", "oldText", "old_string"].includes(key) && !(normalizedToolName === "write" && key === "text")
|
|
276
|
+
)
|
|
277
|
+
),
|
|
278
|
+
80
|
|
279
|
+
) ?? "(no args)";
|
|
280
|
+
return `[called ${call.name}: ${relevantArgValue}]`;
|
|
281
|
+
}
|
|
282
|
+
function toolResultPlaceholder(toolName, args) {
|
|
283
|
+
const normalizedToolName = toolName.trim().length > 0 ? toolName.trim() : "unknown";
|
|
284
|
+
const identifier = toolIdentifier(normalizedToolName, args);
|
|
285
|
+
return `[tool result from ${normalizedToolName}: ${identifier} - filtered]`;
|
|
286
|
+
}
|
|
287
|
+
function shouldKeepToolResult(toolName, text, policy) {
|
|
288
|
+
const normalizedToolName = (toolName ?? "").trim().toLowerCase();
|
|
289
|
+
const dropToolNames = policy?.dropToolNames ?? DEFAULT_TOOL_RESULT_DROP_NAME_SET;
|
|
290
|
+
const keepToolNames = policy?.keepToolNames ?? DEFAULT_TOOL_RESULT_KEEP_NAME_SET;
|
|
291
|
+
if (normalizedToolName && dropToolNames.has(normalizedToolName)) {
|
|
292
|
+
return { keep: false };
|
|
293
|
+
}
|
|
294
|
+
if (normalizedToolName && keepToolNames.has(normalizedToolName)) {
|
|
295
|
+
return { keep: true, truncateTo: 2e3 };
|
|
296
|
+
}
|
|
297
|
+
if (normalizedToolName === "exec") {
|
|
298
|
+
if (text.length < 1e3) {
|
|
299
|
+
return { keep: true, truncateTo: 2e3 };
|
|
300
|
+
}
|
|
301
|
+
if (/(error|failed|fail)/i.test(text)) {
|
|
302
|
+
return { keep: true, truncateTo: 2e3 };
|
|
303
|
+
}
|
|
304
|
+
return { keep: false };
|
|
305
|
+
}
|
|
306
|
+
if (text.length < 500) {
|
|
307
|
+
return { keep: true, truncateTo: 2e3 };
|
|
308
|
+
}
|
|
309
|
+
return { keep: false };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/adapters/openclaw/transcript/message-content.ts
|
|
313
|
+
var TEXT_BLOCK_TYPES = /* @__PURE__ */ new Set(["input_text", "output_text", "text"]);
|
|
314
|
+
function normalizeWhitespace(value) {
|
|
315
|
+
return value.replace(/\s+/g, " ").trim();
|
|
316
|
+
}
|
|
317
|
+
function extractTextBlocks(content) {
|
|
318
|
+
if (typeof content === "string") {
|
|
319
|
+
const normalized = normalizeWhitespace(content);
|
|
320
|
+
return normalized ? [normalized] : [];
|
|
321
|
+
}
|
|
322
|
+
if (!Array.isArray(content)) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
const textBlocks = [];
|
|
326
|
+
let nonTextBlockCount = 0;
|
|
327
|
+
for (const block of content) {
|
|
328
|
+
if (typeof block === "string") {
|
|
329
|
+
const normalized = normalizeWhitespace(block);
|
|
330
|
+
if (normalized) {
|
|
331
|
+
textBlocks.push(normalized);
|
|
332
|
+
}
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const record = asRecord(block);
|
|
336
|
+
if (!record) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (typeof record.text === "string") {
|
|
340
|
+
const normalized = normalizeWhitespace(record.text);
|
|
341
|
+
if (normalized) {
|
|
342
|
+
textBlocks.push(normalized);
|
|
343
|
+
}
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const type = typeof record.type === "string" ? record.type.trim().toLowerCase() : "";
|
|
347
|
+
if (typeof record.content === "string" && TEXT_BLOCK_TYPES.has(type)) {
|
|
348
|
+
const normalized = normalizeWhitespace(record.content);
|
|
349
|
+
if (normalized) {
|
|
350
|
+
textBlocks.push(normalized);
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
nonTextBlockCount += 1;
|
|
355
|
+
}
|
|
356
|
+
if (textBlocks.length === 0 && nonTextBlockCount > 0) {
|
|
357
|
+
textBlocks.push(`[non-text content omitted: ${nonTextBlockCount} block${nonTextBlockCount === 1 ? "" : "s"}]`);
|
|
358
|
+
}
|
|
359
|
+
return textBlocks;
|
|
360
|
+
}
|
|
361
|
+
function extractRawTextBlocks(content) {
|
|
362
|
+
if (typeof content === "string") {
|
|
363
|
+
return [content];
|
|
364
|
+
}
|
|
365
|
+
if (!Array.isArray(content)) {
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
const textBlocks = [];
|
|
369
|
+
for (const block of content) {
|
|
370
|
+
if (typeof block === "string") {
|
|
371
|
+
textBlocks.push(block);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const record = asRecord(block);
|
|
375
|
+
if (!record) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (typeof record.text === "string") {
|
|
379
|
+
textBlocks.push(record.text);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const type = typeof record.type === "string" ? record.type.trim().toLowerCase() : "";
|
|
383
|
+
if (typeof record.content === "string" && TEXT_BLOCK_TYPES.has(type)) {
|
|
384
|
+
textBlocks.push(record.content);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return textBlocks;
|
|
388
|
+
}
|
|
389
|
+
function normalizeLabel(value) {
|
|
390
|
+
return value.trim().toLowerCase().replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
391
|
+
}
|
|
392
|
+
function normalizeMessageText(content) {
|
|
393
|
+
return normalizeWhitespace(extractTextBlocks(content).join("\n"));
|
|
394
|
+
}
|
|
395
|
+
function normalizeOpenClawRole(value) {
|
|
396
|
+
if (typeof value !== "string") {
|
|
397
|
+
return "unknown";
|
|
398
|
+
}
|
|
399
|
+
const normalized = value.trim().toLowerCase();
|
|
400
|
+
if (normalized === "user" || normalized === "human") {
|
|
401
|
+
return "user";
|
|
402
|
+
}
|
|
403
|
+
if (normalized === "assistant" || normalized === "ai" || normalized === "developer") {
|
|
404
|
+
return "assistant";
|
|
405
|
+
}
|
|
406
|
+
if (normalized === "system") {
|
|
407
|
+
return "system";
|
|
408
|
+
}
|
|
409
|
+
if (normalized === "tool" || normalized === "toolresult" || normalized === "tool_result") {
|
|
410
|
+
return "toolResult";
|
|
411
|
+
}
|
|
412
|
+
return "unknown";
|
|
413
|
+
}
|
|
414
|
+
function truncateWithMarker(text, maxChars) {
|
|
415
|
+
if (text.length <= maxChars) {
|
|
416
|
+
return text;
|
|
417
|
+
}
|
|
418
|
+
return `${text.slice(0, maxChars)}
|
|
419
|
+
[...truncated]`;
|
|
420
|
+
}
|
|
421
|
+
function isPureBase64(text) {
|
|
422
|
+
const trimmed = text.trim();
|
|
423
|
+
if (trimmed.length < 500) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (!/[+/=]/.test(trimmed)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return /^[A-Za-z0-9+/=\s]{500,}$/.test(trimmed);
|
|
430
|
+
}
|
|
431
|
+
function normalizeSessionLabel(value) {
|
|
432
|
+
const normalized = normalizeLabel(value);
|
|
433
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
434
|
+
}
|
|
435
|
+
function extractConversationLabel(content) {
|
|
436
|
+
const rawTextBlocks = extractRawTextBlocks(content);
|
|
437
|
+
for (const block of rawTextBlocks) {
|
|
438
|
+
const matches = block.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi);
|
|
439
|
+
for (const match of matches) {
|
|
440
|
+
const candidate = match[1];
|
|
441
|
+
if (!candidate) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const parsed = JSON.parse(candidate);
|
|
446
|
+
const record = asRecord(parsed);
|
|
447
|
+
const conversationLabel = record ? getString(record.conversation_label) : void 0;
|
|
448
|
+
const normalizedLabel = conversationLabel ? normalizeSessionLabel(conversationLabel) : void 0;
|
|
449
|
+
if (normalizedLabel) {
|
|
450
|
+
return normalizedLabel;
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
function extractAssistantTextParts(content) {
|
|
459
|
+
if (typeof content === "string") {
|
|
460
|
+
const normalized = normalizeWhitespace(content);
|
|
461
|
+
return normalized ? [normalized] : [];
|
|
462
|
+
}
|
|
463
|
+
if (!Array.isArray(content)) {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
const textParts = [];
|
|
467
|
+
for (const block of content) {
|
|
468
|
+
if (typeof block === "string") {
|
|
469
|
+
const normalized = normalizeWhitespace(block);
|
|
470
|
+
if (normalized) {
|
|
471
|
+
textParts.push(normalized);
|
|
472
|
+
}
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const record = asRecord(block);
|
|
476
|
+
if (!record) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (typeof record.text === "string") {
|
|
480
|
+
const normalized = normalizeWhitespace(record.text);
|
|
481
|
+
if (normalized) {
|
|
482
|
+
textParts.push(normalized);
|
|
483
|
+
}
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const type = typeof record.type === "string" ? record.type.trim().toLowerCase() : "";
|
|
487
|
+
if (typeof record.content === "string" && TEXT_BLOCK_TYPES.has(type)) {
|
|
488
|
+
const normalized = normalizeWhitespace(record.content);
|
|
489
|
+
if (normalized) {
|
|
490
|
+
textParts.push(normalized);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return textParts;
|
|
495
|
+
}
|
|
496
|
+
function pushMessage(messages, role, text, timestamp) {
|
|
497
|
+
messages.push({
|
|
498
|
+
index: messages.length,
|
|
499
|
+
role,
|
|
500
|
+
text,
|
|
501
|
+
timestamp
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/adapters/openclaw/transcript/timestamps.ts
|
|
506
|
+
import * as fs from "fs/promises";
|
|
507
|
+
function parseTimestampValue(value) {
|
|
508
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
509
|
+
const parsed = new Date(value);
|
|
510
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
511
|
+
return parsed.toISOString();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
515
|
+
const milliseconds = value > 1e12 ? value : value * 1e3;
|
|
516
|
+
const parsed = new Date(milliseconds);
|
|
517
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
518
|
+
return parsed.toISOString();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
function extractTimestamp(record) {
|
|
524
|
+
for (const field of ["timestamp", "ts", "created_at", "createdAt", "time", "date"]) {
|
|
525
|
+
const parsed = parseTimestampValue(record[field]);
|
|
526
|
+
if (parsed) {
|
|
527
|
+
return parsed;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return void 0;
|
|
531
|
+
}
|
|
532
|
+
async function getFileMtimeTimestamp(filePath) {
|
|
533
|
+
try {
|
|
534
|
+
const stat2 = await fs.stat(filePath);
|
|
535
|
+
return parseTimestampValue(stat2.mtime.toISOString());
|
|
536
|
+
} catch {
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function resolveTimestampFallback(filePath, ...candidates) {
|
|
541
|
+
for (const candidate of candidates) {
|
|
542
|
+
const parsed = parseTimestampValue(candidate);
|
|
543
|
+
if (parsed) {
|
|
544
|
+
return parsed;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const fileMtime = await getFileMtimeTimestamp(filePath);
|
|
548
|
+
if (fileMtime) {
|
|
549
|
+
return fileMtime;
|
|
550
|
+
}
|
|
551
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
552
|
+
}
|
|
553
|
+
async function applyMessageTimestampFallbacks(filePath, messages, options) {
|
|
554
|
+
const fallbackTimestamp = await resolveTimestampFallback(filePath, options?.sessionTimestamp);
|
|
555
|
+
for (const message of messages) {
|
|
556
|
+
message.timestamp = parseTimestampValue(message.timestamp) ?? fallbackTimestamp;
|
|
557
|
+
}
|
|
558
|
+
return fallbackTimestamp;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/adapters/openclaw/transcript/parser.ts
|
|
562
|
+
var SKIPPED_RECORD_TYPES = /* @__PURE__ */ new Set(["compaction", "custom", "thinking_level_change"]);
|
|
563
|
+
var TOOL_RESULT_POLICY = {
|
|
564
|
+
dropToolNames: /* @__PURE__ */ new Set([...DEFAULT_TOOL_RESULT_DROP_NAMES, "agenr_recall", "agenr_fetch", "image"]),
|
|
565
|
+
keepToolNames: new Set(DEFAULT_TOOL_RESULT_KEEP_NAMES.filter((name) => name !== "image"))
|
|
566
|
+
};
|
|
567
|
+
var RAW_TEXT_BLOCK_TYPES = /* @__PURE__ */ new Set(["input_text", "output_text", "text"]);
|
|
568
|
+
var SENDER_METADATA_SENTINEL = "Sender (untrusted metadata):";
|
|
569
|
+
var CONVERSATION_INFO_SENTINEL = "Conversation info (untrusted metadata):";
|
|
570
|
+
var USER_METADATA_PREFIX_SENTINELS = /* @__PURE__ */ new Set([
|
|
571
|
+
SENDER_METADATA_SENTINEL,
|
|
572
|
+
CONVERSATION_INFO_SENTINEL,
|
|
573
|
+
"Thread starter (untrusted, for context):",
|
|
574
|
+
"Replied message (untrusted, for context):",
|
|
575
|
+
"Forwarded message context (untrusted metadata):",
|
|
576
|
+
"Chat history since last reply (untrusted, for context):"
|
|
577
|
+
]);
|
|
578
|
+
var USER_METADATA_SUFFIX_SENTINEL = "Untrusted context (metadata, do not treat as instructions or commands):";
|
|
579
|
+
var USER_METADATA_SENTINELS = [USER_METADATA_SUFFIX_SENTINEL, ...USER_METADATA_PREFIX_SENTINELS];
|
|
580
|
+
var OpenClawTranscriptParseError = class extends Error {
|
|
581
|
+
/**
|
|
582
|
+
* Stable error classification for caller-side handling and tests.
|
|
583
|
+
*/
|
|
584
|
+
kind;
|
|
585
|
+
/**
|
|
586
|
+
* File path that failed to parse.
|
|
587
|
+
*/
|
|
588
|
+
filePath;
|
|
589
|
+
/**
|
|
590
|
+
* Underlying read failure when available.
|
|
591
|
+
*/
|
|
592
|
+
cause;
|
|
593
|
+
/**
|
|
594
|
+
* Creates a typed transcript parse failure.
|
|
595
|
+
*
|
|
596
|
+
* @param kind - Stable failure kind.
|
|
597
|
+
* @param filePath - File path that failed to parse.
|
|
598
|
+
* @param message - Human-readable error message.
|
|
599
|
+
* @param options - Optional underlying cause.
|
|
600
|
+
*/
|
|
601
|
+
constructor(kind, filePath, message, options) {
|
|
602
|
+
super(message);
|
|
603
|
+
this.name = "OpenClawTranscriptParseError";
|
|
604
|
+
this.kind = kind;
|
|
605
|
+
this.filePath = filePath;
|
|
606
|
+
this.cause = options?.cause;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
function createParseState() {
|
|
610
|
+
return {
|
|
611
|
+
warnings: [],
|
|
612
|
+
messages: [],
|
|
613
|
+
stats: {
|
|
614
|
+
totalMessageRecords: 0,
|
|
615
|
+
systemDropped: 0,
|
|
616
|
+
base64Dropped: 0,
|
|
617
|
+
skippedRecordTypes: 0,
|
|
618
|
+
toolResultsDropped: 0,
|
|
619
|
+
toolResultsKept: 0
|
|
620
|
+
},
|
|
621
|
+
modelsUsed: [],
|
|
622
|
+
modelsUsedSet: /* @__PURE__ */ new Set(),
|
|
623
|
+
pendingToolCalls: [],
|
|
624
|
+
pendingToolCallsById: /* @__PURE__ */ new Map(),
|
|
625
|
+
detectedSurface: null,
|
|
626
|
+
surfaceDetected: false,
|
|
627
|
+
firstUserRawText: null
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function toTranscriptDiagnostic(diagnostic) {
|
|
631
|
+
return {
|
|
632
|
+
kind: diagnostic.kind,
|
|
633
|
+
lineNumber: diagnostic.lineNumber,
|
|
634
|
+
message: diagnostic.message
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function formatTranscriptDiagnosticWarning(diagnostic) {
|
|
638
|
+
return diagnostic.message;
|
|
639
|
+
}
|
|
640
|
+
async function readTranscriptFileStrict(filePath) {
|
|
641
|
+
try {
|
|
642
|
+
return await fs2.readFile(filePath, "utf8");
|
|
643
|
+
} catch (error) {
|
|
644
|
+
if (isFileNotFound(error)) {
|
|
645
|
+
throw new OpenClawTranscriptParseError("missing_file", filePath, `Transcript file not found: ${filePath}`, { cause: error });
|
|
646
|
+
}
|
|
647
|
+
throw new OpenClawTranscriptParseError("unreadable_file", filePath, `Could not read transcript file ${filePath}: ${formatErrorMessage(error)}`, {
|
|
648
|
+
cause: error
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function extractRawMessageText(content) {
|
|
653
|
+
if (typeof content === "string") {
|
|
654
|
+
return content;
|
|
655
|
+
}
|
|
656
|
+
if (!Array.isArray(content)) {
|
|
657
|
+
return "";
|
|
658
|
+
}
|
|
659
|
+
const blocks = [];
|
|
660
|
+
for (const block of content) {
|
|
661
|
+
if (typeof block === "string") {
|
|
662
|
+
blocks.push(block);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const record = asRecord(block);
|
|
666
|
+
if (!record) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (typeof record.text === "string") {
|
|
670
|
+
blocks.push(record.text);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const type = typeof record.type === "string" ? record.type.trim().toLowerCase() : "";
|
|
674
|
+
if (typeof record.content === "string" && RAW_TEXT_BLOCK_TYPES.has(type)) {
|
|
675
|
+
blocks.push(record.content);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return blocks.join("\n");
|
|
679
|
+
}
|
|
680
|
+
function stripOpenClawUserMetadata(content) {
|
|
681
|
+
const normalizedText = normalizeMessageText(content);
|
|
682
|
+
if (normalizedText.length === 0) {
|
|
683
|
+
return normalizedText;
|
|
684
|
+
}
|
|
685
|
+
const rawText = extractRawMessageText(content);
|
|
686
|
+
if (rawText.length === 0 || !USER_METADATA_SENTINELS.some((sentinel) => rawText.includes(sentinel))) {
|
|
687
|
+
return normalizedText;
|
|
688
|
+
}
|
|
689
|
+
return normalizeMessageText(stripMetadataBlocks(rawText));
|
|
690
|
+
}
|
|
691
|
+
function stripMetadataBlocks(text) {
|
|
692
|
+
const lines = text.split(/\r?\n/u);
|
|
693
|
+
let index = 0;
|
|
694
|
+
while (index < lines.length) {
|
|
695
|
+
while (index < lines.length && lines[index]?.trim().length === 0) {
|
|
696
|
+
index += 1;
|
|
697
|
+
}
|
|
698
|
+
if (index >= lines.length) {
|
|
699
|
+
return "";
|
|
700
|
+
}
|
|
701
|
+
const line = lines[index]?.trim();
|
|
702
|
+
if (line === USER_METADATA_SUFFIX_SENTINEL) {
|
|
703
|
+
return "";
|
|
704
|
+
}
|
|
705
|
+
if (!line || !USER_METADATA_PREFIX_SENTINELS.has(line)) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
const nextIndex = skipMetadataJsonFence(lines, index);
|
|
709
|
+
if (nextIndex === index) {
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
index = nextIndex;
|
|
713
|
+
}
|
|
714
|
+
const suffixIndex = lines.findIndex((line, lineIndex) => lineIndex >= index && line.trim() === USER_METADATA_SUFFIX_SENTINEL);
|
|
715
|
+
const body = suffixIndex >= 0 ? lines.slice(index, suffixIndex) : lines.slice(index);
|
|
716
|
+
return body.join("\n").trim();
|
|
717
|
+
}
|
|
718
|
+
function skipMetadataJsonFence(lines, startIndex) {
|
|
719
|
+
let index = startIndex + 1;
|
|
720
|
+
while (index < lines.length && lines[index]?.trim().length === 0) {
|
|
721
|
+
index += 1;
|
|
722
|
+
}
|
|
723
|
+
if (index >= lines.length || !/^```(?:json)?\s*$/iu.test(lines[index]?.trim() ?? "")) {
|
|
724
|
+
return startIndex;
|
|
725
|
+
}
|
|
726
|
+
index += 1;
|
|
727
|
+
while (index < lines.length && !/^```\s*$/u.test(lines[index]?.trim() ?? "")) {
|
|
728
|
+
index += 1;
|
|
729
|
+
}
|
|
730
|
+
if (index >= lines.length) {
|
|
731
|
+
return startIndex;
|
|
732
|
+
}
|
|
733
|
+
index += 1;
|
|
734
|
+
while (index < lines.length && lines[index]?.trim().length === 0) {
|
|
735
|
+
index += 1;
|
|
736
|
+
}
|
|
737
|
+
return index;
|
|
738
|
+
}
|
|
739
|
+
function addModelUsed(state, value) {
|
|
740
|
+
const modelId = getString(value);
|
|
741
|
+
if (!modelId || state.modelsUsedSet.has(modelId)) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
state.modelsUsedSet.add(modelId);
|
|
745
|
+
state.modelsUsed.push(modelId);
|
|
746
|
+
}
|
|
747
|
+
function setDetectedSurface(state, surface) {
|
|
748
|
+
if (state.surfaceDetected || !surface) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
state.detectedSurface = surface;
|
|
752
|
+
state.surfaceDetected = true;
|
|
753
|
+
}
|
|
754
|
+
function readInboundSurface(record) {
|
|
755
|
+
const inboundMeta = asRecord(record.inbound_meta);
|
|
756
|
+
const surface = getString(inboundMeta?.surface)?.trim().toLowerCase();
|
|
757
|
+
return surface || null;
|
|
758
|
+
}
|
|
759
|
+
function extractMetadataPayload(rawText, sentinel) {
|
|
760
|
+
const lines = rawText.split(/\r?\n/u);
|
|
761
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
762
|
+
if (lines[index]?.trim() !== sentinel) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
let fenceIndex = index + 1;
|
|
766
|
+
while (fenceIndex < lines.length && lines[fenceIndex]?.trim().length === 0) {
|
|
767
|
+
fenceIndex += 1;
|
|
768
|
+
}
|
|
769
|
+
if (fenceIndex >= lines.length || !/^```(?:json)?\s*$/iu.test(lines[fenceIndex]?.trim() ?? "")) {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
fenceIndex += 1;
|
|
773
|
+
const jsonLines = [];
|
|
774
|
+
while (fenceIndex < lines.length && !/^```\s*$/u.test(lines[fenceIndex]?.trim() ?? "")) {
|
|
775
|
+
jsonLines.push(lines[fenceIndex] ?? "");
|
|
776
|
+
fenceIndex += 1;
|
|
777
|
+
}
|
|
778
|
+
if (fenceIndex >= lines.length) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
const parsed = JSON.parse(jsonLines.join("\n").trim());
|
|
783
|
+
return asRecord(parsed);
|
|
784
|
+
} catch {
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
function mapKnownSurface(value) {
|
|
791
|
+
if (!value) {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
if (value.includes("telegram")) {
|
|
795
|
+
return "telegram";
|
|
796
|
+
}
|
|
797
|
+
if (value.includes("signal")) {
|
|
798
|
+
return "signal";
|
|
799
|
+
}
|
|
800
|
+
if (value.includes("discord")) {
|
|
801
|
+
return "discord";
|
|
802
|
+
}
|
|
803
|
+
if (value.includes("openclaw-tui")) {
|
|
804
|
+
return "tui";
|
|
805
|
+
}
|
|
806
|
+
if (value.includes("gateway-client") || value.includes("openclaw-control-ui") || value.includes("webchat")) {
|
|
807
|
+
return "webchat";
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
function extractSenderSurface(rawText) {
|
|
812
|
+
const payload = extractMetadataPayload(rawText, SENDER_METADATA_SENTINEL);
|
|
813
|
+
if (!payload) {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
const label = getString(payload.label)?.trim().toLowerCase() ?? getString(payload.id)?.trim().toLowerCase() ?? "";
|
|
817
|
+
return mapKnownSurface(label);
|
|
818
|
+
}
|
|
819
|
+
function extractConversationInfoSurface(rawText) {
|
|
820
|
+
const payload = extractMetadataPayload(rawText, CONVERSATION_INFO_SENTINEL);
|
|
821
|
+
if (!payload) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
const senderId = getString(payload.sender_id)?.trim().toLowerCase() ?? "";
|
|
825
|
+
return mapKnownSurface(senderId);
|
|
826
|
+
}
|
|
827
|
+
function inferSurfaceFromContent(firstUserRawText) {
|
|
828
|
+
const normalized = firstUserRawText?.trim().toLowerCase() ?? "";
|
|
829
|
+
if (!normalized) {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
if (normalized.includes("[subagent context]")) {
|
|
833
|
+
return "subagent";
|
|
834
|
+
}
|
|
835
|
+
if (normalized.includes("heartbeat.md")) {
|
|
836
|
+
return "heartbeat";
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
function resolveToolContext(state, message) {
|
|
841
|
+
const toolCallId = getString(message.toolCallId) ?? getString(message.tool_call_id) ?? getString(message.call_id) ?? getString(message.id);
|
|
842
|
+
if (toolCallId && state.pendingToolCallsById.has(toolCallId)) {
|
|
843
|
+
const context = state.pendingToolCallsById.get(toolCallId) ?? null;
|
|
844
|
+
state.pendingToolCallsById.delete(toolCallId);
|
|
845
|
+
if (context) {
|
|
846
|
+
const queuedIndex = state.pendingToolCalls.findIndex((toolCall) => toolCall.id === toolCallId);
|
|
847
|
+
if (queuedIndex >= 0) {
|
|
848
|
+
state.pendingToolCalls.splice(queuedIndex, 1);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return context;
|
|
852
|
+
}
|
|
853
|
+
return state.pendingToolCalls.shift() ?? null;
|
|
854
|
+
}
|
|
855
|
+
function handleMessageRecord(state, record, message) {
|
|
856
|
+
state.stats.totalMessageRecords += 1;
|
|
857
|
+
const role = normalizeOpenClawRole(message.role);
|
|
858
|
+
if (!state.surfaceDetected) {
|
|
859
|
+
setDetectedSurface(state, readInboundSurface(message));
|
|
860
|
+
}
|
|
861
|
+
if (!state.surfaceDetected && role === "user") {
|
|
862
|
+
const rawText = extractRawMessageText(message.content);
|
|
863
|
+
if (state.firstUserRawText === null) {
|
|
864
|
+
state.firstUserRawText = rawText;
|
|
865
|
+
}
|
|
866
|
+
setDetectedSurface(state, extractSenderSurface(rawText));
|
|
867
|
+
if (!state.surfaceDetected) {
|
|
868
|
+
setDetectedSurface(state, extractConversationInfoSurface(rawText));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (role === "system") {
|
|
872
|
+
state.stats.systemDropped += 1;
|
|
873
|
+
return "known_skip";
|
|
874
|
+
}
|
|
875
|
+
const timestamp = extractTimestamp(record) ?? extractTimestamp(message);
|
|
876
|
+
if (role === "user") {
|
|
877
|
+
const extractedLabel = extractConversationLabel(message.content);
|
|
878
|
+
if (extractedLabel) {
|
|
879
|
+
state.sessionLabel = extractedLabel;
|
|
880
|
+
}
|
|
881
|
+
const text = stripOpenClawUserMetadata(message.content);
|
|
882
|
+
if (!text) {
|
|
883
|
+
return "known_skip";
|
|
884
|
+
}
|
|
885
|
+
if (isPureBase64(text)) {
|
|
886
|
+
state.stats.base64Dropped += 1;
|
|
887
|
+
return "known_skip";
|
|
888
|
+
}
|
|
889
|
+
pushMessage(state.messages, "user", text, timestamp);
|
|
890
|
+
return "accepted";
|
|
891
|
+
}
|
|
892
|
+
if (role === "assistant") {
|
|
893
|
+
const toolCalls = extractToolCallBlocks(message.content);
|
|
894
|
+
for (const toolCall of toolCalls) {
|
|
895
|
+
state.pendingToolCalls.push(toolCall);
|
|
896
|
+
if (toolCall.id) {
|
|
897
|
+
state.pendingToolCallsById.set(toolCall.id, toolCall);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
const assistantText = [...extractAssistantTextParts(message.content), ...toolCalls.map((toolCall) => summarizeToolCall(toolCall))].join(" ").trim();
|
|
901
|
+
addModelUsed(state, message.model);
|
|
902
|
+
if (!assistantText) {
|
|
903
|
+
return "known_skip";
|
|
904
|
+
}
|
|
905
|
+
if (isPureBase64(assistantText)) {
|
|
906
|
+
state.stats.base64Dropped += 1;
|
|
907
|
+
return "known_skip";
|
|
908
|
+
}
|
|
909
|
+
pushMessage(state.messages, "assistant", truncateWithMarker(assistantText, 5e3), timestamp);
|
|
910
|
+
return "accepted";
|
|
911
|
+
}
|
|
912
|
+
if (role !== "toolResult") {
|
|
913
|
+
return "structurally_invalid";
|
|
914
|
+
}
|
|
915
|
+
const toolContext = resolveToolContext(state, message);
|
|
916
|
+
const toolName = getString(message.name) ?? getString(message.tool) ?? getString(record.name) ?? getString(record.tool) ?? toolContext?.name;
|
|
917
|
+
const toolArgs = toolContext?.args ?? {};
|
|
918
|
+
const toolText = normalizeMessageText(message.content);
|
|
919
|
+
if (!toolText) {
|
|
920
|
+
return "known_skip";
|
|
921
|
+
}
|
|
922
|
+
if (isPureBase64(toolText)) {
|
|
923
|
+
state.stats.base64Dropped += 1;
|
|
924
|
+
return "known_skip";
|
|
925
|
+
}
|
|
926
|
+
const decision = shouldKeepToolResult(toolName, toolText, TOOL_RESULT_POLICY);
|
|
927
|
+
if (decision.keep) {
|
|
928
|
+
state.stats.toolResultsKept += 1;
|
|
929
|
+
pushMessage(state.messages, "assistant", decision.truncateTo ? truncateWithMarker(toolText, decision.truncateTo) : toolText, timestamp);
|
|
930
|
+
return "accepted";
|
|
931
|
+
}
|
|
932
|
+
state.stats.toolResultsDropped += 1;
|
|
933
|
+
pushMessage(state.messages, "assistant", toolResultPlaceholder(toolName ?? "unknown", toolArgs), timestamp);
|
|
934
|
+
return "accepted";
|
|
935
|
+
}
|
|
936
|
+
function handleRecord(state, record) {
|
|
937
|
+
if (record.type === "session") {
|
|
938
|
+
state.sessionId = getString(record.id) ?? state.sessionId;
|
|
939
|
+
state.sessionTimestamp = extractTimestamp(record) ?? state.sessionTimestamp;
|
|
940
|
+
state.sessionLabel = normalizeSessionLabel(getString(record.conversation_label) ?? "") ?? state.sessionLabel;
|
|
941
|
+
state.workingDirectory = getString(record.cwd) ?? state.workingDirectory;
|
|
942
|
+
addModelUsed(state, record.model);
|
|
943
|
+
if (!state.surfaceDetected) {
|
|
944
|
+
setDetectedSurface(state, readInboundSurface(record));
|
|
945
|
+
}
|
|
946
|
+
return "accepted";
|
|
947
|
+
}
|
|
948
|
+
if (!state.surfaceDetected) {
|
|
949
|
+
setDetectedSurface(state, readInboundSurface(record));
|
|
950
|
+
}
|
|
951
|
+
if (record.type === "model_change") {
|
|
952
|
+
addModelUsed(state, record.modelId);
|
|
953
|
+
state.stats.skippedRecordTypes += 1;
|
|
954
|
+
return "known_skip";
|
|
955
|
+
}
|
|
956
|
+
if (typeof record.type === "string" && SKIPPED_RECORD_TYPES.has(record.type)) {
|
|
957
|
+
state.stats.skippedRecordTypes += 1;
|
|
958
|
+
return "known_skip";
|
|
959
|
+
}
|
|
960
|
+
const message = asRecord(record.message);
|
|
961
|
+
if (!message) {
|
|
962
|
+
return "structurally_invalid";
|
|
963
|
+
}
|
|
964
|
+
return handleMessageRecord(state, record, message);
|
|
965
|
+
}
|
|
966
|
+
function buildFilterWarning(stats) {
|
|
967
|
+
return `Filtered transcript: ${stats.toolResultsDropped} tool results dropped, ${stats.toolResultsKept} kept, ${stats.systemDropped} system dropped, ${stats.base64Dropped} base64 dropped.`;
|
|
968
|
+
}
|
|
969
|
+
function isFileNotFound(error) {
|
|
970
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
971
|
+
}
|
|
972
|
+
function formatErrorMessage(error) {
|
|
973
|
+
if (error instanceof Error) {
|
|
974
|
+
return error.message;
|
|
975
|
+
}
|
|
976
|
+
return String(error);
|
|
977
|
+
}
|
|
978
|
+
var OpenClawTranscriptParser = class {
|
|
979
|
+
/**
|
|
980
|
+
* Parses an OpenClaw JSONL transcript file into agenr transcript data.
|
|
981
|
+
*
|
|
982
|
+
* @param filePath - Absolute or relative path to the transcript file.
|
|
983
|
+
* @param options - Optional parser flags for verbose diagnostics.
|
|
984
|
+
* @returns Parsed transcript messages, warnings, and metadata.
|
|
985
|
+
*/
|
|
986
|
+
async parseFile(filePath, options) {
|
|
987
|
+
const raw = await readTranscriptFileStrict(filePath);
|
|
988
|
+
const verbose = options?.verbose === true;
|
|
989
|
+
const state = createParseState();
|
|
990
|
+
const transcriptHash = createHash("sha256").update(raw).digest("hex");
|
|
991
|
+
const diagnostics = [];
|
|
992
|
+
const jsonlResult = parseJsonlLines(raw, (record, lineNumber) => {
|
|
993
|
+
const outcome = handleRecord(state, record);
|
|
994
|
+
if (outcome === "structurally_invalid") {
|
|
995
|
+
diagnostics.push({
|
|
996
|
+
kind: "structurally_invalid_record",
|
|
997
|
+
lineNumber,
|
|
998
|
+
message: `Skipped structurally invalid transcript record on line ${lineNumber}`
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
diagnostics.push(...jsonlResult.diagnostics.map(toTranscriptDiagnostic));
|
|
1003
|
+
state.warnings.push(...diagnostics.map(formatTranscriptDiagnosticWarning));
|
|
1004
|
+
if (!state.surfaceDetected && state.firstUserRawText) {
|
|
1005
|
+
setDetectedSurface(state, inferSurfaceFromContent(state.firstUserRawText));
|
|
1006
|
+
}
|
|
1007
|
+
const fallbackTimestamp = state.messages.length > 0 ? await applyMessageTimestampFallbacks(filePath, state.messages, { sessionTimestamp: state.sessionTimestamp }) : await resolveTimestampFallback(filePath, state.sessionTimestamp);
|
|
1008
|
+
if (verbose) {
|
|
1009
|
+
state.warnings.push(buildFilterWarning(state.stats));
|
|
1010
|
+
}
|
|
1011
|
+
const startedAt = state.sessionTimestamp ?? state.messages[0]?.timestamp ?? fallbackTimestamp;
|
|
1012
|
+
const endedAt = state.messages[state.messages.length - 1]?.timestamp ?? state.sessionTimestamp ?? fallbackTimestamp;
|
|
1013
|
+
const stableSessionId = state.sessionId ?? deriveOpenClawSessionIdFromFilePath(filePath);
|
|
1014
|
+
return {
|
|
1015
|
+
messages: state.messages,
|
|
1016
|
+
warnings: state.warnings,
|
|
1017
|
+
metadata: {
|
|
1018
|
+
sessionId: state.sessionId,
|
|
1019
|
+
sessionLabel: state.sessionLabel,
|
|
1020
|
+
startedAt,
|
|
1021
|
+
endedAt,
|
|
1022
|
+
messageCount: state.messages.length,
|
|
1023
|
+
transcriptHash,
|
|
1024
|
+
modelsUsed: state.modelsUsed.length > 0 ? state.modelsUsed : void 0,
|
|
1025
|
+
reconstructedSurface: state.detectedSurface,
|
|
1026
|
+
surfaceReconstructionSource: state.surfaceDetected ? "reconstructed" : "none",
|
|
1027
|
+
sourceIdentity: stableSessionId ? `openclaw-session:${stableSessionId}` : void 0,
|
|
1028
|
+
sourceIdentityKind: stableSessionId ? "openclaw_session" : void 0,
|
|
1029
|
+
workingDirectory: state.workingDirectory
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
var openClawTranscriptParser = new OpenClawTranscriptParser();
|
|
1035
|
+
|
|
1036
|
+
// src/adapters/openclaw/session/tui-lane.ts
|
|
1037
|
+
var TUI_SESSION_KEY_PATTERN = /^agent:([^:]+):([^:]+)$/i;
|
|
1038
|
+
var TUI_UUID_LANE_PATTERN = /^tui[a-z0-9]*-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1039
|
+
var TUI_UUID_SUFFIX_PATTERN = /-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1040
|
+
function parseTuiSessionKey(sessionKey) {
|
|
1041
|
+
const normalizedSessionKey = sessionKey.trim();
|
|
1042
|
+
if (normalizedSessionKey.length === 0) {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
const match = TUI_SESSION_KEY_PATTERN.exec(normalizedSessionKey);
|
|
1046
|
+
if (!match) {
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
const [, agentId, instanceLane] = match;
|
|
1050
|
+
const normalizedAgentId = agentId?.trim();
|
|
1051
|
+
const normalizedInstanceLane = instanceLane?.trim();
|
|
1052
|
+
if (!normalizedAgentId || !normalizedInstanceLane || !normalizedInstanceLane.toLowerCase().startsWith("tui")) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
const stableLane = TUI_UUID_LANE_PATTERN.test(normalizedInstanceLane) ? normalizedInstanceLane.replace(TUI_UUID_SUFFIX_PATTERN, "") : normalizedInstanceLane;
|
|
1056
|
+
return {
|
|
1057
|
+
agentId: normalizedAgentId,
|
|
1058
|
+
stableLane,
|
|
1059
|
+
instanceLane: normalizedInstanceLane
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
export {
|
|
1064
|
+
deriveOpenClawSessionIdFromFilePath,
|
|
1065
|
+
parseJsonObjectLineWithDiagnostics,
|
|
1066
|
+
OpenClawTranscriptParser,
|
|
1067
|
+
openClawTranscriptParser,
|
|
1068
|
+
parseTuiSessionKey
|
|
1069
|
+
};
|