codesesh 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,149 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../core/dist/chunk-M5ISIPFR.mjs
3
+ // ../core/dist/chunk-BOTICQC6.mjs
4
+ function toRecord(value) {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
6
+ }
7
+ function optionalString(value) {
8
+ return typeof value === "string" && value ? value : void 0;
9
+ }
10
+ function optionalTime(value) {
11
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12
+ }
13
+ function timeField(time_created) {
14
+ return time_created === void 0 ? {} : { time_created };
15
+ }
16
+ function firstDefined(...values) {
17
+ return values.find((value) => value !== void 0);
18
+ }
19
+ function normalizeStatus(state, output, error) {
20
+ const status = state.status;
21
+ if (status === "running" || status === "completed" || status === "error") return status;
22
+ if (status === "success") return "completed";
23
+ if (error != null) return "error";
24
+ if (output !== void 0) return "completed";
25
+ return "running";
26
+ }
27
+ function normalizeMetadata(state) {
28
+ const rawMetadata = state.metadata ?? state.meta;
29
+ const extras = Object.fromEntries(
30
+ Object.entries(state).filter(
31
+ ([key]) => !["status", "input", "arguments", "output", "result", "error", "metadata", "meta"].includes(
32
+ key
33
+ )
34
+ )
35
+ );
36
+ if (Object.keys(extras).length === 0) return rawMetadata;
37
+ const metadata = toRecord(rawMetadata);
38
+ return metadata ? { ...metadata, ...extras } : extras;
39
+ }
40
+ function normalizeToolState(value, legacyPart) {
41
+ const state = toRecord(value) ?? {};
42
+ const input = firstDefined(state.input, state.arguments, legacyPart.input);
43
+ const output = firstDefined(state.output, state.result, legacyPart.output);
44
+ const error = state.error;
45
+ const metadata = normalizeMetadata(state);
46
+ return {
47
+ status: normalizeStatus(state, output, error),
48
+ ...input !== void 0 ? { input } : {},
49
+ ...output !== void 0 ? { output } : {},
50
+ ...error !== void 0 ? { error } : {},
51
+ ...metadata !== void 0 ? { metadata } : {}
52
+ };
53
+ }
54
+ function planText(value) {
55
+ if (typeof value === "string") return value;
56
+ const record = toRecord(value);
57
+ if (!record) return "";
58
+ return planText(record.text ?? record.plan ?? record.content);
59
+ }
60
+ function normalizePlanPart(value, time_created) {
61
+ const approval_status = value.approval_status === "fail" ? "fail" : "success";
62
+ const text = planText(value.text ?? (approval_status === "fail" ? value.output : value.input));
63
+ return text ? { type: "plan", text, approval_status, ...timeField(time_created) } : null;
64
+ }
65
+ function normalizeImagePart(value, time_created) {
66
+ const data = optionalString(value.data);
67
+ const url = optionalString(value.url);
68
+ const mime_type = optionalString(value.mime_type);
69
+ if (data && mime_type) {
70
+ return {
71
+ type: "image",
72
+ data,
73
+ mime_type,
74
+ ...url ? { url } : {},
75
+ ...timeField(time_created)
76
+ };
77
+ }
78
+ if (url) {
79
+ return {
80
+ type: "image",
81
+ url,
82
+ ...data ? { data } : {},
83
+ ...mime_type ? { mime_type } : {},
84
+ ...timeField(time_created)
85
+ };
86
+ }
87
+ return null;
88
+ }
89
+ function normalizeToolPart(value, time_created) {
90
+ const title = optionalString(value.title);
91
+ const callID = optionalString(value.callID);
92
+ const tool = optionalString(value.tool)?.trim() || title?.replace(/^tool:\s*/i, "").trim();
93
+ if (!tool) return null;
94
+ return {
95
+ type: "tool",
96
+ tool,
97
+ ...title ? { title } : {},
98
+ ...callID ? { callID } : {},
99
+ state: normalizeToolState(value.state, value),
100
+ ...timeField(time_created)
101
+ };
102
+ }
103
+ function normalizeMessagePart(value) {
104
+ const part = toRecord(value);
105
+ if (!part) return null;
106
+ const time_created = optionalTime(part.time_created);
107
+ if (part.type === "text" || part.type === "reasoning") {
108
+ if (typeof part.text !== "string") return null;
109
+ return { type: part.type, text: part.text, ...timeField(time_created) };
110
+ }
111
+ if (part.type === "plan") return normalizePlanPart(part, time_created);
112
+ if (part.type === "image") return normalizeImagePart(part, time_created);
113
+ if (part.type === "tool") return normalizeToolPart(part, time_created);
114
+ return null;
115
+ }
116
+ function normalizeMessageParts(value) {
117
+ if (!Array.isArray(value)) return [];
118
+ return value.flatMap((part) => {
119
+ const normalized = normalizeMessagePart(part);
120
+ return normalized ? [normalized] : [];
121
+ });
122
+ }
123
+ var UNKNOWN_AGENT_NAME = "unknown";
124
+ function normalizeSessionReference(reference) {
125
+ return {
126
+ agentName: reference.agentName.trim().toLowerCase(),
127
+ sessionId: reference.sessionId
128
+ };
129
+ }
130
+ function parseSessionReference(value) {
131
+ const separatorIndex = value.indexOf("/");
132
+ if (separatorIndex <= 0 || separatorIndex === value.length - 1) return null;
133
+ const agentName = value.slice(0, separatorIndex).trim().toLowerCase();
134
+ if (!agentName) return null;
135
+ return {
136
+ agentName,
137
+ sessionId: value.slice(separatorIndex + 1)
138
+ };
139
+ }
140
+ function formatSessionReference(reference) {
141
+ const normalized = normalizeSessionReference(reference);
142
+ return `${normalized.agentName}/${normalized.sessionId}`;
143
+ }
144
+ function getSessionAgentKey(session) {
145
+ return parseSessionReference(session.slug)?.agentName ?? UNKNOWN_AGENT_NAME;
146
+ }
4
147
  function compareSessionActivityDesc(a, b) {
5
148
  return (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created);
6
149
  }
@@ -12,55 +155,146 @@ function sortSessionsByActivity(sessions) {
12
155
  }
13
156
  return [...sessions];
14
157
  }
158
+ function mergeSortedSessions(shards) {
159
+ const active = shards.filter((shard) => shard.length > 0);
160
+ if (active.length === 0) return [];
161
+ if (active.length === 1) return [...active[0]];
162
+ const total = active.reduce((sum, shard) => sum + shard.length, 0);
163
+ const merged = [];
164
+ const cursors = Array.from({ length: active.length }, () => 0);
165
+ for (let position = 0; position < total; position += 1) {
166
+ let pick = -1;
167
+ for (let shard = 0; shard < active.length; shard += 1) {
168
+ const cursor = cursors[shard];
169
+ if (cursor >= active[shard].length) continue;
170
+ if (pick === -1 || compareSessionActivityDesc(active[shard][cursor], active[pick][cursors[pick]]) < 0) {
171
+ pick = shard;
172
+ }
173
+ }
174
+ merged.push(active[pick][cursors[pick]]);
175
+ cursors[pick] += 1;
176
+ }
177
+ return merged;
178
+ }
179
+ var SAMPLE_SESSION_HEAD = {
180
+ id: "session-1",
181
+ slug: "claudecode/session-1",
182
+ title: "Fix flaky search index test",
183
+ directory: "/Users/dev/project",
184
+ project_identity: {
185
+ kind: "git_remote",
186
+ key: "github.com/example/project",
187
+ displayName: "example/project"
188
+ },
189
+ time_created: 17e11,
190
+ time_updated: 17000036e5,
191
+ stats: {
192
+ message_count: 12,
193
+ total_input_tokens: 4200,
194
+ total_output_tokens: 1800,
195
+ total_cost: 0.042,
196
+ cost_source: "recorded",
197
+ total_tokens: 6e3,
198
+ total_cache_read_tokens: 3e3,
199
+ total_cache_create_tokens: 500
200
+ },
201
+ model_usage: { "claude-5-sonnet": 6e3 },
202
+ smart_tags: ["bugfix"],
203
+ smart_tags_source_updated_at: 17000036e5
204
+ };
205
+ var SAMPLE_SESSIONS_UPDATED_EVENT = {
206
+ type: "sessions-updated",
207
+ changedAgents: ["claudecode"],
208
+ newSessions: 1,
209
+ updatedSessions: 0,
210
+ removedSessions: 0,
211
+ totalSessions: 43,
212
+ timestamp: 170000002e4,
213
+ changedSessionHeads: [
214
+ {
215
+ reference: { agentName: "claudecode", sessionId: SAMPLE_SESSION_HEAD.id },
216
+ session: SAMPLE_SESSION_HEAD
217
+ }
218
+ ],
219
+ removedSessionRefs: []
220
+ };
221
+ var SAMPLE_DASHBOARD_DATA = {
222
+ totals: {
223
+ sessions: 1,
224
+ messages: 12,
225
+ tokens: 6e3,
226
+ cost: 0.042,
227
+ cost_source: "recorded",
228
+ latestActivity: 17000036e5
229
+ },
230
+ perAgent: [
231
+ {
232
+ name: "claudecode",
233
+ displayName: "Claude Code",
234
+ icon: "claude",
235
+ sessions: 1,
236
+ messages: 12,
237
+ tokens: 6e3
238
+ }
239
+ ],
240
+ dailyActivity: [{ date: "2023-11-14", sessions: 1, messages: 12 }],
241
+ dailyTokenActivity: [
242
+ { date: "2023-11-14", input: 700, output: 1800, cache_read: 3e3, cache_create: 500 }
243
+ ],
244
+ modelDistribution: [{ model: "claude-5-sonnet", tokens: 6e3, sessions: 1 }],
245
+ recentSessions: [
246
+ {
247
+ reference: { agentName: "claudecode", sessionId: SAMPLE_SESSION_HEAD.id },
248
+ session: SAMPLE_SESSION_HEAD
249
+ }
250
+ ],
251
+ recentFileActivities: [],
252
+ window: { from: 16999e8, to: 17000036e5, days: 1 }
253
+ };
15
254
 
16
255
  // ../core/dist/index.mjs
17
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
18
- import { join as join3, basename as basename2, dirname } from "path";
19
- import { existsSync, statSync } from "fs";
256
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
257
+ import { join as join4, basename as basename2, dirname } from "path";
258
+ import { existsSync, readdirSync, statSync } from "fs";
259
+ import { join } from "path";
20
260
  import { existsSync as existsSync2 } from "fs";
21
261
  import { homedir, platform } from "os";
22
- import { join } from "path";
262
+ import { join as join2 } from "path";
23
263
  import { closeSync, openSync, readSync } from "fs";
24
264
  import { StringDecoder } from "string_decoder";
25
265
  import { basename } from "path";
26
266
  import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
27
267
  import { homedir as homedir2 } from "os";
28
- import { join as join2 } from "path";
29
- import { join as join5 } from "path";
268
+ import { join as join3 } from "path";
269
+ import { join as join6 } from "path";
30
270
  import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
31
- import { basename as basename3, dirname as dirname2, join as join4 } from "path";
271
+ import { basename as basename3, dirname as dirname2, join as join5 } from "path";
32
272
  import { createRequire } from "module";
33
273
  import { createHash } from "crypto";
34
- import { existsSync as existsSync6, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
35
- import { join as join6, basename as basename4, dirname as dirname3 } from "path";
36
- import {
37
- closeSync as closeSync2,
38
- existsSync as existsSync7,
39
- openSync as openSync2,
40
- readFileSync as readFileSync4,
41
- readSync as readSync2,
42
- readdirSync as readdirSync3,
43
- statSync as statSync4
44
- } from "fs";
45
- import { join as join7, basename as basename5 } from "path";
274
+ import { existsSync as existsSync6, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
275
+ import { join as join7, basename as basename4, dirname as dirname3 } from "path";
276
+ import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync2, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync4 } from "fs";
277
+ import { join as join8, basename as basename5 } from "path";
46
278
  import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
47
- import { join as join8, normalize } from "path";
48
- import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
49
- import { basename as basename6, join as join9 } from "path";
50
- import { join as join10 } from "path";
279
+ import { homedir as homedir3, platform as platform2 } from "os";
280
+ import { join as join9, normalize } from "path";
281
+ import { existsSync as existsSync9 } from "fs";
282
+ import { basename as basename6, join as join10 } from "path";
283
+ import { homedir as homedir4, platform as platform3 } from "os";
284
+ import { join as join11 } from "path";
51
285
  import { availableParallelism } from "os";
52
286
  import { Worker } from "worker_threads";
53
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
287
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
54
288
  import { spawnSync } from "child_process";
55
289
  import * as os from "os";
56
290
  import * as path from "path";
57
291
  import { resolve, sep } from "path";
58
- import { existsSync as existsSync11 } from "fs";
59
- import { join as join11 } from "path";
60
- import { homedir as homedir4 } from "os";
61
292
  import { existsSync as existsSync12, rmSync, unlinkSync } from "fs";
62
- import { homedir as homedir5, platform as platform2 } from "os";
293
+ import { existsSync as existsSync11 } from "fs";
63
294
  import { join as join12 } from "path";
295
+ import { homedir as homedir6 } from "os";
296
+ import { homedir as homedir7, platform as platform4 } from "os";
297
+ import { join as join13 } from "path";
64
298
  var registrations = [];
65
299
  function registerAgent(reg) {
66
300
  registrations.push(reg);
@@ -79,16 +313,20 @@ function getAgentInfoMap(sessionsByAgent) {
79
313
  displayName: agent.displayName,
80
314
  icon: registration.icon,
81
315
  iconColored: registration.iconColored,
316
+ resumeCommandPrefix: registration.resumeCommandPrefix,
82
317
  count: sessionsByAgent[agent.name] ?? 0
83
318
  };
84
319
  });
85
320
  }
86
- function getAgentByName(name) {
87
- return registrations.find((registration) => registration.create().name === name);
88
- }
89
321
  var diagnostics = null;
