codesesh 0.4.0 → 0.5.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,31 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../core/dist/index.mjs
4
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
5
- import { join as join2, basename as basename2, dirname } from "path";
4
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
5
+ import { join as join3, basename as basename2, dirname } from "path";
6
6
  import { existsSync } from "fs";
7
7
  import { homedir, platform } from "os";
8
8
  import { join } from "path";
9
9
  import { readFileSync } from "fs";
10
10
  import { basename } from "path";
11
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
12
- import { join as join3 } from "path";
13
- import { mkdirSync } from "fs";
11
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
12
+ import { homedir as homedir2 } from "os";
13
+ import { join as join2 } from "path";
14
+ import { existsSync as existsSync4, statSync as statSync2 } from "fs";
15
+ import { join as join4 } from "path";
16
+ import { mkdirSync as mkdirSync2 } from "fs";
14
17
  import { dirname as dirname2 } from "path";
15
18
  import { createRequire } from "module";
16
19
  import { createHash } from "crypto";
17
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
18
- import { join as join4, basename as basename3, dirname as dirname3 } from "path";
19
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
20
- import { join as join5, basename as basename4 } from "path";
21
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
22
- import { join as join6, normalize } from "path";
20
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
21
+ import { join as join5, basename as basename3, dirname as dirname3 } from "path";
22
+ import {
23
+ closeSync,
24
+ existsSync as existsSync6,
25
+ openSync,
26
+ readFileSync as readFileSync5,
27
+ readSync,
28
+ readdirSync as readdirSync3,
29
+ statSync as statSync4
30
+ } from "fs";
31
+ import { join as join6, basename as basename4 } from "path";
32
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
33
+ import { join as join7, normalize } from "path";
23
34
  import { resolve, sep } from "path";
24
- import { existsSync as existsSync7, rmSync, unlinkSync } from "fs";
25
- import { join as join7 } from "path";
26
- import { homedir as homedir2 } from "os";
27
- import { homedir as homedir3, platform as platform2 } from "os";
35
+ import { availableParallelism } from "os";
36
+ import { Worker } from "worker_threads";
37
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
38
+ import { spawnSync } from "child_process";
39
+ import { homedir as homedir3 } from "os";
40
+ import * as path from "path";
41
+ import { existsSync as existsSync9, rmSync, unlinkSync } from "fs";
28
42
  import { join as join8 } from "path";
43
+ import { homedir as homedir4 } from "os";
44
+ import { homedir as homedir5, platform as platform2 } from "os";
45
+ import { join as join9 } from "path";
29
46
  var registrations = [];
