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