90
322
  function toSafeSink(sink) {
91
323
  return {
324
+ info(event, detail) {
325
+ try {
326
+ sink.info?.(event, detail);
327
+ } catch {
328
+ }
329
+ },
92
330
  warn(event, detail) {
93
331
  try {
94
332
  sink.warn(event, detail);
@@ -120,6 +358,36 @@ function matchesScanWindow(activityTime, options) {
120
358
  if (options?.to != null && activityTime > options.to) return false;
121
359
  return true;
122
360
  }
361
+ function readCachedMeta(meta, sessionId) {
362
+ if (meta instanceof Map) return meta.get(sessionId);
363
+ return meta[sessionId];
364
+ }
365
+ function fingerprintMatches(ref, cached) {
366
+ return typeof cached?.sourceFingerprint === "string" && cached.sourceFingerprint === ref.fingerprint;
367
+ }
368
+ function wasEnumeratedThisPass(cached, options) {
369
+ if (options?.from == null && options?.to == null) return true;
370
+ const mtimeMs = cached?.sourceMtimeMs;
371
+ return typeof mtimeMs === "number" && matchesScanWindow(mtimeMs, options);
372
+ }
373
+ function diffSessionSources(refs, cachedSessions, cachedMeta, options) {
374
+ const cachedIds = new Set(cachedSessions.map((session) => session.id));
375
+ const enumeratedIds = /* @__PURE__ */ new Set();
376
+ const changedIds = [];
377
+ for (const ref of refs) {
378
+ enumeratedIds.add(ref.sessionId);
379
+ const meta = readCachedMeta(cachedMeta, ref.sessionId);
380
+ const unchanged = cachedIds.has(ref.sessionId) && meta?.sourcePath === ref.sourcePath && fingerprintMatches(ref, meta);
381
+ if (!unchanged) changedIds.push(ref.sessionId);
382
+ }
383
+ const removedIds = [];
384
+ for (const session of cachedSessions) {
385
+ if (enumeratedIds.has(session.id)) continue;
386
+ if (!wasEnumeratedThisPass(readCachedMeta(cachedMeta, session.id), options)) continue;
387
+ removedIds.push(session.id);
388
+ }
389
+ return { changedIds, removedIds };
390
+ }
123
391
  var BaseAgent = class {
124
392
  getUri(sessionId) {
125
393
  return `${this.name}://${sessionId}`;
@@ -127,6 +395,7 @@ var BaseAgent = class {
127
395
  };
128
396
  var FileSystemSessionSource = class extends BaseAgent {
129
397
  sessionMetaMap = /* @__PURE__ */ new Map();
398
+ sourceFileStats = /* @__PURE__ */ new Map();
130
399
  scan(options) {
131
400
  const sources = this.listSessionSources(options);
132
401
  const sessions = [];
@@ -158,27 +427,62 @@ var FileSystemSessionSource = class extends BaseAgent {
158
427
  setSessionMetaMap(meta) {
159
428
  this.sessionMetaMap = meta;
160
429
  }
430
+ walkFiles(roots, isSessionFile, options = {}) {
431
+ const files = [];
432
+ this.sourceFileStats.clear();
433
+ const walk = (directory) => {
434
+ let entries;
435
+ try {
436
+ entries = readdirSync(directory, { withFileTypes: true });
437
+ } catch {
438
+ return;
439
+ }
440
+ for (const entry of entries) {
441
+ const filePath = join(directory, entry.name);
442
+ if (entry.isDirectory()) {
443
+ if (options.recursive !== false) walk(filePath);
444
+ continue;
445
+ }
446
+ if (!isSessionFile(entry)) continue;
447
+ let stat;
448
+ try {
449
+ stat = statSync(filePath);
450
+ } catch {
451
+ continue;
452
+ }
453
+ if (!matchesScanWindow(stat.mtimeMs, options.scanWindow)) continue;
454
+ files.push({ file: filePath, stat });
455
+ this.sourceFileStats.set(filePath, stat);
456
+ }
457
+ };
458
+ for (const root of typeof roots === "string" ? [roots] : roots) walk(root);
459
+ return files;
460
+ }
461
+ sessionSourceFile(sourcePath) {
462
+ return {
463
+ file: sourcePath,
464
+ stat: this.sourceFileStats.get(sourcePath) ?? statSync(sourcePath)
465
+ };
466
+ }
467
+ readFileMtimeMs(filePath) {
468
+ if (!filePath) return null;
469
+ try {
470
+ return statSync(filePath).mtimeMs;
471
+ } catch {
472
+ return null;
473
+ }
474
+ }
161
475
  /**
162
- * 变更检测:枚举当前源 → 与缓存 metaMap 的指纹/路径比对。
476
+ * 变更检测:枚举当前源 → 交给 diffSessionSources 比对。
163
477
  * 新增、变更、删除三类统一产出 changedIds。
164
478
  */
165
479
  checkForChanges(_sinceTimestamp, cachedSessions) {
166
480
  const currentRefs = this.listSessionSources();
167
- const currentIds = new Set(currentRefs.map((ref) => ref.sessionId));
168
- const changedIds = /* @__PURE__ */ new Set();
169
- for (const ref of currentRefs) {
170
- const meta = this.sessionMetaMap.get(ref.sessionId);
171
- const samePath = meta?.sourcePath === ref.sourcePath;
172
- const sameFingerprint = typeof meta?.sourceFingerprint === "string" && meta.sourceFingerprint === ref.fingerprint;
173
- if (!samePath || !sameFingerprint) changedIds.add(ref.sessionId);
174
- }
175
- for (const session of cachedSessions) {
176
- if (!currentIds.has(session.id)) changedIds.add(session.id);
177
- }
178
- const changedIdList = [...changedIds];
481
+ const diff = diffSessionSources(currentRefs, cachedSessions, this.sessionMetaMap);
482
+ const changedIds = [.../* @__PURE__ */ new Set([...diff.changedIds, ...diff.removedIds])];
179
483
  return {
180
- hasChanges: changedIdList.length > 0,
181
- changedIds: changedIdList,
484
+ hasChanges: changedIds.length > 0,
485
+ changedIds,
182
486
  timestamp: Date.now(),
183
487
  refs: currentRefs
184
488
  };
@@ -212,6 +516,37 @@ var FileSystemSessionSource = class extends BaseAgent {
212
516
  return [...sessionMap.values()];
213
517
  }
214
518
  };
519
+ var SingleFileSessionSource = class extends FileSystemSessionSource {
520
+ scanSessionSource(sourcePath, options) {
521
+ const head = this.parseFileSessionHead(sourcePath, options);
522
+ if (head) {
523
+ this.sessionMetaMap.set(
524
+ head.id,
525
+ this.createFileSessionMeta(head, this.sessionSourceFile(sourcePath))
526
+ );
527
+ }
528
+ return head;
529
+ }
530
+ buildFileSessionMeta({
531
+ head,
532
+ source,
533
+ fingerprint,
534
+ extras
535
+ }) {
536
+ return {
537
+ ...extras,
538
+ id: head.id,
539
+ title: head.title,
540
+ sourcePath: source.file,
541
+ sourceFingerprint: fingerprint,
542
+ sourceMtimeMs: source.stat.mtimeMs,
543
+ directory: head.directory,
544
+ messageCount: head.stats.message_count,
545
+ createdAt: head.time_created,
546
+ updatedAt: head.time_updated ?? head.time_created
547
+ };
548
+ }
549
+ };
215
550
  var DatabaseSessionSource = class extends BaseAgent {
216
551
  sessionMetaMap = /* @__PURE__ */ new Map();
217
552
  /** 记录单个会话的缓存 meta(sourcePath = dbPath)。 */
@@ -227,10 +562,9 @@ var DatabaseSessionSource = class extends BaseAgent {
227
562
  this.sessionMetaMap = meta;
228
563
  }
229
564
  /**
230
- * 变更检测:数据库内部变更难以按行定位,简单起见按库文件 mtime 判定。
231
- * 库有变更则标记全部缓存会话刷新。
565
+ * 变更检测:数据库内部变更难以按行定位,按库文件 mtime 判定。
232
566
  */
233
- checkForChanges(sinceTimestamp, cachedSessions) {
567
+ checkForChanges(sinceTimestamp, _cachedSessions) {
234
568
  const dbPath = this.getDatabasePath();
235
569
  if (!dbPath || !existsSync(dbPath)) {
236
570
  return { hasChanges: false, timestamp: Date.now() };
@@ -239,7 +573,6 @@ var DatabaseSessionSource = class extends BaseAgent {
239
573
  const hasChanges = statSync(dbPath).mtimeMs > sinceTimestamp;
240
574
  return {
241
575
  hasChanges,
242
- changedIds: hasChanges ? cachedSessions.map((session) => session.id) : [],
243
576
  timestamp: Date.now()
244
577
  };
245
578
  } catch {
@@ -251,7 +584,7 @@ var DatabaseSessionSource = class extends BaseAgent {
251
584
  return this.scan();
252
585
  }
253
586
  };
254
- function envPath(name) {
587
+ function readEnvPath(name) {
255
588
  const value = process.env[name];
256
589
  if (!value) return null;
257
590
  return value;
@@ -262,49 +595,17 @@ function firstExisting(...paths) {
262
595
  }
263
596
  return null;
264
597
  }
265
- function getDataHome() {
266
- const xdg = envPath("XDG_DATA_HOME");
598
+ function resolveDataHome() {
599
+ const xdg = readEnvPath("XDG_DATA_HOME");
267
600
  if (xdg) return xdg;
268
601
  const p = platform();
269
602
  if (p === "win32") {
270
- return envPath("LOCALAPPDATA") ?? envPath("APPDATA") ?? join(homedir(), "AppData", "Local");
271
- }
272
- return join(homedir(), ".local", "share");
273
- }
274
- function resolveProviderRoots() {
275
- const home = homedir();
276
- return {
277
- codexRoot: envPath("CODEX_HOME") ?? join(home, ".codex"),
278
- claudeRoot: envPath("CLAUDE_CONFIG_DIR") ?? join(home, ".claude"),
279
- kimiRoot: envPath("KIMI_SHARE_DIR") ?? join(home, ".kimi"),
280
- opencodeRoot: join(getDataHome(), "opencode"),
281
- piRoot: envPath("PI_HOME") ?? join(home, ".pi"),
282
- zcodeRoot: getZCodeDataPath()
283
- };
284
- }
285
- function getCursorDataPath() {
286
- const override = envPath("CURSOR_DATA_PATH");
287
- if (override) return override;
288
- const p = platform();
289
- if (p === "darwin") {
290
- return firstExisting(join(homedir(), "Library", "Application Support", "Cursor", "User"));
291
- }
292
- if (p === "linux") {
293
- const xdg = envPath("XDG_CONFIG_HOME") ?? join(homedir(), ".config");
294
- return firstExisting(join(xdg, "Cursor", "User"));
295
- }
296
- if (p === "win32") {
297
- const appData = envPath("APPDATA") ?? join(homedir(), "AppData", "Roaming");
298
- return firstExisting(join(appData, "Cursor", "User"));
603
+ return readEnvPath("LOCALAPPDATA") ?? readEnvPath("APPDATA") ?? join2(homedir(), "AppData", "Local");
299
604
  }
300
- return null;
605
+ return join2(homedir(), ".local", "share");
301
606
  }
302
- function getZCodeDataPath() {
303
- const p = platform();
304
- if (p === "darwin" || p === "win32") {
305
- return join(homedir(), ".zcode");
306
- }
307
- return null;
607
+ function resolveHomePath(environmentVariable, fallbackDirectory) {
608
+ return readEnvPath(environmentVariable) ?? join2(homedir(), fallbackDirectory);
308
609
  }
309
610
  var READ_CHUNK_BYTES = 1 << 20;
310
611
  function* parseJsonlLines(content) {
@@ -329,18 +630,26 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
329
630
  try {
330
631
  const buffer = Buffer.alloc(chunkBytes);
331
632
  const decoder = new StringDecoder("utf8");
332
- let remainder = "";
633
+ let pending = [];
333
634
  let bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
334
635
  while (bytesRead > 0) {
335
- const lines = (remainder + decoder.write(buffer.subarray(0, bytesRead))).split("\n");
336
- remainder = lines.pop();
337
- for (const line of lines) {
338
- const trimmed = line.trim();
339
- if (trimmed) yield trimmed;
636
+ const decoded = decoder.write(buffer.subarray(0, bytesRead));
637
+ const lastBreak = decoded.lastIndexOf("\n");
638
+ if (lastBreak === -1) {
639
+ if (decoded) pending.push(decoded);
640
+ } else {
641
+ pending.push(decoded.slice(0, lastBreak));
642
+ const complete = pending.join("");
643
+ pending = [decoded.slice(lastBreak + 1)];
644
+ for (const line of complete.split("\n")) {
645
+ const trimmed = line.trim();
646
+ if (trimmed) yield trimmed;
647
+ }
340
648
  }
341
649
  bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
342
650
  }
343
- const tail = (remainder + decoder.end()).trim();
651
+ pending.push(decoder.end());
652
+ const tail = pending.join("").trim();
344
653
  if (tail) yield tail;
345
654
  } finally {
346
655
  closeSync(fd);
@@ -412,11 +721,6 @@ function cleanDisplayText(text) {
412
721
  cleaned = cleaned.replace(/[ \t]+(?=\r?\n|$)/g, "").replace(/(?:\r?\n)+$/g, "");
413
722
  return cleaned.trim() ? cleaned : null;
414
723
  }
415
- function firstVisibleLine(text) {
416
- const cleaned = cleanDisplayText(text);
417
- if (!cleaned) return null;
418
- return cleaned.split("\n").find((line) => line.trim())?.trim() ?? null;
419
- }
420
724
  var TITLE_MAX_LENGTH = 100;
421
725
  var UNTITLED_SESSION = "Untitled Session";
422
726
  function normalizeTitleText(text) {
@@ -459,28 +763,25 @@ function cleanUnknown(value) {
459
763
  return cleaned;
460
764
  }
461
765
  function cleanMessagePart(part) {
462
- const next = { ...part };
463
- if (typeof next.text === "string") {
464
- next.text = cleanInternalText(next.text);
465
- if (!next.text && (next.type === "text" || next.type === "reasoning" || next.type === "plan")) {
466
- return null;
467
- }
468
- }
469
- if (typeof next.title === "string") {
470
- const title = cleanInternalText(next.title);
471
- if (title) next.title = title;
472
- else delete next.title;
473
- }
474
- if (next.input !== void 0) {
475
- next.input = cleanUnknown(next.input);
476
- }
477
- if (next.output !== void 0) {
478
- next.output = cleanUnknown(next.output);
479
- }
480
- if (next.state !== void 0) {
481
- next.state = cleanUnknown(next.state);
482
- }
483
- return next;
766
+ if (part.type === "text" || part.type === "reasoning" || part.type === "plan") {
767
+ const text = cleanInternalText(part.text);
768
+ return text ? { ...part, text } : null;
769
+ }
770
+ if (part.type !== "tool") return part;
771
+ const title = part.title ? cleanInternalText(part.title) : "";
772
+ const state = {
773
+ ...part.state,
774
+ ...part.state.input !== void 0 ? { input: cleanUnknown(part.state.input) } : {},
775
+ ...part.state.output !== void 0 ? { output: cleanUnknown(part.state.output) } : {},
776
+ ...part.state.error !== void 0 ? { error: cleanUnknown(part.state.error) } : {},
777
+ ...part.state.metadata !== void 0 ? { metadata: cleanUnknown(part.state.metadata) } : {}
778
+ };
779
+ const { title: _title, ...withoutTitle } = part;
780
+ return {
781
+ ...withoutTitle,
782
+ ...title ? { title } : {},
783
+ state
784
+ };
484
785
  }
485
786
  function cleanMessageParts(parts) {
486
787
  return parts.flatMap((part) => {
@@ -503,7 +804,7 @@ function firstUserMessageTitle(messages) {
503
804
  for (const message of messages) {
504
805
  if (message.role !== "user") continue;
505
806
  for (const part of message.parts) {
506
- if (part.type !== "text" || typeof part.text !== "string") continue;
807
+ if (part.type !== "text") continue;
507
808
  const title = normalizeTitleText(cleanInternalText(part.text));
508
809
  if (title) return title;
509
810
  }
@@ -587,10 +888,10 @@ function costNumber(value, fallback) {
587
888
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
588
889
  }
589
890
  function getCacheDir() {
590
- return join2(homedir2(), ".cache", "codesesh");
891
+ return join3(homedir2(), ".cache", "codesesh");
591
892
  }
592
893
  function getCachePath() {
593
- return join2(getCacheDir(), "litellm-pricing.json");
894
+ return join3(getCacheDir(), "litellm-pricing.json");
594
895
  }
595
896
  function loadSnapshot() {
596
897
  const map = /* @__PURE__ */ new Map();
@@ -794,42 +1095,6 @@ function estimateCostForTokens(model, usage) {
794
1095
  const cost = input * pricing.inputCostPerToken + output * pricing.outputCostPerToken + reasoning * pricing.reasoningCostPerToken + cacheRead * pricing.cacheReadCostPerToken + cacheCreate * pricing.cacheCreateCostPerToken + webSearchRequests * pricing.webSearchCostPerRequest;
795
1096
  return cost > 0 ? { cost: Number(cost.toFixed(8)), source: "estimated" } : null;
796
1097
  }
797
- function applyMessageCost(message) {
798
- if ((message.cost ?? 0) > 0) {
799
- message.cost_source = "recorded";
800
- return;
801
- }
802
- const estimate = estimateCostForTokens(message.model, message.tokens);
803
- if (!estimate) return;
804
- message.cost = estimate.cost;
805
- message.cost_source = estimate.source;
806
- }
807
- function applyMessageCosts(messages) {
808
- let totalCost = 0;
809
- let source;
810
- for (const message of messages) {
811
- applyMessageCost(message);
812
- const cost = message.cost ?? 0;
813
- if (cost <= 0) continue;
814
- totalCost += cost;
815
- if (message.cost_source === "estimated") source = "estimated";
816
- else if (!source) source = "recorded";
817
- }
818
- return { totalCost: Number(totalCost.toFixed(8)), source };
819
- }
820
- function withEstimatedSessionCost(stats, model) {
821
- if (stats.total_cost > 0) {
822
- return { ...stats, cost_source: stats.cost_source ?? "recorded" };
823
- }
824
- const estimate = estimateCostForTokens(model, {
825
- input: stats.total_input_tokens,
826
- output: stats.total_output_tokens,
827
- cache_read: stats.total_cache_read_tokens,
828
- cache_create: stats.total_cache_create_tokens
829
- });
830
- if (!estimate) return stats;
831
- return { ...stats, total_cost: estimate.cost, cost_source: estimate.source };
832
- }
833
1098
  function estimateTokenCost(model, tokens) {
834
1099
  return estimateCostForTokens(model, tokens)?.cost ?? null;
835
1100
  }
@@ -943,9 +1208,12 @@ var TranscriptBuilder = class {
943
1208
  }
944
1209
  resolveToolCall(callId, resolution) {
945
1210
  return this.updateToolCall(callId, (part) => {
946
- const state = part.state ?? (part.state = {});
1211
+ const state = part.state;
947
1212
  if (resolution.output !== void 0) state.output = resolution.output;
948
1213
  if (resolution.status !== void 0) state.status = resolution.status;
1214
+ else if (resolution.output !== void 0 && state.status === "running") {
1215
+ state.status = "completed";
1216
+ }
949
1217
  if (resolution.metadata !== void 0) state.metadata = resolution.metadata;
950
1218
  if (resolution.consume) this.pendingToolCalls.delete(callId);
951
1219
  });
@@ -1004,7 +1272,9 @@ var TranscriptBuilder = class {
1004
1272
  }
1005
1273
  appendPartIfNew(message, part) {
1006
1274
  const tail = message.parts.at(-1);
1007
- if (tail?.type === part.type && tail.text === part.text) return;
1275
+ if ("text" in part && tail?.type === part.type && "text" in tail && tail.text === part.text) {
1276
+ return;
1277
+ }
1008
1278
  message.parts.push(part);
1009
1279
  }
1010
1280
  applyMissingMetadata(message, input) {
@@ -1044,6 +1314,9 @@ var TranscriptBuilder = class {
1044
1314
  }
1045
1315
  };
1046
1316
  var HEAD_INDEX_VERSION = "claudecode-head-v2";
1317
+ function resolveClaudeCodeDataRoot() {
1318
+ return resolveHomePath("CLAUDE_CONFIG_DIR", ".claude");
1319
+ }
1047
1320
  function parseTimestampMs(data) {
1048
1321
  const raw = data["timestamp"];
1049
1322
  const value = asString(raw);
@@ -1089,7 +1362,7 @@ function extractClaudeUsage(data, msg) {
1089
1362
  cacheCreate: readUsageNumber(usage, "cache_creation_input_tokens")
1090
1363
  };
1091
1364
  }
1092
- var ClaudeCodeAgent = class extends FileSystemSessionSource {
1365
+ var ClaudeCodeAgent = class extends SingleFileSessionSource {
1093
1366
  name = "claudecode";
1094
1367
  displayName = "Claude Code";
1095
1368
  basePath = null;
@@ -1097,16 +1370,22 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1097
1370
  sessionsIndexCache = {};
1098
1371
  sessionsIndexMtime = {};
1099
1372
  findBasePath() {
1100
- const roots = resolveProviderRoots();
1101
- return firstExisting(join3(roots.claudeRoot, "projects"), "data/claudecode");
1373
+ return firstExisting(join4(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
1374
+ }
1375
+ getSessionWatchPlan() {
1376
+ const dataRoot = resolveClaudeCodeDataRoot();
1377
+ return {
1378
+ status: "supported",
1379
+ targets: [{ root: dataRoot, path: join4(dataRoot, "projects") }, { path: "data/claudecode" }]
1380
+ };
1102
1381
  }
1103
1382
  isAvailable() {
1104
1383
  this.basePath = this.findBasePath();
1105
1384
  if (!this.basePath) return false;
1106
1385
  try {
1107
- for (const entry of readdirSync(this.basePath)) {
1108
- const dir = join3(this.basePath, entry);
1109
- if (existsSync4(dir) && readdirSync(dir).some((f) => f.endsWith(".jsonl"))) {
1386
+ for (const entry of readdirSync2(this.basePath)) {
1387
+ const dir = join4(this.basePath, entry);
1388
+ if (existsSync4(dir) && readdirSync2(dir).some((f) => f.endsWith(".jsonl"))) {
1110
1389
  return true;
1111
1390
  }
1112
1391
  }
@@ -1116,34 +1395,24 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1116
1395
  }
1117
1396
  listSessionSources(options) {
1118
1397
  if (!this.basePath) return [];
1119
- const refs = [];
1120
- for (const projectDir of this.listProjectDirs()) {
1398
+ const projectDirs = this.listProjectDirs();
1399
+ const indexMtimes = /* @__PURE__ */ new Map();
1400
+ for (const projectDir of projectDirs) {
1121
1401
  const indexPath = this.getSessionsIndexPath(projectDir);
1122
- for (const file of this.listJsonlFiles(projectDir)) {
1123
- let stat;
1124
- try {
1125
- stat = statSync2(file);
1126
- } catch {
1127
- continue;
1128
- }
1129
- if (!matchesScanWindow(stat.mtimeMs, options)) continue;
1130
- const sessionId = basename2(file, ".jsonl");
1131
- refs.push({
1132
- sessionId,
1133
- sourcePath: file,
1134
- fingerprint: this.sourceFingerprint(stat, indexPath)
1135
- });
1136
- }
1402
+ indexMtimes.set(projectDir, this.readFileMtimeMs(indexPath));
1137
1403
  }
1138
- return refs;
1404
+ return this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
1405
+ recursive: false,
1406
+ scanWindow: options
1407
+ }).map(({ file, stat }) => ({
1408
+ sessionId: basename2(file, ".jsonl"),
1409
+ sourcePath: file,
1410
+ fingerprint: this.sourceFingerprint(stat, indexMtimes.get(dirname(file)) ?? null)
1411
+ }));
1139
1412
  }
1140
- scanSessionSource(sourcePath) {
1413
+ parseFileSessionHead(sourcePath) {
1141
1414
  const projectDir = dirname(sourcePath);
1142
- const head = getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir));
1143
- if (head) {
1144
- this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, sourcePath, projectDir));
1145
- }
1146
- return head;
1415
+ return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir));
1147
1416
  }
1148
1417
  getSessionData(sessionId) {
1149
1418
  const meta = this.sessionMetaMap.get(sessionId);
@@ -1153,11 +1422,10 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1153
1422
  if (!existsSync4(meta.sourcePath)) {
1154
1423
  throw new Error(`Session file missing: ${meta.sourcePath}`);
1155
1424
  }
1156
- const content = readFileSync2(meta.sourcePath, "utf-8");
1157
1425
  const builder = new TranscriptBuilder();
1158
1426
  const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
1159
1427
  const countedUsageKeys = /* @__PURE__ */ new Set();
1160
- for (const record of parseJsonlLines(content)) {
1428
+ for (const record of readJsonlFile(meta.sourcePath)) {
1161
1429
  try {
1162
1430
  this.convertRecord(record, builder, assistantUuidToToolCalls, countedUsageKeys);
1163
1431
  } catch {
@@ -1165,6 +1433,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1165
1433
  }
1166
1434
  const transcript = builder.finish();
1167
1435
  return {
1436
+ reference: { agentName: this.name, sessionId: meta.id },
1168
1437
  id: meta.id,
1169
1438
  title: meta.title,
1170
1439
  slug: `claudecode/${meta.id}`,
@@ -1180,60 +1449,38 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1180
1449
  listProjectDirs() {
1181
1450
  if (!this.basePath) return [];
1182
1451
  try {
1183
- return readdirSync(this.basePath).map((e) => join3(this.basePath, e)).filter((p) => existsSync4(p));
1184
- } catch {
1185
- return [];
1186
- }
1187
- }
1188
- listJsonlFiles(dir) {
1189
- try {
1190
- return readdirSync(dir).filter((f) => f.endsWith(".jsonl") && f !== "sessions-index.json").map((f) => join3(dir, f));
1452
+ return readdirSync2(this.basePath).map((e) => join4(this.basePath, e)).filter((p) => existsSync4(p));
1191
1453
  } catch {
1192
1454
  return [];
1193
1455
  }
1194
1456
  }
1195
- buildSessionMeta(head, file, projectDir) {
1457
+ createFileSessionMeta(head, source) {
1458
+ const projectDir = dirname(source.file);
1196
1459
  const indexPath = this.getSessionsIndexPath(projectDir);
1197
- const stat = statSync2(file);
1198
- return {
1199
- id: head.id,
1200
- title: head.title,
1201
- sourcePath: file,
1202
- sourceFingerprint: this.sourceFingerprint(stat, indexPath),
1203
- sourceMtimeMs: stat.mtimeMs,
1204
- indexPath: existsSync4(indexPath) ? indexPath : null,
1205
- indexMtimeMs: this.getFileMtimeMs(indexPath),
1206
- headIndexVersion: HEAD_INDEX_VERSION,
1207
- directory: head.directory,
1208
- model: head.stats.total_tokens ? "unknown" : void 0,
1209
- messageCount: head.stats.message_count,
1210
- createdAt: head.time_created,
1211
- updatedAt: head.time_updated ?? head.time_created
1212
- };
1460
+ const indexMtime = this.readFileMtimeMs(indexPath);
1461
+ return this.buildFileSessionMeta({
1462
+ head,
1463
+ source,
1464
+ fingerprint: this.sourceFingerprint(source.stat, indexMtime),
1465
+ extras: {
1466
+ indexPath: indexMtime === null ? null : indexPath,
1467
+ indexMtimeMs: indexMtime,
1468
+ headIndexVersion: HEAD_INDEX_VERSION,
1469
+ model: head.stats.total_tokens ? "unknown" : void 0
1470
+ }
1471
+ });
1213
1472
  }
1214
1473
  /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
1215
- sourceFingerprint(stat, indexPath) {
1216
- return JSON.stringify([
1217
- HEAD_INDEX_VERSION,
1218
- stat.mtimeMs,
1219
- stat.size,
1220
- this.getFileMtimeMs(indexPath)
1221
- ]);
1474
+ sourceFingerprint(stat, indexMtime) {
1475
+ return JSON.stringify([HEAD_INDEX_VERSION, stat.mtimeMs, stat.size, indexMtime]);
1222
1476
  }
1223
1477
  getSessionsIndexPath(projectDir) {
1224
- return join3(projectDir, "sessions-index.json");
1225
- }
1226
- getFileMtimeMs(filePath) {
1227
- try {
1228
- return statSync2(filePath).mtimeMs;
1229
- } catch {
1230
- return null;
1231
- }
1478
+ return join4(projectDir, "sessions-index.json");
1232
1479
  }
1233
1480
  loadSessionsIndex(projectDir) {
1234
1481
  const cacheKey = basename2(projectDir);
1235
1482
  const indexPath = this.getSessionsIndexPath(projectDir);
1236
- const mtime = this.getFileMtimeMs(indexPath);
1483
+ const mtime = this.readFileMtimeMs(indexPath);
1237
1484
  if (cacheKey in this.sessionsIndexCache && this.sessionsIndexMtime[cacheKey] === mtime) {
1238
1485
  return this.sessionsIndexCache[cacheKey];
1239
1486
  }
@@ -1255,25 +1502,14 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1255
1502
  this.sessionsIndexMtime[cacheKey] = mtime;
1256
1503
  return map;
1257
1504
  }
1258
- parseSessionHead(filePath, projectDir) {
1259
- return getParsedSession(this.parseSessionHeadResult(filePath, projectDir));
1260
- }
1261
1505
  parseSessionHeadResult(filePath, projectDir) {
1262
- const content = readFileSync2(filePath, "utf-8");
1263
- const lines = content.split("\n").filter((l) => l.trim());
1264
- if (lines.length === 0) return skippedSession("empty file");
1265
1506
  const sessionId = basename2(filePath, ".jsonl");
1266
- let firstRecord;
1267
- try {
1268
- firstRecord = JSON.parse(lines[0]);
1269
- } catch {
1270
- return skippedSession("malformed first record");
1271
- }
1272
- const createdAt = parseTimestampMs(firstRecord) || statSync2(filePath).mtimeMs;
1273
1507
  const index = this.loadSessionsIndex(projectDir);
1274
1508
  const indexEntry = index.get(sessionId);
1275
1509
  const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
1276
- let updatedAt = createdAt;
1510
+ let createdAt = 0;
1511
+ let updatedAt = 0;
1512
+ let lineIndex = 0;
1277
1513
  let messageCount = 0;
1278
1514
  let model = null;
1279
1515
  let cwd = null;
@@ -1285,9 +1521,22 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1285
1521
  const modelUsageMap = {};
1286
1522
  const countedUsageKeys = /* @__PURE__ */ new Set();
1287
1523
  let messageTitle = null;
1288
- for (const [lineIndex, line] of lines.entries()) {
1524
+ for (const line of readJsonlFileLines(filePath)) {
1525
+ let data;
1526
+ try {
1527
+ data = JSON.parse(line);
1528
+ } catch {
1529
+ if (lineIndex === 0) return skippedSession("malformed first record");
1530
+ lineIndex += 1;
1531
+ continue;
1532
+ }
1533
+ if (lineIndex === 0) {
1534
+ createdAt = parseTimestampMs(data) || statSync2(filePath).mtimeMs;
1535
+ updatedAt = createdAt;
1536
+ }
1537
+ const recordIndex = lineIndex;
1538
+ lineIndex += 1;
1289
1539
  try {
1290
- const data = JSON.parse(line);
1291
1540
  if (isInternalEventType(data["type"])) continue;
1292
1541
  const ts = parseTimestampMs(data);
1293
1542
  if (ts > updatedAt) updatedAt = ts;
@@ -1310,7 +1559,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1310
1559
  const m = asString(msg["model"]);
1311
1560
  if (m?.trim()) model = m.trim();
1312
1561
  }
1313
- if (messageTitle === null && lineIndex < 20 && role === "user") {
1562
+ if (messageTitle === null && recordIndex < 20 && role === "user") {
1314
1563
  const candidate = this.extractUserMessageTitle(msg["content"]);
1315
1564
  if (candidate) messageTitle = candidate;
1316
1565
  }
@@ -1345,6 +1594,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1345
1594
  } catch {
1346
1595
  }
1347
1596
  }
1597
+ if (lineIndex === 0) return skippedSession("empty file");
1348
1598
  const directory = cwd ?? projectDir;
1349
1599
  const directoryTitle = basenameTitle(directory) || basenameTitle(projectDir);
1350
1600
  const title = resolveSessionTitle(explicitTitle, messageTitle, directoryTitle);
@@ -1523,6 +1773,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1523
1773
  callID: String(part["id"] ?? ""),
1524
1774
  title: `Tool: ${toolName}`,
1525
1775
  state: {
1776
+ status: "running",
1526
1777
  input: part["input"] ?? {},
1527
1778
  output: null
1528
1779
  },
@@ -1609,15 +1860,16 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1609
1860
  backfillToolOutput(builder, callId, outputParts, stateUpdates) {
1610
1861
  if (!callId) return false;
1611
1862
  return builder.updateToolCall(callId, (part) => {
1612
- const state = part.state ?? (part.state = {});
1863
+ const state = part.state;
1613
1864
  if (outputParts.length > 0) {
1614
1865
  const existing = state.output;
1615
1866
  if (Array.isArray(existing)) existing.push(...outputParts);
1616
1867
  else if (existing == null) state.output = [...outputParts];
1617
1868
  else state.output = [existing, ...outputParts];
1618
1869
  }
1619
- if (stateUpdates) Object.assign(state, stateUpdates);
1620
- if (outputParts.length > 0 && !state.status) state.status = "completed";
1870
+ if (stateUpdates?.status) state.status = stateUpdates.status;
1871
+ if (stateUpdates?.metadata !== void 0) state.metadata = stateUpdates.metadata;
1872
+ if (outputParts.length > 0 && state.status === "running") state.status = "completed";
1621
1873
  });
1622
1874
  }
1623
1875
  resolveToolCallId(data, item, assistantUuidToToolCalls) {
@@ -1635,11 +1887,11 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1635
1887
  const updates = {};
1636
1888
  const success = result["success"];
1637
1889
  if (typeof success === "boolean") {
1638
- updates["status"] = success ? "success" : "error";
1890
+ updates.status = success ? "completed" : "error";
1639
1891
  }
1640
1892
  const commandName = result["commandName"];
1641
1893
  if (commandName) {
1642
- updates["meta"] = { commandName };
1894
+ updates.metadata = { commandName };
1643
1895
  }
1644
1896
  return updates;
1645
1897
  }
@@ -1715,9 +1967,9 @@ function backupDatabase(db, dbPath, label) {
1715
1967
  return null;
1716
1968
  }
1717
1969
  const timestamp = new Date(Date.now()).toISOString().replaceAll(":", "").replaceAll(".", "-");
1718
- let backupPath = join4(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.bak`);
1970
+ let backupPath = join5(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.bak`);
1719
1971
  for (let counter = 1; existsSync5(backupPath); counter += 1) {
1720
- backupPath = join4(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.${counter}.bak`);
1972
+ backupPath = join5(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.${counter}.bak`);
1721
1973
  }
1722
1974
  db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
1723
1975
  return backupPath;
@@ -1738,23 +1990,53 @@ function runSchemaMigrations(db, options) {
1738
1990
  if (migration.version > options.targetVersion) {
1739
1991
  break;
1740
1992
  }
1741
- if (migration.destructive) {
1742
- const backupPath = backupDatabaseIfPopulated(
1743
- db,
1744
- options.dbPath,
1745
- options.backupLabel,
1746
- options.backupTables
1747
- );
1748
- if (backupPath) {
1749
- backups.push(backupPath);
1993
+ const fromVersion = currentVersion;
1994
+ const startedAt = performance.now();
1995
+ getCoreDiagnostics()?.info?.("sqlite.migration.started", {
1996
+ label: options.backupLabel,
1997
+ from_version: fromVersion,
1998
+ to_version: migration.version,
1999
+ destructive: migration.destructive ?? false
2000
+ });
2001
+ try {
2002
+ let backupCreated = false;
2003
+ if (migration.destructive) {
2004
+ const backupPath = backupDatabaseIfPopulated(
2005
+ db,
2006
+ options.dbPath,
2007
+ options.backupLabel,
2008
+ options.backupTables
2009
+ );
2010
+ if (backupPath) {
2011
+ backups.push(backupPath);
2012
+ backupCreated = true;
2013
+ }
1750
2014
  }
2015
+ const apply = db.transaction(() => {
2016
+ migration.migrate(db);
2017
+ setUserVersion(db, migration.version);
2018
+ });
2019
+ apply();
2020
+ currentVersion = migration.version;
2021
+ getCoreDiagnostics()?.info?.("sqlite.migration.completed", {
2022
+ label: options.backupLabel,
2023
+ from_version: fromVersion,
2024
+ to_version: migration.version,
2025
+ destructive: migration.destructive ?? false,
2026
+ backup_created: backupCreated,
2027
+ duration_ms: Math.round(performance.now() - startedAt)
2028
+ });
2029
+ } catch (error) {
2030
+ getCoreDiagnostics()?.warn("sqlite.migration.failed", {
2031
+ label: options.backupLabel,
2032
+ from_version: fromVersion,
2033
+ to_version: migration.version,
2034
+ destructive: migration.destructive ?? false,
2035
+ duration_ms: Math.round(performance.now() - startedAt),
2036
+ message: error instanceof Error ? error.message : String(error)
2037
+ });
2038
+ throw error;
1751
2039
  }
1752
- const apply = db.transaction(() => {
1753
- migration.migrate(db);
1754
- setUserVersion(db, migration.version);
1755
- });
1756
- apply();
1757
- currentVersion = migration.version;
1758
2040
  }
1759
2041
  if (currentVersion < options.targetVersion) {
1760
2042
  setUserVersion(db, options.targetVersion);
@@ -1843,6 +2125,9 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1843
2125
  }
1844
2126
  return this.dbPath;
1845
2127
  }
2128
+ getSessionWatchPlan() {
2129
+ return this.config.getSessionWatchPlan();
2130
+ }
1846
2131
  findDbPath() {
1847
2132
  return this.config.findDbPath();
1848
2133
  }
@@ -1960,26 +2245,10 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1960
2245
  const partData = parseJsonRecord(partRow.data, this.name, "part.data");
1961
2246
  const partType = String(partData.type ?? "");
1962
2247
  if (isInternalEventType(partType)) return null;
1963
- if (partType === "text" || partType === "reasoning") {
1964
- const text = cleanInternalText(String(partData.text ?? ""));
1965
- if (!text) return null;
1966
- return {
1967
- type: partType,
1968
- text,
1969
- time_created: Number(partRow.time_created ?? 0)
1970
- };
1971
- }
1972
- if (partType === "tool") {
1973
- return {
1974
- type: "tool",
1975
- tool: String(partData.tool ?? ""),
1976
- callID: String(partData.callID ?? ""),
1977
- title: cleanInternalText(String(partData.title ?? "")),
1978
- state: asRecord(partData.state) ?? {},
1979
- time_created: Number(partRow.time_created ?? 0)
1980
- };
1981
- }
1982
- return null;
2248
+ const [part] = normalizeMessageParts([
2249
+ { ...partData, time_created: Number(partRow.time_created ?? 0) }
2250
+ ]);
2251
+ return part ? cleanMessagePart(part) : null;
1983
2252
  }
1984
2253
  readMessageParts(db, messageId) {
1985
2254
  const partRows = db.prepare("SELECT data, time_created FROM part WHERE message_id = ? ORDER BY time_created ASC").all(messageId);
@@ -2120,6 +2389,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2120
2389
  if (message.cost_source === "estimated") hasEstimatedCost = true;
2121
2390
  }
2122
2391
  return {
2392
+ reference: { agentName: this.name, sessionId: id },
2123
2393
  id,
2124
2394
  title,
2125
2395
  slug,
@@ -2142,17 +2412,30 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
2142
2412
  }
2143
2413
  }
2144
2414
  };
2415
+ function resolveOpenCodeDataRoot() {
2416
+ return join6(resolveDataHome(), "opencode");
2417
+ }
2145
2418
  function findOpenCodeDbPath() {
2146
2419
  if (!isSqliteAvailable()) return null;
2147
- const roots = resolveProviderRoots();
2148
- return firstExisting(join5(roots.opencodeRoot, "opencode.db"), "data/opencode/opencode.db");
2420
+ return firstExisting(join6(resolveOpenCodeDataRoot(), "opencode.db"), "data/opencode/opencode.db");
2421
+ }
2422
+ function getOpenCodeSessionWatchPlan() {
2423
+ const dataRoot = resolveOpenCodeDataRoot();
2424
+ return {
2425
+ status: "supported",
2426
+ targets: [
2427
+ { root: dataRoot, path: join6(dataRoot, "opencode.db") },
2428
+ { root: "data/opencode", path: "data/opencode/opencode.db" }
2429
+ ]
2430
+ };
2149
2431
  }
2150
2432
  var OpenCodeAgent = class extends OpenCodeSqliteAgent {
2151
2433
  constructor() {
2152
2434
  super({
2153
2435
  name: "opencode",
2154
2436
  displayName: "OpenCode",
2155
- findDbPath: findOpenCodeDbPath
2437
+ findDbPath: findOpenCodeDbPath,
2438
+ getSessionWatchPlan: getOpenCodeSessionWatchPlan
2156
2439
  });
2157
2440
  }
2158
2441
  };
@@ -2165,6 +2448,9 @@ var KIMI_TOOL_TITLE_MAP = {
2165
2448
  Shell: "bash"
2166
2449
  };
2167
2450
  var KIMI_IGNORED_TOOLS = /* @__PURE__ */ new Set(["SetTodoList"]);
2451
+ function resolveKimiDataRoot() {
2452
+ return resolveHomePath("KIMI_SHARE_DIR", ".kimi");
2453
+ }
2168
2454
  function mapToolTitle(toolName) {
2169
2455
  return KIMI_TOOL_TITLE_MAP[toolName] ?? toolName;
2170
2456
  }
@@ -2236,16 +2522,14 @@ function kimiContentText(content) {
2236
2522
  }
2237
2523
  function extractFirstUserTitle(contextFile, wireFile) {
2238
2524
  if (contextFile && existsSync6(contextFile)) {
2239
- const content = readFileSync3(contextFile, "utf-8");
2240
- for (const record of parseJsonlLines(content)) {
2525
+ for (const record of readJsonlFile(contextFile)) {
2241
2526
  if (record.role !== "user") continue;
2242
2527
  const title = normalizeTitleText(kimiContentText(record.content));
2243
2528
  if (title) return title;
2244
2529
  }
2245
2530
  }
2246
2531
  if (wireFile && existsSync6(wireFile)) {
2247
- const content = readFileSync3(wireFile, "utf-8");
2248
- for (const record of parseJsonlLines(content)) {
2532
+ for (const record of readJsonlFile(wireFile)) {
2249
2533
  const message = asRecord(record.message) ?? {};
2250
2534
  if (message.type !== "TurnBegin") continue;
2251
2535
  const payload = asRecord(message.payload) ?? {};
@@ -2264,14 +2548,20 @@ var KimiAgent = class extends FileSystemSessionSource {
2264
2548
  projectMap = /* @__PURE__ */ new Map();
2265
2549
  defaultModel = null;
2266
2550
  findBasePath() {
2267
- const roots = resolveProviderRoots();
2268
- return firstExisting(join6(roots.kimiRoot, "sessions"), "data/kimi");
2551
+ return firstExisting(join7(resolveKimiDataRoot(), "sessions"), "data/kimi");
2552
+ }
2553
+ getSessionWatchPlan() {
2554
+ const dataRoot = resolveKimiDataRoot();
2555
+ return {
2556
+ status: "supported",
2557
+ targets: [{ root: dataRoot, path: join7(dataRoot, "sessions") }, { path: "data/kimi" }]
2558
+ };
2269
2559
  }
2270
2560
  /** Parse kimi.json and build md5(project_path) → cwd mapping */
2271
2561
  loadKimiConfig() {
2272
- const roots = resolveProviderRoots();
2273
- const configPath = join6(roots.kimiRoot, "kimi.json");
2274
- const tomlPath = join6(roots.kimiRoot, "config.toml");
2562
+ const dataRoot = resolveKimiDataRoot();
2563
+ const configPath = join7(dataRoot, "kimi.json");
2564
+ const tomlPath = join7(dataRoot, "config.toml");
2275
2565
  if (existsSync6(tomlPath)) {
2276
2566
  const configText = readFileSync3(tomlPath, "utf-8");
2277
2567
  this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
@@ -2305,14 +2595,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2305
2595
  if (!this.basePath) return [];
2306
2596
  const dirs = [];
2307
2597
  try {
2308
- for (const hashEntry of readdirSync2(this.basePath, { withFileTypes: true })) {
2598
+ for (const hashEntry of readdirSync3(this.basePath, { withFileTypes: true })) {
2309
2599
  if (!hashEntry.isDirectory()) continue;
2310
- const hashPath = join6(this.basePath, hashEntry.name);
2600
+ const hashPath = join7(this.basePath, hashEntry.name);
2311
2601
  try {
2312
- for (const sessionEntry of readdirSync2(hashPath, { withFileTypes: true })) {
2602
+ for (const sessionEntry of readdirSync3(hashPath, { withFileTypes: true })) {
2313
2603
  if (!sessionEntry.isDirectory()) continue;
2314
- const sessionPath = join6(hashPath, sessionEntry.name);
2315
- if (existsSync6(join6(sessionPath, "metadata.json")) || existsSync6(join6(sessionPath, "state.json"))) {
2604
+ const sessionPath = join7(hashPath, sessionEntry.name);
2605
+ if (existsSync6(join7(sessionPath, "metadata.json")) || existsSync6(join7(sessionPath, "state.json"))) {
2316
2606
  dirs.push(sessionPath);
2317
2607
  }
2318
2608
  }
@@ -2323,64 +2613,73 @@ var KimiAgent = class extends FileSystemSessionSource {
2323
2613
  }
2324
2614
  return dirs;
2325
2615
  }
2326
- /** Parse session directory, preferring state.json over metadata.json */
2327
- parseSessionDir(sessionDir) {
2328
- return getParsedSession(this.parseSessionDirResult(sessionDir));
2329
- }
2330
- parseSessionDirResult(sessionDir) {
2616
+ /**
2617
+ * 解析会话源,优先 state.json 而非 metadata.json。
2618
+ * 只读小 JSON 文件与 statSync,不触碰 transcript——枚举路径依赖这一点。
2619
+ */
2620
+ resolveSessionSourceResult(sessionDir) {
2331
2621
  try {
2332
2622
  const sessionId = basename4(sessionDir);
2333
2623
  const projectHash = basename4(dirname3(sessionDir));
2334
- const contextFile = join6(sessionDir, "context.jsonl");
2335
- const wireFile = join6(sessionDir, "wire.jsonl");
2336
- if (!existsSync6(contextFile) && !existsSync6(wireFile)) {
2624
+ const contextFile = join7(sessionDir, "context.jsonl");
2625
+ const wireFile = join7(sessionDir, "wire.jsonl");
2626
+ const existingContextFile = existsSync6(contextFile) ? contextFile : null;
2627
+ const existingWireFile = existsSync6(wireFile) ? wireFile : null;
2628
+ if (!existingContextFile && !existingWireFile) {
2337
2629
  return skippedSession("missing transcript");
2338
2630
  }
2339
- const statePath = join6(sessionDir, "state.json");
2340
- const metaPath = join6(sessionDir, "metadata.json");
2341
- let title = "";
2631
+ const statePath = join7(sessionDir, "state.json");
2632
+ const metaPath = join7(sessionDir, "metadata.json");
2633
+ let explicitTitle = "";
2342
2634
  let wireMtime = null;
2343
2635
  let metaFile = "";
2344
2636
  if (existsSync6(statePath)) {
2345
2637
  const state = asRecord(JSON.parse(readFileSync3(statePath, "utf-8"))) ?? {};
2346
- title = String(state.custom_title ?? "");
2638
+ explicitTitle = String(state.custom_title ?? "");
2347
2639
  wireMtime = readWireMtime(state);
2348
2640
  metaFile = statePath;
2349
2641
  } else if (existsSync6(metaPath)) {
2350
2642
  const meta = asRecord(JSON.parse(readFileSync3(metaPath, "utf-8"))) ?? {};
2351
- title = String(meta.title ?? "");
2643
+ explicitTitle = String(meta.title ?? "");
2352
2644
  wireMtime = readWireMtime(meta);
2353
2645
  metaFile = metaPath;
2354
2646
  }
2355
- const cwd = this.projectMap.get(projectHash) || "";
2356
- const existingContextFile = existsSync6(contextFile) ? contextFile : null;
2357
- const existingWireFile = existsSync6(wireFile) ? wireFile : null;
2358
- const messageTitle = extractFirstUserTitle(existingContextFile, existingWireFile);
2359
2647
  const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync3(metaFile).mtimeMs : statSync3(sessionDir).mtimeMs;
2360
2648
  return parsedSession({
2361
2649
  id: sessionId,
2362
- title: resolveSessionTitle(title, messageTitle, null),
2363
2650
  sourcePath: sessionDir,
2364
- cwd,
2651
+ cwd: this.projectMap.get(projectHash) || "",
2365
2652
  contextFile: existingContextFile,
2366
2653
  wireFile: existingWireFile,
2367
2654
  createdAt,
2368
- metaFile
2655
+ metaFile,
2656
+ explicitTitle
2369
2657
  });
2370
2658
  } catch {
2371
2659
  return skippedSession("malformed metadata");
2372
2660
  }
2373
2661
  }
2662
+ /**
2663
+ * 在会话源之上补齐 title。仅当 state/metadata 里没有可用标题时才回退去读
2664
+ * transcript 找首条用户消息,所以标题解析的成本只在真正需要重解析时付出。
2665
+ */
2666
+ parseSessionDirResult(sessionDir) {
2667
+ const result = this.resolveSessionSourceResult(sessionDir);
2668
+ if (result.status !== "parsed") return result;
2669
+ const source = result.data;
2670
+ const title = normalizeTitleText(source.explicitTitle) ?? resolveSessionTitle(null, extractFirstUserTitle(source.contextFile, source.wireFile), null);
2671
+ return parsedSession({ ...source, title, sourceMtimeMs: source.createdAt });
2672
+ }
2374
2673
  listSessionSources(options) {
2375
2674
  if (!this.basePath) return [];
2376
2675
  const refs = [];
2377
2676
  for (const dir of this.listSessionDirs()) {
2378
- const meta = getParsedSession(this.parseSessionDirResult(dir));
2379
- if (!meta || !matchesScanWindow(meta.createdAt, options)) continue;
2677
+ const source = getParsedSession(this.resolveSessionSourceResult(dir));
2678
+ if (!source || !matchesScanWindow(source.createdAt, options)) continue;
2380
2679
  refs.push({
2381
- sessionId: meta.id,
2382
- sourcePath: meta.sourcePath,
2383
- fingerprint: this.sourceFingerprint(meta)
2680
+ sessionId: source.id,
2681
+ sourcePath: source.sourcePath,
2682
+ fingerprint: this.sourceFingerprint(source)
2384
2683
  });
2385
2684
  }
2386
2685
  return refs;
@@ -2411,12 +2710,11 @@ var KimiAgent = class extends FileSystemSessionSource {
2411
2710
  }
2412
2711
  getSessionDataFromContext(meta) {
2413
2712
  if (!meta.contextFile) throw new Error("context.jsonl is missing");
2414
- const content = readFileSync3(meta.contextFile, "utf-8");
2415
2713
  const builder = new TranscriptBuilder();
2416
2714
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
2417
2715
  let seq = 0;
2418
2716
  const fallbackTs = meta.createdAt;
2419
- for (const record of parseJsonlLines(content)) {
2717
+ for (const record of readJsonlFile(meta.contextFile)) {
2420
2718
  seq++;
2421
2719
  try {
2422
2720
  const role = String(record.role ?? "");
@@ -2467,15 +2765,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2467
2765
  return this.buildSessionData(meta, builder, stats);
2468
2766
  }
2469
2767
  getSessionDataFromWire(meta) {
2470
- const wirePath = meta.wireFile ?? join6(meta.sourcePath, "wire.jsonl");
2768
+ const wirePath = meta.wireFile ?? join7(meta.sourcePath, "wire.jsonl");
2471
2769
  if (!existsSync6(wirePath)) throw new Error("wire.jsonl is missing");
2472
- const content = readFileSync3(wirePath, "utf-8");
2473
2770
  const builder = new TranscriptBuilder();
2474
2771
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
2475
2772
  const openToolArgumentBuffer = /* @__PURE__ */ new Map();
2476
2773
  let openToolCallId = null;
2477
2774
  let seq = 0;
2478
- for (const record of parseJsonlLines(content)) {
2775
+ for (const record of readJsonlFile(wirePath)) {
2479
2776
  seq++;
2480
2777
  try {
2481
2778
  const message = asRecord(record.message) ?? {};
@@ -2555,7 +2852,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2555
2852
  tool: toolName,
2556
2853
  callID: callId,
2557
2854
  title: mapToolTitle(toolName),
2558
- state: { arguments: normalizedArgs, output: null },
2855
+ state: { status: "running", input: normalizedArgs, output: null },
2559
2856
  time_created: timestampMs
2560
2857
  };
2561
2858
  builder.appendToolCall(
@@ -2605,18 +2902,10 @@ var KimiAgent = class extends FileSystemSessionSource {
2605
2902
  }
2606
2903
  // --- Helpers ---
2607
2904
  sourceFingerprint(meta) {
2608
- const fileMtime = (path2) => {
2609
- if (!path2) return null;
2610
- try {
2611
- return statSync3(path2).mtimeMs;
2612
- } catch {
2613
- return null;
2614
- }
2615
- };
2616
2905
  return JSON.stringify([
2617
- fileMtime(meta.metaFile),
2618
- fileMtime(meta.contextFile),
2619
- fileMtime(meta.wireFile)
2906
+ this.readFileMtimeMs(meta.metaFile),
2907
+ this.readFileMtimeMs(meta.contextFile),
2908
+ this.readFileMtimeMs(meta.wireFile)
2620
2909
  ]);
2621
2910
  }
2622
2911
  buildContextAssistantMessage(record, seq, ignoredToolCallIds, fallbackTs) {
@@ -2654,7 +2943,11 @@ var KimiAgent = class extends FileSystemSessionSource {
2654
2943
  tool: toolName,
2655
2944
  callID: callId,
2656
2945
  title: mapToolTitle(toolName),
2657
- state: { arguments: normalizeToolArguments(function_.arguments), output: null },
2946
+ state: {
2947
+ status: "running",
2948
+ input: normalizeToolArguments(function_.arguments),
2949
+ output: null
2950
+ },
2658
2951
  time_created: fallbackTs
2659
2952
  };
2660
2953
  parts.push(part);
@@ -2680,8 +2973,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2680
2973
  try {
2681
2974
  const parsed = JSON.parse(combined);
2682
2975
  if (builder.updateToolCall(openCallId, (part) => {
2683
- const state = part.state ?? (part.state = {});
2684
- state.arguments = parsed;
2976
+ part.state.input = parsed;
2685
2977
  })) {
2686
2978
  buffer.delete(openCallId);
2687
2979
  }
@@ -2693,6 +2985,16 @@ var KimiAgent = class extends FileSystemSessionSource {
2693
2985
  if (!outputParts.length || !callId) return false;
2694
2986
  return builder.resolveToolCall(callId, { output: [...outputParts] });
2695
2987
  }
2988
+ /** Applies a `_usage` record's running total; other records are ignored. */
2989
+ applyUsageTotal(record, stats) {
2990
+ if (record.role !== "_usage") return;
2991
+ const tokenCount = asNumber(record.token_count);
2992
+ if (tokenCount === void 0) {
2993
+ reportFieldMismatch("kimi", "usage.token_count");
2994
+ return;
2995
+ }
2996
+ stats.total_tokens = tokenCount;
2997
+ }
2696
2998
  extractStats(sessionDir) {
2697
2999
  let totalCost = 0;
2698
3000
  const stats = {
@@ -2702,15 +3004,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2702
3004
  total_tokens: 0,
2703
3005
  message_count: 0
2704
3006
  };
2705
- const wirePath = join6(sessionDir, "wire.jsonl");
3007
+ const wirePath = join7(sessionDir, "wire.jsonl");
2706
3008
  if (!existsSync6(wirePath)) return stats;
3009
+ const contextPath = join7(sessionDir, "context.jsonl");
3010
+ const hasContext = existsSync6(contextPath);
2707
3011
  try {
2708
- const content = readFileSync3(wirePath, "utf-8");
2709
- for (const line of content.split("\n").filter((l) => l.trim())) {
2710
- try {
2711
- const data = asRecord(JSON.parse(line));
2712
- const tokenUsage = asRecord(asRecord(data?.message)?.usage);
2713
- if (!tokenUsage) continue;
3012
+ for (const record of readJsonlFile(wirePath)) {
3013
+ const tokenUsage = asRecord(asRecord(record.message)?.usage);
3014
+ if (tokenUsage) {
2714
3015
  const inputTokens = extractTokenField(tokenUsage, "input_tokens");
2715
3016
  const outputTokens = extractTokenField(tokenUsage, "output_tokens");
2716
3017
  stats.total_input_tokens += inputTokens;
@@ -2720,30 +3021,16 @@ var KimiAgent = class extends FileSystemSessionSource {
2720
3021
  output: outputTokens
2721
3022
  });
2722
3023
  if (cost !== null) totalCost += cost;
2723
- } catch {
2724
3024
  }
3025
+ if (!hasContext) this.applyUsageTotal(record, stats);
2725
3026
  }
2726
3027
  } catch {
2727
3028
  }
2728
- const contextPath = join6(sessionDir, "context.jsonl");
2729
- const rawPath = existsSync6(contextPath) ? contextPath : wirePath;
2730
- if (!existsSync6(rawPath)) return stats;
2731
- try {
2732
- const rawContent = readFileSync3(rawPath, "utf-8");
2733
- for (const line of rawContent.split("\n").filter((l) => l.trim())) {
2734
- try {
2735
- const data = asRecord(JSON.parse(line));
2736
- if (data?.role !== "_usage") continue;
2737
- const tokenCount = asNumber(data.token_count);
2738
- if (tokenCount === void 0) {
2739
- reportFieldMismatch("kimi", "usage.token_count");
2740
- continue;
2741
- }
2742
- stats.total_tokens = tokenCount;
2743
- } catch {
2744
- }
3029
+ if (hasContext) {
3030
+ try {
3031
+ for (const record of readJsonlFile(contextPath)) this.applyUsageTotal(record, stats);
3032
+ } catch {
2745
3033
  }
2746
- } catch {
2747
3034
  }
2748
3035
  stats.total_cost = Number(totalCost.toFixed(8));
2749
3036
  if (stats.total_cost > 0) {
@@ -2754,6 +3041,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2754
3041
  buildSessionData(meta, builder, stats) {
2755
3042
  const transcript = builder.finish(stats);
2756
3043
  return {
3044
+ reference: { agentName: this.name, sessionId: meta.id },
2757
3045
  id: meta.id,
2758
3046
  title: meta.title,
2759
3047
  slug: `kimi/${meta.id}`,
@@ -3027,6 +3315,9 @@ var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
3027
3315
  var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
3028
3316
  var HEAD_INDEX_VERSION2 = "codex-head-v1";
3029
3317
  var PARSER_VERSION = "codex-parser-v4";
3318
+ function resolveCodexDataRoot() {
3319
+ return resolveHomePath("CODEX_HOME", ".codex");
3320
+ }
3030
3321
  var DEVELOPER_LIKE_USER_MARKERS = [
3031
3322
  "agents.md instructions for",
3032
3323
  "<instructions>",
@@ -3209,26 +3500,31 @@ function extractPatchContent(lines, startIndex) {
3209
3500
  }
3210
3501
  return { text: contentLines.join("\n"), nextLineIndex: i };
3211
3502
  }
3212
- var CodexAgent = class extends FileSystemSessionSource {
3503
+ var CodexAgent = class extends SingleFileSessionSource {
3213
3504
  name = "codex";
3214
3505
  displayName = "Codex";
3215
3506
  basePath = null;
3216
3507
  sessionIndexCache = /* @__PURE__ */ new Map();
3217
- sessionIndexMtime = null;
3508
+ sessionIndexMtime;
3509
+ sessionIndexPath;
3218
3510
  // ---- BaseAgent implementation ----
3219
3511
  findBasePath() {
3220
- const roots = resolveProviderRoots();
3221
- return firstExisting(join7(roots.codexRoot, "sessions"));
3512
+ return firstExisting(join8(resolveCodexDataRoot(), "sessions"));
3513
+ }
3514
+ getSessionWatchPlan() {
3515
+ const dataRoot = resolveCodexDataRoot();
3516
+ return {
3517
+ status: "supported",
3518
+ targets: [
3519
+ { path: join8(dataRoot, "sessions") },
3520
+ { path: join8(dataRoot, "session_index.jsonl") }
3521
+ ]
3522
+ };
3222
3523
  }
3223
3524
  isAvailable() {
3224
3525
  this.basePath = this.findBasePath();
3225
3526
  if (!this.basePath) return false;
3226
- try {
3227
- const files = this.walkDirForRolloutFiles(this.basePath);
3228
- return files.length > 0;
3229
- } catch {
3230
- return false;
3231
- }
3527
+ return this.listRolloutFiles().length > 0;
3232
3528
  }
3233
3529
  listSessionSources(options) {
3234
3530
  if (!this.basePath) return [];
@@ -3239,14 +3535,6 @@ var CodexAgent = class extends FileSystemSessionSource {
3239
3535
  fingerprint: this.sourceFingerprint(file, stat)
3240
3536
  }));
3241
3537
  }
3242
- scanSessionSource(sourcePath, options) {
3243
- this.loadSessionIndex();
3244
- const head = getParsedSession(this.parseSessionHeadResult(sourcePath, options));
3245
- if (head) {
3246
- this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, sourcePath));
3247
- }
3248
- return head;
3249
- }
3250
3538
  getSessionData(sessionId) {
3251
3539
  const meta = this.sessionMetaMap.get(sessionId);
3252
3540
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
@@ -3334,6 +3622,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3334
3622
  cost_source: totalCost > 0 ? "estimated" : void 0
3335
3623
  });
3336
3624
  return {
3625
+ reference: { agentName: this.name, sessionId: meta.id },
3337
3626
  id: meta.id,
3338
3627
  title: meta.title,
3339
3628
  slug: `codex/${meta.id}`,
@@ -3347,54 +3636,27 @@ var CodexAgent = class extends FileSystemSessionSource {
3347
3636
  // ---- File listing ----
3348
3637
  listRolloutFiles(options) {
3349
3638
  if (!this.basePath) return [];
3350
- try {
3351
- return this.walkDirForRolloutFiles(this.basePath, options);
3352
- } catch {
3353
- return [];
3354
- }
3355
- }
3356
- /** Stats each rollout file once during the walk; caller reuses it for the scan window check and the fingerprint. */
3357
- walkDirForRolloutFiles(dir, options) {
3358
- const files = [];
3359
- try {
3360
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3361
- const fullPath = join7(dir, entry.name);
3362
- if (entry.isDirectory()) {
3363
- files.push(...this.walkDirForRolloutFiles(fullPath, options));
3364
- } else if (entry.name.endsWith(".jsonl") && entry.name.startsWith("rollout-")) {
3365
- let stat;
3366
- try {
3367
- stat = statSync4(fullPath);
3368
- } catch {
3369
- continue;
3370
- }
3371
- if (!matchesScanWindow(stat.mtimeMs, options)) continue;
3372
- files.push({ file: fullPath, stat });
3373
- }
3374
- }
3375
- } catch {
3376
- }
3377
- return files;
3639
+ return this.walkFiles(
3640
+ this.basePath,
3641
+ (entry) => entry.name.endsWith(".jsonl") && entry.name.startsWith("rollout-"),
3642
+ { scanWindow: options }
3643
+ );
3378
3644
  }
3379
- buildSessionMeta(head, file) {
3645
+ createFileSessionMeta(head, source) {
3380
3646
  const indexPath = this.getSessionIndexPath();
3381
- const stat = statSync4(file);
3382
- return {
3383
- id: head.id,
3384
- title: head.title,
3385
- sourcePath: file,
3386
- sourceFingerprint: this.sourceFingerprint(file, stat),
3387
- sourceMtimeMs: stat.mtimeMs,
3388
- indexPath: existsSync7(indexPath) ? indexPath : null,
3389
- indexMtimeMs: this.getFileMtimeMs(indexPath),
3390
- headIndexVersion: HEAD_INDEX_VERSION2,
3391
- parserVersion: PARSER_VERSION,
3392
- directory: head.directory,
3393
- model: null,
3394
- messageCount: head.stats.message_count,
3395
- createdAt: head.time_created,
3396
- updatedAt: head.time_updated ?? head.time_created
3397
- };
3647
+ const indexMtime = this.sessionIndexMtime ?? null;
3648
+ return this.buildFileSessionMeta({
3649
+ head,
3650
+ source,
3651
+ fingerprint: this.sourceFingerprint(source.file, source.stat),
3652
+ extras: {
3653
+ indexPath: indexMtime === null ? null : indexPath,
3654
+ indexMtimeMs: indexMtime,
3655
+ headIndexVersion: HEAD_INDEX_VERSION2,
3656
+ parserVersion: PARSER_VERSION,
3657
+ model: null
3658
+ }
3659
+ });
3398
3660
  }
3399
3661
  /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
3400
3662
  sourceFingerprint(file, stat) {
@@ -3408,38 +3670,37 @@ var CodexAgent = class extends FileSystemSessionSource {
3408
3670
  ]);
3409
3671
  }
3410
3672
  getSessionIndexPath() {
3411
- const roots = resolveProviderRoots();
3412
- return join7(roots.codexRoot, "session_index.jsonl");
3413
- }
3414
- getFileMtimeMs(filePath) {
3415
- try {
3416
- return statSync4(filePath).mtimeMs;
3417
- } catch {
3418
- return null;
3419
- }
3673
+ this.sessionIndexPath ??= join8(resolveCodexDataRoot(), "session_index.jsonl");
3674
+ return this.sessionIndexPath;
3420
3675
  }
3421
3676
  // ---- Session index ----
3422
3677
  loadSessionIndex() {
3423
3678
  const indexPath = this.getSessionIndexPath();
3424
- const mtime = this.getFileMtimeMs(indexPath);
3425
- if (this.sessionIndexCache.size > 0 && this.sessionIndexMtime === mtime) return;
3426
- this.sessionIndexCache.clear();
3427
- this.sessionIndexMtime = mtime;
3428
- if (mtime === null) return;
3679
+ const mtime = this.readFileMtimeMs(indexPath);
3680
+ if (this.sessionIndexMtime !== void 0 && this.sessionIndexMtime === mtime) return;
3681
+ if (mtime === null) {
3682
+ this.sessionIndexCache.clear();
3683
+ this.sessionIndexMtime = null;
3684
+ return;
3685
+ }
3429
3686
  try {
3430
3687
  const content = readFileSync4(indexPath, "utf-8");
3688
+ const cache = /* @__PURE__ */ new Map();
3431
3689
  for (const record of parseJsonlLines(content)) {
3432
3690
  const sid = String(record["id"] ?? "").trim();
3433
3691
  const threadName = String(record["thread_name"] ?? "").trim();
3434
3692
  if (sid && threadName) {
3435
- this.sessionIndexCache.set(sid, threadName);
3693
+ cache.set(sid, threadName);
3436
3694
  }
3437
3695
  }
3696
+ this.sessionIndexCache = cache;
3697
+ this.sessionIndexMtime = mtime;
3438
3698
  } catch {
3699
+ this.sessionIndexCache.clear();
3700
+ this.sessionIndexMtime = void 0;
3439
3701
  }
3440
3702
  }
3441
3703
  getTitleForSession(sessionId) {
3442
- this.loadSessionIndex();
3443
3704
  return this.sessionIndexCache.get(sessionId) ?? null;
3444
3705
  }
3445
3706
  // ---- Session head parsing ----
@@ -3453,6 +3714,10 @@ var CodexAgent = class extends FileSystemSessionSource {
3453
3714
  closeSync2(fd);
3454
3715
  }
3455
3716
  }
3717
+ parseFileSessionHead(filePath, options) {
3718
+ this.loadSessionIndex();
3719
+ return this.parseSessionHead(filePath, options);
3720
+ }
3456
3721
  parseSessionHead(filePath, options) {
3457
3722
  return getParsedSession(this.parseSessionHeadResult(filePath, options));
3458
3723
  }
@@ -3735,10 +4000,10 @@ var CodexAgent = class extends FileSystemSessionSource {
3735
4000
  const fullText = textParts.join("\n");
3736
4001
  const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
3737
4002
  if (planMatch) {
3738
- const planText = planMatch[1].trim();
4003
+ const planText2 = planMatch[1].trim();
3739
4004
  const planPart = {
3740
4005
  type: "plan",
3741
- text: planText,
4006
+ text: planText2,
3742
4007
  approval_status: "success",
3743
4008
  time_created: timestampMs
3744
4009
  };
@@ -3854,7 +4119,8 @@ var CodexAgent = class extends FileSystemSessionSource {
3854
4119
  callID: callId,
3855
4120
  title: `Tool: ${toolIdentity.tool}`,
3856
4121
  state: {
3857
- arguments: arguments_,
4122
+ status: "running",
4123
+ input: arguments_,
3858
4124
  output: null,
3859
4125
  metadata: toolIdentity.metadata
3860
4126
  },
@@ -3899,7 +4165,8 @@ var CodexAgent = class extends FileSystemSessionSource {
3899
4165
  callID: callId,
3900
4166
  title: `Tool: ${toolIdentity.tool}`,
3901
4167
  state: {
3902
- arguments: normalizedInput,
4168
+ status: "running",
4169
+ input: normalizedInput,
3903
4170
  output: null,
3904
4171
  metadata: toolIdentity.metadata
3905
4172
  },
@@ -3929,7 +4196,8 @@ var CodexAgent = class extends FileSystemSessionSource {
3929
4196
  callID: callId,
3930
4197
  title: `Tool: ${toolIdentity.tool}`,
3931
4198
  state: {
3932
- arguments: arguments_,
4199
+ status: "running",
4200
+ input: arguments_,
3933
4201
  output: null,
3934
4202
  metadata: toolIdentity.metadata
3935
4203
  },
@@ -4016,6 +4284,23 @@ var PerfTracer = class {
4016
4284
  }
4017
4285
  };
4018
4286
  var perf = new PerfTracer();
4287
+ function resolveCursorDataRoot() {
4288
+ const override = readEnvPath("CURSOR_DATA_PATH");
4289
+ if (override) return override;
4290
+ const currentPlatform = platform2();
4291
+ if (currentPlatform === "darwin") {
4292
+ return firstExisting(join9(homedir3(), "Library", "Application Support", "Cursor", "User"));
4293
+ }
4294
+ if (currentPlatform === "linux") {
4295
+ const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join9(homedir3(), ".config");
4296
+ return firstExisting(join9(configRoot, "Cursor", "User"));
4297
+ }
4298
+ if (currentPlatform === "win32") {
4299
+ const appData = readEnvPath("APPDATA") ?? join9(homedir3(), "AppData", "Roaming");
4300
+ return firstExisting(join9(appData, "Cursor", "User"));
4301
+ }
4302
+ return null;
4303
+ }
4019
4304
  function narrowString(field, value) {
4020
4305
  return narrowField("cursor", field, value, asString);
4021
4306
  }
@@ -4163,29 +4448,22 @@ function isInternalBubble(bubble) {
4163
4448
  return ["eventType", "kind", "subtype", "name"].some((key) => isInternalEventType(bubble[key]));
4164
4449
  }
4165
4450
  function buildToolState(action) {
4166
- const state = {};
4167
- if (action.input) {
4168
- state.input = action.input;
4169
- }
4451
+ let output = action.output;
4170
4452
  if (action.output != null) {
4171
4453
  const ts = 0;
4172
4454
  const outputParts = normalizeToolOutputParts2(action.output, ts);
4173
- state.output = outputParts.length > 0 ? outputParts : action.output;
4174
- }
4175
- if (action.state) {
4176
- Object.assign(state, action.state);
4455
+ output = outputParts.length > 0 ? outputParts : action.output;
4177
4456
  }
4178
- if (!state.status) {
4179
- if (typeof action.output === "object" && action.output !== null) {
4180
- const out = asRecord(action.output);
4181
- if (out?.success === true) state.status = "completed";
4182
- else if (out?.success === false) state.status = "error";
4183
- else state.status = "completed";
4184
- } else if (action.output != null) {
4185
- state.status = "completed";
4457
+ const [part] = normalizeMessageParts([
4458
+ {
4459
+ type: "tool",
4460
+ tool: action.tool ?? "unknown",
4461
+ input: action.input,
4462
+ output,
4463
+ state: action.state
4186
4464
  }
4187
- }
4188
- return state;
4465
+ ]);
4466
+ return part?.type === "tool" ? part.state : { status: "running" };
4189
4467
  }
4190
4468
  function buildToolPart(action, timestampMs) {
4191
4469
  const toolName = action.tool ?? "unknown";
@@ -4201,15 +4479,15 @@ function buildToolPart(action, timestampMs) {
4201
4479
  function buildTerminalToolPart(action, timestampMs) {
4202
4480
  const command = String(action.input?.command ?? "");
4203
4481
  const description = cleanInternalText(String(action.input?.commandDescription ?? ""));
4482
+ const state = buildToolState(action);
4483
+ state.input = { command };
4484
+ state.output = typeof action.output === "string" ? [{ type: "text", text: action.output, time_created: timestampMs }] : normalizeToolOutputParts2(action.output, timestampMs);
4204
4485
  return {
4205
4486
  type: "tool",
4206
4487
  tool: "bash",
4207
4488
  callID: "",
4208
4489
  title: description || `bash: ${command.slice(0, 60)}`,
4209
- state: {
4210
- input: { command },
4211
- output: typeof action.output === "string" ? [{ type: "text", text: action.output, time_created: timestampMs }] : normalizeToolOutputParts2(action.output, timestampMs)
4212
- },
4490
+ state,
4213
4491
  time_created: timestampMs
4214
4492
  };
4215
4493
  }
@@ -4237,9 +4515,22 @@ var CursorAgent = class extends DatabaseSessionSource {
4237
4515
  }
4238
4516
  findDbPath() {
4239
4517
  if (!isSqliteAvailable()) return null;
4240
- const dataPath = getCursorDataPath();
4518
+ const dataPath = resolveCursorDataRoot();
4241
4519
  if (!dataPath) return null;
4242
- return join8(dataPath, "globalStorage", "state.vscdb");
4520
+ return join9(dataPath, "globalStorage", "state.vscdb");
4521
+ }
4522
+ getSessionWatchPlan() {
4523
+ const dataPath = resolveCursorDataRoot();
4524
+ return {
4525
+ status: "supported",
4526
+ targets: dataPath ? [
4527
+ {
4528
+ root: dataPath,
4529
+ path: join9(dataPath, "globalStorage", "state.vscdb")
4530
+ },
4531
+ { root: dataPath, path: join9(dataPath, "workspaceStorage") }
4532
+ ] : []
4533
+ };
4243
4534
  }
4244
4535
  /**
4245
4536
  * Build a map of composerId → workspace folder path by reading
@@ -4247,9 +4538,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4247
4538
  */
4248
4539
  buildWorkspacePathMap() {
4249
4540
  const map = /* @__PURE__ */ new Map();
4250
- const dataPath = getCursorDataPath();
4541
+ const dataPath = resolveCursorDataRoot();
4251
4542
  if (!dataPath) return map;
4252
- const wsStoragePath = join8(dataPath, "workspaceStorage");
4543
+ const wsStoragePath = join9(dataPath, "workspaceStorage");
4253
4544
  if (!existsSync8(wsStoragePath)) return map;
4254
4545
  let entryNames;
4255
4546
  try {
@@ -4258,13 +4549,13 @@ var CursorAgent = class extends DatabaseSessionSource {
4258
4549
  return map;
4259
4550
  }
4260
4551
  for (const name of entryNames) {
4261
- const wsDir = join8(wsStoragePath, name);
4552
+ const wsDir = join9(wsStoragePath, name);
4262
4553
  try {
4263
4554
  if (!statSync5(wsDir).isDirectory()) continue;
4264
4555
  } catch {
4265
4556
  continue;
4266
4557
  }
4267
- const wsJsonPath = join8(wsDir, "workspace.json");
4558
+ const wsJsonPath = join9(wsDir, "workspace.json");
4268
4559
  if (!existsSync8(wsJsonPath)) continue;
4269
4560
  let workspacePath;
4270
4561
  try {
@@ -4275,7 +4566,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4275
4566
  } catch {
4276
4567
  continue;
4277
4568
  }
4278
- const wsDbPath = join8(wsDir, "state.vscdb");
4569
+ const wsDbPath = join9(wsDir, "state.vscdb");
4279
4570
  if (!existsSync8(wsDbPath)) continue;
4280
4571
  const wsDb = openDbReadOnly(wsDbPath);
4281
4572
  if (!wsDb) continue;
@@ -4490,6 +4781,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4490
4781
  const cachedDir = this.composerCache.get(`__dir__${composerId}`);
4491
4782
  const directory = cachedDir?.directory ?? this.buildWorkspacePathMap().get(composerId) ?? "";
4492
4783
  return {
4784
+ reference: { agentName: this.name, sessionId: resolvedSessionId },
4493
4785
  id: resolvedSessionId,
4494
4786
  title,
4495
4787
  slug: `cursor/${resolvedSessionId}`,
@@ -4675,15 +4967,15 @@ var CursorAgent = class extends DatabaseSessionSource {
4675
4967
  }
4676
4968
  }
4677
4969
  if (toolName === "create_plan") {
4678
- const planText = asRecord(state.input)?.plan;
4679
- return {
4680
- type: "plan",
4681
- title: "Plan",
4682
- input: planText,
4683
- approval_status: state.status === "completed" ? "success" : "fail",
4684
- state,
4685
- time_created: timestampMs
4686
- };
4970
+ const planText2 = String(asRecord(state.input)?.plan ?? "").trim();
4971
+ if (planText2) {
4972
+ return {
4973
+ type: "plan",
4974
+ text: planText2,
4975
+ approval_status: state.status === "completed" ? "success" : "fail",
4976
+ time_created: timestampMs
4977
+ };
4978
+ }
4687
4979
  }
4688
4980
  return {
4689
4981
  type: "tool",
@@ -4749,6 +5041,9 @@ var CursorAgent = class extends DatabaseSessionSource {
4749
5041
  };
4750
5042
  var HEAD_INDEX_VERSION3 = "pi-head-v1";
4751
5043
  var PARSER_VERSION2 = "pi-parser-v1";
5044
+ function resolvePiDataRoot() {
5045
+ return resolveHomePath("PI_HOME", ".pi");
5046
+ }
4752
5047
  function parseTimestampMs3(value) {
4753
5048
  if (typeof value === "number") return Number.isFinite(value) ? value : 0;
4754
5049
  const text = String(value ?? "").trim();
@@ -4819,13 +5114,22 @@ function buildCurrentPathEntries(entries) {
4819
5114
  }
4820
5115
  return path2.reverse();
4821
5116
  }
4822
- var PiAgent = class extends FileSystemSessionSource {
5117
+ var PiAgent = class extends SingleFileSessionSource {
4823
5118
  name = "pi";
4824
5119
  displayName = "Pi";
4825
5120
  basePath = null;
4826
5121
  findBasePath() {
4827
- const roots = resolveProviderRoots();
4828
- return firstExisting(join9(roots.piRoot, "agent", "sessions"), "data/pi");
5122
+ return firstExisting(join10(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
5123
+ }
5124
+ getSessionWatchPlan() {
5125
+ const dataRoot = resolvePiDataRoot();
5126
+ return {
5127
+ status: "supported",
5128
+ targets: [
5129
+ { root: dataRoot, path: join10(dataRoot, "agent", "sessions") },
5130
+ { root: "data/pi", path: "data/pi" }
5131
+ ]
5132
+ };
4829
5133
  }
4830
5134
  isAvailable() {
4831
5135
  this.basePath = this.findBasePath();
@@ -4834,19 +5138,12 @@ var PiAgent = class extends FileSystemSessionSource {
4834
5138
  }
4835
5139
  listSessionSources(options) {
4836
5140
  if (!this.basePath) return [];
4837
- return this.walkJsonlFiles(this.basePath, options).map(({ file, stat }) => ({
5141
+ return this.listSessionFiles(options).map(({ file, stat }) => ({
4838
5142
  sessionId: extractSessionIdFromFilename(file),
4839
5143
  sourcePath: file,
4840
5144
  fingerprint: this.sourceFingerprint(stat)
4841
5145
  }));
4842
5146
  }
4843
- scanSessionSource(sourcePath) {
4844
- const head = getParsedSession(this.parseSessionHeadResult(sourcePath));
4845
- if (head) {
4846
- this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, sourcePath));
4847
- }
4848
- return head;
4849
- }
4850
5147
  getSessionData(sessionId) {
4851
5148
  const meta = this.sessionMetaMap.get(sessionId);
4852
5149
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
@@ -4854,6 +5151,7 @@ var PiAgent = class extends FileSystemSessionSource {
4854
5151
  const parsed = this.parsePiFile(meta.sourcePath);
4855
5152
  const state = this.convertEntries(parsed.pathEntries);
4856
5153
  return {
5154
+ reference: { agentName: this.name, sessionId: meta.id },
4857
5155
  id: meta.id,
4858
5156
  title: meta.title,
4859
5157
  slug: `pi/${meta.id}`,
@@ -4874,52 +5172,30 @@ var PiAgent = class extends FileSystemSessionSource {
4874
5172
  }
4875
5173
  listSessionFiles(options) {
4876
5174
  if (!this.basePath) return [];
4877
- return this.walkJsonlFiles(this.basePath, options).map(({ file }) => file);
5175
+ return this.walkFiles(
5176
+ this.basePath,
5177
+ (entry) => entry.isFile() && entry.name.endsWith(".jsonl"),
5178
+ { scanWindow: options }
5179
+ );
4878
5180
  }
4879
- /** Stats each file once during the walk; caller reuses it for both the scan window check and the fingerprint. */
4880
- walkJsonlFiles(dir, options) {
4881
- const files = [];
4882
- try {
4883
- for (const entry of readdirSync5(dir, { withFileTypes: true })) {
4884
- const fullPath = join9(dir, entry.name);
4885
- if (entry.isDirectory()) {
4886
- files.push(...this.walkJsonlFiles(fullPath, options));
4887
- continue;
4888
- }
4889
- if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
4890
- let stat;
4891
- try {
4892
- stat = statSync6(fullPath);
4893
- } catch {
4894
- continue;
4895
- }
4896
- if (!matchesScanWindow(stat.mtimeMs, options)) continue;
4897
- files.push({ file: fullPath, stat });
5181
+ createFileSessionMeta(head, source) {
5182
+ return this.buildFileSessionMeta({
5183
+ head,
5184
+ source,
5185
+ fingerprint: this.sourceFingerprint(source.stat),
5186
+ extras: {
5187
+ headIndexVersion: HEAD_INDEX_VERSION3,
5188
+ parserVersion: PARSER_VERSION2
4898
5189
  }
4899
- } catch {
4900
- }
4901
- return files;
4902
- }
4903
- buildSessionMeta(head, file) {
4904
- const stat = statSync6(file);
4905
- return {
4906
- id: head.id,
4907
- title: head.title,
4908
- sourcePath: file,
4909
- sourceFingerprint: this.sourceFingerprint(stat),
4910
- sourceMtimeMs: stat.mtimeMs,
4911
- headIndexVersion: HEAD_INDEX_VERSION3,
4912
- parserVersion: PARSER_VERSION2,
4913
- directory: head.directory,
4914
- messageCount: head.stats.message_count,
4915
- createdAt: head.time_created,
4916
- updatedAt: head.time_updated ?? head.time_created
4917
- };
5190
+ });
4918
5191
  }
4919
5192
  /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
4920
5193
  sourceFingerprint(stat) {
4921
5194
  return JSON.stringify([HEAD_INDEX_VERSION3, PARSER_VERSION2, stat.mtimeMs, stat.size]);
4922
5195
  }
5196
+ parseFileSessionHead(filePath) {
5197
+ return getParsedSession(this.parseSessionHeadResult(filePath));
5198
+ }
4923
5199
  parseSessionHeadResult(filePath) {
4924
5200
  const parsed = this.parsePiFile(filePath);
4925
5201
  const state = this.convertEntries(parsed.pathEntries);
@@ -4946,16 +5222,24 @@ var PiAgent = class extends FileSystemSessionSource {
4946
5222
  });
4947
5223
  }
4948
5224
  parsePiFile(filePath) {
4949
- const records = Array.from(parseJsonlLines(readFileSync6(filePath, "utf-8")));
4950
- if (records.length === 0) throw new Error("empty file");
4951
- const header = records.find((record) => record["type"] === "session");
5225
+ let header = null;
5226
+ let recordCount = 0;
5227
+ const entries = [];
5228
+ for (const record of readJsonlFile(filePath)) {
5229
+ recordCount += 1;
5230
+ if (record["type"] === "session") {
5231
+ header ??= record;
5232
+ continue;
5233
+ }
5234
+ entries.push(record);
5235
+ }
5236
+ if (recordCount === 0) throw new Error("empty file");
4952
5237
  if (!header) throw new Error("missing session header");
4953
- const entries = records.filter((record) => record["type"] !== "session");
4954
5238
  const pathEntries = buildCurrentPathEntries(entries);
4955
5239
  if (pathEntries.length === 0) throw new Error("empty session tree");
4956
5240
  const sessionId = String(header["id"] ?? extractSessionIdFromFilename(filePath)).trim();
4957
5241
  if (!sessionId) throw new Error("missing session id");
4958
- const stat = statSync6(filePath);
5242
+ const stat = this.sessionSourceFile(filePath).stat;
4959
5243
  const directory = String(header["cwd"] ?? "").trim() || basename6(filePath, ".jsonl");
4960
5244
  const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
4961
5245
  const updatedAt = pathEntries.reduce(
@@ -5203,48 +5487,82 @@ var PiAgent = class extends FileSystemSessionSource {
5203
5487
  };
5204
5488
  }
5205
5489
  };
5490
+ function resolveZCodeDataRoot() {
5491
+ const currentPlatform = platform3();
5492
+ if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
5493
+ return join11(homedir4(), ".zcode");
5494
+ }
5206
5495
  function findZCodeDbPath() {
5207
5496
  if (!isSqliteAvailable()) return null;
5208
- const roots = resolveProviderRoots();
5209
- if (!roots.zcodeRoot) return null;
5210
- return firstExisting(join10(roots.zcodeRoot, "cli", "db", "db.sqlite"));
5497
+ const dataRoot = resolveZCodeDataRoot();
5498
+ if (!dataRoot) return null;
5499
+ return firstExisting(join11(dataRoot, "cli", "db", "db.sqlite"));
5500
+ }
5501
+ function getZCodeSessionWatchPlan() {
5502
+ const dataRoot = resolveZCodeDataRoot();
5503
+ return {
5504
+ status: "supported",
5505
+ targets: dataRoot ? [{ root: dataRoot, path: join11(dataRoot, "cli", "db", "db.sqlite") }] : []
5506
+ };
5211
5507
  }
5212
5508
  var ZCodeAgent = class extends OpenCodeSqliteAgent {
5213
5509
  constructor() {
5214
5510
  super({
5215
5511
  name: "zcode",
5216
5512
  displayName: "ZCode",
5217
- findDbPath: findZCodeDbPath
5513
+ findDbPath: findZCodeDbPath,
5514
+ getSessionWatchPlan: getZCodeSessionWatchPlan
5218
5515
  });
5219
5516
  }
5220
5517
  };
5221
5518
  registerAgent({
5222
5519
  icon: "/icon/agent/claudecode.svg",
5223
5520
  iconColored: true,
5521
+ resolveDataRoot: resolveClaudeCodeDataRoot,
5522
+ resumeCommandPrefix: "claude --resume",
5523
+ toolStrategy: "custom",
5224
5524
  create: () => new ClaudeCodeAgent()
5225
5525
  });
5226
5526
  registerAgent({
5227
5527
  icon: "/icon/agent/opencode.svg",
5528
+ resolveDataRoot: resolveOpenCodeDataRoot,
5529
+ resumeCommandPrefix: "opencode -s",
5530
+ toolStrategy: "custom",
5228
5531
  create: () => new OpenCodeAgent()
5229
5532
  });
5230
5533
  registerAgent({
5231
5534
  icon: "/icon/agent/zcode.svg",
5535
+ resolveDataRoot: resolveZCodeDataRoot,
5536
+ resumeCommandPrefix: null,
5537
+ toolStrategy: "custom",
5232
5538
  create: () => new ZCodeAgent()
5233
5539
  });
5234
5540
  registerAgent({
5235
5541
  icon: "/icon/agent/kimi.svg",
5542
+ resolveDataRoot: resolveKimiDataRoot,
5543
+ resumeCommandPrefix: "kimi -r",
5544
+ toolStrategy: "custom",
5236
5545
  create: () => new KimiAgent()
5237
5546
  });
5238
5547
  registerAgent({
5239
5548
  icon: "/icon/agent/codex.svg",
5549
+ resolveDataRoot: resolveCodexDataRoot,
5550
+ resumeCommandPrefix: "codex resume",
5551
+ toolStrategy: "custom",
5240
5552
  create: () => new CodexAgent()
5241
5553
  });
5242
5554
  registerAgent({
5243
5555
  icon: "/icon/agent/pi.svg",
5556
+ resolveDataRoot: resolvePiDataRoot,
5557
+ resumeCommandPrefix: "pi --session",
5558
+ toolStrategy: "custom",
5244
5559
  create: () => new PiAgent()
5245
5560
  });
5246
5561
  registerAgent({
5247
5562
  icon: "/icon/agent/cursor.svg",
5563
+ resolveDataRoot: resolveCursorDataRoot,
5564
+ resumeCommandPrefix: null,
5565
+ toolStrategy: "custom",
5248
5566
  create: () => new CursorAgent()
5249
5567
  });
5250
5568
  function fallbackDisplayName(input) {
@@ -5259,7 +5577,7 @@ var realFs = {
5259
5577
  },
5260
5578
  readText(path2) {
5261
5579
  try {
5262
- return readFileSync7(path2, "utf8");
5580
+ return readFileSync6(path2, "utf8");
5263
5581
  } catch {
5264
5582
  return null;
5265
5583
  }
@@ -5312,9 +5630,6 @@ function normalizeGitRemote(url) {
5312
5630
  }
5313
5631
  var IDENTITY_CACHE_TTL_MS = 10 * 60 * 1e3;
5314
5632
  var identityCache = /* @__PURE__ */ new Map();
5315
- function clearIdentityCache() {
5316
- identityCache.clear();
5317
- }
5318
5633
  function computeIdentity(cwd, fs) {
5319
5634
  if (fs !== realFs) return resolveIdentity(cwd, fs);
5320
5635
  const key = cwd ?? "";
@@ -5447,9 +5762,6 @@ function parseManifestName(file, text) {
5447
5762
  }
5448
5763
  return null;
5449
5764
  }
5450
- function getAgentName(session) {
5451
- return session.slug.split("/")[0]?.toLowerCase() || "unknown";
5452
- }
5453
5765
  function buildProjectGroups(sessions) {
5454
5766
  const groups = /* @__PURE__ */ new Map();
5455
5767
  for (const session of sessions) {
@@ -5459,13 +5771,13 @@ function buildProjectGroups(sessions) {
5459
5771
  const groupKey = getProjectIdentityKey(identity);
5460
5772
  const current = groups.get(groupKey);
5461
5773
  if (current) {
5462
- current.sources.add(getAgentName(session));
5774
+ current.sources.add(getSessionAgentKey(session));
5463
5775
  current.sessionCount += 1;
5464
5776
  current.lastActivity = Math.max(current.lastActivity, activity);
5465
5777
  } else {
5466
5778
  groups.set(groupKey, {
5467
5779
  identity,
5468
- sources: /* @__PURE__ */ new Set([getAgentName(session)]),
5780
+ sources: /* @__PURE__ */ new Set([getSessionAgentKey(session)]),
5469
5781
  sessionCount: 1,
5470
5782
  lastActivity: activity
5471
5783
  });
@@ -5548,7 +5860,7 @@ function classifySessionTags(session) {
5548
5860
  for (const part of message.parts) {
5549
5861
  if (part.type === "plan") tags.add("planning");
5550
5862
  if (part.type !== "tool") continue;
5551
- const toolName = `${part.tool ?? ""} ${part.title ?? ""}`;
5863
+ const toolName = `${part.tool} ${part.title ?? ""}`;
5552
5864
  const toolPayload = stringifyToolPayload(part);
5553
5865
  if (PLAN_TOOL_RE.test(toolName)) tags.add("planning");
5554
5866
  if (READ_TOOL_RE.test(toolName)) readToolCount += 1;
@@ -5556,7 +5868,7 @@ function classifySessionTags(session) {
5556
5868
  if (TESTING_COMMAND_RE.test(toolPayload)) tags.add("testing");
5557
5869
  if (GIT_COMMAND_RE.test(toolPayload)) tags.add("git-ops");
5558
5870
  if (BUILD_DEPLOY_COMMAND_RE.test(toolPayload)) tags.add("build-deploy");
5559
- if (hasEditedDocPath(part.state?.arguments) || hasEditedDocPath(part.state?.input)) {
5871
+ if (hasEditedDocPath(part.state.input)) {
5560
5872
  tags.add("docs");
5561
5873
  }
5562
5874
  }
@@ -5567,16 +5879,10 @@ function classifySessionTags(session) {
5567
5879
  return TAG_ORDER.filter((tag) => tags.has(tag));
5568
5880
  }
5569
5881
  function partText(part) {
5570
- return typeof part.text === "string" ? part.text : "";
5882
+ return part.type === "text" || part.type === "reasoning" || part.type === "plan" ? part.text : "";
5571
5883
  }
5572
5884
  function stringifyToolPayload(part) {
5573
- return [
5574
- part.tool,
5575
- part.title,
5576
- valueToText(part.input),
5577
- valueToText(part.output),
5578
- valueToText(part.state)
5579
- ].filter(Boolean).join("\n");
5885
+ return [part.tool, part.title, valueToText(part.state)].filter(Boolean).join("\n");
5580
5886
  }
5581
5887
  function valueToText(value) {
5582
5888
  if (value == null) return "";
@@ -5600,7 +5906,7 @@ function hasEditedDocPath(value) {
5600
5906
  }
5601
5907
  return Object.values(record).some(hasEditedDocPath);
5602
5908
  }
5603
- function toRecord(value) {
5909
+ function toRecord2(value) {
5604
5910
  if (value && typeof value === "object" && !Array.isArray(value)) {
5605
5911
  return value;
5606
5912
  }
@@ -5610,10 +5916,10 @@ function toStringValue(value) {
5610
5916
  return typeof value === "string" ? value : "";
5611
5917
  }
5612
5918
  function normalizeToolLabel(part) {
5613
- if (typeof part.title === "string" && part.title.trim()) {
5919
+ if (part.title?.trim()) {
5614
5920
  return part.title.trim().replace(/^tool:\s*/i, "");
5615
5921
  }
5616
- if (typeof part.tool === "string" && part.tool.trim()) return part.tool.trim();
5922
+ if (part.tool.trim()) return part.tool.trim();
5617
5923
  return "tool";
5618
5924
  }
5619
5925
  function normalizeToolName(part) {
@@ -5664,7 +5970,7 @@ function extractPathsFromToolInput(inputValue) {
5664
5970
  return [...paths];
5665
5971
  }
5666
5972
  function getToolInputValue(part) {
5667
- return part.state?.arguments ?? part.state?.input ?? part.input ?? null;
5973
+ return part.state.input ?? null;
5668
5974
  }
5669
5975
  function classifyToolKind(part) {
5670
5976
  const toolName = normalizeToolName(part);
@@ -5681,7 +5987,7 @@ function classifyToolKind(part) {
5681
5987
  return null;
5682
5988
  }
5683
5989
  function normalizeCodexPatchEntry(entry) {
5684
- const record = toRecord(entry);
5990
+ const record = toRecord2(entry);
5685
5991
  const type = toStringValue(record.type);
5686
5992
  if (!type) return null;
5687
5993
  return {
@@ -5691,7 +5997,7 @@ function normalizeCodexPatchEntry(entry) {
5691
5997
  };
5692
5998
  }
5693
5999
  function getCodexPatchEntries(inputValue) {
5694
- const input = toRecord(inputValue);
6000
+ const input = toRecord2(inputValue);
5695
6001
  const rawContent = Array.isArray(inputValue) ? inputValue : Array.isArray(input.content) ? input.content : [];
5696
6002
  return rawContent.map((entry) => normalizeCodexPatchEntry(entry)).filter((entry) => entry != null);
5697
6003
  }
@@ -5750,21 +6056,20 @@ function summarizeFileActivity(agentName, sessionId, projectIdentityKey, occurre
5750
6056
  const current = grouped.get(key);
5751
6057
  if (current) {
5752
6058
  current.count += 1;
5753
- current.latest_time = Math.max(current.latest_time, occurrence.time);
6059
+ current.latestTime = Math.max(current.latestTime, occurrence.time);
5754
6060
  continue;
5755
6061
  }
5756
6062
  grouped.set(key, {
5757
- agent_name: agentName,
5758
- session_id: sessionId,
5759
- project_identity_key: projectIdentityKey,
6063
+ reference: { agentName, sessionId },
6064
+ projectIdentityKey,
5760
6065
  path: occurrence.path,
5761
6066
  kind: occurrence.kind,
5762
6067
  count: 1,
5763
- latest_time: occurrence.time
6068
+ latestTime: occurrence.time
5764
6069
  });
5765
6070
  }
5766
6071
  return [...grouped.values()].sort((a, b) => {
5767
- if (b.latest_time !== a.latest_time) return b.latest_time - a.latest_time;
6072
+ if (b.latestTime !== a.latestTime) return b.latestTime - a.latestTime;
5768
6073
  return a.path.localeCompare(b.path);
5769
6074
  });
5770
6075
  }
@@ -5779,13 +6084,6 @@ function extractSessionFileActivity(agentName, sessionId, projectIdentityKey, me
5779
6084
  var CACHE_FILENAME = "codesesh.db";
5780
6085
  var LEGACY_CACHE_FILENAME = "scan-cache.json";
5781
6086
  var SEARCH_INDEX_BULK_SYNC_THRESHOLD = 100;
5782
- var ftsIntegrityCheckedPath = null;
5783
- function getFtsIntegrityCheckedPath() {
5784
- return ftsIntegrityCheckedPath;
5785
- }
5786
- function setFtsIntegrityCheckedPath(path2) {
5787
- ftsIntegrityCheckedPath = path2;
5788
- }
5789
6087
  var schemaEnsuredPath = null;
5790
6088
  function getSchemaEnsuredPath() {
5791
6089
  return schemaEnsuredPath;
@@ -5794,13 +6092,13 @@ function setSchemaEnsuredPath(path2) {
5794
6092
  schemaEnsuredPath = path2;
5795
6093
  }
5796
6094
  function getCacheDir2() {
5797
- return join11(homedir4(), ".cache", "codesesh");
6095
+ return join12(homedir6(), ".cache", "codesesh");
5798
6096
  }
5799
6097
  function getCachePath2() {
5800
- return join11(getCacheDir2(), CACHE_FILENAME);
6098
+ return join12(getCacheDir2(), CACHE_FILENAME);
5801
6099
  }
5802
6100
  function getLegacyCachePath() {
5803
- return join11(getCacheDir2(), LEGACY_CACHE_FILENAME);
6101
+ return join12(getCacheDir2(), LEGACY_CACHE_FILENAME);
5804
6102
  }
5805
6103
  function hasCacheStorage() {
5806
6104
  return existsSync11(getCachePath2());
@@ -5819,6 +6117,7 @@ function escapeRegExp(value) {
5819
6117
  function normalizeFilePathSearch(value) {
5820
6118
  return value.trim().replace(/^"|"$/g, "");
5821
6119
  }
6120
+ var MESSAGE_PARTS_FORMAT_VERSION = 1;
5822
6121
  function stringifyOptionalJson(value) {
5823
6122
  return value == null ? null : JSON.stringify(value);
5824
6123
  }
@@ -5833,34 +6132,6 @@ function sourcePathFromMetaJson(metaJson) {
5833
6132
  const meta = JSON.parse(metaJson);
5834
6133
  return sourcePathFromMeta(meta);
5835
6134
  }
5836
- function prepareUpsertCachedSession(db) {
5837
- return db.prepare(`
5838
- INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
5839
- VALUES (?, ?, ?, ?)
5840
- ON CONFLICT(agent_name, session_id) DO UPDATE SET
5841
- session_json = excluded.session_json,
5842
- meta_json = excluded.meta_json
5843
- `);
5844
- }
5845
- function prepareUpsertProjectSession(db) {
5846
- return db.prepare(`
5847
- INSERT INTO project_sessions(
5848
- agent_name,
5849
- session_id,
5850
- identity_kind,
5851
- identity_key,
5852
- display_name,
5853
- directory,
5854
- activity_time
5855
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
5856
- ON CONFLICT(agent_name, session_id) DO UPDATE SET
5857
- identity_kind = excluded.identity_kind,
5858
- identity_key = excluded.identity_key,
5859
- display_name = excluded.display_name,
5860
- directory = excluded.directory,
5861
- activity_time = excluded.activity_time
5862
- `);
5863
- }
5864
6135
  function prepareUpsertSession(db) {
5865
6136
  return db.prepare(`
5866
6137
  INSERT INTO sessions(
@@ -6025,27 +6296,16 @@ function prepareInsertMessageTool(db) {
6025
6296
  function writeFileActivityRows(statement, activities) {
6026
6297
  for (const activity of activities) {
6027
6298
  statement.run(
6028
- activity.agent_name,
6029
- activity.session_id,
6030
- activity.project_identity_key,
6299
+ activity.reference.agentName,
6300
+ activity.reference.sessionId,
6301
+ activity.projectIdentityKey,
6031
6302
  activity.path,
6032
6303
  activity.kind,
6033
6304
  activity.count,
6034
- activity.latest_time
6305
+ activity.latestTime
6035
6306
  );
6036
6307
  }
6037
6308
  }
6038
- function writeProjectSessionRow(statement, agentName, session, identity) {
6039
- statement.run(
6040
- agentName,
6041
- session.id,
6042
- identity.kind,
6043
- identity.key,
6044
- identity.displayName,
6045
- session.directory,
6046
- session.time_updated ?? session.time_created
6047
- );
6048
- }
6049
6309
  function sessionFromRow(row) {
6050
6310
  const session = {
6051
6311
  id: String(row.session_id),
@@ -6095,7 +6355,7 @@ function sessionFromRow(row) {
6095
6355
  }
6096
6356
  return session;
6097
6357
  }
6098
- function messageFromBackfillRow(row) {
6358
+ function messageMetadataFromBackfillRow(row) {
6099
6359
  const role = row.role === "assistant" || row.role === "tool" ? row.role : "user";
6100
6360
  return {
6101
6361
  id: String(row.message_id ?? ""),
@@ -6106,13 +6366,12 @@ function messageFromBackfillRow(row) {
6106
6366
  mode: row.mode ?? null,
6107
6367
  model: row.model ?? null,
6108
6368
  provider: row.provider ?? null,
6109
- parts: JSON.parse(String(row.parts_json ?? "[]")),
6110
6369
  subagent_id: row.subagent_id ?? void 0,
6111
6370
  nickname: row.nickname ?? void 0
6112
6371
  };
6113
6372
  }
6114
- function messageFromCachedRow(row) {
6115
- const message = messageFromBackfillRow(row);
6373
+ function messageMetadataFromCachedRow(row) {
6374
+ const message = messageMetadataFromBackfillRow(row);
6116
6375
  const tokens = parseOptionalJson(row.tokens_json);
6117
6376
  if (tokens) {
6118
6377
  message.tokens = tokens;
@@ -6125,6 +6384,44 @@ function messageFromCachedRow(row) {
6125
6384
  }
6126
6385
  return message;
6127
6386
  }
6387
+ function messageFromBackfillRow(row) {
6388
+ return {
6389
+ ...messageMetadataFromBackfillRow(row),
6390
+ parts: messagePartsFromJson(row.parts_json)
6391
+ };
6392
+ }
6393
+ function messageFromCachedRow(row) {
6394
+ return {
6395
+ ...messageMetadataFromCachedRow(row),
6396
+ parts: messagePartsFromJson(row.parts_json)
6397
+ };
6398
+ }
6399
+ function messagePartsFromJson(value) {
6400
+ try {
6401
+ return normalizeMessageParts(JSON.parse(String(value ?? "[]")));
6402
+ } catch {
6403
+ return [];
6404
+ }
6405
+ }
6406
+ function normalizeMessagePartsJson(value) {
6407
+ return JSON.stringify(messagePartsFromJson(value));
6408
+ }
6409
+ function messageJsonFromCachedRow(row) {
6410
+ const metadataJson = JSON.stringify(messageMetadataFromBackfillRow(row));
6411
+ const fields = [];
6412
+ if (row.tokens_json != null) {
6413
+ fields.push(`"tokens":${String(row.tokens_json)}`);
6414
+ }
6415
+ if (row.cost != null) {
6416
+ fields.push(`"cost":${JSON.stringify(Number(row.cost))}`);
6417
+ }
6418
+ if (row.cost_source) {
6419
+ fields.push(`"cost_source":${JSON.stringify(row.cost_source)}`);
6420
+ }
6421
+ const partsJson = Number(row.parts_format_version) >= MESSAGE_PARTS_FORMAT_VERSION ? String(row.parts_json ?? "[]") : normalizeMessagePartsJson(row.parts_json);
6422
+ fields.push(`"parts":${partsJson}`);
6423
+ return `${metadataJson.slice(0, -1)},${fields.join(",")}}`;
6424
+ }
6128
6425
  function appendPlainText(value, chunks) {
6129
6426
  if (value == null) return;
6130
6427
  if (typeof value === "string") {
@@ -6184,7 +6481,7 @@ function toolNamesFromMessage(message) {
6184
6481
  return [...tools];
6185
6482
  }
6186
6483
  function summarizeToolPart(part) {
6187
- const state = part.state == null ? void 0 : compactRecord({
6484
+ const state = compactRecord({
6188
6485
  status: part.state.status,
6189
6486
  error: part.state.error,
6190
6487
  metadata: part.state.metadata
@@ -6193,9 +6490,7 @@ function summarizeToolPart(part) {
6193
6490
  type: part.type,
6194
6491
  tool: part.tool,
6195
6492
  title: part.title,
6196
- nickname: part.nickname,
6197
6493
  callID: part.callID,
6198
- approval_status: part.approval_status,
6199
6494
  state
6200
6495
  });
6201
6496
  }
@@ -6206,13 +6501,13 @@ function buildMessageText(message) {
6206
6501
  appendPlainText(message.model, chunks);
6207
6502
  for (const part of message.parts) {
6208
6503
  appendPlainText(part.type, chunks);
6209
- appendPlainText(part.title, chunks);
6210
- appendPlainText(part.nickname, chunks);
6211
- appendPlainText(part.tool, chunks);
6212
- appendPlainText(part.text, chunks);
6213
- appendPlainText(part.input, chunks);
6214
- appendPlainText(part.output, chunks);
6215
- appendPlainText(part.state, chunks);
6504
+ if (part.type === "text" || part.type === "reasoning" || part.type === "plan") {
6505
+ appendPlainText(part.text, chunks);
6506
+ } else if (part.type === "tool") {
6507
+ appendPlainText(part.title, chunks);
6508
+ appendPlainText(part.tool, chunks);
6509
+ appendPlainText(part.state, chunks);
6510
+ }
6216
6511
  }
6217
6512
  return chunks.join("\n");
6218
6513
  }
@@ -6249,7 +6544,7 @@ function buildSessionContentFromMessages(title, messages) {
6249
6544
  }
6250
6545
  return chunks.join("\n");
6251
6546
  }
6252
- var CACHE_SCHEMA_VERSION = 14;
6547
+ var CACHE_SCHEMA_VERSION = 17;
6253
6548
  function withCacheDb(fn) {
6254
6549
  const cachePath = getCachePath2();
6255
6550
  const db = openDb(cachePath);
@@ -6280,6 +6575,31 @@ function withCacheDbReadOnly(fn) {
6280
6575
  db.close();
6281
6576
  }
6282
6577
  }
6578
+ function withSearchDb(fn) {
6579
+ return withCacheDb((db) => runWithFtsRecovery(db, fn));
6580
+ }
6581
+ function withSearchIndexDb(fn) {
6582
+ return withCacheDb((db) => runWithFtsRecovery(db, fn));
6583
+ }
6584
+ function runSearchIndexWrite(db, rebuild, write) {
6585
+ return db.transaction(() => {
6586
+ if (rebuild) {
6587
+ dropSearchTriggers(db);
6588
+ dropMessageSearchTriggers(db);
6589
+ }
6590
+ const value = write();
6591
+ let rebuildDurationMs;
6592
+ if (rebuild) {
6593
+ const rebuildStartedAt = performance.now();
6594
+ rebuildSearchIndex(db);
6595
+ rebuildMessageSearchIndex(db);
6596
+ rebuildDurationMs = performance.now() - rebuildStartedAt;
6597
+ createSearchTriggers(db);
6598
+ createMessageSearchTriggers(db);
6599
+ }
6600
+ return { value, rebuildDurationMs };
6601
+ })();
6602
+ }
6283
6603
  function createCacheTables(db) {
6284
6604
  db.exec(`
6285
6605
  CREATE TABLE IF NOT EXISTS cache_meta (
@@ -6367,6 +6687,7 @@ function createSessionTables(db) {
6367
6687
  cost REAL,
6368
6688
  cost_source TEXT,
6369
6689
  parts_json TEXT NOT NULL,
6690
+ parts_format_version INTEGER NOT NULL DEFAULT 0,
6370
6691
  subagent_id TEXT,
6371
6692
  nickname TEXT,
6372
6693
  content_text TEXT NOT NULL,
@@ -6523,12 +6844,7 @@ function createSearchTables(db) {
6523
6844
  id INTEGER PRIMARY KEY AUTOINCREMENT,
6524
6845
  agent_name TEXT NOT NULL,
6525
6846
  session_id TEXT NOT NULL,
6526
- slug TEXT NOT NULL,
6527
6847
  title TEXT NOT NULL,
6528
- directory TEXT NOT NULL,
6529
- time_created INTEGER NOT NULL,
6530
- time_updated INTEGER,
6531
- activity_time INTEGER NOT NULL,
6532
6848
  content_text TEXT NOT NULL,
6533
6849
  content_hash TEXT NOT NULL,
6534
6850
  indexed_message_count INTEGER NOT NULL,
@@ -6590,28 +6906,27 @@ function dropSearchTriggers(db) {
6590
6906
  DROP TRIGGER IF EXISTS session_documents_au;
6591
6907
  `);
6592
6908
  }
6593
- function ensureProjectColumns(db) {
6909
+ var LEGACY_SESSION_DOCUMENT_COLUMNS = [
6910
+ ["slug", "TEXT NOT NULL DEFAULT ''"],
6911
+ ["directory", "TEXT NOT NULL DEFAULT ''"],
6912
+ ["time_created", "INTEGER NOT NULL DEFAULT 0"],
6913
+ ["time_updated", "INTEGER"],
6914
+ ["activity_time", "INTEGER NOT NULL DEFAULT 0"],
6915
+ ["project_identity_kind", "TEXT NOT NULL DEFAULT 'path'"],
6916
+ ["project_identity_key", "TEXT NOT NULL DEFAULT ''"],
6917
+ ["project_display_name", "TEXT NOT NULL DEFAULT ''"]
6918
+ ];
6919
+ function ensureLegacySessionDocumentColumns(db) {
6594
6920
  if (!tableExists(db, "session_documents")) {
6595
6921
  return;
6596
6922
  }
6597
- if (!columnExists(db, "session_documents", "project_identity_kind")) {
6598
- db.exec(
6599
- "ALTER TABLE session_documents ADD COLUMN project_identity_kind TEXT NOT NULL DEFAULT 'path'"
6600
- );
6601
- }
6602
- if (!columnExists(db, "session_documents", "project_identity_key")) {
6603
- db.exec(
6604
- "ALTER TABLE session_documents ADD COLUMN project_identity_key TEXT NOT NULL DEFAULT ''"
6605
- );
6606
- }
6607
- if (!columnExists(db, "session_documents", "project_display_name")) {
6608
- db.exec(
6609
- "ALTER TABLE session_documents ADD COLUMN project_display_name TEXT NOT NULL DEFAULT ''"
6610
- );
6923
+ for (const [name, definition] of LEGACY_SESSION_DOCUMENT_COLUMNS) {
6924
+ if (!columnExists(db, "session_documents", name)) {
6925
+ db.exec(`ALTER TABLE session_documents ADD COLUMN ${name} ${definition}`);
6926
+ }
6611
6927
  }
6612
6928
  }
6613
6929
  function createProjectTables(db) {
6614
- ensureProjectColumns(db);
6615
6930
  db.exec(`
6616
6931
  CREATE TABLE IF NOT EXISTS project_sessions (
6617
6932
  agent_name TEXT NOT NULL,
@@ -6665,10 +6980,9 @@ function recreateProjectGroupsView(db) {
6665
6980
  function createLatestCacheSchema(db) {
6666
6981
  createCacheTables(db);
6667
6982
  createSessionTables(db);
6668
- createMessageSearchTables(db);
6669
6983
  createFileActivityTables(db);
6670
- createSearchTables(db);
6671
6984
  createProjectTables(db);
6985
+ ensureFtsReady(db);
6672
6986
  }
6673
6987
  function recreateSearchIndexSchema(db) {
6674
6988
  db.exec(`
@@ -6689,7 +7003,7 @@ function readLegacyCacheVersion(db) {
6689
7003
  }
6690
7004
  function inferCacheSchemaVersion(db) {
6691
7005
  if (columnExists(db, "session_documents", "indexed_message_count")) {
6692
- return 14;
7006
+ return columnExists(db, "session_documents", "slug") ? 14 : 15;
6693
7007
  }
6694
7008
  if (tableExists(db, "message_tools")) {
6695
7009
  return 11;
@@ -6802,6 +7116,7 @@ function backfillSessionDocumentProjects(db) {
6802
7116
  }
6803
7117
  }
6804
7118
  function migrateProjectIdentity(db) {
7119
+ ensureLegacySessionDocumentColumns(db);
6805
7120
  createProjectTables(db);
6806
7121
  backfillProjectSessions(db);
6807
7122
  backfillSessionDocumentProjects(db);
@@ -7009,6 +7324,49 @@ function invalidateSearchContentHashes(db) {
7009
7324
  db.exec("UPDATE session_documents SET content_hash = ''");
7010
7325
  }
7011
7326
  }
7327
+ function compactSessionDocuments(db) {
7328
+ if (!tableExists(db, "session_documents")) {
7329
+ createSearchTables(db);
7330
+ return;
7331
+ }
7332
+ dropSearchTriggers(db);
7333
+ db.exec(`
7334
+ DROP TABLE IF EXISTS session_documents_fts;
7335
+ DROP TABLE IF EXISTS session_documents_legacy_v14;
7336
+ ALTER TABLE session_documents RENAME TO session_documents_legacy_v14;
7337
+ `);
7338
+ createSearchTables(db);
7339
+ db.exec(`
7340
+ INSERT INTO session_documents(
7341
+ id,
7342
+ agent_name,
7343
+ session_id,
7344
+ title,
7345
+ content_text,
7346
+ content_hash,
7347
+ indexed_message_count,
7348
+ indexed_at
7349
+ )
7350
+ SELECT
7351
+ id,
7352
+ agent_name,
7353
+ session_id,
7354
+ title,
7355
+ content_text,
7356
+ content_hash,
7357
+ indexed_message_count,
7358
+ indexed_at
7359
+ FROM session_documents_legacy_v14;
7360
+
7361
+ DROP TABLE session_documents_legacy_v14;
7362
+ `);
7363
+ }
7364
+ function addMessagePartsFormatVersion(db) {
7365
+ if (!tableExists(db, "messages") || columnExists(db, "messages", "parts_format_version")) {
7366
+ return;
7367
+ }
7368
+ db.exec("ALTER TABLE messages ADD COLUMN parts_format_version INTEGER NOT NULL DEFAULT 0");
7369
+ }
7012
7370
  var CODEX_EXEC_DECODE_MIGRATION_KEY = "codex_exec_decode_migrated_v3";
7013
7371
  function migrateCodexExecDecode(db) {
7014
7372
  if (!tableExists(db, "cache_meta")) return;
@@ -7035,33 +7393,84 @@ function rebuildMessageSearchIndex(db) {
7035
7393
  }
7036
7394
  db.exec("INSERT INTO messages_fts(messages_fts) VALUES ('rebuild')");
7037
7395
  }
7038
- function ensureFtsReady(db) {
7039
- if (!tableExists(db, "session_documents_fts")) {
7040
- createSearchTables(db);
7396
+ var SEARCH_FTS_INDEXES = ["session_documents_fts", "messages_fts"];
7397
+ function triggerExists(db, triggerName) {
7398
+ return db.prepare("SELECT 1 AS value FROM sqlite_master WHERE name = ? AND type = 'trigger' LIMIT 1").get(triggerName) !== void 0;
7399
+ }
7400
+ function hasAllTriggers(db, triggerNames) {
7401
+ return triggerNames.every((triggerName) => triggerExists(db, triggerName));
7402
+ }
7403
+ function rebuildSearchFtsIndexes(db, indexes, reason) {
7404
+ if (indexes.length === 0) return;
7405
+ const startedAt = performance.now();
7406
+ getCoreDiagnostics()?.info?.("sqlite.fts_rebuild.started", { indexes, reason });
7407
+ try {
7408
+ for (const index of indexes) {
7409
+ if (index === "session_documents_fts") rebuildSearchIndex(db);
7410
+ else rebuildMessageSearchIndex(db);
7411
+ }
7412
+ getCoreDiagnostics()?.info?.("sqlite.fts_rebuild.completed", {
7413
+ indexes,
7414
+ reason,
7415
+ duration_ms: Math.round(performance.now() - startedAt)
7416
+ });
7417
+ } catch (error) {
7418
+ getCoreDiagnostics()?.warn("sqlite.fts_rebuild.failed", {
7419
+ indexes,
7420
+ reason,
7421
+ duration_ms: Math.round(performance.now() - startedAt),
7422
+ message: error instanceof Error ? error.message : String(error)
7423
+ });
7424
+ throw error;
7041
7425
  }
7042
- createSearchTriggers(db);
7043
- const needsMessageSearchRebuild = !tableExists(db, "messages_fts");
7426
+ }
7427
+ function ensureFtsReady(db) {
7428
+ const needsSearchRebuild = !tableExists(db, "session_documents_fts") || !hasAllTriggers(db, ["session_documents_ai", "session_documents_ad", "session_documents_au"]);
7429
+ const needsMessageSearchRebuild = !tableExists(db, "messages_fts") || !hasAllTriggers(db, ["messages_ai", "messages_ad", "messages_au"]);
7430
+ createSearchTables(db);
7044
7431
  createMessageSearchTables(db);
7045
- if (needsMessageSearchRebuild) {
7046
- rebuildMessageSearchIndex(db);
7047
- }
7432
+ const indexes = [];
7433
+ if (needsSearchRebuild) indexes.push("session_documents_fts");
7434
+ if (needsMessageSearchRebuild) indexes.push("messages_fts");
7435
+ rebuildSearchFtsIndexes(db, indexes, "schema_missing");
7048
7436
  }
7049
7437
  function ensureFtsConsistency(db) {
7050
- ensureFtsReady(db);
7051
- const cachePath = getCachePath2();
7052
- if (getFtsIntegrityCheckedPath() === cachePath) {
7053
- return;
7054
- }
7438
+ const startedAt = performance.now();
7439
+ getCoreDiagnostics()?.info?.("sqlite.fts_integrity.started", {
7440
+ indexes: 2
7441
+ });
7055
7442
  try {
7056
7443
  db.exec(
7057
7444
  "INSERT INTO session_documents_fts(session_documents_fts, rank) VALUES ('integrity-check', 1)"
7058
7445
  );
7059
7446
  db.exec("INSERT INTO messages_fts(messages_fts, rank) VALUES ('integrity-check', 1)");
7060
- setFtsIntegrityCheckedPath(cachePath);
7061
- } catch {
7062
- rebuildSearchIndex(db);
7063
- rebuildMessageSearchIndex(db);
7064
- setFtsIntegrityCheckedPath(cachePath);
7447
+ getCoreDiagnostics()?.info?.("sqlite.fts_integrity.completed", {
7448
+ indexes: 2,
7449
+ duration_ms: Math.round(performance.now() - startedAt)
7450
+ });
7451
+ } catch (error) {
7452
+ getCoreDiagnostics()?.warn("sqlite.fts_integrity.failed", {
7453
+ indexes: 2,
7454
+ duration_ms: Math.round(performance.now() - startedAt),
7455
+ message: error instanceof Error ? error.message : String(error)
7456
+ });
7457
+ rebuildSearchFtsIndexes(db, [...SEARCH_FTS_INDEXES], "corruption");
7458
+ }
7459
+ }
7460
+ function isFtsCorruptionError(error) {
7461
+ return typeof error === "object" && error !== null && "code" in error && error.code === "SQLITE_CORRUPT_VTAB";
7462
+ }
7463
+ function runWithFtsRecovery(db, fn) {
7464
+ ensureFtsReady(db);
7465
+ try {
7466
+ return fn(db);
7467
+ } catch (error) {
7468
+ if (!isFtsCorruptionError(error)) throw error;
7469
+ getCoreDiagnostics()?.warn("sqlite.fts_corruption.detected", {
7470
+ message: error instanceof Error ? error.message : String(error)
7471
+ });
7472
+ ensureFtsConsistency(db);
7473
+ return fn(db);
7065
7474
  }
7066
7475
  }
7067
7476
  function setCacheSchemaVersion(db) {
@@ -7141,7 +7550,9 @@ function ensureSchema(db, dbPath) {
7141
7550
  }
7142
7551
  },
7143
7552
  { version: 13, migrate: createCacheTables },
7144
- { version: 14, migrate: addIndexedMessageCount }
7553
+ { version: 14, migrate: addIndexedMessageCount },
7554
+ { version: 15, destructive: true, migrate: compactSessionDocuments },
7555
+ { version: 17, migrate: addMessagePartsFormatVersion }
7145
7556
  ]
7146
7557
  });
7147
7558
  createLatestCacheSchema(db);
@@ -7357,7 +7768,6 @@ function loadSearchIndexEntry(agentName, change, loadSessionData) {
7357
7768
  const identity = change.session.project_identity ?? data.project_identity ?? computeIdentity(change.session.directory, realFs);
7358
7769
  return {
7359
7770
  session: change.session,
7360
- identity,
7361
7771
  messages,
7362
7772
  contentText: buildSessionContentFromMessages(data.title ?? change.session.title, messages),
7363
7773
  contentHash: sessionContentHash(change.session),
@@ -7412,11 +7822,12 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7412
7822
  cost,
7413
7823
  cost_source,
7414
7824
  parts_json,
7825
+ parts_format_version,
7415
7826
  subagent_id,
7416
7827
  nickname,
7417
7828
  content_text,
7418
7829
  tool_metadata_json
7419
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7830
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7420
7831
  ON CONFLICT(agent_name, session_id, message_index) DO UPDATE SET
7421
7832
  message_id = excluded.message_id,
7422
7833
  role = excluded.role,
@@ -7430,6 +7841,7 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7430
7841
  cost = excluded.cost,
7431
7842
  cost_source = excluded.cost_source,
7432
7843
  parts_json = excluded.parts_json,
7844
+ parts_format_version = excluded.parts_format_version,
7433
7845
  subagent_id = excluded.subagent_id,
7434
7846
  nickname = excluded.nickname,
7435
7847
  content_text = excluded.content_text,
@@ -7439,30 +7851,14 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7439
7851
  INSERT INTO session_documents(
7440
7852
  agent_name,
7441
7853
  session_id,
7442
- slug,
7443
7854
  title,
7444
- directory,
7445
- project_identity_kind,
7446
- project_identity_key,
7447
- project_display_name,
7448
- time_created,
7449
- time_updated,
7450
- activity_time,
7451
7855
  content_text,
7452
7856
  content_hash,
7453
7857
  indexed_message_count,
7454
7858
  indexed_at
7455
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7859
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
7456
7860
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
7457
- slug = excluded.slug,
7458
7861
  title = excluded.title,
7459
- directory = excluded.directory,
7460
- project_identity_kind = excluded.project_identity_kind,
7461
- project_identity_key = excluded.project_identity_key,
7462
- project_display_name = excluded.project_display_name,
7463
- time_created = excluded.time_created,
7464
- time_updated = excluded.time_updated,
7465
- activity_time = excluded.activity_time,
7466
7862
  content_text = excluded.content_text,
7467
7863
  content_hash = excluded.content_hash,
7468
7864
  indexed_message_count = excluded.indexed_message_count,
@@ -7480,7 +7876,6 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7480
7876
  }
7481
7877
  let indexed = 0;
7482
7878
  for (const entry of entries) {
7483
- const activityTime = entry.session.time_updated ?? entry.session.time_created;
7484
7879
  upsertSessionRow(upsertIndexedSession, agentName, entry.session, null, entry.sortIndex, null);
7485
7880
  deleteFileActivity.run(agentName, entry.session.id);
7486
7881
  deleteMessageTools.run(agentName, entry.session.id, 0);
@@ -7503,6 +7898,7 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7503
7898
  message.cost ?? null,
7504
7899
  message.costSource ?? null,
7505
7900
  message.partsJson,
7901
+ MESSAGE_PARTS_FORMAT_VERSION,
7506
7902
  message.subagentId ?? null,
7507
7903
  message.nickname ?? null,
7508
7904
  message.contentText,
@@ -7516,15 +7912,7 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7516
7912
  upsertRow.run(
7517
7913
  agentName,
7518
7914
  entry.session.id,
7519
- entry.session.slug,
7520
7915
  entry.session.title,
7521
- entry.session.directory,
7522
- entry.identity.kind,
7523
- entry.identity.key,
7524
- entry.identity.displayName,
7525
- entry.session.time_created,
7526
- entry.session.time_updated ?? null,
7527
- activityTime,
7528
7916
  entry.contentText,
7529
7917
  entry.contentHash,
7530
7918
  entry.messages.length,
@@ -7535,8 +7923,7 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
7535
7923
  return indexed;
7536
7924
  }
7537
7925
  function syncSessionSearchIndex(agentName, sessions, loadSessionData, options = {}) {
7538
- return withCacheDb((db) => {
7539
- ensureFtsConsistency(db);
7926
+ return withSearchIndexDb((db) => {
7540
7927
  const startedAt = performance.now();
7541
7928
  const existingRows = db.prepare(
7542
7929
  "SELECT session_id, content_hash, indexed_message_count FROM session_documents WHERE agent_name = ? ORDER BY id"
@@ -7570,23 +7957,8 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
7570
7957
  loadSearchIndexEntries(agentName, changes, loadSessionData)
7571
7958
  );
7572
7959
  };
7573
- let rebuildDurationMs;
7574
7960
  const needsRebuild = isBulk && changedCount > 0;
7575
- if (needsRebuild) {
7576
- db.transaction(() => {
7577
- dropSearchTriggers(db);
7578
- dropMessageSearchTriggers(db);
7579
- writeRows();
7580
- const rebuildStartedAt = performance.now();
7581
- rebuildSearchIndex(db);
7582
- rebuildMessageSearchIndex(db);
7583
- rebuildDurationMs = performance.now() - rebuildStartedAt;
7584
- createSearchTriggers(db);
7585
- createMessageSearchTriggers(db);
7586
- })();
7587
- } else {
7588
- db.transaction(writeRows)();
7589
- }
7961
+ const { rebuildDurationMs } = runSearchIndexWrite(db, needsRebuild, writeRows);
7590
7962
  return {
7591
7963
  agentName,
7592
7964
  mode: isBulk ? "bulk" : "incremental",
@@ -7613,8 +7985,7 @@ function syncSessionSearchIndexChanges(agentName, changes, removedSessionIds, lo
7613
7985
  durationMs: 0
7614
7986
  };
7615
7987
  }
7616
- return withCacheDb((db) => {
7617
- ensureFtsConsistency(db);
7988
+ return withSearchIndexDb((db) => {
7618
7989
  const startedAt = performance.now();
7619
7990
  const searchIndexState = readSearchIndexState(
7620
7991
  db,
@@ -7636,23 +8007,8 @@ function syncSessionSearchIndexChanges(agentName, changes, removedSessionIds, lo
7636
8007
  loadSearchIndexEntries(agentName, toUpsert, loadSessionData)
7637
8008
  );
7638
8009
  };
7639
- let rebuildDurationMs;
7640
8010
  const needsRebuild = isBulk && changedCount > 0;
7641
- if (needsRebuild) {
7642
- db.transaction(() => {
7643
- dropSearchTriggers(db);
7644
- dropMessageSearchTriggers(db);
7645
- writeRows();
7646
- const rebuildStartedAt = performance.now();
7647
- rebuildSearchIndex(db);
7648
- rebuildMessageSearchIndex(db);
7649
- rebuildDurationMs = performance.now() - rebuildStartedAt;
7650
- createSearchTriggers(db);
7651
- createMessageSearchTriggers(db);
7652
- })();
7653
- } else {
7654
- db.transaction(writeRows)();
7655
- }
8011
+ const { rebuildDurationMs } = runSearchIndexWrite(db, needsRebuild, writeRows);
7656
8012
  return {
7657
8013
  agentName,
7658
8014
  mode: isBulk ? "bulk" : "incremental",
@@ -7794,6 +8150,32 @@ function buildSessionSearchFilters(options) {
7794
8150
  params
7795
8151
  };
7796
8152
  }
8153
+ var SQLITE_REFERENCE_BATCH_SIZE = 200;
8154
+ function filterIndexedSessionReferences(references, options) {
8155
+ if (references.length === 0 || !hasCacheStorage()) return /* @__PURE__ */ new Set();
8156
+ const matches = withSearchDb((db) => {
8157
+ const filters = buildSessionSearchFilters(options);
8158
+ const result = /* @__PURE__ */ new Set();
8159
+ for (let offset = 0; offset < references.length; offset += SQLITE_REFERENCE_BATCH_SIZE) {
8160
+ const batch = references.slice(offset, offset + SQLITE_REFERENCE_BATCH_SIZE);
8161
+ const referenceClauses = batch.map(() => "(s.agent_name = ? AND s.session_id = ?)");
8162
+ const referenceParams = batch.flatMap(({ agentName, sessionId }) => [agentName, sessionId]);
8163
+ const rows = db.prepare(
8164
+ `
8165
+ SELECT s.agent_name, s.session_id
8166
+ FROM sessions s
8167
+ WHERE (${referenceClauses.join(" OR ")})
8168
+ ${filters.where}
8169
+ `
8170
+ ).all(...referenceParams, ...filters.params);
8171
+ for (const row of rows) {
8172
+ result.add(searchResultRowKey(row));
8173
+ }
8174
+ }
8175
+ return result;
8176
+ });
8177
+ return matches ?? /* @__PURE__ */ new Set();
8178
+ }
7797
8179
  function searchSessionColumns() {
7798
8180
  return `
7799
8181
  s.agent_name,
@@ -7919,7 +8301,10 @@ function rowsToSearchResults(db, rows, textQuery, ftsQuery = toFtsQuery(textQuer
7919
8301
  return rows.map((row) => {
7920
8302
  const match = resolveSearchMatch(row, terms, messageMatches);
7921
8303
  return {
7922
- agentName: String(row.agent_name),
8304
+ reference: {
8305
+ agentName: String(row.agent_name),
8306
+ sessionId: String(row.session_id)
8307
+ },
7923
8308
  session: sessionHeadFromSearchRow(row),
7924
8309
  snippet: match.snippet,
7925
8310
  matchType: match.matchType
@@ -7932,8 +8317,7 @@ function searchSessions(query, options = {}) {
7932
8317
  if (!hasCacheStorage()) {
7933
8318
  return [];
7934
8319
  }
7935
- const results = withCacheDb((db) => {
7936
- ensureFtsReady(db);
8320
+ const results = withSearchDb((db) => {
7937
8321
  const filters = buildSessionSearchFilters(search.options);
7938
8322
  if (!normalizedQuery) {
7939
8323
  const rows2 = db.prepare(
@@ -7989,13 +8373,15 @@ function fileActivityFilters(options) {
7989
8373
  }
7990
8374
  function fileActivityFromRow(row) {
7991
8375
  return {
7992
- agent_name: String(row.agent_name),
7993
- session_id: String(row.session_id),
7994
- project_identity_key: String(row.project_identity_key ?? ""),
8376
+ reference: {
8377
+ agentName: String(row.agent_name),
8378
+ sessionId: String(row.session_id)
8379
+ },
8380
+ projectIdentityKey: String(row.project_identity_key ?? ""),
7995
8381
  path: String(row.path ?? ""),
7996
8382
  kind: row.kind ?? "read",
7997
8383
  count: Number(row.count ?? 0),
7998
- latest_time: Number(row.latest_time ?? 0)
8384
+ latestTime: Number(row.latest_time ?? 0)
7999
8385
  };
8000
8386
  }
8001
8387
  function buildFileActivityWhere(options) {
@@ -8060,10 +8446,19 @@ function buildFileActivityWhere(options) {
8060
8446
  };
8061
8447
  }
8062
8448
  function listFileActivity(options = {}) {
8449
+ return queryFileActivity(options);
8450
+ }
8451
+ function queryFileActivity(options, sessionSearchOptions) {
8063
8452
  if (!hasCacheStorage()) {
8064
8453
  return [];
8065
8454
  }
8066
8455
  const filters = buildFileActivityWhere(options);
8456
+ const sessionFilters = sessionSearchOptions ? buildSessionSearchFilters(sessionSearchOptions) : { where: "", params: [] };
8457
+ const whereClauses = [
8458
+ filters.where.replace(/^WHERE /, ""),
8459
+ sessionFilters.where.replace(/^ AND /, "")
8460
+ ].filter(Boolean);
8461
+ const where = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
8067
8462
  const queryRows = (db) => db.prepare(
8068
8463
  `
8069
8464
  SELECT
@@ -8088,14 +8483,17 @@ function listFileActivity(options = {}) {
8088
8483
  s.total_cache_create_tokens,
8089
8484
  s.total_cost,
8090
8485
  s.cost_source,
8091
- s.total_tokens
8486
+ s.total_tokens,
8487
+ s.model_usage_json,
8488
+ s.smart_tags_json,
8489
+ s.smart_tags_source_updated_at
8092
8490
  FROM session_file_activity fa
8093
8491
  JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
8094
- ${filters.where}
8492
+ ${where}
8095
8493
  ORDER BY fa.latest_time DESC, fa.count DESC, fa.path
8096
8494
  LIMIT ?
8097
8495
  `
8098
- ).all(...filters.params, options.limit ?? 50);
8496
+ ).all(...filters.params, ...sessionFilters.params, options.limit ?? 50);
8099
8497
  let rows = withCacheDbReadOnly(queryRows);
8100
8498
  if (rows == null && options.path) {
8101
8499
  rows = withCacheDb(queryRows);
@@ -8124,27 +8522,22 @@ function searchFileActivitySessions(query, options = {}) {
8124
8522
  const search = mergeSearchQueryOptions(query, options);
8125
8523
  const path2 = normalizeFilePathSearch(search.options.file ?? search.text);
8126
8524
  if (!path2) return [];
8127
- const rows = listFileActivity({
8128
- agent: search.options.agent,
8129
- projectKind: search.options.projectKind,
8130
- projectKey: search.options.projectKey,
8131
- project: search.options.project,
8132
- cwd: search.options.cwd,
8133
- path: path2,
8134
- kind: search.options.fileKind,
8135
- from: search.options.from,
8136
- to: search.options.to,
8137
- limit: (search.options.limit ?? 50) * 3
8138
- });
8525
+ const rows = queryFileActivity(
8526
+ {
8527
+ path: path2,
8528
+ kind: search.options.fileKind,
8529
+ limit: (search.options.limit ?? 50) * 3
8530
+ },
8531
+ search.options
8532
+ );
8139
8533
  const seen = /* @__PURE__ */ new Set();
8140
8534
  const results = [];
8141
8535
  for (const row of rows) {
8142
- const key = `${row.agent_name}/${row.session_id}`;
8536
+ const key = `${row.reference.agentName}/${row.reference.sessionId}`;
8143
8537
  if (seen.has(key)) continue;
8144
- if (!sessionMatchesSearchCost(row.session, search.options)) continue;
8145
8538
  seen.add(key);
8146
8539
  results.push({
8147
- agentName: row.agent_name,
8540
+ reference: row.reference,
8148
8541
  session: row.session,
8149
8542
  snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
8150
8543
  matchType: "file_path"
@@ -8280,7 +8673,7 @@ function markAgentFullSyncCompleted(agentName) {
8280
8673
  ).run(Date.now(), agentName);
8281
8674
  });
8282
8675
  }
8283
- function loadCachedSessionDataEntry(agentName, sessionId) {
8676
+ function loadCachedSessionRawEntry(agentName, sessionId) {
8284
8677
  if (!hasCacheStorage()) {
8285
8678
  return null;
8286
8679
  }
@@ -8334,6 +8727,7 @@ function loadCachedSessionDataEntry(agentName, sessionId) {
8334
8727
  cost,
8335
8728
  cost_source,
8336
8729
  parts_json,
8730
+ parts_format_version,
8337
8731
  subagent_id,
8338
8732
  nickname
8339
8733
  FROM messages
@@ -8354,20 +8748,16 @@ function loadCachedSessionDataEntry(agentName, sessionId) {
8354
8748
  return {
8355
8749
  data: {
8356
8750
  ...head,
8357
- messages: messageRows.map((messageRow) => messageFromCachedRow(messageRow)),
8751
+ reference: { agentName, sessionId },
8358
8752
  file_activity: fileActivityRows.map((activityRow) => fileActivityFromRow(activityRow))
8359
8753
  },
8754
+ messageRows,
8360
8755
  meta: parseCachedSessionMeta(row.meta_json)
8361
8756
  };
8362
8757
  });
8363
8758
  }
8364
- function loadCachedSessionData(agentName, sessionId) {
8365
- return loadCachedSessionDataEntry(agentName, sessionId)?.data ?? null;
8366
- }
8367
8759
  function saveCachedSessions(agentName, sessions, meta = {}) {
8368
8760
  withCacheDb((db) => {
8369
- const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
8370
- const deleteLegacySessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
8371
8761
  const deleteSession = db.prepare(
8372
8762
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
8373
8763
  );
@@ -8383,25 +8773,16 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
8383
8773
  const deleteFileActivity = db.prepare(
8384
8774
  "DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
8385
8775
  );
8386
- const deleteProjectSession = db.prepare(
8387
- "DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
8388
- );
8389
- const deleteProjectSessions = db.prepare("DELETE FROM project_sessions WHERE agent_name = ?");
8390
8776
  const upsertAgent = db.prepare(`
8391
8777
  INSERT INTO agent_cache(agent_name, timestamp)
8392
8778
  VALUES (?, ?)
8393
8779
  ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
8394
8780
  `);
8395
- const upsertCachedSession = prepareUpsertCachedSession(db);
8396
8781
  const upsertSession = prepareUpsertSession(db);
8397
- const upsertProjectSession = prepareUpsertProjectSession(db);
8398
8782
  const write = db.transaction(() => {
8399
8783
  const timestamp = Date.now();
8400
8784
  const sessionIds = new Set(sessions.map((session) => session.id));
8401
8785
  const existingSessionIds = db.prepare("SELECT session_id FROM sessions WHERE agent_name = ?").all(agentName);
8402
- deleteAgent.run(agentName);
8403
- deleteLegacySessions.run(agentName);
8404
- deleteProjectSessions.run(agentName);
8405
8786
  upsertAgent.run(agentName, timestamp);
8406
8787
  for (const row of existingSessionIds) {
8407
8788
  const sessionId = String(row.session_id);
@@ -8410,15 +8791,12 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
8410
8791
  deleteMessageTools.run(agentName, sessionId);
8411
8792
  deleteMessages.run(agentName, sessionId);
8412
8793
  deleteFileActivity.run(agentName, sessionId);
8413
- deleteProjectSession.run(agentName, sessionId);
8414
8794
  deleteSession.run(agentName, sessionId);
8415
8795
  }
8416
8796
  }
8417
8797
  sessions.forEach((session, index) => {
8418
- const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
8419
8798
  const sessionMeta = meta[session.id];
8420
8799
  const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
8421
- upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
8422
8800
  upsertSessionRow(
8423
8801
  upsertSession,
8424
8802
  agentName,
@@ -8427,7 +8805,6 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
8427
8805
  index,
8428
8806
  sourcePathFromMeta(sessionMeta)
8429
8807
  );
8430
- writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
8431
8808
  });
8432
8809
  });
8433
8810
  write();
@@ -8439,9 +8816,6 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8439
8816
  return;
8440
8817
  }
8441
8818
  withCacheDb((db) => {
8442
- const deleteLegacySession = db.prepare(
8443
- "DELETE FROM cached_sessions WHERE agent_name = ? AND session_id = ?"
8444
- );
8445
8819
  const deleteSession = db.prepare(
8446
8820
  "DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
8447
8821
  );
@@ -8457,33 +8831,24 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8457
8831
  const deleteFileActivity = db.prepare(
8458
8832
  "DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
8459
8833
  );
8460
- const deleteProjectSession = db.prepare(
8461
- "DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
8462
- );
8463
8834
  const upsertAgent = db.prepare(`
8464
8835
  INSERT INTO agent_cache(agent_name, timestamp)
8465
8836
  VALUES (?, ?)
8466
8837
  ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
8467
8838
  `);
8468
- const upsertCachedSession = prepareUpsertCachedSession(db);
8469
8839
  const upsertSession = prepareUpsertSession(db);
8470
- const upsertProjectSession = prepareUpsertProjectSession(db);
8471
8840
  const write = db.transaction(() => {
8472
8841
  upsertAgent.run(agentName, Date.now());
8473
8842
  for (const sessionId of new Set(removedSessionIds)) {
8474
- deleteLegacySession.run(agentName, sessionId);
8475
8843
  deleteSearchDocument.run(agentName, sessionId);
8476
8844
  deleteMessageTools.run(agentName, sessionId);
8477
8845
  deleteMessages.run(agentName, sessionId);
8478
8846
  deleteFileActivity.run(agentName, sessionId);
8479
- deleteProjectSession.run(agentName, sessionId);
8480
8847
  deleteSession.run(agentName, sessionId);
8481
8848
  }
8482
8849
  for (const { session, sortIndex } of changes) {
8483
- const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
8484
8850
  const sessionMeta = meta[session.id];
8485
8851
  const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
8486
- upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
8487
8852
  upsertSessionRow(
8488
8853
  upsertSession,
8489
8854
  agentName,
@@ -8492,7 +8857,6 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8492
8857
  sortIndex,
8493
8858
  sourcePathFromMeta(sessionMeta)
8494
8859
  );
8495
- writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
8496
8860
  }
8497
8861
  });
8498
8862
  write();
@@ -8500,7 +8864,6 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8500
8864
  });
8501
8865
  }
8502
8866
  function clearCache() {
8503
- setFtsIntegrityCheckedPath(null);
8504
8867
  setSchemaEnsuredPath(null);
8505
8868
  if (!hasCacheStorage()) {
8506
8869
  deleteLegacyCacheFile();
@@ -8533,49 +8896,6 @@ function clearCache() {
8533
8896
  }
8534
8897
  }
8535
8898
  }
8536
- function getCacheInfo() {
8537
- if (!hasCacheStorage()) {
8538
- return { lastScanTime: null, size: 0 };
8539
- }
8540
- const info = withCacheDb((db) => {
8541
- const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
8542
- const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM sessions").get();
8543
- const lastScanTime = Number(timestampRow?.value ?? 0) || null;
8544
- const size = Number(sizeRow?.value ?? 0);
8545
- return { lastScanTime, size };
8546
- });
8547
- return info ?? { lastScanTime: null, size: 0 };
8548
- }
8549
- function listCachedProjectGroups(sessions) {
8550
- if (sessions) {
8551
- return buildProjectGroups(sessions);
8552
- }
8553
- if (!hasCacheStorage()) {
8554
- return [];
8555
- }
8556
- const queryRows = (db) => db.prepare(
8557
- `
8558
- SELECT identity_kind, identity_key, display_name, sources_csv, session_count, last_activity
8559
- FROM project_groups_v
8560
- ORDER BY
8561
- CASE identity_kind WHEN 'loose' THEN 1 ELSE 0 END,
8562
- last_activity IS NULL,
8563
- last_activity DESC
8564
- `
8565
- ).all();
8566
- let rows = withCacheDbReadOnly(queryRows);
8567
- if (rows == null) {
8568
- rows = withCacheDb(queryRows);
8569
- }
8570
- return (rows ?? []).map((row) => ({
8571
- identityKind: row.identity_kind ?? "path",
8572
- identityKey: String(row.identity_key ?? ""),
8573
- displayName: String(row.display_name ?? ""),
8574
- sources: String(row.sources_csv ?? "").split(",").filter(Boolean).sort(),
8575
- sessionCount: Number(row.session_count ?? 0),
8576
- lastActivity: row.last_activity == null ? null : Number(row.last_activity)
8577
- }));
8578
- }
8579
8899
  function attachMissingProjectIdentities(sessions) {
8580
8900
  return sessions.map((session) => {
8581
8901
  if (session.project_identity) return session;
@@ -8965,8 +9285,140 @@ async function scanSessions(options = {}, onProgress) {
8965
9285
  cacheTimestamps: Object.keys(cacheTimestamps).length > 0 ? cacheTimestamps : void 0
8966
9286
  };
8967
9287
  }
8968
- async function scanSessionsAsync(options = {}, onProgress) {
8969
- return scanSessions(options, onProgress);
9288
+ var sessionDetailLookups = /* @__PURE__ */ new WeakMap();
9289
+ function sessionReferenceKey(agentName, sessionId) {
9290
+ return `${agentName}\0${sessionId}`;
9291
+ }
9292
+ function getSessionDetailLookup(scanResult) {
9293
+ const cached = sessionDetailLookups.get(scanResult.sessions);
9294
+ if (cached) return cached;
9295
+ const agentsByName = /* @__PURE__ */ new Map();
9296
+ for (const agent of scanResult.agents) {
9297
+ if (!agentsByName.has(agent.name)) agentsByName.set(agent.name, agent);
9298
+ }
9299
+ const headsByReference = /* @__PURE__ */ new Map();
9300
+ for (const [agentName, sessions] of Object.entries(scanResult.byAgent)) {
9301
+ for (const session of sessions) {
9302
+ const key = sessionReferenceKey(agentName, session.id);
9303
+ if (!headsByReference.has(key)) headsByReference.set(key, session);
9304
+ }
9305
+ }
9306
+ const lookup = { agentsByName, headsByReference };
9307
+ sessionDetailLookups.set(scanResult.sessions, lookup);
9308
+ return lookup;
9309
+ }
9310
+ function getSessionDetailContext(scanResult, reference) {
9311
+ const lookup = getSessionDetailLookup(scanResult);
9312
+ const agent = lookup.agentsByName.get(reference.agentName);
9313
+ if (!agent) return null;
9314
+ return {
9315
+ agent,
9316
+ head: lookup.headsByReference.get(
9317
+ sessionReferenceKey(reference.agentName, reference.sessionId)
9318
+ )
9319
+ };
9320
+ }
9321
+ function cacheMatchesCurrentSource(cachedMeta, currentMeta) {
9322
+ const currentFingerprint = currentMeta?.sourceFingerprint;
9323
+ if (typeof currentFingerprint !== "string") return true;
9324
+ return cachedMeta?.sourceFingerprint === currentFingerprint;
9325
+ }
9326
+ function cacheHasCompleteDetail(cachedEntry, currentMeta) {
9327
+ if (!cachedEntry || !cacheMatchesCurrentSource(cachedEntry.meta, currentMeta)) {
9328
+ return false;
9329
+ }
9330
+ return cachedEntry.messageRows.length > 0 || cachedEntry.data.stats.message_count === 0;
9331
+ }
9332
+ function getProjectIdentity(data, head) {
9333
+ return data.project_identity ?? head?.project_identity ?? computeIdentity(data.directory, realFs);
9334
+ }
9335
+ function materializeStructuredSessionDetail(context, reference, cachedEntry = loadCachedSessionRawEntry(reference.agentName, reference.sessionId)) {
9336
+ const { agent, head } = context;
9337
+ const currentMeta = head ? agent.getSessionMetaMap().get(reference.sessionId) : void 0;
9338
+ const useCache = cacheHasCompleteDetail(cachedEntry, currentMeta);
9339
+ const data = useCache ? {
9340
+ ...cachedEntry.data,
9341
+ messages: cachedEntry.messageRows.map((messageRow) => messageFromCachedRow(messageRow))
9342
+ } : head ? agent.getSessionData(reference.sessionId) : null;
9343
+ if (!data) {
9344
+ return { status: "not-ready" };
9345
+ }
9346
+ const projectIdentity = getProjectIdentity(data, head);
9347
+ const fileActivity = data.file_activity ?? (useCache ? listSessionFileActivity(reference.agentName, reference.sessionId) : extractSessionFileActivity(
9348
+ reference.agentName,
9349
+ reference.sessionId,
9350
+ projectIdentity.key,
9351
+ data.messages
9352
+ ));
9353
+ return {
9354
+ status: "found",
9355
+ data: {
9356
+ ...data,
9357
+ reference,
9358
+ project_identity: projectIdentity,
9359
+ smart_tags: data.smart_tags ?? classifySessionTags(data),
9360
+ smart_tags_source_updated_at: getSmartTagSourceTimestamp(data),
9361
+ file_activity: fileActivity
9362
+ }
9363
+ };
9364
+ }
9365
+ function* serializeCachedMessages(entry) {
9366
+ for (const messageRow of entry.messageRows) {
9367
+ yield messageJsonFromCachedRow(messageRow);
9368
+ }
9369
+ }
9370
+ function materializeSessionDetailResponse(scanResult, reference) {
9371
+ const context = getSessionDetailContext(scanResult, reference);
9372
+ if (!context) return { status: "unknown-agent" };
9373
+ const cachedEntry = loadCachedSessionRawEntry(reference.agentName, reference.sessionId);
9374
+ const currentMeta = context.head ? context.agent.getSessionMetaMap().get(reference.sessionId) : void 0;
9375
+ const useCache = cacheHasCompleteDetail(cachedEntry, currentMeta);
9376
+ if (!useCache || cachedEntry.data.smart_tags == null) {
9377
+ return materializeStructuredSessionDetail(context, reference, cachedEntry);
9378
+ }
9379
+ const data = cachedEntry.data;
9380
+ return {
9381
+ status: "found-json",
9382
+ data: {
9383
+ ...data,
9384
+ reference,
9385
+ project_identity: getProjectIdentity(data, context.head),
9386
+ smart_tags_source_updated_at: getSmartTagSourceTimestamp(data),
9387
+ file_activity: data.file_activity ?? listSessionFileActivity(reference.agentName, reference.sessionId)
9388
+ },
9389
+ messages: serializeCachedMessages(cachedEntry),
9390
+ messageCount: cachedEntry.messageRows.length
9391
+ };
9392
+ }
9393
+ function listCachedProjectGroups(sessions) {
9394
+ if (sessions) {
9395
+ return buildProjectGroups(sessions);
9396
+ }
9397
+ if (!hasCacheStorage()) {
9398
+ return [];
9399
+ }
9400
+ const queryRows = (db) => db.prepare(
9401
+ `
9402
+ SELECT identity_kind, identity_key, display_name, sources_csv, session_count, last_activity
9403
+ FROM project_groups_v
9404
+ ORDER BY
9405
+ CASE identity_kind WHEN 'loose' THEN 1 ELSE 0 END,
9406
+ last_activity IS NULL,
9407
+ last_activity DESC
9408
+ `
9409
+ ).all();
9410
+ let rows = withCacheDbReadOnly(queryRows);
9411
+ if (rows == null) {
9412
+ rows = withCacheDb(queryRows);
9413
+ }
9414
+ return (rows ?? []).map((row) => ({
9415
+ identityKind: row.identity_kind ?? "path",
9416
+ identityKey: String(row.identity_key ?? ""),
9417
+ displayName: String(row.display_name ?? ""),
9418
+ sources: String(row.sources_csv ?? "").split(",").filter(Boolean).sort(),
9419
+ sessionCount: Number(row.session_count ?? 0),
9420
+ lastActivity: row.last_activity == null ? null : Number(row.last_activity)
9421
+ }));
8970
9422
  }
8971
9423
  var STATE_DB_FILENAME = "state.db";
8972
9424
  var STATE_SCHEMA_VERSION = 2;
@@ -8986,18 +9438,18 @@ var StateStorageUnavailableError = class extends Error {
8986
9438
  };
8987
9439
  function getStateDir() {
8988
9440
  if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
8989
- const currentPlatform = platform2();
9441
+ const currentPlatform = platform4();
8990
9442
  if (currentPlatform === "darwin") {
8991
- return join12(homedir5(), "Library", "Application Support", "codesesh");
9443
+ return join13(homedir7(), "Library", "Application Support", "codesesh");
8992
9444
  }
8993
9445
  if (currentPlatform === "win32") {
8994
9446
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
8995
- return join12(appData ?? join12(homedir5(), "AppData", "Roaming"), "codesesh");
9447
+ return join13(appData ?? join13(homedir7(), "AppData", "Roaming"), "codesesh");
8996
9448
  }
8997
- return join12(process.env.XDG_DATA_HOME ?? join12(homedir5(), ".local", "share"), "codesesh");
9449
+ return join13(process.env.XDG_DATA_HOME ?? join13(homedir7(), ".local", "share"), "codesesh");
8998
9450
  }
8999
9451
  function getStateDbPath() {
9000
- return join12(getStateDir(), STATE_DB_FILENAME);
9452
+ return join13(getStateDir(), STATE_DB_FILENAME);
9001
9453
  }
9002
9454
  function useMemoryStateStore() {
9003
9455
  return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
@@ -9103,41 +9555,59 @@ function withStateDb(fn) {
9103
9555
  }
9104
9556
  }
9105
9557
  var memoryBookmarks = /* @__PURE__ */ new Map();
9106
- function getBookmarkKey(agentKey, sessionId) {
9107
- return JSON.stringify([agentKey, sessionId]);
9558
+ function getBookmarkKey(reference) {
9559
+ const normalized = normalizeSessionReference(reference);
9560
+ return JSON.stringify([normalized.agentName, normalized.sessionId]);
9108
9561
  }
9109
9562
  function getActivityTime(bookmark) {
9110
- return bookmark.time_updated ?? bookmark.time_created;
9563
+ return bookmark.session.time_updated ?? bookmark.session.time_created;
9111
9564
  }
9112
9565
  function sortBookmarks(bookmarks) {
9113
9566
  return bookmarks.sort((a, b) => {
9114
9567
  const activityDelta = getActivityTime(b) - getActivityTime(a);
9115
- return activityDelta || b.bookmarked_at - a.bookmarked_at;
9568
+ return activityDelta || b.bookmarkedAt - a.bookmarkedAt;
9116
9569
  });
9117
9570
  }
9118
9571
  function listMemoryBookmarks() {
9119
9572
  return sortBookmarks(Array.from(memoryBookmarks.values()));
9120
9573
  }
9574
+ function normalizeBookmark(bookmark) {
9575
+ const reference = normalizeSessionReference(bookmark.reference);
9576
+ return {
9577
+ reference,
9578
+ session: {
9579
+ ...bookmark.session,
9580
+ id: reference.sessionId,
9581
+ slug: formatSessionReference(reference)
9582
+ }
9583
+ };
9584
+ }
9121
9585
  function upsertMemoryBookmark(bookmark) {
9122
- const key = getBookmarkKey(bookmark.agentKey, bookmark.sessionId);
9586
+ const key = getBookmarkKey(bookmark.reference);
9123
9587
  const saved = {
9124
9588
  ...bookmark,
9125
- bookmarked_at: memoryBookmarks.get(key)?.bookmarked_at ?? Date.now()
9589
+ bookmarkedAt: memoryBookmarks.get(key)?.bookmarkedAt ?? Date.now()
9126
9590
  };
9127
9591
  memoryBookmarks.set(key, saved);
9128
9592
  return saved;
9129
9593
  }
9130
9594
  function toBookmarkRecord(row) {
9595
+ const reference = normalizeSessionReference({
9596
+ agentName: String(row.agent_name ?? ""),
9597
+ sessionId: String(row.session_id ?? "")
9598
+ });
9131
9599
  return {
9132
- agentKey: String(row.agent_name ?? ""),
9133
- sessionId: String(row.session_id ?? ""),
9134
- fullPath: String(row.slug ?? ""),
9135
- title: String(row.title ?? ""),
9136
- directory: String(row.directory ?? ""),
9137
- time_created: Number(row.time_created ?? 0),
9138
- time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
9139
- stats: JSON.parse(String(row.stats_json ?? "{}")),
9140
- bookmarked_at: Number(row.bookmarked_at ?? 0)
9600
+ reference,
9601
+ session: {
9602
+ id: reference.sessionId,
9603
+ slug: formatSessionReference(reference),
9604
+ title: String(row.title ?? ""),
9605
+ directory: String(row.directory ?? ""),
9606
+ time_created: Number(row.time_created ?? 0),
9607
+ time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
9608
+ stats: JSON.parse(String(row.stats_json ?? "{}"))
9609
+ },
9610
+ bookmarkedAt: Number(row.bookmarked_at ?? 0)
9141
9611
  };
9142
9612
  }
9143
9613
  function listBookmarks() {
@@ -9150,7 +9620,6 @@ function listBookmarks() {
9150
9620
  SELECT
9151
9621
  agent_name,
9152
9622
  session_id,
9153
- slug,
9154
9623
  title,
9155
9624
  directory,
9156
9625
  time_created,
@@ -9165,8 +9634,9 @@ function listBookmarks() {
9165
9634
  });
9166
9635
  }
9167
9636
  function upsertBookmark(bookmark) {
9637
+ const normalized = normalizeBookmark(bookmark);
9168
9638
  if (useMemoryStateStore()) {
9169
- return upsertMemoryBookmark(bookmark);
9639
+ return upsertMemoryBookmark(normalized);
9170
9640
  }
9171
9641
  return withStateDb((db) => {
9172
9642
  const existing = db.prepare(
@@ -9175,7 +9645,7 @@ function upsertBookmark(bookmark) {
9175
9645
  FROM bookmarks
9176
9646
  WHERE agent_name = ? AND session_id = ?
9177
9647
  `
9178
- ).get(bookmark.agentKey, bookmark.sessionId);
9648
+ ).get(normalized.reference.agentName, normalized.reference.sessionId);
9179
9649
  const bookmarkedAt = Number(existing?.bookmarked_at ?? Date.now());
9180
9650
  db.prepare(
9181
9651
  `
@@ -9199,22 +9669,23 @@ function upsertBookmark(bookmark) {
9199
9669
  stats_json = excluded.stats_json
9200
9670
  `
9201
9671
  ).run(
9202
- bookmark.agentKey,
9203
- bookmark.sessionId,
9204
- bookmark.fullPath,
9205
- bookmark.title,
9206
- bookmark.directory,
9207
- bookmark.time_created,
9208
- bookmark.time_updated ?? null,
9209
- JSON.stringify(bookmark.stats),
9672
+ normalized.reference.agentName,
9673
+ normalized.reference.sessionId,
9674
+ formatSessionReference(normalized.reference),
9675
+ normalized.session.title,
9676
+ normalized.session.directory,
9677
+ normalized.session.time_created,
9678
+ normalized.session.time_updated ?? null,
9679
+ JSON.stringify(normalized.session.stats),
9210
9680
  bookmarkedAt
9211
9681
  );
9212
- return { ...bookmark, bookmarked_at: bookmarkedAt };
9682
+ return { ...normalized, bookmarkedAt };
9213
9683
  });
9214
9684
  }
9215
9685
  function importBookmarks(bookmarks) {
9686
+ const normalizedBookmarks = bookmarks.map(normalizeBookmark);
9216
9687
  if (useMemoryStateStore()) {
9217
- for (const bookmark of bookmarks) {
9688
+ for (const bookmark of normalizedBookmarks) {
9218
9689
  upsertMemoryBookmark(bookmark);
9219
9690
  }
9220
9691
  return listMemoryBookmarks();
@@ -9223,7 +9694,10 @@ function importBookmarks(bookmarks) {
9223
9694
  const existingRows = db.prepare("SELECT agent_name, session_id, bookmarked_at FROM bookmarks").all();
9224
9695
  const existingTimes = new Map(
9225
9696
  existingRows.map((row) => [
9226
- `${String(row.agent_name ?? "")}:${String(row.session_id ?? "")}`,
9697
+ getBookmarkKey({
9698
+ agentName: String(row.agent_name ?? ""),
9699
+ sessionId: String(row.session_id ?? "")
9700
+ }),
9227
9701
  Number(row.bookmarked_at ?? 0)
9228
9702
  ])
9229
9703
  );
@@ -9250,17 +9724,17 @@ function importBookmarks(bookmarks) {
9250
9724
  `
9251
9725
  );
9252
9726
  const write = db.transaction(() => {
9253
- for (const bookmark of bookmarks) {
9254
- const key = `${bookmark.agentKey}:${bookmark.sessionId}`;
9727
+ for (const bookmark of normalizedBookmarks) {
9728
+ const key = getBookmarkKey(bookmark.reference);
9255
9729
  upsert.run(
9256
- bookmark.agentKey,
9257
- bookmark.sessionId,
9258
- bookmark.fullPath,
9259
- bookmark.title,
9260
- bookmark.directory,
9261
- bookmark.time_created,
9262
- bookmark.time_updated ?? null,
9263
- JSON.stringify(bookmark.stats),
9730
+ bookmark.reference.agentName,
9731
+ bookmark.reference.sessionId,
9732
+ formatSessionReference(bookmark.reference),
9733
+ bookmark.session.title,
9734
+ bookmark.session.directory,
9735
+ bookmark.session.time_created,
9736
+ bookmark.session.time_updated ?? null,
9737
+ JSON.stringify(bookmark.session.stats),
9264
9738
  existingTimes.get(key) ?? Date.now()
9265
9739
  );
9266
9740
  }
@@ -9271,7 +9745,6 @@ function importBookmarks(bookmarks) {
9271
9745
  SELECT
9272
9746
  agent_name,
9273
9747
  session_id,
9274
- slug,
9275
9748
  title,
9276
9749
  directory,
9277
9750
  time_created,
@@ -9285,9 +9758,10 @@ function importBookmarks(bookmarks) {
9285
9758
  return rows.map(toBookmarkRecord);
9286
9759
  });
9287
9760
  }
9288
- function deleteBookmark(agentKey, sessionId) {
9761
+ function deleteBookmark(reference) {
9762
+ const normalized = normalizeSessionReference(reference);
9289
9763
  if (useMemoryStateStore()) {
9290
- memoryBookmarks.delete(getBookmarkKey(agentKey, sessionId));
9764
+ memoryBookmarks.delete(getBookmarkKey(normalized));
9291
9765
  return;
9292
9766
  }
9293
9767
  withStateDb((db) => {
@@ -9296,20 +9770,30 @@ function deleteBookmark(agentKey, sessionId) {
9296
9770
  DELETE FROM bookmarks
9297
9771
  WHERE agent_name = ? AND session_id = ?
9298
9772
  `
9299
- ).run(agentKey, sessionId);
9773
+ ).run(normalized.agentName, normalized.sessionId);
9300
9774
  });
9301
9775
  }
9302
9776
  var SESSION_ALIAS_MAX_LENGTH = 160;
9777
+ var SessionAliasValidationError = class extends Error {
9778
+ constructor() {
9779
+ super("Invalid session alias");
9780
+ this.name = "SessionAliasValidationError";
9781
+ }
9782
+ };
9303
9783
  var memoryAliases = /* @__PURE__ */ new Map();
9304
- function getAliasKey(agentKey, sessionId) {
9305
- return JSON.stringify([agentKey, sessionId]);
9784
+ function getAliasKey(reference) {
9785
+ const normalized = normalizeSessionReference(reference);
9786
+ return JSON.stringify([normalized.agentName, normalized.sessionId]);
9306
9787
  }
9307
9788
  function toSessionAlias(row) {
9789
+ const reference = normalizeSessionReference({
9790
+ agentName: String(row.agent_name ?? ""),
9791
+ sessionId: String(row.session_id ?? "")
9792
+ });
9308
9793
  return {
9309
- agentKey: String(row.agent_name ?? ""),
9310
- sessionId: String(row.session_id ?? ""),
9794
+ reference,
9311
9795
  alias: String(row.alias ?? ""),
9312
- updated_at: Number(row.updated_at ?? 0)
9796
+ updatedAt: Number(row.updated_at ?? 0)
9313
9797
  };
9314
9798
  }
9315
9799
  function normalizeSessionAlias(value) {
@@ -9329,19 +9813,19 @@ function listSessionAliases() {
9329
9813
  ).all().map(toSessionAlias)
9330
9814
  );
9331
9815
  }
9332
- function upsertSessionAlias(agentKey, sessionId, alias) {
9816
+ function upsertSessionAlias(reference, alias) {
9333
9817
  const normalizedAlias = normalizeSessionAlias(alias);
9334
9818
  if (!normalizedAlias) {
9335
- throw new TypeError("Invalid session alias");
9819
+ throw new SessionAliasValidationError();
9336
9820
  }
9821
+ const normalizedReference = normalizeSessionReference(reference);
9337
9822
  const saved = {
9338
- agentKey,
9339
- sessionId,
9823
+ reference: normalizedReference,
9340
9824
  alias: normalizedAlias,
9341
- updated_at: Date.now()
9825
+ updatedAt: Date.now()
9342
9826
  };
9343
9827
  if (useMemoryStateStore()) {
9344
- memoryAliases.set(getAliasKey(agentKey, sessionId), saved);
9828
+ memoryAliases.set(getAliasKey(normalizedReference), saved);
9345
9829
  return saved;
9346
9830
  }
9347
9831
  return withStateDb((db) => {
@@ -9353,13 +9837,14 @@ function upsertSessionAlias(agentKey, sessionId, alias) {
9353
9837
  alias = excluded.alias,
9354
9838
  updated_at = excluded.updated_at
9355
9839
  `
9356
- ).run(saved.agentKey, saved.sessionId, saved.alias, saved.updated_at);
9840
+ ).run(saved.reference.agentName, saved.reference.sessionId, saved.alias, saved.updatedAt);
9357
9841
  return saved;
9358
9842
  });
9359
9843
  }
9360
- function deleteSessionAlias(agentKey, sessionId) {
9844
+ function deleteSessionAlias(reference) {
9845
+ const normalizedReference = normalizeSessionReference(reference);
9361
9846
  if (useMemoryStateStore()) {
9362
- memoryAliases.delete(getAliasKey(agentKey, sessionId));
9847
+ memoryAliases.delete(getAliasKey(normalizedReference));
9363
9848
  return;
9364
9849
  }
9365
9850
  withStateDb((db) => {
@@ -9368,7 +9853,7 @@ function deleteSessionAlias(agentKey, sessionId) {
9368
9853
  DELETE FROM session_aliases
9369
9854
  WHERE agent_name = ? AND session_id = ?
9370
9855
  `
9371
- ).run(agentKey, sessionId);
9856
+ ).run(normalizedReference.agentName, normalizedReference.sessionId);
9372
9857
  });
9373
9858
  }
9374
9859
  var DASHBOARD_RECENT_LIMIT = 10;
@@ -9376,7 +9861,7 @@ function getTotalTokens(stats) {
9376
9861
  return stats.total_tokens ?? stats.total_input_tokens + stats.total_output_tokens;
9377
9862
  }
9378
9863
  function getSessionAgentName(session) {
9379
- return session.slug.split("/")[0]?.toLowerCase() || "unknown";
9864
+ return getSessionAgentKey(session);
9380
9865
  }
9381
9866
  function getSessionActivityTime(session) {
9382
9867
  return session.time_updated ?? session.time_created;
@@ -9510,7 +9995,10 @@ function buildDashboard(sessions, options) {
9510
9995
  const modelDistribution = [...modelAgg.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
9511
9996
  const recentSessions = recentCandidates.map(({ session }) => {
9512
9997
  const agentKey = getSessionAgentName(session);
9513
- return { ...session, agentName: agentKey };
9998
+ return {
9999
+ reference: { agentName: agentKey, sessionId: session.id },
10000
+ session
10001
+ };
9514
10002
  });
9515
10003
  return {
9516
10004
  totals: {
@@ -9528,6 +10016,58 @@ function buildDashboard(sessions, options) {
9528
10016
  recentSessions
9529
10017
  };
9530
10018
  }
10019
+ function getProjectGroupKey(identityKind, identityKey) {
10020
+ return `${identityKind}:${identityKey}`;
10021
+ }
10022
+ function emptyMetrics() {
10023
+ return { messages: 0, tokens: 0, cost: 0, hasEstimatedCost: false, agentStats: /* @__PURE__ */ new Map() };
10024
+ }
10025
+ function attachProjectMetrics(projects, sessions) {
10026
+ const metrics = /* @__PURE__ */ new Map();
10027
+ for (const session of sessions) {
10028
+ const identity = session.project_identity;
10029
+ if (!identity) continue;
10030
+ const key = getProjectGroupKey(identity.kind, identity.key);
10031
+ let current = metrics.get(key);
10032
+ if (!current) {
10033
+ current = emptyMetrics();
10034
+ metrics.set(key, current);
10035
+ }
10036
+ const tokens = getTotalTokens(session.stats);
10037
+ const cost = session.stats.total_cost ?? 0;
10038
+ current.messages += session.stats.message_count;
10039
+ current.tokens += tokens;
10040
+ current.cost += cost;
10041
+ if (session.stats.cost_source === "estimated") current.hasEstimatedCost = true;
10042
+ const agentName = getSessionAgentName(session);
10043
+ const agent = current.agentStats.get(agentName);
10044
+ if (agent) {
10045
+ agent.sessions += 1;
10046
+ agent.messages += session.stats.message_count;
10047
+ agent.tokens += tokens;
10048
+ agent.cost += cost;
10049
+ } else {
10050
+ current.agentStats.set(agentName, {
10051
+ name: agentName,
10052
+ sessions: 1,
10053
+ messages: session.stats.message_count,
10054
+ tokens,
10055
+ cost
10056
+ });
10057
+ }
10058
+ }
10059
+ return projects.map((project) => {
10060
+ const metric = metrics.get(getProjectGroupKey(project.identityKind, project.identityKey));
10061
+ return {
10062
+ ...project,
10063
+ messages: metric?.messages ?? 0,
10064
+ tokens: metric?.tokens ?? 0,
10065
+ cost: metric?.cost ?? 0,
10066
+ cost_source: metric && metric.cost > 0 ? metric.hasEstimatedCost ? "estimated" : "recorded" : void 0,
10067
+ agentStats: [...metric?.agentStats.values() ?? []].sort((a, b) => b.sessions - a.sessions)
10068
+ };
10069
+ });
10070
+ }
9531
10071
  function executeSessionSearch(query, options, snapshot) {
9532
10072
  const merged = mergeSearchQueryOptions(query, options);
9533
10073
  if (!needsIndexedSearch(merged.text, merged.options)) {
@@ -9570,15 +10110,49 @@ function matchesSessionSearchFilters(agentName, session, options, projectScope =
9570
10110
  if (options.to != null && activity > options.to) return false;
9571
10111
  return matchesRecentSearchFilters(session, options, projectScope);
9572
10112
  }
10113
+ function sessionReferenceKey2(agentName, sessionId) {
10114
+ return `${agentName}\0${sessionId}`;
10115
+ }
10116
+ function filterSessionSearchCandidates(candidates, options) {
10117
+ const projectScope = options.cwd ? createProjectScopeMatcher(options.cwd) : null;
10118
+ const headMatches = candidates.filter(
10119
+ (candidate) => matchesSessionSearchFilters(
10120
+ candidate.reference.agentName,
10121
+ candidate.session,
10122
+ options,
10123
+ projectScope
10124
+ )
10125
+ );
10126
+ if (!options.file && !options.fileKind && !options.tools?.length) return headMatches;
10127
+ const indexedMatches = filterIndexedSessionReferences(
10128
+ headMatches.map((candidate) => ({
10129
+ agentName: candidate.reference.agentName,
10130
+ sessionId: candidate.reference.sessionId
10131
+ })),
10132
+ {
10133
+ file: options.file,
10134
+ fileKind: options.fileKind,
10135
+ tools: options.tools
10136
+ }
10137
+ );
10138
+ return headMatches.filter(
10139
+ (candidate) => indexedMatches.has(
10140
+ sessionReferenceKey2(candidate.reference.agentName, candidate.reference.sessionId)
10141
+ )
10142
+ );
10143
+ }
9573
10144
  function searchRecentSessions(snapshot, options) {
9574
10145
  const projectScope = options.cwd ? createProjectScopeMatcher(options.cwd) : null;
9575
10146
  const entries = options.agent ? [[options.agent, snapshot.byAgent[options.agent] ?? []]] : Object.entries(snapshot.byAgent);
9576
10147
  return entries.flatMap(
9577
- ([agentName, sessions]) => sessions.filter((session) => matchesSessionSearchFilters(agentName, session, options, projectScope)).map((session) => ({ agentName, session }))
10148
+ ([agentName, sessions]) => sessions.filter((session) => matchesSessionSearchFilters(agentName, session, options, projectScope)).map((session) => ({
10149
+ reference: { agentName, sessionId: session.id },
10150
+ session
10151
+ }))
9578
10152
  ).sort(
9579
10153
  (a, b) => (b.session.time_updated ?? b.session.time_created) - (a.session.time_updated ?? a.session.time_created)
9580
- ).slice(0, options.limit ?? 50).map(({ agentName, session }) => ({
9581
- agentName,
10154
+ ).slice(0, options.limit ?? 50).map(({ reference, session }) => ({
10155
+ reference,
9582
10156
  session,
9583
10157
  snippet: `Recent session \xB7 ${session.directory}`,
9584
10158
  matchType: "recent"
@@ -9591,7 +10165,7 @@ function mergeSearchResultSources(results, limit) {
9591
10165
  const seen = /* @__PURE__ */ new Set();
9592
10166
  const merged = [];
9593
10167
  for (const result of results) {
9594
- const key = `${result.agentName}/${result.session.id}`;
10168
+ const key = sessionReferenceKey2(result.reference.agentName, result.reference.sessionId);
9595
10169
  if (seen.has(key)) continue;
9596
10170
  seen.add(key);
9597
10171
  merged.push(result);
@@ -9599,133 +10173,83 @@ function mergeSearchResultSources(results, limit) {
9599
10173
  }
9600
10174
  return merged;
9601
10175
  }
9602
- function canSkipSessionsSearch(fileQuery, textQuery, options) {
9603
- return Boolean(
9604
- fileQuery && !textQuery && !options.tools?.length && !options.tags?.length && options.from == null && options.to == null
9605
- );
10176
+ function canSkipSessionsSearch(fileQuery, textQuery) {
10177
+ return Boolean(fileQuery && !textQuery);
9606
10178
  }
9607
10179
  function searchIndexedSessions(query, textQuery, parsed, options) {
9608
10180
  const fileQuery = deriveFileQuery(query, parsed, options);
9609
10181
  const fileResults = fileQuery ? searchFileActivitySessions(fileQuery, options) : [];
9610
- const sessionResults = canSkipSessionsSearch(fileQuery, textQuery, options) ? [] : searchSessions(query, options);
9611
- return mergeSearchResultSources([...fileResults, ...sessionResults], options.limit ?? 50);
10182
+ const sessionResults = canSkipSessionsSearch(fileQuery, textQuery) ? [] : searchSessions(query, options);
10183
+ const textMatchReferences = textQuery && options.file ? new Set(
10184
+ sessionResults.map(
10185
+ (result) => sessionReferenceKey2(result.reference.agentName, result.reference.sessionId)
10186
+ )
10187
+ ) : null;
10188
+ const matchingFileResults = textMatchReferences ? fileResults.filter(
10189
+ (result) => textMatchReferences.has(
10190
+ sessionReferenceKey2(result.reference.agentName, result.reference.sessionId)
10191
+ )
10192
+ ) : fileResults;
10193
+ return mergeSearchResultSources([...matchingFileResults, ...sessionResults], options.limit ?? 50);
9612
10194
  }
9613
10195
 
9614
10196
  export {
10197
+ normalizeSessionReference,
10198
+ formatSessionReference,
10199
+ getSessionAgentKey,
10200
+ mergeSortedSessions,
9615
10201
  registerAgent,
9616
10202
  createRegisteredAgents,
9617
10203
  getRegisteredAgents,
9618
10204
  getAgentInfoMap,
9619
- getAgentByName,
9620
10205
  setCoreDiagnostics,
9621
- parsedSession,
9622
- skippedSession,
9623
- filteredSession,
9624
- getParsedSession,
9625
- matchesScanWindow,
10206
+ diffSessionSources,
9626
10207
  BaseAgent,
9627
10208
  FileSystemSessionSource,
9628
- DatabaseSessionSource,
9629
- firstExisting,
9630
- resolveProviderRoots,
9631
- getCursorDataPath,
9632
- parseJsonlLines,
9633
- readJsonlFile,
9634
- cleanDisplayText,
9635
- firstVisibleLine,
9636
- normalizeTitleText,
9637
- basenameTitle,
9638
- resolveSessionTitle,
9639
- cleanInternalText,
9640
- cleanMessagePart,
9641
- cleanMessageParts,
9642
- cleanParsedMessage,
9643
- cleanParsedMessages,
9644
- firstUserMessageTitle,
9645
- getPricingRegistry,
9646
- hasBillablePricing,
9647
10209
  refreshPricingCache,
9648
- pricingResolver,
9649
- estimateCostForTokens,
9650
- applyMessageCost,
9651
- applyMessageCosts,
9652
- withEstimatedSessionCost,
9653
- estimateTokenCost,
9654
- asRecord,
9655
- asString,
9656
- asNumber,
9657
- asArray,
9658
- reportFieldMismatch,
9659
- openDbReadOnly,
9660
- openDb,
9661
- isSqliteAvailable,
9662
10210
  perf,
9663
- fallbackDisplayName,
9664
- realFs,
9665
10211
  isProjectIdentityKind,
9666
- getProjectIdentityKey,
9667
10212
  matchesProjectIdentity,
9668
- normalizeGitRemote,
9669
- clearIdentityCache,
9670
- computeIdentity,
9671
- buildProjectGroups,
9672
10213
  createProjectScopeMatcher,
9673
10214
  matchesProjectScope,
9674
- filterSessionsByProjectScope,
9675
10215
  getSmartTagSourceTimestamp,
9676
10216
  classifySessionTags,
9677
- extractFileActivityOccurrences,
9678
- summarizeFileActivity,
9679
- extractSessionFileActivity,
9680
- setFtsIntegrityCheckedPath,
9681
10217
  getCachePath2,
9682
- parseSearchQuery,
9683
10218
  syncSessionSearchIndex,
9684
10219
  syncSessionSearchIndexChanges,
9685
10220
  mergeSearchQueryOptions,
9686
- searchSessions,
9687
10221
  listFileActivity,
9688
- listSessionFileActivity,
9689
- searchFileActivitySessions,
9690
10222
  loadCachedSessions,
9691
10223
  isAgentCacheInitialized,
9692
10224
  markAgentCacheInitialized,
9693
10225
  getAgentLastFullSyncAt,
9694
10226
  markAgentFullSyncCompleted,
9695
- loadCachedSessionDataEntry,
9696
- loadCachedSessionData,
9697
10227
  saveCachedSessions,
9698
10228
  saveCachedSessionChanges,
9699
10229
  clearCache,
9700
- getCacheInfo,
9701
- listCachedProjectGroups,
9702
10230
  attachMissingProjectIdentities,
9703
10231
  buildAgentCacheMeta,
9704
10232
  sessionSignature,
9705
10233
  sortSessions,
9706
10234
  computeSessionDiff,
9707
- filterSessions,
9708
10235
  ensureSessionTagsSync,
9709
10236
  scanSessions,
9710
- scanSessionsAsync,
10237
+ materializeSessionDetailResponse,
10238
+ listCachedProjectGroups,
9711
10239
  StateStorageUnavailableError,
9712
10240
  listBookmarks,
9713
10241
  upsertBookmark,
9714
10242
  importBookmarks,
9715
10243
  deleteBookmark,
9716
- SESSION_ALIAS_MAX_LENGTH,
9717
- normalizeSessionAlias,
10244
+ SessionAliasValidationError,
9718
10245
  listSessionAliases,
9719
10246
  upsertSessionAlias,
9720
10247
  deleteSessionAlias,
9721
- DASHBOARD_RECENT_LIMIT,
9722
- getTotalTokens,
9723
- getSessionAgentName,
9724
10248
  getSessionActivityTime,
9725
- toLocalDateKey,
9726
10249
  startOfLocalDay,
9727
10250
  buildDashboard,
10251
+ attachProjectMetrics,
9728
10252
  executeSessionSearch,
9729
- matchesSessionSearchFilters
10253
+ filterSessionSearchCandidates
9730
10254
  };
9731
- //# sourceMappingURL=chunk-MWSJTNOW.js.map
10255
+ //# sourceMappingURL=chunk-7APNDHQ6.js.map