codesesh 0.4.1 → 0.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.
@@ -1,4375 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ../core/dist/index.mjs
4
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
5
- import { join as join2, basename as basename2, dirname } from "path";
6
- import { existsSync } from "fs";
7
- import { homedir, platform } from "os";
8
- import { join } from "path";
9
- import { readFileSync } from "fs";
10
- import { basename } from "path";
11
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
12
- import { join as join3 } from "path";
13
- import { mkdirSync } from "fs";
14
- import { dirname as dirname2 } from "path";
15
- import { createRequire } from "module";
16
- import { createHash } from "crypto";
17
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
18
- import { join as join4, basename as basename3, dirname as dirname3 } from "path";
19
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
20
- import { join as join5, basename as basename4 } from "path";
21
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
22
- import { join as join6, normalize } from "path";
23
- import { resolve, sep } from "path";
24
- import { existsSync as existsSync7, rmSync, unlinkSync } from "fs";
25
- import { join as join7 } from "path";
26
- import { homedir as homedir2 } from "os";
27
- import { homedir as homedir3, platform as platform2 } from "os";
28
- import { join as join8 } from "path";
29
- var registrations = [];
30
- function registerAgent(reg) {
31
- registrations.push(reg);
32
- }
33
- function createRegisteredAgents() {
34
- return registrations.map((r) => r.create());
35
- }
36
- function getRegisteredAgents() {
37
- return registrations;
38
- }
39
- function getAgentInfoMap(sessionsByAgent) {
40
- return registrations.map((r) => ({
41
- name: r.name,
42
- displayName: r.displayName,
43
- icon: r.icon,
44
- count: sessionsByAgent[r.name] ?? 0
45
- }));
46
- }
47
- function getAgentByName(name) {
48
- return registrations.find((r) => r.name === name);
49
- }
50
- var BaseAgent = class {
51
- getUri(sessionId) {
52
- return `${this.name}://${sessionId}`;
53
- }
54
- };
55
- function envPath(name) {
56
- const value = process.env[name];
57
- if (!value) return null;
58
- return value;
59
- }
60
- function firstExisting(...paths) {
61
- for (const p of paths) {
62
- if (existsSync(p)) return p;
63
- }
64
- return null;
65
- }
66
- function getDataHome() {
67
- const xdg = envPath("XDG_DATA_HOME");
68
- if (xdg) return xdg;
69
- const p = platform();
70
- if (p === "win32") {
71
- return envPath("LOCALAPPDATA") ?? envPath("APPDATA") ?? join(homedir(), "AppData", "Local");
72
- }
73
- return join(homedir(), ".local", "share");
74
- }
75
- function resolveProviderRoots() {
76
- const home = homedir();
77
- return {
78
- codexRoot: envPath("CODEX_HOME") ?? join(home, ".codex"),
79
- claudeRoot: envPath("CLAUDE_CONFIG_DIR") ?? join(home, ".claude"),
80
- kimiRoot: envPath("KIMI_SHARE_DIR") ?? join(home, ".kimi"),
81
- opencodeRoot: join(getDataHome(), "opencode")
82
- };
83
- }
84
- function getCursorDataPath() {
85
- const override = envPath("CURSOR_DATA_PATH");
86
- if (override) return override;
87
- const p = platform();
88
- if (p === "darwin") {
89
- return firstExisting(join(homedir(), "Library", "Application Support", "Cursor", "User"));
90
- }
91
- if (p === "linux") {
92
- const xdg = envPath("XDG_CONFIG_HOME") ?? join(homedir(), ".config");
93
- return firstExisting(join(xdg, "Cursor", "User"));
94
- }
95
- if (p === "win32") {
96
- const appData = envPath("APPDATA") ?? join(homedir(), "AppData", "Roaming");
97
- return firstExisting(join(appData, "Cursor", "User"));
98
- }
99
- return null;
100
- }
101
- function* parseJsonlLines(content) {
102
- for (const line of content.split("\n")) {
103
- const trimmed = line.trim();
104
- if (!trimmed) continue;
105
- try {
106
- yield JSON.parse(trimmed);
107
- } catch {
108
- }
109
- }
110
- }
111
- function readJsonlFile(filePath) {
112
- const content = readFileSync(filePath, "utf-8");
113
- return parseJsonlLines(content);
114
- }
115
- var TITLE_MAX_LENGTH = 100;
116
- var UNTITLED_SESSION = "Untitled Session";
117
- function normalizeTitleText(text) {
118
- const cleaned = text.replace(/\s+/g, " ").trim();
119
- if (!cleaned) return null;
120
- return cleaned.slice(0, TITLE_MAX_LENGTH);
121
- }
122
- function basenameTitle(path) {
123
- if (!path) return null;
124
- const normalized = path.trim().replace(/[/\\]+$/, "");
125
- if (!normalized) return null;
126
- const name = basename(normalized).trim();
127
- return name || null;
128
- }
129
- function resolveSessionTitle(explicit, message, directory) {
130
- for (const candidate of [explicit, message, directory]) {
131
- if (candidate) {
132
- const normalized = normalizeTitleText(candidate);
133
- if (normalized) return normalized;
134
- }
135
- }
136
- return UNTITLED_SESSION;
137
- }
138
- var PerfTracer = class {
139
- rootMarkers = [];
140
- activeStack = [];
141
- enabled = false;
142
- enable() {
143
- this.enabled = true;
144
- }
145
- start(name) {
146
- const marker = {
147
- name,
148
- startTime: performance.now(),
149
- children: []
150
- };
151
- if (!this.enabled) return marker;
152
- const parent = this.activeStack[this.activeStack.length - 1];
153
- if (parent) {
154
- marker.parent = parent;
155
- parent.children.push(marker);
156
- } else {
157
- this.rootMarkers.push(marker);
158
- }
159
- this.activeStack.push(marker);
160
- return marker;
161
- }
162
- end(marker) {
163
- if (!this.enabled) return;
164
- const target = marker ?? this.activeStack[this.activeStack.length - 1];
165
- if (!target) return;
166
- target.endTime = performance.now();
167
- target.duration = target.endTime - target.startTime;
168
- while (this.activeStack.length > 0) {
169
- const popped = this.activeStack.pop();
170
- if (popped === target) break;
171
- }
172
- }
173
- measure(name, fn) {
174
- const marker = this.start(name);
175
- try {
176
- return fn();
177
- } finally {
178
- this.end(marker);
179
- }
180
- }
181
- async measureAsync(name, fn) {
182
- const marker = this.start(name);
183
- try {
184
- return await fn();
185
- } finally {
186
- this.end(marker);
187
- }
188
- }
189
- getReport() {
190
- if (!this.enabled) return "Performance tracing disabled";
191
- const lines = [];
192
- lines.push("\n=== Performance Report ===\n");
193
- for (const marker of this.rootMarkers) {
194
- this.formatMarker(marker, 0, lines);
195
- }
196
- return lines.join("\n");
197
- }
198
- formatMarker(marker, depth, lines) {
199
- const indent = " ".repeat(depth);
200
- const duration = marker.duration?.toFixed(2) ?? "?";
201
- lines.push(`${indent}${marker.name}: ${duration}ms`);
202
- for (const child of marker.children) {
203
- this.formatMarker(child, depth + 1, lines);
204
- }
205
- }
206
- reset() {
207
- this.rootMarkers = [];
208
- this.activeStack = [];
209
- }
210
- };
211
- var perf = new PerfTracer();
212
- var RECENT_SESSION_REVALIDATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
213
- function parseTimestampMs(data) {
214
- const raw = String(data["timestamp"] ?? "").trim();
215
- if (!raw) return 0;
216
- try {
217
- return new Date(raw.includes("Z") ? raw : raw + "Z").getTime();
218
- } catch {
219
- return 0;
220
- }
221
- }
222
- function normalizeTitleText2(text) {
223
- const line = text.split("\n").find((l) => l.trim());
224
- return line?.trim().slice(0, 80) || "";
225
- }
226
- var ClaudeCodeAgent = class extends BaseAgent {
227
- name = "claudecode";
228
- displayName = "Claude Code";
229
- basePath = null;
230
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
231
- sessionsIndexCache = {};
232
- sessionMetaMap = /* @__PURE__ */ new Map();
233
- findBasePath() {
234
- const roots = resolveProviderRoots();
235
- return firstExisting(join2(roots.claudeRoot, "projects"), "data/claudecode");
236
- }
237
- isAvailable() {
238
- this.basePath = this.findBasePath();
239
- if (!this.basePath) return false;
240
- try {
241
- for (const entry of readdirSync(this.basePath)) {
242
- const dir = join2(this.basePath, entry);
243
- if (existsSync2(dir) && readdirSync(dir).some((f) => f.endsWith(".jsonl"))) {
244
- return true;
245
- }
246
- }
247
- } catch {
248
- }
249
- return false;
250
- }
251
- scan() {
252
- if (!this.basePath) return [];
253
- const scanMarker = perf.start("claudecode:scan");
254
- const heads = [];
255
- const listMarker = perf.start("listProjectDirs");
256
- const projectDirs = this.listProjectDirs();
257
- perf.end(listMarker);
258
- for (const projectDir of projectDirs) {
259
- const fileMarker = perf.start(`listJsonlFiles:${basename2(projectDir)}`);
260
- const files = this.listJsonlFiles(projectDir);
261
- perf.end(fileMarker);
262
- for (const file of files) {
263
- try {
264
- const parseMarker = perf.start(`parseSessionHead:${basename2(file)}`);
265
- const head = this.parseSessionHead(file, projectDir);
266
- perf.end(parseMarker);
267
- if (head) {
268
- heads.push(head);
269
- this.sessionMetaMap.set(head.id, {
270
- id: head.id,
271
- title: head.title,
272
- sourcePath: file,
273
- directory: head.directory,
274
- model: head.stats.total_tokens ? "unknown" : void 0,
275
- messageCount: head.stats.message_count,
276
- createdAt: head.time_created,
277
- updatedAt: head.time_updated ?? head.time_created
278
- });
279
- }
280
- } catch {
281
- }
282
- }
283
- }
284
- perf.end(scanMarker);
285
- return heads;
286
- }
287
- getSessionMetaMap() {
288
- return this.sessionMetaMap;
289
- }
290
- setSessionMetaMap(meta) {
291
- this.sessionMetaMap = meta;
292
- }
293
- getSessionData(sessionId) {
294
- const meta = this.sessionMetaMap.get(sessionId);
295
- if (!meta) {
296
- throw new Error(`Session not found: ${sessionId}`);
297
- }
298
- if (!existsSync2(meta.sourcePath)) {
299
- throw new Error(`Session file missing: ${meta.sourcePath}`);
300
- }
301
- const content = readFileSync2(meta.sourcePath, "utf-8");
302
- const messages = [];
303
- const pendingToolCalls = /* @__PURE__ */ new Map();
304
- const ignoredToolCallIds = /* @__PURE__ */ new Set();
305
- const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
306
- const assistantState = {
307
- currentIndex: null,
308
- latestTextIndex: null
309
- };
310
- let totalCost = 0;
311
- let totalInputTokens = 0;
312
- let totalOutputTokens = 0;
313
- let totalCacheRead = 0;
314
- let totalCacheCreate = 0;
315
- for (const record of parseJsonlLines(content)) {
316
- try {
317
- this.convertRecord(
318
- record,
319
- messages,
320
- pendingToolCalls,
321
- ignoredToolCallIds,
322
- assistantUuidToToolCalls,
323
- assistantState
324
- );
325
- } catch {
326
- }
327
- }
328
- for (const msg of messages) {
329
- totalCost += msg.cost ?? 0;
330
- totalInputTokens += msg.tokens?.input ?? 0;
331
- totalOutputTokens += msg.tokens?.output ?? 0;
332
- totalCacheRead += msg.tokens?.cache_read ?? 0;
333
- totalCacheCreate += msg.tokens?.cache_create ?? 0;
334
- }
335
- return {
336
- id: meta.id,
337
- title: meta.title,
338
- slug: `claudecode/${meta.id}`,
339
- directory: meta.directory,
340
- version: void 0,
341
- time_created: meta.createdAt,
342
- time_updated: meta.updatedAt,
343
- stats: {
344
- message_count: messages.length,
345
- total_input_tokens: totalInputTokens,
346
- total_output_tokens: totalOutputTokens,
347
- total_cost: totalCost,
348
- total_cache_read_tokens: totalCacheRead,
349
- total_cache_create_tokens: totalCacheCreate
350
- },
351
- messages
352
- };
353
- }
354
- /**
355
- * 检测文件系统变更
356
- * 通过比较文件修改时间判断是否有新内容
357
- */
358
- checkForChanges(sinceTimestamp, cachedSessions) {
359
- if (!this.basePath) {
360
- return { hasChanges: false, timestamp: Date.now() };
361
- }
362
- const now = Date.now();
363
- const changedIds = /* @__PURE__ */ new Set();
364
- const recentSessions = cachedSessions.filter(
365
- (session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS
366
- );
367
- for (const session of recentSessions) {
368
- changedIds.add(session.id);
369
- const meta = this.sessionMetaMap.get(session.id);
370
- if (!meta) continue;
371
- delete this.sessionsIndexCache[basename2(dirname(meta.sourcePath))];
372
- }
373
- for (const session of cachedSessions) {
374
- const meta = this.sessionMetaMap.get(session.id);
375
- if (!meta) {
376
- changedIds.add(session.id);
377
- continue;
378
- }
379
- try {
380
- const stat = statSync(meta.sourcePath);
381
- if (stat.mtimeMs > sinceTimestamp) {
382
- changedIds.add(session.id);
383
- }
384
- } catch {
385
- changedIds.add(session.id);
386
- }
387
- }
388
- try {
389
- let totalFiles = 0;
390
- for (const dir of this.listProjectDirs()) {
391
- totalFiles += this.listJsonlFiles(dir).length;
392
- }
393
- const hasNewFiles = totalFiles > cachedSessions.length;
394
- return {
395
- hasChanges: changedIds.size > 0 || hasNewFiles,
396
- changedIds: Array.from(changedIds),
397
- timestamp: Date.now()
398
- };
399
- } catch {
400
- return {
401
- hasChanges: changedIds.size > 0,
402
- changedIds: Array.from(changedIds),
403
- timestamp: Date.now()
404
- };
405
- }
406
- }
407
- /**
408
- * 增量扫描 - 只扫描变更的会话
409
- */
410
- incrementalScan(cachedSessions, changedIds) {
411
- if (!this.basePath) return cachedSessions;
412
- const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
413
- for (const projectDir of this.listProjectDirs()) {
414
- for (const file of this.listJsonlFiles(projectDir)) {
415
- try {
416
- const sessionId = basename2(file, ".jsonl");
417
- if (changedIds.includes(sessionId)) {
418
- const head = this.parseSessionHead(file, projectDir);
419
- if (head) {
420
- sessionMap.set(head.id, head);
421
- this.sessionMetaMap.set(head.id, {
422
- id: head.id,
423
- title: head.title,
424
- sourcePath: file,
425
- directory: head.directory,
426
- model: head.stats.total_tokens ? "unknown" : void 0,
427
- messageCount: head.stats.message_count,
428
- createdAt: head.time_created,
429
- updatedAt: head.time_updated ?? head.time_created
430
- });
431
- }
432
- }
433
- } catch {
434
- }
435
- }
436
- }
437
- for (const projectDir of this.listProjectDirs()) {
438
- for (const file of this.listJsonlFiles(projectDir)) {
439
- try {
440
- const sessionId = basename2(file, ".jsonl");
441
- if (!sessionMap.has(sessionId)) {
442
- const head = this.parseSessionHead(file, projectDir);
443
- if (head) {
444
- sessionMap.set(head.id, head);
445
- this.sessionMetaMap.set(head.id, {
446
- id: head.id,
447
- title: head.title,
448
- sourcePath: file,
449
- directory: head.directory,
450
- model: head.stats.total_tokens ? "unknown" : void 0,
451
- messageCount: head.stats.message_count,
452
- createdAt: head.time_created,
453
- updatedAt: head.time_updated ?? head.time_created
454
- });
455
- }
456
- }
457
- } catch {
458
- }
459
- }
460
- }
461
- return Array.from(sessionMap.values());
462
- }
463
- // --- Private helpers ---
464
- listProjectDirs() {
465
- if (!this.basePath) return [];
466
- try {
467
- return readdirSync(this.basePath).map((e) => join2(this.basePath, e)).filter((p) => existsSync2(p));
468
- } catch {
469
- return [];
470
- }
471
- }
472
- listJsonlFiles(dir) {
473
- try {
474
- return readdirSync(dir).filter((f) => f.endsWith(".jsonl") && f !== "sessions-index.json").map((f) => join2(dir, f));
475
- } catch {
476
- return [];
477
- }
478
- }
479
- loadSessionsIndex(projectDir) {
480
- const cacheKey = basename2(projectDir);
481
- if (cacheKey in this.sessionsIndexCache) {
482
- return this.sessionsIndexCache[cacheKey];
483
- }
484
- const indexPath = join2(projectDir, "sessions-index.json");
485
- const map = /* @__PURE__ */ new Map();
486
- if (existsSync2(indexPath)) {
487
- try {
488
- const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
489
- const entries = data?.entries ?? [];
490
- for (const entry of entries) {
491
- const sid = entry?.sessionId;
492
- if (typeof sid === "string") {
493
- map.set(sid, entry);
494
- }
495
- }
496
- } catch {
497
- }
498
- }
499
- this.sessionsIndexCache[cacheKey] = map;
500
- return map;
501
- }
502
- parseSessionHead(filePath, projectDir) {
503
- const content = readFileSync2(filePath, "utf-8");
504
- const lines = content.split("\n").filter((l) => l.trim());
505
- if (lines.length === 0) return null;
506
- const sessionId = basename2(filePath, ".jsonl");
507
- let firstRecord;
508
- try {
509
- firstRecord = JSON.parse(lines[0]);
510
- } catch {
511
- return null;
512
- }
513
- const createdAt = parseTimestampMs(firstRecord) || statSync(filePath).mtimeMs;
514
- const index = this.loadSessionsIndex(projectDir);
515
- const indexEntry = index.get(sessionId);
516
- const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
517
- let updatedAt = createdAt;
518
- let messageCount = 0;
519
- let model = null;
520
- let cwd = null;
521
- let totalInputTokens = 0;
522
- let totalOutputTokens = 0;
523
- let totalCacheReadTokens = 0;
524
- let totalCacheCreateTokens = 0;
525
- const modelUsageMap = {};
526
- for (const line of lines) {
527
- try {
528
- const data = JSON.parse(line);
529
- const ts = parseTimestampMs(data);
530
- if (ts > updatedAt) updatedAt = ts;
531
- if (!cwd && data["cwd"] && typeof data["cwd"] === "string") {
532
- cwd = data["cwd"];
533
- }
534
- const msg = data["message"];
535
- if (msg && typeof msg === "object") {
536
- const role = msg["role"];
537
- if (typeof role === "string" && role.trim()) {
538
- messageCount++;
539
- }
540
- if (!model) {
541
- const m = msg["model"];
542
- if (typeof m === "string" && m.trim()) model = m.trim();
543
- }
544
- if (role === "assistant") {
545
- const usage = msg["usage"];
546
- if (usage && typeof usage === "object") {
547
- const inputTokens = usage["input_tokens"] ?? 0;
548
- const cacheRead = usage["cache_read_input_tokens"] ?? 0;
549
- const cacheCreate = usage["cache_creation_input_tokens"] ?? 0;
550
- const outputTokens = usage["output_tokens"] ?? 0;
551
- totalInputTokens += inputTokens + cacheRead + cacheCreate;
552
- totalOutputTokens += outputTokens;
553
- totalCacheReadTokens += cacheRead;
554
- totalCacheCreateTokens += cacheCreate;
555
- const m = msg["model"];
556
- if (typeof m === "string" && m.trim()) {
557
- const name = m.trim();
558
- const msgTotal = inputTokens + cacheRead + cacheCreate + outputTokens;
559
- modelUsageMap[name] = (modelUsageMap[name] ?? 0) + msgTotal;
560
- }
561
- }
562
- }
563
- }
564
- } catch {
565
- }
566
- }
567
- const directory = cwd ?? projectDir;
568
- const messageTitle = this.extractTitle(lines);
569
- const directoryTitle = basenameTitle(directory) || basenameTitle(projectDir);
570
- const title = resolveSessionTitle(explicitTitle, messageTitle, directoryTitle);
571
- const hasModelUsage = Object.keys(modelUsageMap).length > 0;
572
- return {
573
- id: sessionId,
574
- slug: `claudecode/${sessionId}`,
575
- title,
576
- directory,
577
- time_created: createdAt,
578
- time_updated: updatedAt,
579
- stats: {
580
- message_count: messageCount,
581
- total_input_tokens: totalInputTokens,
582
- total_output_tokens: totalOutputTokens,
583
- total_cost: 0,
584
- total_cache_read_tokens: totalCacheReadTokens,
585
- total_cache_create_tokens: totalCacheCreateTokens
586
- },
587
- model_usage: hasModelUsage ? modelUsageMap : void 0
588
- };
589
- }
590
- extractTitle(lines) {
591
- for (const line of lines.slice(0, 20)) {
592
- try {
593
- const data = JSON.parse(line);
594
- const msg = data["message"];
595
- if (!msg || typeof msg !== "object") continue;
596
- if (msg["role"] !== "user") continue;
597
- const content = msg["content"];
598
- if (!content) continue;
599
- if (typeof content === "string") {
600
- return normalizeTitleText2(content);
601
- }
602
- if (Array.isArray(content)) {
603
- const texts = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
604
- return normalizeTitleText2(texts);
605
- }
606
- } catch {
607
- }
608
- }
609
- return null;
610
- }
611
- // --- Record conversion ---
612
- convertRecord(data, messages, pendingToolCalls, ignoredToolCallIds, assistantUuidToToolCalls, assistantState) {
613
- if (data["isMeta"] === true) return;
614
- const msgType = String(data["type"] ?? "");
615
- if (msgType === "assistant") {
616
- this.convertAssistantRecord(
617
- data,
618
- messages,
619
- pendingToolCalls,
620
- ignoredToolCallIds,
621
- assistantUuidToToolCalls,
622
- assistantState
623
- );
624
- } else if (msgType === "user") {
625
- this.convertUserRecord(
626
- data,
627
- messages,
628
- pendingToolCalls,
629
- ignoredToolCallIds,
630
- assistantUuidToToolCalls,
631
- assistantState
632
- );
633
- } else if (msgType === "tool_result") {
634
- this.convertToolResultRecord(data, messages, assistantState);
635
- }
636
- }
637
- convertAssistantRecord(data, messages, pendingToolCalls, ignoredToolCallIds, assistantUuidToToolCalls, assistantState) {
638
- const msg = data["message"] ?? {};
639
- const timestampMs = parseTimestampMs(data);
640
- const rawContent = msg["content"] ?? [];
641
- const uuid = String(data["uuid"] ?? "");
642
- const toolCallIds = [];
643
- let currentAssistantIndex = assistantState.currentIndex;
644
- let latestAssistantTextIndex = assistantState.latestTextIndex;
645
- if (Array.isArray(rawContent)) {
646
- for (const item of rawContent) {
647
- if (!item || typeof item !== "object") continue;
648
- const part = item;
649
- const partType = String(part["type"] ?? "");
650
- if (partType === "thinking") {
651
- const text = String(part["thinking"] ?? "");
652
- if (text.trim()) {
653
- currentAssistantIndex = this.appendAssistantReasoning(
654
- messages,
655
- { messageId: uuid, msg, timestampMs, text },
656
- currentAssistantIndex
657
- );
658
- }
659
- continue;
660
- }
661
- if (partType === "text") {
662
- const text = String(part["text"] ?? "");
663
- if (text.trim()) {
664
- currentAssistantIndex = this.appendAssistantText(
665
- messages,
666
- { messageId: uuid, msg, timestampMs, text },
667
- currentAssistantIndex
668
- );
669
- latestAssistantTextIndex = currentAssistantIndex;
670
- }
671
- continue;
672
- }
673
- if (partType !== "tool_use") continue;
674
- const toolName = String(part["name"] ?? "").trim();
675
- const toolCallId = String(part["id"] ?? "").trim();
676
- if (toolName && toolCallId && this.shouldIgnoreTool(toolName)) {
677
- ignoredToolCallIds.add(toolCallId);
678
- continue;
679
- }
680
- const toolPart = this.buildToolPart(part, timestampMs);
681
- const [msgIndex, partIndex] = this.attachToolCallToLatestAssistant(messages, {
682
- messageId: uuid,
683
- msg,
684
- timestampMs,
685
- toolPart,
686
- latestTextIndex: latestAssistantTextIndex
687
- });
688
- currentAssistantIndex = msgIndex;
689
- if (toolCallId) {
690
- pendingToolCalls.set(toolCallId, [msgIndex, partIndex]);
691
- toolCallIds.push(toolCallId);
692
- }
693
- }
694
- }
695
- if (toolCallIds.length > 0) {
696
- assistantUuidToToolCalls.set(uuid, toolCallIds);
697
- }
698
- assistantState.currentIndex = currentAssistantIndex;
699
- assistantState.latestTextIndex = latestAssistantTextIndex;
700
- }
701
- convertUserRecord(data, messages, pendingToolCalls, ignoredToolCallIds, assistantUuidToToolCalls, assistantState) {
702
- const msg = data["message"] ?? {};
703
- const timestampMs = parseTimestampMs(data);
704
- const content = msg["content"] ?? "";
705
- const uuid = String(data["uuid"] ?? "");
706
- if (typeof content === "string") {
707
- const parts = this.normalizeUserTextParts(content, timestampMs);
708
- if (parts.length === 0) {
709
- assistantState.currentIndex = null;
710
- assistantState.latestTextIndex = null;
711
- return;
712
- }
713
- messages.push(this.buildMessage({ messageId: uuid, role: "user", timestampMs, parts }));
714
- assistantState.currentIndex = null;
715
- assistantState.latestTextIndex = null;
716
- return;
717
- }
718
- if (!Array.isArray(content)) {
719
- assistantState.currentIndex = null;
720
- assistantState.latestTextIndex = null;
721
- return;
722
- }
723
- const visibleParts = this.normalizeUserTextParts(content, timestampMs);
724
- const toolStateUpdates = this.extractToolStateUpdates(data["toolUseResult"]);
725
- for (const item of content) {
726
- if (!item || typeof item !== "object") continue;
727
- const ci = item;
728
- if (ci["type"] !== "tool_result") continue;
729
- const toolCallId = this.resolveToolCallId(data, ci, assistantUuidToToolCalls);
730
- if (toolCallId && ignoredToolCallIds.has(toolCallId)) continue;
731
- const outputParts = this.normalizeClaudeToolOutput(ci["content"], timestampMs);
732
- if (this.backfillToolOutput(
733
- messages,
734
- pendingToolCalls,
735
- toolCallId,
736
- outputParts,
737
- toolStateUpdates
738
- )) {
739
- continue;
740
- }
741
- const fallback = this.buildFallbackToolMessage({
742
- messageId: uuid,
743
- timestampMs,
744
- toolCallId,
745
- outputParts
746
- });
747
- if (fallback) messages.push(fallback);
748
- }
749
- if (visibleParts.length > 0) {
750
- messages.push(
751
- this.buildMessage({ messageId: uuid, role: "user", timestampMs, parts: visibleParts })
752
- );
753
- }
754
- assistantState.currentIndex = null;
755
- assistantState.latestTextIndex = null;
756
- }
757
- convertToolResultRecord(data, messages, assistantState) {
758
- const timestampMs = parseTimestampMs(data);
759
- const msg = data["message"] ?? {};
760
- const outputParts = this.normalizeClaudeToolOutput(msg["content"], timestampMs);
761
- const uuid = String(data["uuid"] ?? "");
762
- const fallback = this.buildFallbackToolMessage({
763
- messageId: uuid,
764
- timestampMs,
765
- toolCallId: null,
766
- outputParts
767
- });
768
- if (fallback) messages.push(fallback);
769
- assistantState.currentIndex = null;
770
- assistantState.latestTextIndex = null;
771
- }
772
- // --- Message building ---
773
- buildMessage(opts) {
774
- return {
775
- id: opts.messageId,
776
- role: opts.role,
777
- agent: opts.agent ?? null,
778
- time_created: opts.timestampMs,
779
- mode: opts.mode ?? null,
780
- model: opts.model ?? null,
781
- provider: opts.provider ?? null,
782
- tokens: opts.tokens ? opts.tokens : void 0,
783
- cost: opts.cost ?? 0,
784
- parts: opts.parts
785
- };
786
- }
787
- buildTextPart(text, timestampMs) {
788
- return { type: "text", text, time_created: timestampMs };
789
- }
790
- buildReasoningPart(text, timestampMs) {
791
- return { type: "reasoning", text, time_created: timestampMs };
792
- }
793
- buildToolPart(part, timestampMs) {
794
- const toolName = String(part["name"] ?? "");
795
- return {
796
- type: "tool",
797
- tool: toolName,
798
- callID: String(part["id"] ?? ""),
799
- title: `Tool: ${toolName}`,
800
- state: {
801
- input: part["input"] ?? {},
802
- output: null
803
- },
804
- time_created: timestampMs
805
- };
806
- }
807
- applyAssistantMetadata(message, msg) {
808
- const model = msg["model"];
809
- if (model && typeof model === "string" && !message.model) {
810
- message.model = model;
811
- }
812
- const usage = msg["usage"];
813
- if (usage && typeof usage === "object" && !message.tokens) {
814
- const u = usage;
815
- const cacheRead = u["cache_read_input_tokens"] ?? 0;
816
- const cacheCreate = u["cache_creation_input_tokens"] ?? 0;
817
- message.tokens = {
818
- input: (u["input_tokens"] ?? 0) + cacheCreate + cacheRead,
819
- output: u["output_tokens"] ?? 0,
820
- cache_read: cacheRead,
821
- cache_create: cacheCreate
822
- };
823
- }
824
- }
825
- // --- Assistant message grouping ---
826
- appendAssistantReasoning(messages, opts, currentIndex) {
827
- const part = this.buildReasoningPart(opts.text, opts.timestampMs);
828
- if (currentIndex !== null) {
829
- const message2 = messages[currentIndex];
830
- const hasText = message2.parts.some((p) => p.type === "text");
831
- const hasTool = message2.parts.some((p) => p.type === "tool");
832
- if (!hasText && !hasTool) {
833
- this.appendPartIfNew(message2, part);
834
- this.applyAssistantMetadata(message2, opts.msg);
835
- return currentIndex;
836
- }
837
- }
838
- const message = this.buildMessage({
839
- messageId: opts.messageId,
840
- role: "assistant",
841
- timestampMs: opts.timestampMs,
842
- parts: [part],
843
- agent: "claude"
844
- });
845
- this.applyAssistantMetadata(message, opts.msg);
846
- messages.push(message);
847
- return messages.length - 1;
848
- }
849
- appendAssistantText(messages, opts, currentIndex) {
850
- const part = this.buildTextPart(opts.text, opts.timestampMs);
851
- if (currentIndex !== null) {
852
- const message2 = messages[currentIndex];
853
- const hasTool = message2.parts.some((p) => p.type === "tool");
854
- if (!hasTool) {
855
- this.appendPartIfNew(message2, part);
856
- this.applyAssistantMetadata(message2, opts.msg);
857
- return currentIndex;
858
- }
859
- }
860
- const message = this.buildMessage({
861
- messageId: opts.messageId,
862
- role: "assistant",
863
- timestampMs: opts.timestampMs,
864
- parts: [part],
865
- agent: "claude"
866
- });
867
- this.applyAssistantMetadata(message, opts.msg);
868
- messages.push(message);
869
- return messages.length - 1;
870
- }
871
- attachToolCallToLatestAssistant(messages, opts) {
872
- if (opts.latestTextIndex !== null) {
873
- const message2 = messages[opts.latestTextIndex];
874
- message2.parts.push(opts.toolPart);
875
- this.applyAssistantMetadata(message2, opts.msg);
876
- return [opts.latestTextIndex, message2.parts.length - 1];
877
- }
878
- const message = this.buildMessage({
879
- messageId: opts.messageId,
880
- role: "assistant",
881
- timestampMs: opts.timestampMs,
882
- parts: [opts.toolPart],
883
- agent: "claude",
884
- mode: "tool"
885
- });
886
- this.applyAssistantMetadata(message, opts.msg);
887
- messages.push(message);
888
- return [messages.length - 1, 0];
889
- }
890
- // --- User content normalization ---
891
- normalizeUserTextParts(content, timestampMs) {
892
- if (typeof content === "string") {
893
- return content.trim() ? [this.buildTextPart(content, timestampMs)] : [];
894
- }
895
- if (!Array.isArray(content)) return [];
896
- const parts = [];
897
- for (const item of content) {
898
- if (typeof item === "object" && item !== null) {
899
- const ci = item;
900
- if (ci["type"] === "tool_result") continue;
901
- const text = String(ci["text"] ?? "");
902
- if (text.trim()) parts.push(this.buildTextPart(text, timestampMs));
903
- } else if (typeof item === "string" && item.trim()) {
904
- parts.push(this.buildTextPart(item, timestampMs));
905
- }
906
- }
907
- return parts;
908
- }
909
- normalizeClaudeToolOutput(content, timestampMs) {
910
- if (typeof content === "string") {
911
- return content.trim() ? [this.buildTextPart(content, timestampMs)] : [];
912
- }
913
- if (content === null || content === void 0) return [];
914
- if (Array.isArray(content)) {
915
- const parts = [];
916
- for (const item of content) {
917
- if (typeof item === "object" && item !== null) {
918
- const text2 = String(
919
- item["text"] ?? item["content"] ?? ""
920
- );
921
- if (text2.trim()) parts.push(this.buildTextPart(text2, timestampMs));
922
- } else if (typeof item === "string" && item.trim()) {
923
- parts.push(this.buildTextPart(item, timestampMs));
924
- }
925
- }
926
- return parts;
927
- }
928
- const text = String(content);
929
- return text.trim() ? [this.buildTextPart(text, timestampMs)] : [];
930
- }
931
- // --- Tool backfill ---
932
- backfillToolOutput(messages, pendingToolCalls, callId, outputParts, stateUpdates) {
933
- if (!callId) return false;
934
- const location = pendingToolCalls.get(callId);
935
- if (location === void 0) return false;
936
- const [msgIndex, partIndex] = location;
937
- const state = messages[msgIndex].parts[partIndex].state ?? (messages[msgIndex].parts[partIndex].state = {});
938
- if (outputParts.length > 0) {
939
- const existing = state.output;
940
- if (Array.isArray(existing)) {
941
- existing.push(...outputParts);
942
- } else if (existing === null || existing === void 0) {
943
- state.output = [...outputParts];
944
- } else {
945
- state.output = [existing, ...outputParts];
946
- }
947
- }
948
- if (stateUpdates) {
949
- Object.assign(state, stateUpdates);
950
- }
951
- if (outputParts.length > 0 && !state.status) {
952
- state.status = "completed";
953
- }
954
- return outputParts.length > 0 || !!stateUpdates;
955
- }
956
- resolveToolCallId(data, item, assistantUuidToToolCalls) {
957
- const directId = String(item["tool_use_id"] ?? "").trim();
958
- if (directId) return directId;
959
- const sourceUuid = String(data["sourceToolAssistantUUID"] ?? "").trim();
960
- if (!sourceUuid) return "";
961
- const ids = assistantUuidToToolCalls.get(sourceUuid);
962
- if (ids && ids.length === 1) return ids[0];
963
- return "";
964
- }
965
- extractToolStateUpdates(toolUseResult) {
966
- if (!toolUseResult || typeof toolUseResult !== "object") return {};
967
- const result = toolUseResult;
968
- const updates = {};
969
- const success = result["success"];
970
- if (typeof success === "boolean") {
971
- updates["status"] = success ? "success" : "error";
972
- }
973
- const commandName = result["commandName"];
974
- if (commandName) {
975
- updates["meta"] = { commandName };
976
- }
977
- return updates;
978
- }
979
- // --- Fallback ---
980
- buildFallbackToolMessage(opts) {
981
- if (opts.outputParts.length === 0) return null;
982
- return this.buildMessage({
983
- messageId: opts.messageId,
984
- role: "tool",
985
- timestampMs: opts.timestampMs,
986
- parts: opts.outputParts
987
- });
988
- }
989
- // --- Utilities ---
990
- shouldIgnoreTool(toolName) {
991
- return toolName === "TodoWrite";
992
- }
993
- appendPartIfNew(message, part) {
994
- const parts = message.parts;
995
- if (parts.length > 0 && parts[parts.length - 1].type === part.type) {
996
- const tail = parts[parts.length - 1];
997
- if (tail.text === part.text) return;
998
- }
999
- parts.push(part);
1000
- }
1001
- };
1002
- var DatabaseConstructor = null;
1003
- try {
1004
- const require2 = createRequire(import.meta.url);
1005
- const mod = require2("better-sqlite3");
1006
- DatabaseConstructor = typeof mod === "function" ? mod : mod.default;
1007
- } catch {
1008
- }
1009
- function openDbReadOnly(dbPath) {
1010
- if (!DatabaseConstructor) return null;
1011
- try {
1012
- const db = DatabaseConstructor(dbPath, { readonly: true });
1013
- return db;
1014
- } catch {
1015
- return null;
1016
- }
1017
- }
1018
- function openDb(dbPath) {
1019
- if (!DatabaseConstructor) return null;
1020
- try {
1021
- mkdirSync(dirname2(dbPath), { recursive: true });
1022
- const db = DatabaseConstructor(dbPath);
1023
- db.pragma("journal_mode = WAL");
1024
- db.pragma("synchronous = NORMAL");
1025
- db.pragma("foreign_keys = ON");
1026
- return db;
1027
- } catch {
1028
- return null;
1029
- }
1030
- }
1031
- function isSqliteAvailable() {
1032
- return DatabaseConstructor !== null;
1033
- }
1034
- var OpenCodeAgent = class extends BaseAgent {
1035
- name = "opencode";
1036
- displayName = "OpenCode";
1037
- dbPath = null;
1038
- // Session metadata for caching
1039
- sessionMetaMap = /* @__PURE__ */ new Map();
1040
- findDbPath() {
1041
- if (!isSqliteAvailable()) return null;
1042
- const roots = resolveProviderRoots();
1043
- return firstExisting(join3(roots.opencodeRoot, "opencode.db"), "data/opencode/opencode.db");
1044
- }
1045
- isAvailable() {
1046
- this.dbPath = this.findDbPath();
1047
- return this.dbPath !== null;
1048
- }
1049
- scan() {
1050
- if (!this.dbPath) return [];
1051
- const db = openDbReadOnly(this.dbPath);
1052
- if (!db) return [];
1053
- try {
1054
- const cutoffTime = Date.now() - 3650 * 24 * 60 * 60 * 1e3;
1055
- const hasMessageTable = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get();
1056
- let rows;
1057
- if (hasMessageTable) {
1058
- rows = db.prepare(`
1059
- SELECT
1060
- s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
1061
- s.version, s.summary_files,
1062
- (SELECT COUNT(*) FROM message m WHERE m.session_id = s.id) AS message_count,
1063
- (SELECT m.data FROM message m
1064
- WHERE m.session_id = s.id AND m.data LIKE '%"modelID"%'
1065
- ORDER BY m.time_created DESC LIMIT 1) AS model_message_data
1066
- FROM session s
1067
- WHERE s.time_created >= ?
1068
- ORDER BY s.time_created DESC
1069
- `).all(cutoffTime);
1070
- } else {
1071
- rows = db.prepare(`
1072
- SELECT s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
1073
- s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data
1074
- FROM session s
1075
- WHERE s.time_created >= ?
1076
- ORDER BY s.time_created DESC
1077
- `).all(cutoffTime);
1078
- }
1079
- const heads = [];
1080
- for (const row of rows) {
1081
- const id = String(row.id ?? "");
1082
- const title = String(row.title ?? "").trim() || "Untitled";
1083
- const timeCreated = Number(row.time_created ?? 0);
1084
- const timeUpdated = Number(row.time_updated ?? timeCreated);
1085
- const slug = `opencode/${id}`;
1086
- const directory = String(row.directory ?? "");
1087
- const stats = hasMessageTable ? this.readSessionStats(db, id) : null;
1088
- heads.push({
1089
- id,
1090
- slug,
1091
- title,
1092
- directory,
1093
- time_created: timeCreated,
1094
- time_updated: timeUpdated,
1095
- stats: {
1096
- message_count: stats?.message_count ?? Number(row.message_count ?? 0),
1097
- total_input_tokens: stats?.total_input_tokens ?? 0,
1098
- total_output_tokens: stats?.total_output_tokens ?? 0,
1099
- total_cost: stats?.total_cost ?? 0
1100
- }
1101
- });
1102
- if (this.dbPath) {
1103
- this.sessionMetaMap.set(id, {
1104
- id,
1105
- sourcePath: this.dbPath
1106
- });
1107
- }
1108
- }
1109
- return heads;
1110
- } catch {
1111
- return [];
1112
- } finally {
1113
- db.close();
1114
- }
1115
- }
1116
- getSessionMetaMap() {
1117
- return this.sessionMetaMap;
1118
- }
1119
- setSessionMetaMap(meta) {
1120
- this.sessionMetaMap = meta;
1121
- }
1122
- /**
1123
- * 检测数据库变更
1124
- * 对于 SQLite,检测数据库文件修改时间
1125
- */
1126
- checkForChanges(sinceTimestamp, cachedSessions) {
1127
- if (!this.dbPath) {
1128
- this.dbPath = this.findDbPath();
1129
- }
1130
- if (!this.dbPath || !existsSync3(this.dbPath)) {
1131
- return { hasChanges: false, timestamp: Date.now() };
1132
- }
1133
- try {
1134
- const stat = statSync2(this.dbPath);
1135
- const hasChanges = stat.mtimeMs > sinceTimestamp;
1136
- const changedIds = hasChanges ? cachedSessions.map((s) => s.id) : [];
1137
- return {
1138
- hasChanges,
1139
- changedIds,
1140
- timestamp: Date.now()
1141
- };
1142
- } catch {
1143
- return { hasChanges: false, timestamp: Date.now() };
1144
- }
1145
- }
1146
- /**
1147
- * 增量扫描 - 重新查询数据库
1148
- */
1149
- incrementalScan(_cachedSessions, _changedIds) {
1150
- return this.scan();
1151
- }
1152
- readSessionStats(db, sessionId) {
1153
- try {
1154
- const rows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
1155
- let totalCost = 0;
1156
- let totalInputTokens = 0;
1157
- let totalOutputTokens = 0;
1158
- for (const row of rows) {
1159
- const msgData = JSON.parse(String(row.data ?? "{}"));
1160
- const cost = Number(msgData.cost ?? 0);
1161
- const tokens = msgData.tokens;
1162
- totalCost += cost;
1163
- totalInputTokens += Number(tokens?.input ?? 0);
1164
- totalOutputTokens += Number(tokens?.output ?? 0);
1165
- }
1166
- return {
1167
- message_count: rows.length,
1168
- total_input_tokens: totalInputTokens,
1169
- total_output_tokens: totalOutputTokens,
1170
- total_cost: totalCost
1171
- };
1172
- } catch {
1173
- return null;
1174
- }
1175
- }
1176
- getSessionData(sessionId) {
1177
- if (!this.dbPath) {
1178
- this.dbPath = this.findDbPath();
1179
- }
1180
- if (!this.dbPath) {
1181
- throw new Error("OpenCode database is missing");
1182
- }
1183
- const db = openDbReadOnly(this.dbPath);
1184
- if (!db) {
1185
- throw new Error("OpenCode database is missing");
1186
- }
1187
- try {
1188
- const sessionRow = db.prepare("SELECT * FROM session WHERE id = ?").get(sessionId);
1189
- if (!sessionRow) {
1190
- throw new Error(`Session not found: ${sessionId}`);
1191
- }
1192
- const id = String(sessionRow.id ?? sessionId);
1193
- const title = String(sessionRow.title ?? "Untitled");
1194
- const slug = `opencode/${id}`;
1195
- const directory = String(sessionRow.directory ?? "");
1196
- const timeCreated = Number(sessionRow.time_created ?? 0);
1197
- const timeUpdated = Number(sessionRow.time_updated ?? timeCreated);
1198
- const messages = [];
1199
- let totalCost = 0;
1200
- let totalInputTokens = 0;
1201
- let totalOutputTokens = 0;
1202
- const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
1203
- for (const msgRow of msgRows) {
1204
- const msgData = JSON.parse(String(msgRow.data ?? "{}"));
1205
- const parts = [];
1206
- const cost = Number(msgData.cost ?? 0);
1207
- const tokens = msgData.tokens;
1208
- const inputTokens = Number(tokens?.input ?? 0);
1209
- const outputTokens = Number(tokens?.output ?? 0);
1210
- totalCost += cost;
1211
- totalInputTokens += inputTokens;
1212
- totalOutputTokens += outputTokens;
1213
- const partRows = db.prepare("SELECT * FROM part WHERE message_id = ? ORDER BY time_created ASC").all(msgRow.id);
1214
- for (const partRow of partRows) {
1215
- const partData = JSON.parse(String(partRow.data ?? "{}"));
1216
- const partType = String(partData.type ?? "");
1217
- if (partType === "text" || partType === "reasoning") {
1218
- parts.push({
1219
- type: partType,
1220
- text: partData.text ?? "",
1221
- time_created: Number(partRow.time_created ?? 0)
1222
- });
1223
- } else if (partType === "tool") {
1224
- parts.push({
1225
- type: "tool",
1226
- tool: String(partData.tool ?? ""),
1227
- callID: String(partData.callID ?? ""),
1228
- title: String(partData.title ?? ""),
1229
- state: partData.state ?? {},
1230
- time_created: Number(partRow.time_created ?? 0)
1231
- });
1232
- }
1233
- }
1234
- messages.push({
1235
- id: String(msgRow.id ?? ""),
1236
- role: String(msgData.role ?? "assistant"),
1237
- agent: msgData.agent ?? null,
1238
- mode: msgData.mode ?? null,
1239
- model: msgData.modelID ?? null,
1240
- provider: msgData.providerID ?? null,
1241
- time_created: Number(msgRow.time_created ?? 0),
1242
- tokens: tokens ? { input: inputTokens, output: outputTokens } : void 0,
1243
- cost,
1244
- parts
1245
- });
1246
- }
1247
- return {
1248
- id,
1249
- title,
1250
- slug,
1251
- directory,
1252
- version: sessionRow.version ?? void 0,
1253
- time_created: timeCreated,
1254
- time_updated: timeUpdated,
1255
- summary_files: sessionRow.summary_files ?? void 0,
1256
- stats: {
1257
- message_count: messages.length,
1258
- total_input_tokens: totalInputTokens,
1259
- total_output_tokens: totalOutputTokens,
1260
- total_cost: totalCost
1261
- },
1262
- messages
1263
- };
1264
- } finally {
1265
- db.close();
1266
- }
1267
- }
1268
- };
1269
- var KIMI_TOOL_TITLE_MAP = {
1270
- ReadFile: "read",
1271
- Glob: "glob",
1272
- StrReplaceFile: "edit",
1273
- Grep: "grep",
1274
- WriteFile: "write",
1275
- Shell: "bash"
1276
- };
1277
- var KIMI_IGNORED_TOOLS = /* @__PURE__ */ new Set(["SetTodoList"]);
1278
- function mapToolTitle(toolName) {
1279
- return KIMI_TOOL_TITLE_MAP[toolName] ?? toolName;
1280
- }
1281
- function normalizeToolArguments(raw) {
1282
- if (typeof raw === "string") {
1283
- try {
1284
- return JSON.parse(raw);
1285
- } catch {
1286
- return raw;
1287
- }
1288
- }
1289
- return raw;
1290
- }
1291
- function normalizeToolOutputParts(content, timestampMs) {
1292
- if (typeof content === "string") {
1293
- return content.trim() ? [{ type: "text", text: content, time_created: timestampMs }] : [];
1294
- }
1295
- if (Array.isArray(content)) {
1296
- const parts = [];
1297
- for (const item of content) {
1298
- if (typeof item === "object" && item !== null && "text" in item) {
1299
- const text2 = String(item.text ?? "");
1300
- if (text2.trim()) parts.push({ type: "text", text: text2, time_created: timestampMs });
1301
- } else if (typeof item === "string" && item.trim()) {
1302
- parts.push({ type: "text", text: item, time_created: timestampMs });
1303
- }
1304
- }
1305
- return parts;
1306
- }
1307
- if (content == null) return [];
1308
- const text = String(content);
1309
- return text.trim() ? [{ type: "text", text, time_created: timestampMs }] : [];
1310
- }
1311
- function normalizeWireToolOutputParts(returnValue, timestampMs) {
1312
- if (returnValue == null) return [];
1313
- if (typeof returnValue === "string") {
1314
- return returnValue.trim() ? [{ type: "text", text: returnValue, time_created: timestampMs }] : [];
1315
- }
1316
- if (typeof returnValue === "object") {
1317
- return [
1318
- { type: "text", text: JSON.stringify(returnValue, null, 2), time_created: timestampMs }
1319
- ];
1320
- }
1321
- const text = String(returnValue);
1322
- return text.trim() ? [{ type: "text", text, time_created: timestampMs }] : [];
1323
- }
1324
- var KimiAgent = class extends BaseAgent {
1325
- name = "kimi";
1326
- displayName = "Kimi-Cli";
1327
- basePath = null;
1328
- sessionMetaMap = /* @__PURE__ */ new Map();
1329
- projectMap = /* @__PURE__ */ new Map();
1330
- findBasePath() {
1331
- const roots = resolveProviderRoots();
1332
- return firstExisting(join4(roots.kimiRoot, "sessions"), "data/kimi");
1333
- }
1334
- /** Parse kimi.json and build md5(project_path) → cwd mapping */
1335
- loadKimiConfig() {
1336
- const roots = resolveProviderRoots();
1337
- const configPath = join4(roots.kimiRoot, "kimi.json");
1338
- if (!existsSync4(configPath)) return;
1339
- try {
1340
- const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1341
- const workDirs = raw?.work_dirs;
1342
- if (!Array.isArray(workDirs)) return;
1343
- for (const wd of workDirs) {
1344
- const path = wd.path;
1345
- if (typeof path !== "string") continue;
1346
- const hash = createHash("md5").update(path).digest("hex");
1347
- this.projectMap.set(hash, path);
1348
- }
1349
- } catch {
1350
- }
1351
- }
1352
- isAvailable() {
1353
- this.basePath = this.findBasePath();
1354
- if (!this.basePath) return false;
1355
- this.loadKimiConfig();
1356
- try {
1357
- return this.listSessionDirs().length > 0;
1358
- } catch {
1359
- }
1360
- return false;
1361
- }
1362
- /** Walk sessions/{project_hash}/{session_id}/ and find valid session dirs */
1363
- listSessionDirs() {
1364
- if (!this.basePath) return [];
1365
- const dirs = [];
1366
- try {
1367
- for (const hashEntry of readdirSync2(this.basePath, { withFileTypes: true })) {
1368
- if (!hashEntry.isDirectory()) continue;
1369
- const hashPath = join4(this.basePath, hashEntry.name);
1370
- try {
1371
- for (const sessionEntry of readdirSync2(hashPath, { withFileTypes: true })) {
1372
- if (!sessionEntry.isDirectory()) continue;
1373
- const sessionPath = join4(hashPath, sessionEntry.name);
1374
- if (existsSync4(join4(sessionPath, "metadata.json")) || existsSync4(join4(sessionPath, "state.json"))) {
1375
- dirs.push(sessionPath);
1376
- }
1377
- }
1378
- } catch {
1379
- }
1380
- }
1381
- } catch {
1382
- }
1383
- return dirs;
1384
- }
1385
- /** Parse session directory, preferring state.json over metadata.json */
1386
- parseSessionDir(sessionDir) {
1387
- try {
1388
- const sessionId = basename3(sessionDir);
1389
- const projectHash = basename3(dirname3(sessionDir));
1390
- const contextFile = join4(sessionDir, "context.jsonl");
1391
- const wireFile = join4(sessionDir, "wire.jsonl");
1392
- if (!existsSync4(contextFile) && !existsSync4(wireFile)) return null;
1393
- const statePath = join4(sessionDir, "state.json");
1394
- const metaPath = join4(sessionDir, "metadata.json");
1395
- let title = "";
1396
- let wireMtime = null;
1397
- let metaFile = "";
1398
- if (existsSync4(statePath)) {
1399
- const state = JSON.parse(readFileSync3(statePath, "utf-8"));
1400
- title = String(state.custom_title ?? "");
1401
- wireMtime = typeof state.wire_mtime === "number" ? state.wire_mtime : null;
1402
- metaFile = statePath;
1403
- } else if (existsSync4(metaPath)) {
1404
- const meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
1405
- title = String(meta.title ?? "");
1406
- wireMtime = typeof meta.wire_mtime === "number" ? meta.wire_mtime : null;
1407
- metaFile = metaPath;
1408
- }
1409
- const cwd = this.projectMap.get(projectHash) || "";
1410
- const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync3(metaFile).mtimeMs : statSync3(sessionDir).mtimeMs;
1411
- return {
1412
- id: sessionId,
1413
- title: title || "Untitled Session",
1414
- sourcePath: sessionDir,
1415
- cwd,
1416
- contextFile: existsSync4(contextFile) ? contextFile : null,
1417
- wireFile: existsSync4(wireFile) ? wireFile : null,
1418
- createdAt,
1419
- metaFile
1420
- };
1421
- } catch {
1422
- return null;
1423
- }
1424
- }
1425
- scan() {
1426
- if (!this.basePath) return [];
1427
- const scanMarker = perf.start("kimi:scan");
1428
- const listMarker = perf.start("listSessionDirs");
1429
- const sessionDirs = this.listSessionDirs();
1430
- perf.end(listMarker);
1431
- const heads = [];
1432
- for (const dir of sessionDirs) {
1433
- try {
1434
- const parseMarker = perf.start(`parseSessionDir:${basename3(dir)}`);
1435
- const meta = this.parseSessionDir(dir);
1436
- perf.end(parseMarker);
1437
- if (!meta) continue;
1438
- this.sessionMetaMap.set(meta.id, meta);
1439
- const stats = this.extractStats(meta.sourcePath);
1440
- heads.push({
1441
- id: meta.id,
1442
- slug: `kimi/${meta.id}`,
1443
- title: meta.title,
1444
- directory: meta.cwd,
1445
- time_created: meta.createdAt,
1446
- time_updated: meta.createdAt,
1447
- stats
1448
- });
1449
- } catch {
1450
- }
1451
- }
1452
- perf.end(scanMarker);
1453
- return heads;
1454
- }
1455
- getSessionMetaMap() {
1456
- return this.sessionMetaMap;
1457
- }
1458
- setSessionMetaMap(meta) {
1459
- this.sessionMetaMap = meta;
1460
- }
1461
- /**
1462
- * 检测文件系统变更
1463
- */
1464
- checkForChanges(sinceTimestamp, cachedSessions) {
1465
- const changedIds = [];
1466
- for (const session of cachedSessions) {
1467
- const meta = this.sessionMetaMap.get(session.id);
1468
- if (!meta) continue;
1469
- try {
1470
- if (meta.metaFile) {
1471
- const stat = statSync3(meta.metaFile);
1472
- if (stat.mtimeMs > sinceTimestamp) {
1473
- changedIds.push(session.id);
1474
- continue;
1475
- }
1476
- }
1477
- const dataFile = meta.wireFile || meta.contextFile;
1478
- if (dataFile) {
1479
- const dataStat = statSync3(dataFile);
1480
- if (dataStat.mtimeMs > sinceTimestamp) {
1481
- changedIds.push(session.id);
1482
- }
1483
- }
1484
- } catch {
1485
- changedIds.push(session.id);
1486
- }
1487
- }
1488
- return {
1489
- hasChanges: changedIds.length > 0,
1490
- changedIds,
1491
- timestamp: Date.now()
1492
- };
1493
- }
1494
- /**
1495
- * 增量扫描
1496
- */
1497
- incrementalScan(cachedSessions, changedIds) {
1498
- const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
1499
- for (const dir of this.listSessionDirs()) {
1500
- try {
1501
- const meta = this.parseSessionDir(dir);
1502
- if (!meta) continue;
1503
- if (changedIds.includes(meta.id)) {
1504
- this.sessionMetaMap.set(meta.id, meta);
1505
- const stats = this.extractStats(meta.sourcePath);
1506
- sessionMap.set(meta.id, {
1507
- id: meta.id,
1508
- slug: `kimi/${meta.id}`,
1509
- title: meta.title,
1510
- directory: meta.cwd,
1511
- time_created: meta.createdAt,
1512
- time_updated: meta.createdAt,
1513
- stats
1514
- });
1515
- }
1516
- } catch {
1517
- }
1518
- }
1519
- return Array.from(sessionMap.values());
1520
- }
1521
- getSessionData(sessionId) {
1522
- const meta = this.sessionMetaMap.get(sessionId);
1523
- if (!meta) throw new Error(`Session not found: ${sessionId}`);
1524
- if (meta.contextFile) {
1525
- return this.getSessionDataFromContext(meta);
1526
- }
1527
- return this.getSessionDataFromWire(meta);
1528
- }
1529
- getSessionDataFromContext(meta) {
1530
- if (!meta.contextFile) throw new Error("context.jsonl is missing");
1531
- const content = readFileSync3(meta.contextFile, "utf-8");
1532
- const messages = [];
1533
- const pendingToolCalls = /* @__PURE__ */ new Map();
1534
- const ignoredToolCallIds = /* @__PURE__ */ new Set();
1535
- let seq = 0;
1536
- const fallbackTs = meta.createdAt;
1537
- for (const record of parseJsonlLines(content)) {
1538
- seq++;
1539
- try {
1540
- const role = String(record.role ?? "");
1541
- if (role === "_checkpoint" || role === "_usage") continue;
1542
- if (role === "user") {
1543
- const text = String(record.content ?? "");
1544
- if (text.trim()) {
1545
- messages.push(
1546
- this.buildMessage({
1547
- messageId: `context-${seq}`,
1548
- role: "user",
1549
- timestampMs: fallbackTs,
1550
- parts: [{ type: "text", text, time_created: fallbackTs }]
1551
- })
1552
- );
1553
- }
1554
- continue;
1555
- }
1556
- if (role === "assistant") {
1557
- const { message, toolIndexes } = this.buildContextAssistantMessage(
1558
- record,
1559
- seq,
1560
- ignoredToolCallIds,
1561
- fallbackTs
1562
- );
1563
- if (!message) continue;
1564
- const msgIndex = messages.length;
1565
- messages.push(message);
1566
- for (const [callId, partIndex] of toolIndexes) {
1567
- pendingToolCalls.set(callId, [msgIndex, partIndex]);
1568
- }
1569
- continue;
1570
- }
1571
- if (role === "tool") {
1572
- const callId = String(record.tool_call_id ?? "").trim();
1573
- if (callId && ignoredToolCallIds.has(callId)) continue;
1574
- const outputParts = normalizeToolOutputParts(record.content, fallbackTs);
1575
- if (callId && this.backfillToolOutput(messages, pendingToolCalls, callId, outputParts)) {
1576
- continue;
1577
- }
1578
- if (outputParts.length > 0) {
1579
- messages.push(
1580
- this.buildMessage({
1581
- messageId: `context-${seq}`,
1582
- role: "tool",
1583
- timestampMs: fallbackTs,
1584
- parts: outputParts
1585
- })
1586
- );
1587
- }
1588
- }
1589
- } catch {
1590
- }
1591
- }
1592
- const stats = this.extractStats(meta.sourcePath);
1593
- return this.buildSessionData(meta, messages, stats);
1594
- }
1595
- getSessionDataFromWire(meta) {
1596
- const wirePath = meta.wireFile ?? join4(meta.sourcePath, "wire.jsonl");
1597
- if (!existsSync4(wirePath)) throw new Error("wire.jsonl is missing");
1598
- const content = readFileSync3(wirePath, "utf-8");
1599
- const messages = [];
1600
- const pendingToolCalls = /* @__PURE__ */ new Map();
1601
- const ignoredToolCallIds = /* @__PURE__ */ new Set();
1602
- const openToolArgumentBuffer = /* @__PURE__ */ new Map();
1603
- let currentAssistantIndex = null;
1604
- let openToolCallId = null;
1605
- let seq = 0;
1606
- for (const record of parseJsonlLines(content)) {
1607
- seq++;
1608
- try {
1609
- const message = record.message ?? {};
1610
- const msgType = String(message.type ?? "");
1611
- const payload = message.payload ?? {};
1612
- const timestamp = Number(record.timestamp ?? 0);
1613
- const timestampMs = Number.isFinite(timestamp) ? Math.floor(timestamp * 1e3) : 0;
1614
- const usage = message["usage"];
1615
- if (usage && typeof usage === "object") {
1616
- const inputTokens = Number(usage["input_tokens"] ?? 0);
1617
- const outputTokens = Number(usage["output_tokens"] ?? 0);
1618
- if (inputTokens || outputTokens) {
1619
- for (let i = messages.length - 1; i >= 0; i--) {
1620
- const msg = messages[i];
1621
- if (msg.role === "assistant" && !msg.tokens) {
1622
- msg.tokens = { input: inputTokens, output: outputTokens };
1623
- break;
1624
- }
1625
- }
1626
- }
1627
- }
1628
- if (msgType === "TurnBegin") {
1629
- const userInput = payload.user_input;
1630
- if (Array.isArray(userInput) && userInput.length > 0) {
1631
- const text = String(userInput[0]?.text ?? "");
1632
- if (text.trim()) {
1633
- messages.push(
1634
- this.buildMessage({
1635
- messageId: `wire-${seq}`,
1636
- role: "user",
1637
- timestampMs,
1638
- parts: [{ type: "text", text, time_created: timestampMs }]
1639
- })
1640
- );
1641
- }
1642
- }
1643
- currentAssistantIndex = null;
1644
- openToolCallId = null;
1645
- continue;
1646
- }
1647
- if (msgType === "ContentPart") {
1648
- currentAssistantIndex = this.getOrCreateWireAssistant(
1649
- messages,
1650
- currentAssistantIndex,
1651
- `wire-${seq}`
1652
- );
1653
- const assistant = messages[currentAssistantIndex];
1654
- const partType = String(payload.type ?? "");
1655
- if (partType === "think") {
1656
- const text = String(payload.think ?? "");
1657
- if (text.trim()) {
1658
- assistant.parts.push({ type: "reasoning", text, time_created: timestampMs });
1659
- }
1660
- } else if (partType === "text") {
1661
- const text = String(payload.text ?? "");
1662
- if (text.trim()) {
1663
- assistant.parts.push({ type: "text", text, time_created: timestampMs });
1664
- }
1665
- }
1666
- continue;
1667
- }
1668
- if (msgType === "ToolCall") {
1669
- const function_ = payload.function;
1670
- const toolName = String(function_?.name ?? "").trim();
1671
- const callId = String(payload.id ?? "").trim();
1672
- if (toolName && callId && KIMI_IGNORED_TOOLS.has(toolName)) {
1673
- ignoredToolCallIds.add(callId);
1674
- openToolCallId = callId;
1675
- continue;
1676
- }
1677
- if (!function_ || !callId || !toolName) continue;
1678
- currentAssistantIndex = this.getOrCreateWireAssistant(
1679
- messages,
1680
- currentAssistantIndex,
1681
- `wire-${seq}`
1682
- );
1683
- const assistant = messages[currentAssistantIndex];
1684
- const rawArgs = function_.arguments;
1685
- const normalizedArgs = normalizeToolArguments(rawArgs);
1686
- const buffer = typeof rawArgs === "string" && typeof normalizedArgs !== "string" ? rawArgs : null;
1687
- const toolPart = {
1688
- type: "tool",
1689
- tool: toolName,
1690
- callID: callId,
1691
- title: mapToolTitle(toolName),
1692
- state: { arguments: normalizedArgs, output: null },
1693
- time_created: timestampMs
1694
- };
1695
- const partIndex = assistant.parts.length;
1696
- assistant.parts.push(toolPart);
1697
- assistant.mode = "tool";
1698
- pendingToolCalls.set(callId, [currentAssistantIndex, partIndex]);
1699
- openToolCallId = callId;
1700
- if (buffer !== null) {
1701
- openToolArgumentBuffer.set(callId, buffer);
1702
- }
1703
- continue;
1704
- }
1705
- if (msgType === "ToolCallPart") {
1706
- if (openToolCallId && ignoredToolCallIds.has(openToolCallId)) continue;
1707
- const argumentsPart = String(payload.arguments_part ?? "");
1708
- this.appendWireToolCallPart(
1709
- argumentsPart,
1710
- openToolCallId,
1711
- openToolArgumentBuffer,
1712
- messages,
1713
- pendingToolCalls
1714
- );
1715
- continue;
1716
- }
1717
- if (msgType === "ToolResult") {
1718
- const callId = String(payload.tool_call_id ?? "").trim();
1719
- if (callId && ignoredToolCallIds.has(callId)) continue;
1720
- const outputParts = normalizeWireToolOutputParts(payload.return_value, timestampMs);
1721
- if (callId && this.backfillToolOutput(messages, pendingToolCalls, callId, outputParts)) {
1722
- continue;
1723
- }
1724
- if (outputParts.length > 0) {
1725
- messages.push(
1726
- this.buildMessage({
1727
- messageId: `wire-${seq}`,
1728
- role: "tool",
1729
- timestampMs,
1730
- parts: outputParts
1731
- })
1732
- );
1733
- }
1734
- continue;
1735
- }
1736
- } catch {
1737
- }
1738
- }
1739
- const filteredMessages = messages.filter((m) => m.parts.length > 0);
1740
- const stats = this.extractStats(meta.sourcePath);
1741
- return this.buildSessionData(meta, filteredMessages, stats);
1742
- }
1743
- // --- Helpers ---
1744
- buildMessage(opts) {
1745
- return {
1746
- id: opts.messageId,
1747
- role: opts.role,
1748
- agent: opts.agent ?? null,
1749
- time_created: opts.timestampMs,
1750
- mode: opts.mode ?? null,
1751
- model: opts.model ?? null,
1752
- provider: opts.provider ?? null,
1753
- tokens: opts.tokens ? opts.tokens : void 0,
1754
- cost: opts.cost ?? 0,
1755
- parts: opts.parts
1756
- };
1757
- }
1758
- buildContextAssistantMessage(record, seq, ignoredToolCallIds, fallbackTs) {
1759
- const parts = [];
1760
- const toolIndexes = /* @__PURE__ */ new Map();
1761
- const content = record.content;
1762
- if (Array.isArray(content)) {
1763
- for (const item of content) {
1764
- if (typeof item !== "object" || item === null) continue;
1765
- const ci = item;
1766
- const partType = String(ci.type ?? "");
1767
- if (partType === "think") {
1768
- const text = String(ci.think ?? "");
1769
- if (text.trim()) parts.push({ type: "reasoning", text, time_created: fallbackTs });
1770
- } else if (partType === "text") {
1771
- const text = String(ci.text ?? "");
1772
- if (text.trim()) parts.push({ type: "text", text, time_created: fallbackTs });
1773
- }
1774
- }
1775
- }
1776
- const toolCalls = record.tool_calls;
1777
- if (Array.isArray(toolCalls)) {
1778
- for (const tc of toolCalls) {
1779
- if (typeof tc !== "object" || tc === null) continue;
1780
- const tcRecord = tc;
1781
- const function_ = tcRecord.function;
1782
- if (!function_) continue;
1783
- const toolName = String(function_.name ?? "").trim();
1784
- const callId = String(tcRecord.id ?? "").trim();
1785
- if (toolName && callId && KIMI_IGNORED_TOOLS.has(toolName)) {
1786
- ignoredToolCallIds.add(callId);
1787
- continue;
1788
- }
1789
- if (!toolName || !callId) continue;
1790
- const part = {
1791
- type: "tool",
1792
- tool: toolName,
1793
- callID: callId,
1794
- title: mapToolTitle(toolName),
1795
- state: { arguments: normalizeToolArguments(function_.arguments), output: null },
1796
- time_created: fallbackTs
1797
- };
1798
- toolIndexes.set(callId, parts.length);
1799
- parts.push(part);
1800
- }
1801
- }
1802
- if (parts.length === 0) {
1803
- return {
1804
- message: this.buildMessage({
1805
- messageId: `context-${seq}`,
1806
- role: "assistant",
1807
- timestampMs: fallbackTs,
1808
- parts: []
1809
- }),
1810
- toolIndexes
1811
- };
1812
- }
1813
- const allTools = parts.every((p) => p.type === "tool");
1814
- const message = this.buildMessage({
1815
- messageId: `context-${seq}`,
1816
- role: "assistant",
1817
- timestampMs: fallbackTs,
1818
- parts,
1819
- agent: "kimi",
1820
- mode: allTools ? "tool" : void 0
1821
- });
1822
- return { message, toolIndexes };
1823
- }
1824
- getOrCreateWireAssistant(messages, currentIndex, messageId) {
1825
- if (currentIndex !== null) return currentIndex;
1826
- messages.push(
1827
- this.buildMessage({
1828
- messageId,
1829
- role: "assistant",
1830
- timestampMs: 0,
1831
- parts: [],
1832
- agent: "kimi"
1833
- })
1834
- );
1835
- return messages.length - 1;
1836
- }
1837
- appendWireToolCallPart(argumentsPart, openCallId, buffer, messages, pendingToolCalls) {
1838
- if (!openCallId || !pendingToolCalls.has(openCallId)) return;
1839
- const existing = buffer.get(openCallId) ?? "";
1840
- const combined = existing + argumentsPart;
1841
- try {
1842
- const parsed = JSON.parse(combined);
1843
- const location = pendingToolCalls.get(openCallId);
1844
- if (!location) return;
1845
- const msgPart = messages[location[0]]?.parts[location[1]];
1846
- if (msgPart?.state) {
1847
- msgPart.state.arguments = parsed;
1848
- }
1849
- buffer.delete(openCallId);
1850
- } catch {
1851
- buffer.set(openCallId, combined);
1852
- }
1853
- }
1854
- backfillToolOutput(messages, pendingToolCalls, callId, outputParts) {
1855
- if (!outputParts.length || !callId) return false;
1856
- const location = pendingToolCalls.get(callId);
1857
- if (!location) return false;
1858
- const part = messages[location[0]]?.parts[location[1]];
1859
- if (!part) return false;
1860
- if (!part.state) part.state = {};
1861
- part.state.output = [...outputParts];
1862
- return true;
1863
- }
1864
- extractStats(sessionDir) {
1865
- const stats = {
1866
- total_cost: 0,
1867
- total_input_tokens: 0,
1868
- total_output_tokens: 0,
1869
- total_tokens: 0,
1870
- message_count: 0
1871
- };
1872
- const wirePath = join4(sessionDir, "wire.jsonl");
1873
- if (!existsSync4(wirePath)) return stats;
1874
- try {
1875
- const content = readFileSync3(wirePath, "utf-8");
1876
- for (const line of content.split("\n").filter((l) => l.trim())) {
1877
- try {
1878
- const data = JSON.parse(line);
1879
- const tokenUsage = data.message?.usage;
1880
- if (!tokenUsage) continue;
1881
- stats.total_input_tokens += Number(tokenUsage.input_tokens ?? 0);
1882
- stats.total_output_tokens += Number(tokenUsage.output_tokens ?? 0);
1883
- } catch {
1884
- }
1885
- }
1886
- } catch {
1887
- }
1888
- const contextPath = join4(sessionDir, "context.jsonl");
1889
- const rawPath = existsSync4(contextPath) ? contextPath : wirePath;
1890
- if (!existsSync4(rawPath)) return stats;
1891
- try {
1892
- const rawContent = readFileSync3(rawPath, "utf-8");
1893
- for (const line of rawContent.split("\n").filter((l) => l.trim())) {
1894
- try {
1895
- const data = JSON.parse(line);
1896
- if (data.role === "_usage" && typeof data.token_count === "number") {
1897
- stats.total_tokens = data.token_count;
1898
- }
1899
- } catch {
1900
- }
1901
- }
1902
- } catch {
1903
- }
1904
- return stats;
1905
- }
1906
- buildSessionData(meta, messages, stats) {
1907
- stats.message_count = messages.length;
1908
- return {
1909
- id: meta.id,
1910
- title: meta.title,
1911
- slug: `kimi/${meta.id}`,
1912
- directory: meta.cwd,
1913
- time_created: meta.createdAt,
1914
- time_updated: meta.createdAt,
1915
- stats,
1916
- messages
1917
- };
1918
- }
1919
- };
1920
- var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
1921
- var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
1922
- var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
1923
- var DEVELOPER_LIKE_USER_MARKERS = [
1924
- "agents.md instructions for",
1925
- "<instructions>",
1926
- "<environment_context>",
1927
- "<permissions instructions>",
1928
- "<collaboration_mode>"
1929
- ];
1930
- function isDeveloperLikeUserMessage(text) {
1931
- const lower = text.toLowerCase();
1932
- return DEVELOPER_LIKE_USER_MARKERS.some((m) => lower.includes(m));
1933
- }
1934
- var CODEX_TOOL_TITLE_MAP = {
1935
- exec_command: "bash",
1936
- apply_patch: "patch",
1937
- patch: "patch",
1938
- spawn_agent: "subagent",
1939
- subagent: "subagent"
1940
- };
1941
- var RECENT_SESSION_REVALIDATION_WINDOW_MS2 = 24 * 60 * 60 * 1e3;
1942
- function extractSessionId(filename) {
1943
- const stem = basename4(filename, ".jsonl");
1944
- const parts = stem.split("-");
1945
- if (parts.length >= 5) {
1946
- return parts.slice(-5).join("-");
1947
- }
1948
- return stem;
1949
- }
1950
- function parseTimestampMs2(data) {
1951
- const ts = String(data["timestamp"] ?? "").trim();
1952
- if (!ts) return 0;
1953
- try {
1954
- return new Date(ts.includes("Z") ? ts : ts.replace(" ", "T") + "Z").getTime();
1955
- } catch {
1956
- return 0;
1957
- }
1958
- }
1959
- function extractModelName(raw) {
1960
- return typeof raw === "string" && raw.trim() ? raw.trim() : null;
1961
- }
1962
- function normalizeTitleText3(text) {
1963
- const line = text.split("\n").find((l) => l.trim());
1964
- return line?.trim().slice(0, 80) || "";
1965
- }
1966
- function mapToolTitle2(name) {
1967
- return CODEX_TOOL_TITLE_MAP[name] ?? name;
1968
- }
1969
- function normalizeToolArguments2(raw) {
1970
- if (typeof raw === "string") {
1971
- try {
1972
- return JSON.parse(raw);
1973
- } catch {
1974
- return raw;
1975
- }
1976
- }
1977
- return raw;
1978
- }
1979
- function normalizeCustomToolArguments(toolName, input) {
1980
- if (toolName === "apply_patch") {
1981
- return parseApplyPatchInput(input);
1982
- }
1983
- return input;
1984
- }
1985
- var PATCH_BEGIN_RE = /\*\*\* Begin Patch/;
1986
- var PATCH_END_RE = /\*\*\* End Patch/;
1987
- var PATCH_HEADER_RE = /\*\*\*\s+(Add|Delete|Update|Move)\s+File:\s*(.+)/;
1988
- var PATCH_MOVE_TO_RE = /\*\*\*\s+Move to:\s*(.+)/;
1989
- function parseApplyPatchInput(input) {
1990
- const text = typeof input === "string" ? input : "";
1991
- if (!text) return [];
1992
- const blocks = [];
1993
- const lines = text.split("\n");
1994
- let inPatch = false;
1995
- let i = 0;
1996
- while (i < lines.length) {
1997
- const line = lines[i];
1998
- if (!inPatch && PATCH_BEGIN_RE.test(line)) {
1999
- inPatch = true;
2000
- i++;
2001
- continue;
2002
- }
2003
- if (inPatch && PATCH_END_RE.test(line)) {
2004
- inPatch = false;
2005
- i++;
2006
- continue;
2007
- }
2008
- if (inPatch) {
2009
- const headerMatch = line.match(PATCH_HEADER_RE);
2010
- if (headerMatch) {
2011
- const action = headerMatch[1];
2012
- const filePath = headerMatch[2].trim();
2013
- i++;
2014
- if (action === "Add") {
2015
- const content = extractPatchContent(lines, i);
2016
- i = content.nextLineIndex;
2017
- blocks.push({ type: "write_file", path: filePath, content: content.text });
2018
- } else if (action === "Update") {
2019
- let moveToTarget = null;
2020
- let contentStart = i;
2021
- for (let j = i; j < lines.length; j++) {
2022
- const l = lines[j];
2023
- if (!l.trim()) continue;
2024
- const moveMatch = l.match(PATCH_MOVE_TO_RE);
2025
- if (moveMatch) {
2026
- moveToTarget = moveMatch[1].trim();
2027
- contentStart = j + 1;
2028
- break;
2029
- }
2030
- break;
2031
- }
2032
- if (moveToTarget) {
2033
- const content = extractPatchContent(lines, contentStart);
2034
- i = content.nextLineIndex;
2035
- blocks.push({
2036
- type: "move_file",
2037
- path: filePath,
2038
- targetPath: moveToTarget,
2039
- content: content.text
2040
- });
2041
- } else {
2042
- const content = extractPatchContent(lines, i);
2043
- i = content.nextLineIndex;
2044
- blocks.push({ type: "edit_file", path: filePath, content: content.text });
2045
- }
2046
- } else if (action === "Delete") {
2047
- blocks.push({ type: "delete_file", path: filePath });
2048
- }
2049
- continue;
2050
- }
2051
- }
2052
- i++;
2053
- }
2054
- return blocks;
2055
- }
2056
- function extractPatchContent(lines, startIndex) {
2057
- const contentLines = [];
2058
- let i = startIndex;
2059
- while (i < lines.length) {
2060
- const line = lines[i];
2061
- if (PATCH_HEADER_RE.test(line) || PATCH_END_RE.test(line)) break;
2062
- contentLines.push(line);
2063
- i++;
2064
- }
2065
- return { text: contentLines.join("\n"), nextLineIndex: i };
2066
- }
2067
- var CodexAgent = class extends BaseAgent {
2068
- name = "codex";
2069
- displayName = "Codex";
2070
- basePath = null;
2071
- sessionIndexCache = /* @__PURE__ */ new Map();
2072
- sessionMetaMap = /* @__PURE__ */ new Map();
2073
- // ---- BaseAgent implementation ----
2074
- findBasePath() {
2075
- const roots = resolveProviderRoots();
2076
- return firstExisting(join5(roots.codexRoot, "sessions"));
2077
- }
2078
- isAvailable() {
2079
- this.basePath = this.findBasePath();
2080
- if (!this.basePath) return false;
2081
- try {
2082
- const files = this.walkDirForRolloutFiles(this.basePath);
2083
- return files.length > 0;
2084
- } catch {
2085
- return false;
2086
- }
2087
- }
2088
- scan() {
2089
- if (!this.basePath) return [];
2090
- const scanMarker = perf.start("codex:scan");
2091
- const indexMarker = perf.start("loadSessionIndex");
2092
- this.loadSessionIndex();
2093
- perf.end(indexMarker);
2094
- const heads = [];
2095
- const listMarker = perf.start("listRolloutFiles");
2096
- const files = this.listRolloutFiles();
2097
- perf.end(listMarker);
2098
- for (const file of files) {
2099
- try {
2100
- const parseMarker = perf.start(`parseSessionHead:${basename4(file)}`);
2101
- const head = this.parseSessionHead(file);
2102
- perf.end(parseMarker);
2103
- if (head) {
2104
- heads.push(head);
2105
- this.sessionMetaMap.set(head.id, {
2106
- id: head.id,
2107
- title: head.title,
2108
- sourcePath: file,
2109
- directory: head.directory,
2110
- model: null,
2111
- messageCount: head.stats.message_count,
2112
- createdAt: head.time_created,
2113
- updatedAt: head.time_updated ?? head.time_created
2114
- });
2115
- }
2116
- } catch {
2117
- }
2118
- }
2119
- perf.end(scanMarker);
2120
- return heads;
2121
- }
2122
- getSessionMetaMap() {
2123
- return this.sessionMetaMap;
2124
- }
2125
- setSessionMetaMap(meta) {
2126
- this.sessionMetaMap = meta;
2127
- }
2128
- /**
2129
- * 检测文件系统变更
2130
- */
2131
- checkForChanges(sinceTimestamp, cachedSessions) {
2132
- if (!this.basePath) {
2133
- return { hasChanges: false, timestamp: Date.now() };
2134
- }
2135
- const now = Date.now();
2136
- const changedIds = /* @__PURE__ */ new Set();
2137
- const currentFiles = this.listRolloutFiles();
2138
- const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
2139
- const cachedIds = new Set(cachedSessions.map((session) => session.id));
2140
- const recentIds = cachedSessions.filter((session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS2).map((session) => session.id);
2141
- for (const sessionId of recentIds) {
2142
- changedIds.add(sessionId);
2143
- }
2144
- for (const session of cachedSessions) {
2145
- const meta = this.sessionMetaMap.get(session.id);
2146
- if (!currentIds.has(session.id)) {
2147
- changedIds.add(session.id);
2148
- continue;
2149
- }
2150
- if (!meta) {
2151
- changedIds.add(session.id);
2152
- continue;
2153
- }
2154
- try {
2155
- const stat = statSync4(meta.sourcePath);
2156
- if (stat.mtimeMs > sinceTimestamp) {
2157
- changedIds.add(session.id);
2158
- }
2159
- } catch {
2160
- changedIds.add(session.id);
2161
- }
2162
- }
2163
- const hasAddedSessions = currentFiles.some((file) => !cachedIds.has(extractSessionId(file)));
2164
- if (recentIds.length > 0) {
2165
- this.sessionIndexCache.clear();
2166
- }
2167
- return {
2168
- hasChanges: changedIds.size > 0 || hasAddedSessions,
2169
- changedIds: Array.from(changedIds),
2170
- timestamp: Date.now()
2171
- };
2172
- }
2173
- /**
2174
- * 增量扫描
2175
- */
2176
- incrementalScan(cachedSessions, changedIds) {
2177
- if (!this.basePath) return cachedSessions;
2178
- const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
2179
- const changedSet = new Set(changedIds);
2180
- const currentFiles = this.listRolloutFiles();
2181
- const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
2182
- for (const session of cachedSessions) {
2183
- if (!currentIds.has(session.id)) {
2184
- sessionMap.delete(session.id);
2185
- this.sessionMetaMap.delete(session.id);
2186
- }
2187
- }
2188
- for (const file of currentFiles) {
2189
- try {
2190
- const sessionId = extractSessionId(file);
2191
- if (changedSet.has(sessionId)) {
2192
- const head = this.parseSessionHead(file);
2193
- if (head) {
2194
- sessionMap.set(head.id, head);
2195
- this.sessionMetaMap.set(head.id, {
2196
- id: head.id,
2197
- title: head.title,
2198
- sourcePath: file,
2199
- directory: head.directory,
2200
- model: null,
2201
- messageCount: head.stats.message_count,
2202
- createdAt: head.time_created,
2203
- updatedAt: head.time_updated ?? head.time_created
2204
- });
2205
- }
2206
- }
2207
- } catch {
2208
- }
2209
- }
2210
- for (const file of currentFiles) {
2211
- try {
2212
- const sessionId = extractSessionId(file);
2213
- if (!sessionMap.has(sessionId)) {
2214
- const head = this.parseSessionHead(file);
2215
- if (head) {
2216
- sessionMap.set(head.id, head);
2217
- this.sessionMetaMap.set(head.id, {
2218
- id: head.id,
2219
- title: head.title,
2220
- sourcePath: file,
2221
- directory: head.directory,
2222
- model: null,
2223
- messageCount: head.stats.message_count,
2224
- createdAt: head.time_created,
2225
- updatedAt: head.time_updated ?? head.time_created
2226
- });
2227
- }
2228
- }
2229
- } catch {
2230
- }
2231
- }
2232
- return Array.from(sessionMap.values());
2233
- }
2234
- getSessionData(sessionId) {
2235
- const meta = this.sessionMetaMap.get(sessionId);
2236
- if (!meta) throw new Error(`Session not found: ${sessionId}`);
2237
- if (!existsSync5(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
2238
- const content = readFileSync4(meta.sourcePath, "utf-8");
2239
- const messages = [];
2240
- const pendingToolCalls = /* @__PURE__ */ new Map();
2241
- let totalInputTokens = 0;
2242
- let totalOutputTokens = 0;
2243
- let currentAssistantIndex = null;
2244
- let latestAssistantTextIndex = null;
2245
- let pendingPlan = null;
2246
- let activeModel = meta.model;
2247
- let prevCumulativeTotal = 0;
2248
- let prevInput = 0;
2249
- let prevOutput = 0;
2250
- let prevReasoning = 0;
2251
- for (const record of parseJsonlLines(content)) {
2252
- try {
2253
- const recordType = String(record["type"] ?? "");
2254
- if (recordType === "turn_context") {
2255
- const payload = record["payload"] ?? {};
2256
- activeModel = extractModelName(payload["model"]) ?? activeModel;
2257
- }
2258
- const result = this.convertRecord(
2259
- record,
2260
- messages,
2261
- pendingToolCalls,
2262
- meta.id,
2263
- currentAssistantIndex,
2264
- latestAssistantTextIndex,
2265
- pendingPlan
2266
- );
2267
- currentAssistantIndex = result.currentAssistantIndex;
2268
- latestAssistantTextIndex = result.latestAssistantTextIndex;
2269
- pendingPlan = result.pendingPlan;
2270
- if (currentAssistantIndex !== null && activeModel) {
2271
- const message = messages[currentAssistantIndex];
2272
- if (message?.role === "assistant" && !message.model) {
2273
- message.model = activeModel;
2274
- }
2275
- }
2276
- if (recordType === "event_msg") {
2277
- const payload = record["payload"] ?? {};
2278
- if (String(payload["type"] ?? "") === "token_count") {
2279
- const info = payload["info"];
2280
- const totalUsage = info?.["total_token_usage"];
2281
- const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
2282
- if (cumulativeTotal > 0 && cumulativeTotal === prevCumulativeTotal) {
2283
- } else {
2284
- prevCumulativeTotal = cumulativeTotal;
2285
- const lastUsage = info?.["last_token_usage"];
2286
- let inputTokens = 0;
2287
- let outputTokens = 0;
2288
- let reasoningTokens = 0;
2289
- if (lastUsage) {
2290
- inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2291
- outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2292
- reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2293
- } else if (cumulativeTotal > 0 && totalUsage) {
2294
- inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
2295
- outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
2296
- reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
2297
- prevInput = Number(totalUsage["input_tokens"] ?? 0);
2298
- prevOutput = Number(totalUsage["output_tokens"] ?? 0);
2299
- prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2300
- }
2301
- const totalInput = Math.max(0, inputTokens);
2302
- if (totalInput || outputTokens || reasoningTokens) {
2303
- totalInputTokens += totalInput;
2304
- totalOutputTokens += outputTokens + reasoningTokens;
2305
- for (let i = messages.length - 1; i >= 0; i--) {
2306
- const msg = messages[i];
2307
- if (msg.role === "assistant" && !msg.tokens) {
2308
- msg.tokens = {
2309
- input: totalInput,
2310
- output: outputTokens + reasoningTokens
2311
- };
2312
- break;
2313
- }
2314
- }
2315
- }
2316
- }
2317
- }
2318
- }
2319
- } catch {
2320
- }
2321
- }
2322
- if (pendingPlan && currentAssistantIndex !== null) {
2323
- messages[currentAssistantIndex].parts.push(pendingPlan);
2324
- }
2325
- return {
2326
- id: meta.id,
2327
- title: meta.title,
2328
- slug: `codex/${meta.id}`,
2329
- directory: meta.directory,
2330
- time_created: meta.createdAt,
2331
- time_updated: meta.updatedAt,
2332
- stats: {
2333
- message_count: messages.length,
2334
- total_input_tokens: totalInputTokens,
2335
- total_output_tokens: totalOutputTokens,
2336
- total_cost: 0
2337
- },
2338
- messages
2339
- };
2340
- }
2341
- // ---- File listing ----
2342
- listRolloutFiles() {
2343
- if (!this.basePath) return [];
2344
- try {
2345
- return this.walkDirForRolloutFiles(this.basePath);
2346
- } catch {
2347
- return [];
2348
- }
2349
- }
2350
- walkDirForRolloutFiles(dir) {
2351
- const files = [];
2352
- try {
2353
- for (const entry of readdirSync3(dir)) {
2354
- const fullPath = join5(dir, entry);
2355
- const stat = statSync4(fullPath);
2356
- if (stat.isDirectory()) {
2357
- files.push(...this.walkDirForRolloutFiles(fullPath));
2358
- } else if (entry.endsWith(".jsonl") && entry.startsWith("rollout-")) {
2359
- files.push(fullPath);
2360
- }
2361
- }
2362
- } catch {
2363
- }
2364
- return files;
2365
- }
2366
- // ---- Session index ----
2367
- loadSessionIndex() {
2368
- if (this.sessionIndexCache.size > 0) return;
2369
- const roots = resolveProviderRoots();
2370
- const indexPath = join5(roots.codexRoot, "session_index.jsonl");
2371
- if (!existsSync5(indexPath)) return;
2372
- try {
2373
- const content = readFileSync4(indexPath, "utf-8");
2374
- for (const record of parseJsonlLines(content)) {
2375
- const sid = String(record["id"] ?? "").trim();
2376
- const threadName = String(record["thread_name"] ?? "").trim();
2377
- if (sid && threadName) {
2378
- this.sessionIndexCache.set(sid, threadName);
2379
- }
2380
- }
2381
- } catch {
2382
- }
2383
- }
2384
- getTitleForSession(sessionId) {
2385
- this.loadSessionIndex();
2386
- return this.sessionIndexCache.get(sessionId) ?? null;
2387
- }
2388
- // ---- Session head parsing ----
2389
- parseSessionHead(filePath) {
2390
- const content = readFileSync4(filePath, "utf-8");
2391
- const lines = content.split("\n").filter((l) => l.trim());
2392
- if (lines.length === 0) return null;
2393
- const sessionId = extractSessionId(filePath);
2394
- let firstRecord;
2395
- try {
2396
- firstRecord = JSON.parse(lines[0]);
2397
- } catch {
2398
- return null;
2399
- }
2400
- const payload = firstRecord["payload"] ?? {};
2401
- const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
2402
- const indexTitle = this.getTitleForSession(sessionId);
2403
- const messageTitle = this.extractTitleFromLines(lines);
2404
- const directoryTitle = basenameTitle(payload["cwd"] ? String(payload["cwd"]) : null);
2405
- const title = resolveSessionTitle(indexTitle, messageTitle, directoryTitle);
2406
- let updatedAt = createdAt;
2407
- let messageCount = 0;
2408
- let model = null;
2409
- let totalInputTokens = 0;
2410
- let totalOutputTokens = 0;
2411
- let scanPrevCumulativeTotal = 0;
2412
- let scanPrevInput = 0;
2413
- let scanPrevOutput = 0;
2414
- let scanPrevReasoning = 0;
2415
- const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
2416
- for (const line of lines) {
2417
- try {
2418
- const data = JSON.parse(line);
2419
- const recordType = String(data["type"] ?? "");
2420
- const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
2421
- if (recordTs > updatedAt) updatedAt = recordTs;
2422
- if (recordType === "session_meta" || recordType === "turn_context") {
2423
- const payload2 = data["payload"] ?? {};
2424
- if (!model) {
2425
- model = extractModelName(payload2["model"]);
2426
- }
2427
- continue;
2428
- }
2429
- if (recordType === "response_item") {
2430
- const p = data["payload"] ?? {};
2431
- const pType = String(p["type"] ?? "");
2432
- if (COUNTED_TYPES.has(pType)) {
2433
- messageCount++;
2434
- }
2435
- if (!model) {
2436
- const info = p["info"];
2437
- const m = info?.["model"] ?? p["model"];
2438
- if (typeof m === "string" && m.trim()) model = m.trim();
2439
- }
2440
- }
2441
- if (recordType === "event_msg") {
2442
- const p = data["payload"] ?? {};
2443
- if (String(p["type"] ?? "") === "token_count") {
2444
- const info = p["info"];
2445
- const totalUsage = info?.["total_token_usage"];
2446
- const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
2447
- if (cumulativeTotal > 0 && cumulativeTotal !== scanPrevCumulativeTotal) {
2448
- scanPrevCumulativeTotal = cumulativeTotal;
2449
- const lastUsage = info?.["last_token_usage"];
2450
- let inputTokens = 0;
2451
- let outputTokens = 0;
2452
- let reasoningTokens = 0;
2453
- if (lastUsage) {
2454
- inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2455
- outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2456
- reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2457
- } else if (cumulativeTotal > 0 && totalUsage) {
2458
- inputTokens = Number(totalUsage["input_tokens"] ?? 0) - scanPrevInput;
2459
- outputTokens = Number(totalUsage["output_tokens"] ?? 0) - scanPrevOutput;
2460
- reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - scanPrevReasoning;
2461
- scanPrevInput = Number(totalUsage["input_tokens"] ?? 0);
2462
- scanPrevOutput = Number(totalUsage["output_tokens"] ?? 0);
2463
- scanPrevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2464
- }
2465
- const totalInput = Math.max(0, inputTokens);
2466
- totalInputTokens += totalInput;
2467
- totalOutputTokens += outputTokens + reasoningTokens;
2468
- }
2469
- }
2470
- }
2471
- } catch {
2472
- }
2473
- }
2474
- const directory = payload["cwd"] ? String(payload["cwd"]) : "";
2475
- return {
2476
- id: sessionId,
2477
- slug: `codex/${sessionId}`,
2478
- title,
2479
- directory,
2480
- time_created: createdAt,
2481
- time_updated: updatedAt,
2482
- stats: {
2483
- message_count: messageCount,
2484
- total_input_tokens: totalInputTokens,
2485
- total_output_tokens: totalOutputTokens,
2486
- total_cost: 0
2487
- }
2488
- };
2489
- }
2490
- extractTitleFromLines(lines) {
2491
- let userMessageCount = 0;
2492
- for (const line of lines.slice(0, 20)) {
2493
- try {
2494
- const data = JSON.parse(line);
2495
- const recordType = String(data["type"] ?? "");
2496
- if (recordType !== "response_item") continue;
2497
- const payload = data["payload"] ?? {};
2498
- const pType = String(payload["type"] ?? "");
2499
- if (pType !== "message") continue;
2500
- if (String(payload["role"] ?? "") !== "user") continue;
2501
- userMessageCount++;
2502
- if (userMessageCount < 2) continue;
2503
- const content = payload["content"];
2504
- if (Array.isArray(content)) {
2505
- const texts = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
2506
- return normalizeTitleText3(texts);
2507
- }
2508
- if (typeof content === "string") {
2509
- return normalizeTitleText3(content);
2510
- }
2511
- } catch {
2512
- }
2513
- }
2514
- return null;
2515
- }
2516
- // ---- Record conversion ----
2517
- convertRecord(data, messages, pendingToolCalls, sessionId, currentAssistantIndex, latestAssistantTextIndex, pendingPlan) {
2518
- const recordType = String(data["type"] ?? "");
2519
- if (recordType === "session_meta" || recordType === "event_msg") {
2520
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2521
- }
2522
- if (recordType !== "response_item") {
2523
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2524
- }
2525
- const payload = data["payload"] ?? {};
2526
- const payloadType = String(payload["type"] ?? "");
2527
- const timestampMs = parseTimestampMs2(data) || parseTimestampMs2(payload);
2528
- switch (payloadType) {
2529
- case "message": {
2530
- const role = String(payload["role"] ?? "");
2531
- if (role === "assistant") {
2532
- return this.convertAssistantMessage(
2533
- payload,
2534
- messages,
2535
- timestampMs,
2536
- currentAssistantIndex,
2537
- latestAssistantTextIndex,
2538
- pendingPlan
2539
- );
2540
- }
2541
- if (role === "user") {
2542
- return this.convertUserMessage(
2543
- payload,
2544
- messages,
2545
- timestampMs,
2546
- currentAssistantIndex,
2547
- latestAssistantTextIndex,
2548
- pendingPlan
2549
- );
2550
- }
2551
- break;
2552
- }
2553
- case "reasoning":
2554
- return this.convertReasoning(payload, messages, timestampMs, currentAssistantIndex);
2555
- case "function_call":
2556
- return this.convertFunctionCall(
2557
- payload,
2558
- messages,
2559
- pendingToolCalls,
2560
- timestampMs,
2561
- currentAssistantIndex,
2562
- latestAssistantTextIndex
2563
- );
2564
- case "function_call_output":
2565
- this.convertFunctionCallOutput(payload, messages, pendingToolCalls, timestampMs);
2566
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2567
- case "custom_tool_call":
2568
- return this.convertCustomToolCall(
2569
- payload,
2570
- messages,
2571
- pendingToolCalls,
2572
- timestampMs,
2573
- currentAssistantIndex,
2574
- latestAssistantTextIndex
2575
- );
2576
- case "custom_tool_call_output":
2577
- this.convertCustomToolCallOutput(payload, messages, pendingToolCalls, timestampMs);
2578
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2579
- }
2580
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2581
- }
2582
- // ---- Assistant message ----
2583
- convertAssistantMessage(payload, messages, timestampMs, currentAssistantIndex, latestAssistantTextIndex, pendingPlan) {
2584
- const content = payload["content"];
2585
- if (!Array.isArray(content)) {
2586
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2587
- }
2588
- const textParts = [];
2589
- for (const item of content) {
2590
- if (typeof item !== "object" || item === null) continue;
2591
- const ci = item;
2592
- if (String(ci["type"] ?? "") === "output_text") {
2593
- const text = String(ci["text"] ?? "");
2594
- if (text.trim()) textParts.push(text);
2595
- }
2596
- }
2597
- if (textParts.length === 0) {
2598
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2599
- }
2600
- const fullText = textParts.join("\n");
2601
- const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
2602
- if (planMatch) {
2603
- const planText = planMatch[1].trim();
2604
- const planPart = {
2605
- type: "plan",
2606
- text: planText,
2607
- approval_status: "success",
2608
- time_created: timestampMs
2609
- };
2610
- pendingPlan = planPart;
2611
- }
2612
- const displayText = fullText.replace(PROPOSED_PLAN_PATTERN, "").trim();
2613
- if (!displayText) {
2614
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2615
- }
2616
- const textPart = { type: "text", text: displayText, time_created: timestampMs };
2617
- if (currentAssistantIndex !== null) {
2618
- const message = messages[currentAssistantIndex];
2619
- const hasTool = message.parts.some((p) => p.type === "tool");
2620
- if (!hasTool) {
2621
- message.parts.push(textPart);
2622
- latestAssistantTextIndex = currentAssistantIndex;
2623
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2624
- }
2625
- }
2626
- messages.push(
2627
- this.buildMessage({
2628
- messageId: "",
2629
- role: "assistant",
2630
- timestampMs,
2631
- parts: [textPart],
2632
- agent: "codex"
2633
- })
2634
- );
2635
- currentAssistantIndex = messages.length - 1;
2636
- latestAssistantTextIndex = currentAssistantIndex;
2637
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2638
- }
2639
- // ---- User message ----
2640
- convertUserMessage(payload, messages, timestampMs, currentAssistantIndex, latestAssistantTextIndex, pendingPlan) {
2641
- const content = payload["content"];
2642
- const text = Array.isArray(content) ? content.map(
2643
- (c) => typeof c === "object" && c !== null ? String(c["text"] ?? "") : String(c ?? "")
2644
- ).join(" ") : String(content ?? "");
2645
- if (!text.trim()) {
2646
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2647
- }
2648
- if (isDeveloperLikeUserMessage(text)) {
2649
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2650
- }
2651
- if (text.trimStart().startsWith(PLAN_APPROVAL_PREFIX)) {
2652
- if (pendingPlan && currentAssistantIndex !== null) {
2653
- messages[currentAssistantIndex].parts.push(pendingPlan);
2654
- }
2655
- pendingPlan = null;
2656
- messages.push(
2657
- this.buildMessage({
2658
- messageId: "",
2659
- role: "user",
2660
- timestampMs,
2661
- parts: [{ type: "text", text: text.trim(), time_created: timestampMs }]
2662
- })
2663
- );
2664
- currentAssistantIndex = null;
2665
- latestAssistantTextIndex = null;
2666
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2667
- }
2668
- const subagentMatch = text.match(SUBAGENT_NOTIFICATION_PATTERN);
2669
- if (subagentMatch) {
2670
- try {
2671
- const notifPayload = JSON.parse(subagentMatch[1]);
2672
- const agentId = String(notifPayload["agent_id"] ?? "");
2673
- const nickname = String(notifPayload["nickname"] ?? "");
2674
- const completedText = String(notifPayload["completed"] ?? "");
2675
- const textPart = {
2676
- type: "text",
2677
- text: completedText || `Subagent ${nickname} completed`,
2678
- time_created: timestampMs
2679
- };
2680
- messages.push(
2681
- this.buildMessage({
2682
- messageId: "",
2683
- role: "assistant",
2684
- timestampMs,
2685
- parts: [textPart],
2686
- agent: "codex",
2687
- subagent_id: agentId || void 0,
2688
- nickname: nickname || void 0
2689
- })
2690
- );
2691
- currentAssistantIndex = null;
2692
- latestAssistantTextIndex = null;
2693
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2694
- } catch {
2695
- }
2696
- }
2697
- messages.push(
2698
- this.buildMessage({
2699
- messageId: "",
2700
- role: "user",
2701
- timestampMs,
2702
- parts: [{ type: "text", text: text.trim(), time_created: timestampMs }]
2703
- })
2704
- );
2705
- currentAssistantIndex = null;
2706
- latestAssistantTextIndex = null;
2707
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan };
2708
- }
2709
- // ---- Reasoning ----
2710
- convertReasoning(payload, messages, timestampMs, currentAssistantIndex) {
2711
- const summary = payload["summary"];
2712
- if (!Array.isArray(summary)) {
2713
- return { currentAssistantIndex, latestAssistantTextIndex: null, pendingPlan: null };
2714
- }
2715
- const texts = [];
2716
- for (const item of summary) {
2717
- if (typeof item === "object" && item !== null) {
2718
- const ci = item;
2719
- if (String(ci["type"] ?? "") === "summary_text") {
2720
- const text = String(ci["text"] ?? "");
2721
- if (text.trim()) texts.push(text);
2722
- }
2723
- }
2724
- }
2725
- if (texts.length === 0) {
2726
- return { currentAssistantIndex, latestAssistantTextIndex: null, pendingPlan: null };
2727
- }
2728
- const reasoningText = texts.join("\n");
2729
- const part = { type: "reasoning", text: reasoningText, time_created: timestampMs };
2730
- if (currentAssistantIndex !== null) {
2731
- const message = messages[currentAssistantIndex];
2732
- const hasText = message.parts.some((p) => p.type === "text");
2733
- const hasTool = message.parts.some((p) => p.type === "tool");
2734
- if (!hasText && !hasTool) {
2735
- message.parts.push(part);
2736
- return { currentAssistantIndex, latestAssistantTextIndex: null, pendingPlan: null };
2737
- }
2738
- }
2739
- messages.push(
2740
- this.buildMessage({
2741
- messageId: "",
2742
- role: "assistant",
2743
- timestampMs,
2744
- parts: [part],
2745
- agent: "codex"
2746
- })
2747
- );
2748
- return {
2749
- currentAssistantIndex: messages.length - 1,
2750
- latestAssistantTextIndex: null,
2751
- pendingPlan: null
2752
- };
2753
- }
2754
- // ---- Function call ----
2755
- convertFunctionCall(payload, messages, pendingToolCalls, timestampMs, currentAssistantIndex, latestAssistantTextIndex) {
2756
- const callId = String(payload["call_id"] ?? "").trim();
2757
- const name = String(payload["name"] ?? "").trim();
2758
- if (!name) {
2759
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan: null };
2760
- }
2761
- const mappedName = mapToolTitle2(name);
2762
- const arguments_ = normalizeToolArguments2(payload["arguments"]);
2763
- const toolPart = {
2764
- type: "tool",
2765
- tool: mappedName,
2766
- callID: callId,
2767
- title: `Tool: ${mappedName}`,
2768
- state: {
2769
- arguments: arguments_,
2770
- output: null
2771
- },
2772
- time_created: timestampMs
2773
- };
2774
- const targetIndex = latestAssistantTextIndex ?? currentAssistantIndex;
2775
- if (targetIndex !== null) {
2776
- const message = messages[targetIndex];
2777
- const partIndex = message.parts.length;
2778
- message.parts.push(toolPart);
2779
- message.mode = "tool";
2780
- if (callId) {
2781
- pendingToolCalls.set(callId, [targetIndex, partIndex]);
2782
- }
2783
- return {
2784
- currentAssistantIndex: targetIndex,
2785
- latestAssistantTextIndex: targetIndex,
2786
- pendingPlan: null
2787
- };
2788
- }
2789
- messages.push(
2790
- this.buildMessage({
2791
- messageId: "",
2792
- role: "assistant",
2793
- timestampMs,
2794
- parts: [toolPart],
2795
- agent: "codex",
2796
- mode: "tool"
2797
- })
2798
- );
2799
- const newIndex = messages.length - 1;
2800
- if (callId) {
2801
- pendingToolCalls.set(callId, [newIndex, 0]);
2802
- }
2803
- return { currentAssistantIndex: newIndex, latestAssistantTextIndex: null, pendingPlan: null };
2804
- }
2805
- // ---- Function call output ----
2806
- convertFunctionCallOutput(payload, messages, pendingToolCalls, timestampMs) {
2807
- const callId = String(payload["call_id"] ?? "").trim();
2808
- if (!callId) return;
2809
- const location = pendingToolCalls.get(callId);
2810
- if (!location) return;
2811
- const outputText = String(payload["output"] ?? "");
2812
- const outputParts = outputText.trim() ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
2813
- const [msgIndex, partIndex] = location;
2814
- const state = messages[msgIndex].parts[partIndex].state ?? (messages[msgIndex].parts[partIndex].state = {});
2815
- if (outputParts.length > 0) {
2816
- state.output = [...outputParts];
2817
- state.status = "completed";
2818
- }
2819
- }
2820
- // ---- Custom tool call ----
2821
- convertCustomToolCall(payload, messages, pendingToolCalls, timestampMs, currentAssistantIndex, latestAssistantTextIndex) {
2822
- const callId = String(payload["call_id"] ?? "").trim();
2823
- const name = String(payload["name"] ?? "").trim();
2824
- if (!name) {
2825
- return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan: null };
2826
- }
2827
- const mappedName = mapToolTitle2(name);
2828
- const rawInput = payload["input"];
2829
- const normalizedInput = normalizeCustomToolArguments(name, rawInput);
2830
- const toolPart = {
2831
- type: "tool",
2832
- tool: mappedName,
2833
- callID: callId,
2834
- title: `Tool: ${mappedName}`,
2835
- state: {
2836
- arguments: normalizedInput,
2837
- output: null
2838
- },
2839
- time_created: timestampMs
2840
- };
2841
- const targetIndex = latestAssistantTextIndex ?? currentAssistantIndex;
2842
- if (targetIndex !== null) {
2843
- const message = messages[targetIndex];
2844
- const partIndex = message.parts.length;
2845
- message.parts.push(toolPart);
2846
- message.mode = "tool";
2847
- if (callId) {
2848
- pendingToolCalls.set(callId, [targetIndex, partIndex]);
2849
- }
2850
- return {
2851
- currentAssistantIndex: targetIndex,
2852
- latestAssistantTextIndex: targetIndex,
2853
- pendingPlan: null
2854
- };
2855
- }
2856
- messages.push(
2857
- this.buildMessage({
2858
- messageId: "",
2859
- role: "assistant",
2860
- timestampMs,
2861
- parts: [toolPart],
2862
- agent: "codex",
2863
- mode: "tool"
2864
- })
2865
- );
2866
- const newIndex = messages.length - 1;
2867
- if (callId) {
2868
- pendingToolCalls.set(callId, [newIndex, 0]);
2869
- }
2870
- return { currentAssistantIndex: newIndex, latestAssistantTextIndex: null, pendingPlan: null };
2871
- }
2872
- // ---- Custom tool call output ----
2873
- convertCustomToolCallOutput(payload, messages, pendingToolCalls, timestampMs) {
2874
- const callId = String(payload["call_id"] ?? "").trim();
2875
- if (!callId) return;
2876
- const location = pendingToolCalls.get(callId);
2877
- if (!location) return;
2878
- const outputText = String(payload["output"] ?? "");
2879
- const outputParts = outputText.trim() ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
2880
- const [msgIndex, partIndex] = location;
2881
- const state = messages[msgIndex].parts[partIndex].state ?? (messages[msgIndex].parts[partIndex].state = {});
2882
- if (outputParts.length > 0) {
2883
- state.output = [...outputParts];
2884
- state.status = "completed";
2885
- }
2886
- }
2887
- // ---- Message builder ----
2888
- buildMessage(opts) {
2889
- return {
2890
- id: opts.messageId,
2891
- role: opts.role,
2892
- agent: opts.agent ?? null,
2893
- time_created: opts.timestampMs,
2894
- mode: opts.mode ?? null,
2895
- model: opts.model ?? null,
2896
- provider: opts.provider ?? null,
2897
- tokens: opts.tokens ? opts.tokens : void 0,
2898
- cost: opts.cost ?? 0,
2899
- parts: opts.parts,
2900
- subagent_id: opts.subagent_id,
2901
- nickname: opts.nickname
2902
- };
2903
- }
2904
- };
2905
- var CURSOR_TOOL_TITLE_MAP = {
2906
- read_file_v2: "read",
2907
- edit_file_v2: "edit",
2908
- run_terminal_command_v2: "bash",
2909
- ripgrep_raw_search: "grep",
2910
- glob_file_search: "glob"
2911
- };
2912
- function mapToolTitle3(toolName) {
2913
- return CURSOR_TOOL_TITLE_MAP[toolName] ?? toolName;
2914
- }
2915
- function normalizeToolOutputParts2(output, timestampMs) {
2916
- if (output == null) return [];
2917
- if (typeof output === "string") {
2918
- return output.trim() ? [{ type: "text", text: output, time_created: timestampMs }] : [];
2919
- }
2920
- if (Array.isArray(output)) {
2921
- const parts = [];
2922
- for (const item of output) {
2923
- if (typeof item === "object" && item !== null) {
2924
- const text2 = String(
2925
- item.text ?? item.content ?? ""
2926
- );
2927
- if (text2.trim()) parts.push({ type: "text", text: text2, time_created: timestampMs });
2928
- } else if (typeof item === "string" && item.trim()) {
2929
- parts.push({ type: "text", text: item, time_created: timestampMs });
2930
- }
2931
- }
2932
- return parts;
2933
- }
2934
- const text = String(output);
2935
- return text.trim() ? [{ type: "text", text, time_created: timestampMs }] : [];
2936
- }
2937
- function extractTimestamp(msg) {
2938
- if (msg.createdAt && typeof msg.createdAt === "number" && msg.createdAt > 0) {
2939
- return msg.createdAt;
2940
- }
2941
- if (msg.timestamp && typeof msg.timestamp === "number" && msg.timestamp > 0) {
2942
- return msg.timestamp;
2943
- }
2944
- return 0;
2945
- }
2946
- function buildToolState(action) {
2947
- const state = {};
2948
- if (action.input) {
2949
- state.input = action.input;
2950
- }
2951
- if (action.output != null) {
2952
- const ts = 0;
2953
- const outputParts = normalizeToolOutputParts2(action.output, ts);
2954
- state.output = outputParts.length > 0 ? outputParts : action.output;
2955
- }
2956
- if (action.state) {
2957
- Object.assign(state, action.state);
2958
- }
2959
- if (!state.status) {
2960
- if (typeof action.output === "object" && action.output !== null) {
2961
- const out = action.output;
2962
- if (out.success === true) state.status = "completed";
2963
- else if (out.success === false) state.status = "error";
2964
- else state.status = "completed";
2965
- } else if (action.output != null) {
2966
- state.status = "completed";
2967
- }
2968
- }
2969
- return state;
2970
- }
2971
- function buildToolPart(action, timestampMs) {
2972
- const toolName = action.tool ?? "unknown";
2973
- return {
2974
- type: "tool",
2975
- tool: mapToolTitle3(toolName),
2976
- callID: action.type ? `${action.type}:${String(action.input?.id ?? "")}` : "",
2977
- title: `Tool: ${mapToolTitle3(toolName)}`,
2978
- state: buildToolState(action),
2979
- time_created: timestampMs
2980
- };
2981
- }
2982
- function buildTerminalToolPart(action, timestampMs) {
2983
- const command = String(action.input?.command ?? "");
2984
- const description = String(action.input?.commandDescription ?? "");
2985
- return {
2986
- type: "tool",
2987
- tool: "bash",
2988
- callID: "",
2989
- title: description || `bash: ${command.slice(0, 60)}`,
2990
- state: {
2991
- input: { command },
2992
- output: typeof action.output === "string" ? [{ type: "text", text: action.output, time_created: timestampMs }] : normalizeToolOutputParts2(action.output, timestampMs)
2993
- },
2994
- time_created: timestampMs
2995
- };
2996
- }
2997
- function convertActionToPart(action, timestampMs) {
2998
- const toolName = action.tool ?? "";
2999
- if (toolName === "run_terminal_command_v2") {
3000
- return buildTerminalToolPart(action, timestampMs);
3001
- }
3002
- if (toolName && action.type === "tool") {
3003
- return buildToolPart(action, timestampMs);
3004
- }
3005
- return null;
3006
- }
3007
- var CursorAgent = class extends BaseAgent {
3008
- name = "cursor";
3009
- displayName = "Cursor";
3010
- dbPath = null;
3011
- // Cache composer data from scan so getSessionData can reuse it
3012
- composerCache = /* @__PURE__ */ new Map();
3013
- // Session metadata for caching
3014
- sessionMetaMap = /* @__PURE__ */ new Map();
3015
- findDbPath() {
3016
- if (!isSqliteAvailable()) return null;
3017
- const dataPath = getCursorDataPath();
3018
- if (!dataPath) return null;
3019
- return join6(dataPath, "globalStorage", "state.vscdb");
3020
- }
3021
- /**
3022
- * Build a map of composerId → workspace folder path by reading
3023
- * workspaceStorage/{id}/workspace.json and the corresponding state.vscdb.
3024
- */
3025
- buildWorkspacePathMap() {
3026
- const map = /* @__PURE__ */ new Map();
3027
- const dataPath = getCursorDataPath();
3028
- if (!dataPath) return map;
3029
- const wsStoragePath = join6(dataPath, "workspaceStorage");
3030
- if (!existsSync6(wsStoragePath)) return map;
3031
- let entryNames;
3032
- try {
3033
- entryNames = readdirSync4(wsStoragePath);
3034
- } catch {
3035
- return map;
3036
- }
3037
- for (const name of entryNames) {
3038
- const wsDir = join6(wsStoragePath, name);
3039
- try {
3040
- if (!statSync5(wsDir).isDirectory()) continue;
3041
- } catch {
3042
- continue;
3043
- }
3044
- const wsJsonPath = join6(wsDir, "workspace.json");
3045
- if (!existsSync6(wsJsonPath)) continue;
3046
- let workspacePath;
3047
- try {
3048
- const data = JSON.parse(readFileSync5(wsJsonPath, "utf-8"));
3049
- const uri = data.folder ?? data.workspace ?? "";
3050
- if (!uri) continue;
3051
- workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
3052
- } catch {
3053
- continue;
3054
- }
3055
- const wsDbPath = join6(wsDir, "state.vscdb");
3056
- if (!existsSync6(wsDbPath)) continue;
3057
- const wsDb = openDbReadOnly(wsDbPath);
3058
- if (!wsDb) continue;
3059
- try {
3060
- const row = wsDb.prepare("SELECT value FROM ItemTable WHERE key = 'composer.composerData'").get();
3061
- if (!row?.value) continue;
3062
- const parsed = JSON.parse(row.value);
3063
- let composers;
3064
- if (parsed !== null && typeof parsed === "object" && "allComposers" in parsed && Array.isArray(parsed["allComposers"])) {
3065
- composers = parsed.allComposers;
3066
- } else if (Array.isArray(parsed)) {
3067
- composers = parsed;
3068
- } else {
3069
- continue;
3070
- }
3071
- for (const c of composers) {
3072
- const id = c.composerId ?? c.id;
3073
- if (id) map.set(id, workspacePath);
3074
- }
3075
- } catch {
3076
- } finally {
3077
- wsDb.close();
3078
- }
3079
- }
3080
- return map;
3081
- }
3082
- isAvailable() {
3083
- this.dbPath = this.findDbPath();
3084
- return this.dbPath !== null && existsSync6(this.dbPath);
3085
- }
3086
- scan() {
3087
- if (!this.dbPath) return [];
3088
- const scanMarker = perf.start("cursor:scan");
3089
- const dbMarker = perf.start("openDatabase");
3090
- const db = this.openDatabase();
3091
- perf.end(dbMarker);
3092
- if (!db) return [];
3093
- const wsMarker = perf.start("buildWorkspacePathMap");
3094
- const workspacePathMap = this.buildWorkspacePathMap();
3095
- perf.end(wsMarker);
3096
- try {
3097
- const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'").all();
3098
- const heads = [];
3099
- for (const row of rows) {
3100
- try {
3101
- const composer = JSON.parse(row.value);
3102
- if (!composer.id && !composer.composerId) continue;
3103
- const composerId = composer.id || composer.composerId || "";
3104
- const requestId = this.extractRequestIdFromBubbles(db, composerId);
3105
- const sessionId = requestId || composerId;
3106
- const title = this.extractTitle(composer);
3107
- const createdAt = composer.createdAt ?? 0;
3108
- const updatedAt = composer.updatedAt ?? createdAt;
3109
- const messages = this.loadMessagesFromBubbles(
3110
- db,
3111
- composerId,
3112
- sessionId,
3113
- composer.modelConfig?.modelName ?? composer.model ?? null
3114
- );
3115
- const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
3116
- if (messages.length === 0 && !hasSubagents) {
3117
- continue;
3118
- }
3119
- const messageCount = messages.length;
3120
- const directory = workspacePathMap.get(composerId) ?? "";
3121
- const modelUsageMap = {};
3122
- for (const msg of messages) {
3123
- if (msg.model) {
3124
- const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
3125
- if (msgTokens > 0) {
3126
- modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
3127
- }
3128
- }
3129
- }
3130
- const hasModelUsage = Object.keys(modelUsageMap).length > 0;
3131
- heads.push({
3132
- id: sessionId,
3133
- slug: `cursor/${sessionId}`,
3134
- title,
3135
- directory,
3136
- time_created: createdAt,
3137
- time_updated: updatedAt || void 0,
3138
- stats: {
3139
- message_count: messageCount,
3140
- total_input_tokens: composer.inputTokenCount ?? 0,
3141
- total_output_tokens: composer.outputTokenCount ?? 0,
3142
- total_cost: 0
3143
- },
3144
- model_usage: hasModelUsage ? modelUsageMap : void 0
3145
- });
3146
- this.composerCache.set(sessionId, composer);
3147
- this.composerCache.set(`__mapping__${composerId}`, {
3148
- sessionId
3149
- });
3150
- if (directory) {
3151
- this.composerCache.set(`__dir__${composerId}`, {
3152
- directory
3153
- });
3154
- }
3155
- this.sessionMetaMap.set(sessionId, {
3156
- id: sessionId,
3157
- sourcePath: this.dbPath || ""
3158
- });
3159
- } catch {
3160
- }
3161
- }
3162
- perf.end(scanMarker);
3163
- return heads;
3164
- } catch {
3165
- return [];
3166
- } finally {
3167
- db.close();
3168
- }
3169
- }
3170
- getSessionMetaMap() {
3171
- return this.sessionMetaMap;
3172
- }
3173
- setSessionMetaMap(meta) {
3174
- this.sessionMetaMap = meta;
3175
- }
3176
- /**
3177
- * 检测数据库变更
3178
- * 对于 SQLite,检测数据库文件修改时间
3179
- */
3180
- checkForChanges(sinceTimestamp, cachedSessions) {
3181
- if (!this.dbPath) {
3182
- this.dbPath = this.findDbPath();
3183
- }
3184
- if (!this.dbPath || !existsSync6(this.dbPath)) {
3185
- return { hasChanges: false, timestamp: Date.now() };
3186
- }
3187
- try {
3188
- const stat = statSync5(this.dbPath);
3189
- const hasChanges = stat.mtimeMs > sinceTimestamp;
3190
- const changedIds = hasChanges ? cachedSessions.map((s) => s.id) : [];
3191
- return {
3192
- hasChanges,
3193
- changedIds,
3194
- timestamp: Date.now()
3195
- };
3196
- } catch {
3197
- return { hasChanges: false, timestamp: Date.now() };
3198
- }
3199
- }
3200
- /**
3201
- * 增量扫描 - 重新查询数据库
3202
- */
3203
- incrementalScan(_cachedSessions, _changedIds) {
3204
- return this.scan();
3205
- }
3206
- getSessionData(sessionId) {
3207
- if (!this.dbPath) {
3208
- this.dbPath = this.findDbPath();
3209
- }
3210
- if (!this.dbPath) {
3211
- throw new Error("Cursor database is missing");
3212
- }
3213
- const db = this.openDatabase();
3214
- if (!db) {
3215
- throw new Error("Cursor database is missing");
3216
- }
3217
- try {
3218
- let composer = this.composerCache.get(sessionId);
3219
- let resolvedSessionId = sessionId;
3220
- if (!composer) {
3221
- composer = this.loadComposer(db, sessionId) ?? void 0;
3222
- }
3223
- if (!composer) {
3224
- const composerId2 = this.findComposerIdByRequestId(db, sessionId);
3225
- if (composerId2) {
3226
- composer = this.loadComposer(db, composerId2) ?? void 0;
3227
- resolvedSessionId = sessionId;
3228
- }
3229
- }
3230
- if (!composer) {
3231
- throw new Error(`Session not found: ${sessionId}`);
3232
- }
3233
- const composerId = composer.id || composer.composerId || "";
3234
- const title = this.extractTitle(composer);
3235
- const createdAt = composer.createdAt ?? 0;
3236
- const updatedAt = composer.updatedAt ?? createdAt;
3237
- const messages = this.loadMessagesFromBubbles(
3238
- db,
3239
- composerId,
3240
- resolvedSessionId,
3241
- composer.modelConfig?.modelName ?? composer.model ?? null
3242
- );
3243
- let totalInputTokens = 0;
3244
- let totalOutputTokens = 0;
3245
- for (const msg of messages) {
3246
- totalInputTokens += msg.tokens?.input ?? 0;
3247
- totalOutputTokens += msg.tokens?.output ?? 0;
3248
- }
3249
- if (totalInputTokens === 0) totalInputTokens = composer.inputTokenCount ?? 0;
3250
- if (totalOutputTokens === 0) totalOutputTokens = composer.outputTokenCount ?? 0;
3251
- this.appendSubagentMessages(db, composer, messages);
3252
- const cachedDir = this.composerCache.get(`__dir__${composerId}`);
3253
- const directory = cachedDir?.directory ?? this.buildWorkspacePathMap().get(composerId) ?? "";
3254
- return {
3255
- id: resolvedSessionId,
3256
- title,
3257
- slug: `cursor/${resolvedSessionId}`,
3258
- directory,
3259
- time_created: createdAt,
3260
- time_updated: updatedAt || void 0,
3261
- stats: {
3262
- message_count: messages.length,
3263
- total_input_tokens: totalInputTokens,
3264
- total_output_tokens: totalOutputTokens,
3265
- total_cost: 0
3266
- },
3267
- messages
3268
- };
3269
- } finally {
3270
- db.close();
3271
- }
3272
- }
3273
- // --- Private helpers ---
3274
- openDatabase() {
3275
- if (!this.dbPath) return null;
3276
- return openDbReadOnly(this.dbPath);
3277
- }
3278
- /** Extract requestId from bubbles for a composer (like agent-dump) */
3279
- extractRequestIdFromBubbles(db, composerId) {
3280
- try {
3281
- const rows = db.prepare("SELECT value FROM cursorDiskKV WHERE key LIKE ? ORDER BY key").all(`bubbleId:${composerId}:%`);
3282
- for (const row of rows) {
3283
- try {
3284
- const bubble = JSON.parse(row.value);
3285
- if (bubble.requestId && typeof bubble.requestId === "string" && bubble.requestId.trim()) {
3286
- return bubble.requestId.trim();
3287
- }
3288
- } catch {
3289
- }
3290
- }
3291
- } catch {
3292
- }
3293
- return null;
3294
- }
3295
- /** Find composerId by requestId (reverse lookup) */
3296
- findComposerIdByRequestId(db, requestId) {
3297
- try {
3298
- const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%' AND value LIKE ?").all(`%"requestId":"${requestId}"%`);
3299
- for (const row of rows) {
3300
- try {
3301
- const bubble = JSON.parse(row.value);
3302
- if (bubble.requestId === requestId) {
3303
- const keyParts = row.key.split(":");
3304
- if (keyParts.length >= 2 && keyParts[1]) {
3305
- return keyParts[1];
3306
- }
3307
- }
3308
- } catch {
3309
- }
3310
- }
3311
- } catch {
3312
- }
3313
- return null;
3314
- }
3315
- /** Extract title from composer (like agent-dump) */
3316
- extractTitle(composer) {
3317
- if (composer.name && typeof composer.name === "string" && composer.name.trim()) {
3318
- return composer.name.trim();
3319
- }
3320
- if (composer.title && typeof composer.title === "string" && composer.title.trim()) {
3321
- return composer.title.trim();
3322
- }
3323
- if (composer.text && typeof composer.text === "string" && composer.text.trim()) {
3324
- const firstLine = composer.text.split("\n").find((l) => l.trim())?.trim().slice(0, 80);
3325
- if (firstLine) return firstLine;
3326
- }
3327
- const composerId = composer.composerId || composer.id || "";
3328
- return `Cursor Session ${composerId.slice(0, 8)}`;
3329
- }
3330
- /** Count messages from bubbles */
3331
- countMessagesFromBubbles(db, composerId) {
3332
- try {
3333
- const rows = db.prepare("SELECT value FROM cursorDiskKV WHERE key LIKE ?").all(`bubbleId:${composerId}:%`);
3334
- let count = 0;
3335
- for (const row of rows) {
3336
- try {
3337
- const bubble = JSON.parse(row.value);
3338
- if (bubble.type === 1 || bubble.type === 2) {
3339
- count++;
3340
- }
3341
- } catch {
3342
- }
3343
- }
3344
- return count;
3345
- } catch {
3346
- return 0;
3347
- }
3348
- }
3349
- /** Load messages from bubbles (like agent-dump) */
3350
- loadMessagesFromBubbles(db, composerId, _sessionId, initialModelName) {
3351
- const messages = [];
3352
- try {
3353
- const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ORDER BY rowid ASC").all(`bubbleId:${composerId}:%`);
3354
- let activeModelName = initialModelName;
3355
- let messageIndex = 0;
3356
- for (const row of rows) {
3357
- try {
3358
- const bubble = JSON.parse(row.value);
3359
- const bubbleId = row.key.split(":").pop() || String(messageIndex);
3360
- const role = bubble.type === 2 ? "assistant" : "user";
3361
- let timestampMs = 0;
3362
- if (bubble.timingInfo?.clientRpcSendTime) {
3363
- timestampMs = Math.floor(bubble.timingInfo.clientRpcSendTime);
3364
- } else if (bubble.createdAt) {
3365
- timestampMs = bubble.createdAt;
3366
- } else if (bubble.timestamp) {
3367
- timestampMs = bubble.timestamp;
3368
- }
3369
- if (bubble.modelInfo?.modelName) {
3370
- activeModelName = bubble.modelInfo.modelName;
3371
- }
3372
- const inputTokens = bubble.tokenCount?.inputTokens ?? 0;
3373
- const outputTokens = bubble.tokenCount?.outputTokens ?? 0;
3374
- const parts = [];
3375
- const text = bubble.text?.trim();
3376
- if (text) {
3377
- parts.push({ type: "text", text, time_created: timestampMs });
3378
- }
3379
- if (bubble.toolFormerData) {
3380
- const toolPart = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
3381
- if (toolPart) {
3382
- parts.push(toolPart);
3383
- }
3384
- }
3385
- if (parts.length === 0) continue;
3386
- messages.push({
3387
- id: `cursor-${composerId}-${bubbleId}`,
3388
- role,
3389
- agent: "cursor",
3390
- time_created: timestampMs,
3391
- time_completed: null,
3392
- mode: role === "assistant" && parts.some((p) => p.type === "tool") ? "tool" : null,
3393
- model: bubble.modelInfo?.modelName ?? activeModelName,
3394
- provider: null,
3395
- tokens: { input: inputTokens, output: outputTokens },
3396
- cost: 0,
3397
- parts
3398
- });
3399
- messageIndex++;
3400
- } catch {
3401
- }
3402
- }
3403
- } catch {
3404
- }
3405
- return messages;
3406
- }
3407
- /** Convert toolFormerData to MessagePart */
3408
- convertToolFormerData(toolData, timestampMs) {
3409
- if (!toolData || !toolData.name) return null;
3410
- const toolName = toolData.name;
3411
- const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle3(toolName);
3412
- const state = {
3413
- status: toolData.status === "completed" ? "completed" : "running"
3414
- };
3415
- if (toolData.params) {
3416
- if (typeof toolData.params === "string") {
3417
- try {
3418
- state.input = JSON.parse(toolData.params);
3419
- } catch {
3420
- state.input = { _raw: toolData.params };
3421
- }
3422
- } else {
3423
- state.input = toolData.params;
3424
- }
3425
- }
3426
- if (toolData.result !== void 0) {
3427
- if (typeof toolData.result === "string") {
3428
- try {
3429
- const parsed = JSON.parse(toolData.result);
3430
- state.output = parsed;
3431
- if (parsed.error || parsed.message || parsed.stderr) {
3432
- state.error = parsed.error || parsed.message || parsed.stderr;
3433
- state.status = "error";
3434
- }
3435
- } catch {
3436
- state.output = toolData.result;
3437
- }
3438
- } else {
3439
- state.output = toolData.result;
3440
- }
3441
- }
3442
- if (toolName === "create_plan") {
3443
- const planText = typeof state.input === "object" && state.input !== null ? state.input.plan : void 0;
3444
- return {
3445
- type: "plan",
3446
- title: "Plan",
3447
- input: planText,
3448
- approval_status: state.status === "completed" ? "success" : "fail",
3449
- state,
3450
- time_created: timestampMs
3451
- };
3452
- }
3453
- return {
3454
- type: "tool",
3455
- tool: normalizedName,
3456
- callID: toolData.toolCallId || "",
3457
- title: `Tool: ${toolName}`,
3458
- state,
3459
- time_created: timestampMs
3460
- };
3461
- }
3462
- loadComposer(db, sessionId) {
3463
- const row = db.prepare("SELECT value FROM cursorDiskKV WHERE key = ?").get(`composerData:${sessionId}`);
3464
- if (!row) return null;
3465
- try {
3466
- return JSON.parse(row.value);
3467
- } catch {
3468
- return null;
3469
- }
3470
- }
3471
- loadBubble(db, sessionId) {
3472
- const row = db.prepare("SELECT value FROM cursorDiskKV WHERE key = ?").get(`bubble:${sessionId}`);
3473
- if (!row) return null;
3474
- try {
3475
- return JSON.parse(row.value);
3476
- } catch {
3477
- return null;
3478
- }
3479
- }
3480
- appendSubagentMessages(db, composer, messages) {
3481
- const subagentInfos = composer.subagentInfos;
3482
- if (!Array.isArray(subagentInfos) || subagentInfos.length === 0) return;
3483
- for (const subInfo of subagentInfos) {
3484
- if (!subInfo.id) continue;
3485
- const bubble = this.loadBubble(db, subInfo.id);
3486
- if (!bubble || !Array.isArray(bubble.chatMessages)) continue;
3487
- for (const chatMsg of bubble.chatMessages) {
3488
- const role = chatMsg.role?.trim().toLowerCase();
3489
- if (role !== "user" && role !== "assistant") continue;
3490
- const timestampMs = extractTimestamp(chatMsg);
3491
- const parts = [];
3492
- const text = chatMsg.text ?? "";
3493
- if (text.trim()) {
3494
- parts.push({ type: "text", text, time_created: timestampMs });
3495
- }
3496
- if (role === "assistant" && Array.isArray(chatMsg.actions)) {
3497
- for (const action of chatMsg.actions) {
3498
- const part = convertActionToPart(action, timestampMs);
3499
- if (part) parts.push(part);
3500
- }
3501
- }
3502
- if (parts.length === 0) continue;
3503
- messages.push({
3504
- id: `cursor-sub-${subInfo.id}`,
3505
- role,
3506
- agent: "cursor",
3507
- time_created: timestampMs,
3508
- time_completed: null,
3509
- mode: null,
3510
- model: null,
3511
- provider: null,
3512
- tokens: void 0,
3513
- cost: 0,
3514
- subagent_id: subInfo.id,
3515
- nickname: subInfo.nickname ?? subInfo.title,
3516
- parts
3517
- });
3518
- }
3519
- }
3520
- }
3521
- };
3522
- registerAgent({
3523
- name: "claudecode",
3524
- displayName: "Claude Code",
3525
- icon: "/icon/agent/claudecode.svg",
3526
- create: () => new ClaudeCodeAgent()
3527
- });
3528
- registerAgent({
3529
- name: "opencode",
3530
- displayName: "OpenCode",
3531
- icon: "/icon/agent/opencode.svg",
3532
- create: () => new OpenCodeAgent()
3533
- });
3534
- registerAgent({
3535
- name: "kimi",
3536
- displayName: "Kimi-Cli",
3537
- icon: "/icon/agent/kimi.svg",
3538
- create: () => new KimiAgent()
3539
- });
3540
- registerAgent({
3541
- name: "codex",
3542
- displayName: "Codex",
3543
- icon: "/icon/agent/codex.svg",
3544
- create: () => new CodexAgent()
3545
- });
3546
- registerAgent({
3547
- name: "cursor",
3548
- displayName: "Cursor",
3549
- icon: "/icon/agent/cursor.svg",
3550
- create: () => new CursorAgent()
3551
- });
3552
- var CACHE_VERSION = 4;
3553
- var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3554
- var CACHE_FILENAME = "codesesh.db";
3555
- var LEGACY_CACHE_FILENAME = "scan-cache.json";
3556
- function getCacheDir() {
3557
- return join7(homedir2(), ".cache", "codesesh");
3558
- }
3559
- function getCachePath() {
3560
- return join7(getCacheDir(), CACHE_FILENAME);
3561
- }
3562
- function getLegacyCachePath() {
3563
- return join7(getCacheDir(), LEGACY_CACHE_FILENAME);
3564
- }
3565
- function hasCacheStorage() {
3566
- return existsSync7(getCachePath());
3567
- }
3568
- function withCacheDb(fn) {
3569
- const db = openDb(getCachePath());
3570
- if (!db) return null;
3571
- try {
3572
- ensureSchema(db);
3573
- return fn(db);
3574
- } catch {
3575
- return null;
3576
- } finally {
3577
- db.close();
3578
- }
3579
- }
3580
- function ensureSchema(db) {
3581
- db.exec(`
3582
- CREATE TABLE IF NOT EXISTS cache_meta (
3583
- key TEXT PRIMARY KEY,
3584
- value TEXT NOT NULL
3585
- );
3586
-
3587
- CREATE TABLE IF NOT EXISTS agent_cache (
3588
- agent_name TEXT PRIMARY KEY,
3589
- timestamp INTEGER NOT NULL
3590
- );
3591
-
3592
- CREATE TABLE IF NOT EXISTS cached_sessions (
3593
- agent_name TEXT NOT NULL,
3594
- session_id TEXT NOT NULL,
3595
- session_json TEXT NOT NULL,
3596
- meta_json TEXT,
3597
- PRIMARY KEY (agent_name, session_id)
3598
- );
3599
-
3600
- CREATE TABLE IF NOT EXISTS session_documents (
3601
- id INTEGER PRIMARY KEY AUTOINCREMENT,
3602
- agent_name TEXT NOT NULL,
3603
- session_id TEXT NOT NULL,
3604
- slug TEXT NOT NULL,
3605
- title TEXT NOT NULL,
3606
- directory TEXT NOT NULL,
3607
- time_created INTEGER NOT NULL,
3608
- time_updated INTEGER,
3609
- activity_time INTEGER NOT NULL,
3610
- content_text TEXT NOT NULL,
3611
- content_hash TEXT NOT NULL,
3612
- indexed_at INTEGER NOT NULL,
3613
- UNIQUE(agent_name, session_id)
3614
- );
3615
-
3616
- CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
3617
- title,
3618
- content_text,
3619
- content='session_documents',
3620
- content_rowid='id'
3621
- );
3622
-
3623
- CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
3624
- INSERT INTO session_documents_fts(rowid, title, content_text)
3625
- VALUES (new.id, new.title, new.content_text);
3626
- END;
3627
-
3628
- CREATE TRIGGER IF NOT EXISTS session_documents_ad AFTER DELETE ON session_documents BEGIN
3629
- INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3630
- VALUES ('delete', old.id, old.title, old.content_text);
3631
- END;
3632
-
3633
- CREATE TRIGGER IF NOT EXISTS session_documents_au AFTER UPDATE ON session_documents BEGIN
3634
- INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
3635
- VALUES ('delete', old.id, old.title, old.content_text);
3636
- INSERT INTO session_documents_fts(rowid, title, content_text)
3637
- VALUES (new.id, new.title, new.content_text);
3638
- END;
3639
- `);
3640
- const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
3641
- const version = Number(versionRow?.value ?? 0);
3642
- if (version === CACHE_VERSION) {
3643
- return;
3644
- }
3645
- db.exec(`
3646
- DELETE FROM agent_cache;
3647
- DELETE FROM cached_sessions;
3648
- DELETE FROM session_documents;
3649
- INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild');
3650
- INSERT INTO cache_meta(key, value)
3651
- VALUES ('version', '${CACHE_VERSION}')
3652
- ON CONFLICT(key) DO UPDATE SET value = excluded.value;
3653
- `);
3654
- }
3655
- function sessionContentHash(session) {
3656
- return JSON.stringify([
3657
- session.slug,
3658
- session.title,
3659
- session.directory,
3660
- session.time_created,
3661
- session.time_updated ?? session.time_created,
3662
- session.stats.message_count,
3663
- session.stats.total_input_tokens,
3664
- session.stats.total_output_tokens,
3665
- session.stats.total_cost,
3666
- session.stats.total_tokens ?? 0
3667
- ]);
3668
- }
3669
- function escapeFtsTerm(value) {
3670
- return value.replaceAll('"', '""');
3671
- }
3672
- function toFtsQuery(input) {
3673
- const tokens = input.match(/"[^"]+"|\S+/g) ?? [];
3674
- return tokens.map((token) => {
3675
- if (/^OR$/i.test(token)) {
3676
- return "OR";
3677
- }
3678
- if (token.startsWith('"') && token.endsWith('"')) {
3679
- return `"${escapeFtsTerm(token.slice(1, -1))}"`;
3680
- }
3681
- return `"${escapeFtsTerm(token)}"`;
3682
- }).join(" ");
3683
- }
3684
- function appendPlainText(value, chunks) {
3685
- if (value == null) return;
3686
- if (typeof value === "string") {
3687
- const normalized = value.trim();
3688
- if (normalized) {
3689
- chunks.push(normalized);
3690
- }
3691
- return;
3692
- }
3693
- if (typeof value === "number" || typeof value === "boolean") {
3694
- chunks.push(String(value));
3695
- return;
3696
- }
3697
- if (Array.isArray(value)) {
3698
- for (const item of value) {
3699
- appendPlainText(item, chunks);
3700
- }
3701
- return;
3702
- }
3703
- if (typeof value === "object") {
3704
- for (const nested of Object.values(value)) {
3705
- appendPlainText(nested, chunks);
3706
- }
3707
- }
3708
- }
3709
- function buildSessionContent(session) {
3710
- const chunks = [];
3711
- appendPlainText(session.title, chunks);
3712
- for (const message of session.messages) {
3713
- chunks.push(message.role);
3714
- appendPlainText(message.agent, chunks);
3715
- appendPlainText(message.model, chunks);
3716
- for (const part of message.parts) {
3717
- appendPlainText(part.type, chunks);
3718
- appendPlainText(part.title, chunks);
3719
- appendPlainText(part.nickname, chunks);
3720
- appendPlainText(part.tool, chunks);
3721
- appendPlainText(part.text, chunks);
3722
- appendPlainText(part.input, chunks);
3723
- appendPlainText(part.output, chunks);
3724
- appendPlainText(part.state, chunks);
3725
- }
3726
- }
3727
- return chunks.join("\n");
3728
- }
3729
- function deleteLegacyCacheFile() {
3730
- const legacyPath = getLegacyCachePath();
3731
- if (!existsSync7(legacyPath)) {
3732
- return;
3733
- }
3734
- try {
3735
- unlinkSync(legacyPath);
3736
- } catch {
3737
- }
3738
- }
3739
- function loadCachedSessions(agentName) {
3740
- if (!hasCacheStorage()) {
3741
- return null;
3742
- }
3743
- return withCacheDb((db) => {
3744
- const timestampRow = db.prepare("SELECT timestamp AS value FROM agent_cache WHERE agent_name = ?").get(agentName);
3745
- const timestamp = Number(timestampRow?.value ?? 0);
3746
- if (!timestamp || Date.now() - timestamp > CACHE_TTL) {
3747
- return null;
3748
- }
3749
- const rows = db.prepare(
3750
- `
3751
- SELECT session_json, meta_json
3752
- FROM cached_sessions
3753
- WHERE agent_name = ?
3754
- ORDER BY rowid
3755
- `
3756
- ).all(agentName);
3757
- const sessions = [];
3758
- const meta = {};
3759
- for (const row of rows) {
3760
- if (!row.session_json) {
3761
- continue;
3762
- }
3763
- const session = JSON.parse(row.session_json);
3764
- sessions.push(session);
3765
- if (row.meta_json) {
3766
- meta[session.id] = JSON.parse(row.meta_json);
3767
- }
3768
- }
3769
- return { sessions, meta, timestamp };
3770
- });
3771
- }
3772
- function saveCachedSessions(agentName, sessions, meta = {}) {
3773
- withCacheDb((db) => {
3774
- const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
3775
- const deleteSessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
3776
- const upsertAgent = db.prepare(`
3777
- INSERT INTO agent_cache(agent_name, timestamp)
3778
- VALUES (?, ?)
3779
- ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
3780
- `);
3781
- const insertSession = db.prepare(`
3782
- INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
3783
- VALUES (?, ?, ?, ?)
3784
- `);
3785
- const write = db.transaction(() => {
3786
- const timestamp = Date.now();
3787
- deleteAgent.run(agentName);
3788
- deleteSessions.run(agentName);
3789
- upsertAgent.run(agentName, timestamp);
3790
- for (const session of sessions) {
3791
- insertSession.run(
3792
- agentName,
3793
- session.id,
3794
- JSON.stringify(session),
3795
- meta[session.id] ? JSON.stringify(meta[session.id]) : null
3796
- );
3797
- }
3798
- });
3799
- write();
3800
- deleteLegacyCacheFile();
3801
- });
3802
- }
3803
- function clearCache() {
3804
- if (!hasCacheStorage()) {
3805
- deleteLegacyCacheFile();
3806
- return;
3807
- }
3808
- withCacheDb((db) => {
3809
- db.exec(`
3810
- DELETE FROM agent_cache;
3811
- DELETE FROM cached_sessions;
3812
- `);
3813
- });
3814
- deleteLegacyCacheFile();
3815
- const cachePath = getCachePath();
3816
- const walPath = `${cachePath}-wal`;
3817
- const shmPath = `${cachePath}-shm`;
3818
- for (const filePath of [walPath, shmPath]) {
3819
- if (!existsSync7(filePath)) {
3820
- continue;
3821
- }
3822
- try {
3823
- rmSync(filePath, { force: true });
3824
- } catch {
3825
- }
3826
- }
3827
- }
3828
- function getCacheInfo() {
3829
- if (!hasCacheStorage()) {
3830
- return { lastScanTime: null, size: 0 };
3831
- }
3832
- const info = withCacheDb((db) => {
3833
- const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
3834
- const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM cached_sessions").get();
3835
- const lastScanTime = Number(timestampRow?.value ?? 0) || null;
3836
- const size = Number(sizeRow?.value ?? 0);
3837
- return { lastScanTime, size };
3838
- });
3839
- return info ?? { lastScanTime: null, size: 0 };
3840
- }
3841
- function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
3842
- if (!hasCacheStorage()) {
3843
- return;
3844
- }
3845
- withCacheDb((db) => {
3846
- const existingRows = db.prepare(
3847
- "SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
3848
- ).all(agentName);
3849
- const existingMap = new Map(
3850
- existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
3851
- );
3852
- const sessionMap = new Map(sessions.map((session) => [session.id, session]));
3853
- const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
3854
- const toUpsert = sessions.filter(
3855
- (session) => existingMap.get(session.id) !== sessionContentHash(session)
3856
- );
3857
- const loaded = toUpsert.map((session) => {
3858
- try {
3859
- const data = loadSessionData(session.id);
3860
- return {
3861
- session,
3862
- contentText: buildSessionContent(data),
3863
- contentHash: sessionContentHash(session)
3864
- };
3865
- } catch {
3866
- return null;
3867
- }
3868
- }).filter((entry) => entry !== null);
3869
- const deleteRow = db.prepare(
3870
- "DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
3871
- );
3872
- const upsertRow = db.prepare(`
3873
- INSERT INTO session_documents(
3874
- agent_name,
3875
- session_id,
3876
- slug,
3877
- title,
3878
- directory,
3879
- time_created,
3880
- time_updated,
3881
- activity_time,
3882
- content_text,
3883
- content_hash,
3884
- indexed_at
3885
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3886
- ON CONFLICT(agent_name, session_id) DO UPDATE SET
3887
- slug = excluded.slug,
3888
- title = excluded.title,
3889
- directory = excluded.directory,
3890
- time_created = excluded.time_created,
3891
- time_updated = excluded.time_updated,
3892
- activity_time = excluded.activity_time,
3893
- content_text = excluded.content_text,
3894
- content_hash = excluded.content_hash,
3895
- indexed_at = excluded.indexed_at
3896
- `);
3897
- const write = db.transaction(() => {
3898
- for (const sessionId of toDelete) {
3899
- deleteRow.run(agentName, sessionId);
3900
- }
3901
- for (const entry of loaded) {
3902
- const activityTime = entry.session.time_updated ?? entry.session.time_created;
3903
- upsertRow.run(
3904
- agentName,
3905
- entry.session.id,
3906
- entry.session.slug,
3907
- entry.session.title,
3908
- entry.session.directory,
3909
- entry.session.time_created,
3910
- entry.session.time_updated ?? null,
3911
- activityTime,
3912
- entry.contentText,
3913
- entry.contentHash,
3914
- Date.now()
3915
- );
3916
- }
3917
- });
3918
- write();
3919
- });
3920
- }
3921
- function searchSessions(query, options = {}) {
3922
- const normalizedQuery = query.trim();
3923
- if (!normalizedQuery || !hasCacheStorage()) {
3924
- return [];
3925
- }
3926
- const ftsQuery = toFtsQuery(normalizedQuery);
3927
- const results = withCacheDb((db) => {
3928
- const rows = db.prepare(
3929
- `
3930
- SELECT
3931
- d.agent_name,
3932
- d.session_id,
3933
- d.slug,
3934
- d.title,
3935
- d.directory,
3936
- d.time_created,
3937
- d.time_updated,
3938
- COALESCE(
3939
- NULLIF(snippet(session_documents_fts, 1, '<mark>', '</mark>', ' \u2026 ', 18), ''),
3940
- highlight(session_documents_fts, 0, '<mark>', '</mark>')
3941
- ) AS snippet
3942
- FROM session_documents_fts
3943
- JOIN session_documents d ON d.id = session_documents_fts.rowid
3944
- WHERE session_documents_fts MATCH ?
3945
- AND (? IS NULL OR d.agent_name = ?)
3946
- AND (? IS NULL OR LOWER(d.directory) LIKE ?)
3947
- AND (? IS NULL OR d.activity_time >= ?)
3948
- AND (? IS NULL OR d.activity_time <= ?)
3949
- ORDER BY bm25(session_documents_fts, 8.0, 1.0), d.activity_time DESC
3950
- LIMIT ?
3951
- `
3952
- ).all(
3953
- ftsQuery,
3954
- options.agent ?? null,
3955
- options.agent ?? null,
3956
- options.cwd?.toLowerCase() ?? null,
3957
- options.cwd ? `%${options.cwd.toLowerCase()}%` : null,
3958
- options.from ?? null,
3959
- options.from ?? null,
3960
- options.to ?? null,
3961
- options.to ?? null,
3962
- options.limit ?? 50
3963
- );
3964
- return rows.map((row) => ({
3965
- agentName: String(row.agent_name),
3966
- session: {
3967
- id: String(row.session_id),
3968
- slug: String(row.slug),
3969
- title: String(row.title),
3970
- directory: String(row.directory),
3971
- time_created: Number(row.time_created),
3972
- time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
3973
- stats: {
3974
- message_count: 0,
3975
- total_input_tokens: 0,
3976
- total_output_tokens: 0,
3977
- total_cost: 0
3978
- }
3979
- },
3980
- snippet: String(row.snippet ?? "")
3981
- }));
3982
- });
3983
- return results ?? [];
3984
- }
3985
- function isPathScopeMatch(queryPath, sessionPath) {
3986
- if (!sessionPath) return false;
3987
- const q = resolve(queryPath);
3988
- const s = resolve(sessionPath);
3989
- const sepNorm = (p) => p.replaceAll(sep, "/");
3990
- const sn = sepNorm(s);
3991
- const qn = sepNorm(q);
3992
- return sn === qn || sn.startsWith(qn + "/") || qn.startsWith(sn + "/");
3993
- }
3994
- function filterSessions(sessions, options) {
3995
- let result = sessions;
3996
- if (options.cwd) {
3997
- const cwd = options.cwd;
3998
- result = result.filter((s) => isPathScopeMatch(cwd, s.directory));
3999
- }
4000
- if (options.from != null) {
4001
- result = result.filter((s) => s.time_created >= options.from);
4002
- }
4003
- if (options.to != null) {
4004
- result = result.filter((s) => s.time_created <= options.to);
4005
- }
4006
- return result;
4007
- }
4008
- function buildAgentCacheMeta(agent) {
4009
- const metaMap = agent.getSessionMetaMap?.();
4010
- const meta = {};
4011
- if (!metaMap) return meta;
4012
- for (const [id, data] of metaMap.entries()) {
4013
- meta[id] = { id, ...data };
4014
- }
4015
- return meta;
4016
- }
4017
- async function scanAgentSmart(agent, options, onProgress) {
4018
- const useCache = options.useCache ?? true;
4019
- const canValidateCache = Boolean(agent.checkForChanges && agent.incrementalScan);
4020
- if (useCache) {
4021
- const cached = loadCachedSessions(agent.name);
4022
- if (cached !== null) {
4023
- if (agent.setSessionMetaMap) {
4024
- const metaMap = /* @__PURE__ */ new Map();
4025
- for (const [id, meta] of Object.entries(cached.meta)) {
4026
- metaMap.set(id, meta);
4027
- }
4028
- agent.setSessionMetaMap(metaMap);
4029
- }
4030
- const isAvail = agent.isAvailable();
4031
- if (!isAvail) {
4032
- return null;
4033
- }
4034
- onProgress?.({
4035
- agent: agent.name,
4036
- phase: "cache",
4037
- cachedCount: cached.sessions.length
4038
- });
4039
- if (canValidateCache) {
4040
- onProgress?.({ agent: agent.name, phase: "checking" });
4041
- const checkResult = await Promise.resolve(
4042
- agent.checkForChanges(cached.timestamp, cached.sessions)
4043
- );
4044
- if (checkResult.hasChanges) {
4045
- onProgress?.({
4046
- agent: agent.name,
4047
- phase: "incremental",
4048
- changedCount: checkResult.changedIds?.length
4049
- });
4050
- const updatedSessions = await Promise.resolve(
4051
- agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
4052
- );
4053
- saveCachedSessions(agent.name, updatedSessions, buildAgentCacheMeta(agent));
4054
- onProgress?.({
4055
- agent: agent.name,
4056
- phase: "complete",
4057
- newCount: updatedSessions.length
4058
- });
4059
- const filtered2 = filterSessions(updatedSessions, options);
4060
- return { agent, heads: filtered2, fromCache: true, refreshed: true };
4061
- }
4062
- onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
4063
- }
4064
- const filtered = filterSessions(cached.sessions, options);
4065
- return { agent, heads: filtered, fromCache: true };
4066
- }
4067
- }
4068
- return scanAgentFull(agent, options, onProgress);
4069
- }
4070
- async function scanAgentFull(agent, options, onProgress) {
4071
- const availMarker = perf.start(`agent:${agent.name}:isAvailable`);
4072
- const isAvail = agent.isAvailable();
4073
- perf.end(availMarker);
4074
- if (!isAvail) {
4075
- return null;
4076
- }
4077
- try {
4078
- const scanMarker = perf.start(`agent:${agent.name}:scan`);
4079
- const heads = agent.scan();
4080
- perf.end(scanMarker);
4081
- const meta = buildAgentCacheMeta(agent);
4082
- saveCachedSessions(agent.name, heads, meta);
4083
- onProgress?.({ agent: agent.name, phase: "complete", newCount: heads.length });
4084
- const filtered = filterSessions(heads, options);
4085
- return { agent, heads: filtered, fromCache: false };
4086
- } catch (err) {
4087
- console.error(`Error scanning ${agent.name}:`, err);
4088
- return { agent, heads: [], fromCache: false };
4089
- }
4090
- }
4091
- async function scanSessions(options = {}, onProgress) {
4092
- const scanMarker = perf.start("scanSessions");
4093
- const agents = createRegisteredAgents();
4094
- const byAgent = {};
4095
- const allSessions = [];
4096
- const availableAgents = [];
4097
- const agentFilter = options.agents?.length ? new Set(options.agents.map((a) => a.toLowerCase())) : null;
4098
- const agentsToScan = agents.filter((agent) => {
4099
- if (agentFilter && !agentFilter.has(agent.name.toLowerCase())) {
4100
- return false;
4101
- }
4102
- return true;
4103
- });
4104
- const scanPromises = agentsToScan.map((agent) => scanAgentSmart(agent, options, onProgress));
4105
- const results = await Promise.all(scanPromises);
4106
- for (const result of results) {
4107
- if (result) {
4108
- availableAgents.push(result.agent);
4109
- byAgent[result.agent.name] = result.heads;
4110
- allSessions.push(...result.heads);
4111
- }
4112
- }
4113
- perf.end(scanMarker);
4114
- return { sessions: allSessions, byAgent, agents: availableAgents };
4115
- }
4116
- async function scanSessionsAsync(options = {}, onProgress) {
4117
- return scanSessions(options, onProgress);
4118
- }
4119
- var BOOKMARK_DB_FILENAME = "state.db";
4120
- var BOOKMARK_DB_VERSION = 1;
4121
- var BookmarkStorageUnavailableError = class extends Error {
4122
- constructor() {
4123
- super("SQLite state database is unavailable");
4124
- this.name = "BookmarkStorageUnavailableError";
4125
- }
4126
- };
4127
- function getStateDir() {
4128
- const p = platform2();
4129
- if (p === "darwin") {
4130
- return join8(homedir3(), "Library", "Application Support", "codesesh");
4131
- }
4132
- if (p === "win32") {
4133
- const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
4134
- return join8(appData ?? join8(homedir3(), "AppData", "Roaming"), "codesesh");
4135
- }
4136
- return join8(process.env.XDG_DATA_HOME ?? join8(homedir3(), ".local", "share"), "codesesh");
4137
- }
4138
- function getStateDbPath() {
4139
- return join8(getStateDir(), BOOKMARK_DB_FILENAME);
4140
- }
4141
- function ensureSchema2(db) {
4142
- db.exec(`
4143
- CREATE TABLE IF NOT EXISTS state_meta (
4144
- key TEXT PRIMARY KEY,
4145
- value TEXT NOT NULL
4146
- );
4147
-
4148
- CREATE TABLE IF NOT EXISTS bookmarks (
4149
- agent_name TEXT NOT NULL,
4150
- session_id TEXT NOT NULL,
4151
- slug TEXT NOT NULL,
4152
- title TEXT NOT NULL,
4153
- directory TEXT NOT NULL,
4154
- time_created INTEGER NOT NULL,
4155
- time_updated INTEGER,
4156
- stats_json TEXT NOT NULL,
4157
- bookmarked_at INTEGER NOT NULL,
4158
- PRIMARY KEY (agent_name, session_id)
4159
- );
4160
- `);
4161
- const row = db.prepare("SELECT value FROM state_meta WHERE key = 'version'").get();
4162
- const version = Number(row?.value ?? 0);
4163
- if (version === BOOKMARK_DB_VERSION) return;
4164
- db.prepare(
4165
- `
4166
- INSERT INTO state_meta(key, value)
4167
- VALUES ('version', ?)
4168
- ON CONFLICT(key) DO UPDATE SET value = excluded.value
4169
- `
4170
- ).run(String(BOOKMARK_DB_VERSION));
4171
- }
4172
- function withStateDb(fn) {
4173
- const db = openDb(getStateDbPath());
4174
- if (!db) {
4175
- throw new BookmarkStorageUnavailableError();
4176
- }
4177
- try {
4178
- ensureSchema2(db);
4179
- return fn(db);
4180
- } finally {
4181
- db.close();
4182
- }
4183
- }
4184
- function toBookmarkRecord(row) {
4185
- return {
4186
- agentKey: String(row.agent_name ?? ""),
4187
- sessionId: String(row.session_id ?? ""),
4188
- fullPath: String(row.slug ?? ""),
4189
- title: String(row.title ?? ""),
4190
- directory: String(row.directory ?? ""),
4191
- time_created: Number(row.time_created ?? 0),
4192
- time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
4193
- stats: JSON.parse(String(row.stats_json ?? "{}")),
4194
- bookmarked_at: Number(row.bookmarked_at ?? 0)
4195
- };
4196
- }
4197
- function listBookmarks() {
4198
- return withStateDb((db) => {
4199
- const rows = db.prepare(
4200
- `
4201
- SELECT
4202
- agent_name,
4203
- session_id,
4204
- slug,
4205
- title,
4206
- directory,
4207
- time_created,
4208
- time_updated,
4209
- stats_json,
4210
- bookmarked_at
4211
- FROM bookmarks
4212
- ORDER BY COALESCE(time_updated, time_created) DESC, bookmarked_at DESC
4213
- `
4214
- ).all();
4215
- return rows.map(toBookmarkRecord);
4216
- });
4217
- }
4218
- function upsertBookmark(bookmark) {
4219
- return withStateDb((db) => {
4220
- const existing = db.prepare(
4221
- `
4222
- SELECT bookmarked_at
4223
- FROM bookmarks
4224
- WHERE agent_name = ? AND session_id = ?
4225
- `
4226
- ).get(bookmark.agentKey, bookmark.sessionId);
4227
- const bookmarkedAt = Number(existing?.bookmarked_at ?? Date.now());
4228
- db.prepare(
4229
- `
4230
- INSERT INTO bookmarks(
4231
- agent_name,
4232
- session_id,
4233
- slug,
4234
- title,
4235
- directory,
4236
- time_created,
4237
- time_updated,
4238
- stats_json,
4239
- bookmarked_at
4240
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4241
- ON CONFLICT(agent_name, session_id) DO UPDATE SET
4242
- slug = excluded.slug,
4243
- title = excluded.title,
4244
- directory = excluded.directory,
4245
- time_created = excluded.time_created,
4246
- time_updated = excluded.time_updated,
4247
- stats_json = excluded.stats_json
4248
- `
4249
- ).run(
4250
- bookmark.agentKey,
4251
- bookmark.sessionId,
4252
- bookmark.fullPath,
4253
- bookmark.title,
4254
- bookmark.directory,
4255
- bookmark.time_created,
4256
- bookmark.time_updated ?? null,
4257
- JSON.stringify(bookmark.stats),
4258
- bookmarkedAt
4259
- );
4260
- return { ...bookmark, bookmarked_at: bookmarkedAt };
4261
- });
4262
- }
4263
- function importBookmarks(bookmarks) {
4264
- return withStateDb((db) => {
4265
- const existingRows = db.prepare("SELECT agent_name, session_id, bookmarked_at FROM bookmarks").all();
4266
- const existingTimes = new Map(
4267
- existingRows.map((row) => [
4268
- `${String(row.agent_name ?? "")}:${String(row.session_id ?? "")}`,
4269
- Number(row.bookmarked_at ?? 0)
4270
- ])
4271
- );
4272
- const upsert = db.prepare(
4273
- `
4274
- INSERT INTO bookmarks(
4275
- agent_name,
4276
- session_id,
4277
- slug,
4278
- title,
4279
- directory,
4280
- time_created,
4281
- time_updated,
4282
- stats_json,
4283
- bookmarked_at
4284
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4285
- ON CONFLICT(agent_name, session_id) DO UPDATE SET
4286
- slug = excluded.slug,
4287
- title = excluded.title,
4288
- directory = excluded.directory,
4289
- time_created = excluded.time_created,
4290
- time_updated = excluded.time_updated,
4291
- stats_json = excluded.stats_json
4292
- `
4293
- );
4294
- const write = db.transaction(() => {
4295
- for (const bookmark of bookmarks) {
4296
- const key = `${bookmark.agentKey}:${bookmark.sessionId}`;
4297
- upsert.run(
4298
- bookmark.agentKey,
4299
- bookmark.sessionId,
4300
- bookmark.fullPath,
4301
- bookmark.title,
4302
- bookmark.directory,
4303
- bookmark.time_created,
4304
- bookmark.time_updated ?? null,
4305
- JSON.stringify(bookmark.stats),
4306
- existingTimes.get(key) ?? Date.now()
4307
- );
4308
- }
4309
- });
4310
- write();
4311
- const rows = db.prepare(
4312
- `
4313
- SELECT
4314
- agent_name,
4315
- session_id,
4316
- slug,
4317
- title,
4318
- directory,
4319
- time_created,
4320
- time_updated,
4321
- stats_json,
4322
- bookmarked_at
4323
- FROM bookmarks
4324
- ORDER BY COALESCE(time_updated, time_created) DESC, bookmarked_at DESC
4325
- `
4326
- ).all();
4327
- return rows.map(toBookmarkRecord);
4328
- });
4329
- }
4330
- function deleteBookmark(agentKey, sessionId) {
4331
- withStateDb((db) => {
4332
- db.prepare(
4333
- `
4334
- DELETE FROM bookmarks
4335
- WHERE agent_name = ? AND session_id = ?
4336
- `
4337
- ).run(agentKey, sessionId);
4338
- });
4339
- }
4340
-
4341
- export {
4342
- registerAgent,
4343
- createRegisteredAgents,
4344
- getRegisteredAgents,
4345
- getAgentInfoMap,
4346
- getAgentByName,
4347
- BaseAgent,
4348
- firstExisting,
4349
- resolveProviderRoots,
4350
- getCursorDataPath,
4351
- parseJsonlLines,
4352
- readJsonlFile,
4353
- normalizeTitleText,
4354
- basenameTitle,
4355
- resolveSessionTitle,
4356
- perf,
4357
- openDbReadOnly,
4358
- openDb,
4359
- isSqliteAvailable,
4360
- loadCachedSessions,
4361
- saveCachedSessions,
4362
- clearCache,
4363
- getCacheInfo,
4364
- syncSessionSearchIndex,
4365
- searchSessions,
4366
- filterSessions,
4367
- scanSessions,
4368
- scanSessionsAsync,
4369
- BookmarkStorageUnavailableError,
4370
- listBookmarks,
4371
- upsertBookmark,
4372
- importBookmarks,
4373
- deleteBookmark
4374
- };
4375
- //# sourceMappingURL=chunk-BGQXQTWM.js.map