ultracontext 1.4.13 → 1.6.0
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/cli/entry.mjs +9 -3
- package/dist/cli/entry.mjs.map +1 -1
- package/dist/cli/onboarding.mjs +268 -111
- package/dist/cli/onboarding.mjs.map +1 -1
- package/dist/cli/sdk-sync.mjs +199 -939
- package/dist/cli/sdk-sync.mjs.map +1 -1
- package/dist/cli/switch.mjs +168 -0
- package/dist/cli/switch.mjs.map +1 -0
- package/dist/{ctl-CXfNEPN8.mjs → ctl-DTQZxn3N.mjs} +2 -2
- package/dist/{ctl-CXfNEPN8.mjs.map → ctl-DTQZxn3N.mjs.map} +1 -1
- package/dist/hero-art-C03HmDXN.mjs +46 -0
- package/dist/hero-art-C03HmDXN.mjs.map +1 -0
- package/dist/index.d.mts +21 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +25 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{launcher-BMMjzr5k.mjs → launcher-ZylswrpR.mjs} +3 -3
- package/dist/{launcher-BMMjzr5k.mjs.map → launcher-ZylswrpR.mjs.map} +1 -1
- package/dist/{lock-5aJnda81.mjs → lock-BhZX2aF3.mjs} +2 -2
- package/dist/{lock-5aJnda81.mjs.map → lock-BhZX2aF3.mjs.map} +1 -1
- package/dist/onboarding-preferences-Alhblobi.mjs +76 -0
- package/dist/onboarding-preferences-Alhblobi.mjs.map +1 -0
- package/dist/src-Bovo1ukU.mjs +1200 -0
- package/dist/src-Bovo1ukU.mjs.map +1 -0
- package/dist/{tui-DZ1SDOH2.mjs → tui-DLEjew3K.mjs} +334 -115
- package/dist/tui-DLEjew3K.mjs.map +1 -0
- package/dist/utils-BTfShW0g.mjs +36 -0
- package/dist/utils-BTfShW0g.mjs.map +1 -0
- package/dist/{utils-CmuIYHtm.mjs → utils-D9CKnbke.mjs} +26 -34
- package/dist/utils-D9CKnbke.mjs.map +1 -0
- package/lib/register-skills.mjs +96 -0
- package/package.json +8 -3
- package/plugin/.claude-plugin/plugin.json +6 -0
- package/plugin/README.md +112 -0
- package/plugin/marketplace.json +17 -0
- package/plugin/skills/switch/SKILL.md +27 -0
- package/postinstall.mjs +35 -2
- package/dist/Spinner-CwBjkXHv.mjs +0 -153
- package/dist/Spinner-CwBjkXHv.mjs.map +0 -1
- package/dist/tui-DZ1SDOH2.mjs.map +0 -1
- package/dist/utils-CmuIYHtm.mjs.map +0 -1
|
@@ -0,0 +1,1200 @@
|
|
|
1
|
+
import { a as extractSessionIdFromPath, c as normalizeRole, d as safeJsonParse, f as stripIDEContextTags, i as expandHome, l as normalizeWhitespace, m as truncateString, n as claudeProjectDirName, o as firstMessageTimestamp, p as toMessage, r as coerceMessageText, s as isSafeCwd, t as asIso, u as preserveText } from "./utils-D9CKnbke.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import fs$1 from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) {
|
|
12
|
+
__defProp(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (!no_symbols) {
|
|
18
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region ../../packages/parsers/src/agents/claude.mjs
|
|
25
|
+
function formatToolUse(item) {
|
|
26
|
+
const name = (item.name ?? "unknown").toLowerCase();
|
|
27
|
+
const input = item.input ?? {};
|
|
28
|
+
const filePath = input.file_path ?? input.path ?? "";
|
|
29
|
+
if (name === "write") return `[Write] ${filePath}\n${preserveText(input.content ?? input.file_text ?? "")}`;
|
|
30
|
+
if (name === "edit") {
|
|
31
|
+
const parts = [`[Edit] ${filePath}`];
|
|
32
|
+
if (input.old_string) parts.push(`- ${preserveText(input.old_string)}`);
|
|
33
|
+
if (input.new_string) parts.push(`+ ${preserveText(input.new_string)}`);
|
|
34
|
+
return parts.join("\n");
|
|
35
|
+
}
|
|
36
|
+
if (name === "read") return `[Read] ${filePath}`;
|
|
37
|
+
if (name === "bash") return `[Bash] ${preserveText(input.command ?? "")}`;
|
|
38
|
+
if (name === "grep" || name === "glob") {
|
|
39
|
+
const loc = filePath ? ` in ${filePath}` : "";
|
|
40
|
+
return `[${item.name}] ${input.pattern ?? ""}${loc}`;
|
|
41
|
+
}
|
|
42
|
+
const compact = JSON.stringify(input);
|
|
43
|
+
return `[${item.name ?? name}] ${compact.length > 500 ? compact.slice(0, 500) + "..." : compact}`;
|
|
44
|
+
}
|
|
45
|
+
function formatToolResult(item) {
|
|
46
|
+
const content = item.content ?? "";
|
|
47
|
+
if (typeof content === "string") {
|
|
48
|
+
const text = preserveText(content);
|
|
49
|
+
return text ? `[result] ${truncateString(text, 1e3)}` : "[result] ok";
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(content)) {
|
|
52
|
+
const text = content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => preserveText(c.text)).filter(Boolean).join("\n");
|
|
53
|
+
return text ? `[result] ${truncateString(text, 1e3)}` : "[result] ok";
|
|
54
|
+
}
|
|
55
|
+
return "[result] ok";
|
|
56
|
+
}
|
|
57
|
+
function extractClaudeTextContent(content) {
|
|
58
|
+
if (!content) return "";
|
|
59
|
+
if (typeof content === "string") return preserveText(content);
|
|
60
|
+
if (Array.isArray(content)) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
for (const item of content) {
|
|
63
|
+
if (!item || typeof item !== "object") continue;
|
|
64
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
65
|
+
const chunk = preserveText(item.text);
|
|
66
|
+
if (chunk) parts.push(chunk);
|
|
67
|
+
}
|
|
68
|
+
if (item.type === "thinking" && typeof item.thinking === "string") {
|
|
69
|
+
const chunk = preserveText(item.thinking);
|
|
70
|
+
if (chunk) parts.push(`[thinking] ${chunk}`);
|
|
71
|
+
}
|
|
72
|
+
if (item.type === "tool_use") parts.push(formatToolUse(item));
|
|
73
|
+
if (item.type === "tool_result") parts.push(formatToolResult(item));
|
|
74
|
+
}
|
|
75
|
+
return parts.join("\n\n");
|
|
76
|
+
}
|
|
77
|
+
if (typeof content === "object") {
|
|
78
|
+
if (typeof content.text === "string") return preserveText(content.text);
|
|
79
|
+
if (typeof content.content === "string") return preserveText(content.content);
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
function parseClaudeCodeLine({ line, filePath }) {
|
|
84
|
+
const parsed = safeJsonParse(line);
|
|
85
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
86
|
+
const type = String(parsed.type ?? "").toLowerCase();
|
|
87
|
+
const sessionId = parsed.sessionId ?? parsed.session_id ?? parsed.payload?.sessionId ?? parsed.payload?.session_id ?? extractSessionIdFromPath(filePath);
|
|
88
|
+
const timestamp = parsed.timestamp ?? parsed.ts ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
89
|
+
if (type === "summary") {
|
|
90
|
+
const summary = normalizeWhitespace(parsed.summary);
|
|
91
|
+
if (!summary) return null;
|
|
92
|
+
return {
|
|
93
|
+
sessionId,
|
|
94
|
+
eventType: "claude.summary",
|
|
95
|
+
kind: "system",
|
|
96
|
+
timestamp,
|
|
97
|
+
message: toMessage(summary),
|
|
98
|
+
raw: parsed
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (type === "file-history-snapshot") {
|
|
102
|
+
const trackedFiles = Object.keys(parsed.snapshot?.trackedFileBackups ?? {}).length;
|
|
103
|
+
return {
|
|
104
|
+
sessionId,
|
|
105
|
+
eventType: "claude.file_snapshot",
|
|
106
|
+
kind: "system",
|
|
107
|
+
timestamp,
|
|
108
|
+
message: toMessage(`${parsed.isSnapshotUpdate ? "File snapshot update" : "File snapshot"}: ${trackedFiles} tracked files`),
|
|
109
|
+
raw: parsed
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (type === "system") {
|
|
113
|
+
const subtype = parsed.subtype ?? "unknown";
|
|
114
|
+
let message;
|
|
115
|
+
if (subtype === "local_command") message = parsed.content ? preserveText(parsed.content) : "Local command executed";
|
|
116
|
+
else if (subtype === "stop_hook_summary") message = `Hook summary: ${parsed.hookCount ?? 0} hooks`;
|
|
117
|
+
else if (subtype === "turn_duration") message = `Turn completed in ${parsed.durationMs ?? 0}ms (${parsed.messageCount ?? 0} messages)`;
|
|
118
|
+
else message = `System event: ${subtype}`;
|
|
119
|
+
if (!message) return null;
|
|
120
|
+
return {
|
|
121
|
+
sessionId,
|
|
122
|
+
eventType: `claude.system.${subtype}`,
|
|
123
|
+
kind: "system",
|
|
124
|
+
timestamp,
|
|
125
|
+
message: toMessage(message),
|
|
126
|
+
raw: parsed
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (type === "attachment") {
|
|
130
|
+
const attachmentType = parsed.attachment?.type ?? "unknown";
|
|
131
|
+
const parts = [];
|
|
132
|
+
const added = parsed.attachment?.addedNames;
|
|
133
|
+
const removed = parsed.attachment?.removedNames;
|
|
134
|
+
if (Array.isArray(added) && added.length) parts.push(`added: ${added.join(", ")}`);
|
|
135
|
+
if (Array.isArray(removed) && removed.length) parts.push(`removed: ${removed.join(", ")}`);
|
|
136
|
+
const detail = parts.length ? ` (${parts.join("; ")})` : "";
|
|
137
|
+
return {
|
|
138
|
+
sessionId,
|
|
139
|
+
eventType: `claude.attachment.${attachmentType}`,
|
|
140
|
+
kind: "system",
|
|
141
|
+
timestamp,
|
|
142
|
+
message: toMessage(`Attachment: ${attachmentType}${detail}`),
|
|
143
|
+
raw: parsed
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (type === "progress") {
|
|
147
|
+
const data = parsed.data ?? {};
|
|
148
|
+
const subtype = data.type ?? "unknown";
|
|
149
|
+
const label = data.hookName ?? subtype;
|
|
150
|
+
return {
|
|
151
|
+
sessionId,
|
|
152
|
+
eventType: `claude.progress.${subtype}`,
|
|
153
|
+
kind: "system",
|
|
154
|
+
timestamp,
|
|
155
|
+
message: toMessage(`Progress: ${subtype} (${label})`),
|
|
156
|
+
raw: parsed
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (type === "queue-operation") return {
|
|
160
|
+
sessionId,
|
|
161
|
+
eventType: "claude.queue_operation",
|
|
162
|
+
kind: "system",
|
|
163
|
+
timestamp,
|
|
164
|
+
message: toMessage(`Queue ${parsed.operation ?? "unknown"}${parsed.content ? `: ${parsed.content}` : ""}`),
|
|
165
|
+
raw: parsed
|
|
166
|
+
};
|
|
167
|
+
if (type === "custom-title") return {
|
|
168
|
+
sessionId,
|
|
169
|
+
eventType: "claude.custom_title",
|
|
170
|
+
kind: "system",
|
|
171
|
+
timestamp,
|
|
172
|
+
message: toMessage(`Custom title: ${parsed.customTitle ?? "unknown"}`),
|
|
173
|
+
raw: parsed
|
|
174
|
+
};
|
|
175
|
+
if (type === "agent-name") return {
|
|
176
|
+
sessionId,
|
|
177
|
+
eventType: "claude.agent_name",
|
|
178
|
+
kind: "system",
|
|
179
|
+
timestamp,
|
|
180
|
+
message: toMessage(`Agent name: ${parsed.agentName ?? "unknown"}`),
|
|
181
|
+
raw: parsed
|
|
182
|
+
};
|
|
183
|
+
if (type === "pr-link") return {
|
|
184
|
+
sessionId,
|
|
185
|
+
eventType: "claude.pr_link",
|
|
186
|
+
kind: "system",
|
|
187
|
+
timestamp,
|
|
188
|
+
message: toMessage(`PR #${parsed.prNumber ?? 0}: ${parsed.prUrl ?? ""}`),
|
|
189
|
+
raw: parsed
|
|
190
|
+
};
|
|
191
|
+
if (type === "permission-mode") return {
|
|
192
|
+
sessionId,
|
|
193
|
+
eventType: "claude.permission_mode",
|
|
194
|
+
kind: "system",
|
|
195
|
+
timestamp,
|
|
196
|
+
message: toMessage(`Permission mode: ${parsed.permissionMode ?? "unknown"}`),
|
|
197
|
+
raw: parsed
|
|
198
|
+
};
|
|
199
|
+
if (type === "last-prompt") {
|
|
200
|
+
const prompt = parsed.lastPrompt ?? "";
|
|
201
|
+
if (!prompt) return null;
|
|
202
|
+
return {
|
|
203
|
+
sessionId,
|
|
204
|
+
eventType: "claude.last_prompt",
|
|
205
|
+
kind: "system",
|
|
206
|
+
timestamp,
|
|
207
|
+
message: toMessage(prompt),
|
|
208
|
+
raw: parsed
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (type === "ai-title") return {
|
|
212
|
+
sessionId,
|
|
213
|
+
eventType: "claude.ai_title",
|
|
214
|
+
kind: "system",
|
|
215
|
+
timestamp,
|
|
216
|
+
message: toMessage(`AI title: ${parsed.aiTitle ?? "unknown"}`),
|
|
217
|
+
raw: parsed
|
|
218
|
+
};
|
|
219
|
+
if (type === "task-summary") return {
|
|
220
|
+
sessionId,
|
|
221
|
+
eventType: "claude.task_summary",
|
|
222
|
+
kind: "system",
|
|
223
|
+
timestamp,
|
|
224
|
+
message: toMessage(parsed.summary ?? "[task summary]"),
|
|
225
|
+
raw: parsed
|
|
226
|
+
};
|
|
227
|
+
if (type === "tag") return {
|
|
228
|
+
sessionId,
|
|
229
|
+
eventType: "claude.tag",
|
|
230
|
+
kind: "system",
|
|
231
|
+
timestamp,
|
|
232
|
+
message: toMessage(`Tag: ${parsed.tag ?? ""}`),
|
|
233
|
+
raw: parsed
|
|
234
|
+
};
|
|
235
|
+
if (type === "agent-color") return {
|
|
236
|
+
sessionId,
|
|
237
|
+
eventType: "claude.agent_color",
|
|
238
|
+
kind: "system",
|
|
239
|
+
timestamp,
|
|
240
|
+
message: toMessage(`Agent color: ${parsed.agentColor ?? "unknown"}`),
|
|
241
|
+
raw: parsed
|
|
242
|
+
};
|
|
243
|
+
if (type === "agent-setting") return {
|
|
244
|
+
sessionId,
|
|
245
|
+
eventType: "claude.agent_setting",
|
|
246
|
+
kind: "system",
|
|
247
|
+
timestamp,
|
|
248
|
+
message: toMessage(`Agent setting: ${parsed.agentSetting ?? "unknown"}`),
|
|
249
|
+
raw: parsed
|
|
250
|
+
};
|
|
251
|
+
if (type === "mode") return {
|
|
252
|
+
sessionId,
|
|
253
|
+
eventType: "claude.mode",
|
|
254
|
+
kind: "system",
|
|
255
|
+
timestamp,
|
|
256
|
+
message: toMessage(`Mode: ${parsed.mode ?? "unknown"}`),
|
|
257
|
+
raw: parsed
|
|
258
|
+
};
|
|
259
|
+
if (type === "worktree-state") {
|
|
260
|
+
const ws = parsed.worktreeSession;
|
|
261
|
+
return {
|
|
262
|
+
sessionId,
|
|
263
|
+
eventType: "claude.worktree_state",
|
|
264
|
+
kind: "system",
|
|
265
|
+
timestamp,
|
|
266
|
+
message: toMessage(`Worktree: ${ws ? `entered ${ws.worktreePath ?? ""}` : "exited"}`),
|
|
267
|
+
raw: parsed
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (type === "attribution-snapshot") {
|
|
271
|
+
const fileCount = Object.keys(parsed.fileStates ?? {}).length;
|
|
272
|
+
return {
|
|
273
|
+
sessionId,
|
|
274
|
+
eventType: "claude.attribution_snapshot",
|
|
275
|
+
kind: "system",
|
|
276
|
+
timestamp,
|
|
277
|
+
message: toMessage(`Attribution snapshot: ${fileCount} files`),
|
|
278
|
+
raw: parsed
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (type === "content-replacement") return {
|
|
282
|
+
sessionId,
|
|
283
|
+
eventType: "claude.content_replacement",
|
|
284
|
+
kind: "system",
|
|
285
|
+
timestamp,
|
|
286
|
+
message: toMessage(`Content replacement: ${parsed.replacements?.length ?? 0} blocks`),
|
|
287
|
+
raw: parsed
|
|
288
|
+
};
|
|
289
|
+
if (type === "speculation-accept") return {
|
|
290
|
+
sessionId,
|
|
291
|
+
eventType: "claude.speculation_accept",
|
|
292
|
+
kind: "system",
|
|
293
|
+
timestamp,
|
|
294
|
+
message: toMessage(`Speculation accepted: saved ${parsed.timeSavedMs ?? 0}ms`),
|
|
295
|
+
raw: parsed
|
|
296
|
+
};
|
|
297
|
+
if (type === "marble-origami-commit") return {
|
|
298
|
+
sessionId,
|
|
299
|
+
eventType: "claude.context_collapse_commit",
|
|
300
|
+
kind: "system",
|
|
301
|
+
timestamp,
|
|
302
|
+
message: toMessage(parsed.summary ?? `Context collapse: ${parsed.collapseId ?? ""}`),
|
|
303
|
+
raw: parsed
|
|
304
|
+
};
|
|
305
|
+
if (type === "marble-origami-snapshot") return {
|
|
306
|
+
sessionId,
|
|
307
|
+
eventType: "claude.context_collapse_snapshot",
|
|
308
|
+
kind: "system",
|
|
309
|
+
timestamp,
|
|
310
|
+
message: toMessage(`Context collapse snapshot: ${parsed.staged?.length ?? 0} staged`),
|
|
311
|
+
raw: parsed
|
|
312
|
+
};
|
|
313
|
+
if (type !== "user" && type !== "assistant") return null;
|
|
314
|
+
const rawContent = parsed.message?.content ?? parsed.message ?? parsed.content ?? parsed.payload?.content ?? "";
|
|
315
|
+
let message = extractClaudeTextContent(rawContent);
|
|
316
|
+
if (!message) {
|
|
317
|
+
if (!(Array.isArray(rawContent) && rawContent.some((c) => c?.type === "thinking"))) return null;
|
|
318
|
+
message = "[thinking]";
|
|
319
|
+
}
|
|
320
|
+
const roleHint = parsed.message?.role ?? type;
|
|
321
|
+
return {
|
|
322
|
+
sessionId,
|
|
323
|
+
eventType: `claude.${type}`,
|
|
324
|
+
kind: normalizeRole(roleHint, type === "user" ? "user" : "assistant"),
|
|
325
|
+
timestamp,
|
|
326
|
+
message: toMessage(message),
|
|
327
|
+
raw: parsed
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region ../../packages/parsers/src/agents/codex.mjs
|
|
333
|
+
function parseCodexLine({ line, filePath }) {
|
|
334
|
+
const parsed = safeJsonParse(line);
|
|
335
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
336
|
+
const payload = parsed.payload ?? {};
|
|
337
|
+
const sessionId = payload.session_id ?? payload.id ?? parsed.session_id ?? extractSessionIdFromPath(filePath);
|
|
338
|
+
if (parsed.type === "event_msg") {
|
|
339
|
+
const eventType = payload.type ?? "unknown";
|
|
340
|
+
if (![
|
|
341
|
+
"user_message",
|
|
342
|
+
"agent_message",
|
|
343
|
+
"task_started",
|
|
344
|
+
"task_complete",
|
|
345
|
+
"token_count",
|
|
346
|
+
"agent_reasoning",
|
|
347
|
+
"turn_aborted",
|
|
348
|
+
"context_compacted"
|
|
349
|
+
].includes(eventType)) return null;
|
|
350
|
+
const kind = eventType === "user_message" ? "user" : eventType === "agent_message" ? "assistant" : "system";
|
|
351
|
+
const message = eventType === "token_count" ? "Token count update" : eventType === "agent_reasoning" ? payload.text : eventType === "turn_aborted" ? payload.reason ? `Turn aborted: ${payload.reason}` : "Turn aborted" : eventType === "context_compacted" ? "Context compacted" : payload.message ?? payload.last_agent_message ?? `${eventType}${payload.turn_id ? ` (${payload.turn_id})` : ""}`;
|
|
352
|
+
return {
|
|
353
|
+
sessionId,
|
|
354
|
+
eventType: `event_msg.${eventType}`,
|
|
355
|
+
kind,
|
|
356
|
+
timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
357
|
+
message: toMessage(message),
|
|
358
|
+
raw: parsed
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (parsed.type === "session_meta") return {
|
|
362
|
+
sessionId,
|
|
363
|
+
eventType: "session_meta",
|
|
364
|
+
kind: "system",
|
|
365
|
+
timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
366
|
+
message: `Session started in ${payload.cwd ?? "unknown cwd"}`,
|
|
367
|
+
raw: parsed
|
|
368
|
+
};
|
|
369
|
+
if (parsed.type === "response_item") {
|
|
370
|
+
const subtype = payload.type;
|
|
371
|
+
const ts = parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
372
|
+
if (subtype === "message") {
|
|
373
|
+
const text = (payload.content ?? []).map((c) => c.text ?? "").filter(Boolean).join("\n");
|
|
374
|
+
return {
|
|
375
|
+
sessionId,
|
|
376
|
+
eventType: "response_item.message",
|
|
377
|
+
kind: {
|
|
378
|
+
developer: "system",
|
|
379
|
+
assistant: "assistant",
|
|
380
|
+
user: "user"
|
|
381
|
+
}[payload.role] ?? normalizeRole(payload.role),
|
|
382
|
+
timestamp: ts,
|
|
383
|
+
message: toMessage(text || `[${payload.role ?? "unknown"} message]`),
|
|
384
|
+
raw: parsed
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (subtype === "reasoning") return {
|
|
388
|
+
sessionId,
|
|
389
|
+
eventType: "response_item.reasoning",
|
|
390
|
+
kind: "system",
|
|
391
|
+
timestamp: ts,
|
|
392
|
+
message: toMessage((payload.summary ?? []).map((s) => s.text ?? "").filter(Boolean).join("\n") || "[reasoning]"),
|
|
393
|
+
raw: parsed
|
|
394
|
+
};
|
|
395
|
+
if (subtype === "function_call") return {
|
|
396
|
+
sessionId,
|
|
397
|
+
eventType: "response_item.function_call",
|
|
398
|
+
kind: "system",
|
|
399
|
+
timestamp: ts,
|
|
400
|
+
message: toMessage(`[${payload.name ?? "unknown"}] ${payload.arguments ?? ""}`),
|
|
401
|
+
raw: parsed
|
|
402
|
+
};
|
|
403
|
+
if (subtype === "function_call_output") return {
|
|
404
|
+
sessionId,
|
|
405
|
+
eventType: "response_item.function_call_output",
|
|
406
|
+
kind: "system",
|
|
407
|
+
timestamp: ts,
|
|
408
|
+
message: toMessage(payload.output ?? `[output ${payload.call_id ?? ""}]`),
|
|
409
|
+
raw: parsed
|
|
410
|
+
};
|
|
411
|
+
if (subtype === "web_search_call") {
|
|
412
|
+
const query = payload.action?.query;
|
|
413
|
+
return {
|
|
414
|
+
sessionId,
|
|
415
|
+
eventType: "response_item.web_search_call",
|
|
416
|
+
kind: "system",
|
|
417
|
+
timestamp: ts,
|
|
418
|
+
message: query ? `[web_search] ${query}` : "[web_search]",
|
|
419
|
+
raw: parsed
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (subtype === "custom_tool_call") return {
|
|
423
|
+
sessionId,
|
|
424
|
+
eventType: "response_item.custom_tool_call",
|
|
425
|
+
kind: "system",
|
|
426
|
+
timestamp: ts,
|
|
427
|
+
message: toMessage(`[${payload.name ?? "unknown"}] ${payload.input ?? ""}`),
|
|
428
|
+
raw: parsed
|
|
429
|
+
};
|
|
430
|
+
if (subtype === "custom_tool_call_output") return {
|
|
431
|
+
sessionId,
|
|
432
|
+
eventType: "response_item.custom_tool_call_output",
|
|
433
|
+
kind: "system",
|
|
434
|
+
timestamp: ts,
|
|
435
|
+
message: toMessage(payload.output ?? `[output ${payload.call_id ?? ""}]`),
|
|
436
|
+
raw: parsed
|
|
437
|
+
};
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
if (parsed.type === "compacted") return {
|
|
441
|
+
sessionId,
|
|
442
|
+
eventType: "compacted",
|
|
443
|
+
kind: "system",
|
|
444
|
+
timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
445
|
+
message: "Session compacted",
|
|
446
|
+
raw: parsed
|
|
447
|
+
};
|
|
448
|
+
if (parsed.type === "turn_context") return {
|
|
449
|
+
sessionId,
|
|
450
|
+
eventType: "turn_context",
|
|
451
|
+
kind: "system",
|
|
452
|
+
timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
453
|
+
message: `Turn context: model=${payload.model}, policy=${payload.approval_policy}`,
|
|
454
|
+
raw: parsed
|
|
455
|
+
};
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region ../../packages/parsers/src/agents/openclaw.mjs
|
|
461
|
+
function extractOpenClawTextContent(content) {
|
|
462
|
+
if (!content) return "";
|
|
463
|
+
if (typeof content === "string") return normalizeWhitespace(content);
|
|
464
|
+
if (Array.isArray(content)) {
|
|
465
|
+
const textParts = [];
|
|
466
|
+
for (const item of content) {
|
|
467
|
+
if (!item || typeof item !== "object") continue;
|
|
468
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
469
|
+
const chunk = normalizeWhitespace(item.text);
|
|
470
|
+
if (chunk) textParts.push(chunk);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return textParts.join("\n");
|
|
474
|
+
}
|
|
475
|
+
if (typeof content === "object" && typeof content.text === "string") return normalizeWhitespace(content.text);
|
|
476
|
+
return "";
|
|
477
|
+
}
|
|
478
|
+
function extractOpenClawToolCalls(content) {
|
|
479
|
+
if (!Array.isArray(content)) return [];
|
|
480
|
+
const names = [];
|
|
481
|
+
for (const item of content) {
|
|
482
|
+
if (!item || typeof item !== "object" || item.type !== "toolCall") continue;
|
|
483
|
+
const name = normalizeWhitespace(item.name);
|
|
484
|
+
if (name) names.push(name);
|
|
485
|
+
}
|
|
486
|
+
return names;
|
|
487
|
+
}
|
|
488
|
+
function buildOpenClawRaw(parsed) {
|
|
489
|
+
const raw = {
|
|
490
|
+
type: parsed.type,
|
|
491
|
+
id: parsed.id,
|
|
492
|
+
parentId: parsed.parentId,
|
|
493
|
+
timestamp: parsed.timestamp
|
|
494
|
+
};
|
|
495
|
+
if (parsed.type === "session") {
|
|
496
|
+
raw.session = {
|
|
497
|
+
id: parsed.id,
|
|
498
|
+
version: parsed.version,
|
|
499
|
+
cwd: parsed.cwd,
|
|
500
|
+
parentSession: parsed.parentSession
|
|
501
|
+
};
|
|
502
|
+
return raw;
|
|
503
|
+
}
|
|
504
|
+
if (parsed.type === "custom") {
|
|
505
|
+
raw.customType = parsed.customType;
|
|
506
|
+
if (parsed.customType === "model-snapshot" && parsed.data && typeof parsed.data === "object") raw.data = {
|
|
507
|
+
provider: parsed.data.provider,
|
|
508
|
+
modelApi: parsed.data.modelApi,
|
|
509
|
+
modelId: parsed.data.modelId,
|
|
510
|
+
timestamp: parsed.data.timestamp
|
|
511
|
+
};
|
|
512
|
+
return raw;
|
|
513
|
+
}
|
|
514
|
+
if (parsed.message && typeof parsed.message === "object") {
|
|
515
|
+
const contentTypes = Array.isArray(parsed.message.content) ? parsed.message.content.filter((item) => item && typeof item === "object").map((item) => String(item.type ?? "unknown")).slice(0, 12) : [];
|
|
516
|
+
raw.message = {
|
|
517
|
+
role: parsed.message.role,
|
|
518
|
+
stopReason: parsed.message.stopReason,
|
|
519
|
+
toolName: parsed.message.toolName,
|
|
520
|
+
toolCallId: parsed.message.toolCallId,
|
|
521
|
+
isError: parsed.message.isError,
|
|
522
|
+
contentTypes
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (parsed.type === "compaction") raw.compaction = {
|
|
526
|
+
firstKeptEntryId: parsed.firstKeptEntryId,
|
|
527
|
+
tokensBefore: parsed.tokensBefore
|
|
528
|
+
};
|
|
529
|
+
else if (parsed.type === "branch_summary") raw.branchSummary = {
|
|
530
|
+
firstKeptEntryId: parsed.firstKeptEntryId,
|
|
531
|
+
summary: typeof parsed.summary === "string" ? truncateString(parsed.summary, 350) : ""
|
|
532
|
+
};
|
|
533
|
+
return raw;
|
|
534
|
+
}
|
|
535
|
+
function parseOpenClawLine({ line, filePath }) {
|
|
536
|
+
const parsed = safeJsonParse(line);
|
|
537
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
538
|
+
const type = String(parsed.type ?? "").toLowerCase();
|
|
539
|
+
const sessionId = parsed.session_id ?? parsed.sessionId ?? parsed.message?.session_id ?? parsed.message?.sessionId ?? extractSessionIdFromPath(filePath);
|
|
540
|
+
const timestamp = parsed.timestamp ?? parsed.message?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
541
|
+
if (type === "session") return {
|
|
542
|
+
sessionId,
|
|
543
|
+
eventType: "openclaw.session",
|
|
544
|
+
kind: "system",
|
|
545
|
+
timestamp,
|
|
546
|
+
message: toMessage(`Session started in ${parsed.cwd ?? "unknown cwd"}`),
|
|
547
|
+
raw: buildOpenClawRaw(parsed)
|
|
548
|
+
};
|
|
549
|
+
if (type === "custom") {
|
|
550
|
+
const customType = normalizeWhitespace(parsed.customType || "custom");
|
|
551
|
+
if (customType === "openclaw.cache-ttl") return null;
|
|
552
|
+
let message = `Custom event: ${customType}`;
|
|
553
|
+
if (customType === "model-snapshot" && parsed.data && typeof parsed.data === "object") {
|
|
554
|
+
const provider = normalizeWhitespace(parsed.data.provider || "");
|
|
555
|
+
const modelId = normalizeWhitespace(parsed.data.modelId || "");
|
|
556
|
+
const modelApi = normalizeWhitespace(parsed.data.modelApi || "");
|
|
557
|
+
const details = [provider, modelId].filter(Boolean).join("/");
|
|
558
|
+
message = `Model snapshot${details ? `: ${details}` : ""}${modelApi ? ` (${modelApi})` : ""}`;
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
sessionId,
|
|
562
|
+
eventType: `openclaw.custom.${customType || "custom"}`,
|
|
563
|
+
kind: "system",
|
|
564
|
+
timestamp,
|
|
565
|
+
message: toMessage(message),
|
|
566
|
+
raw: buildOpenClawRaw(parsed)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (type === "compaction") return {
|
|
570
|
+
sessionId,
|
|
571
|
+
eventType: "openclaw.compaction",
|
|
572
|
+
kind: "system",
|
|
573
|
+
timestamp,
|
|
574
|
+
message: toMessage("Session compaction summary updated"),
|
|
575
|
+
raw: buildOpenClawRaw(parsed)
|
|
576
|
+
};
|
|
577
|
+
if (type === "branch_summary") return {
|
|
578
|
+
sessionId,
|
|
579
|
+
eventType: "openclaw.branch_summary",
|
|
580
|
+
kind: "system",
|
|
581
|
+
timestamp,
|
|
582
|
+
message: toMessage(normalizeWhitespace(parsed.summary || "") || "Branch summary updated"),
|
|
583
|
+
raw: buildOpenClawRaw(parsed)
|
|
584
|
+
};
|
|
585
|
+
if (type !== "message" && type !== "custom_message") return null;
|
|
586
|
+
const eventMessage = parsed.message ?? {};
|
|
587
|
+
const role = String(eventMessage.role ?? "").toLowerCase();
|
|
588
|
+
if (role === "user" || role === "assistant") {
|
|
589
|
+
const text = extractOpenClawTextContent(eventMessage.content);
|
|
590
|
+
if (text) return {
|
|
591
|
+
sessionId,
|
|
592
|
+
eventType: `openclaw.${role}`,
|
|
593
|
+
kind: role === "user" ? "user" : "assistant",
|
|
594
|
+
timestamp,
|
|
595
|
+
message: toMessage(text),
|
|
596
|
+
raw: buildOpenClawRaw(parsed)
|
|
597
|
+
};
|
|
598
|
+
if (role === "assistant") {
|
|
599
|
+
const toolCalls = extractOpenClawToolCalls(eventMessage.content);
|
|
600
|
+
if (toolCalls.length > 0) return {
|
|
601
|
+
sessionId,
|
|
602
|
+
eventType: "openclaw.assistant.tool_use",
|
|
603
|
+
kind: "system",
|
|
604
|
+
timestamp,
|
|
605
|
+
message: toMessage(`Assistant requested tools: ${toolCalls.slice(0, 5).join(", ")}${toolCalls.length > 5 ? ` (+${toolCalls.length - 5})` : ""}`),
|
|
606
|
+
raw: buildOpenClawRaw(parsed)
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
if (role === "toolresult") {
|
|
612
|
+
const toolName = normalizeWhitespace(eventMessage.toolName || "");
|
|
613
|
+
const isError = Boolean(eventMessage.isError);
|
|
614
|
+
let message = `Tool result${toolName ? `: ${toolName}` : ""} (${isError ? "error" : "ok"})`;
|
|
615
|
+
const text = extractOpenClawTextContent(eventMessage.content);
|
|
616
|
+
if (text) message = `${message} ${truncateString(text, 320)}`;
|
|
617
|
+
return {
|
|
618
|
+
sessionId,
|
|
619
|
+
eventType: "openclaw.tool_result",
|
|
620
|
+
kind: "system",
|
|
621
|
+
timestamp,
|
|
622
|
+
message: toMessage(message),
|
|
623
|
+
raw: buildOpenClawRaw(parsed)
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region ../../packages/parsers/src/agents/cursor.mjs
|
|
631
|
+
function parseCursorLine({ line, filePath }) {
|
|
632
|
+
const parsed = safeJsonParse(line);
|
|
633
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
634
|
+
if (!parsed.type && parsed.role) parsed.type = parsed.role;
|
|
635
|
+
const result = parseClaudeCodeLine({
|
|
636
|
+
line: JSON.stringify(parsed),
|
|
637
|
+
filePath
|
|
638
|
+
});
|
|
639
|
+
if (!result) return null;
|
|
640
|
+
result.eventType = result.eventType.replace(/^claude\./, "cursor.");
|
|
641
|
+
if (result.kind === "user" && result.message) result.message = stripIDEContextTags(result.message);
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
//#endregion
|
|
646
|
+
//#region ../../packages/parsers/src/agents/gemini.mjs
|
|
647
|
+
const FILE_MOD_TOOLS = [
|
|
648
|
+
"write_file",
|
|
649
|
+
"edit_file",
|
|
650
|
+
"save_file",
|
|
651
|
+
"replace"
|
|
652
|
+
];
|
|
653
|
+
const FILE_PATH_KEYS = [
|
|
654
|
+
"file_path",
|
|
655
|
+
"path",
|
|
656
|
+
"filePath",
|
|
657
|
+
"filename"
|
|
658
|
+
];
|
|
659
|
+
function extractGeminiTextContent(content) {
|
|
660
|
+
if (!content) return "";
|
|
661
|
+
if (typeof content === "string") return preserveText(content);
|
|
662
|
+
if (Array.isArray(content)) {
|
|
663
|
+
const parts = [];
|
|
664
|
+
for (const item of content) {
|
|
665
|
+
if (!item || typeof item !== "object") continue;
|
|
666
|
+
if (typeof item.text === "string") {
|
|
667
|
+
const chunk = preserveText(item.text);
|
|
668
|
+
if (chunk) parts.push(chunk);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return parts.join("\n");
|
|
672
|
+
}
|
|
673
|
+
if (typeof content === "object" && typeof content.text === "string") return preserveText(content.text);
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
function formatToolCall(tc) {
|
|
677
|
+
const name = tc.name ?? "unknown";
|
|
678
|
+
const args = tc.args ?? {};
|
|
679
|
+
let filePath = "";
|
|
680
|
+
for (const key of FILE_PATH_KEYS) if (typeof args[key] === "string" && args[key]) {
|
|
681
|
+
filePath = args[key];
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
if (FILE_MOD_TOOLS.includes(name)) {
|
|
685
|
+
const content = preserveText(args.content ?? args.file_text ?? args.new_content ?? "");
|
|
686
|
+
if (content) return `[${name}] ${filePath}\n${truncateString(content, 500)}`;
|
|
687
|
+
return `[${name}] ${filePath}`;
|
|
688
|
+
}
|
|
689
|
+
const compact = JSON.stringify(args);
|
|
690
|
+
const detail = compact.length > 500 ? compact.slice(0, 500) + "..." : compact;
|
|
691
|
+
return `[${name}]${filePath ? ` ${filePath}` : ""} ${detail}`;
|
|
692
|
+
}
|
|
693
|
+
function extractGeminiSessionId(filePath) {
|
|
694
|
+
const base = path.basename(filePath, ".json");
|
|
695
|
+
const match = base.match(/session-[\d-]+-(.+)$/);
|
|
696
|
+
if (match) return match[1];
|
|
697
|
+
return base || "unknown-session";
|
|
698
|
+
}
|
|
699
|
+
function extractGeminiTimestamp(filePath) {
|
|
700
|
+
const match = path.basename(filePath, ".json").match(/session-(\d{4})(\d{2})(\d{2})/);
|
|
701
|
+
if (match) return `${match[1]}-${match[2]}-${match[3]}T00:00:00.000Z`;
|
|
702
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
703
|
+
}
|
|
704
|
+
function parseGeminiFile({ fileContents, filePath }) {
|
|
705
|
+
const parsed = safeJsonParse(fileContents);
|
|
706
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
707
|
+
const messages = parsed.messages;
|
|
708
|
+
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
709
|
+
const sessionId = extractGeminiSessionId(filePath);
|
|
710
|
+
const fileTimestamp = extractGeminiTimestamp(filePath);
|
|
711
|
+
const events = [];
|
|
712
|
+
for (let i = 0; i < messages.length; i++) {
|
|
713
|
+
const msg = messages[i];
|
|
714
|
+
if (!msg || typeof msg !== "object") continue;
|
|
715
|
+
const type = String(msg.type ?? "").toLowerCase();
|
|
716
|
+
const isUser = type === "user";
|
|
717
|
+
if (!isUser && !(type === "gemini")) continue;
|
|
718
|
+
const text = extractGeminiTextContent(msg.content);
|
|
719
|
+
const toolCallTexts = [];
|
|
720
|
+
if (Array.isArray(msg.toolCalls)) for (const tc of msg.toolCalls) {
|
|
721
|
+
if (!tc || typeof tc !== "object") continue;
|
|
722
|
+
toolCallTexts.push(formatToolCall(tc));
|
|
723
|
+
}
|
|
724
|
+
const parts = [];
|
|
725
|
+
if (text) parts.push(text);
|
|
726
|
+
if (toolCallTexts.length) parts.push(toolCallTexts.join("\n\n"));
|
|
727
|
+
const message = parts.join("\n\n");
|
|
728
|
+
if (!message) continue;
|
|
729
|
+
events.push({
|
|
730
|
+
sessionId,
|
|
731
|
+
eventType: isUser ? "gemini.user" : "gemini.assistant",
|
|
732
|
+
kind: isUser ? "user" : "assistant",
|
|
733
|
+
timestamp: msg.timestamp ?? fileTimestamp,
|
|
734
|
+
message: toMessage(message),
|
|
735
|
+
raw: {
|
|
736
|
+
type: msg.type,
|
|
737
|
+
id: msg.id,
|
|
738
|
+
index: i,
|
|
739
|
+
hasToolCalls: toolCallTexts.length > 0,
|
|
740
|
+
toolCallCount: toolCallTexts.length
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
return events;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region ../../packages/parsers/src/gstack.mjs
|
|
749
|
+
function extractGstackProjectSlug(filePath) {
|
|
750
|
+
return filePath.match(/\.gstack\/projects\/([^/]+)\//)?.[1] ?? "unknown-project";
|
|
751
|
+
}
|
|
752
|
+
function gstackFileType(filePath) {
|
|
753
|
+
const base = filePath.split("/").pop() ?? "";
|
|
754
|
+
if (base === "learnings.jsonl") return "learning";
|
|
755
|
+
if (base === "timeline.jsonl") return "timeline";
|
|
756
|
+
if (base === "resources-shown.jsonl") return "resource";
|
|
757
|
+
if (base.endsWith("-reviews.jsonl")) return "review";
|
|
758
|
+
return "unknown";
|
|
759
|
+
}
|
|
760
|
+
function parseGstackLine({ line, filePath }) {
|
|
761
|
+
const parsed = safeJsonParse(line);
|
|
762
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
763
|
+
const projectSlug = extractGstackProjectSlug(filePath);
|
|
764
|
+
const fileType = gstackFileType(filePath);
|
|
765
|
+
const timestamp = parsed.ts ?? parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
766
|
+
const sessionId = projectSlug;
|
|
767
|
+
if (fileType === "learning") {
|
|
768
|
+
const insight = normalizeWhitespace(parsed.insight);
|
|
769
|
+
if (!insight) return null;
|
|
770
|
+
const skill = parsed.skill ?? "unknown";
|
|
771
|
+
const conf = typeof parsed.confidence === "number" ? ` [${parsed.confidence}/10]` : "";
|
|
772
|
+
return {
|
|
773
|
+
sessionId,
|
|
774
|
+
eventType: `gstack.learning.${parsed.type ?? "insight"}`,
|
|
775
|
+
kind: "system",
|
|
776
|
+
timestamp,
|
|
777
|
+
message: toMessage(`[${skill}]${conf} ${insight}`),
|
|
778
|
+
raw: parsed
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
if (fileType === "timeline") {
|
|
782
|
+
const skill = parsed.skill ?? "unknown";
|
|
783
|
+
const event = parsed.event ?? "unknown";
|
|
784
|
+
const branch = parsed.branch ? ` (${parsed.branch})` : "";
|
|
785
|
+
const outcome = parsed.outcome ? ` → ${parsed.outcome}` : "";
|
|
786
|
+
const duration = parsed.duration_s ? ` ${parsed.duration_s}s` : "";
|
|
787
|
+
return {
|
|
788
|
+
sessionId,
|
|
789
|
+
eventType: `gstack.timeline.${event}`,
|
|
790
|
+
kind: "system",
|
|
791
|
+
timestamp,
|
|
792
|
+
message: toMessage(`${skill} ${event}${branch}${outcome}${duration}`),
|
|
793
|
+
raw: parsed
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
if (fileType === "review") {
|
|
797
|
+
const skill = parsed.skill ?? "review";
|
|
798
|
+
const score = parsed.overall_score != null ? ` score=${parsed.overall_score}` : "";
|
|
799
|
+
return {
|
|
800
|
+
sessionId,
|
|
801
|
+
eventType: "gstack.review",
|
|
802
|
+
kind: "system",
|
|
803
|
+
timestamp,
|
|
804
|
+
message: toMessage(`${skill}${parsed.status ? ` [${parsed.status}]` : ""}${score}`),
|
|
805
|
+
raw: parsed
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (fileType === "resource") {
|
|
809
|
+
const title = normalizeWhitespace(parsed.title);
|
|
810
|
+
const url = parsed.url ?? "";
|
|
811
|
+
if (!title && !url) return null;
|
|
812
|
+
return {
|
|
813
|
+
sessionId,
|
|
814
|
+
eventType: "gstack.resource",
|
|
815
|
+
kind: "system",
|
|
816
|
+
timestamp,
|
|
817
|
+
message: toMessage(title ? `${title} — ${url}` : url),
|
|
818
|
+
raw: parsed
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
//#endregion
|
|
825
|
+
//#region ../../packages/parsers/src/writers/claude.mjs
|
|
826
|
+
function isUuid(value) {
|
|
827
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(String(value ?? "").trim());
|
|
828
|
+
}
|
|
829
|
+
function normalizeSessionUuid(raw) {
|
|
830
|
+
const value = String(raw ?? "").trim();
|
|
831
|
+
if (isUuid(value)) return value;
|
|
832
|
+
return randomUUID();
|
|
833
|
+
}
|
|
834
|
+
function claudeSessionFilePath(sessionId, cwd, baseDir) {
|
|
835
|
+
const root = baseDir || expandHome("~/.claude/projects");
|
|
836
|
+
return path.join(root, claudeProjectDirName(cwd), `${sessionId}.jsonl`);
|
|
837
|
+
}
|
|
838
|
+
async function hasLocalClaudeSession(sessionId, cwd = "", baseDir) {
|
|
839
|
+
const id = String(sessionId ?? "").trim();
|
|
840
|
+
if (!id) return false;
|
|
841
|
+
const preferredPath = claudeSessionFilePath(id, cwd || process.cwd(), baseDir);
|
|
842
|
+
try {
|
|
843
|
+
if ((await fs$1.stat(preferredPath)).isFile()) return true;
|
|
844
|
+
} catch {}
|
|
845
|
+
const fg = (await import("fast-glob")).default;
|
|
846
|
+
const root = baseDir || expandHome("~/.claude/projects");
|
|
847
|
+
return (await fg([path.join(root, `**/*${id}.jsonl`)], {
|
|
848
|
+
onlyFiles: true,
|
|
849
|
+
absolute: true,
|
|
850
|
+
unique: true,
|
|
851
|
+
suppressErrors: true,
|
|
852
|
+
followSymbolicLinks: false
|
|
853
|
+
})).some((filePath) => path.basename(filePath, ".jsonl") === id);
|
|
854
|
+
}
|
|
855
|
+
function isNativeClaudeEntry(raw) {
|
|
856
|
+
return raw && typeof raw === "object" && "type" in raw && ("sessionId" in raw || "uuid" in raw);
|
|
857
|
+
}
|
|
858
|
+
async function writeClaudeSession({ sessionId, cwd, messages, baseDir }) {
|
|
859
|
+
const runCwd = String(cwd || process.cwd());
|
|
860
|
+
const resolvedSessionId = normalizeSessionUuid(sessionId);
|
|
861
|
+
const firstTs = asIso(firstMessageTimestamp(messages));
|
|
862
|
+
const filePath = claudeSessionFilePath(resolvedSessionId, runCwd, baseDir);
|
|
863
|
+
if (await hasLocalClaudeSession(resolvedSessionId, runCwd, baseDir)) return {
|
|
864
|
+
written: false,
|
|
865
|
+
reason: "already_exists",
|
|
866
|
+
filePath,
|
|
867
|
+
sessionId: resolvedSessionId
|
|
868
|
+
};
|
|
869
|
+
try {
|
|
870
|
+
await fs$1.mkdir(path.dirname(filePath), { recursive: true });
|
|
871
|
+
const lines = [];
|
|
872
|
+
let parentUuid = null;
|
|
873
|
+
for (let i = 0; i < (messages?.length ?? 0); i += 1) {
|
|
874
|
+
const message = messages[i];
|
|
875
|
+
const raw = message?.content?.raw;
|
|
876
|
+
if (isNativeClaudeEntry(raw)) {
|
|
877
|
+
const entryUuid = raw.uuid || randomUUID();
|
|
878
|
+
lines.push(JSON.stringify({
|
|
879
|
+
...raw,
|
|
880
|
+
parentUuid,
|
|
881
|
+
sessionId: resolvedSessionId
|
|
882
|
+
}));
|
|
883
|
+
parentUuid = entryUuid;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const normalizedRole = normalizeRole(message?.role);
|
|
887
|
+
const role = normalizedRole === "assistant" ? "assistant" : normalizedRole === "user" ? "user" : "assistant";
|
|
888
|
+
const text = extractMessageText(message, normalizedRole);
|
|
889
|
+
if (!text) continue;
|
|
890
|
+
const ts = asIso(message?.content?.timestamp ?? message?.metadata?.timestamp ?? new Date(new Date(firstTs).getTime() + i * 1e3).toISOString());
|
|
891
|
+
const entryUuid = randomUUID();
|
|
892
|
+
lines.push(JSON.stringify({
|
|
893
|
+
parentUuid,
|
|
894
|
+
isSidechain: false,
|
|
895
|
+
userType: "external",
|
|
896
|
+
cwd: runCwd,
|
|
897
|
+
sessionId: resolvedSessionId,
|
|
898
|
+
version: "adapter",
|
|
899
|
+
gitBranch: "",
|
|
900
|
+
type: role,
|
|
901
|
+
message: {
|
|
902
|
+
role,
|
|
903
|
+
content: [{
|
|
904
|
+
type: "text",
|
|
905
|
+
text
|
|
906
|
+
}]
|
|
907
|
+
},
|
|
908
|
+
timestamp: ts,
|
|
909
|
+
uuid: entryUuid
|
|
910
|
+
}));
|
|
911
|
+
parentUuid = entryUuid;
|
|
912
|
+
}
|
|
913
|
+
if (lines.length === 0) lines.push(JSON.stringify({
|
|
914
|
+
parentUuid: null,
|
|
915
|
+
isSidechain: false,
|
|
916
|
+
userType: "external",
|
|
917
|
+
cwd: runCwd,
|
|
918
|
+
sessionId: resolvedSessionId,
|
|
919
|
+
version: "adapter",
|
|
920
|
+
gitBranch: "",
|
|
921
|
+
type: "assistant",
|
|
922
|
+
message: {
|
|
923
|
+
role: "assistant",
|
|
924
|
+
content: [{
|
|
925
|
+
type: "text",
|
|
926
|
+
text: "[system] Session restored from UltraContext with no user/assistant messages."
|
|
927
|
+
}]
|
|
928
|
+
},
|
|
929
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
930
|
+
uuid: randomUUID()
|
|
931
|
+
}));
|
|
932
|
+
await fs$1.writeFile(filePath, `${lines.join("\n")}\n`, "utf8");
|
|
933
|
+
return {
|
|
934
|
+
written: true,
|
|
935
|
+
reason: "created",
|
|
936
|
+
filePath,
|
|
937
|
+
sessionId: resolvedSessionId
|
|
938
|
+
};
|
|
939
|
+
} catch (error) {
|
|
940
|
+
return {
|
|
941
|
+
written: false,
|
|
942
|
+
reason: "write_failed",
|
|
943
|
+
filePath,
|
|
944
|
+
sessionId: resolvedSessionId,
|
|
945
|
+
error: error instanceof Error ? error.message : String(error)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function extractMessageText(message, normalizedRole) {
|
|
950
|
+
const raw = message?.content?.raw;
|
|
951
|
+
if (raw?.type === "response_item" && raw?.payload?.type === "message") {
|
|
952
|
+
const text = (raw.payload.content ?? []).map((c) => c.text ?? "").filter(Boolean).join("\n");
|
|
953
|
+
if (text) return normalizedRole === "system" ? `[system] ${text}` : text;
|
|
954
|
+
}
|
|
955
|
+
if (raw?.type === "event_msg" && raw?.payload?.type === "user_message") return raw.payload.message ?? "";
|
|
956
|
+
const msg = message?.content?.message ?? "";
|
|
957
|
+
if (!msg) return "";
|
|
958
|
+
return normalizedRole === "system" ? `[system] ${msg}` : msg;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region ../../packages/parsers/src/writers/codex.mjs
|
|
963
|
+
function buildEventMsgLine(message, fallbackTs) {
|
|
964
|
+
const ts = asIso(message?.content?.timestamp ?? message?.metadata?.timestamp ?? fallbackTs);
|
|
965
|
+
const role = normalizeRole(message?.role);
|
|
966
|
+
const text = coerceMessageText(message).trim();
|
|
967
|
+
if (!text) return null;
|
|
968
|
+
if (role === "user") return {
|
|
969
|
+
timestamp: ts,
|
|
970
|
+
type: "event_msg",
|
|
971
|
+
payload: {
|
|
972
|
+
type: "user_message",
|
|
973
|
+
message: text,
|
|
974
|
+
images: [],
|
|
975
|
+
local_images: [],
|
|
976
|
+
text_elements: []
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
if (role === "assistant") return {
|
|
980
|
+
timestamp: ts,
|
|
981
|
+
type: "event_msg",
|
|
982
|
+
payload: {
|
|
983
|
+
type: "agent_message",
|
|
984
|
+
message: text
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
return {
|
|
988
|
+
timestamp: ts,
|
|
989
|
+
type: "event_msg",
|
|
990
|
+
payload: {
|
|
991
|
+
type: "agent_message",
|
|
992
|
+
message: `[system] ${text}`
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function sessionFilePath(sessionId, firstTimestamp, baseDir) {
|
|
997
|
+
const iso = asIso(firstTimestamp);
|
|
998
|
+
const [year, month, day] = iso.slice(0, 10).split("-");
|
|
999
|
+
const stamp = iso.replace(/\.\d{3}Z$/, "").replace(/:/g, "-").replace("Z", "");
|
|
1000
|
+
const root = baseDir || expandHome("~/.codex/sessions");
|
|
1001
|
+
const fileName = `rollout-${stamp}-${sessionId}.jsonl`;
|
|
1002
|
+
return path.join(root, year, month, day, fileName);
|
|
1003
|
+
}
|
|
1004
|
+
async function hasLocalCodexSession(sessionId, baseDir) {
|
|
1005
|
+
const id = String(sessionId ?? "").trim();
|
|
1006
|
+
if (!id) return false;
|
|
1007
|
+
const fg = (await import("fast-glob")).default;
|
|
1008
|
+
const root = baseDir || expandHome("~/.codex/sessions");
|
|
1009
|
+
return (await fg([path.join(root, `**/*${id}*.jsonl`)], {
|
|
1010
|
+
onlyFiles: true,
|
|
1011
|
+
absolute: true,
|
|
1012
|
+
unique: true,
|
|
1013
|
+
suppressErrors: true,
|
|
1014
|
+
followSymbolicLinks: false
|
|
1015
|
+
})).some((filePath) => filePath.includes(id));
|
|
1016
|
+
}
|
|
1017
|
+
async function writeCodexSession({ sessionId, cwd, messages, baseDir }) {
|
|
1018
|
+
const id = String(sessionId ?? "").trim();
|
|
1019
|
+
if (!id) return {
|
|
1020
|
+
written: false,
|
|
1021
|
+
reason: "missing_session_id",
|
|
1022
|
+
filePath: "",
|
|
1023
|
+
sessionId: ""
|
|
1024
|
+
};
|
|
1025
|
+
if (await hasLocalCodexSession(id, baseDir)) return {
|
|
1026
|
+
written: false,
|
|
1027
|
+
reason: "already_exists",
|
|
1028
|
+
filePath: "",
|
|
1029
|
+
sessionId: id
|
|
1030
|
+
};
|
|
1031
|
+
const firstTs = asIso(firstMessageTimestamp(messages));
|
|
1032
|
+
const filePath = sessionFilePath(id, firstTs, baseDir);
|
|
1033
|
+
try {
|
|
1034
|
+
await fs$1.mkdir(path.dirname(filePath), { recursive: true });
|
|
1035
|
+
const lines = [];
|
|
1036
|
+
lines.push(JSON.stringify({
|
|
1037
|
+
timestamp: firstTs,
|
|
1038
|
+
type: "session_meta",
|
|
1039
|
+
payload: {
|
|
1040
|
+
id,
|
|
1041
|
+
timestamp: firstTs,
|
|
1042
|
+
cwd: cwd || process.cwd(),
|
|
1043
|
+
originator: "ultracontext_daemon",
|
|
1044
|
+
cli_version: "restored",
|
|
1045
|
+
source: "cli",
|
|
1046
|
+
model_provider: "openai"
|
|
1047
|
+
}
|
|
1048
|
+
}));
|
|
1049
|
+
let emitted = 0;
|
|
1050
|
+
for (let i = 0; i < (messages?.length ?? 0); i += 1) {
|
|
1051
|
+
const fallbackTs = new Date(new Date(firstTs).getTime() + i * 1e3).toISOString();
|
|
1052
|
+
const line = buildEventMsgLine(messages[i], fallbackTs);
|
|
1053
|
+
if (!line) continue;
|
|
1054
|
+
lines.push(JSON.stringify(line));
|
|
1055
|
+
emitted += 1;
|
|
1056
|
+
}
|
|
1057
|
+
if (emitted === 0) lines.push(JSON.stringify({
|
|
1058
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1059
|
+
type: "event_msg",
|
|
1060
|
+
payload: {
|
|
1061
|
+
type: "agent_message",
|
|
1062
|
+
message: "[system] Session restored from UltraContext with no user/assistant messages."
|
|
1063
|
+
}
|
|
1064
|
+
}));
|
|
1065
|
+
await fs$1.writeFile(filePath, `${lines.join("\n")}\n`, "utf8");
|
|
1066
|
+
return {
|
|
1067
|
+
written: true,
|
|
1068
|
+
reason: "created",
|
|
1069
|
+
filePath,
|
|
1070
|
+
sessionId: id
|
|
1071
|
+
};
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
return {
|
|
1074
|
+
written: false,
|
|
1075
|
+
reason: "write_failed",
|
|
1076
|
+
filePath,
|
|
1077
|
+
sessionId: id,
|
|
1078
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region ../../packages/parsers/src/switch.mjs
|
|
1085
|
+
const MAX_SESSION_BYTES = 256 * 1024 * 1024;
|
|
1086
|
+
async function findLatestSession(pattern, excludePattern) {
|
|
1087
|
+
const fg = (await import("fast-glob")).default;
|
|
1088
|
+
const files = await fg([pattern], {
|
|
1089
|
+
onlyFiles: true,
|
|
1090
|
+
absolute: true,
|
|
1091
|
+
unique: true,
|
|
1092
|
+
suppressErrors: true,
|
|
1093
|
+
followSymbolicLinks: false,
|
|
1094
|
+
ignore: excludePattern ? [excludePattern] : [],
|
|
1095
|
+
stats: true
|
|
1096
|
+
});
|
|
1097
|
+
if (!files.length) return null;
|
|
1098
|
+
files.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
|
|
1099
|
+
return files[0].path;
|
|
1100
|
+
}
|
|
1101
|
+
async function resolveSessionPath(source, cwd, baseDirs = {}) {
|
|
1102
|
+
if (source === "claude") {
|
|
1103
|
+
const projectDir = claudeProjectDirName(cwd);
|
|
1104
|
+
const root = baseDirs.claude || expandHome("~/.claude/projects");
|
|
1105
|
+
return findLatestSession(path.join(root, projectDir, "*.jsonl"), "**/subagents/**");
|
|
1106
|
+
}
|
|
1107
|
+
if (source === "codex") {
|
|
1108
|
+
const root = baseDirs.codex || expandHome("~/.codex/sessions");
|
|
1109
|
+
return findLatestSession(path.join(root, "**/*.jsonl"));
|
|
1110
|
+
}
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
async function readLocalSession({ source, sessionPath, cwd, sourceRoots }) {
|
|
1114
|
+
const resolvedCwd = cwd || process.cwd();
|
|
1115
|
+
const filePath = sessionPath || await resolveSessionPath(source, resolvedCwd, sourceRoots);
|
|
1116
|
+
if (!filePath) throw new Error(`No ${source} session found. Specify --session <path>.`);
|
|
1117
|
+
if (!fs.existsSync(filePath)) throw new Error(`Session file not found: ${filePath}`);
|
|
1118
|
+
const stat = fs.lstatSync(filePath);
|
|
1119
|
+
if (!stat.isFile()) throw new Error(`Session path is not a regular file: ${filePath}`);
|
|
1120
|
+
if (stat.size > MAX_SESSION_BYTES) throw new Error(`Session file too large: ${stat.size} bytes (max ${MAX_SESSION_BYTES}).`);
|
|
1121
|
+
const lines = fs.readFileSync(filePath, "utf8").split("\n").filter((l) => l.trim());
|
|
1122
|
+
const parser = source === "claude" ? parseClaudeCodeLine : parseCodexLine;
|
|
1123
|
+
const messages = [];
|
|
1124
|
+
let extractedCwd = null;
|
|
1125
|
+
let sessionId = null;
|
|
1126
|
+
for (const line of lines) {
|
|
1127
|
+
const parsed = parser({
|
|
1128
|
+
line,
|
|
1129
|
+
filePath
|
|
1130
|
+
});
|
|
1131
|
+
if (!parsed) continue;
|
|
1132
|
+
if (!sessionId && parsed.sessionId) sessionId = parsed.sessionId;
|
|
1133
|
+
if (source === "claude" && parsed.raw?.cwd && !extractedCwd && isSafeCwd(parsed.raw.cwd)) extractedCwd = parsed.raw.cwd;
|
|
1134
|
+
if (source === "codex" && parsed.raw?.type === "session_meta" && !extractedCwd && isSafeCwd(parsed.raw.payload?.cwd)) extractedCwd = parsed.raw.payload.cwd;
|
|
1135
|
+
if (parsed.kind === "user" || parsed.kind === "assistant") messages.push(parsed);
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
messages,
|
|
1139
|
+
sessionId: sessionId || extractSessionIdFromPath(filePath),
|
|
1140
|
+
cwd: extractedCwd || resolvedCwd,
|
|
1141
|
+
filePath
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
function toWriterMessages(messages, source) {
|
|
1145
|
+
return messages.map((m) => ({
|
|
1146
|
+
role: m.kind,
|
|
1147
|
+
content: {
|
|
1148
|
+
message: typeof m.message === "string" ? m.message : m.message ?? "",
|
|
1149
|
+
timestamp: m.timestamp,
|
|
1150
|
+
raw: m.raw
|
|
1151
|
+
},
|
|
1152
|
+
metadata: { source }
|
|
1153
|
+
}));
|
|
1154
|
+
}
|
|
1155
|
+
async function switchSession({ source, target, sessionPath, cwd, last, baseDir, sourceRoots }) {
|
|
1156
|
+
const session = await readLocalSession({
|
|
1157
|
+
source,
|
|
1158
|
+
sessionPath,
|
|
1159
|
+
cwd,
|
|
1160
|
+
sourceRoots
|
|
1161
|
+
});
|
|
1162
|
+
let msgs = session.messages;
|
|
1163
|
+
if (typeof last === "number" && last > 0) msgs = msgs.slice(-last);
|
|
1164
|
+
const writerMessages = toWriterMessages(msgs, source);
|
|
1165
|
+
const firstId = randomUUID();
|
|
1166
|
+
const writer = target === "codex" ? writeCodexSession : writeClaudeSession;
|
|
1167
|
+
let result = await writer({
|
|
1168
|
+
sessionId: firstId,
|
|
1169
|
+
cwd: session.cwd,
|
|
1170
|
+
messages: writerMessages,
|
|
1171
|
+
baseDir
|
|
1172
|
+
});
|
|
1173
|
+
let usedId = firstId;
|
|
1174
|
+
if (!result.written && result.reason === "already_exists") {
|
|
1175
|
+
const retryId = randomUUID();
|
|
1176
|
+
result = await writer({
|
|
1177
|
+
sessionId: retryId,
|
|
1178
|
+
cwd: session.cwd,
|
|
1179
|
+
messages: writerMessages,
|
|
1180
|
+
baseDir
|
|
1181
|
+
});
|
|
1182
|
+
usedId = retryId;
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
written: result.written,
|
|
1186
|
+
filePath: result.filePath,
|
|
1187
|
+
sessionId: result.written ? result.sessionId || usedId : null,
|
|
1188
|
+
messageCount: writerMessages.length,
|
|
1189
|
+
reason: result.reason,
|
|
1190
|
+
cwd: session.cwd
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region ../../packages/parsers/src/index.mjs
|
|
1196
|
+
var src_exports = /* @__PURE__ */ __exportAll({ switchSession: () => switchSession });
|
|
1197
|
+
|
|
1198
|
+
//#endregion
|
|
1199
|
+
export { writeClaudeSession as a, parseCursorLine as c, parseClaudeCodeLine as d, hasLocalClaudeSession as i, parseOpenClawLine as l, hasLocalCodexSession as n, parseGstackLine as o, writeCodexSession as r, parseGeminiFile as s, src_exports as t, parseCodexLine as u };
|
|
1200
|
+
//# sourceMappingURL=src-Bovo1ukU.mjs.map
|