30
47
  function registerAgent(reg) {
31
48
  registrations.push(reg);
@@ -47,6 +64,11 @@ function getAgentInfoMap(sessionsByAgent) {
47
64
  function getAgentByName(name) {
48
65
  return registrations.find((r) => r.name === name);
49
66
  }
67
+ function matchesScanWindow(activityTime, options) {
68
+ if (options?.from != null && activityTime < options.from) return false;
69
+ if (options?.to != null && activityTime > options.to) return false;
70
+ return true;
71
+ }
50
72
  var BaseAgent = class {
51
73
  getUri(sessionId) {
52
74
  return `${this.name}://${sessionId}`;
@@ -119,9 +141,9 @@ function normalizeTitleText(text) {
119
141
  if (!cleaned) return null;
120
142
  return cleaned.slice(0, TITLE_MAX_LENGTH);
121
143
  }
122
- function basenameTitle(path) {
123
- if (!path) return null;
124
- const normalized = path.trim().replace(/[/\\]+$/, "");
144
+ function basenameTitle(path2) {
145
+ if (!path2) return null;
146
+ const normalized = path2.trim().replace(/[/\\]+$/, "");
125
147
  if (!normalized) return null;
126
148
  const name = basename(normalized).trim();
127
149
  return name || null;
@@ -209,6 +231,329 @@ var PerfTracer = class {
209
231
  }
210
232
  };
211
233
  var perf = new PerfTracer();
234
+ var aliases_default = {
235
+ "anthropic--claude-4.6-opus": "claude-opus-4-6",
236
+ "anthropic--claude-4.6-sonnet": "claude-sonnet-4-6",
237
+ "claude-4.6-sonnet": "claude-sonnet-4-6",
238
+ "anthropic--claude-4.5-opus": "claude-opus-4-5",
239
+ "anthropic--claude-4.5-sonnet": "claude-sonnet-4-5",
240
+ "anthropic--claude-4.5-haiku": "claude-haiku-4-5",
241
+ "claude-4.5-sonnet-thinking": "claude-sonnet-4-5",
242
+ "claude-4-sonnet-thinking": "claude-sonnet-4-5",
243
+ "claude-4-opus": "claude-opus-4-5",
244
+ "cursor-auto": "claude-sonnet-4-5",
245
+ "cursor-agent-auto": "claude-sonnet-4-5",
246
+ "copilot-auto": "claude-sonnet-4-5",
247
+ "copilot-openai-auto": "gpt-5.3-codex",
248
+ "copilot-anthropic-auto": "claude-sonnet-4-5",
249
+ "kimi-for-coding": "moonshotai/kimi-k2-0905",
250
+ "kimi-k2.5": "moonshotai/kimi-k2-0905",
251
+ "moonshot-cn/kimi-k2.5": "moonshotai/kimi-k2-0905",
252
+ "gpt-5-codex": "gpt-5",
253
+ "gpt-5.1-codex-high": "gpt-5.3-codex",
254
+ "gpt-5.2-low": "gpt-5.2"
255
+ };
256
+ var snapshot_default = {
257
+ "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
258
+ "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
259
+ "claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
260
+ "claude-haiku-4-5": [1e-6, 5e-6, 125e-8, 1e-7],
261
+ "claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
262
+ "claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
263
+ "claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
264
+ "claude-opus-4": [15e-6, 75e-6, 1875e-8, 15e-7],
265
+ "claude-opus-4-1": [15e-6, 75e-6, 1875e-8, 15e-7],
266
+ "claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
267
+ "claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
268
+ "claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
269
+ "anthropic/claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
270
+ "anthropic/claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
271
+ "anthropic/claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
272
+ "gpt-4.1": [2e-6, 8e-6, null, 5e-7],
273
+ "gpt-4.1-mini": [4e-7, 16e-7, null, 1e-7],
274
+ "gpt-4.1-nano": [1e-7, 4e-7, null, 25e-9],
275
+ "gpt-4o": [25e-7, 1e-5, null, 125e-8],
276
+ "gpt-4o-mini": [15e-8, 6e-7, null, 75e-9],
277
+ "gpt-5": [125e-8, 1e-5, null, 125e-9],
278
+ "gpt-5.1": [125e-8, 1e-5, null, 125e-9],
279
+ "gpt-5.1-codex": [125e-8, 1e-5, null, 125e-9],
280
+ "gpt-5.1-codex-mini": [25e-8, 2e-6, null, 25e-9],
281
+ "gpt-5.2": [175e-8, 14e-6, null, 175e-9],
282
+ "gpt-5.2-codex": [175e-8, 14e-6, null, 175e-9],
283
+ "gpt-5.3-codex": [175e-8, 14e-6, null, 175e-9],
284
+ "gpt-5.4": [25e-7, 15e-6, null, 25e-8],
285
+ "gpt-5.4-mini": [75e-8, 45e-7, null, 75e-9],
286
+ "gpt-5.4-nano": [2e-7, 125e-8, null, 2e-8],
287
+ "gpt-5.5": [5e-6, 3e-5, null, 5e-7],
288
+ "gpt-5-mini": [25e-8, 2e-6, null, 25e-9],
289
+ "gpt-5-nano": [5e-8, 4e-7, null, 5e-9],
290
+ "moonshotai/kimi-k2-0905": [6e-7, 25e-7, null, null],
291
+ "moonshotai/kimi-k2-instruct": [57e-8, 23e-7, null, null],
292
+ "moonshotai/kimi-k2-thinking": [6e-7, 25e-7, null, null],
293
+ "deepseek-chat": [28e-8, 42e-8, null, 28e-9],
294
+ "deepseek-reasoner": [28e-8, 42e-8, null, 28e-9],
295
+ "qwen3-coder-480b-a35b-instruct": [3e-7, 13e-7, null, null],
296
+ "gemini-2.5-pro": [125e-8, 1e-5, null, 125e-9],
297
+ "gemini-2.5-flash": [3e-7, 25e-7, null, 3e-8]
298
+ };
299
+ var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
300
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
301
+ var WEB_SEARCH_COST = 0.01;
302
+ var pricingCache = loadSnapshot();
303
+ loadDiskCache();
304
+ function normalizeKey(key) {
305
+ return key.trim().toLowerCase();
306
+ }
307
+ function costNumber(value, fallback) {
308
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
309
+ }
310
+ function getCacheDir() {
311
+ return join2(homedir2(), ".cache", "codesesh");
312
+ }
313
+ function getCachePath() {
314
+ return join2(getCacheDir(), "litellm-pricing.json");
315
+ }
316
+ function loadSnapshot() {
317
+ const map = /* @__PURE__ */ new Map();
318
+ const snapshot = snapshot_default;
319
+ for (const [name, entry] of Object.entries(snapshot)) {
320
+ const [input, output, cacheCreate, cacheRead, reasoning, webSearch] = entry;
321
+ map.set(normalizeKey(name), {
322
+ inputCostPerToken: input,
323
+ outputCostPerToken: output,
324
+ cacheCreateCostPerToken: cacheCreate ?? input * 1.25,
325
+ cacheReadCostPerToken: cacheRead ?? input * 0.1,
326
+ reasoningCostPerToken: reasoning ?? output,
327
+ webSearchCostPerRequest: webSearch ?? WEB_SEARCH_COST
328
+ });
329
+ }
330
+ return map;
331
+ }
332
+ function parseLiteLLMEntry(entry) {
333
+ const input = entry.input_cost_per_token;
334
+ const output = entry.output_cost_per_token;
335
+ if (typeof input !== "number" || typeof output !== "number") return null;
336
+ return {
337
+ inputCostPerToken: input,
338
+ outputCostPerToken: output,
339
+ cacheCreateCostPerToken: entry.cache_creation_input_token_cost ?? input * 1.25,
340
+ cacheReadCostPerToken: entry.cache_read_input_token_cost ?? input * 0.1,
341
+ reasoningCostPerToken: entry.output_reasoning_cost_per_token ?? output,
342
+ webSearchCostPerRequest: costNumber(
343
+ entry.web_search_cost_per_request ?? entry.search_context_cost_per_query,
344
+ WEB_SEARCH_COST
345
+ )
346
+ };
347
+ }
348
+ function normalizeCachedPricing(raw) {
349
+ const input = costNumber(raw["inputCostPerToken"], 0);
350
+ const output = costNumber(raw["outputCostPerToken"], 0);
351
+ if (input <= 0 || output <= 0) return null;
352
+ return {
353
+ inputCostPerToken: input,
354
+ outputCostPerToken: output,
355
+ cacheCreateCostPerToken: costNumber(raw["cacheCreateCostPerToken"], input * 1.25),
356
+ cacheReadCostPerToken: costNumber(raw["cacheReadCostPerToken"], input * 0.1),
357
+ reasoningCostPerToken: costNumber(raw["reasoningCostPerToken"], output),
358
+ webSearchCostPerRequest: costNumber(raw["webSearchCostPerRequest"], WEB_SEARCH_COST)
359
+ };
360
+ }
361
+ function indexPricing(map, name, pricing) {
362
+ const normalized = normalizeKey(name);
363
+ map.set(normalized, pricing);
364
+ const slashIndex = normalized.indexOf("/");
365
+ if (slashIndex >= 0) {
366
+ const stripped = normalized.slice(slashIndex + 1);
367
+ if (!map.has(stripped)) map.set(stripped, pricing);
368
+ }
369
+ }
370
+ function parseLiteLLMData(data) {
371
+ const map = /* @__PURE__ */ new Map();
372
+ for (const [name, entry] of Object.entries(data)) {
373
+ const pricing = parseLiteLLMEntry(entry);
374
+ if (pricing) indexPricing(map, name, pricing);
375
+ }
376
+ return map;
377
+ }
378
+ function loadDiskCache() {
379
+ const path2 = getCachePath();
380
+ if (!existsSync2(path2)) return;
381
+ try {
382
+ const cached = JSON.parse(readFileSync2(path2, "utf-8"));
383
+ if (Date.now() - cached.timestamp <= CACHE_TTL_MS) {
384
+ const next = loadSnapshot();
385
+ for (const [name, rawPricing] of Object.entries(cached.data)) {
386
+ const pricing = normalizeCachedPricing(rawPricing);
387
+ if (!pricing) continue;
388
+ next.set(normalizeKey(name), pricing);
389
+ }
390
+ pricingCache = next;
391
+ }
392
+ } catch {
393
+ }
394
+ }
395
+ function getPricingRegistry() {
396
+ return pricingCache;
397
+ }
398
+ function hasBillablePricing(pricing) {
399
+ return pricing.inputCostPerToken > 0 || pricing.outputCostPerToken > 0 || pricing.cacheReadCostPerToken > 0 || pricing.cacheCreateCostPerToken > 0;
400
+ }
401
+ async function refreshPricingCache() {
402
+ const path2 = getCachePath();
403
+ if (existsSync2(path2)) {
404
+ try {
405
+ const cached = JSON.parse(readFileSync2(path2, "utf-8"));
406
+ if (typeof cached.timestamp === "number" && Date.now() - cached.timestamp <= CACHE_TTL_MS) {
407
+ return false;
408
+ }
409
+ } catch {
410
+ }
411
+ }
412
+ try {
413
+ const response = await fetch(LITELLM_URL);
414
+ if (!response.ok) return false;
415
+ const data = await response.json();
416
+ const remote = parseLiteLLMData(data);
417
+ if (remote.size === 0) return false;
418
+ const next = loadSnapshot();
419
+ for (const [name, pricing] of remote.entries()) {
420
+ next.set(name, pricing);
421
+ }
422
+ pricingCache = next;
423
+ mkdirSync(getCacheDir(), { recursive: true });
424
+ writeFileSync(path2, JSON.stringify({ timestamp: Date.now(), data: Object.fromEntries(next) }));
425
+ return true;
426
+ } catch {
427
+ return false;
428
+ }
429
+ }
430
+ var BUILTIN_ALIASES = Object.fromEntries(
431
+ Object.entries(aliases_default).map(([key, value]) => [
432
+ normalizeModelKey(key),
433
+ normalizeModelKey(value)
434
+ ])
435
+ );
436
+ function normalizeModelKey(model) {
437
+ return model.trim().toLowerCase().replaceAll("_", "-");
438
+ }
439
+ function stripVersion(model) {
440
+ return model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
441
+ }
442
+ function stripProviderPrefix(model) {
443
+ const parts = model.split("/");
444
+ return parts[parts.length - 1] ?? model;
445
+ }
446
+ function resolveAlias(model) {
447
+ return BUILTIN_ALIASES[model] ?? model;
448
+ }
449
+ function getCandidates(rawModel) {
450
+ const withPrefix = stripVersion(normalizeModelKey(rawModel));
451
+ const stripped = stripProviderPrefix(withPrefix);
452
+ const aliased = resolveAlias(stripped);
453
+ const candidates = [
454
+ withPrefix,
455
+ resolveAlias(withPrefix),
456
+ stripped,
457
+ aliased,
458
+ `anthropic/${stripped}`,
459
+ `openai/${stripped}`,
460
+ `openrouter/openai/${stripped}`,
461
+ `openrouter/anthropic/${stripped}`,
462
+ `moonshotai/${stripped}`,
463
+ `novita/moonshotai/${stripped}`
464
+ ];
465
+ return [...new Set(candidates.filter(Boolean))];
466
+ }
467
+ function lookupCandidate(model, registry) {
468
+ const direct = registry.get(model);
469
+ if (direct && hasBillablePricing(direct)) return direct;
470
+ const alias = resolveAlias(model);
471
+ if (alias !== model) {
472
+ const aliased = registry.get(alias);
473
+ if (aliased && hasBillablePricing(aliased)) return aliased;
474
+ }
475
+ return null;
476
+ }
477
+ function fuzzyLookup(model, registry) {
478
+ let best = null;
479
+ for (const [key, pricing] of registry.entries()) {
480
+ if (!hasBillablePricing(pricing)) continue;
481
+ if (model.startsWith(`${key}-`) || model.startsWith(`${key}@`) || model === key) {
482
+ if (!best || key.length > best[0].length) best = [key, pricing];
483
+ }
484
+ }
485
+ return best?.[1] ?? null;
486
+ }
487
+ var pricingResolver = {
488
+ resolve(rawModelName) {
489
+ const registry = getPricingRegistry();
490
+ const candidates = getCandidates(rawModelName);
491
+ for (const candidate of candidates) {
492
+ const pricing = lookupCandidate(candidate, registry);
493
+ if (pricing) return pricing;
494
+ }
495
+ for (const candidate of candidates) {
496
+ const pricing = fuzzyLookup(candidate, registry);
497
+ if (pricing) return pricing;
498
+ }
499
+ return null;
500
+ }
501
+ };
502
+ function positive(value) {
503
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
504
+ }
505
+ function estimateCostForTokens(model, usage) {
506
+ if (!model || !usage) return null;
507
+ const pricing = pricingResolver.resolve(model);
508
+ if (!pricing) return null;
509
+ const cacheRead = positive(usage.cache_read);
510
+ const cacheCreate = positive(usage.cache_create);
511
+ const input = Math.max(0, positive(usage.input) - cacheRead - cacheCreate);
512
+ const output = positive(usage.output);
513
+ const reasoning = positive(usage.reasoning);
514
+ const webSearchRequests = positive(usage.web_search_requests);
515
+ const cost = input * pricing.inputCostPerToken + output * pricing.outputCostPerToken + reasoning * pricing.reasoningCostPerToken + cacheRead * pricing.cacheReadCostPerToken + cacheCreate * pricing.cacheCreateCostPerToken + webSearchRequests * pricing.webSearchCostPerRequest;
516
+ return cost > 0 ? { cost: Number(cost.toFixed(8)), source: "estimated" } : null;
517
+ }
518
+ function applyMessageCost(message) {
519
+ if ((message.cost ?? 0) > 0) {
520
+ message.cost_source = "recorded";
521
+ return;
522
+ }
523
+ const estimate = estimateCostForTokens(message.model, message.tokens);
524
+ if (!estimate) return;
525
+ message.cost = estimate.cost;
526
+ message.cost_source = estimate.source;
527
+ }
528
+ function applyMessageCosts(messages) {
529
+ let totalCost = 0;
530
+ let source;
531
+ for (const message of messages) {
532
+ applyMessageCost(message);
533
+ const cost = message.cost ?? 0;
534
+ if (cost <= 0) continue;
535
+ totalCost += cost;
536
+ if (message.cost_source === "estimated") source = "estimated";
537
+ else if (!source) source = "recorded";
538
+ }
539
+ return { totalCost: Number(totalCost.toFixed(8)), source };
540
+ }
541
+ function withEstimatedSessionCost(stats, model) {
542
+ if (stats.total_cost > 0) {
543
+ return { ...stats, cost_source: stats.cost_source ?? "recorded" };
544
+ }
545
+ const estimate = estimateCostForTokens(model, {
546
+ input: stats.total_input_tokens,
547
+ output: stats.total_output_tokens,
548
+ cache_read: stats.total_cache_read_tokens,
549
+ cache_create: stats.total_cache_create_tokens
550
+ });
551
+ if (!estimate) return stats;
552
+ return { ...stats, total_cost: estimate.cost, cost_source: estimate.source };
553
+ }
554
+ function estimateTokenCost(model, tokens) {
555
+ return estimateCostForTokens(model, tokens)?.cost ?? null;
556
+ }
212
557
  var RECENT_SESSION_REVALIDATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
213
558
  function parseTimestampMs(data) {
214
559
  const raw = String(data["timestamp"] ?? "").trim();
@@ -232,15 +577,15 @@ var ClaudeCodeAgent = class extends BaseAgent {
232
577
  sessionMetaMap = /* @__PURE__ */ new Map();
233
578
  findBasePath() {
234
579
  const roots = resolveProviderRoots();
235
- return firstExisting(join2(roots.claudeRoot, "projects"), "data/claudecode");
580
+ return firstExisting(join3(roots.claudeRoot, "projects"), "data/claudecode");
236
581
  }
237
582
  isAvailable() {
238
583
  this.basePath = this.findBasePath();
239
584
  if (!this.basePath) return false;
240
585
  try {
241
586
  for (const entry of readdirSync(this.basePath)) {
242
- const dir = join2(this.basePath, entry);
243
- if (existsSync2(dir) && readdirSync(dir).some((f) => f.endsWith(".jsonl"))) {
587
+ const dir = join3(this.basePath, entry);
588
+ if (existsSync3(dir) && readdirSync(dir).some((f) => f.endsWith(".jsonl"))) {
244
589
  return true;
245
590
  }
246
591
  }
@@ -248,7 +593,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
248
593
  }
249
594
  return false;
250
595
  }
251
- scan() {
596
+ scan(options) {
252
597
  if (!this.basePath) return [];
253
598
  const scanMarker = perf.start("claudecode:scan");
254
599
  const heads = [];
@@ -261,6 +606,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
261
606
  perf.end(fileMarker);
262
607
  for (const file of files) {
263
608
  try {
609
+ if (!matchesScanWindow(statSync(file).mtimeMs, options)) continue;
264
610
  const parseMarker = perf.start(`parseSessionHead:${basename2(file)}`);
265
611
  const head = this.parseSessionHead(file, projectDir);
266
612
  perf.end(parseMarker);
@@ -295,10 +641,10 @@ var ClaudeCodeAgent = class extends BaseAgent {
295
641
  if (!meta) {
296
642
  throw new Error(`Session not found: ${sessionId}`);
297
643
  }
298
- if (!existsSync2(meta.sourcePath)) {
644
+ if (!existsSync3(meta.sourcePath)) {
299
645
  throw new Error(`Session file missing: ${meta.sourcePath}`);
300
646
  }
301
- const content = readFileSync2(meta.sourcePath, "utf-8");
647
+ const content = readFileSync3(meta.sourcePath, "utf-8");
302
648
  const messages = [];
303
649
  const pendingToolCalls = /* @__PURE__ */ new Map();
304
650
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -345,6 +691,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
345
691
  total_input_tokens: totalInputTokens,
346
692
  total_output_tokens: totalOutputTokens,
347
693
  total_cost: totalCost,
694
+ cost_source: totalCost > 0 ? "estimated" : void 0,
348
695
  total_cache_read_tokens: totalCacheRead,
349
696
  total_cache_create_tokens: totalCacheCreate
350
697
  },
@@ -464,14 +811,14 @@ var ClaudeCodeAgent = class extends BaseAgent {
464
811
  listProjectDirs() {
465
812
  if (!this.basePath) return [];
466
813
  try {
467
- return readdirSync(this.basePath).map((e) => join2(this.basePath, e)).filter((p) => existsSync2(p));
814
+ return readdirSync(this.basePath).map((e) => join3(this.basePath, e)).filter((p) => existsSync3(p));
468
815
  } catch {
469
816
  return [];
470
817
  }
471
818
  }
472
819
  listJsonlFiles(dir) {
473
820
  try {
474
- return readdirSync(dir).filter((f) => f.endsWith(".jsonl") && f !== "sessions-index.json").map((f) => join2(dir, f));
821
+ return readdirSync(dir).filter((f) => f.endsWith(".jsonl") && f !== "sessions-index.json").map((f) => join3(dir, f));
475
822
  } catch {
476
823
  return [];
477
824
  }
@@ -481,11 +828,11 @@ var ClaudeCodeAgent = class extends BaseAgent {
481
828
  if (cacheKey in this.sessionsIndexCache) {
482
829
  return this.sessionsIndexCache[cacheKey];
483
830
  }
484
- const indexPath = join2(projectDir, "sessions-index.json");
831
+ const indexPath = join3(projectDir, "sessions-index.json");
485
832
  const map = /* @__PURE__ */ new Map();
486
- if (existsSync2(indexPath)) {
833
+ if (existsSync3(indexPath)) {
487
834
  try {
488
- const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
835
+ const data = JSON.parse(readFileSync3(indexPath, "utf-8"));
489
836
  const entries = data?.entries ?? [];
490
837
  for (const entry of entries) {
491
838
  const sid = entry?.sessionId;
@@ -500,7 +847,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
500
847
  return map;
501
848
  }
502
849
  parseSessionHead(filePath, projectDir) {
503
- const content = readFileSync2(filePath, "utf-8");
850
+ const content = readFileSync3(filePath, "utf-8");
504
851
  const lines = content.split("\n").filter((l) => l.trim());
505
852
  if (lines.length === 0) return null;
506
853
  const sessionId = basename2(filePath, ".jsonl");
@@ -522,6 +869,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
522
869
  let totalOutputTokens = 0;
523
870
  let totalCacheReadTokens = 0;
524
871
  let totalCacheCreateTokens = 0;
872
+ let totalCost = 0;
525
873
  const modelUsageMap = {};
526
874
  for (const line of lines) {
527
875
  try {
@@ -557,6 +905,13 @@ var ClaudeCodeAgent = class extends BaseAgent {
557
905
  const name = m.trim();
558
906
  const msgTotal = inputTokens + cacheRead + cacheCreate + outputTokens;
559
907
  modelUsageMap[name] = (modelUsageMap[name] ?? 0) + msgTotal;
908
+ const cost = estimateTokenCost(name, {
909
+ input: inputTokens + cacheRead + cacheCreate,
910
+ output: outputTokens,
911
+ cache_read: cacheRead,
912
+ cache_create: cacheCreate
913
+ });
914
+ if (cost !== null) totalCost += cost;
560
915
  }
561
916
  }
562
917
  }
@@ -580,7 +935,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
580
935
  message_count: messageCount,
581
936
  total_input_tokens: totalInputTokens,
582
937
  total_output_tokens: totalOutputTokens,
583
- total_cost: 0,
938
+ total_cost: totalCost,
939
+ cost_source: totalCost > 0 ? "estimated" : void 0,
584
940
  total_cache_read_tokens: totalCacheReadTokens,
585
941
  total_cache_create_tokens: totalCacheCreateTokens
586
942
  },
@@ -781,6 +1137,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
781
1137
  provider: opts.provider ?? null,
782
1138
  tokens: opts.tokens ? opts.tokens : void 0,
783
1139
  cost: opts.cost ?? 0,
1140
+ cost_source: opts.cost_source,
784
1141
  parts: opts.parts
785
1142
  };
786
1143
  }
@@ -820,6 +1177,11 @@ var ClaudeCodeAgent = class extends BaseAgent {
820
1177
  cache_read: cacheRead,
821
1178
  cache_create: cacheCreate
822
1179
  };
1180
+ const cost = estimateTokenCost(message.model, message.tokens);
1181
+ if (cost !== null) {
1182
+ message.cost = cost;
1183
+ message.cost_source = "estimated";
1184
+ }
823
1185
  }
824
1186
  }
825
1187
  // --- Assistant message grouping ---
@@ -1018,7 +1380,7 @@ function openDbReadOnly(dbPath) {
1018
1380
  function openDb(dbPath) {
1019
1381
  if (!DatabaseConstructor) return null;
1020
1382
  try {
1021
- mkdirSync(dirname2(dbPath), { recursive: true });
1383
+ mkdirSync2(dirname2(dbPath), { recursive: true });
1022
1384
  const db = DatabaseConstructor(dbPath);
1023
1385
  db.pragma("journal_mode = WAL");
1024
1386
  db.pragma("synchronous = NORMAL");
@@ -1040,18 +1402,18 @@ var OpenCodeAgent = class extends BaseAgent {
1040
1402
  findDbPath() {
1041
1403
  if (!isSqliteAvailable()) return null;
1042
1404
  const roots = resolveProviderRoots();
1043
- return firstExisting(join3(roots.opencodeRoot, "opencode.db"), "data/opencode/opencode.db");
1405
+ return firstExisting(join4(roots.opencodeRoot, "opencode.db"), "data/opencode/opencode.db");
1044
1406
  }
1045
1407
  isAvailable() {
1046
1408
  this.dbPath = this.findDbPath();
1047
1409
  return this.dbPath !== null;
1048
1410
  }
1049
- scan() {
1411
+ scan(options) {
1050
1412
  if (!this.dbPath) return [];
1051
1413
  const db = openDbReadOnly(this.dbPath);
1052
1414
  if (!db) return [];
1053
1415
  try {
1054
- const cutoffTime = Date.now() - 3650 * 24 * 60 * 60 * 1e3;
1416
+ const cutoffTime = options?.from ?? Date.now() - 3650 * 24 * 60 * 60 * 1e3;
1055
1417
  const hasMessageTable = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get();
1056
1418
  let rows;
1057
1419
  if (hasMessageTable) {
@@ -1064,7 +1426,7 @@ var OpenCodeAgent = class extends BaseAgent {
1064
1426
  WHERE m.session_id = s.id AND m.data LIKE '%"modelID"%'
1065
1427
  ORDER BY m.time_created DESC LIMIT 1) AS model_message_data
1066
1428
  FROM session s
1067
- WHERE s.time_created >= ?
1429
+ WHERE COALESCE(s.time_updated, s.time_created) >= ?
1068
1430
  ORDER BY s.time_created DESC
1069
1431
  `).all(cutoffTime);
1070
1432
  } else {
@@ -1072,7 +1434,7 @@ var OpenCodeAgent = class extends BaseAgent {
1072
1434
  SELECT s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
1073
1435
  s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data
1074
1436
  FROM session s
1075
- WHERE s.time_created >= ?
1437
+ WHERE COALESCE(s.time_updated, s.time_created) >= ?
1076
1438
  ORDER BY s.time_created DESC
1077
1439
  `).all(cutoffTime);
1078
1440
  }
@@ -1096,7 +1458,8 @@ var OpenCodeAgent = class extends BaseAgent {
1096
1458
  message_count: stats?.message_count ?? Number(row.message_count ?? 0),
1097
1459
  total_input_tokens: stats?.total_input_tokens ?? 0,
1098
1460
  total_output_tokens: stats?.total_output_tokens ?? 0,
1099
- total_cost: stats?.total_cost ?? 0
1461
+ total_cost: stats?.total_cost ?? 0,
1462
+ cost_source: stats?.cost_source
1100
1463
  }
1101
1464
  });
1102
1465
  if (this.dbPath) {
@@ -1127,7 +1490,7 @@ var OpenCodeAgent = class extends BaseAgent {
1127
1490
  if (!this.dbPath) {
1128
1491
  this.dbPath = this.findDbPath();
1129
1492
  }
1130
- if (!this.dbPath || !existsSync3(this.dbPath)) {
1493
+ if (!this.dbPath || !existsSync4(this.dbPath)) {
1131
1494
  return { hasChanges: false, timestamp: Date.now() };
1132
1495
  }
1133
1496
  try {
@@ -1155,19 +1518,26 @@ var OpenCodeAgent = class extends BaseAgent {
1155
1518
  let totalCost = 0;
1156
1519
  let totalInputTokens = 0;
1157
1520
  let totalOutputTokens = 0;
1521
+ let hasEstimatedCost = false;
1158
1522
  for (const row of rows) {
1159
1523
  const msgData = JSON.parse(String(row.data ?? "{}"));
1160
1524
  const cost = Number(msgData.cost ?? 0);
1161
1525
  const tokens = msgData.tokens;
1162
- totalCost += cost;
1163
- totalInputTokens += Number(tokens?.input ?? 0);
1164
- totalOutputTokens += Number(tokens?.output ?? 0);
1526
+ const inputTokens = Number(tokens?.input ?? 0);
1527
+ const outputTokens = Number(tokens?.output ?? 0);
1528
+ const model = msgData.modelID ?? null;
1529
+ const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
1530
+ if (estimatedCost !== null) hasEstimatedCost = true;
1531
+ totalCost += cost || estimatedCost || 0;
1532
+ totalInputTokens += inputTokens;
1533
+ totalOutputTokens += outputTokens;
1165
1534
  }
1166
1535
  return {
1167
1536
  message_count: rows.length,
1168
1537
  total_input_tokens: totalInputTokens,
1169
1538
  total_output_tokens: totalOutputTokens,
1170
- total_cost: totalCost
1539
+ total_cost: totalCost,
1540
+ cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
1171
1541
  };
1172
1542
  } catch {
1173
1543
  return null;
@@ -1199,6 +1569,7 @@ var OpenCodeAgent = class extends BaseAgent {
1199
1569
  let totalCost = 0;
1200
1570
  let totalInputTokens = 0;
1201
1571
  let totalOutputTokens = 0;
1572
+ let hasEstimatedCost = false;
1202
1573
  const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
1203
1574
  for (const msgRow of msgRows) {
1204
1575
  const msgData = JSON.parse(String(msgRow.data ?? "{}"));
@@ -1207,7 +1578,11 @@ var OpenCodeAgent = class extends BaseAgent {
1207
1578
  const tokens = msgData.tokens;
1208
1579
  const inputTokens = Number(tokens?.input ?? 0);
1209
1580
  const outputTokens = Number(tokens?.output ?? 0);
1210
- totalCost += cost;
1581
+ const model = msgData.modelID ?? null;
1582
+ const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
1583
+ const resolvedCost = cost || estimatedCost || 0;
1584
+ if (estimatedCost !== null) hasEstimatedCost = true;
1585
+ totalCost += resolvedCost;
1211
1586
  totalInputTokens += inputTokens;
1212
1587
  totalOutputTokens += outputTokens;
1213
1588
  const partRows = db.prepare("SELECT * FROM part WHERE message_id = ? ORDER BY time_created ASC").all(msgRow.id);
@@ -1236,11 +1611,12 @@ var OpenCodeAgent = class extends BaseAgent {
1236
1611
  role: String(msgData.role ?? "assistant"),
1237
1612
  agent: msgData.agent ?? null,
1238
1613
  mode: msgData.mode ?? null,
1239
- model: msgData.modelID ?? null,
1614
+ model,
1240
1615
  provider: msgData.providerID ?? null,
1241
1616
  time_created: Number(msgRow.time_created ?? 0),
1242
1617
  tokens: tokens ? { input: inputTokens, output: outputTokens } : void 0,
1243
- cost,
1618
+ cost: resolvedCost,
1619
+ cost_source: resolvedCost > 0 ? cost > 0 ? "recorded" : "estimated" : void 0,
1244
1620
  parts
1245
1621
  });
1246
1622
  }
@@ -1257,7 +1633,8 @@ var OpenCodeAgent = class extends BaseAgent {
1257
1633
  message_count: messages.length,
1258
1634
  total_input_tokens: totalInputTokens,
1259
1635
  total_output_tokens: totalOutputTokens,
1260
- total_cost: totalCost
1636
+ total_cost: totalCost,
1637
+ cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
1261
1638
  },
1262
1639
  messages
1263
1640
  };
@@ -1327,24 +1704,30 @@ var KimiAgent = class extends BaseAgent {
1327
1704
  basePath = null;
1328
1705
  sessionMetaMap = /* @__PURE__ */ new Map();
1329
1706
  projectMap = /* @__PURE__ */ new Map();
1707
+ defaultModel = null;
1330
1708
  findBasePath() {
1331
1709
  const roots = resolveProviderRoots();
1332
- return firstExisting(join4(roots.kimiRoot, "sessions"), "data/kimi");
1710
+ return firstExisting(join5(roots.kimiRoot, "sessions"), "data/kimi");
1333
1711
  }
1334
1712
  /** Parse kimi.json and build md5(project_path) → cwd mapping */
1335
1713
  loadKimiConfig() {
1336
1714
  const roots = resolveProviderRoots();
1337
- const configPath = join4(roots.kimiRoot, "kimi.json");
1338
- if (!existsSync4(configPath)) return;
1715
+ const configPath = join5(roots.kimiRoot, "kimi.json");
1716
+ const tomlPath = join5(roots.kimiRoot, "config.toml");
1717
+ if (existsSync5(tomlPath)) {
1718
+ const configText = readFileSync4(tomlPath, "utf-8");
1719
+ this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
1720
+ }
1721
+ if (!existsSync5(configPath)) return;
1339
1722
  try {
1340
- const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
1723
+ const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
1341
1724
  const workDirs = raw?.work_dirs;
1342
1725
  if (!Array.isArray(workDirs)) return;
1343
1726
  for (const wd of workDirs) {
1344
- const path = wd.path;
1345
- if (typeof path !== "string") continue;
1346
- const hash = createHash("md5").update(path).digest("hex");
1347
- this.projectMap.set(hash, path);
1727
+ const path2 = wd.path;
1728
+ if (typeof path2 !== "string") continue;
1729
+ const hash = createHash("md5").update(path2).digest("hex");
1730
+ this.projectMap.set(hash, path2);
1348
1731
  }
1349
1732
  } catch {
1350
1733
  }
@@ -1366,12 +1749,12 @@ var KimiAgent = class extends BaseAgent {
1366
1749
  try {
1367
1750
  for (const hashEntry of readdirSync2(this.basePath, { withFileTypes: true })) {
1368
1751
  if (!hashEntry.isDirectory()) continue;
1369
- const hashPath = join4(this.basePath, hashEntry.name);
1752
+ const hashPath = join5(this.basePath, hashEntry.name);
1370
1753
  try {
1371
1754
  for (const sessionEntry of readdirSync2(hashPath, { withFileTypes: true })) {
1372
1755
  if (!sessionEntry.isDirectory()) continue;
1373
- const sessionPath = join4(hashPath, sessionEntry.name);
1374
- if (existsSync4(join4(sessionPath, "metadata.json")) || existsSync4(join4(sessionPath, "state.json"))) {
1756
+ const sessionPath = join5(hashPath, sessionEntry.name);
1757
+ if (existsSync5(join5(sessionPath, "metadata.json")) || existsSync5(join5(sessionPath, "state.json"))) {
1375
1758
  dirs.push(sessionPath);
1376
1759
  }
1377
1760
  }
@@ -1387,21 +1770,21 @@ var KimiAgent = class extends BaseAgent {
1387
1770
  try {
1388
1771
  const sessionId = basename3(sessionDir);
1389
1772
  const projectHash = basename3(dirname3(sessionDir));
1390
- const contextFile = join4(sessionDir, "context.jsonl");
1391
- const wireFile = join4(sessionDir, "wire.jsonl");
1392
- if (!existsSync4(contextFile) && !existsSync4(wireFile)) return null;
1393
- const statePath = join4(sessionDir, "state.json");
1394
- const metaPath = join4(sessionDir, "metadata.json");
1773
+ const contextFile = join5(sessionDir, "context.jsonl");
1774
+ const wireFile = join5(sessionDir, "wire.jsonl");
1775
+ if (!existsSync5(contextFile) && !existsSync5(wireFile)) return null;
1776
+ const statePath = join5(sessionDir, "state.json");
1777
+ const metaPath = join5(sessionDir, "metadata.json");
1395
1778
  let title = "";
1396
1779
  let wireMtime = null;
1397
1780
  let metaFile = "";
1398
- if (existsSync4(statePath)) {
1399
- const state = JSON.parse(readFileSync3(statePath, "utf-8"));
1781
+ if (existsSync5(statePath)) {
1782
+ const state = JSON.parse(readFileSync4(statePath, "utf-8"));
1400
1783
  title = String(state.custom_title ?? "");
1401
1784
  wireMtime = typeof state.wire_mtime === "number" ? state.wire_mtime : null;
1402
1785
  metaFile = statePath;
1403
- } else if (existsSync4(metaPath)) {
1404
- const meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
1786
+ } else if (existsSync5(metaPath)) {
1787
+ const meta = JSON.parse(readFileSync4(metaPath, "utf-8"));
1405
1788
  title = String(meta.title ?? "");
1406
1789
  wireMtime = typeof meta.wire_mtime === "number" ? meta.wire_mtime : null;
1407
1790
  metaFile = metaPath;
@@ -1413,8 +1796,8 @@ var KimiAgent = class extends BaseAgent {
1413
1796
  title: title || "Untitled Session",
1414
1797
  sourcePath: sessionDir,
1415
1798
  cwd,
1416
- contextFile: existsSync4(contextFile) ? contextFile : null,
1417
- wireFile: existsSync4(wireFile) ? wireFile : null,
1799
+ contextFile: existsSync5(contextFile) ? contextFile : null,
1800
+ wireFile: existsSync5(wireFile) ? wireFile : null,
1418
1801
  createdAt,
1419
1802
  metaFile
1420
1803
  };
@@ -1422,7 +1805,7 @@ var KimiAgent = class extends BaseAgent {
1422
1805
  return null;
1423
1806
  }
1424
1807
  }
1425
- scan() {
1808
+ scan(options) {
1426
1809
  if (!this.basePath) return [];
1427
1810
  const scanMarker = perf.start("kimi:scan");
1428
1811
  const listMarker = perf.start("listSessionDirs");
@@ -1435,6 +1818,7 @@ var KimiAgent = class extends BaseAgent {
1435
1818
  const meta = this.parseSessionDir(dir);
1436
1819
  perf.end(parseMarker);
1437
1820
  if (!meta) continue;
1821
+ if (!matchesScanWindow(meta.createdAt, options)) continue;
1438
1822
  this.sessionMetaMap.set(meta.id, meta);
1439
1823
  const stats = this.extractStats(meta.sourcePath);
1440
1824
  heads.push({
@@ -1462,32 +1846,42 @@ var KimiAgent = class extends BaseAgent {
1462
1846
  * 检测文件系统变更
1463
1847
  */
1464
1848
  checkForChanges(sinceTimestamp, cachedSessions) {
1465
- const changedIds = [];
1466
- for (const session of cachedSessions) {
1467
- const meta = this.sessionMetaMap.get(session.id);
1849
+ const changedIds = /* @__PURE__ */ new Set();
1850
+ const cachedIds = new Set(cachedSessions.map((session) => session.id));
1851
+ const currentIds = /* @__PURE__ */ new Set();
1852
+ for (const dir of this.listSessionDirs()) {
1853
+ const meta = this.parseSessionDir(dir);
1468
1854
  if (!meta) continue;
1855
+ currentIds.add(meta.id);
1856
+ this.sessionMetaMap.set(meta.id, meta);
1857
+ if (!cachedIds.has(meta.id)) {
1858
+ changedIds.add(meta.id);
1859
+ continue;
1860
+ }
1861
+ const dataFile = meta.wireFile || meta.contextFile;
1469
1862
  try {
1470
- if (meta.metaFile) {
1471
- const stat = statSync3(meta.metaFile);
1472
- if (stat.mtimeMs > sinceTimestamp) {
1473
- changedIds.push(session.id);
1474
- continue;
1475
- }
1863
+ const metaStat = statSync3(meta.metaFile);
1864
+ if (metaStat.mtimeMs > sinceTimestamp) {
1865
+ changedIds.add(meta.id);
1866
+ continue;
1476
1867
  }
1477
- const dataFile = meta.wireFile || meta.contextFile;
1478
1868
  if (dataFile) {
1479
1869
  const dataStat = statSync3(dataFile);
1480
1870
  if (dataStat.mtimeMs > sinceTimestamp) {
1481
- changedIds.push(session.id);
1871
+ changedIds.add(meta.id);
1482
1872
  }
1483
1873
  }
1484
1874
  } catch {
1485
- changedIds.push(session.id);
1875
+ changedIds.add(meta.id);
1486
1876
  }
1487
1877
  }
1878
+ for (const session of cachedSessions) {
1879
+ if (!currentIds.has(session.id)) changedIds.add(session.id);
1880
+ }
1881
+ const changedIdList = Array.from(changedIds);
1488
1882
  return {
1489
- hasChanges: changedIds.length > 0,
1490
- changedIds,
1883
+ hasChanges: changedIdList.length > 0,
1884
+ changedIds: changedIdList,
1491
1885
  timestamp: Date.now()
1492
1886
  };
1493
1887
  }
@@ -1496,11 +1890,16 @@ var KimiAgent = class extends BaseAgent {
1496
1890
  */
1497
1891
  incrementalScan(cachedSessions, changedIds) {
1498
1892
  const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
1893
+ const changedIdSet = new Set(changedIds);
1894
+ for (const id of changedIdSet) {
1895
+ sessionMap.delete(id);
1896
+ this.sessionMetaMap.delete(id);
1897
+ }
1499
1898
  for (const dir of this.listSessionDirs()) {
1500
1899
  try {
1501
1900
  const meta = this.parseSessionDir(dir);
1502
1901
  if (!meta) continue;
1503
- if (changedIds.includes(meta.id)) {
1902
+ if (changedIdSet.has(meta.id)) {
1504
1903
  this.sessionMetaMap.set(meta.id, meta);
1505
1904
  const stats = this.extractStats(meta.sourcePath);
1506
1905
  sessionMap.set(meta.id, {
@@ -1528,7 +1927,7 @@ var KimiAgent = class extends BaseAgent {
1528
1927
  }
1529
1928
  getSessionDataFromContext(meta) {
1530
1929
  if (!meta.contextFile) throw new Error("context.jsonl is missing");
1531
- const content = readFileSync3(meta.contextFile, "utf-8");
1930
+ const content = readFileSync4(meta.contextFile, "utf-8");
1532
1931
  const messages = [];
1533
1932
  const pendingToolCalls = /* @__PURE__ */ new Map();
1534
1933
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -1593,9 +1992,9 @@ var KimiAgent = class extends BaseAgent {
1593
1992
  return this.buildSessionData(meta, messages, stats);
1594
1993
  }
1595
1994
  getSessionDataFromWire(meta) {
1596
- const wirePath = meta.wireFile ?? join4(meta.sourcePath, "wire.jsonl");
1597
- if (!existsSync4(wirePath)) throw new Error("wire.jsonl is missing");
1598
- const content = readFileSync3(wirePath, "utf-8");
1995
+ const wirePath = meta.wireFile ?? join5(meta.sourcePath, "wire.jsonl");
1996
+ if (!existsSync5(wirePath)) throw new Error("wire.jsonl is missing");
1997
+ const content = readFileSync4(wirePath, "utf-8");
1599
1998
  const messages = [];
1600
1999
  const pendingToolCalls = /* @__PURE__ */ new Map();
1601
2000
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -1620,6 +2019,12 @@ var KimiAgent = class extends BaseAgent {
1620
2019
  const msg = messages[i];
1621
2020
  if (msg.role === "assistant" && !msg.tokens) {
1622
2021
  msg.tokens = { input: inputTokens, output: outputTokens };
2022
+ msg.model ??= this.defaultModel;
2023
+ const cost = estimateTokenCost(msg.model, msg.tokens);
2024
+ if (cost !== null) {
2025
+ msg.cost = cost;
2026
+ msg.cost_source = "estimated";
2027
+ }
1623
2028
  break;
1624
2029
  }
1625
2030
  }
@@ -1862,6 +2267,7 @@ var KimiAgent = class extends BaseAgent {
1862
2267
  return true;
1863
2268
  }
1864
2269
  extractStats(sessionDir) {
2270
+ let totalCost = 0;
1865
2271
  const stats = {
1866
2272
  total_cost: 0,
1867
2273
  total_input_tokens: 0,
@@ -1869,27 +2275,34 @@ var KimiAgent = class extends BaseAgent {
1869
2275
  total_tokens: 0,
1870
2276
  message_count: 0
1871
2277
  };
1872
- const wirePath = join4(sessionDir, "wire.jsonl");
1873
- if (!existsSync4(wirePath)) return stats;
2278
+ const wirePath = join5(sessionDir, "wire.jsonl");
2279
+ if (!existsSync5(wirePath)) return stats;
1874
2280
  try {
1875
- const content = readFileSync3(wirePath, "utf-8");
2281
+ const content = readFileSync4(wirePath, "utf-8");
1876
2282
  for (const line of content.split("\n").filter((l) => l.trim())) {
1877
2283
  try {
1878
2284
  const data = JSON.parse(line);
1879
2285
  const tokenUsage = data.message?.usage;
1880
2286
  if (!tokenUsage) continue;
1881
- stats.total_input_tokens += Number(tokenUsage.input_tokens ?? 0);
1882
- stats.total_output_tokens += Number(tokenUsage.output_tokens ?? 0);
2287
+ const inputTokens = Number(tokenUsage.input_tokens ?? 0);
2288
+ const outputTokens = Number(tokenUsage.output_tokens ?? 0);
2289
+ stats.total_input_tokens += inputTokens;
2290
+ stats.total_output_tokens += outputTokens;
2291
+ const cost = estimateTokenCost(this.defaultModel, {
2292
+ input: inputTokens,
2293
+ output: outputTokens
2294
+ });
2295
+ if (cost !== null) totalCost += cost;
1883
2296
  } catch {
1884
2297
  }
1885
2298
  }
1886
2299
  } catch {
1887
2300
  }
1888
- const contextPath = join4(sessionDir, "context.jsonl");
1889
- const rawPath = existsSync4(contextPath) ? contextPath : wirePath;
1890
- if (!existsSync4(rawPath)) return stats;
2301
+ const contextPath = join5(sessionDir, "context.jsonl");
2302
+ const rawPath = existsSync5(contextPath) ? contextPath : wirePath;
2303
+ if (!existsSync5(rawPath)) return stats;
1891
2304
  try {
1892
- const rawContent = readFileSync3(rawPath, "utf-8");
2305
+ const rawContent = readFileSync4(rawPath, "utf-8");
1893
2306
  for (const line of rawContent.split("\n").filter((l) => l.trim())) {
1894
2307
  try {
1895
2308
  const data = JSON.parse(line);
@@ -1901,10 +2314,19 @@ var KimiAgent = class extends BaseAgent {
1901
2314
  }
1902
2315
  } catch {
1903
2316
  }
2317
+ stats.total_cost = Number(totalCost.toFixed(8));
2318
+ if (stats.total_cost > 0) {
2319
+ stats.cost_source = "estimated";
2320
+ }
1904
2321
  return stats;
1905
2322
  }
1906
2323
  buildSessionData(meta, messages, stats) {
1907
2324
  stats.message_count = messages.length;
2325
+ const totalCost = messages.reduce((sum, message) => sum + (message.cost ?? 0), 0);
2326
+ if (totalCost > 0) {
2327
+ stats.total_cost = Number(totalCost.toFixed(8));
2328
+ stats.cost_source = "estimated";
2329
+ }
1908
2330
  return {
1909
2331
  id: meta.id,
1910
2332
  title: meta.title,
@@ -1959,6 +2381,10 @@ function parseTimestampMs2(data) {
1959
2381
  function extractModelName(raw) {
1960
2382
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
1961
2383
  }
2384
+ function extractCachedInputTokens(usage) {
2385
+ if (!usage) return 0;
2386
+ return Number(usage["cached_input_tokens"] ?? usage["cache_read_input_tokens"] ?? 0);
2387
+ }
1962
2388
  function normalizeTitleText3(text) {
1963
2389
  const line = text.split("\n").find((l) => l.trim());
1964
2390
  return line?.trim().slice(0, 80) || "";
@@ -2073,7 +2499,7 @@ var CodexAgent = class extends BaseAgent {
2073
2499
  // ---- BaseAgent implementation ----
2074
2500
  findBasePath() {
2075
2501
  const roots = resolveProviderRoots();
2076
- return firstExisting(join5(roots.codexRoot, "sessions"));
2502
+ return firstExisting(join6(roots.codexRoot, "sessions"));
2077
2503
  }
2078
2504
  isAvailable() {
2079
2505
  this.basePath = this.findBasePath();
@@ -2085,7 +2511,7 @@ var CodexAgent = class extends BaseAgent {
2085
2511
  return false;
2086
2512
  }
2087
2513
  }
2088
- scan() {
2514
+ scan(options) {
2089
2515
  if (!this.basePath) return [];
2090
2516
  const scanMarker = perf.start("codex:scan");
2091
2517
  const indexMarker = perf.start("loadSessionIndex");
@@ -2093,12 +2519,12 @@ var CodexAgent = class extends BaseAgent {
2093
2519
  perf.end(indexMarker);
2094
2520
  const heads = [];
2095
2521
  const listMarker = perf.start("listRolloutFiles");
2096
- const files = this.listRolloutFiles();
2522
+ const files = this.listRolloutFiles(options);
2097
2523
  perf.end(listMarker);
2098
2524
  for (const file of files) {
2099
2525
  try {
2100
2526
  const parseMarker = perf.start(`parseSessionHead:${basename4(file)}`);
2101
- const head = this.parseSessionHead(file);
2527
+ const head = this.parseSessionHead(file, options);
2102
2528
  perf.end(parseMarker);
2103
2529
  if (head) {
2104
2530
  heads.push(head);
@@ -2234,12 +2660,14 @@ var CodexAgent = class extends BaseAgent {
2234
2660
  getSessionData(sessionId) {
2235
2661
  const meta = this.sessionMetaMap.get(sessionId);
2236
2662
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
2237
- if (!existsSync5(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
2238
- const content = readFileSync4(meta.sourcePath, "utf-8");
2663
+ if (!existsSync6(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
2664
+ const content = readFileSync5(meta.sourcePath, "utf-8");
2239
2665
  const messages = [];
2240
2666
  const pendingToolCalls = /* @__PURE__ */ new Map();
2241
2667
  let totalInputTokens = 0;
2242
2668
  let totalOutputTokens = 0;
2669
+ let totalCacheReadTokens = 0;
2670
+ let totalCost = 0;
2243
2671
  let currentAssistantIndex = null;
2244
2672
  let latestAssistantTextIndex = null;
2245
2673
  let pendingPlan = null;
@@ -2248,6 +2676,7 @@ var CodexAgent = class extends BaseAgent {
2248
2676
  let prevInput = 0;
2249
2677
  let prevOutput = 0;
2250
2678
  let prevReasoning = 0;
2679
+ let prevCachedInput = 0;
2251
2680
  for (const record of parseJsonlLines(content)) {
2252
2681
  try {
2253
2682
  const recordType = String(record["type"] ?? "");
@@ -2286,29 +2715,43 @@ var CodexAgent = class extends BaseAgent {
2286
2715
  let inputTokens = 0;
2287
2716
  let outputTokens = 0;
2288
2717
  let reasoningTokens = 0;
2718
+ let cacheReadTokens = 0;
2289
2719
  if (lastUsage) {
2290
2720
  inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2291
2721
  outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2292
2722
  reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2723
+ cacheReadTokens = extractCachedInputTokens(lastUsage);
2293
2724
  } else if (cumulativeTotal > 0 && totalUsage) {
2294
2725
  inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
2295
2726
  outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
2296
2727
  reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
2728
+ cacheReadTokens = extractCachedInputTokens(totalUsage) - prevCachedInput;
2297
2729
  prevInput = Number(totalUsage["input_tokens"] ?? 0);
2298
2730
  prevOutput = Number(totalUsage["output_tokens"] ?? 0);
2299
2731
  prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2732
+ prevCachedInput = extractCachedInputTokens(totalUsage);
2300
2733
  }
2301
2734
  const totalInput = Math.max(0, inputTokens);
2735
+ const totalCacheRead = Math.max(0, cacheReadTokens);
2302
2736
  if (totalInput || outputTokens || reasoningTokens) {
2303
2737
  totalInputTokens += totalInput;
2304
2738
  totalOutputTokens += outputTokens + reasoningTokens;
2739
+ totalCacheReadTokens += totalCacheRead;
2305
2740
  for (let i = messages.length - 1; i >= 0; i--) {
2306
2741
  const msg = messages[i];
2307
2742
  if (msg.role === "assistant" && !msg.tokens) {
2308
2743
  msg.tokens = {
2309
2744
  input: totalInput,
2310
- output: outputTokens + reasoningTokens
2745
+ output: outputTokens,
2746
+ reasoning: reasoningTokens || void 0,
2747
+ cache_read: totalCacheRead || void 0
2311
2748
  };
2749
+ const cost = estimateTokenCost(msg.model ?? activeModel, msg.tokens);
2750
+ if (cost !== null) {
2751
+ msg.cost = cost;
2752
+ msg.cost_source = "estimated";
2753
+ totalCost += cost;
2754
+ }
2312
2755
  break;
2313
2756
  }
2314
2757
  }
@@ -2333,29 +2776,32 @@ var CodexAgent = class extends BaseAgent {
2333
2776
  message_count: messages.length,
2334
2777
  total_input_tokens: totalInputTokens,
2335
2778
  total_output_tokens: totalOutputTokens,
2336
- total_cost: 0
2779
+ total_cache_read_tokens: totalCacheReadTokens || void 0,
2780
+ total_cost: totalCost,
2781
+ cost_source: totalCost > 0 ? "estimated" : void 0
2337
2782
  },
2338
2783
  messages
2339
2784
  };
2340
2785
  }
2341
2786
  // ---- File listing ----
2342
- listRolloutFiles() {
2787
+ listRolloutFiles(options) {
2343
2788
  if (!this.basePath) return [];
2344
2789
  try {
2345
- return this.walkDirForRolloutFiles(this.basePath);
2790
+ return this.walkDirForRolloutFiles(this.basePath, options);
2346
2791
  } catch {
2347
2792
  return [];
2348
2793
  }
2349
2794
  }
2350
- walkDirForRolloutFiles(dir) {
2795
+ walkDirForRolloutFiles(dir, options) {
2351
2796
  const files = [];
2352
2797
  try {
2353
2798
  for (const entry of readdirSync3(dir)) {
2354
- const fullPath = join5(dir, entry);
2799
+ const fullPath = join6(dir, entry);
2355
2800
  const stat = statSync4(fullPath);
2356
2801
  if (stat.isDirectory()) {
2357
- files.push(...this.walkDirForRolloutFiles(fullPath));
2802
+ files.push(...this.walkDirForRolloutFiles(fullPath, options));
2358
2803
  } else if (entry.endsWith(".jsonl") && entry.startsWith("rollout-")) {
2804
+ if (!matchesScanWindow(stat.mtimeMs, options)) continue;
2359
2805
  files.push(fullPath);
2360
2806
  }
2361
2807
  }
@@ -2367,10 +2813,10 @@ var CodexAgent = class extends BaseAgent {
2367
2813
  loadSessionIndex() {
2368
2814
  if (this.sessionIndexCache.size > 0) return;
2369
2815
  const roots = resolveProviderRoots();
2370
- const indexPath = join5(roots.codexRoot, "session_index.jsonl");
2371
- if (!existsSync5(indexPath)) return;
2816
+ const indexPath = join6(roots.codexRoot, "session_index.jsonl");
2817
+ if (!existsSync6(indexPath)) return;
2372
2818
  try {
2373
- const content = readFileSync4(indexPath, "utf-8");
2819
+ const content = readFileSync5(indexPath, "utf-8");
2374
2820
  for (const record of parseJsonlLines(content)) {
2375
2821
  const sid = String(record["id"] ?? "").trim();
2376
2822
  const threadName = String(record["thread_name"] ?? "").trim();
@@ -2386,8 +2832,21 @@ var CodexAgent = class extends BaseAgent {
2386
2832
  return this.sessionIndexCache.get(sessionId) ?? null;
2387
2833
  }
2388
2834
  // ---- Session head parsing ----
2389
- parseSessionHead(filePath) {
2390
- const content = readFileSync4(filePath, "utf-8");
2835
+ readFilePrefix(filePath, bytes = 64 * 1024) {
2836
+ const fd = openSync(filePath, "r");
2837
+ try {
2838
+ const buffer = Buffer.alloc(bytes);
2839
+ const bytesRead = readSync(fd, buffer, 0, bytes, 0);
2840
+ return buffer.subarray(0, bytesRead).toString("utf-8");
2841
+ } finally {
2842
+ closeSync(fd);
2843
+ }
2844
+ }
2845
+ parseSessionHead(filePath, options) {
2846
+ if (options?.fast) {
2847
+ return this.parseFastSessionHead(filePath);
2848
+ }
2849
+ const content = readFileSync5(filePath, "utf-8");
2391
2850
  const lines = content.split("\n").filter((l) => l.trim());
2392
2851
  if (lines.length === 0) return null;
2393
2852
  const sessionId = extractSessionId(filePath);
@@ -2406,12 +2865,17 @@ var CodexAgent = class extends BaseAgent {
2406
2865
  let updatedAt = createdAt;
2407
2866
  let messageCount = 0;
2408
2867
  let model = null;
2868
+ let activeModel = null;
2869
+ const modelUsageMap = {};
2409
2870
  let totalInputTokens = 0;
2410
2871
  let totalOutputTokens = 0;
2872
+ let totalCacheReadTokens = 0;
2873
+ let totalCost = 0;
2411
2874
  let scanPrevCumulativeTotal = 0;
2412
2875
  let scanPrevInput = 0;
2413
2876
  let scanPrevOutput = 0;
2414
2877
  let scanPrevReasoning = 0;
2878
+ let scanPrevCachedInput = 0;
2415
2879
  const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
2416
2880
  for (const line of lines) {
2417
2881
  try {
@@ -2421,8 +2885,10 @@ var CodexAgent = class extends BaseAgent {
2421
2885
  if (recordTs > updatedAt) updatedAt = recordTs;
2422
2886
  if (recordType === "session_meta" || recordType === "turn_context") {
2423
2887
  const payload2 = data["payload"] ?? {};
2424
- if (!model) {
2425
- model = extractModelName(payload2["model"]);
2888
+ const nextModel = extractModelName(payload2["model"]);
2889
+ if (nextModel) {
2890
+ activeModel = nextModel;
2891
+ model ??= nextModel;
2426
2892
  }
2427
2893
  continue;
2428
2894
  }
@@ -2432,10 +2898,11 @@ var CodexAgent = class extends BaseAgent {
2432
2898
  if (COUNTED_TYPES.has(pType)) {
2433
2899
  messageCount++;
2434
2900
  }
2435
- if (!model) {
2436
- const info = p["info"];
2437
- const m = info?.["model"] ?? p["model"];
2438
- if (typeof m === "string" && m.trim()) model = m.trim();
2901
+ const info = p["info"];
2902
+ const m = info?.["model"] ?? p["model"];
2903
+ if (typeof m === "string" && m.trim()) {
2904
+ activeModel = m.trim();
2905
+ model ??= activeModel;
2439
2906
  }
2440
2907
  }
2441
2908
  if (recordType === "event_msg") {
@@ -2450,21 +2917,38 @@ var CodexAgent = class extends BaseAgent {
2450
2917
  let inputTokens = 0;
2451
2918
  let outputTokens = 0;
2452
2919
  let reasoningTokens = 0;
2920
+ let cacheReadTokens = 0;
2453
2921
  if (lastUsage) {
2454
2922
  inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2455
2923
  outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2456
2924
  reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2925
+ cacheReadTokens = extractCachedInputTokens(lastUsage);
2457
2926
  } else if (cumulativeTotal > 0 && totalUsage) {
2458
2927
  inputTokens = Number(totalUsage["input_tokens"] ?? 0) - scanPrevInput;
2459
2928
  outputTokens = Number(totalUsage["output_tokens"] ?? 0) - scanPrevOutput;
2460
2929
  reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - scanPrevReasoning;
2930
+ cacheReadTokens = extractCachedInputTokens(totalUsage) - scanPrevCachedInput;
2461
2931
  scanPrevInput = Number(totalUsage["input_tokens"] ?? 0);
2462
2932
  scanPrevOutput = Number(totalUsage["output_tokens"] ?? 0);
2463
2933
  scanPrevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2934
+ scanPrevCachedInput = extractCachedInputTokens(totalUsage);
2464
2935
  }
2465
2936
  const totalInput = Math.max(0, inputTokens);
2937
+ const totalCacheRead = Math.max(0, cacheReadTokens);
2466
2938
  totalInputTokens += totalInput;
2467
2939
  totalOutputTokens += outputTokens + reasoningTokens;
2940
+ totalCacheReadTokens += totalCacheRead;
2941
+ const totalForModel = totalInput + outputTokens + reasoningTokens;
2942
+ if (activeModel && totalForModel > 0) {
2943
+ modelUsageMap[activeModel] = (modelUsageMap[activeModel] ?? 0) + totalForModel;
2944
+ }
2945
+ const cost = estimateTokenCost(activeModel, {
2946
+ input: totalInput,
2947
+ output: outputTokens,
2948
+ reasoning: reasoningTokens || void 0,
2949
+ cache_read: totalCacheRead || void 0
2950
+ });
2951
+ if (cost !== null) totalCost += cost;
2468
2952
  }
2469
2953
  }
2470
2954
  }
@@ -2483,6 +2967,43 @@ var CodexAgent = class extends BaseAgent {
2483
2967
  message_count: messageCount,
2484
2968
  total_input_tokens: totalInputTokens,
2485
2969
  total_output_tokens: totalOutputTokens,
2970
+ total_cache_read_tokens: totalCacheReadTokens || void 0,
2971
+ total_cost: totalCost,
2972
+ cost_source: totalCost > 0 ? "estimated" : void 0
2973
+ },
2974
+ model_usage: Object.keys(modelUsageMap).length > 0 ? modelUsageMap : void 0
2975
+ };
2976
+ }
2977
+ parseFastSessionHead(filePath) {
2978
+ const prefix = this.readFilePrefix(filePath);
2979
+ const lines = prefix.split("\n").filter((l) => l.trim());
2980
+ if (lines.length === 0) return null;
2981
+ const sessionId = extractSessionId(filePath);
2982
+ let firstRecord;
2983
+ try {
2984
+ firstRecord = JSON.parse(lines[0]);
2985
+ } catch {
2986
+ return null;
2987
+ }
2988
+ const payload = firstRecord["payload"] ?? {};
2989
+ const stat = statSync4(filePath);
2990
+ const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
2991
+ const indexTitle = this.getTitleForSession(sessionId);
2992
+ const messageTitle = this.extractTitleFromLines(lines);
2993
+ const directory = payload["cwd"] ? String(payload["cwd"]) : "";
2994
+ const directoryTitle = basenameTitle(directory || null);
2995
+ const title = resolveSessionTitle(indexTitle, messageTitle, directoryTitle);
2996
+ return {
2997
+ id: sessionId,
2998
+ slug: `codex/${sessionId}`,
2999
+ title,
3000
+ directory,
3001
+ time_created: createdAt,
3002
+ time_updated: stat.mtimeMs,
3003
+ stats: {
3004
+ message_count: 0,
3005
+ total_input_tokens: 0,
3006
+ total_output_tokens: 0,
2486
3007
  total_cost: 0
2487
3008
  }
2488
3009
  };
@@ -3016,7 +3537,7 @@ var CursorAgent = class extends BaseAgent {
3016
3537
  if (!isSqliteAvailable()) return null;
3017
3538
  const dataPath = getCursorDataPath();
3018
3539
  if (!dataPath) return null;
3019
- return join6(dataPath, "globalStorage", "state.vscdb");
3540
+ return join7(dataPath, "globalStorage", "state.vscdb");
3020
3541
  }
3021
3542
  /**
3022
3543
  * Build a map of composerId → workspace folder path by reading
@@ -3026,8 +3547,8 @@ var CursorAgent = class extends BaseAgent {
3026
3547
  const map = /* @__PURE__ */ new Map();
3027
3548
  const dataPath = getCursorDataPath();
3028
3549
  if (!dataPath) return map;
3029
- const wsStoragePath = join6(dataPath, "workspaceStorage");
3030
- if (!existsSync6(wsStoragePath)) return map;
3550
+ const wsStoragePath = join7(dataPath, "workspaceStorage");
3551
+ if (!existsSync7(wsStoragePath)) return map;
3031
3552
  let entryNames;
3032
3553
  try {
3033
3554
  entryNames = readdirSync4(wsStoragePath);
@@ -3035,25 +3556,25 @@ var CursorAgent = class extends BaseAgent {
3035
3556
  return map;
3036
3557
  }
3037
3558
  for (const name of entryNames) {
3038
- const wsDir = join6(wsStoragePath, name);
3559
+ const wsDir = join7(wsStoragePath, name);
3039
3560
  try {
3040
3561
  if (!statSync5(wsDir).isDirectory()) continue;
3041
3562
  } catch {
3042
3563
  continue;
3043
3564
  }
3044
- const wsJsonPath = join6(wsDir, "workspace.json");
3045
- if (!existsSync6(wsJsonPath)) continue;
3565
+ const wsJsonPath = join7(wsDir, "workspace.json");
3566
+ if (!existsSync7(wsJsonPath)) continue;
3046
3567
  let workspacePath;
3047
3568
  try {
3048
- const data = JSON.parse(readFileSync5(wsJsonPath, "utf-8"));
3569
+ const data = JSON.parse(readFileSync6(wsJsonPath, "utf-8"));
3049
3570
  const uri = data.folder ?? data.workspace ?? "";
3050
3571
  if (!uri) continue;
3051
3572
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
3052
3573
  } catch {
3053
3574
  continue;
3054
3575
  }
3055
- const wsDbPath = join6(wsDir, "state.vscdb");
3056
- if (!existsSync6(wsDbPath)) continue;
3576
+ const wsDbPath = join7(wsDir, "state.vscdb");
3577
+ if (!existsSync7(wsDbPath)) continue;
3057
3578
  const wsDb = openDbReadOnly(wsDbPath);
3058
3579
  if (!wsDb) continue;
3059
3580
  try {
@@ -3081,9 +3602,9 @@ var CursorAgent = class extends BaseAgent {
3081
3602
  }
3082
3603
  isAvailable() {
3083
3604
  this.dbPath = this.findDbPath();
3084
- return this.dbPath !== null && existsSync6(this.dbPath);
3605
+ return this.dbPath !== null && existsSync7(this.dbPath);
3085
3606
  }
3086
- scan() {
3607
+ scan(options) {
3087
3608
  if (!this.dbPath) return [];
3088
3609
  const scanMarker = perf.start("cursor:scan");
3089
3610
  const dbMarker = perf.start("openDatabase");
@@ -3101,25 +3622,57 @@ var CursorAgent = class extends BaseAgent {
3101
3622
  const composer = JSON.parse(row.value);
3102
3623
  if (!composer.id && !composer.composerId) continue;
3103
3624
  const composerId = composer.id || composer.composerId || "";
3625
+ const createdAt = composer.createdAt ?? 0;
3626
+ const updatedAt = composer.updatedAt ?? composer.lastUpdatedAt ?? composer.lastSendTime ?? createdAt;
3627
+ if (!matchesScanWindow(updatedAt, options)) continue;
3628
+ const title = this.extractTitle(composer);
3629
+ const fastMessageCount = composer.chatMessages?.length ?? 0;
3630
+ const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
3631
+ if (options?.fast) {
3632
+ const directory2 = workspacePathMap.get(composerId) ?? "";
3633
+ const totalCost2 = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
3634
+ input: composer.inputTokenCount ?? 0,
3635
+ output: composer.outputTokenCount ?? 0
3636
+ }) ?? 0;
3637
+ heads.push({
3638
+ id: composerId,
3639
+ slug: `cursor/${composerId}`,
3640
+ title,
3641
+ directory: directory2,
3642
+ time_created: createdAt,
3643
+ time_updated: updatedAt || void 0,
3644
+ stats: {
3645
+ message_count: fastMessageCount,
3646
+ total_input_tokens: composer.inputTokenCount ?? 0,
3647
+ total_output_tokens: composer.outputTokenCount ?? 0,
3648
+ total_cost: totalCost2,
3649
+ cost_source: totalCost2 > 0 ? "estimated" : void 0
3650
+ }
3651
+ });
3652
+ this.composerCache.set(composerId, composer);
3653
+ this.sessionMetaMap.set(composerId, {
3654
+ id: composerId,
3655
+ sourcePath: this.dbPath || ""
3656
+ });
3657
+ continue;
3658
+ }
3104
3659
  const requestId = this.extractRequestIdFromBubbles(db, composerId);
3105
3660
  const sessionId = requestId || composerId;
3106
- const title = this.extractTitle(composer);
3107
- const createdAt = composer.createdAt ?? 0;
3108
- const updatedAt = composer.updatedAt ?? createdAt;
3109
3661
  const messages = this.loadMessagesFromBubbles(
3110
3662
  db,
3111
3663
  composerId,
3112
3664
  sessionId,
3113
3665
  composer.modelConfig?.modelName ?? composer.model ?? null
3114
3666
  );
3115
- const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
3116
3667
  if (messages.length === 0 && !hasSubagents) {
3117
3668
  continue;
3118
3669
  }
3119
3670
  const messageCount = messages.length;
3120
3671
  const directory = workspacePathMap.get(composerId) ?? "";
3121
3672
  const modelUsageMap = {};
3673
+ let totalCost = 0;
3122
3674
  for (const msg of messages) {
3675
+ totalCost += msg.cost ?? 0;
3123
3676
  if (msg.model) {
3124
3677
  const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
3125
3678
  if (msgTokens > 0) {
@@ -3139,7 +3692,8 @@ var CursorAgent = class extends BaseAgent {
3139
3692
  message_count: messageCount,
3140
3693
  total_input_tokens: composer.inputTokenCount ?? 0,
3141
3694
  total_output_tokens: composer.outputTokenCount ?? 0,
3142
- total_cost: 0
3695
+ total_cost: totalCost,
3696
+ cost_source: totalCost > 0 ? "estimated" : void 0
3143
3697
  },
3144
3698
  model_usage: hasModelUsage ? modelUsageMap : void 0
3145
3699
  });
@@ -3181,7 +3735,7 @@ var CursorAgent = class extends BaseAgent {
3181
3735
  if (!this.dbPath) {
3182
3736
  this.dbPath = this.findDbPath();
3183
3737
  }
3184
- if (!this.dbPath || !existsSync6(this.dbPath)) {
3738
+ if (!this.dbPath || !existsSync7(this.dbPath)) {
3185
3739
  return { hasChanges: false, timestamp: Date.now() };
3186
3740
  }
3187
3741
  try {
@@ -3242,12 +3796,20 @@ var CursorAgent = class extends BaseAgent {
3242
3796
  );
3243
3797
  let totalInputTokens = 0;
3244
3798
  let totalOutputTokens = 0;
3799
+ let totalCost = 0;
3245
3800
  for (const msg of messages) {
3246
3801
  totalInputTokens += msg.tokens?.input ?? 0;
3247
3802
  totalOutputTokens += msg.tokens?.output ?? 0;
3803
+ totalCost += msg.cost ?? 0;
3248
3804
  }
3249
3805
  if (totalInputTokens === 0) totalInputTokens = composer.inputTokenCount ?? 0;
3250
3806
  if (totalOutputTokens === 0) totalOutputTokens = composer.outputTokenCount ?? 0;
3807
+ if (totalCost === 0) {
3808
+ totalCost = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
3809
+ input: totalInputTokens,
3810
+ output: totalOutputTokens
3811
+ }) ?? 0;
3812
+ }
3251
3813
  this.appendSubagentMessages(db, composer, messages);
3252
3814
  const cachedDir = this.composerCache.get(`__dir__${composerId}`);
3253
3815
  const directory = cachedDir?.directory ?? this.buildWorkspacePathMap().get(composerId) ?? "";
@@ -3262,7 +3824,8 @@ var CursorAgent = class extends BaseAgent {
3262
3824
  message_count: messages.length,
3263
3825
  total_input_tokens: totalInputTokens,
3264
3826
  total_output_tokens: totalOutputTokens,
3265
- total_cost: 0
3827
+ total_cost: totalCost,
3828
+ cost_source: totalCost > 0 ? "estimated" : void 0
3266
3829
  },
3267
3830
  messages
3268
3831
  };
@@ -3383,6 +3946,9 @@ var CursorAgent = class extends BaseAgent {
3383
3946
  }
3384
3947
  }
3385
3948
  if (parts.length === 0) continue;
3949
+ const modelName = bubble.modelInfo?.modelName ?? activeModelName;
3950
+ const tokens = { input: inputTokens, output: outputTokens };
3951
+ const cost = estimateTokenCost(modelName, tokens);
3386
3952
  messages.push({
3387
3953
  id: `cursor-${composerId}-${bubbleId}`,
3388
3954
  role,
@@ -3390,10 +3956,11 @@ var CursorAgent = class extends BaseAgent {
3390
3956
  time_created: timestampMs,
3391
3957
  time_completed: null,
3392
3958
  mode: role === "assistant" && parts.some((p) => p.type === "tool") ? "tool" : null,
3393
- model: bubble.modelInfo?.modelName ?? activeModelName,
3959
+ model: modelName,
3394
3960
  provider: null,
3395
- tokens: { input: inputTokens, output: outputTokens },
3396
- cost: 0,
3961
+ tokens,
3962
+ cost: cost ?? 0,
3963
+ cost_source: cost !== null ? "estimated" : void 0,
3397
3964
  parts
3398
3965
  });
3399
3966
  messageIndex++;
@@ -3549,24 +4116,309 @@ registerAgent({
3549
4116
  icon: "/icon/agent/cursor.svg",
3550
4117
  create: () => new CursorAgent()
3551
4118
  });
3552
- var CACHE_VERSION = 4;
4119
+ function fallbackDisplayName(input) {
4120
+ if (input === "/") return "(root)";
4121
+ const trimmed = input.replace(/[/\\]+$/, "");
4122
+ const parts = trimmed.split(/[/\\]+/).filter(Boolean);
4123
+ return parts.at(-1) ?? trimmed;
4124
+ }
4125
+ var realFs = {
4126
+ exists(path2) {
4127
+ return existsSync8(path2);
4128
+ },
4129
+ readText(path2) {
4130
+ try {
4131
+ return readFileSync7(path2, "utf8");
4132
+ } catch {
4133
+ return null;
4134
+ }
4135
+ },
4136
+ spawn(cmd, args, opts) {
4137
+ const result = spawnSync(cmd, args, { cwd: opts.cwd, encoding: "utf8", timeout: 1e3 });
4138
+ return {
4139
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
4140
+ exitCode: result.status ?? 1
4141
+ };
4142
+ }
4143
+ };
4144
+ function getAgentName(session) {
4145
+ return session.slug.split("/")[0]?.toLowerCase() || "unknown";
4146
+ }
4147
+ function buildProjectGroups(sessions) {
4148
+ const groups = /* @__PURE__ */ new Map();
4149
+ for (const session of sessions) {
4150
+ const identity = session.project_identity;
4151
+ if (!identity) continue;
4152
+ const activity = session.time_updated ?? session.time_created;
4153
+ const groupKey = `${identity.kind}:${identity.key}`;
4154
+ const current = groups.get(groupKey);
4155
+ if (current) {
4156
+ current.sources.add(getAgentName(session));
4157
+ current.sessionCount += 1;
4158
+ current.lastActivity = Math.max(current.lastActivity, activity);
4159
+ } else {
4160
+ groups.set(groupKey, {
4161
+ identity,
4162
+ sources: /* @__PURE__ */ new Set([getAgentName(session)]),
4163
+ sessionCount: 1,
4164
+ lastActivity: activity
4165
+ });
4166
+ }
4167
+ }
4168
+ return [...groups.values()].map((group) => ({
4169
+ identityKind: group.identity.kind,
4170
+ identityKey: group.identity.key,
4171
+ displayName: group.identity.displayName,
4172
+ sources: [...group.sources].sort(),
4173
+ sessionCount: group.sessionCount,
4174
+ lastActivity: group.lastActivity || null
4175
+ })).sort((a, b) => {
4176
+ if (a.identityKind === "loose" && b.identityKind !== "loose") return 1;
4177
+ if (b.identityKind === "loose" && a.identityKind !== "loose") return -1;
4178
+ return (b.lastActivity ?? 0) - (a.lastActivity ?? 0);
4179
+ });
4180
+ }
4181
+ var MANIFESTS = [
4182
+ "package.json",
4183
+ "Cargo.toml",
4184
+ "pyproject.toml",
4185
+ "go.mod",
4186
+ "Gemfile",
4187
+ "pom.xml",
4188
+ "build.gradle"
4189
+ ];
4190
+ var PARSEABLE_MANIFESTS = ["package.json", "Cargo.toml", "pyproject.toml"];
4191
+ var LOOSE_DIRS = /* @__PURE__ */ new Set(["/tmp", "/private/tmp"]);
4192
+ var LOOSE_HOME_DIRS = ["Desktop", "Downloads", "Documents"];
4193
+ function normalizeGitRemote(url) {
4194
+ if (!url) return null;
4195
+ let value = url.trim().replace(/\.git$/, "");
4196
+ const sshMatch = value.match(/^[^@]+@([^:]+):(.+)$/);
4197
+ if (sshMatch) value = `${sshMatch[1]}/${sshMatch[2]}`;
4198
+ value = value.replace(/^[a-z]+:\/\/(?:[^@/]*@)?/i, "");
4199
+ if (!value.includes("/")) return null;
4200
+ return value.toLowerCase();
4201
+ }
4202
+ function computeIdentity(cwd, fs) {
4203
+ if (!cwd) return loose();
4204
+ const pathOps = getPathOps(cwd);
4205
+ const absoluteCwd = pathOps.resolve(cwd);
4206
+ const homeDir = homedir3();
4207
+ const homePathOps = getPathOps(homeDir);
4208
+ const home = homePathOps === pathOps ? pathOps.resolve(homeDir) : homeDir;
4209
+ if (absoluteCwd === home || LOOSE_DIRS.has(absoluteCwd)) return loose();
4210
+ if (homePathOps === pathOps && LOOSE_HOME_DIRS.some((dir) => absoluteCwd === pathOps.join(home, dir))) {
4211
+ return loose();
4212
+ }
4213
+ const gitRoot = findGitRoot(absoluteCwd, fs, pathOps);
4214
+ if (gitRoot) {
4215
+ const remote = fs.spawn("git", ["config", "--get", "remote.origin.url"], { cwd: gitRoot });
4216
+ if (remote.exitCode === 0) {
4217
+ const normalized = normalizeGitRemote(remote.stdout.trim());
4218
+ if (normalized) {
4219
+ return {
4220
+ kind: "git_remote",
4221
+ key: normalized,
4222
+ displayName: deriveDisplayName({ kind: "git_remote", key: normalized, gitRoot, fs })
4223
+ };
4224
+ }
4225
+ }
4226
+ const common = fs.spawn("git", ["rev-parse", "--git-common-dir"], { cwd: gitRoot });
4227
+ if (common.exitCode === 0) {
4228
+ const raw = common.stdout.trim();
4229
+ if (raw) {
4230
+ const key = pathOps.isAbsolute(raw) ? raw : pathOps.resolve(gitRoot, raw);
4231
+ return {
4232
+ kind: "git_common_dir",
4233
+ key,
4234
+ displayName: deriveDisplayName({ kind: "git_common_dir", key, gitRoot, fs })
4235
+ };
4236
+ }
4237
+ }
4238
+ }
4239
+ const manifestDir = findManifestDir(absoluteCwd, fs, pathOps);
4240
+ if (manifestDir) {
4241
+ return {
4242
+ kind: "manifest_path",
4243
+ key: manifestDir,
4244
+ displayName: deriveDisplayName({ kind: "manifest_path", key: manifestDir, fs })
4245
+ };
4246
+ }
4247
+ return {
4248
+ kind: "path",
4249
+ key: absoluteCwd,
4250
+ displayName: fallbackDisplayName(absoluteCwd)
4251
+ };
4252
+ }
4253
+ function loose() {
4254
+ return { kind: "loose", key: "loose", displayName: "Loose" };
4255
+ }
4256
+ function getPathOps(input) {
4257
+ if (/^[a-zA-Z]:[\\/]/.test(input) || input.startsWith("\\\\") || input.includes("\\")) {
4258
+ return path.win32;
4259
+ }
4260
+ if (input.startsWith("/")) return path.posix;
4261
+ return path;
4262
+ }
4263
+ function findGitRoot(start, fs, pathOps) {
4264
+ let current = start;
4265
+ while (current) {
4266
+ if (fs.exists(pathOps.join(current, ".git"))) return current;
4267
+ const parent = pathOps.dirname(current);
4268
+ if (parent === current) break;
4269
+ current = parent;
4270
+ }
4271
+ return null;
4272
+ }
4273
+ function findManifestDir(start, fs, pathOps) {
4274
+ let current = start;
4275
+ while (current) {
4276
+ for (const manifest of MANIFESTS) {
4277
+ if (fs.exists(pathOps.join(current, manifest))) return current;
4278
+ }
4279
+ const parent = pathOps.dirname(current);
4280
+ if (parent === current) break;
4281
+ current = parent;
4282
+ }
4283
+ return null;
4284
+ }
4285
+ function deriveDisplayName(input) {
4286
+ const pathOps = getPathOps(input.gitRoot ?? input.key);
4287
+ const dir = input.gitRoot ?? (input.kind === "manifest_path" ? input.key : null);
4288
+ if (dir) {
4289
+ for (const manifest of PARSEABLE_MANIFESTS) {
4290
+ const manifestPath = pathOps.join(dir, manifest);
4291
+ if (input.fs.exists(manifestPath)) {
4292
+ const name = parseManifestName(manifest, input.fs.readText(manifestPath) ?? "");
4293
+ if (name) return name;
4294
+ }
4295
+ }
4296
+ }
4297
+ if (input.kind === "git_remote") {
4298
+ return input.key.split("/").at(-1) || input.key;
4299
+ }
4300
+ if (input.gitRoot) return fallbackDisplayName(input.gitRoot);
4301
+ return fallbackDisplayName(input.key);
4302
+ }
4303
+ function parseManifestName(file, text) {
4304
+ if (!text) return null;
4305
+ if (file === "package.json" || file === "Cargo.toml" || file === "pyproject.toml") {
4306
+ const match = text.match(/"name"\s*:\s*"([^"]+)"/) || text.match(/^\s*name\s*=\s*"([^"]+)"/m);
4307
+ if (match?.[1]) return match[1];
4308
+ }
4309
+ return null;
4310
+ }
4311
+ var TAG_ORDER = [
4312
+ "bugfix",
4313
+ "refactoring",
4314
+ "feature-dev",
4315
+ "testing",
4316
+ "docs",
4317
+ "git-ops",
4318
+ "build-deploy",
4319
+ "exploration",
4320
+ "planning"
4321
+ ];
4322
+ var USER_RULES = [
4323
+ ["bugfix", /\b(fix|bug|error|crash|exception|fail(?:ed|ure)?)\b|修复|错误|报错|崩溃|异常/i],
4324
+ ["refactoring", /\b(refactor|rename|simplify|clean up|cleanup)\b|重构|重命名|简化|清理/i],
4325
+ ["feature-dev", /\b(add|create|implement|new|support|build)\b|新增|创建|实现|增加|开发|支持/i],
4326
+ ["docs", /\b(document|documentation|readme|docs?)\b|文档|说明/i]
4327
+ ];
4328
+ var TESTING_COMMAND_RE = /\b(pytest|vitest|jest|mocha|pnpm\s+test|npm\s+test|yarn\s+test)\b/i;
4329
+ var GIT_COMMAND_RE = /\bgit\s+(push|commit|merge|branch|checkout|switch|rebase|tag)\b/i;
4330
+ var BUILD_DEPLOY_COMMAND_RE = /\b((npm|pnpm|yarn|bun)\s+(run\s+)?build|docker|pm2|deploy|vercel|netlify)\b/i;
4331
+ var DOC_PATH_RE = /\.(md|mdx|txt|rst|adoc)$/i;
4332
+ var READ_TOOL_RE = /\b(read|grep|glob|websearch|web_search|search|find|rg)\b/i;
4333
+ var EDIT_TOOL_RE = /\b(edit|write|apply_patch|patch|multiedit|notebookedit)\b/i;
4334
+ var PLAN_TOOL_RE = /\b(enterplanmode|taskcreate|update_plan|plan)\b/i;
4335
+ function getSmartTagSourceTimestamp(session) {
4336
+ return session.time_updated ?? session.time_created;
4337
+ }
4338
+ function classifySessionTags(session) {
4339
+ const tags = /* @__PURE__ */ new Set();
4340
+ let readToolCount = 0;
4341
+ let editToolCount = 0;
4342
+ for (const message of session.messages) {
4343
+ if (message.role === "user") {
4344
+ const text = message.parts.map(partText).join("\n");
4345
+ for (const [tag, pattern] of USER_RULES) {
4346
+ if (pattern.test(text)) tags.add(tag);
4347
+ }
4348
+ }
4349
+ for (const part of message.parts) {
4350
+ if (part.type === "plan") tags.add("planning");
4351
+ if (part.type !== "tool") continue;
4352
+ const toolName = `${part.tool ?? ""} ${part.title ?? ""}`;
4353
+ const toolPayload = stringifyToolPayload(part);
4354
+ if (PLAN_TOOL_RE.test(toolName)) tags.add("planning");
4355
+ if (READ_TOOL_RE.test(toolName)) readToolCount += 1;
4356
+ if (EDIT_TOOL_RE.test(toolName)) editToolCount += 1;
4357
+ if (TESTING_COMMAND_RE.test(toolPayload)) tags.add("testing");
4358
+ if (GIT_COMMAND_RE.test(toolPayload)) tags.add("git-ops");
4359
+ if (BUILD_DEPLOY_COMMAND_RE.test(toolPayload)) tags.add("build-deploy");
4360
+ if (hasEditedDocPath(part.state?.arguments) || hasEditedDocPath(part.state?.input)) {
4361
+ tags.add("docs");
4362
+ }
4363
+ }
4364
+ }
4365
+ if (readToolCount >= 3 && editToolCount <= 1) {
4366
+ tags.add("exploration");
4367
+ }
4368
+ return TAG_ORDER.filter((tag) => tags.has(tag));
4369
+ }
4370
+ function partText(part) {
4371
+ return typeof part.text === "string" ? part.text : "";
4372
+ }
4373
+ function stringifyToolPayload(part) {
4374
+ return [
4375
+ part.tool,
4376
+ part.title,
4377
+ valueToText(part.input),
4378
+ valueToText(part.output),
4379
+ valueToText(part.state)
4380
+ ].filter(Boolean).join("\n");
4381
+ }
4382
+ function valueToText(value) {
4383
+ if (value == null) return "";
4384
+ if (typeof value === "string") return value;
4385
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
4386
+ try {
4387
+ return JSON.stringify(value);
4388
+ } catch {
4389
+ return "";
4390
+ }
4391
+ }
4392
+ function hasEditedDocPath(value) {
4393
+ if (value == null) return false;
4394
+ if (typeof value === "string") return DOC_PATH_RE.test(value);
4395
+ if (Array.isArray(value)) return value.some(hasEditedDocPath);
4396
+ if (typeof value !== "object") return false;
4397
+ const record = value;
4398
+ for (const key of ["path", "file", "filePath", "file_path", "targetPath", "target_path"]) {
4399
+ const raw = record[key];
4400
+ if (typeof raw === "string" && DOC_PATH_RE.test(raw)) return true;
4401
+ }
4402
+ return Object.values(record).some(hasEditedDocPath);
4403
+ }
4404
+ var CACHE_VERSION = 6;
3553
4405
  var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
3554
4406
  var CACHE_FILENAME = "codesesh.db";
3555
4407
  var LEGACY_CACHE_FILENAME = "scan-cache.json";
3556
- function getCacheDir() {
3557
- return join7(homedir2(), ".cache", "codesesh");
4408
+ function getCacheDir2() {
4409
+ return join8(homedir4(), ".cache", "codesesh");
3558
4410
  }
3559
- function getCachePath() {
3560
- return join7(getCacheDir(), CACHE_FILENAME);
4411
+ function getCachePath2() {
4412
+ return join8(getCacheDir2(), CACHE_FILENAME);
3561
4413
  }
3562
4414
  function getLegacyCachePath() {
3563
- return join7(getCacheDir(), LEGACY_CACHE_FILENAME);
4415
+ return join8(getCacheDir2(), LEGACY_CACHE_FILENAME);
3564
4416
  }
3565
4417
  function hasCacheStorage() {
3566
- return existsSync7(getCachePath());
4418
+ return existsSync9(getCachePath2());
3567
4419
  }
3568
4420
  function withCacheDb(fn) {
3569
- const db = openDb(getCachePath());
4421
+ const db = openDb(getCachePath2());
3570
4422
  if (!db) return null;
3571
4423
  try {
3572
4424
  ensureSchema(db);
@@ -3604,6 +4456,9 @@ function ensureSchema(db) {
3604
4456
  slug TEXT NOT NULL,
3605
4457
  title TEXT NOT NULL,
3606
4458
  directory TEXT NOT NULL,
4459
+ project_identity_kind TEXT NOT NULL DEFAULT 'path',
4460
+ project_identity_key TEXT NOT NULL DEFAULT '',
4461
+ project_display_name TEXT NOT NULL DEFAULT '',
3607
4462
  time_created INTEGER NOT NULL,
3608
4463
  time_updated INTEGER,
3609
4464
  activity_time INTEGER NOT NULL,
@@ -3613,6 +4468,31 @@ function ensureSchema(db) {
3613
4468
  UNIQUE(agent_name, session_id)
3614
4469
  );
3615
4470
 
4471
+ CREATE TABLE IF NOT EXISTS project_sessions (
4472
+ agent_name TEXT NOT NULL,
4473
+ session_id TEXT NOT NULL,
4474
+ identity_kind TEXT NOT NULL,
4475
+ identity_key TEXT NOT NULL,
4476
+ display_name TEXT NOT NULL,
4477
+ directory TEXT NOT NULL,
4478
+ activity_time INTEGER NOT NULL,
4479
+ PRIMARY KEY (agent_name, session_id)
4480
+ );
4481
+
4482
+ CREATE INDEX IF NOT EXISTS idx_project_sessions_identity
4483
+ ON project_sessions(identity_kind, identity_key);
4484
+
4485
+ CREATE VIEW IF NOT EXISTS project_groups_v AS
4486
+ SELECT
4487
+ identity_kind,
4488
+ identity_key,
4489
+ MIN(display_name) AS display_name,
4490
+ GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
4491
+ COUNT(*) AS session_count,
4492
+ MAX(activity_time) AS last_activity
4493
+ FROM project_sessions
4494
+ GROUP BY identity_kind, identity_key;
4495
+
3616
4496
  CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
3617
4497
  title,
3618
4498
  content_text,
@@ -3637,6 +4517,18 @@ function ensureSchema(db) {
3637
4517
  VALUES (new.id, new.title, new.content_text);
3638
4518
  END;
3639
4519
  `);
4520
+ const sessionDocumentColumns = new Set(
4521
+ db.prepare("PRAGMA table_info(session_documents)").all().map(
4522
+ (row) => String(row.name)
4523
+ )
4524
+ );
4525
+ if (!sessionDocumentColumns.has("project_identity_kind")) {
4526
+ db.exec(`
4527
+ ALTER TABLE session_documents ADD COLUMN project_identity_kind TEXT NOT NULL DEFAULT 'path';
4528
+ ALTER TABLE session_documents ADD COLUMN project_identity_key TEXT NOT NULL DEFAULT '';
4529
+ ALTER TABLE session_documents ADD COLUMN project_display_name TEXT NOT NULL DEFAULT '';
4530
+ `);
4531
+ }
3640
4532
  const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
3641
4533
  const version = Number(versionRow?.value ?? 0);
3642
4534
  if (version === CACHE_VERSION) {
@@ -3646,6 +4538,7 @@ function ensureSchema(db) {
3646
4538
  DELETE FROM agent_cache;
3647
4539
  DELETE FROM cached_sessions;
3648
4540
  DELETE FROM session_documents;
4541
+ DELETE FROM project_sessions;
3649
4542
  INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild');
3650
4543
  INSERT INTO cache_meta(key, value)
3651
4544
  VALUES ('version', '${CACHE_VERSION}')
@@ -3662,7 +4555,10 @@ function sessionContentHash(session) {
3662
4555
  session.stats.message_count,
3663
4556
  session.stats.total_input_tokens,
3664
4557
  session.stats.total_output_tokens,
4558
+ session.stats.total_cache_read_tokens ?? 0,
4559
+ session.stats.total_cache_create_tokens ?? 0,
3665
4560
  session.stats.total_cost,
4561
+ session.stats.cost_source ?? "",
3666
4562
  session.stats.total_tokens ?? 0
3667
4563
  ]);
3668
4564
  }
@@ -3728,7 +4624,7 @@ function buildSessionContent(session) {
3728
4624
  }
3729
4625
  function deleteLegacyCacheFile() {
3730
4626
  const legacyPath = getLegacyCachePath();
3731
- if (!existsSync7(legacyPath)) {
4627
+ if (!existsSync9(legacyPath)) {
3732
4628
  return;
3733
4629
  }
3734
4630
  try {
@@ -3773,6 +4669,7 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
3773
4669
  withCacheDb((db) => {
3774
4670
  const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
3775
4671
  const deleteSessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
4672
+ const deleteProjectSessions = db.prepare("DELETE FROM project_sessions WHERE agent_name = ?");
3776
4673
  const upsertAgent = db.prepare(`
3777
4674
  INSERT INTO agent_cache(agent_name, timestamp)
3778
4675
  VALUES (?, ?)
@@ -3782,18 +4679,40 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
3782
4679
  INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
3783
4680
  VALUES (?, ?, ?, ?)
3784
4681
  `);
4682
+ const insertProjectSession = db.prepare(`
4683
+ INSERT INTO project_sessions(
4684
+ agent_name,
4685
+ session_id,
4686
+ identity_kind,
4687
+ identity_key,
4688
+ display_name,
4689
+ directory,
4690
+ activity_time
4691
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
4692
+ `);
3785
4693
  const write = db.transaction(() => {
3786
4694
  const timestamp = Date.now();
3787
4695
  deleteAgent.run(agentName);
3788
4696
  deleteSessions.run(agentName);
4697
+ deleteProjectSessions.run(agentName);
3789
4698
  upsertAgent.run(agentName, timestamp);
3790
4699
  for (const session of sessions) {
4700
+ const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
3791
4701
  insertSession.run(
3792
4702
  agentName,
3793
4703
  session.id,
3794
4704
  JSON.stringify(session),
3795
4705
  meta[session.id] ? JSON.stringify(meta[session.id]) : null
3796
4706
  );
4707
+ insertProjectSession.run(
4708
+ agentName,
4709
+ session.id,
4710
+ identity.kind,
4711
+ identity.key,
4712
+ identity.displayName,
4713
+ session.directory,
4714
+ session.time_updated ?? session.time_created
4715
+ );
3797
4716
  }
3798
4717
  });
3799
4718
  write();
@@ -3809,14 +4728,15 @@ function clearCache() {
3809
4728
  db.exec(`
3810
4729
  DELETE FROM agent_cache;
3811
4730
  DELETE FROM cached_sessions;
4731
+ DELETE FROM project_sessions;
3812
4732
  `);
3813
4733
  });
3814
4734
  deleteLegacyCacheFile();
3815
- const cachePath = getCachePath();
4735
+ const cachePath = getCachePath2();
3816
4736
  const walPath = `${cachePath}-wal`;
3817
4737
  const shmPath = `${cachePath}-shm`;
3818
4738
  for (const filePath of [walPath, shmPath]) {
3819
- if (!existsSync7(filePath)) {
4739
+ if (!existsSync9(filePath)) {
3820
4740
  continue;
3821
4741
  }
3822
4742
  try {
@@ -3876,17 +4796,23 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
3876
4796
  slug,
3877
4797
  title,
3878
4798
  directory,
4799
+ project_identity_kind,
4800
+ project_identity_key,
4801
+ project_display_name,
3879
4802
  time_created,
3880
4803
  time_updated,
3881
4804
  activity_time,
3882
4805
  content_text,
3883
4806
  content_hash,
3884
4807
  indexed_at
3885
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4808
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3886
4809
  ON CONFLICT(agent_name, session_id) DO UPDATE SET
3887
4810
  slug = excluded.slug,
3888
4811
  title = excluded.title,
3889
4812
  directory = excluded.directory,
4813
+ project_identity_kind = excluded.project_identity_kind,
4814
+ project_identity_key = excluded.project_identity_key,
4815
+ project_display_name = excluded.project_display_name,
3890
4816
  time_created = excluded.time_created,
3891
4817
  time_updated = excluded.time_updated,
3892
4818
  activity_time = excluded.activity_time,
@@ -3900,12 +4826,16 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData) {
3900
4826
  }
3901
4827
  for (const entry of loaded) {
3902
4828
  const activityTime = entry.session.time_updated ?? entry.session.time_created;
4829
+ const identity = entry.session.project_identity ?? computeIdentity(entry.session.directory, realFs);
3903
4830
  upsertRow.run(
3904
4831
  agentName,
3905
4832
  entry.session.id,
3906
4833
  entry.session.slug,
3907
4834
  entry.session.title,
3908
4835
  entry.session.directory,
4836
+ identity.kind,
4837
+ identity.key,
4838
+ identity.displayName,
3909
4839
  entry.session.time_created,
3910
4840
  entry.session.time_updated ?? null,
3911
4841
  activityTime,
@@ -3943,7 +4873,7 @@ function searchSessions(query, options = {}) {
3943
4873
  JOIN session_documents d ON d.id = session_documents_fts.rowid
3944
4874
  WHERE session_documents_fts MATCH ?
3945
4875
  AND (? IS NULL OR d.agent_name = ?)
3946
- AND (? IS NULL OR LOWER(d.directory) LIKE ?)
4876
+ AND (? IS NULL OR d.project_identity_key = ? OR LOWER(d.directory) LIKE ?)
3947
4877
  AND (? IS NULL OR d.activity_time >= ?)
3948
4878
  AND (? IS NULL OR d.activity_time <= ?)
3949
4879
  ORDER BY bm25(session_documents_fts, 8.0, 1.0), d.activity_time DESC
@@ -3953,7 +4883,8 @@ function searchSessions(query, options = {}) {
3953
4883
  ftsQuery,
3954
4884
  options.agent ?? null,
3955
4885
  options.agent ?? null,
3956
- options.cwd?.toLowerCase() ?? null,
4886
+ options.cwd ?? null,
4887
+ options.cwd ? computeIdentity(options.cwd, realFs).key : null,
3957
4888
  options.cwd ? `%${options.cwd.toLowerCase()}%` : null,
3958
4889
  options.from ?? null,
3959
4890
  options.from ?? null,
@@ -3982,6 +4913,35 @@ function searchSessions(query, options = {}) {
3982
4913
  });
3983
4914
  return results ?? [];
3984
4915
  }
4916
+ function listCachedProjectGroups(sessions) {
4917
+ if (sessions) {
4918
+ return buildProjectGroups(sessions);
4919
+ }
4920
+ if (!hasCacheStorage()) {
4921
+ return [];
4922
+ }
4923
+ const groups = withCacheDb((db) => {
4924
+ const rows = db.prepare(
4925
+ `
4926
+ SELECT identity_kind, identity_key, display_name, sources_csv, session_count, last_activity
4927
+ FROM project_groups_v
4928
+ ORDER BY
4929
+ CASE identity_kind WHEN 'loose' THEN 1 ELSE 0 END,
4930
+ last_activity IS NULL,
4931
+ last_activity DESC
4932
+ `
4933
+ ).all();
4934
+ return rows.map((row) => ({
4935
+ identityKind: row.identity_kind ?? "path",
4936
+ identityKey: String(row.identity_key ?? ""),
4937
+ displayName: String(row.display_name ?? ""),
4938
+ sources: String(row.sources_csv ?? "").split(",").filter(Boolean).sort(),
4939
+ sessionCount: Number(row.session_count ?? 0),
4940
+ lastActivity: row.last_activity == null ? null : Number(row.last_activity)
4941
+ }));
4942
+ });
4943
+ return groups ?? [];
4944
+ }
3985
4945
  function isPathScopeMatch(queryPath, sessionPath) {
3986
4946
  if (!sessionPath) return false;
3987
4947
  const q = resolve(queryPath);
@@ -3991,17 +4951,44 @@ function isPathScopeMatch(queryPath, sessionPath) {
3991
4951
  const qn = sepNorm(q);
3992
4952
  return sn === qn || sn.startsWith(qn + "/") || qn.startsWith(sn + "/");
3993
4953
  }
4954
+ function createIdentityResolver() {
4955
+ const cache = /* @__PURE__ */ new Map();
4956
+ return (directory) => {
4957
+ const key = directory || "";
4958
+ const cached = cache.get(key);
4959
+ if (cached) return cached;
4960
+ const identity = computeIdentity(directory, realFs);
4961
+ cache.set(key, identity);
4962
+ return identity;
4963
+ };
4964
+ }
4965
+ function attachProjectIdentities(sessions) {
4966
+ const resolveIdentity = createIdentityResolver();
4967
+ return sessions.map((session) => {
4968
+ if (session.project_identity) return session;
4969
+ return {
4970
+ ...session,
4971
+ project_identity: resolveIdentity(session.directory)
4972
+ };
4973
+ });
4974
+ }
4975
+ function isProjectScopeMatch(queryPath, session) {
4976
+ if (!session.directory) return false;
4977
+ const queryIdentity = computeIdentity(queryPath, realFs);
4978
+ if (session.project_identity?.key === queryIdentity.key) return true;
4979
+ return isPathScopeMatch(queryPath, session.directory);
4980
+ }
3994
4981
  function filterSessions(sessions, options) {
3995
4982
  let result = sessions;
3996
4983
  if (options.cwd) {
3997
4984
  const cwd = options.cwd;
3998
- result = result.filter((s) => isPathScopeMatch(cwd, s.directory));
4985
+ result = result.filter((s) => isProjectScopeMatch(cwd, s));
3999
4986
  }
4000
4987
  if (options.from != null) {
4001
- result = result.filter((s) => s.time_created >= options.from);
4988
+ result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
4002
4989
  }
4003
4990
  if (options.to != null) {
4004
- result = result.filter((s) => s.time_created <= options.to);
4991
+ result = result.filter((s) => (s.time_updated ?? s.time_created) <= options.to);
4005
4992
  }
4006
4993
  return result;
4007
4994
  }
@@ -4014,6 +5001,137 @@ function buildAgentCacheMeta(agent) {
4014
5001
  }
4015
5002
  return meta;
4016
5003
  }
5004
+ function getSmartTagWorkerCount(sessionCount) {
5005
+ if (sessionCount < 8) return 1;
5006
+ return Math.min(sessionCount, Math.max(1, Math.min(4, availableParallelism() - 1)));
5007
+ }
5008
+ function chunkSessions(items, chunkCount) {
5009
+ const chunks = Array.from({ length: chunkCount }, () => []);
5010
+ items.forEach((item, index) => {
5011
+ chunks[index % chunkCount].push(item);
5012
+ });
5013
+ return chunks.filter((chunk) => chunk.length > 0);
5014
+ }
5015
+ function ensureSessionTagsSync(agent, sessions) {
5016
+ let changed = false;
5017
+ const tagged = sessions.map((session) => {
5018
+ const sourceUpdatedAt = session.time_updated ?? session.time_created;
5019
+ const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
5020
+ if (currentTags && session.smart_tags_source_updated_at === sourceUpdatedAt) {
5021
+ return session;
5022
+ }
5023
+ try {
5024
+ const data = agent.getSessionData(session.id);
5025
+ const tags = classifySessionTags(data);
5026
+ changed = true;
5027
+ return {
5028
+ ...session,
5029
+ smart_tags: tags,
5030
+ smart_tags_source_updated_at: getSmartTagSourceTimestamp(data)
5031
+ };
5032
+ } catch {
5033
+ return session;
5034
+ }
5035
+ });
5036
+ return { sessions: tagged, changed };
5037
+ }
5038
+ async function classifySessionTagsInWorker(agentName, sessionIds) {
5039
+ return new Promise((resolveWorker, rejectWorker) => {
5040
+ const worker = new Worker(
5041
+ `
5042
+ const { parentPort, workerData } = require("node:worker_threads");
5043
+
5044
+ (async () => {
5045
+ const {
5046
+ createRegisteredAgents,
5047
+ classifySessionTags,
5048
+ getSmartTagSourceTimestamp,
5049
+ } = await import("@codesesh/core");
5050
+
5051
+ const agent = createRegisteredAgents().find((item) => item.name === workerData.agentName);
5052
+ const results = [];
5053
+
5054
+ if (agent) {
5055
+ for (const sessionId of workerData.sessionIds) {
5056
+ try {
5057
+ const data = agent.getSessionData(sessionId);
5058
+ results.push({
5059
+ id: sessionId,
5060
+ tags: classifySessionTags(data),
5061
+ sourceUpdatedAt: getSmartTagSourceTimestamp(data),
5062
+ });
5063
+ } catch (error) {
5064
+ results.push({
5065
+ id: sessionId,
5066
+ error: error instanceof Error ? error.message : String(error),
5067
+ });
5068
+ }
5069
+ }
5070
+ }
5071
+
5072
+ parentPort?.postMessage(results);
5073
+ })().catch((error) => {
5074
+ parentPort?.postMessage([
5075
+ {
5076
+ id: "",
5077
+ error: error instanceof Error ? error.message : String(error),
5078
+ },
5079
+ ]);
5080
+ });
5081
+ `,
5082
+ {
5083
+ eval: true,
5084
+ workerData: { agentName, sessionIds }
5085
+ }
5086
+ );
5087
+ worker.once("message", (results) => {
5088
+ resolveWorker(results);
5089
+ });
5090
+ worker.once("error", rejectWorker);
5091
+ worker.once("exit", (code) => {
5092
+ if (code !== 0) {
5093
+ rejectWorker(new Error(`Smart tag worker exited with code ${code}`));
5094
+ }
5095
+ });
5096
+ });
5097
+ }
5098
+ async function ensureSessionTags(agent, sessions) {
5099
+ const staleSessions = sessions.filter((session) => {
5100
+ const sourceUpdatedAt = session.time_updated ?? session.time_created;
5101
+ const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
5102
+ return !currentTags || session.smart_tags_source_updated_at !== sourceUpdatedAt;
5103
+ });
5104
+ if (staleSessions.length === 0) {
5105
+ return { sessions, changed: false };
5106
+ }
5107
+ const workerCount = getSmartTagWorkerCount(staleSessions.length);
5108
+ if (workerCount <= 1) {
5109
+ return ensureSessionTagsSync(agent, sessions);
5110
+ }
5111
+ try {
5112
+ const results = (await Promise.all(
5113
+ chunkSessions(
5114
+ staleSessions.map((session) => session.id),
5115
+ workerCount
5116
+ ).map((sessionIds) => classifySessionTagsInWorker(agent.name, sessionIds))
5117
+ )).flat();
5118
+ const resultMap = new Map(results.filter((item) => item.tags).map((item) => [item.id, item]));
5119
+ return {
5120
+ changed: resultMap.size > 0,
5121
+ sessions: sessions.map((session) => {
5122
+ const result = resultMap.get(session.id);
5123
+ if (!result?.tags || result.sourceUpdatedAt == null) return session;
5124
+ return {
5125
+ ...session,
5126
+ smart_tags: result.tags,
5127
+ smart_tags_source_updated_at: result.sourceUpdatedAt
5128
+ };
5129
+ })
5130
+ };
5131
+ } catch {
5132
+ return ensureSessionTagsSync(agent, sessions);
5133
+ }
5134
+ }
4017
5135
  async function scanAgentSmart(agent, options, onProgress) {
4018
5136
  const useCache = options.useCache ?? true;
4019
5137
  const canValidateCache = Boolean(agent.checkForChanges && agent.incrementalScan);
@@ -4050,18 +5168,27 @@ async function scanAgentSmart(agent, options, onProgress) {
4050
5168
  const updatedSessions = await Promise.resolve(
4051
5169
  agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
4052
5170
  );
4053
- saveCachedSessions(agent.name, updatedSessions, buildAgentCacheMeta(agent));
5171
+ const sessionsWithIdentity = attachProjectIdentities(updatedSessions);
5172
+ const tagged2 = options.includeSmartTags === false ? { sessions: sessionsWithIdentity, changed: false } : await ensureSessionTags(agent, sessionsWithIdentity);
5173
+ if (options.writeCache !== false && options.from == null && options.to == null) {
5174
+ saveCachedSessions(agent.name, tagged2.sessions, buildAgentCacheMeta(agent));
5175
+ }
4054
5176
  onProgress?.({
4055
5177
  agent: agent.name,
4056
5178
  phase: "complete",
4057
- newCount: updatedSessions.length
5179
+ newCount: tagged2.sessions.length
4058
5180
  });
4059
- const filtered2 = filterSessions(updatedSessions, options);
5181
+ const filtered2 = filterSessions(tagged2.sessions, options);
4060
5182
  return { agent, heads: filtered2, fromCache: true, refreshed: true };
4061
5183
  }
4062
5184
  onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
4063
5185
  }
4064
- const filtered = filterSessions(cached.sessions, options);
5186
+ const cachedWithIdentity = attachProjectIdentities(cached.sessions);
5187
+ const tagged = options.includeSmartTags === false ? { sessions: cachedWithIdentity, changed: false } : await ensureSessionTags(agent, cachedWithIdentity);
5188
+ if (tagged.changed && options.writeCache !== false && options.from == null && options.to == null) {
5189
+ saveCachedSessions(agent.name, tagged.sessions, buildAgentCacheMeta(agent));
5190
+ }
5191
+ const filtered = filterSessions(tagged.sessions, options);
4065
5192
  return { agent, heads: filtered, fromCache: true };
4066
5193
  }
4067
5194
  }
@@ -4076,12 +5203,16 @@ async function scanAgentFull(agent, options, onProgress) {
4076
5203
  }
4077
5204
  try {
4078
5205
  const scanMarker = perf.start(`agent:${agent.name}:scan`);
4079
- const heads = agent.scan();
5206
+ const heads = agent.scan({ from: options.from, to: options.to, fast: options.fast });
4080
5207
  perf.end(scanMarker);
5208
+ const headsWithIdentity = attachProjectIdentities(heads);
5209
+ const tagged = options.includeSmartTags === false ? { sessions: headsWithIdentity, changed: false } : await ensureSessionTags(agent, headsWithIdentity);
4081
5210
  const meta = buildAgentCacheMeta(agent);
4082
- saveCachedSessions(agent.name, heads, meta);
4083
- onProgress?.({ agent: agent.name, phase: "complete", newCount: heads.length });
4084
- const filtered = filterSessions(heads, options);
5211
+ if (options.writeCache !== false && options.from == null && options.to == null) {
5212
+ saveCachedSessions(agent.name, tagged.sessions, meta);
5213
+ }
5214
+ onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
5215
+ const filtered = filterSessions(tagged.sessions, options);
4085
5216
  return { agent, heads: filtered, fromCache: false };
4086
5217
  } catch (err) {
4087
5218
  console.error(`Error scanning ${agent.name}:`, err);
@@ -4118,19 +5249,25 @@ async function scanSessionsAsync(options = {}, onProgress) {
4118
5249
  }
4119
5250
  var BOOKMARK_DB_FILENAME = "state.db";
4120
5251
  var BOOKMARK_DB_VERSION = 1;
5252
+ var BookmarkStorageUnavailableError = class extends Error {
5253
+ constructor() {
5254
+ super("SQLite state database is unavailable");
5255
+ this.name = "BookmarkStorageUnavailableError";
5256
+ }
5257
+ };
4121
5258
  function getStateDir() {
4122
5259
  const p = platform2();
4123
5260
  if (p === "darwin") {
4124
- return join8(homedir3(), "Library", "Application Support", "codesesh");
5261
+ return join9(homedir5(), "Library", "Application Support", "codesesh");
4125
5262
  }
4126
5263
  if (p === "win32") {
4127
5264
  const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
4128
- return join8(appData ?? join8(homedir3(), "AppData", "Roaming"), "codesesh");
5265
+ return join9(appData ?? join9(homedir5(), "AppData", "Roaming"), "codesesh");
4129
5266
  }
4130
- return join8(process.env.XDG_DATA_HOME ?? join8(homedir3(), ".local", "share"), "codesesh");
5267
+ return join9(process.env.XDG_DATA_HOME ?? join9(homedir5(), ".local", "share"), "codesesh");
4131
5268
  }
4132
5269
  function getStateDbPath() {
4133
- return join8(getStateDir(), BOOKMARK_DB_FILENAME);
5270
+ return join9(getStateDir(), BOOKMARK_DB_FILENAME);
4134
5271
  }
4135
5272
  function ensureSchema2(db) {
4136
5273
  db.exec(`
@@ -4166,7 +5303,7 @@ function ensureSchema2(db) {
4166
5303
  function withStateDb(fn) {
4167
5304
  const db = openDb(getStateDbPath());
4168
5305
  if (!db) {
4169
- throw new Error("SQLite state database is unavailable");
5306
+ throw new BookmarkStorageUnavailableError();
4170
5307
  }
4171
5308
  try {
4172
5309
  ensureSchema2(db);
@@ -4348,21 +5485,39 @@ export {
4348
5485
  basenameTitle,
4349
5486
  resolveSessionTitle,
4350
5487
  perf,
5488
+ getPricingRegistry,
5489
+ hasBillablePricing,
5490
+ refreshPricingCache,
5491
+ pricingResolver,
5492
+ estimateCostForTokens,
5493
+ applyMessageCost,
5494
+ applyMessageCosts,
5495
+ withEstimatedSessionCost,
5496
+ estimateTokenCost,
4351
5497
  openDbReadOnly,
4352
5498
  openDb,
4353
5499
  isSqliteAvailable,
5500
+ fallbackDisplayName,
5501
+ realFs,
5502
+ buildProjectGroups,
5503
+ normalizeGitRemote,
5504
+ computeIdentity,
5505
+ getSmartTagSourceTimestamp,
5506
+ classifySessionTags,
4354
5507
  loadCachedSessions,
4355
5508
  saveCachedSessions,
4356
5509
  clearCache,
4357
5510
  getCacheInfo,
4358
5511
  syncSessionSearchIndex,
4359
5512
  searchSessions,
5513
+ listCachedProjectGroups,
4360
5514
  filterSessions,
4361
5515
  scanSessions,
4362
5516
  scanSessionsAsync,
5517
+ BookmarkStorageUnavailableError,
4363
5518
  listBookmarks,
4364
5519
  upsertBookmark,
4365
5520
  importBookmarks,
4366
5521
  deleteBookmark
4367
5522
  };
4368
- //# sourceMappingURL=chunk-EIIG7J6V.js.map
5523
+ //# sourceMappingURL=chunk-FZNZAMTZ.js.map