codesesh 0.14.0 → 0.16.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.
@@ -78,6 +78,7 @@ function getAgentInfoMap(sessionsByAgent) {
78
78
  name: agent.name,
79
79
  displayName: agent.displayName,
80
80
  icon: registration.icon,
81
+ iconColored: registration.iconColored,
81
82
  count: sessionsByAgent[agent.name] ?? 0
82
83
  };
83
84
  });
@@ -85,6 +86,23 @@ function getAgentInfoMap(sessionsByAgent) {
85
86
  function getAgentByName(name) {
86
87
  return registrations.find((registration) => registration.create().name === name);
87
88
  }
89
+ var diagnostics = null;
90
+ function toSafeSink(sink) {
91
+ return {
92
+ warn(event, detail) {
93
+ try {
94
+ sink.warn(event, detail);
95
+ } catch {
96
+ }
97
+ }
98
+ };
99
+ }
100
+ function setCoreDiagnostics(next) {
101
+ diagnostics = next ? toSafeSink(next) : null;
102
+ }
103
+ function getCoreDiagnostics() {
104
+ return diagnostics;
105
+ }
88
106
  function parsedSession(session) {
89
107
  return { status: "parsed", data: session };
90
108
  }
@@ -117,7 +135,12 @@ var FileSystemSessionSource = class extends BaseAgent {
117
135
  try {
118
136
  const session = this.scanSessionSource(source.sourcePath, options);
119
137
  if (session) sessions.push(session);
120
- } catch {
138
+ } catch (error) {
139
+ getCoreDiagnostics()?.warn("agent.session_parse_failed", {
140
+ agentName: this.name,
141
+ sourcePath: source.sourcePath,
142
+ message: error instanceof Error ? error.message : String(error)
143
+ });
121
144
  continue;
122
145
  } finally {
123
146
  options?.onProgress?.({
@@ -156,18 +179,20 @@ var FileSystemSessionSource = class extends BaseAgent {
156
179
  return {
157
180
  hasChanges: changedIdList.length > 0,
158
181
  changedIds: changedIdList,
159
- timestamp: Date.now()
182
+ timestamp: Date.now(),
183
+ refs: currentRefs
160
184
  };
161
185
  }
162
186
  /**
163
187
  * 增量扫描:对变更/新增源调用 scanSessionSource 重解析,
164
188
  * 删除已消失的源,合并回 cachedSessions。
189
+ * refs 未传时回退为自行枚举,供独立调用方(如测试)沿用旧行为。
165
190
  */
166
- incrementalScan(cachedSessions, changedIds) {
191
+ incrementalScan(cachedSessions, changedIds, refs) {
167
192
  const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
168
193
  const changedSet = new Set(changedIds);
169
194
  const currentIds = /* @__PURE__ */ new Set();
170
- for (const ref of this.listSessionSources()) {
195
+ for (const ref of refs ?? this.listSessionSources()) {
171
196
  currentIds.add(ref.sessionId);
172
197
  if (!changedSet.has(ref.sessionId)) continue;
173
198
  const head = this.scanSessionSource(ref.sourcePath);
@@ -283,14 +308,21 @@ function getZCodeDataPath() {
283
308
  }
284
309
  var READ_CHUNK_BYTES = 1 << 20;
285
310
  function* parseJsonlLines(content) {
311
+ let total = 0;
312
+ let skipped = 0;
286
313
  for (const line of content.split("\n")) {
287
314
  const trimmed = line.trim();
288
315
  if (!trimmed) continue;
316
+ total += 1;
289
317
  try {
290
318
  yield JSON.parse(trimmed);
291
319
  } catch {
320
+ skipped += 1;
292
321
  }
293
322
  }
323
+ if (skipped > 0) {
324
+ getCoreDiagnostics()?.warn("agent.jsonl_lines_skipped", { skipped, total });
325
+ }
294
326
  }
295
327
  function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
296
328
  const fd = openSync(filePath, "r");
@@ -315,12 +347,19 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
315
347
  }
316
348
  }
317
349
  function* readJsonlFile(filePath) {
350
+ let total = 0;
351
+ let skipped = 0;
318
352
  for (const line of readJsonlFileLines(filePath)) {
353
+ total += 1;
319
354
  try {
320
355
  yield JSON.parse(line);
321
356
  } catch {
357
+ skipped += 1;
322
358
  }
323
359
  }
360
+ if (skipped > 0) {
361
+ getCoreDiagnostics()?.warn("agent.jsonl_lines_skipped", { skipped, total, filePath });
362
+ }
324
363
  }
325
364
  var INTERNAL_TAGS = [
326
365
  "command-message",
@@ -794,6 +833,43 @@ function withEstimatedSessionCost(stats, model) {
794
833
  function estimateTokenCost(model, tokens) {
795
834
  return estimateCostForTokens(model, tokens)?.cost ?? null;
796
835
  }
836
+ function asRecord(value) {
837
+ if (value && typeof value === "object" && !Array.isArray(value)) {
838
+ return value;
839
+ }
840
+ return void 0;
841
+ }
842
+ function asString(value) {
843
+ return typeof value === "string" ? value : void 0;
844
+ }
845
+ function asNumber(value) {
846
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
847
+ }
848
+ function asArray(value) {
849
+ return Array.isArray(value) ? value : void 0;
850
+ }
851
+ function safeParseJsonRecord(json) {
852
+ let raw;
853
+ try {
854
+ raw = JSON.parse(json);
855
+ } catch {
856
+ return void 0;
857
+ }
858
+ return asRecord(raw);
859
+ }
860
+ var reportedFieldMismatches = /* @__PURE__ */ new Set();
861
+ function reportFieldMismatch(agentName, field) {
862
+ const key = `${agentName}\0${field}`;
863
+ if (reportedFieldMismatches.has(key)) return;
864
+ reportedFieldMismatches.add(key);
865
+ getCoreDiagnostics()?.warn("agent.field_shape_mismatch", { agentName, field });
866
+ }
867
+ function narrowField(agent, field, value, narrow) {
868
+ if (value === void 0 || value === null) return void 0;
869
+ const result = narrow(value);
870
+ if (result === void 0) reportFieldMismatch(agent, field);
871
+ return result;
872
+ }
797
873
  var TranscriptBuilder = class {
798
874
  constructor(options = {}) {
799
875
  this.options = options;
@@ -969,31 +1045,48 @@ var TranscriptBuilder = class {
969
1045
  };
970
1046
  var HEAD_INDEX_VERSION = "claudecode-head-v2";
971
1047
  function parseTimestampMs(data) {
972
- const raw = String(data["timestamp"] ?? "").trim();
973
- if (!raw) return 0;
1048
+ const raw = data["timestamp"];
1049
+ const value = asString(raw);
1050
+ if (value === void 0) {
1051
+ if (raw !== void 0 && raw !== null) reportFieldMismatch("claudecode", "timestamp");
1052
+ return 0;
1053
+ }
1054
+ const trimmed = value.trim();
1055
+ if (!trimmed) return 0;
974
1056
  try {
975
- return new Date(raw.includes("Z") ? raw : raw + "Z").getTime();
1057
+ return new Date(trimmed.includes("Z") ? trimmed : trimmed + "Z").getTime();
976
1058
  } catch {
977
1059
  return 0;
978
1060
  }
979
1061
  }
980
- function numericUsage(value) {
981
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
1062
+ function readUsageNumber(usage, field) {
1063
+ const raw = usage[field];
1064
+ if (raw === void 0) return 0;
1065
+ const value = asNumber(raw);
1066
+ if (value === void 0) {
1067
+ reportFieldMismatch("claudecode", `message.usage.${field}`);
1068
+ return 0;
1069
+ }
1070
+ return value;
982
1071
  }
983
1072
  function extractClaudeUsage(data, msg) {
984
- const usage = msg["usage"];
985
- if (!usage || typeof usage !== "object") return null;
986
- const u = usage;
1073
+ const rawUsage = msg["usage"];
1074
+ if (rawUsage === void 0 || rawUsage === null) return null;
1075
+ const usage = asRecord(rawUsage);
1076
+ if (!usage) {
1077
+ reportFieldMismatch("claudecode", "message.usage");
1078
+ return null;
1079
+ }
987
1080
  const requestId = typeof data["requestId"] === "string" ? data["requestId"].trim() : "";
988
1081
  const uuid = typeof data["uuid"] === "string" ? data["uuid"].trim() : "";
989
1082
  const key = requestId || uuid;
990
1083
  if (!key) return null;
991
1084
  return {
992
1085
  key,
993
- input: numericUsage(u["input_tokens"]),
994
- output: numericUsage(u["output_tokens"]),
995
- cacheRead: numericUsage(u["cache_read_input_tokens"]),
996
- cacheCreate: numericUsage(u["cache_creation_input_tokens"])
1086
+ input: readUsageNumber(usage, "input_tokens"),
1087
+ output: readUsageNumber(usage, "output_tokens"),
1088
+ cacheRead: readUsageNumber(usage, "cache_read_input_tokens"),
1089
+ cacheCreate: readUsageNumber(usage, "cache_creation_input_tokens")
997
1090
  };
998
1091
  }
999
1092
  var ClaudeCodeAgent = class extends FileSystemSessionSource {
@@ -1025,17 +1118,20 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1025
1118
  if (!this.basePath) return [];
1026
1119
  const refs = [];
1027
1120
  for (const projectDir of this.listProjectDirs()) {
1121
+ const indexPath = this.getSessionsIndexPath(projectDir);
1028
1122
  for (const file of this.listJsonlFiles(projectDir)) {
1123
+ let stat;
1029
1124
  try {
1030
- if (!matchesScanWindow(statSync2(file).mtimeMs, options)) continue;
1125
+ stat = statSync2(file);
1031
1126
  } catch {
1032
1127
  continue;
1033
1128
  }
1129
+ if (!matchesScanWindow(stat.mtimeMs, options)) continue;
1034
1130
  const sessionId = basename2(file, ".jsonl");
1035
1131
  refs.push({
1036
1132
  sessionId,
1037
1133
  sourcePath: file,
1038
- fingerprint: this.sourceFingerprint(file, projectDir)
1134
+ fingerprint: this.sourceFingerprint(stat, indexPath)
1039
1135
  });
1040
1136
  }
1041
1137
  }
@@ -1059,18 +1155,11 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1059
1155
  }
1060
1156
  const content = readFileSync2(meta.sourcePath, "utf-8");
1061
1157
  const builder = new TranscriptBuilder();
1062
- const ignoredToolCallIds = /* @__PURE__ */ new Set();
1063
1158
  const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
1064
1159
  const countedUsageKeys = /* @__PURE__ */ new Set();
1065
1160
  for (const record of parseJsonlLines(content)) {
1066
1161
  try {
1067
- this.convertRecord(
1068
- record,
1069
- builder,
1070
- ignoredToolCallIds,
1071
- assistantUuidToToolCalls,
1072
- countedUsageKeys
1073
- );
1162
+ this.convertRecord(record, builder, assistantUuidToToolCalls, countedUsageKeys);
1074
1163
  } catch {
1075
1164
  }
1076
1165
  }
@@ -1105,12 +1194,13 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1105
1194
  }
1106
1195
  buildSessionMeta(head, file, projectDir) {
1107
1196
  const indexPath = this.getSessionsIndexPath(projectDir);
1197
+ const stat = statSync2(file);
1108
1198
  return {
1109
1199
  id: head.id,
1110
1200
  title: head.title,
1111
1201
  sourcePath: file,
1112
- sourceFingerprint: this.sourceFingerprint(file, projectDir),
1113
- sourceMtimeMs: statSync2(file).mtimeMs,
1202
+ sourceFingerprint: this.sourceFingerprint(stat, indexPath),
1203
+ sourceMtimeMs: stat.mtimeMs,
1114
1204
  indexPath: existsSync4(indexPath) ? indexPath : null,
1115
1205
  indexMtimeMs: this.getFileMtimeMs(indexPath),
1116
1206
  headIndexVersion: HEAD_INDEX_VERSION,
@@ -1121,9 +1211,8 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1121
1211
  updatedAt: head.time_updated ?? head.time_created
1122
1212
  };
1123
1213
  }
1124
- sourceFingerprint(file, projectDir) {
1125
- const stat = statSync2(file);
1126
- const indexPath = this.getSessionsIndexPath(projectDir);
1214
+ /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
1215
+ sourceFingerprint(stat, indexPath) {
1127
1216
  return JSON.stringify([
1128
1217
  HEAD_INDEX_VERSION,
1129
1218
  stat.mtimeMs,
@@ -1195,7 +1284,8 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1195
1284
  let totalCost = 0;
1196
1285
  const modelUsageMap = {};
1197
1286
  const countedUsageKeys = /* @__PURE__ */ new Set();
1198
- for (const line of lines) {
1287
+ let messageTitle = null;
1288
+ for (const [lineIndex, line] of lines.entries()) {
1199
1289
  try {
1200
1290
  const data = JSON.parse(line);
1201
1291
  if (isInternalEventType(data["type"])) continue;
@@ -1204,15 +1294,25 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1204
1294
  if (!cwd && data["cwd"] && typeof data["cwd"] === "string") {
1205
1295
  cwd = data["cwd"];
1206
1296
  }
1207
- const msg = data["message"];
1208
- if (msg && typeof msg === "object") {
1209
- const role = msg["role"];
1210
- if (typeof role === "string" && role.trim()) {
1297
+ const msg = asRecord(data["message"]);
1298
+ if (!msg && data["message"] !== void 0 && data["message"] !== null) {
1299
+ reportFieldMismatch("claudecode", "message");
1300
+ }
1301
+ if (msg) {
1302
+ const role = asString(msg["role"]);
1303
+ if (msg["role"] !== void 0 && role === void 0) {
1304
+ reportFieldMismatch("claudecode", "message.role");
1305
+ }
1306
+ if (role?.trim()) {
1211
1307
  messageCount++;
1212
1308
  }
1213
1309
  if (!model) {
1214
- const m = msg["model"];
1215
- if (typeof m === "string" && m.trim()) model = m.trim();
1310
+ const m = asString(msg["model"]);
1311
+ if (m?.trim()) model = m.trim();
1312
+ }
1313
+ if (messageTitle === null && lineIndex < 20 && role === "user") {
1314
+ const candidate = this.extractUserMessageTitle(msg["content"]);
1315
+ if (candidate) messageTitle = candidate;
1216
1316
  }
1217
1317
  if (role === "assistant") {
1218
1318
  const usage = extractClaudeUsage(data, msg);
@@ -1226,8 +1326,8 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1226
1326
  totalOutputTokens += outputTokens;
1227
1327
  totalCacheReadTokens += cacheRead;
1228
1328
  totalCacheCreateTokens += cacheCreate;
1229
- const m = msg["model"];
1230
- if (typeof m === "string" && m.trim()) {
1329
+ const m = asString(msg["model"]);
1330
+ if (m?.trim()) {
1231
1331
  const name = m.trim();
1232
1332
  const msgTotal = inputTokens + cacheRead + cacheCreate + outputTokens;
1233
1333
  modelUsageMap[name] = (modelUsageMap[name] ?? 0) + msgTotal;
@@ -1246,7 +1346,6 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1246
1346
  }
1247
1347
  }
1248
1348
  const directory = cwd ?? projectDir;
1249
- const messageTitle = this.extractTitle(lines);
1250
1349
  const directoryTitle = basenameTitle(directory) || basenameTitle(projectDir);
1251
1350
  const title = resolveSessionTitle(explicitTitle, messageTitle, directoryTitle);
1252
1351
  const hasModelUsage = Object.keys(modelUsageMap).length > 0;
@@ -1270,113 +1369,93 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1270
1369
  model_usage: hasModelUsage ? modelUsageMap : void 0
1271
1370
  });
1272
1371
  }
1273
- extractTitle(lines) {
1274
- for (const line of lines.slice(0, 20)) {
1275
- try {
1276
- const data = JSON.parse(line);
1277
- if (isInternalEventType(data["type"])) continue;
1278
- const msg = data["message"];
1279
- if (!msg || typeof msg !== "object") continue;
1280
- if (msg["role"] !== "user") continue;
1281
- const content = msg["content"];
1282
- if (!content) continue;
1283
- if (typeof content === "string") {
1284
- const title = normalizeTitleText(content);
1285
- if (title) return title;
1286
- }
1287
- if (Array.isArray(content)) {
1288
- const texts = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
1289
- const title = normalizeTitleText(texts);
1290
- if (title) return title;
1291
- }
1292
- } catch {
1293
- }
1372
+ /** Mirrors the title-candidate extraction previously done in a second JSON.parse pass. */
1373
+ extractUserMessageTitle(content) {
1374
+ if (!content) return null;
1375
+ if (typeof content === "string") {
1376
+ const title = normalizeTitleText(content);
1377
+ return title || null;
1378
+ }
1379
+ if (Array.isArray(content)) {
1380
+ const texts = content.filter((item) => {
1381
+ const record = asRecord(item);
1382
+ return record !== void 0 && "text" in record;
1383
+ }).map((item) => String(item["text"] ?? "")).join(" ");
1384
+ const title = normalizeTitleText(texts);
1385
+ return title || null;
1294
1386
  }
1295
1387
  return null;
1296
1388
  }
1297
1389
  // --- Record conversion ---
1298
- convertRecord(data, builder, ignoredToolCallIds, assistantUuidToToolCalls, countedUsageKeys) {
1390
+ convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
1299
1391
  if (data["isMeta"] === true) return;
1300
1392
  const msgType = String(data["type"] ?? "");
1301
1393
  if (isInternalEventType(msgType)) return;
1302
1394
  if (msgType === "assistant") {
1303
- this.convertAssistantRecord(
1304
- data,
1305
- builder,
1306
- ignoredToolCallIds,
1307
- assistantUuidToToolCalls,
1308
- countedUsageKeys
1309
- );
1395
+ this.convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys);
1310
1396
  } else if (msgType === "user") {
1311
- this.convertUserRecord(data, builder, ignoredToolCallIds, assistantUuidToToolCalls);
1397
+ this.convertUserRecord(data, builder, assistantUuidToToolCalls);
1312
1398
  } else if (msgType === "tool_result") {
1313
1399
  this.convertToolResultRecord(data, builder);
1314
1400
  }
1315
1401
  }
1316
- convertAssistantRecord(data, builder, ignoredToolCallIds, assistantUuidToToolCalls, countedUsageKeys) {
1317
- const msg = data["message"] ?? {};
1402
+ convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
1403
+ const msg = asRecord(data["message"]) ?? {};
1318
1404
  const timestampMs = parseTimestampMs(data);
1319
- const rawContent = msg["content"] ?? [];
1405
+ const rawContent = asArray(msg["content"]) ?? [];
1320
1406
  const uuid = String(data["uuid"] ?? "");
1321
1407
  const toolCallIds = [];
1322
- if (Array.isArray(rawContent)) {
1323
- for (const item of rawContent) {
1324
- if (!item || typeof item !== "object") continue;
1325
- const part = item;
1326
- const partType = String(part["type"] ?? "");
1327
- if (partType === "thinking") {
1328
- const text = cleanInternalText(String(part["thinking"] ?? ""));
1329
- if (text) {
1330
- const message2 = builder.appendAssistantPart(
1331
- this.buildReasoningPart(text, timestampMs),
1332
- { id: uuid, timestampMs, agent: "claude" },
1333
- { deduplicateTail: true }
1334
- );
1335
- this.applyAssistantMetadata(message2, data, msg, countedUsageKeys);
1336
- }
1337
- continue;
1338
- }
1339
- if (partType === "text") {
1340
- const text = cleanInternalText(String(part["text"] ?? ""));
1341
- if (text) {
1342
- const message2 = builder.appendAssistantPart(
1343
- this.buildTextPart(text, timestampMs),
1344
- {
1345
- id: uuid,
1346
- timestampMs,
1347
- agent: "claude"
1348
- },
1349
- { deduplicateTail: true }
1350
- );
1351
- this.applyAssistantMetadata(message2, data, msg, countedUsageKeys);
1352
- }
1353
- continue;
1354
- }
1355
- if (partType !== "tool_use") continue;
1356
- const toolName = String(part["name"] ?? "").trim();
1357
- const toolCallId = String(part["id"] ?? "").trim();
1358
- if (toolName && toolCallId && this.shouldIgnoreTool(toolName)) {
1359
- ignoredToolCallIds.add(toolCallId);
1360
- continue;
1408
+ for (const item of rawContent) {
1409
+ const part = asRecord(item);
1410
+ if (!part) continue;
1411
+ const partType = String(part["type"] ?? "");
1412
+ if (partType === "thinking") {
1413
+ const text = cleanInternalText(String(part["thinking"] ?? ""));
1414
+ if (text) {
1415
+ const message2 = builder.appendAssistantPart(
1416
+ this.buildReasoningPart(text, timestampMs),
1417
+ { id: uuid, timestampMs, agent: "claude" },
1418
+ { deduplicateTail: true }
1419
+ );
1420
+ this.applyAssistantMetadata(message2, data, msg, countedUsageKeys);
1361
1421
  }
1362
- const toolPart = this.buildToolPart(part, timestampMs);
1363
- const message = builder.appendToolCall(
1364
- toolPart,
1365
- { id: uuid, timestampMs, agent: "claude" },
1366
- { modeOnCreate: "tool" }
1367
- );
1368
- this.applyAssistantMetadata(message, data, msg, countedUsageKeys);
1369
- if (toolCallId) {
1370
- toolCallIds.push(toolCallId);
1422
+ continue;
1423
+ }
1424
+ if (partType === "text") {
1425
+ const text = cleanInternalText(String(part["text"] ?? ""));
1426
+ if (text) {
1427
+ const message2 = builder.appendAssistantPart(
1428
+ this.buildTextPart(text, timestampMs),
1429
+ {
1430
+ id: uuid,
1431
+ timestampMs,
1432
+ agent: "claude"
1433
+ },
1434
+ { deduplicateTail: true }
1435
+ );
1436
+ this.applyAssistantMetadata(message2, data, msg, countedUsageKeys);
1371
1437
  }
1438
+ continue;
1439
+ }
1440
+ if (partType !== "tool_use") continue;
1441
+ const toolCallId = String(part["id"] ?? "").trim();
1442
+ const toolPart = this.buildToolPart(part, timestampMs);
1443
+ const message = builder.appendToolCall(
1444
+ toolPart,
1445
+ { id: uuid, timestampMs, agent: "claude" },
1446
+ { modeOnCreate: "tool" }
1447
+ );
1448
+ this.applyAssistantMetadata(message, data, msg, countedUsageKeys);
1449
+ if (toolCallId) {
1450
+ toolCallIds.push(toolCallId);
1372
1451
  }
1373
1452
  }
1374
1453
  if (toolCallIds.length > 0) {
1375
1454
  assistantUuidToToolCalls.set(uuid, toolCallIds);
1376
1455
  }
1377
1456
  }
1378
- convertUserRecord(data, builder, ignoredToolCallIds, assistantUuidToToolCalls) {
1379
- const msg = data["message"] ?? {};
1457
+ convertUserRecord(data, builder, assistantUuidToToolCalls) {
1458
+ const msg = asRecord(data["message"]) ?? {};
1380
1459
  const timestampMs = parseTimestampMs(data);
1381
1460
  const content = msg["content"] ?? "";
1382
1461
  const uuid = String(data["uuid"] ?? "");
@@ -1396,11 +1475,9 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1396
1475
  const visibleParts = this.normalizeUserTextParts(content, timestampMs);
1397
1476
  const toolStateUpdates = this.extractToolStateUpdates(data["toolUseResult"]);
1398
1477
  for (const item of content) {
1399
- if (!item || typeof item !== "object") continue;
1400
- const ci = item;
1401
- if (ci["type"] !== "tool_result") continue;
1478
+ const ci = asRecord(item);
1479
+ if (!ci || ci["type"] !== "tool_result") continue;
1402
1480
  const toolCallId = this.resolveToolCallId(data, ci, assistantUuidToToolCalls);
1403
- if (toolCallId && ignoredToolCallIds.has(toolCallId)) continue;
1404
1481
  const outputParts = this.normalizeClaudeToolOutput(ci["content"], timestampMs);
1405
1482
  if (this.backfillToolOutput(builder, toolCallId, outputParts, toolStateUpdates)) {
1406
1483
  continue;
@@ -1420,7 +1497,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1420
1497
  }
1421
1498
  convertToolResultRecord(data, builder) {
1422
1499
  const timestampMs = parseTimestampMs(data);
1423
- const msg = data["message"] ?? {};
1500
+ const msg = asRecord(data["message"]) ?? {};
1424
1501
  const outputParts = this.normalizeClaudeToolOutput(msg["content"], timestampMs);
1425
1502
  const uuid = String(data["uuid"] ?? "");
1426
1503
  const fallback = this.buildFallbackToolMessage({
@@ -1482,8 +1559,8 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1482
1559
  if (!Array.isArray(content)) return [];
1483
1560
  const parts = [];
1484
1561
  for (const item of content) {
1485
- if (typeof item === "object" && item !== null) {
1486
- const ci = item;
1562
+ const ci = asRecord(item);
1563
+ if (ci) {
1487
1564
  if (ci["type"] === "tool_result") continue;
1488
1565
  const text = cleanInternalText(String(ci["text"] ?? ""));
1489
1566
  if (text) parts.push(this.buildTextPart(text, timestampMs));
@@ -1503,10 +1580,19 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1503
1580
  if (Array.isArray(content)) {
1504
1581
  const parts = [];
1505
1582
  for (const item of content) {
1506
- if (typeof item === "object" && item !== null) {
1507
- const text2 = String(
1508
- item["text"] ?? item["content"] ?? ""
1509
- );
1583
+ const itemRecord = asRecord(item);
1584
+ if (itemRecord) {
1585
+ const rawSource = itemRecord["source"];
1586
+ if (itemRecord["type"] === "image" && rawSource) {
1587
+ const source = asRecord(rawSource);
1588
+ const data = asString(source?.["data"]) ?? "";
1589
+ const mimeType = asString(source?.["media_type"]) ?? "";
1590
+ if (data && mimeType.startsWith("image/")) {
1591
+ parts.push({ type: "image", data, mime_type: mimeType, time_created: timestampMs });
1592
+ }
1593
+ continue;
1594
+ }
1595
+ const text2 = String(itemRecord["text"] ?? itemRecord["content"] ?? "");
1510
1596
  const cleaned = cleanInternalText(text2);
1511
1597
  if (cleaned) parts.push(this.buildTextPart(cleaned, timestampMs));
1512
1598
  } else if (typeof item === "string") {
@@ -1544,8 +1630,8 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1544
1630
  return "";
1545
1631
  }
1546
1632
  extractToolStateUpdates(toolUseResult) {
1547
- if (!toolUseResult || typeof toolUseResult !== "object") return {};
1548
- const result = toolUseResult;
1633
+ const result = asRecord(toolUseResult);
1634
+ if (!result) return {};
1549
1635
  const updates = {};
1550
1636
  const success = result["success"];
1551
1637
  if (typeof success === "boolean") {
@@ -1567,17 +1653,21 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1567
1653
  parts: opts.outputParts
1568
1654
  };
1569
1655
  }
1570
- // --- Utilities ---
1571
- shouldIgnoreTool(toolName) {
1572
- return toolName === "TodoWrite";
1573
- }
1574
1656
  };
1575
1657
  var DatabaseConstructor = null;
1658
+ var loadErrorMessage = null;
1576
1659
  try {
1577
1660
  const require2 = createRequire(import.meta.url);
1578
1661
  const mod = require2("better-sqlite3");
1579
1662
  DatabaseConstructor = typeof mod === "function" ? mod : mod.default;
1580
- } catch {
1663
+ } catch (error) {
1664
+ loadErrorMessage = error instanceof Error ? error.message : String(error);
1665
+ }
1666
+ var unavailableReported = false;
1667
+ function reportUnavailableOnce() {
1668
+ if (unavailableReported) return;
1669
+ unavailableReported = true;
1670
+ getCoreDiagnostics()?.warn("sqlite.unavailable", { message: loadErrorMessage });
1581
1671
  }
1582
1672
  function quoteIdentifier(value) {
1583
1673
  return `"${value.replaceAll('"', '""')}"`;
@@ -1672,16 +1762,27 @@ function runSchemaMigrations(db, options) {
1672
1762
  return backups;
1673
1763
  }
1674
1764
  function openDbReadOnly(dbPath) {
1675
- if (!DatabaseConstructor) return null;
1765
+ if (!DatabaseConstructor) {
1766
+ reportUnavailableOnce();
1767
+ return null;
1768
+ }
1676
1769
  try {
1677
1770
  const db = DatabaseConstructor(dbPath, { readonly: true });
1678
1771
  return db;
1679
- } catch {
1772
+ } catch (error) {
1773
+ getCoreDiagnostics()?.warn("sqlite.open_failed", {
1774
+ dbPath,
1775
+ readonly: true,
1776
+ message: error instanceof Error ? error.message : String(error)
1777
+ });
1680
1778
  return null;
1681
1779
  }
1682
1780
  }
1683
1781
  function openDb(dbPath) {
1684
- if (!DatabaseConstructor) return null;
1782
+ if (!DatabaseConstructor) {
1783
+ reportUnavailableOnce();
1784
+ return null;
1785
+ }
1685
1786
  try {
1686
1787
  mkdirSync2(dirname2(dbPath), { recursive: true });
1687
1788
  const db = DatabaseConstructor(dbPath);
@@ -1689,16 +1790,42 @@ function openDb(dbPath) {
1689
1790
  db.pragma("journal_mode = WAL");
1690
1791
  db.pragma("synchronous = NORMAL");
1691
1792
  db.pragma("foreign_keys = ON");
1793
+ db.pragma("busy_timeout = 5000");
1692
1794
  } catch {
1693
1795
  }
1694
1796
  return db;
1695
- } catch {
1797
+ } catch (error) {
1798
+ getCoreDiagnostics()?.warn("sqlite.open_failed", {
1799
+ dbPath,
1800
+ readonly: false,
1801
+ message: error instanceof Error ? error.message : String(error)
1802
+ });
1696
1803
  return null;
1697
1804
  }
1698
1805
  }
1699
1806
  function isSqliteAvailable() {
1700
1807
  return DatabaseConstructor !== null;
1701
1808
  }
1809
+ var MESSAGE_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool"]);
1810
+ function parseJsonRecord(raw, agentName, field) {
1811
+ const parsed = asRecord(JSON.parse(String(raw ?? "{}")));
1812
+ if (parsed) return parsed;
1813
+ reportFieldMismatch(agentName, field);
1814
+ return {};
1815
+ }
1816
+ function narrowMessageRole(value) {
1817
+ const role = asString(value);
1818
+ return role !== void 0 && MESSAGE_ROLES.has(role) ? role : void 0;
1819
+ }
1820
+ function parseMessageRole(value, agentName) {
1821
+ return narrowField(agentName, "message.role", value, narrowMessageRole) ?? "assistant";
1822
+ }
1823
+ function parseTokens(value, agentName) {
1824
+ return narrowField(agentName, "message.tokens", value, asRecord);
1825
+ }
1826
+ function parseModel(value, agentName) {
1827
+ return narrowField(agentName, "message.modelID", value, asString) ?? null;
1828
+ }
1702
1829
  var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1703
1830
  constructor(config) {
1704
1831
  super();
@@ -1830,7 +1957,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1830
1957
  ).all(cutoffTime);
1831
1958
  }
1832
1959
  parsePartRow(partRow) {
1833
- const partData = JSON.parse(String(partRow.data ?? "{}"));
1960
+ const partData = parseJsonRecord(partRow.data, this.name, "part.data");
1834
1961
  const partType = String(partData.type ?? "");
1835
1962
  if (isInternalEventType(partType)) return null;
1836
1963
  if (partType === "text" || partType === "reasoning") {
@@ -1848,7 +1975,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1848
1975
  tool: String(partData.tool ?? ""),
1849
1976
  callID: String(partData.callID ?? ""),
1850
1977
  title: cleanInternalText(String(partData.title ?? "")),
1851
- state: partData.state ?? {},
1978
+ state: asRecord(partData.state) ?? {},
1852
1979
  time_created: Number(partRow.time_created ?? 0)
1853
1980
  };
1854
1981
  }
@@ -1880,7 +2007,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1880
2007
  for (const row of messageRows) {
1881
2008
  const sessionId = String(row.session_id ?? "");
1882
2009
  if (!sessionId) continue;
1883
- const msgData = JSON.parse(String(row.data ?? "{}"));
2010
+ const msgData = parseJsonRecord(row.data, this.name, "message.data");
1884
2011
  if (isInternalEventType(msgData.type)) continue;
1885
2012
  const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
1886
2013
  if (parts.length === 0) continue;
@@ -1898,10 +2025,10 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1898
2025
  contexts.set(sessionId, context);
1899
2026
  }
1900
2027
  const cost = Number(msgData.cost ?? 0);
1901
- const tokens = msgData.tokens;
2028
+ const tokens = parseTokens(msgData.tokens, this.name);
1902
2029
  const inputTokens = Number(tokens?.input ?? 0);
1903
2030
  const outputTokens = Number(tokens?.output ?? 0);
1904
- const model = msgData.modelID ?? null;
2031
+ const model = parseModel(msgData.modelID, this.name);
1905
2032
  const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
1906
2033
  if (estimatedCost !== null) context.stats.cost_source = "estimated";
1907
2034
  context.stats.total_cost += cost || estimatedCost || 0;
@@ -1955,24 +2082,24 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1955
2082
  let hasEstimatedCost = false;
1956
2083
  const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
1957
2084
  for (const msgRow of msgRows) {
1958
- const msgData = JSON.parse(String(msgRow.data ?? "{}"));
2085
+ const msgData = parseJsonRecord(msgRow.data, this.name, "message.data");
1959
2086
  if (isInternalEventType(msgData.type)) continue;
1960
2087
  const cost = Number(msgData.cost ?? 0);
1961
- const tokens = msgData.tokens;
2088
+ const tokens = parseTokens(msgData.tokens, this.name);
1962
2089
  const inputTokens = Number(tokens?.input ?? 0);
1963
2090
  const outputTokens = Number(tokens?.output ?? 0);
1964
- const model = msgData.modelID ?? null;
2091
+ const model = parseModel(msgData.modelID, this.name);
1965
2092
  const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
1966
2093
  const resolvedCost = cost || estimatedCost || 0;
1967
2094
  const parts = this.readMessageParts(db, msgRow.id);
1968
2095
  if (parts.length === 0) continue;
1969
2096
  messages.push({
1970
2097
  id: String(msgRow.id ?? ""),
1971
- role: String(msgData.role ?? "assistant"),
1972
- agent: msgData.agent ?? null,
1973
- mode: msgData.mode ?? null,
2098
+ role: parseMessageRole(msgData.role, this.name),
2099
+ agent: asString(msgData.agent) ?? null,
2100
+ mode: asString(msgData.mode) ?? null,
1974
2101
  model,
1975
- provider: msgData.providerID ?? null,
2102
+ provider: asString(msgData.providerID) ?? null,
1976
2103
  time_created: Number(msgRow.time_created ?? 0),
1977
2104
  tokens: tokens ? { input: inputTokens, output: outputTokens } : void 0,
1978
2105
  cost: resolvedCost,
@@ -1997,7 +2124,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
1997
2124
  title,
1998
2125
  slug,
1999
2126
  directory,
2000
- version: sessionRow.version ?? void 0,
2127
+ version: asString(sessionRow.version) ?? void 0,
2001
2128
  time_created: timeCreated,
2002
2129
  time_updated: timeUpdated,
2003
2130
  summary_files: sessionRow.summary_files ?? void 0,
@@ -2041,6 +2168,15 @@ var KIMI_IGNORED_TOOLS = /* @__PURE__ */ new Set(["SetTodoList"]);
2041
2168
  function mapToolTitle(toolName) {
2042
2169
  return KIMI_TOOL_TITLE_MAP[toolName] ?? toolName;
2043
2170
  }
2171
+ function readWireMtime(record) {
2172
+ return narrowField("kimi", "session.wire_mtime", record.wire_mtime, asNumber) ?? null;
2173
+ }
2174
+ function readWireTimestamp(record) {
2175
+ return narrowField("kimi", "wire.timestamp", record.timestamp, asNumber) ?? 0;
2176
+ }
2177
+ function extractTokenField(usage, field) {
2178
+ return narrowField("kimi", `usage.${field}`, usage[field], asNumber) ?? 0;
2179
+ }
2044
2180
  function normalizeToolArguments(raw) {
2045
2181
  if (typeof raw === "string") {
2046
2182
  try {
@@ -2059,8 +2195,9 @@ function normalizeToolOutputParts(content, timestampMs) {
2059
2195
  if (Array.isArray(content)) {
2060
2196
  const parts = [];
2061
2197
  for (const item of content) {
2062
- if (typeof item === "object" && item !== null && "text" in item) {
2063
- const text2 = String(item.text ?? "");
2198
+ const record = asRecord(item);
2199
+ if (record && "text" in record) {
2200
+ const text2 = String(record.text ?? "");
2064
2201
  const cleaned = cleanInternalText(text2);
2065
2202
  if (cleaned) parts.push({ type: "text", text: cleaned, time_created: timestampMs });
2066
2203
  } else if (typeof item === "string") {
@@ -2092,10 +2229,8 @@ function kimiContentText(content) {
2092
2229
  if (!Array.isArray(content)) return "";
2093
2230
  return content.map((item) => {
2094
2231
  if (typeof item === "string") return item;
2095
- if (typeof item === "object" && item !== null) {
2096
- const record = item;
2097
- return String(record.text ?? record.content ?? "");
2098
- }
2232
+ const record = asRecord(item);
2233
+ if (record) return String(record.text ?? record.content ?? "");
2099
2234
  return "";
2100
2235
  }).join(" ");
2101
2236
  }
@@ -2111,9 +2246,9 @@ function extractFirstUserTitle(contextFile, wireFile) {
2111
2246
  if (wireFile && existsSync6(wireFile)) {
2112
2247
  const content = readFileSync3(wireFile, "utf-8");
2113
2248
  for (const record of parseJsonlLines(content)) {
2114
- const message = record.message ?? {};
2249
+ const message = asRecord(record.message) ?? {};
2115
2250
  if (message.type !== "TurnBegin") continue;
2116
- const payload = message.payload ?? {};
2251
+ const payload = asRecord(message.payload) ?? {};
2117
2252
  const userInput = payload.user_input;
2118
2253
  if (!Array.isArray(userInput)) continue;
2119
2254
  const title = normalizeTitleText(kimiContentText(userInput));
@@ -2143,12 +2278,12 @@ var KimiAgent = class extends FileSystemSessionSource {
2143
2278
  }
2144
2279
  if (!existsSync6(configPath)) return;
2145
2280
  try {
2146
- const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
2147
- const workDirs = raw?.work_dirs;
2148
- if (!Array.isArray(workDirs)) return;
2281
+ const raw = asRecord(JSON.parse(readFileSync3(configPath, "utf-8")));
2282
+ const workDirs = asArray(raw?.work_dirs);
2283
+ if (!workDirs) return;
2149
2284
  for (const wd of workDirs) {
2150
- const path2 = wd.path;
2151
- if (typeof path2 !== "string") continue;
2285
+ const path2 = asString(asRecord(wd)?.path);
2286
+ if (!path2) continue;
2152
2287
  const hash = createHash("md5").update(path2).digest("hex");
2153
2288
  this.projectMap.set(hash, path2);
2154
2289
  }
@@ -2207,14 +2342,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2207
2342
  let wireMtime = null;
2208
2343
  let metaFile = "";
2209
2344
  if (existsSync6(statePath)) {
2210
- const state = JSON.parse(readFileSync3(statePath, "utf-8"));
2345
+ const state = asRecord(JSON.parse(readFileSync3(statePath, "utf-8"))) ?? {};
2211
2346
  title = String(state.custom_title ?? "");
2212
- wireMtime = typeof state.wire_mtime === "number" ? state.wire_mtime : null;
2347
+ wireMtime = readWireMtime(state);
2213
2348
  metaFile = statePath;
2214
2349
  } else if (existsSync6(metaPath)) {
2215
- const meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
2350
+ const meta = asRecord(JSON.parse(readFileSync3(metaPath, "utf-8"))) ?? {};
2216
2351
  title = String(meta.title ?? "");
2217
- wireMtime = typeof meta.wire_mtime === "number" ? meta.wire_mtime : null;
2352
+ wireMtime = readWireMtime(meta);
2218
2353
  metaFile = metaPath;
2219
2354
  }
2220
2355
  const cwd = this.projectMap.get(projectHash) || "";
@@ -2343,16 +2478,15 @@ var KimiAgent = class extends FileSystemSessionSource {
2343
2478
  for (const record of parseJsonlLines(content)) {
2344
2479
  seq++;
2345
2480
  try {
2346
- const message = record.message ?? {};
2347
- const msgType = String(message.type ?? "");
2481
+ const message = asRecord(record.message) ?? {};
2482
+ const msgType = asString(message.type) ?? "";
2348
2483
  if (isInternalEventType(msgType)) continue;
2349
- const payload = message.payload ?? {};
2350
- const timestamp = Number(record.timestamp ?? 0);
2351
- const timestampMs = Number.isFinite(timestamp) ? Math.floor(timestamp * 1e3) : 0;
2352
- const usage = message["usage"];
2353
- if (usage && typeof usage === "object") {
2354
- const inputTokens = Number(usage["input_tokens"] ?? 0);
2355
- const outputTokens = Number(usage["output_tokens"] ?? 0);
2484
+ const payload = asRecord(message.payload) ?? {};
2485
+ const timestampMs = Math.floor(readWireTimestamp(record) * 1e3);
2486
+ const usage = asRecord(message["usage"]);
2487
+ if (usage) {
2488
+ const inputTokens = extractTokenField(usage, "input_tokens");
2489
+ const outputTokens = extractTokenField(usage, "output_tokens");
2356
2490
  if (inputTokens || outputTokens) {
2357
2491
  const tokens = { input: inputTokens, output: outputTokens };
2358
2492
  const cost = estimateTokenCost(this.defaultModel, tokens);
@@ -2404,7 +2538,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2404
2538
  continue;
2405
2539
  }
2406
2540
  if (msgType === "ToolCall") {
2407
- const function_ = payload.function;
2541
+ const function_ = asRecord(payload.function);
2408
2542
  const toolName = String(function_?.name ?? "").trim();
2409
2543
  const callId = String(payload.id ?? "").trim();
2410
2544
  if (toolName && callId && KIMI_IGNORED_TOOLS.has(toolName)) {
@@ -2490,8 +2624,8 @@ var KimiAgent = class extends FileSystemSessionSource {
2490
2624
  const content = record.content;
2491
2625
  if (Array.isArray(content)) {
2492
2626
  for (const item of content) {
2493
- if (typeof item !== "object" || item === null) continue;
2494
- const ci = item;
2627
+ const ci = asRecord(item);
2628
+ if (!ci) continue;
2495
2629
  const partType = String(ci.type ?? "");
2496
2630
  if (partType === "think") {
2497
2631
  const text = cleanInternalText(String(ci.think ?? ""));
@@ -2502,15 +2636,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2502
2636
  }
2503
2637
  }
2504
2638
  }
2505
- const toolCalls = record.tool_calls;
2506
- if (Array.isArray(toolCalls)) {
2639
+ const toolCalls = asArray(record.tool_calls);
2640
+ if (toolCalls) {
2507
2641
  for (const tc of toolCalls) {
2508
- if (typeof tc !== "object" || tc === null) continue;
2509
- const tcRecord = tc;
2510
- const function_ = tcRecord.function;
2642
+ const tcRecord = asRecord(tc);
2643
+ const function_ = asRecord(tcRecord?.function);
2511
2644
  if (!function_) continue;
2512
2645
  const toolName = String(function_.name ?? "").trim();
2513
- const callId = String(tcRecord.id ?? "").trim();
2646
+ const callId = String(tcRecord?.id ?? "").trim();
2514
2647
  if (toolName && callId && KIMI_IGNORED_TOOLS.has(toolName)) {
2515
2648
  ignoredToolCallIds.add(callId);
2516
2649
  continue;
@@ -2575,11 +2708,11 @@ var KimiAgent = class extends FileSystemSessionSource {
2575
2708
  const content = readFileSync3(wirePath, "utf-8");
2576
2709
  for (const line of content.split("\n").filter((l) => l.trim())) {
2577
2710
  try {
2578
- const data = JSON.parse(line);
2579
- const tokenUsage = data.message?.usage;
2711
+ const data = asRecord(JSON.parse(line));
2712
+ const tokenUsage = asRecord(asRecord(data?.message)?.usage);
2580
2713
  if (!tokenUsage) continue;
2581
- const inputTokens = Number(tokenUsage.input_tokens ?? 0);
2582
- const outputTokens = Number(tokenUsage.output_tokens ?? 0);
2714
+ const inputTokens = extractTokenField(tokenUsage, "input_tokens");
2715
+ const outputTokens = extractTokenField(tokenUsage, "output_tokens");
2583
2716
  stats.total_input_tokens += inputTokens;
2584
2717
  stats.total_output_tokens += outputTokens;
2585
2718
  const cost = estimateTokenCost(this.defaultModel, {
@@ -2599,10 +2732,14 @@ var KimiAgent = class extends FileSystemSessionSource {
2599
2732
  const rawContent = readFileSync3(rawPath, "utf-8");
2600
2733
  for (const line of rawContent.split("\n").filter((l) => l.trim())) {
2601
2734
  try {
2602
- const data = JSON.parse(line);
2603
- if (data.role === "_usage" && typeof data.token_count === "number") {
2604
- stats.total_tokens = data.token_count;
2735
+ const data = asRecord(JSON.parse(line));
2736
+ if (data?.role !== "_usage") continue;
2737
+ const tokenCount = asNumber(data.token_count);
2738
+ if (tokenCount === void 0) {
2739
+ reportFieldMismatch("kimi", "usage.token_count");
2740
+ continue;
2605
2741
  }
2742
+ stats.total_tokens = tokenCount;
2606
2743
  } catch {
2607
2744
  }
2608
2745
  }
@@ -2628,11 +2765,268 @@ var KimiAgent = class extends FileSystemSessionSource {
2628
2765
  };
2629
2766
  }
2630
2767
  };
2768
+ var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
2769
+ var EXEC_OUTPUT_ENVELOPE_RE = /^Script completed\nWall time [^\n]*\nOutput:\n?/;
2770
+ function stripExecOutputEnvelope(text) {
2771
+ return text.replace(EXEC_OUTPUT_ENVELOPE_RE, "");
2772
+ }
2773
+ function splitExecToolName(name) {
2774
+ if (name.startsWith("mcp__")) {
2775
+ const separator = name.lastIndexOf("__");
2776
+ if (separator > 0 && separator + 2 < name.length) {
2777
+ return { name: name.slice(separator + 2), namespace: name.slice(0, separator + 2) };
2778
+ }
2779
+ }
2780
+ return { name };
2781
+ }
2782
+ function pickExecOutputTarget(calls) {
2783
+ for (let index = calls.length - 1; index >= 0; index -= 1) {
2784
+ const { name } = splitExecToolName(calls[index].name);
2785
+ if (name !== "apply_patch" && name !== "update_plan") return index;
2786
+ }
2787
+ return calls.length - 1;
2788
+ }
2789
+ function getExecPatchText(args) {
2790
+ if (typeof args === "string") return args;
2791
+ if (args && typeof args === "object") {
2792
+ const patch = args["patch"];
2793
+ if (typeof patch === "string") return patch;
2794
+ }
2795
+ return "";
2796
+ }
2797
+ function decodeExecCalls(input) {
2798
+ if (typeof input !== "string" || !input.includes("tools.")) return [];
2799
+ const scope = collectStringVars(input);
2800
+ const calls = [];
2801
+ const callRe = /tools\.([A-Za-z_$][\w$]*)\s*\(/g;
2802
+ let match;
2803
+ while ((match = callRe.exec(input)) !== null) {
2804
+ const reader = new JsValueReader(input, callRe.lastIndex, scope);
2805
+ const args = reader.parseValue();
2806
+ if (args !== PARSE_FAIL) {
2807
+ calls.push({ name: match[1], args });
2808
+ callRe.lastIndex = reader.pos;
2809
+ }
2810
+ }
2811
+ return calls;
2812
+ }
2813
+ function collectStringVars(input) {
2814
+ const scope = /* @__PURE__ */ new Map();
2815
+ const assignRe = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*/g;
2816
+ let match;
2817
+ while ((match = assignRe.exec(input)) !== null) {
2818
+ const reader = new JsValueReader(input, assignRe.lastIndex, scope);
2819
+ const value = reader.parseValue();
2820
+ if (value !== PARSE_FAIL) {
2821
+ if (typeof value === "string") scope.set(match[1], value);
2822
+ assignRe.lastIndex = reader.pos;
2823
+ }
2824
+ }
2825
+ return scope;
2826
+ }
2827
+ var IDENT_START_RE = /[A-Za-z_$]/;
2828
+ var IDENT_PART_RE = /[\w$]/;
2829
+ var JsValueReader = class {
2830
+ pos;
2831
+ src;
2832
+ scope;
2833
+ constructor(src, start, scope) {
2834
+ this.src = src;
2835
+ this.pos = start;
2836
+ this.scope = scope;
2837
+ }
2838
+ parseValue() {
2839
+ this.skipTrivia();
2840
+ const char = this.src[this.pos];
2841
+ if (char === void 0) return PARSE_FAIL;
2842
+ if (char === "{") return this.parseObject();
2843
+ if (char === "[") return this.parseArray();
2844
+ if (char === '"' || char === "'" || char === "`") return this.parseString(char);
2845
+ if (char === "-" || char === "+" || char >= "0" && char <= "9") return this.parseNumber();
2846
+ if (IDENT_START_RE.test(char)) return this.parseIdentifierValue();
2847
+ return PARSE_FAIL;
2848
+ }
2849
+ parseObject() {
2850
+ this.pos++;
2851
+ const result = {};
2852
+ this.skipTrivia();
2853
+ if (this.src[this.pos] === "}") {
2854
+ this.pos++;
2855
+ return result;
2856
+ }
2857
+ while (this.pos < this.src.length) {
2858
+ this.skipTrivia();
2859
+ const key = this.parseKey();
2860
+ if (key === PARSE_FAIL) return PARSE_FAIL;
2861
+ this.skipTrivia();
2862
+ if (this.src[this.pos] === ":") {
2863
+ this.pos++;
2864
+ const value = this.parseValue();
2865
+ if (value === PARSE_FAIL) return PARSE_FAIL;
2866
+ result[key] = value;
2867
+ } else {
2868
+ result[key] = this.resolveIdentifier(key);
2869
+ }
2870
+ this.skipTrivia();
2871
+ const next = this.src[this.pos];
2872
+ if (next === ",") {
2873
+ this.pos++;
2874
+ this.skipTrivia();
2875
+ if (this.src[this.pos] === "}") {
2876
+ this.pos++;
2877
+ return result;
2878
+ }
2879
+ continue;
2880
+ }
2881
+ if (next === "}") {
2882
+ this.pos++;
2883
+ return result;
2884
+ }
2885
+ return PARSE_FAIL;
2886
+ }
2887
+ return PARSE_FAIL;
2888
+ }
2889
+ parseArray() {
2890
+ this.pos++;
2891
+ const result = [];
2892
+ this.skipTrivia();
2893
+ if (this.src[this.pos] === "]") {
2894
+ this.pos++;
2895
+ return result;
2896
+ }
2897
+ while (this.pos < this.src.length) {
2898
+ const value = this.parseValue();
2899
+ if (value === PARSE_FAIL) return PARSE_FAIL;
2900
+ result.push(value);
2901
+ this.skipTrivia();
2902
+ const next = this.src[this.pos];
2903
+ if (next === ",") {
2904
+ this.pos++;
2905
+ this.skipTrivia();
2906
+ if (this.src[this.pos] === "]") {
2907
+ this.pos++;
2908
+ return result;
2909
+ }
2910
+ continue;
2911
+ }
2912
+ if (next === "]") {
2913
+ this.pos++;
2914
+ return result;
2915
+ }
2916
+ return PARSE_FAIL;
2917
+ }
2918
+ return PARSE_FAIL;
2919
+ }
2920
+ parseKey() {
2921
+ const char = this.src[this.pos];
2922
+ if (char === '"' || char === "'" || char === "`") {
2923
+ const value = this.parseString(char);
2924
+ return typeof value === "string" ? value : PARSE_FAIL;
2925
+ }
2926
+ if (char !== void 0 && IDENT_START_RE.test(char)) return this.readIdentifier();
2927
+ return PARSE_FAIL;
2928
+ }
2929
+ parseString(quote) {
2930
+ this.pos++;
2931
+ let out = "";
2932
+ while (this.pos < this.src.length) {
2933
+ const char = this.src[this.pos++];
2934
+ if (char === "\\") {
2935
+ out += this.readEscape();
2936
+ continue;
2937
+ }
2938
+ if (char === quote) break;
2939
+ out += char;
2940
+ }
2941
+ return out;
2942
+ }
2943
+ readEscape() {
2944
+ const char = this.src[this.pos++];
2945
+ switch (char) {
2946
+ case "n":
2947
+ return "\n";
2948
+ case "t":
2949
+ return " ";
2950
+ case "r":
2951
+ return "\r";
2952
+ case "b":
2953
+ return "\b";
2954
+ case "f":
2955
+ return "\f";
2956
+ case "v":
2957
+ return "\v";
2958
+ case "0":
2959
+ return "\0";
2960
+ case "u": {
2961
+ const hex = this.src.slice(this.pos, this.pos + 4);
2962
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
2963
+ this.pos += 4;
2964
+ return String.fromCharCode(parseInt(hex, 16));
2965
+ }
2966
+ return "u";
2967
+ }
2968
+ case "x": {
2969
+ const hex = this.src.slice(this.pos, this.pos + 2);
2970
+ if (/^[0-9a-fA-F]{2}$/.test(hex)) {
2971
+ this.pos += 2;
2972
+ return String.fromCharCode(parseInt(hex, 16));
2973
+ }
2974
+ return "x";
2975
+ }
2976
+ default:
2977
+ return char ?? "";
2978
+ }
2979
+ }
2980
+ parseNumber() {
2981
+ const numberRe = /[-+]?(?:0[xX][0-9a-fA-F]+|(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/y;
2982
+ numberRe.lastIndex = this.pos;
2983
+ const match = numberRe.exec(this.src);
2984
+ if (!match) return PARSE_FAIL;
2985
+ this.pos += match[0].length;
2986
+ return Number(match[0]);
2987
+ }
2988
+ parseIdentifierValue() {
2989
+ const name = this.readIdentifier();
2990
+ if (name === "true") return true;
2991
+ if (name === "false") return false;
2992
+ if (name === "null") return null;
2993
+ if (name === "undefined") return void 0;
2994
+ return this.resolveIdentifier(name);
2995
+ }
2996
+ resolveIdentifier(name) {
2997
+ return this.scope.has(name) ? this.scope.get(name) : void 0;
2998
+ }
2999
+ readIdentifier() {
3000
+ const start = this.pos;
3001
+ while (this.pos < this.src.length && IDENT_PART_RE.test(this.src[this.pos])) this.pos++;
3002
+ return this.src.slice(start, this.pos);
3003
+ }
3004
+ skipTrivia() {
3005
+ while (this.pos < this.src.length) {
3006
+ const char = this.src[this.pos];
3007
+ if (char === " " || char === " " || char === "\n" || char === "\r") {
3008
+ this.pos++;
3009
+ continue;
3010
+ }
3011
+ if (char === "/" && this.src[this.pos + 1] === "/") {
3012
+ const newline = this.src.indexOf("\n", this.pos + 2);
3013
+ this.pos = newline === -1 ? this.src.length : newline + 1;
3014
+ continue;
3015
+ }
3016
+ if (char === "/" && this.src[this.pos + 1] === "*") {
3017
+ const close = this.src.indexOf("*/", this.pos + 2);
3018
+ this.pos = close === -1 ? this.src.length : close + 2;
3019
+ continue;
3020
+ }
3021
+ break;
3022
+ }
3023
+ }
3024
+ };
2631
3025
  var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
2632
3026
  var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
2633
3027
  var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
2634
3028
  var HEAD_INDEX_VERSION2 = "codex-head-v1";
2635
- var PARSER_VERSION = "codex-parser-v3";
3029
+ var PARSER_VERSION = "codex-parser-v4";
2636
3030
  var DEVELOPER_LIKE_USER_MARKERS = [
2637
3031
  "agents.md instructions for",
2638
3032
  "<instructions>",
@@ -2675,6 +3069,19 @@ function extractCachedInputTokens(usage) {
2675
3069
  if (!usage) return 0;
2676
3070
  return Number(usage["cached_input_tokens"] ?? usage["cache_read_input_tokens"] ?? 0);
2677
3071
  }
3072
+ function narrowRecordField(value, field) {
3073
+ return narrowField("codex", field, value, asRecord);
3074
+ }
3075
+ function extractPayload(data) {
3076
+ return narrowRecordField(data["payload"], "payload") ?? {};
3077
+ }
3078
+ function extractTokenUsage(payload) {
3079
+ const info = narrowRecordField(payload["info"], "token_count.info");
3080
+ return {
3081
+ totalUsage: info ? narrowRecordField(info["total_token_usage"], "token_count.total_token_usage") : void 0,
3082
+ lastUsage: info ? narrowRecordField(info["last_token_usage"], "token_count.last_token_usage") : void 0
3083
+ };
3084
+ }
2678
3085
  function resolveToolIdentity(name, namespace) {
2679
3086
  const mappedName = CODEX_TOOL_TITLE_MAP[name];
2680
3087
  if (mappedName) return { tool: mappedName };
@@ -2709,6 +3116,17 @@ function normalizeCustomToolArguments(toolName, input) {
2709
3116
  }
2710
3117
  return input;
2711
3118
  }
3119
+ function flattenOutputText(output) {
3120
+ if (typeof output === "string") return output;
3121
+ if (Array.isArray(output)) {
3122
+ return output.map((item) => {
3123
+ if (typeof item === "string") return item;
3124
+ const record = asRecord(item);
3125
+ return record ? asString(record["text"]) ?? "" : "";
3126
+ }).join("");
3127
+ }
3128
+ return "";
3129
+ }
2712
3130
  var PATCH_BEGIN_RE = /\*\*\* Begin Patch/;
2713
3131
  var PATCH_END_RE = /\*\*\* End Patch/;
2714
3132
  var PATCH_HEADER_RE = /\*\*\*\s+(Add|Delete|Update|Move)\s+File:\s*(.+)/;
@@ -2815,10 +3233,10 @@ var CodexAgent = class extends FileSystemSessionSource {
2815
3233
  listSessionSources(options) {
2816
3234
  if (!this.basePath) return [];
2817
3235
  this.loadSessionIndex();
2818
- return this.listRolloutFiles(options).map((file) => ({
3236
+ return this.listRolloutFiles(options).map(({ file, stat }) => ({
2819
3237
  sessionId: extractSessionId(file),
2820
3238
  sourcePath: file,
2821
- fingerprint: this.sourceFingerprint(file)
3239
+ fingerprint: this.sourceFingerprint(file, stat)
2822
3240
  }));
2823
3241
  }
2824
3242
  scanSessionSource(sourcePath, options) {
@@ -2849,20 +3267,18 @@ var CodexAgent = class extends FileSystemSessionSource {
2849
3267
  try {
2850
3268
  const recordType = String(record["type"] ?? "");
2851
3269
  if (recordType === "turn_context") {
2852
- const payload = record["payload"] ?? {};
3270
+ const payload = extractPayload(record);
2853
3271
  activeModel = extractModelName(payload["model"]) ?? activeModel;
2854
3272
  }
2855
3273
  pendingPlan = this.convertRecord(record, transcript, pendingPlan, activeModel);
2856
3274
  if (recordType === "event_msg") {
2857
- const payload = record["payload"] ?? {};
3275
+ const payload = extractPayload(record);
2858
3276
  if (String(payload["type"] ?? "") === "token_count") {
2859
- const info = payload["info"];
2860
- const totalUsage = info?.["total_token_usage"];
3277
+ const { totalUsage, lastUsage } = extractTokenUsage(payload);
2861
3278
  const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
2862
3279
  if (cumulativeTotal > 0 && cumulativeTotal === prevCumulativeTotal) {
2863
3280
  } else {
2864
3281
  prevCumulativeTotal = cumulativeTotal;
2865
- const lastUsage = info?.["last_token_usage"];
2866
3282
  let inputTokens = 0;
2867
3283
  let outputTokens = 0;
2868
3284
  let reasoningTokens = 0;
@@ -2937,6 +3353,7 @@ var CodexAgent = class extends FileSystemSessionSource {
2937
3353
  return [];
2938
3354
  }
2939
3355
  }
3356
+ /** Stats each rollout file once during the walk; caller reuses it for the scan window check and the fingerprint. */
2940
3357
  walkDirForRolloutFiles(dir, options) {
2941
3358
  const files = [];
2942
3359
  try {
@@ -2945,14 +3362,14 @@ var CodexAgent = class extends FileSystemSessionSource {
2945
3362
  if (entry.isDirectory()) {
2946
3363
  files.push(...this.walkDirForRolloutFiles(fullPath, options));
2947
3364
  } else if (entry.name.endsWith(".jsonl") && entry.name.startsWith("rollout-")) {
2948
- if (options?.from != null || options?.to != null) {
2949
- try {
2950
- if (!matchesScanWindow(statSync4(fullPath).mtimeMs, options)) continue;
2951
- } catch {
2952
- continue;
2953
- }
3365
+ let stat;
3366
+ try {
3367
+ stat = statSync4(fullPath);
3368
+ } catch {
3369
+ continue;
2954
3370
  }
2955
- files.push(fullPath);
3371
+ if (!matchesScanWindow(stat.mtimeMs, options)) continue;
3372
+ files.push({ file: fullPath, stat });
2956
3373
  }
2957
3374
  }
2958
3375
  } catch {
@@ -2961,12 +3378,13 @@ var CodexAgent = class extends FileSystemSessionSource {
2961
3378
  }
2962
3379
  buildSessionMeta(head, file) {
2963
3380
  const indexPath = this.getSessionIndexPath();
3381
+ const stat = statSync4(file);
2964
3382
  return {
2965
3383
  id: head.id,
2966
3384
  title: head.title,
2967
3385
  sourcePath: file,
2968
- sourceFingerprint: this.sourceFingerprint(file),
2969
- sourceMtimeMs: statSync4(file).mtimeMs,
3386
+ sourceFingerprint: this.sourceFingerprint(file, stat),
3387
+ sourceMtimeMs: stat.mtimeMs,
2970
3388
  indexPath: existsSync7(indexPath) ? indexPath : null,
2971
3389
  indexMtimeMs: this.getFileMtimeMs(indexPath),
2972
3390
  headIndexVersion: HEAD_INDEX_VERSION2,
@@ -2978,8 +3396,8 @@ var CodexAgent = class extends FileSystemSessionSource {
2978
3396
  updatedAt: head.time_updated ?? head.time_created
2979
3397
  };
2980
3398
  }
2981
- sourceFingerprint(file) {
2982
- const stat = statSync4(file);
3399
+ /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
3400
+ sourceFingerprint(file, stat) {
2983
3401
  const sessionId = extractSessionId(file);
2984
3402
  return JSON.stringify([
2985
3403
  HEAD_INDEX_VERSION2,
@@ -3046,7 +3464,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3046
3464
  let firstPayload = {};
3047
3465
  let createdAt = 0;
3048
3466
  let lineCount = 0;
3049
- const titleLines = [];
3467
+ let messageTitle = null;
3050
3468
  let updatedAt = 0;
3051
3469
  let messageCount = 0;
3052
3470
  let model = null;
@@ -3072,20 +3490,23 @@ var CodexAgent = class extends FileSystemSessionSource {
3072
3490
  } catch {
3073
3491
  return skippedSession("malformed first record");
3074
3492
  }
3075
- firstPayload = firstRecord["payload"] ?? {};
3493
+ firstPayload = extractPayload(firstRecord);
3076
3494
  createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync4(filePath).mtimeMs;
3077
3495
  updatedAt = createdAt;
3078
3496
  }
3079
- if (titleLines.length < 20) titleLines.push(line);
3080
3497
  try {
3081
3498
  const data = JSON.parse(line);
3082
3499
  const recordType = String(data["type"] ?? "");
3083
- const payload = data["payload"] ?? {};
3500
+ const payload = extractPayload(data);
3084
3501
  const payloadType = String(payload["type"] ?? "");
3085
3502
  if (isInternalEventType2(recordType) || isInternalEventType2(payloadType)) continue;
3086
3503
  hasNonInternalRecord = true;
3087
- const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
3504
+ const recordTs = parseTimestampMs2(data) || parseTimestampMs2(payload);
3088
3505
  if (recordTs > updatedAt) updatedAt = recordTs;
3506
+ if (messageTitle === null && lineCount <= 20) {
3507
+ const candidate = this.extractCodexRecordTitle(data);
3508
+ if (candidate) messageTitle = candidate;
3509
+ }
3089
3510
  if (recordType === "session_meta" || recordType === "turn_context") {
3090
3511
  const nextModel = extractModelName(payload["model"]);
3091
3512
  if (nextModel) {
@@ -3100,7 +3521,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3100
3521
  if (COUNTED_TYPES.has(pType)) {
3101
3522
  messageCount++;
3102
3523
  }
3103
- const info = p["info"];
3524
+ const info = narrowRecordField(p["info"], "response_item.info");
3104
3525
  const m = info?.["model"] ?? p["model"];
3105
3526
  if (typeof m === "string" && m.trim()) {
3106
3527
  activeModel = m.trim();
@@ -3108,14 +3529,11 @@ var CodexAgent = class extends FileSystemSessionSource {
3108
3529
  }
3109
3530
  }
3110
3531
  if (recordType === "event_msg") {
3111
- const p = data["payload"] ?? {};
3112
- if (String(p["type"] ?? "") === "token_count") {
3113
- const info = p["info"];
3114
- const totalUsage = info?.["total_token_usage"];
3532
+ if (String(payload["type"] ?? "") === "token_count") {
3533
+ const { totalUsage, lastUsage } = extractTokenUsage(payload);
3115
3534
  const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
3116
3535
  if (cumulativeTotal > 0 && cumulativeTotal !== scanPrevCumulativeTotal) {
3117
3536
  scanPrevCumulativeTotal = cumulativeTotal;
3118
- const lastUsage = info?.["last_token_usage"];
3119
3537
  let inputTokens = 0;
3120
3538
  let outputTokens = 0;
3121
3539
  let reasoningTokens = 0;
@@ -3160,7 +3578,6 @@ var CodexAgent = class extends FileSystemSessionSource {
3160
3578
  if (lineCount === 0) return skippedSession("empty file");
3161
3579
  if (!hasNonInternalRecord) return filteredSession("internal events only");
3162
3580
  const indexTitle = this.getTitleForSession(sessionId);
3163
- const messageTitle = this.extractTitleFromLines(titleLines);
3164
3581
  const directory = firstPayload["cwd"] ? String(firstPayload["cwd"]) : "";
3165
3582
  const title = resolveSessionTitle(indexTitle, messageTitle, basenameTitle(directory || null));
3166
3583
  return parsedSession({
@@ -3195,7 +3612,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3195
3612
  } catch {
3196
3613
  return skippedSession("malformed first record");
3197
3614
  }
3198
- const payload = firstRecord["payload"] ?? {};
3615
+ const payload = extractPayload(firstRecord);
3199
3616
  const stat = statSync4(filePath);
3200
3617
  const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
3201
3618
  const indexTitle = this.getTitleForSession(sessionId);
@@ -3218,31 +3635,42 @@ var CodexAgent = class extends FileSystemSessionSource {
3218
3635
  }
3219
3636
  });
3220
3637
  }
3638
+ /** Fast path only: parses each of the first 20 lines to find a title (no full stats pass available). */
3221
3639
  extractTitleFromLines(lines) {
3222
3640
  for (const line of lines.slice(0, 20)) {
3223
3641
  try {
3224
- const data = JSON.parse(line);
3225
- const recordType = String(data["type"] ?? "");
3226
- if (recordType !== "response_item" || isInternalEventType2(recordType)) continue;
3227
- const payload = data["payload"] ?? {};
3228
- const pType = String(payload["type"] ?? "");
3229
- if (pType !== "message" || isInternalEventType2(pType)) continue;
3230
- if (String(payload["role"] ?? "") !== "user") continue;
3231
- const content = payload["content"];
3232
- let text = null;
3233
- if (Array.isArray(content)) {
3234
- text = content.filter((item) => typeof item === "object" && item !== null && "text" in item).map((item) => String(item["text"] ?? "")).join(" ");
3235
- } else if (typeof content === "string") {
3236
- text = content;
3237
- }
3238
- if (!text || isDeveloperLikeUserMessage(text)) continue;
3239
- const title = normalizeTitleText(text);
3642
+ const title = this.extractCodexRecordTitle(JSON.parse(line));
3240
3643
  if (title) return title;
3241
3644
  } catch {
3242
3645
  }
3243
3646
  }
3244
3647
  return null;
3245
3648
  }
3649
+ /**
3650
+ * Title candidate from a single already-parsed record, if it's a visible
3651
+ * user message. Shared by the main streaming pass (which parses every line
3652
+ * once) and the fast path's extractTitleFromLines().
3653
+ */
3654
+ extractCodexRecordTitle(data) {
3655
+ const recordType = String(data["type"] ?? "");
3656
+ if (recordType !== "response_item" || isInternalEventType2(recordType)) return null;
3657
+ const payload = extractPayload(data);
3658
+ const pType = String(payload["type"] ?? "");
3659
+ if (pType !== "message" || isInternalEventType2(pType)) return null;
3660
+ if (String(payload["role"] ?? "") !== "user") return null;
3661
+ const content = payload["content"];
3662
+ let text = null;
3663
+ if (Array.isArray(content)) {
3664
+ text = content.map((item) => {
3665
+ const record = asRecord(item);
3666
+ return record ? String(record["text"] ?? "") : "";
3667
+ }).join(" ");
3668
+ } else if (typeof content === "string") {
3669
+ text = content;
3670
+ }
3671
+ if (!text || isDeveloperLikeUserMessage(text)) return null;
3672
+ return normalizeTitleText(text) || null;
3673
+ }
3246
3674
  // ---- Record conversion ----
3247
3675
  convertRecord(data, transcript, pendingPlan, activeModel) {
3248
3676
  const recordType = String(data["type"] ?? "");
@@ -3251,7 +3679,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3251
3679
  return pendingPlan;
3252
3680
  }
3253
3681
  if (recordType !== "response_item") return pendingPlan;
3254
- const payload = data["payload"] ?? {};
3682
+ const payload = extractPayload(data);
3255
3683
  const payloadType = String(payload["type"] ?? "");
3256
3684
  if (isInternalEventType2(payloadType)) return pendingPlan;
3257
3685
  const timestampMs = parseTimestampMs2(data) || parseTimestampMs2(payload);
@@ -3296,8 +3724,8 @@ var CodexAgent = class extends FileSystemSessionSource {
3296
3724
  if (!Array.isArray(content)) return pendingPlan;
3297
3725
  const textParts = [];
3298
3726
  for (const item of content) {
3299
- if (typeof item !== "object" || item === null) continue;
3300
- const ci = item;
3727
+ const ci = asRecord(item);
3728
+ if (!ci) continue;
3301
3729
  if (String(ci["type"] ?? "") === "output_text") {
3302
3730
  const text = String(ci["text"] ?? "");
3303
3731
  if (text.trim()) textParts.push(text);
@@ -3330,9 +3758,11 @@ var CodexAgent = class extends FileSystemSessionSource {
3330
3758
  // ---- User message ----
3331
3759
  convertUserMessage(payload, transcript, timestampMs, pendingPlan) {
3332
3760
  const content = payload["content"];
3333
- const text = Array.isArray(content) ? content.map(
3334
- (c) => typeof c === "object" && c !== null ? String(c["text"] ?? "") : String(c ?? "")
3335
- ).join(" ") : String(content ?? "");
3761
+ const text = Array.isArray(content) ? content.map((c) => {
3762
+ if (Array.isArray(c)) return "";
3763
+ const record = asRecord(c);
3764
+ return record ? String(record["text"] ?? "") : String(c ?? "");
3765
+ }).join(" ") : String(content ?? "");
3336
3766
  const visibleText = cleanInternalText(text);
3337
3767
  if (!visibleText) return pendingPlan;
3338
3768
  if (isDeveloperLikeUserMessage(visibleText)) return pendingPlan;
@@ -3348,8 +3778,13 @@ var CodexAgent = class extends FileSystemSessionSource {
3348
3778
  }
3349
3779
  const subagentMatch = visibleText.match(SUBAGENT_NOTIFICATION_PATTERN);
3350
3780
  if (subagentMatch) {
3781
+ let notifPayload;
3351
3782
  try {
3352
- const notifPayload = JSON.parse(subagentMatch[1]);
3783
+ notifPayload = asRecord(JSON.parse(subagentMatch[1]));
3784
+ } catch {
3785
+ notifPayload = void 0;
3786
+ }
3787
+ if (notifPayload) {
3353
3788
  const agentId = String(notifPayload["agent_id"] ?? "");
3354
3789
  const nickname = String(notifPayload["nickname"] ?? "");
3355
3790
  const completedText = String(notifPayload["completed"] ?? "");
@@ -3369,7 +3804,6 @@ var CodexAgent = class extends FileSystemSessionSource {
3369
3804
  });
3370
3805
  transcript.beginTurn();
3371
3806
  return pendingPlan;
3372
- } catch {
3373
3807
  }
3374
3808
  }
3375
3809
  transcript.appendMessage({
@@ -3386,12 +3820,11 @@ var CodexAgent = class extends FileSystemSessionSource {
3386
3820
  if (!Array.isArray(summary)) return;
3387
3821
  const texts = [];
3388
3822
  for (const item of summary) {
3389
- if (typeof item === "object" && item !== null) {
3390
- const ci = item;
3391
- if (String(ci["type"] ?? "") === "summary_text") {
3392
- const text = String(ci["text"] ?? "");
3393
- if (text.trim()) texts.push(text);
3394
- }
3823
+ const ci = asRecord(item);
3824
+ if (!ci) continue;
3825
+ if (String(ci["type"] ?? "") === "summary_text") {
3826
+ const text = String(ci["text"] ?? "");
3827
+ if (text.trim()) texts.push(text);
3395
3828
  }
3396
3829
  }
3397
3830
  if (texts.length === 0) return;
@@ -3437,7 +3870,9 @@ var CodexAgent = class extends FileSystemSessionSource {
3437
3870
  convertToolCallOutput(payload, transcript, timestampMs) {
3438
3871
  const callId = String(payload["call_id"] ?? "").trim();
3439
3872
  if (!callId) return;
3440
- const outputText = cleanInternalText(String(payload["output"] ?? ""));
3873
+ const outputText = cleanInternalText(
3874
+ stripExecOutputEnvelope(flattenOutputText(payload["output"]))
3875
+ );
3441
3876
  const outputParts = outputText ? [{ type: "text", text: outputText, time_created: timestampMs }] : [];
3442
3877
  if (outputParts.length > 0) {
3443
3878
  transcript.resolveToolCall(callId, { output: outputParts, status: "completed" });
@@ -3448,6 +3883,13 @@ var CodexAgent = class extends FileSystemSessionSource {
3448
3883
  const callId = String(payload["call_id"] ?? "").trim();
3449
3884
  const name = String(payload["name"] ?? "").trim();
3450
3885
  if (!name) return;
3886
+ if (name === "exec") {
3887
+ const decoded = decodeExecCalls(payload["input"]);
3888
+ if (decoded.length > 0) {
3889
+ this.appendDecodedExecCalls(decoded, callId, transcript, timestampMs, activeModel);
3890
+ return;
3891
+ }
3892
+ }
3451
3893
  const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
3452
3894
  const rawInput = payload["input"];
3453
3895
  const normalizedInput = normalizeCustomToolArguments(name, rawInput);
@@ -3469,6 +3911,36 @@ var CodexAgent = class extends FileSystemSessionSource {
3469
3911
  { markModeAsTool: true }
3470
3912
  );
3471
3913
  }
3914
+ // ---- Decoded code-mode exec calls ----
3915
+ appendDecodedExecCalls(calls, callId, transcript, timestampMs, activeModel) {
3916
+ const outputIndex = pickExecOutputTarget(calls);
3917
+ calls.forEach((call, index) => {
3918
+ const partCallId = index === outputIndex ? callId : `${callId}#${index}`;
3919
+ this.appendDecodedExecCall(call, partCallId, transcript, timestampMs, activeModel);
3920
+ });
3921
+ }
3922
+ appendDecodedExecCall(call, callId, transcript, timestampMs, activeModel) {
3923
+ const { name, namespace } = splitExecToolName(call.name);
3924
+ const toolIdentity = resolveToolIdentity(name, namespace);
3925
+ const arguments_ = name === "apply_patch" ? parseApplyPatchInput(getExecPatchText(call.args)) : call.args;
3926
+ const toolPart = {
3927
+ type: "tool",
3928
+ tool: toolIdentity.tool,
3929
+ callID: callId,
3930
+ title: `Tool: ${toolIdentity.tool}`,
3931
+ state: {
3932
+ arguments: arguments_,
3933
+ output: null,
3934
+ metadata: toolIdentity.metadata
3935
+ },
3936
+ time_created: timestampMs
3937
+ };
3938
+ transcript.appendToolCall(
3939
+ toolPart,
3940
+ { id: "", timestampMs, agent: "codex", model: activeModel },
3941
+ { markModeAsTool: true }
3942
+ );
3943
+ }
3472
3944
  };
3473
3945
  var PerfTracer = class {
3474
3946
  rootMarkers = [];
@@ -3544,6 +4016,106 @@ var PerfTracer = class {
3544
4016
  }
3545
4017
  };
3546
4018
  var perf = new PerfTracer();
4019
+ function narrowString(field, value) {
4020
+ return narrowField("cursor", field, value, asString);
4021
+ }
4022
+ function narrowNumber(field, value) {
4023
+ return narrowField("cursor", field, value, asNumber);
4024
+ }
4025
+ function parseSubagentInfos(value) {
4026
+ const arr = asArray(value);
4027
+ if (arr === void 0) return void 0;
4028
+ const infos = [];
4029
+ for (const item of arr) {
4030
+ const record = asRecord(item);
4031
+ if (!record) continue;
4032
+ infos.push({
4033
+ id: narrowString("subagentInfo.id", record.id),
4034
+ composerId: narrowString("subagentInfo.composerId", record.composerId),
4035
+ title: narrowString("subagentInfo.title", record.title),
4036
+ nickname: narrowString("subagentInfo.nickname", record.nickname)
4037
+ });
4038
+ }
4039
+ return infos;
4040
+ }
4041
+ function parseChatMessages(value) {
4042
+ const arr = asArray(value);
4043
+ if (arr === void 0) return void 0;
4044
+ const messages = [];
4045
+ for (const item of arr) {
4046
+ const record = asRecord(item);
4047
+ if (!record) continue;
4048
+ const role = narrowString("chatMessage.role", record.role);
4049
+ if (role === void 0) continue;
4050
+ messages.push({ ...record, role, text: narrowString("chatMessage.text", record.text) });
4051
+ }
4052
+ return messages;
4053
+ }
4054
+ function parseComposerRow(value) {
4055
+ const record = safeParseJsonRecord(value);
4056
+ if (!record) return null;
4057
+ const modelConfig = asRecord(record.modelConfig);
4058
+ return {
4059
+ id: narrowString("composer.id", record.id),
4060
+ composerId: narrowString("composer.composerId", record.composerId),
4061
+ text: narrowString("composer.text", record.text),
4062
+ name: narrowString("composer.name", record.name),
4063
+ title: narrowString("composer.title", record.title),
4064
+ createdAt: narrowNumber("composer.createdAt", record.createdAt),
4065
+ updatedAt: narrowNumber("composer.updatedAt", record.updatedAt),
4066
+ lastSendTime: narrowNumber("composer.lastSendTime", record.lastSendTime),
4067
+ lastUpdatedAt: narrowNumber("composer.lastUpdatedAt", record.lastUpdatedAt),
4068
+ model: narrowString("composer.model", record.model),
4069
+ modelConfig: modelConfig ? { modelName: narrowString("composer.modelConfig.modelName", modelConfig.modelName) } : void 0,
4070
+ inputTokenCount: narrowNumber("composer.inputTokenCount", record.inputTokenCount),
4071
+ outputTokenCount: narrowNumber("composer.outputTokenCount", record.outputTokenCount),
4072
+ subagentInfos: parseSubagentInfos(record.subagentInfos),
4073
+ chatMessages: parseChatMessages(record.chatMessages)
4074
+ };
4075
+ }
4076
+ function parseBubbleRow(value) {
4077
+ const record = safeParseJsonRecord(value);
4078
+ if (!record) return null;
4079
+ const timingInfo = asRecord(record.timingInfo);
4080
+ const tokenCount = asRecord(record.tokenCount);
4081
+ const modelInfo = asRecord(record.modelInfo);
4082
+ const toolFormerData = asRecord(record.toolFormerData);
4083
+ return {
4084
+ ...record,
4085
+ id: narrowString("bubble.id", record.id),
4086
+ composerId: narrowString("bubble.composerId", record.composerId),
4087
+ chatMessages: parseChatMessages(record.chatMessages),
4088
+ type: narrowNumber("bubble.type", record.type),
4089
+ text: narrowString("bubble.text", record.text),
4090
+ requestId: narrowString("bubble.requestId", record.requestId),
4091
+ createdAt: narrowNumber("bubble.createdAt", record.createdAt),
4092
+ timestamp: narrowNumber("bubble.timestamp", record.timestamp),
4093
+ timingInfo: timingInfo ? {
4094
+ clientRpcSendTime: narrowNumber(
4095
+ "bubble.timingInfo.clientRpcSendTime",
4096
+ timingInfo.clientRpcSendTime
4097
+ ),
4098
+ clientSettleTime: narrowNumber(
4099
+ "bubble.timingInfo.clientSettleTime",
4100
+ timingInfo.clientSettleTime
4101
+ ),
4102
+ clientEndTime: narrowNumber("bubble.timingInfo.clientEndTime", timingInfo.clientEndTime)
4103
+ } : void 0,
4104
+ tokenCount: tokenCount ? {
4105
+ inputTokens: narrowNumber("bubble.tokenCount.inputTokens", tokenCount.inputTokens),
4106
+ outputTokens: narrowNumber("bubble.tokenCount.outputTokens", tokenCount.outputTokens)
4107
+ } : void 0,
4108
+ modelInfo: modelInfo ? { modelName: narrowString("bubble.modelInfo.modelName", modelInfo.modelName) } : void 0,
4109
+ toolFormerData: toolFormerData ? {
4110
+ name: narrowString("bubble.toolFormerData.name", toolFormerData.name),
4111
+ toolCallId: narrowString("bubble.toolFormerData.toolCallId", toolFormerData.toolCallId),
4112
+ status: narrowString("bubble.toolFormerData.status", toolFormerData.status),
4113
+ params: toolFormerData.params,
4114
+ result: toolFormerData.result,
4115
+ additionalData: asRecord(toolFormerData.additionalData)
4116
+ } : void 0
4117
+ };
4118
+ }
3547
4119
  var CURSOR_TOOL_TITLE_MAP = {
3548
4120
  read_file_v2: "read",
3549
4121
  edit_file_v2: "edit",
@@ -3564,9 +4136,8 @@ function normalizeToolOutputParts2(output, timestampMs) {
3564
4136
  const parts = [];
3565
4137
  for (const item of output) {
3566
4138
  if (typeof item === "object" && item !== null) {
3567
- const text2 = String(
3568
- item.text ?? item.content ?? ""
3569
- );
4139
+ const record = asRecord(item);
4140
+ const text2 = String(record?.text ?? record?.content ?? "");
3570
4141
  const cleaned = cleanInternalText(text2);
3571
4142
  if (cleaned) parts.push({ type: "text", text: cleaned, time_created: timestampMs });
3572
4143
  } else if (typeof item === "string") {
@@ -3606,9 +4177,9 @@ function buildToolState(action) {
3606
4177
  }
3607
4178
  if (!state.status) {
3608
4179
  if (typeof action.output === "object" && action.output !== null) {
3609
- const out = action.output;
3610
- if (out.success === true) state.status = "completed";
3611
- else if (out.success === false) state.status = "error";
4180
+ const out = asRecord(action.output);
4181
+ if (out?.success === true) state.status = "completed";
4182
+ else if (out?.success === false) state.status = "error";
3612
4183
  else state.status = "completed";
3613
4184
  } else if (action.output != null) {
3614
4185
  state.status = "completed";
@@ -3697,8 +4268,8 @@ var CursorAgent = class extends DatabaseSessionSource {
3697
4268
  if (!existsSync8(wsJsonPath)) continue;
3698
4269
  let workspacePath;
3699
4270
  try {
3700
- const data = JSON.parse(readFileSync5(wsJsonPath, "utf-8"));
3701
- const uri = data.folder ?? data.workspace ?? "";
4271
+ const data = asRecord(JSON.parse(readFileSync5(wsJsonPath, "utf-8")));
4272
+ const uri = narrowString("workspaceJson.folder", data?.folder) ?? narrowString("workspaceJson.workspace", data?.workspace) ?? "";
3702
4273
  if (!uri) continue;
3703
4274
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
3704
4275
  } catch {
@@ -3712,16 +4283,11 @@ var CursorAgent = class extends DatabaseSessionSource {
3712
4283
  const row = wsDb.prepare("SELECT value FROM ItemTable WHERE key = 'composer.composerData'").get();
3713
4284
  if (!row?.value) continue;
3714
4285
  const parsed = JSON.parse(row.value);
3715
- let composers;
3716
- if (parsed !== null && typeof parsed === "object" && "allComposers" in parsed && Array.isArray(parsed["allComposers"])) {
3717
- composers = parsed.allComposers;
3718
- } else if (Array.isArray(parsed)) {
3719
- composers = parsed;
3720
- } else {
3721
- continue;
3722
- }
3723
- for (const c of composers) {
3724
- const id = c.composerId ?? c.id;
4286
+ const composers = asArray(asRecord(parsed)?.allComposers) ?? asArray(parsed) ?? [];
4287
+ for (const item of composers) {
4288
+ const composer = asRecord(item);
4289
+ if (!composer) continue;
4290
+ const id = narrowString("workspaceComposer.composerId", composer.composerId) ?? narrowString("workspaceComposer.id", composer.id);
3725
4291
  if (id) map.set(id, workspacePath);
3726
4292
  }
3727
4293
  } catch {
@@ -3752,8 +4318,8 @@ var CursorAgent = class extends DatabaseSessionSource {
3752
4318
  let processed = 0;
3753
4319
  for (const row of rows) {
3754
4320
  try {
3755
- const composer = JSON.parse(row.value);
3756
- if (!composer.id && !composer.composerId) continue;
4321
+ const composer = parseComposerRow(row.value);
4322
+ if (!composer || !composer.id && !composer.composerId) continue;
3757
4323
  const composerId = composer.id || composer.composerId || "";
3758
4324
  const createdAt = composer.createdAt ?? 0;
3759
4325
  const updatedAt = composer.updatedAt ?? composer.lastUpdatedAt ?? composer.lastSendTime ?? createdAt;
@@ -3954,8 +4520,8 @@ var CursorAgent = class extends DatabaseSessionSource {
3954
4520
  const rows = db.prepare("SELECT value FROM cursorDiskKV WHERE key LIKE ? ORDER BY key").all(`bubbleId:${composerId}:%`);
3955
4521
  for (const row of rows) {
3956
4522
  try {
3957
- const bubble = JSON.parse(row.value);
3958
- if (bubble.requestId && typeof bubble.requestId === "string" && bubble.requestId.trim()) {
4523
+ const bubble = parseBubbleRow(row.value);
4524
+ if (bubble?.requestId?.trim()) {
3959
4525
  return bubble.requestId.trim();
3960
4526
  }
3961
4527
  } catch {
@@ -3971,8 +4537,8 @@ var CursorAgent = class extends DatabaseSessionSource {
3971
4537
  const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%' AND value LIKE ?").all(`%"requestId":"${requestId}"%`);
3972
4538
  for (const row of rows) {
3973
4539
  try {
3974
- const bubble = JSON.parse(row.value);
3975
- if (bubble.requestId === requestId) {
4540
+ const bubble = parseBubbleRow(row.value);
4541
+ if (bubble?.requestId === requestId) {
3976
4542
  const keyParts = row.key.split(":");
3977
4543
  if (keyParts.length >= 2 && keyParts[1]) {
3978
4544
  return keyParts[1];
@@ -3998,8 +4564,8 @@ var CursorAgent = class extends DatabaseSessionSource {
3998
4564
  let count = 0;
3999
4565
  for (const row of rows) {
4000
4566
  try {
4001
- const bubble = JSON.parse(row.value);
4002
- if (bubble.type === 1 || bubble.type === 2) {
4567
+ const bubble = parseBubbleRow(row.value);
4568
+ if (bubble?.type === 1 || bubble?.type === 2) {
4003
4569
  count++;
4004
4570
  }
4005
4571
  } catch {
@@ -4019,8 +4585,8 @@ var CursorAgent = class extends DatabaseSessionSource {
4019
4585
  let messageIndex = 0;
4020
4586
  for (const row of rows) {
4021
4587
  try {
4022
- const bubble = JSON.parse(row.value);
4023
- if (isInternalBubble(bubble)) continue;
4588
+ const bubble = parseBubbleRow(row.value);
4589
+ if (!bubble || isInternalBubble(bubble)) continue;
4024
4590
  const bubbleId = row.key.split(":").pop() || String(messageIndex);
4025
4591
  const role = bubble.type === 2 ? "assistant" : "user";
4026
4592
  let timestampMs = 0;
@@ -4109,7 +4675,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4109
4675
  }
4110
4676
  }
4111
4677
  if (toolName === "create_plan") {
4112
- const planText = typeof state.input === "object" && state.input !== null ? state.input.plan : void 0;
4678
+ const planText = asRecord(state.input)?.plan;
4113
4679
  return {
4114
4680
  type: "plan",
4115
4681
  title: "Plan",
@@ -4131,20 +4697,12 @@ var CursorAgent = class extends DatabaseSessionSource {
4131
4697
  loadComposer(db, sessionId) {
4132
4698
  const row = db.prepare("SELECT value FROM cursorDiskKV WHERE key = ?").get(`composerData:${sessionId}`);
4133
4699
  if (!row) return null;
4134
- try {
4135
- return JSON.parse(row.value);
4136
- } catch {
4137
- return null;
4138
- }
4700
+ return parseComposerRow(row.value);
4139
4701
  }
4140
4702
  loadBubble(db, sessionId) {
4141
4703
  const row = db.prepare("SELECT value FROM cursorDiskKV WHERE key = ?").get(`bubble:${sessionId}`);
4142
4704
  if (!row) return null;
4143
- try {
4144
- return JSON.parse(row.value);
4145
- } catch {
4146
- return null;
4147
- }
4705
+ return parseBubbleRow(row.value);
4148
4706
  }
4149
4707
  appendSubagentMessages(db, composer, messages) {
4150
4708
  const subagentInfos = composer.subagentInfos;
@@ -4198,6 +4756,17 @@ function parseTimestampMs3(value) {
4198
4756
  const ts = Date.parse(text);
4199
4757
  return Number.isNaN(ts) ? 0 : ts;
4200
4758
  }
4759
+ function narrowPiField(field, value, narrow) {
4760
+ return narrowField("pi", field, value, narrow);
4761
+ }
4762
+ function narrowTimestampMs(field, value) {
4763
+ const shaped = narrowPiField(
4764
+ field,
4765
+ value,
4766
+ (v) => typeof v === "number" || typeof v === "string" ? v : void 0
4767
+ );
4768
+ return shaped === void 0 ? 0 : parseTimestampMs3(shaped);
4769
+ }
4201
4770
  function extractSessionIdFromFilename(filePath) {
4202
4771
  const stem = basename6(filePath, ".jsonl");
4203
4772
  const underscore = stem.indexOf("_");
@@ -4221,7 +4790,7 @@ function normalizeTextParts(content, timestampMs) {
4221
4790
  return text ? [{ type: "text", text, time_created: timestampMs }] : [];
4222
4791
  }
4223
4792
  function getEntryTimestamp(entry) {
4224
- return parseTimestampMs3(entry["timestamp"]);
4793
+ return narrowTimestampMs("entry.timestamp", entry["timestamp"]);
4225
4794
  }
4226
4795
  function chooseLeafEntry(entries) {
4227
4796
  for (let index = entries.length - 1; index >= 0; index -= 1) {
@@ -4265,10 +4834,10 @@ var PiAgent = class extends FileSystemSessionSource {
4265
4834
  }
4266
4835
  listSessionSources(options) {
4267
4836
  if (!this.basePath) return [];
4268
- return this.listSessionFiles(options).map((file) => ({
4837
+ return this.walkJsonlFiles(this.basePath, options).map(({ file, stat }) => ({
4269
4838
  sessionId: extractSessionIdFromFilename(file),
4270
4839
  sourcePath: file,
4271
- fingerprint: this.sourceFingerprint(file)
4840
+ fingerprint: this.sourceFingerprint(stat)
4272
4841
  }));
4273
4842
  }
4274
4843
  scanSessionSource(sourcePath) {
@@ -4305,8 +4874,9 @@ var PiAgent = class extends FileSystemSessionSource {
4305
4874
  }
4306
4875
  listSessionFiles(options) {
4307
4876
  if (!this.basePath) return [];
4308
- return this.walkJsonlFiles(this.basePath, options);
4877
+ return this.walkJsonlFiles(this.basePath, options).map(({ file }) => file);
4309
4878
  }
4879
+ /** Stats each file once during the walk; caller reuses it for both the scan window check and the fingerprint. */
4310
4880
  walkJsonlFiles(dir, options) {
4311
4881
  const files = [];
4312
4882
  try {
@@ -4317,20 +4887,27 @@ var PiAgent = class extends FileSystemSessionSource {
4317
4887
  continue;
4318
4888
  }
4319
4889
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
4320
- if (!matchesScanWindow(statSync6(fullPath).mtimeMs, options)) continue;
4321
- files.push(fullPath);
4890
+ let stat;
4891
+ try {
4892
+ stat = statSync6(fullPath);
4893
+ } catch {
4894
+ continue;
4895
+ }
4896
+ if (!matchesScanWindow(stat.mtimeMs, options)) continue;
4897
+ files.push({ file: fullPath, stat });
4322
4898
  }
4323
4899
  } catch {
4324
4900
  }
4325
4901
  return files;
4326
4902
  }
4327
4903
  buildSessionMeta(head, file) {
4904
+ const stat = statSync6(file);
4328
4905
  return {
4329
4906
  id: head.id,
4330
4907
  title: head.title,
4331
4908
  sourcePath: file,
4332
- sourceFingerprint: this.sourceFingerprint(file),
4333
- sourceMtimeMs: statSync6(file).mtimeMs,
4909
+ sourceFingerprint: this.sourceFingerprint(stat),
4910
+ sourceMtimeMs: stat.mtimeMs,
4334
4911
  headIndexVersion: HEAD_INDEX_VERSION3,
4335
4912
  parserVersion: PARSER_VERSION2,
4336
4913
  directory: head.directory,
@@ -4339,8 +4916,8 @@ var PiAgent = class extends FileSystemSessionSource {
4339
4916
  updatedAt: head.time_updated ?? head.time_created
4340
4917
  };
4341
4918
  }
4342
- sourceFingerprint(file) {
4343
- const stat = statSync6(file);
4919
+ /** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
4920
+ sourceFingerprint(stat) {
4344
4921
  return JSON.stringify([HEAD_INDEX_VERSION3, PARSER_VERSION2, stat.mtimeMs, stat.size]);
4345
4922
  }
4346
4923
  parseSessionHeadResult(filePath) {
@@ -4380,7 +4957,7 @@ var PiAgent = class extends FileSystemSessionSource {
4380
4957
  if (!sessionId) throw new Error("missing session id");
4381
4958
  const stat = statSync6(filePath);
4382
4959
  const directory = String(header["cwd"] ?? "").trim() || basename6(filePath, ".jsonl");
4383
- const createdAt = parseTimestampMs3(header["timestamp"]) || stat.mtimeMs;
4960
+ const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
4384
4961
  const updatedAt = pathEntries.reduce(
4385
4962
  (max, entry) => Math.max(max, getEntryTimestamp(entry)),
4386
4963
  createdAt
@@ -4423,8 +5000,8 @@ var PiAgent = class extends FileSystemSessionSource {
4423
5000
  const timestampMs = getEntryTimestamp(entry);
4424
5001
  const type = String(entry["type"] ?? "");
4425
5002
  if (type === "message") {
4426
- const message = entry["message"];
4427
- if (!isObject(message)) continue;
5003
+ const message = narrowPiField("entry.message", entry["message"], asRecord);
5004
+ if (!message) continue;
4428
5005
  const result2 = this.convertAgentMessage(entry, message, timestampMs, builder);
4429
5006
  if (!result2) continue;
4430
5007
  if (result2.message) builder.appendMessage(result2.message);
@@ -4448,8 +5025,8 @@ var PiAgent = class extends FileSystemSessionSource {
4448
5025
  };
4449
5026
  }
4450
5027
  convertAgentMessage(entry, message, timestampMs, builder) {
4451
- const id = String(entry["id"] ?? "");
4452
- const role = String(message["role"] ?? "");
5028
+ const id = narrowPiField("message.id", entry["id"], asString) ?? "";
5029
+ const role = narrowPiField("message.role", message["role"], asString) ?? "";
4453
5030
  if (role === "user") {
4454
5031
  const parts = normalizeTextParts(message["content"], timestampMs);
4455
5032
  if (parts.length === 0) return null;
@@ -4588,7 +5165,7 @@ var PiAgent = class extends FileSystemSessionSource {
4588
5165
  const text = cleanInternalText(rawText);
4589
5166
  if (!text) return null;
4590
5167
  return {
4591
- id: String(entry["id"] ?? ""),
5168
+ id: narrowPiField("summary.id", entry["id"], asString) ?? "",
4592
5169
  role: type === "custom_message" ? "user" : "assistant",
4593
5170
  agent: type === "custom_message" ? void 0 : "pi",
4594
5171
  timestampMs,
@@ -4596,14 +5173,12 @@ var PiAgent = class extends FileSystemSessionSource {
4596
5173
  };
4597
5174
  }
4598
5175
  normalizeUsage(raw) {
4599
- const usage = isObject(raw) ? raw : {};
4600
- const inputTokens = Number(usage["input"] ?? 0);
4601
- const outputTokens = Number(usage["output"] ?? 0);
4602
- const cacheReadTokens = Number(usage["cacheRead"] ?? 0);
4603
- const cacheCreateTokens = Number(usage["cacheWrite"] ?? 0);
4604
- const totalTokens = Number(
4605
- usage["totalTokens"] ?? inputTokens + outputTokens + cacheReadTokens + cacheCreateTokens
4606
- );
5176
+ const usage = narrowPiField("message.usage", raw, asRecord) ?? {};
5177
+ const inputTokens = narrowPiField("message.usage.input", usage["input"], asNumber) ?? 0;
5178
+ const outputTokens = narrowPiField("message.usage.output", usage["output"], asNumber) ?? 0;
5179
+ const cacheReadTokens = narrowPiField("message.usage.cacheRead", usage["cacheRead"], asNumber) ?? 0;
5180
+ const cacheCreateTokens = narrowPiField("message.usage.cacheWrite", usage["cacheWrite"], asNumber) ?? 0;
5181
+ const totalTokens = narrowPiField("message.usage.totalTokens", usage["totalTokens"], asNumber) ?? inputTokens + outputTokens + cacheReadTokens + cacheCreateTokens;
4607
5182
  const cost = isObject(usage["cost"]) ? Number(usage["cost"]["total"] ?? 0) : null;
4608
5183
  return {
4609
5184
  inputTokens: inputTokens + cacheReadTokens + cacheCreateTokens,
@@ -4645,6 +5220,7 @@ var ZCodeAgent = class extends OpenCodeSqliteAgent {
4645
5220
  };
4646
5221
  registerAgent({
4647
5222
  icon: "/icon/agent/claudecode.svg",
5223
+ iconColored: true,
4648
5224
  create: () => new ClaudeCodeAgent()
4649
5225
  });
4650
5226
  registerAgent({
@@ -4734,7 +5310,23 @@ function normalizeGitRemote(url) {
4734
5310
  if (!value.includes("/")) return null;
4735
5311
  return value.toLowerCase();
4736
5312
  }
5313
+ var IDENTITY_CACHE_TTL_MS = 10 * 60 * 1e3;
5314
+ var identityCache = /* @__PURE__ */ new Map();
5315
+ function clearIdentityCache() {
5316
+ identityCache.clear();
5317
+ }
4737
5318
  function computeIdentity(cwd, fs) {
5319
+ if (fs !== realFs) return resolveIdentity(cwd, fs);
5320
+ const key = cwd ?? "";
5321
+ const cached = identityCache.get(key);
5322
+ if (cached && Date.now() - cached.resolvedAt < IDENTITY_CACHE_TTL_MS) {
5323
+ return cached.identity;
5324
+ }
5325
+ const identity = resolveIdentity(cwd, fs);
5326
+ identityCache.set(key, { identity, resolvedAt: Date.now() });
5327
+ return identity;
5328
+ }
5329
+ function resolveIdentity(cwd, fs) {
4738
5330
  if (!cwd) return loose();
4739
5331
  const pathOps = getPathOps(cwd);
4740
5332
  const absoluteCwd = pathOps.resolve(cwd);
@@ -5668,7 +6260,10 @@ function withCacheDb(fn) {
5668
6260
  setSchemaEnsuredPath(cachePath);
5669
6261
  }
5670
6262
  return fn(db);
5671
- } catch {
6263
+ } catch (error) {
6264
+ getCoreDiagnostics()?.warn("cache.write_failed", {
6265
+ message: error instanceof Error ? error.message : String(error)
6266
+ });
5672
6267
  return null;
5673
6268
  } finally {
5674
6269
  db.close();
@@ -5711,6 +6306,12 @@ function createCacheTables(db) {
5711
6306
  index_version TEXT NOT NULL,
5712
6307
  last_sync_at INTEGER NOT NULL
5713
6308
  );
6309
+
6310
+ CREATE TABLE IF NOT EXISTS pending_reindex (
6311
+ agent_name TEXT NOT NULL,
6312
+ session_id TEXT NOT NULL,
6313
+ PRIMARY KEY (agent_name, session_id)
6314
+ );
5714
6315
  `);
5715
6316
  }
5716
6317
  function createSessionTables(db) {
@@ -6408,6 +7009,20 @@ function invalidateSearchContentHashes(db) {
6408
7009
  db.exec("UPDATE session_documents SET content_hash = ''");
6409
7010
  }
6410
7011
  }
7012
+ var CODEX_EXEC_DECODE_MIGRATION_KEY = "codex_exec_decode_migrated_v3";
7013
+ function migrateCodexExecDecode(db) {
7014
+ if (!tableExists(db, "cache_meta")) return;
7015
+ const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(CODEX_EXEC_DECODE_MIGRATION_KEY);
7016
+ if (done) return;
7017
+ if (tableExists(db, "sessions") && tableExists(db, "pending_reindex")) {
7018
+ db.exec(
7019
+ "INSERT OR IGNORE INTO pending_reindex(agent_name, session_id) SELECT agent_name, session_id FROM sessions WHERE agent_name = 'codex'"
7020
+ );
7021
+ }
7022
+ db.prepare(
7023
+ "INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
7024
+ ).run(CODEX_EXEC_DECODE_MIGRATION_KEY);
7025
+ }
6411
7026
  function rebuildSearchIndex(db) {
6412
7027
  if (!tableExists(db, "session_documents_fts")) {
6413
7028
  return;
@@ -6465,6 +7080,7 @@ function ensureSchema(db, dbPath) {
6465
7080
  if (currentVersion === 0 && !hasAnyCacheSchema(db)) {
6466
7081
  createLatestCacheSchema(db);
6467
7082
  setCacheSchemaVersion(db);
7083
+ migrateCodexExecDecode(db);
6468
7084
  return;
6469
7085
  }
6470
7086
  runSchemaMigrations(db, {
@@ -6532,6 +7148,7 @@ function ensureSchema(db, dbPath) {
6532
7148
  if (getUserVersion(db) <= CACHE_SCHEMA_VERSION) {
6533
7149
  setCacheSchemaVersion(db);
6534
7150
  }
7151
+ migrateCodexExecDecode(db);
6535
7152
  }
6536
7153
  function escapeFtsTerm(value) {
6537
7154
  return value.replaceAll('"', '""');
@@ -6657,6 +7274,10 @@ function toFtsQuery(input) {
6657
7274
  (token, index, values) => token !== "OR" || index > 0 && index < values.length - 1 && values[index - 1] !== "OR" && values[index + 1] !== "OR"
6658
7275
  ).join(" ");
6659
7276
  }
7277
+ function readPendingReindexIds(db, agentName) {
7278
+ const rows = db.prepare("SELECT session_id FROM pending_reindex WHERE agent_name = ?").all(agentName);
7279
+ return new Set(rows.map((row) => String(row.session_id)));
7280
+ }
6660
7281
  var SEARCH_INDEX_STATE_BATCH_SIZE = 900;
6661
7282
  function shouldBulkSyncSearchIndex(options, changedCount) {
6662
7283
  if (options.isBulk != null) {
@@ -6682,7 +7303,7 @@ function sessionContentHash(session) {
6682
7303
  session.stats.total_tokens ?? 0
6683
7304
  ]);
6684
7305
  }
6685
- function searchIndexStateFromRows(indexedRows, messageCountRows) {
7306
+ function searchIndexStateFromRows(indexedRows, messageCountRows, pendingReindexSessionIds = /* @__PURE__ */ new Set()) {
6686
7307
  return {
6687
7308
  contentHashBySessionId: new Map(
6688
7309
  indexedRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
@@ -6692,7 +7313,8 @@ function searchIndexStateFromRows(indexedRows, messageCountRows) {
6692
7313
  ),
6693
7314
  messageCountBySessionId: new Map(
6694
7315
  messageCountRows.map((row) => [String(row.session_id), Number(row.value ?? 0)])
6695
- )
7316
+ ),
7317
+ pendingReindexSessionIds
6696
7318
  };
6697
7319
  }
6698
7320
  function readSearchIndexState(db, agentName, sessionIds) {
@@ -6722,11 +7344,11 @@ function readSearchIndexState(db, agentName, sessionIds) {
6722
7344
  ).all(...batch, agentName, agentName);
6723
7345
  rows.push(...batchRows);
6724
7346
  }
6725
- return searchIndexStateFromRows(rows, rows);
7347
+ return searchIndexStateFromRows(rows, rows, readPendingReindexIds(db, agentName));
6726
7348
  }
6727
7349
  function searchIndexEntryNeedsUpdate(state, session) {
6728
7350
  const sessionId = session.id;
6729
- return state.contentHashBySessionId.get(sessionId) !== sessionContentHash(session) || state.indexedMessageCountBySessionId.get(sessionId) !== (state.messageCountBySessionId.get(sessionId) ?? 0);
7351
+ return state.pendingReindexSessionIds.has(sessionId) || state.contentHashBySessionId.get(sessionId) !== sessionContentHash(session) || state.indexedMessageCountBySessionId.get(sessionId) !== (state.messageCountBySessionId.get(sessionId) ?? 0);
6730
7352
  }
6731
7353
  function loadSearchIndexEntry(agentName, change, loadSessionData) {
6732
7354
  try {
@@ -6846,11 +7468,15 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
6846
7468
  indexed_message_count = excluded.indexed_message_count,
6847
7469
  indexed_at = excluded.indexed_at
6848
7470
  `);
7471
+ const clearPendingReindex = db.prepare(
7472
+ "DELETE FROM pending_reindex WHERE agent_name = ? AND session_id = ?"
7473
+ );
6849
7474
  for (const sessionId of new Set(removedSessionIds)) {
6850
7475
  deleteRow.run(agentName, sessionId);
6851
7476
  deleteFileActivity.run(agentName, sessionId);
6852
7477
  deleteMessageTools.run(agentName, sessionId, 0);
6853
7478
  deleteMessages.run(agentName, sessionId, 0);
7479
+ clearPendingReindex.run(agentName, sessionId);
6854
7480
  }
6855
7481
  let indexed = 0;
6856
7482
  for (const entry of entries) {
@@ -6858,6 +7484,7 @@ function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
6858
7484
  upsertSessionRow(upsertIndexedSession, agentName, entry.session, null, entry.sortIndex, null);
6859
7485
  deleteFileActivity.run(agentName, entry.session.id);
6860
7486
  deleteMessageTools.run(agentName, entry.session.id, 0);
7487
+ clearPendingReindex.run(agentName, entry.session.id);
6861
7488
  writeFileActivityRows(insertFileActivity, entry.fileActivity);
6862
7489
  for (const message of entry.messages) {
6863
7490
  upsertMessage.run(
@@ -6918,7 +7545,11 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
6918
7545
  const messageCountRows = db.prepare(
6919
7546
  "SELECT session_id, COUNT(*) AS value FROM messages WHERE agent_name = ? GROUP BY session_id"
6920
7547
  ).all(agentName);
6921
- const searchIndexState = searchIndexStateFromRows(existingRows, messageCountRows);
7548
+ const searchIndexState = searchIndexStateFromRows(
7549
+ existingRows,
7550
+ messageCountRows,
7551
+ readPendingReindexIds(db, agentName)
7552
+ );
6922
7553
  const sessionMap = new Map(sessions.map((session) => [session.id, session]));
6923
7554
  const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
6924
7555
  const toUpsert = sessions.filter(
@@ -7523,6 +8154,14 @@ function searchFileActivitySessions(query, options = {}) {
7523
8154
  return results;
7524
8155
  }
7525
8156
  var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
8157
+ function parseCachedSessionMeta(value) {
8158
+ if (!value) return null;
8159
+ try {
8160
+ return JSON.parse(value);
8161
+ } catch {
8162
+ return null;
8163
+ }
8164
+ }
7526
8165
  function deleteLegacyCacheFile() {
7527
8166
  const legacyPath = getLegacyCachePath();
7528
8167
  if (!existsSync12(legacyPath)) {
@@ -7641,7 +8280,7 @@ function markAgentFullSyncCompleted(agentName) {
7641
8280
  ).run(Date.now(), agentName);
7642
8281
  });
7643
8282
  }
7644
- function loadCachedSessionData(agentName, sessionId) {
8283
+ function loadCachedSessionDataEntry(agentName, sessionId) {
7645
8284
  if (!hasCacheStorage()) {
7646
8285
  return null;
7647
8286
  }
@@ -7679,7 +8318,8 @@ function loadCachedSessionData(agentName, sessionId) {
7679
8318
  if (!row) {
7680
8319
  return null;
7681
8320
  }
7682
- const messageRows = db.prepare(
8321
+ const pendingReindex = db.prepare("SELECT 1 FROM pending_reindex WHERE agent_name = ? AND session_id = ?").get(agentName, sessionId) != null;
8322
+ const messageRows = pendingReindex ? [] : db.prepare(
7683
8323
  `
7684
8324
  SELECT
7685
8325
  message_id,
@@ -7712,12 +8352,18 @@ function loadCachedSessionData(agentName, sessionId) {
7712
8352
  `
7713
8353
  ).all(agentName, sessionId);
7714
8354
  return {
7715
- ...head,
7716
- messages: messageRows.map((messageRow) => messageFromCachedRow(messageRow)),
7717
- file_activity: fileActivityRows.map((activityRow) => fileActivityFromRow(activityRow))
8355
+ data: {
8356
+ ...head,
8357
+ messages: messageRows.map((messageRow) => messageFromCachedRow(messageRow)),
8358
+ file_activity: fileActivityRows.map((activityRow) => fileActivityFromRow(activityRow))
8359
+ },
8360
+ meta: parseCachedSessionMeta(row.meta_json)
7718
8361
  };
7719
8362
  });
7720
8363
  }
8364
+ function loadCachedSessionData(agentName, sessionId) {
8365
+ return loadCachedSessionDataEntry(agentName, sessionId)?.data ?? null;
8366
+ }
7721
8367
  function saveCachedSessions(agentName, sessions, meta = {}) {
7722
8368
  withCacheDb((db) => {
7723
8369
  const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
@@ -7907,9 +8553,8 @@ function listCachedProjectGroups(sessions) {
7907
8553
  if (!hasCacheStorage()) {
7908
8554
  return [];
7909
8555
  }
7910
- const groups = withCacheDb((db) => {
7911
- const rows = db.prepare(
7912
- `
8556
+ const queryRows = (db) => db.prepare(
8557
+ `
7913
8558
  SELECT identity_kind, identity_key, display_name, sources_csv, session_count, last_activity
7914
8559
  FROM project_groups_v
7915
8560
  ORDER BY
@@ -7917,34 +8562,24 @@ function listCachedProjectGroups(sessions) {
7917
8562
  last_activity IS NULL,
7918
8563
  last_activity DESC
7919
8564
  `
7920
- ).all();
7921
- return rows.map((row) => ({
7922
- identityKind: row.identity_kind ?? "path",
7923
- identityKey: String(row.identity_key ?? ""),
7924
- displayName: String(row.display_name ?? ""),
7925
- sources: String(row.sources_csv ?? "").split(",").filter(Boolean).sort(),
7926
- sessionCount: Number(row.session_count ?? 0),
7927
- lastActivity: row.last_activity == null ? null : Number(row.last_activity)
7928
- }));
7929
- });
7930
- return groups ?? [];
7931
- }
7932
- function createIdentityResolver() {
7933
- const cache = /* @__PURE__ */ new Map();
7934
- return (directory) => {
7935
- const key = directory || "";
7936
- const cached = cache.get(key);
7937
- if (cached) return cached;
7938
- const identity = computeIdentity(directory, realFs);
7939
- cache.set(key, identity);
7940
- return identity;
7941
- };
8565
+ ).all();
8566
+ let rows = withCacheDbReadOnly(queryRows);
8567
+ if (rows == null) {
8568
+ rows = withCacheDb(queryRows);
8569
+ }
8570
+ return (rows ?? []).map((row) => ({
8571
+ identityKind: row.identity_kind ?? "path",
8572
+ identityKey: String(row.identity_key ?? ""),
8573
+ displayName: String(row.display_name ?? ""),
8574
+ sources: String(row.sources_csv ?? "").split(",").filter(Boolean).sort(),
8575
+ sessionCount: Number(row.session_count ?? 0),
8576
+ lastActivity: row.last_activity == null ? null : Number(row.last_activity)
8577
+ }));
7942
8578
  }
7943
8579
  function attachMissingProjectIdentities(sessions) {
7944
- const resolveIdentity = createIdentityResolver();
7945
8580
  return sessions.map((session) => {
7946
8581
  if (session.project_identity) return session;
7947
- return { ...session, project_identity: resolveIdentity(session.directory) };
8582
+ return { ...session, project_identity: computeIdentity(session.directory, realFs) };
7948
8583
  });
7949
8584
  }
7950
8585
  function buildAgentCacheMeta(agent, sessionIds) {
@@ -7974,7 +8609,7 @@ function sessionSignature(session) {
7974
8609
  function sortSessions(sessions) {
7975
8610
  return sortSessionsByActivity(sessions);
7976
8611
  }
7977
- function computeSessionDiff(cachedSessions, updatedSessions, changedIds = [], signature = sessionSignature) {
8612
+ function computeSessionDiff(cachedSessions, updatedSessions, changedIds = [], signature = sessionSignature, signatureCache) {
7978
8613
  const cachedMap = new Map(cachedSessions.map((session) => [session.id, session]));
7979
8614
  const updatedIds = new Set(updatedSessions.map((session) => session.id));
7980
8615
  const changedIdSet = new Set(changedIds);
@@ -7987,10 +8622,13 @@ function computeSessionDiff(cachedSessions, updatedSessions, changedIds = [], si
7987
8622
  if (!cached) {
7988
8623
  newCount += 1;
7989
8624
  changes.push({ session, sortIndex });
8625
+ signatureCache?.set(session.id, signature(session));
7990
8626
  return;
7991
8627
  }
7992
- const hasSignatureChange = signature(cached) !== signature(session);
7993
- if (changedIdSet.has(session.id) || hasSignatureChange) {
8628
+ const cachedSignature = signatureCache?.get(cached.id) ?? signature(cached);
8629
+ const updatedSignature = signature(session);
8630
+ signatureCache?.set(session.id, updatedSignature);
8631
+ if (changedIdSet.has(session.id) || cachedSignature !== updatedSignature) {
7994
8632
  updatedCount += 1;
7995
8633
  changes.push({ session, sortIndex });
7996
8634
  }
@@ -8208,7 +8846,7 @@ async function scanAgentSmart(agent, options, onProgress) {
8208
8846
  });
8209
8847
  const t2 = performance.now();
8210
8848
  const updatedSessions = await Promise.resolve(
8211
- agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
8849
+ agent.incrementalScan(cached.sessions, checkResult.changedIds || [], checkResult.refs)
8212
8850
  );
8213
8851
  timing.scan = performance.now() - t2;
8214
8852
  return finalizeAgentScan(agent, updatedSessions, {
@@ -8272,10 +8910,12 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
8272
8910
  const tagged = options.includeSmartTags === false ? { sessions: headsWithIdentity, changed: false } : await ensureSessionTags(agent, headsWithIdentity, options.smartTagWorkerUrl);
8273
8911
  timing.tags = performance.now() - t2;
8274
8912
  const meta = buildAgentCacheMeta(agent);
8275
- if (options.writeCache !== false && options.from == null && options.to == null) {
8276
- saveCachedSessions(agent.name, tagged.sessions, meta);
8913
+ if (options.writeCache !== false) {
8914
+ if (options.from == null && options.to == null) {
8915
+ saveCachedSessions(agent.name, tagged.sessions, meta);
8916
+ markAgentFullSyncCompleted(agent.name);
8917
+ }
8277
8918
  markAgentCacheInitialized(agent.name);
8278
- markAgentFullSyncCompleted(agent.name);
8279
8919
  }
8280
8920
  onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
8281
8921
  const filtered = filterSessions(tagged.sessions, options);
@@ -8331,6 +8971,13 @@ async function scanSessionsAsync(options = {}, onProgress) {
8331
8971
  var STATE_DB_FILENAME = "state.db";
8332
8972
  var STATE_SCHEMA_VERSION = 2;
8333
8973
  var MEMORY_STATE_STORE = "memory";
8974
+ var stateSchemaEnsuredPath = null;
8975
+ function getStateSchemaEnsuredPath() {
8976
+ return stateSchemaEnsuredPath;
8977
+ }
8978
+ function setStateSchemaEnsuredPath(path2) {
8979
+ stateSchemaEnsuredPath = path2;
8980
+ }
8334
8981
  var StateStorageUnavailableError = class extends Error {
8335
8982
  constructor() {
8336
8983
  super("SQLite state database is unavailable");
@@ -8437,7 +9084,7 @@ function ensureSchema2(db, dbPath) {
8437
9084
  { version: 2, migrate: createSessionAliasesTable }
8438
9085
  ]
8439
9086
  });
8440
- if (currentVersion <= STATE_SCHEMA_VERSION) {
9087
+ if (currentVersion < STATE_SCHEMA_VERSION) {
8441
9088
  setStateSchemaVersion(db);
8442
9089
  }
8443
9090
  }
@@ -8446,7 +9093,10 @@ function withStateDb(fn) {
8446
9093
  const db = openDb(statePath);
8447
9094
  if (!db) throw new StateStorageUnavailableError();
8448
9095
  try {
8449
- ensureSchema2(db, statePath);
9096
+ if (getStateSchemaEnsuredPath() !== statePath) {
9097
+ ensureSchema2(db, statePath);
9098
+ setStateSchemaEnsuredPath(statePath);
9099
+ }
8450
9100
  return fn(db);
8451
9101
  } finally {
8452
9102
  db.close();
@@ -8847,6 +9497,7 @@ function buildDashboard(sessions, options) {
8847
9497
  name,
8848
9498
  displayName: info?.displayName ?? name,
8849
9499
  icon: info?.icon ?? "",
9500
+ iconColored: info?.iconColored,
8850
9501
  sessions: metrics.sessions,
8851
9502
  messages: metrics.messages,
8852
9503
  tokens: metrics.tokens
@@ -8887,15 +9538,6 @@ function executeSessionSearch(query, options, snapshot) {
8887
9538
  function needsIndexedSearch(textQuery, options) {
8888
9539
  return Boolean(textQuery || options.file || options.fileKind || options.tools?.length);
8889
9540
  }
8890
- function filterSessionsByActivityWindow(sessions, from, to) {
8891
- if (from == null && to == null) return sessions;
8892
- return sessions.filter((session) => {
8893
- const activity = getSessionActivityTime(session);
8894
- if (from != null && activity < from) return false;
8895
- if (to != null && activity > to) return false;
8896
- return true;
8897
- });
8898
- }
8899
9541
  function matchesRecentSearchFilters(session, options, projectScope) {
8900
9542
  if (options.projectKind || options.projectKey) {
8901
9543
  if (!options.projectKind || !options.projectKey || !matchesProjectIdentity(session.project_identity, {
@@ -8921,11 +9563,18 @@ function matchesRecentSearchFilters(session, options, projectScope) {
8921
9563
  if (!sessionMatchesSearchCost(session, options)) return false;
8922
9564
  return true;
8923
9565
  }
9566
+ function matchesSessionSearchFilters(agentName, session, options, projectScope = null) {
9567
+ if (options.agent && agentName !== options.agent) return false;
9568
+ const activity = getSessionActivityTime(session);
9569
+ if (options.from != null && activity < options.from) return false;
9570
+ if (options.to != null && activity > options.to) return false;
9571
+ return matchesRecentSearchFilters(session, options, projectScope);
9572
+ }
8924
9573
  function searchRecentSessions(snapshot, options) {
8925
9574
  const projectScope = options.cwd ? createProjectScopeMatcher(options.cwd) : null;
8926
9575
  const entries = options.agent ? [[options.agent, snapshot.byAgent[options.agent] ?? []]] : Object.entries(snapshot.byAgent);
8927
9576
  return entries.flatMap(
8928
- ([agentName, sessions]) => filterSessionsByActivityWindow(sessions, options.from, options.to).filter((session) => matchesRecentSearchFilters(session, options, projectScope)).map((session) => ({ agentName, session }))
9577
+ ([agentName, sessions]) => sessions.filter((session) => matchesSessionSearchFilters(agentName, session, options, projectScope)).map((session) => ({ agentName, session }))
8929
9578
  ).sort(
8930
9579
  (a, b) => (b.session.time_updated ?? b.session.time_created) - (a.session.time_updated ?? a.session.time_created)
8931
9580
  ).slice(0, options.limit ?? 50).map(({ agentName, session }) => ({
@@ -8968,6 +9617,7 @@ export {
8968
9617
  getRegisteredAgents,
8969
9618
  getAgentInfoMap,
8970
9619
  getAgentByName,
9620
+ setCoreDiagnostics,
8971
9621
  parsedSession,
8972
9622
  skippedSession,
8973
9623
  filteredSession,
@@ -9001,6 +9651,11 @@ export {
9001
9651
  applyMessageCosts,
9002
9652
  withEstimatedSessionCost,
9003
9653
  estimateTokenCost,
9654
+ asRecord,
9655
+ asString,
9656
+ asNumber,
9657
+ asArray,
9658
+ reportFieldMismatch,
9004
9659
  openDbReadOnly,
9005
9660
  openDb,
9006
9661
  isSqliteAvailable,
@@ -9011,6 +9666,7 @@ export {
9011
9666
  getProjectIdentityKey,
9012
9667
  matchesProjectIdentity,
9013
9668
  normalizeGitRemote,
9669
+ clearIdentityCache,
9014
9670
  computeIdentity,
9015
9671
  buildProjectGroups,
9016
9672
  createProjectScopeMatcher,
@@ -9036,6 +9692,7 @@ export {
9036
9692
  markAgentCacheInitialized,
9037
9693
  getAgentLastFullSyncAt,
9038
9694
  markAgentFullSyncCompleted,
9695
+ loadCachedSessionDataEntry,
9039
9696
  loadCachedSessionData,
9040
9697
  saveCachedSessions,
9041
9698
  saveCachedSessionChanges,
@@ -9068,6 +9725,7 @@ export {
9068
9725
  toLocalDateKey,
9069
9726
  startOfLocalDay,
9070
9727
  buildDashboard,
9071
- executeSessionSearch
9728
+ executeSessionSearch,
9729
+ matchesSessionSearchFilters
9072
9730
  };
9073
- //# sourceMappingURL=chunk-VRVZJDNL.js.map
9731
+ //# sourceMappingURL=chunk-MWSJTNOW.js.map