codesesh 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,10 +19,12 @@ Your browser will open at `http://localhost:4321` with all your sessions ready t
19
19
  ## Features
20
20
 
21
21
  - **Unified Timeline** — Browse sessions across all your AI agents in a single, searchable interface
22
+ - **Dashboard & Activity Trends** — See totals, daily activity, agent distribution, and recent sessions
22
23
  - **Full Conversation Replay** — Read every message, tool call, and reasoning step exactly as it happened
23
24
  - **Cost & Token Visibility** — See exactly how many tokens and dollars each session consumed
24
25
  - **Zero Configuration** — Just run it. CodeSesh auto-discovers everything on your filesystem
25
26
  - **100% Local & Private** — Nothing leaves your machine. No accounts, no cloud sync, no telemetry
27
+ - **Live Refresh** — Local session changes are picked up automatically while the server is running
26
28
 
27
29
  ## Supported Agents
28
30
 
@@ -46,6 +48,9 @@ npx codesesh --port 8080
46
48
  # Only show sessions from the last 3 days
47
49
  npx codesesh --days 3
48
50
 
51
+ # Jump directly to a session
52
+ npx codesesh --session claudecode://3b0e4ead-eba9-43e7-9fac-b30647e189f8
53
+
49
54
  # Filter to sessions from current project
50
55
  npx codesesh --cwd .
51
56
 
@@ -54,6 +59,9 @@ npx codesesh --agent claudecode
54
59
 
55
60
  # Output JSON instead of starting server
56
61
  npx codesesh --json
62
+
63
+ # Show performance trace logs
64
+ npx codesesh --trace
57
65
  ```
58
66
 
59
67
  ## CLI Options
@@ -66,8 +74,12 @@ npx codesesh --json
66
74
  | `--agent` | `-a` | all | Filter to specific agent(s), comma-separated |
67
75
  | `--from` | — | — | Sessions created after this date `YYYY-MM-DD` |
68
76
  | `--to` | — | — | Sessions created before this date `YYYY-MM-DD` |
77
+ | `--session` | `-s` | — | Directly open a session (`agent://session-id`) |
69
78
  | `--json` | `-j` | `false` | Output JSON and exit (no server) |
70
79
  | `--no-open` | — | `false` | Don't auto-open the browser |
80
+ | `--trace` | — | `false` | Print performance trace logs |
81
+ | `--cache` | — | `true` | Use cached scan results when available |
82
+ | `--clear-cache` | — | `false` | Clear scan cache before starting |
71
83
 
72
84
  ## Requirements
73
85
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  // ../core/dist/index.mjs
4
4
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
5
- import { join as join2, basename as basename2 } from "path";
5
+ import { join as join2, 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";
@@ -13,7 +13,7 @@ import { join as join3 } from "path";
13
13
  import { createRequire } from "module";
14
14
  import { createHash } from "crypto";
15
15
  import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
16
- import { join as join4, basename as basename3, dirname } from "path";
16
+ import { join as join4, basename as basename3, dirname as dirname2 } from "path";
17
17
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
18
18
  import { join as join5, basename as basename4 } from "path";
19
19
  import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
@@ -205,6 +205,7 @@ var PerfTracer = class {
205
205
  }
206
206
  };
207
207
  var perf = new PerfTracer();
208
+ var RECENT_SESSION_REVALIDATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
208
209
  function parseTimestampMs(data) {
209
210
  const raw = String(data["timestamp"] ?? "").trim();
210
211
  if (!raw) return 0;
@@ -348,17 +349,30 @@ var ClaudeCodeAgent = class extends BaseAgent {
348
349
  if (!this.basePath) {
349
350
  return { hasChanges: false, timestamp: Date.now() };
350
351
  }
351
- const changedIds = [];
352
- for (const session of cachedSessions) {
352
+ const now = Date.now();
353
+ const changedIds = /* @__PURE__ */ new Set();
354
+ const recentSessions = cachedSessions.filter(
355
+ (session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS
356
+ );
357
+ for (const session of recentSessions) {
358
+ changedIds.add(session.id);
353
359
  const meta = this.sessionMetaMap.get(session.id);
354
360
  if (!meta) continue;
361
+ delete this.sessionsIndexCache[basename2(dirname(meta.sourcePath))];
362
+ }
363
+ for (const session of cachedSessions) {
364
+ const meta = this.sessionMetaMap.get(session.id);
365
+ if (!meta) {
366
+ changedIds.add(session.id);
367
+ continue;
368
+ }
355
369
  try {
356
370
  const stat = statSync(meta.sourcePath);
357
371
  if (stat.mtimeMs > sinceTimestamp) {
358
- changedIds.push(session.id);
372
+ changedIds.add(session.id);
359
373
  }
360
374
  } catch {
361
- changedIds.push(session.id);
375
+ changedIds.add(session.id);
362
376
  }
363
377
  }
364
378
  try {
@@ -368,14 +382,14 @@ var ClaudeCodeAgent = class extends BaseAgent {
368
382
  }
369
383
  const hasNewFiles = totalFiles > cachedSessions.length;
370
384
  return {
371
- hasChanges: changedIds.length > 0 || hasNewFiles,
372
- changedIds,
385
+ hasChanges: changedIds.size > 0 || hasNewFiles,
386
+ changedIds: Array.from(changedIds),
373
387
  timestamp: Date.now()
374
388
  };
375
389
  } catch {
376
390
  return {
377
- hasChanges: changedIds.length > 0,
378
- changedIds,
391
+ hasChanges: changedIds.size > 0,
392
+ changedIds: Array.from(changedIds),
379
393
  timestamp: Date.now()
380
394
  };
381
395
  }
@@ -1024,6 +1038,7 @@ var OpenCodeAgent = class extends BaseAgent {
1024
1038
  const timeUpdated = Number(row.time_updated ?? timeCreated);
1025
1039
  const slug = `opencode/${id}`;
1026
1040
  const directory = String(row.directory ?? "");
1041
+ const stats = hasMessageTable ? this.readSessionStats(db, id) : null;
1027
1042
  heads.push({
1028
1043
  id,
1029
1044
  slug,
@@ -1032,10 +1047,10 @@ var OpenCodeAgent = class extends BaseAgent {
1032
1047
  time_created: timeCreated,
1033
1048
  time_updated: timeUpdated,
1034
1049
  stats: {
1035
- message_count: Number(row.message_count ?? 0),
1036
- total_input_tokens: 0,
1037
- total_output_tokens: 0,
1038
- total_cost: 0
1050
+ message_count: stats?.message_count ?? Number(row.message_count ?? 0),
1051
+ total_input_tokens: stats?.total_input_tokens ?? 0,
1052
+ total_output_tokens: stats?.total_output_tokens ?? 0,
1053
+ total_cost: stats?.total_cost ?? 0
1039
1054
  }
1040
1055
  });
1041
1056
  if (this.dbPath) {
@@ -1088,6 +1103,30 @@ var OpenCodeAgent = class extends BaseAgent {
1088
1103
  incrementalScan(_cachedSessions, _changedIds) {
1089
1104
  return this.scan();
1090
1105
  }
1106
+ readSessionStats(db, sessionId) {
1107
+ try {
1108
+ const rows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
1109
+ let totalCost = 0;
1110
+ let totalInputTokens = 0;
1111
+ let totalOutputTokens = 0;
1112
+ for (const row of rows) {
1113
+ const msgData = JSON.parse(String(row.data ?? "{}"));
1114
+ const cost = Number(msgData.cost ?? 0);
1115
+ const tokens = msgData.tokens;
1116
+ totalCost += cost;
1117
+ totalInputTokens += Number(tokens?.input ?? 0);
1118
+ totalOutputTokens += Number(tokens?.output ?? 0);
1119
+ }
1120
+ return {
1121
+ message_count: rows.length,
1122
+ total_input_tokens: totalInputTokens,
1123
+ total_output_tokens: totalOutputTokens,
1124
+ total_cost: totalCost
1125
+ };
1126
+ } catch {
1127
+ return null;
1128
+ }
1129
+ }
1091
1130
  getSessionData(sessionId) {
1092
1131
  if (!this.dbPath) {
1093
1132
  this.dbPath = this.findDbPath();
@@ -1301,7 +1340,7 @@ var KimiAgent = class extends BaseAgent {
1301
1340
  parseSessionDir(sessionDir) {
1302
1341
  try {
1303
1342
  const sessionId = basename3(sessionDir);
1304
- const projectHash = basename3(dirname(sessionDir));
1343
+ const projectHash = basename3(dirname2(sessionDir));
1305
1344
  const contextFile = join4(sessionDir, "context.jsonl");
1306
1345
  const wireFile = join4(sessionDir, "wire.jsonl");
1307
1346
  if (!existsSync4(contextFile) && !existsSync4(wireFile)) return null;
@@ -1351,6 +1390,7 @@ var KimiAgent = class extends BaseAgent {
1351
1390
  perf.end(parseMarker);
1352
1391
  if (!meta) continue;
1353
1392
  this.sessionMetaMap.set(meta.id, meta);
1393
+ const stats = this.extractStats(meta.sourcePath);
1354
1394
  heads.push({
1355
1395
  id: meta.id,
1356
1396
  slug: `kimi/${meta.id}`,
@@ -1358,12 +1398,7 @@ var KimiAgent = class extends BaseAgent {
1358
1398
  directory: meta.cwd,
1359
1399
  time_created: meta.createdAt,
1360
1400
  time_updated: meta.createdAt,
1361
- stats: {
1362
- message_count: 0,
1363
- total_input_tokens: 0,
1364
- total_output_tokens: 0,
1365
- total_cost: 0
1366
- }
1401
+ stats
1367
1402
  });
1368
1403
  } catch {
1369
1404
  }
@@ -1421,6 +1456,7 @@ var KimiAgent = class extends BaseAgent {
1421
1456
  if (!meta) continue;
1422
1457
  if (changedIds.includes(meta.id)) {
1423
1458
  this.sessionMetaMap.set(meta.id, meta);
1459
+ const stats = this.extractStats(meta.sourcePath);
1424
1460
  sessionMap.set(meta.id, {
1425
1461
  id: meta.id,
1426
1462
  slug: `kimi/${meta.id}`,
@@ -1428,12 +1464,7 @@ var KimiAgent = class extends BaseAgent {
1428
1464
  directory: meta.cwd,
1429
1465
  time_created: meta.createdAt,
1430
1466
  time_updated: meta.createdAt,
1431
- stats: {
1432
- message_count: 0,
1433
- total_input_tokens: 0,
1434
- total_output_tokens: 0,
1435
- total_cost: 0
1436
- }
1467
+ stats
1437
1468
  });
1438
1469
  }
1439
1470
  } catch {
@@ -1861,6 +1892,7 @@ var CODEX_TOOL_TITLE_MAP = {
1861
1892
  spawn_agent: "subagent",
1862
1893
  subagent: "subagent"
1863
1894
  };
1895
+ var RECENT_SESSION_REVALIDATION_WINDOW_MS2 = 24 * 60 * 60 * 1e3;
1864
1896
  function extractSessionId(filename) {
1865
1897
  const stem = basename4(filename, ".jsonl");
1866
1898
  const parts = stem.split("-");
@@ -1878,6 +1910,9 @@ function parseTimestampMs2(data) {
1878
1910
  return 0;
1879
1911
  }
1880
1912
  }
1913
+ function extractModelName(raw) {
1914
+ return typeof raw === "string" && raw.trim() ? raw.trim() : null;
1915
+ }
1881
1916
  function normalizeTitleText3(text) {
1882
1917
  const line = text.split("\n").find((l) => l.trim());
1883
1918
  return line?.trim().slice(0, 80) || "";
@@ -2051,34 +2086,43 @@ var CodexAgent = class extends BaseAgent {
2051
2086
  if (!this.basePath) {
2052
2087
  return { hasChanges: false, timestamp: Date.now() };
2053
2088
  }
2054
- const changedIds = [];
2089
+ const now = Date.now();
2090
+ const changedIds = /* @__PURE__ */ new Set();
2091
+ const currentFiles = this.listRolloutFiles();
2092
+ const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
2093
+ const cachedIds = new Set(cachedSessions.map((session) => session.id));
2094
+ const recentIds = cachedSessions.filter((session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS2).map((session) => session.id);
2095
+ for (const sessionId of recentIds) {
2096
+ changedIds.add(sessionId);
2097
+ }
2055
2098
  for (const session of cachedSessions) {
2056
2099
  const meta = this.sessionMetaMap.get(session.id);
2057
- if (!meta) continue;
2100
+ if (!currentIds.has(session.id)) {
2101
+ changedIds.add(session.id);
2102
+ continue;
2103
+ }
2104
+ if (!meta) {
2105
+ changedIds.add(session.id);
2106
+ continue;
2107
+ }
2058
2108
  try {
2059
2109
  const stat = statSync4(meta.sourcePath);
2060
2110
  if (stat.mtimeMs > sinceTimestamp) {
2061
- changedIds.push(session.id);
2111
+ changedIds.add(session.id);
2062
2112
  }
2063
2113
  } catch {
2064
- changedIds.push(session.id);
2114
+ changedIds.add(session.id);
2065
2115
  }
2066
2116
  }
2067
- try {
2068
- const allFiles = this.listRolloutFiles();
2069
- const hasNewFiles = allFiles.length > cachedSessions.length;
2070
- return {
2071
- hasChanges: changedIds.length > 0 || hasNewFiles,
2072
- changedIds,
2073
- timestamp: Date.now()
2074
- };
2075
- } catch {
2076
- return {
2077
- hasChanges: changedIds.length > 0,
2078
- changedIds,
2079
- timestamp: Date.now()
2080
- };
2117
+ const hasAddedSessions = currentFiles.some((file) => !cachedIds.has(extractSessionId(file)));
2118
+ if (recentIds.length > 0) {
2119
+ this.sessionIndexCache.clear();
2081
2120
  }
2121
+ return {
2122
+ hasChanges: changedIds.size > 0 || hasAddedSessions,
2123
+ changedIds: Array.from(changedIds),
2124
+ timestamp: Date.now()
2125
+ };
2082
2126
  }
2083
2127
  /**
2084
2128
  * 增量扫描
@@ -2086,10 +2130,19 @@ var CodexAgent = class extends BaseAgent {
2086
2130
  incrementalScan(cachedSessions, changedIds) {
2087
2131
  if (!this.basePath) return cachedSessions;
2088
2132
  const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
2089
- for (const file of this.listRolloutFiles()) {
2133
+ const changedSet = new Set(changedIds);
2134
+ const currentFiles = this.listRolloutFiles();
2135
+ const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
2136
+ for (const session of cachedSessions) {
2137
+ if (!currentIds.has(session.id)) {
2138
+ sessionMap.delete(session.id);
2139
+ this.sessionMetaMap.delete(session.id);
2140
+ }
2141
+ }
2142
+ for (const file of currentFiles) {
2090
2143
  try {
2091
2144
  const sessionId = extractSessionId(file);
2092
- if (changedIds.includes(sessionId)) {
2145
+ if (changedSet.has(sessionId)) {
2093
2146
  const head = this.parseSessionHead(file);
2094
2147
  if (head) {
2095
2148
  sessionMap.set(head.id, head);
@@ -2108,7 +2161,7 @@ var CodexAgent = class extends BaseAgent {
2108
2161
  } catch {
2109
2162
  }
2110
2163
  }
2111
- for (const file of this.listRolloutFiles()) {
2164
+ for (const file of currentFiles) {
2112
2165
  try {
2113
2166
  const sessionId = extractSessionId(file);
2114
2167
  if (!sessionMap.has(sessionId)) {
@@ -2144,13 +2197,18 @@ var CodexAgent = class extends BaseAgent {
2144
2197
  let currentAssistantIndex = null;
2145
2198
  let latestAssistantTextIndex = null;
2146
2199
  let pendingPlan = null;
2200
+ let activeModel = meta.model;
2147
2201
  let prevCumulativeTotal = 0;
2148
2202
  let prevInput = 0;
2149
- let prevCached = 0;
2150
2203
  let prevOutput = 0;
2151
2204
  let prevReasoning = 0;
2152
2205
  for (const record of parseJsonlLines(content)) {
2153
2206
  try {
2207
+ const recordType = String(record["type"] ?? "");
2208
+ if (recordType === "turn_context") {
2209
+ const payload = record["payload"] ?? {};
2210
+ activeModel = extractModelName(payload["model"]) ?? activeModel;
2211
+ }
2154
2212
  const result = this.convertRecord(
2155
2213
  record,
2156
2214
  messages,
@@ -2163,7 +2221,12 @@ var CodexAgent = class extends BaseAgent {
2163
2221
  currentAssistantIndex = result.currentAssistantIndex;
2164
2222
  latestAssistantTextIndex = result.latestAssistantTextIndex;
2165
2223
  pendingPlan = result.pendingPlan;
2166
- const recordType = String(record["type"] ?? "");
2224
+ if (currentAssistantIndex !== null && activeModel) {
2225
+ const message = messages[currentAssistantIndex];
2226
+ if (message?.role === "assistant" && !message.model) {
2227
+ message.model = activeModel;
2228
+ }
2229
+ }
2167
2230
  if (recordType === "event_msg") {
2168
2231
  const payload = record["payload"] ?? {};
2169
2232
  if (String(payload["type"] ?? "") === "token_count") {
@@ -2175,33 +2238,29 @@ var CodexAgent = class extends BaseAgent {
2175
2238
  prevCumulativeTotal = cumulativeTotal;
2176
2239
  const lastUsage = info?.["last_token_usage"];
2177
2240
  let inputTokens = 0;
2178
- let cachedInputTokens = 0;
2179
2241
  let outputTokens = 0;
2180
2242
  let reasoningTokens = 0;
2181
2243
  if (lastUsage) {
2182
2244
  inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2183
- cachedInputTokens = Number(lastUsage["cached_input_tokens"] ?? 0);
2184
2245
  outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2185
2246
  reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2186
2247
  } else if (cumulativeTotal > 0 && totalUsage) {
2187
2248
  inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
2188
- cachedInputTokens = Number(totalUsage["cached_input_tokens"] ?? 0) - prevCached;
2189
2249
  outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
2190
2250
  reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
2191
2251
  prevInput = Number(totalUsage["input_tokens"] ?? 0);
2192
- prevCached = Number(totalUsage["cached_input_tokens"] ?? 0);
2193
2252
  prevOutput = Number(totalUsage["output_tokens"] ?? 0);
2194
2253
  prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2195
2254
  }
2196
- const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
2197
- if (uncachedInput || outputTokens || reasoningTokens) {
2198
- totalInputTokens += uncachedInput;
2255
+ const totalInput = Math.max(0, inputTokens);
2256
+ if (totalInput || outputTokens || reasoningTokens) {
2257
+ totalInputTokens += totalInput;
2199
2258
  totalOutputTokens += outputTokens + reasoningTokens;
2200
2259
  for (let i = messages.length - 1; i >= 0; i--) {
2201
2260
  const msg = messages[i];
2202
2261
  if (msg.role === "assistant" && !msg.tokens) {
2203
2262
  msg.tokens = {
2204
- input: uncachedInput,
2263
+ input: totalInput,
2205
2264
  output: outputTokens + reasoningTokens
2206
2265
  };
2207
2266
  break;
@@ -2277,6 +2336,7 @@ var CodexAgent = class extends BaseAgent {
2277
2336
  }
2278
2337
  }
2279
2338
  getTitleForSession(sessionId) {
2339
+ this.loadSessionIndex();
2280
2340
  return this.sessionIndexCache.get(sessionId) ?? null;
2281
2341
  }
2282
2342
  // ---- Session head parsing ----
@@ -2292,7 +2352,7 @@ var CodexAgent = class extends BaseAgent {
2292
2352
  return null;
2293
2353
  }
2294
2354
  const payload = firstRecord["payload"] ?? {};
2295
- const createdAt = parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
2355
+ const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
2296
2356
  const indexTitle = this.getTitleForSession(sessionId);
2297
2357
  const messageTitle = this.extractTitleFromLines(lines);
2298
2358
  const directoryTitle = basenameTitle(payload["cwd"] ? String(payload["cwd"]) : null);
@@ -2304,7 +2364,6 @@ var CodexAgent = class extends BaseAgent {
2304
2364
  let totalOutputTokens = 0;
2305
2365
  let scanPrevCumulativeTotal = 0;
2306
2366
  let scanPrevInput = 0;
2307
- let scanPrevCached = 0;
2308
2367
  let scanPrevOutput = 0;
2309
2368
  let scanPrevReasoning = 0;
2310
2369
  const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
@@ -2312,10 +2371,13 @@ var CodexAgent = class extends BaseAgent {
2312
2371
  try {
2313
2372
  const data = JSON.parse(line);
2314
2373
  const recordType = String(data["type"] ?? "");
2315
- if (recordType === "session_meta") {
2316
- const p = data["payload"] ?? {};
2317
- const ts = parseTimestampMs2(p);
2318
- if (ts > updatedAt) updatedAt = ts;
2374
+ const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
2375
+ if (recordTs > updatedAt) updatedAt = recordTs;
2376
+ if (recordType === "session_meta" || recordType === "turn_context") {
2377
+ const payload2 = data["payload"] ?? {};
2378
+ if (!model) {
2379
+ model = extractModelName(payload2["model"]);
2380
+ }
2319
2381
  continue;
2320
2382
  }
2321
2383
  if (recordType === "response_item") {
@@ -2340,26 +2402,22 @@ var CodexAgent = class extends BaseAgent {
2340
2402
  scanPrevCumulativeTotal = cumulativeTotal;
2341
2403
  const lastUsage = info?.["last_token_usage"];
2342
2404
  let inputTokens = 0;
2343
- let cachedInputTokens = 0;
2344
2405
  let outputTokens = 0;
2345
2406
  let reasoningTokens = 0;
2346
2407
  if (lastUsage) {
2347
2408
  inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2348
- cachedInputTokens = Number(lastUsage["cached_input_tokens"] ?? 0);
2349
2409
  outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2350
2410
  reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2351
2411
  } else if (cumulativeTotal > 0 && totalUsage) {
2352
2412
  inputTokens = Number(totalUsage["input_tokens"] ?? 0) - scanPrevInput;
2353
- cachedInputTokens = Number(totalUsage["cached_input_tokens"] ?? 0) - scanPrevCached;
2354
2413
  outputTokens = Number(totalUsage["output_tokens"] ?? 0) - scanPrevOutput;
2355
2414
  reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - scanPrevReasoning;
2356
2415
  scanPrevInput = Number(totalUsage["input_tokens"] ?? 0);
2357
- scanPrevCached = Number(totalUsage["cached_input_tokens"] ?? 0);
2358
2416
  scanPrevOutput = Number(totalUsage["output_tokens"] ?? 0);
2359
2417
  scanPrevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2360
2418
  }
2361
- const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
2362
- totalInputTokens += uncachedInput;
2419
+ const totalInput = Math.max(0, inputTokens);
2420
+ totalInputTokens += totalInput;
2363
2421
  totalOutputTokens += outputTokens + reasoningTokens;
2364
2422
  }
2365
2423
  }
@@ -3002,7 +3060,12 @@ var CursorAgent = class extends BaseAgent {
3002
3060
  const title = this.extractTitle(composer);
3003
3061
  const createdAt = composer.createdAt ?? 0;
3004
3062
  const updatedAt = composer.updatedAt ?? createdAt;
3005
- const messages = this.loadMessagesFromBubbles(db, composerId, sessionId);
3063
+ const messages = this.loadMessagesFromBubbles(
3064
+ db,
3065
+ composerId,
3066
+ sessionId,
3067
+ composer.modelConfig?.modelName ?? composer.model ?? null
3068
+ );
3006
3069
  const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
3007
3070
  if (messages.length === 0 && !hasSubagents) {
3008
3071
  continue;
@@ -3114,7 +3177,12 @@ var CursorAgent = class extends BaseAgent {
3114
3177
  const title = this.extractTitle(composer);
3115
3178
  const createdAt = composer.createdAt ?? 0;
3116
3179
  const updatedAt = composer.updatedAt ?? createdAt;
3117
- const messages = this.loadMessagesFromBubbles(db, composerId, resolvedSessionId);
3180
+ const messages = this.loadMessagesFromBubbles(
3181
+ db,
3182
+ composerId,
3183
+ resolvedSessionId,
3184
+ composer.modelConfig?.modelName ?? composer.model ?? null
3185
+ );
3118
3186
  let totalInputTokens = 0;
3119
3187
  let totalOutputTokens = 0;
3120
3188
  for (const msg of messages) {
@@ -3222,11 +3290,11 @@ var CursorAgent = class extends BaseAgent {
3222
3290
  }
3223
3291
  }
3224
3292
  /** Load messages from bubbles (like agent-dump) */
3225
- loadMessagesFromBubbles(db, composerId, _sessionId) {
3293
+ loadMessagesFromBubbles(db, composerId, _sessionId, initialModelName) {
3226
3294
  const messages = [];
3227
3295
  try {
3228
3296
  const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ORDER BY rowid ASC").all(`bubbleId:${composerId}:%`);
3229
- let activeModelName = null;
3297
+ let activeModelName = initialModelName;
3230
3298
  let messageIndex = 0;
3231
3299
  for (const row of rows) {
3232
3300
  try {
@@ -3241,7 +3309,7 @@ var CursorAgent = class extends BaseAgent {
3241
3309
  } else if (bubble.timestamp) {
3242
3310
  timestampMs = bubble.timestamp;
3243
3311
  }
3244
- if (role === "user" && bubble.modelInfo?.modelName) {
3312
+ if (bubble.modelInfo?.modelName) {
3245
3313
  activeModelName = bubble.modelInfo.modelName;
3246
3314
  }
3247
3315
  const inputTokens = bubble.tokenCount?.inputTokens ?? 0;
@@ -3265,7 +3333,7 @@ var CursorAgent = class extends BaseAgent {
3265
3333
  time_created: timestampMs,
3266
3334
  time_completed: null,
3267
3335
  mode: role === "assistant" && parts.some((p) => p.type === "tool") ? "tool" : null,
3268
- model: activeModelName,
3336
+ model: bubble.modelInfo?.modelName ?? activeModelName,
3269
3337
  provider: null,
3270
3338
  tokens: { input: inputTokens, output: outputTokens },
3271
3339
  cost: 0,
@@ -3531,9 +3599,18 @@ function filterSessions(sessions, options) {
3531
3599
  }
3532
3600
  return result;
3533
3601
  }
3602
+ function buildAgentCacheMeta(agent) {
3603
+ const metaMap = agent.getSessionMetaMap?.();
3604
+ const meta = {};
3605
+ if (!metaMap) return meta;
3606
+ for (const [id, data] of metaMap.entries()) {
3607
+ meta[id] = { id, ...data };
3608
+ }
3609
+ return meta;
3610
+ }
3534
3611
  async function scanAgentSmart(agent, options, onProgress) {
3535
3612
  const useCache = options.useCache ?? true;
3536
- const smartRefresh = options.smartRefresh ?? true;
3613
+ const canValidateCache = Boolean(agent.checkForChanges && agent.incrementalScan);
3537
3614
  if (useCache) {
3538
3615
  const cached = loadCachedSessions(agent.name);
3539
3616
  if (cached !== null) {
@@ -3544,16 +3621,39 @@ async function scanAgentSmart(agent, options, onProgress) {
3544
3621
  }
3545
3622
  agent.setSessionMetaMap(metaMap);
3546
3623
  }
3547
- agent.isAvailable();
3624
+ const isAvail = agent.isAvailable();
3625
+ if (!isAvail) {
3626
+ return null;
3627
+ }
3548
3628
  onProgress?.({
3549
3629
  agent: agent.name,
3550
3630
  phase: "cache",
3551
3631
  cachedCount: cached.sessions.length
3552
3632
  });
3553
- if (smartRefresh && agent.checkForChanges) {
3554
- setTimeout(async () => {
3555
- await refreshAgentAsync(agent, cached.sessions, cached.timestamp, onProgress);
3556
- }, 0);
3633
+ if (canValidateCache) {
3634
+ onProgress?.({ agent: agent.name, phase: "checking" });
3635
+ const checkResult = await Promise.resolve(
3636
+ agent.checkForChanges(cached.timestamp, cached.sessions)
3637
+ );
3638
+ if (checkResult.hasChanges) {
3639
+ onProgress?.({
3640
+ agent: agent.name,
3641
+ phase: "incremental",
3642
+ changedCount: checkResult.changedIds?.length
3643
+ });
3644
+ const updatedSessions = await Promise.resolve(
3645
+ agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
3646
+ );
3647
+ saveCachedSessions(agent.name, updatedSessions, buildAgentCacheMeta(agent));
3648
+ onProgress?.({
3649
+ agent: agent.name,
3650
+ phase: "complete",
3651
+ newCount: updatedSessions.length
3652
+ });
3653
+ const filtered2 = filterSessions(updatedSessions, options);
3654
+ return { agent, heads: filtered2, fromCache: true, refreshed: true };
3655
+ }
3656
+ onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
3557
3657
  }
3558
3658
  const filtered = filterSessions(cached.sessions, options);
3559
3659
  return { agent, heads: filtered, fromCache: true };
@@ -3561,41 +3661,6 @@ async function scanAgentSmart(agent, options, onProgress) {
3561
3661
  }
3562
3662
  return scanAgentFull(agent, options, onProgress);
3563
3663
  }
3564
- async function refreshAgentAsync(agent, cachedSessions, cacheTimestamp, onProgress) {
3565
- try {
3566
- onProgress?.({ agent: agent.name, phase: "checking" });
3567
- const checkResult = await Promise.resolve(
3568
- agent.checkForChanges(cacheTimestamp, cachedSessions)
3569
- );
3570
- if (!checkResult.hasChanges) {
3571
- onProgress?.({ agent: agent.name, phase: "complete" });
3572
- return;
3573
- }
3574
- onProgress?.({
3575
- agent: agent.name,
3576
- phase: "incremental",
3577
- changedCount: checkResult.changedIds?.length
3578
- });
3579
- const updatedSessions = await Promise.resolve(
3580
- agent.incrementalScan(cachedSessions, checkResult.changedIds || [])
3581
- );
3582
- const metaMap = agent.getSessionMetaMap?.();
3583
- const meta = {};
3584
- if (metaMap) {
3585
- for (const [id, data] of metaMap.entries()) {
3586
- meta[id] = { id, ...data };
3587
- }
3588
- }
3589
- saveCachedSessions(agent.name, updatedSessions, meta);
3590
- onProgress?.({
3591
- agent: agent.name,
3592
- phase: "complete",
3593
- newCount: updatedSessions.length
3594
- });
3595
- } catch (err) {
3596
- console.error(`[${agent.name}] Background refresh failed:`, err);
3597
- }
3598
- }
3599
3664
  async function scanAgentFull(agent, options, onProgress) {
3600
3665
  const availMarker = perf.start(`agent:${agent.name}:isAvailable`);
3601
3666
  const isAvail = agent.isAvailable();
@@ -3607,13 +3672,7 @@ async function scanAgentFull(agent, options, onProgress) {
3607
3672
  const scanMarker = perf.start(`agent:${agent.name}:scan`);
3608
3673
  const heads = agent.scan();
3609
3674
  perf.end(scanMarker);
3610
- const metaMap = agent.getSessionMetaMap?.();
3611
- const meta = {};
3612
- if (metaMap) {
3613
- for (const [id, data] of metaMap.entries()) {
3614
- meta[id] = { id, ...data };
3615
- }
3616
- }
3675
+ const meta = buildAgentCacheMeta(agent);
3617
3676
  saveCachedSessions(agent.name, heads, meta);
3618
3677
  onProgress?.({ agent: agent.name, phase: "complete", newCount: heads.length });
3619
3678
  const filtered = filterSessions(heads, options);
@@ -3674,7 +3733,8 @@ export {
3674
3733
  saveCachedSessions,
3675
3734
  clearCache,
3676
3735
  getCacheInfo,
3736
+ filterSessions,
3677
3737
  scanSessions,
3678
3738
  scanSessionsAsync
3679
3739
  };
3680
- //# sourceMappingURL=chunk-FG2FZIU5.js.map
3740
+ //# sourceMappingURL=chunk-2UNXB2D3.js.map