codesesh 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // ../core/dist/chunk-M5ISIPFR.mjs
4
+ function compareSessionActivityDesc(a, b) {
5
+ return (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created);
6
+ }
7
+ function sortSessionsByActivity(sessions) {
8
+ for (let index = 1; index < sessions.length; index += 1) {
9
+ if (compareSessionActivityDesc(sessions[index - 1], sessions[index]) > 0) {
10
+ return [...sessions].sort(compareSessionActivityDesc);
11
+ }
12
+ }
13
+ return [...sessions];
14
+ }
15
+
3
16
  // ../core/dist/index.mjs
4
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
17
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
5
18
  import { join as join3, basename as basename2, dirname } from "path";
6
19
  import { existsSync, statSync } from "fs";
7
20
  import { existsSync as existsSync2 } from "fs";
8
21
  import { homedir, platform } from "os";
9
22
  import { join } from "path";
10
- import { readFileSync } from "fs";
23
+ import { closeSync, openSync, readSync } from "fs";
24
+ import { StringDecoder } from "string_decoder";
11
25
  import { basename } from "path";
12
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
26
+ import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
13
27
  import { homedir as homedir2 } from "os";
14
28
  import { join as join2 } from "path";
15
29
  import { join as join5 } from "path";
@@ -17,26 +31,26 @@ import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
17
31
  import { basename as basename3, dirname as dirname2, join as join4 } from "path";
18
32
  import { createRequire } from "module";
19
33
  import { createHash } from "crypto";
20
- import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
34
+ import { existsSync as existsSync6, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
21
35
  import { join as join6, basename as basename4, dirname as dirname3 } from "path";
22
36
  import {
23
- closeSync,
37
+ closeSync as closeSync2,
24
38
  existsSync as existsSync7,
25
- openSync,
26
- readFileSync as readFileSync5,
27
- readSync,
39
+ openSync as openSync2,
40
+ readFileSync as readFileSync4,
41
+ readSync as readSync2,
28
42
  readdirSync as readdirSync3,
29
43
  statSync as statSync4
30
44
  } from "fs";
31
45
  import { join as join7, basename as basename5 } from "path";
32
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
46
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
33
47
  import { join as join8, normalize } from "path";
34
- import { existsSync as existsSync9, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
48
+ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
35
49
  import { basename as basename6, join as join9 } from "path";
36
50
  import { join as join10 } from "path";
37
51
  import { availableParallelism } from "os";
38
52
  import { Worker } from "worker_threads";
39
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
53
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
40
54
  import { spawnSync } from "child_process";
41
55
  import * as os from "os";
42
56
  import * as path from "path";
@@ -244,6 +258,7 @@ function getZCodeDataPath() {
244
258
  }
245
259
  return null;
246
260
  }
261
+ var READ_CHUNK_BYTES = 1 << 20;
247
262
  function* parseJsonlLines(content) {
248
263
  for (const line of content.split("\n")) {
249
264
  const trimmed = line.trim();
@@ -254,9 +269,35 @@ function* parseJsonlLines(content) {
254
269
  }
255
270
  }
256
271
  }
257
- function readJsonlFile(filePath) {
258
- const content = readFileSync(filePath, "utf-8");
259
- return parseJsonlLines(content);
272
+ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
273
+ const fd = openSync(filePath, "r");
274
+ try {
275
+ const buffer = Buffer.alloc(chunkBytes);
276
+ const decoder = new StringDecoder("utf8");
277
+ let remainder = "";
278
+ let bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
279
+ while (bytesRead > 0) {
280
+ const lines = (remainder + decoder.write(buffer.subarray(0, bytesRead))).split("\n");
281
+ remainder = lines.pop();
282
+ for (const line of lines) {
283
+ const trimmed = line.trim();
284
+ if (trimmed) yield trimmed;
285
+ }
286
+ bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
287
+ }
288
+ const tail = (remainder + decoder.end()).trim();
289
+ if (tail) yield tail;
290
+ } finally {
291
+ closeSync(fd);
292
+ }
293
+ }
294
+ function* readJsonlFile(filePath) {
295
+ for (const line of readJsonlFileLines(filePath)) {
296
+ try {
297
+ yield JSON.parse(line);
298
+ } catch {
299
+ }
300
+ }
260
301
  }
261
302
  var INTERNAL_TAGS = [
262
303
  "command-message",
@@ -638,7 +679,7 @@ function loadDiskCache() {
638
679
  const path2 = getCachePath();
639
680
  if (!existsSync3(path2)) return;
640
681
  try {
641
- const cached = JSON.parse(readFileSync2(path2, "utf-8"));
682
+ const cached = JSON.parse(readFileSync(path2, "utf-8"));
642
683
  if (Date.now() - cached.timestamp <= CACHE_TTL_MS) {
643
684
  const next = loadSnapshot();
644
685
  for (const [name, rawPricing] of Object.entries(cached.data)) {
@@ -661,7 +702,7 @@ async function refreshPricingCache() {
661
702
  const path2 = getCachePath();
662
703
  if (existsSync3(path2)) {
663
704
  try {
664
- const cached = JSON.parse(readFileSync2(path2, "utf-8"));
705
+ const cached = JSON.parse(readFileSync(path2, "utf-8"));
665
706
  if (typeof cached.timestamp === "number" && Date.now() - cached.timestamp <= CACHE_TTL_MS) {
666
707
  return false;
667
708
  }
@@ -909,11 +950,16 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
909
950
  perf.end(scanMarker);
910
951
  return heads;
911
952
  }
912
- listSessionSources() {
953
+ listSessionSources(options) {
913
954
  if (!this.basePath) return [];
914
955
  const refs = [];
915
956
  for (const projectDir of this.listProjectDirs()) {
916
957
  for (const file of this.listJsonlFiles(projectDir)) {
958
+ try {
959
+ if (!matchesScanWindow(statSync2(file).mtimeMs, options)) continue;
960
+ } catch {
961
+ continue;
962
+ }
917
963
  const sessionId = basename2(file, ".jsonl");
918
964
  refs.push({
919
965
  sessionId,
@@ -940,7 +986,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
940
986
  if (!existsSync4(meta.sourcePath)) {
941
987
  throw new Error(`Session file missing: ${meta.sourcePath}`);
942
988
  }
943
- const content = readFileSync3(meta.sourcePath, "utf-8");
989
+ const content = readFileSync2(meta.sourcePath, "utf-8");
944
990
  const messages = [];
945
991
  const pendingToolCalls = /* @__PURE__ */ new Map();
946
992
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -1061,7 +1107,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1061
1107
  const map = /* @__PURE__ */ new Map();
1062
1108
  if (existsSync4(indexPath)) {
1063
1109
  try {
1064
- const data = JSON.parse(readFileSync3(indexPath, "utf-8"));
1110
+ const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
1065
1111
  const entries = data?.entries ?? [];
1066
1112
  for (const entry of entries) {
1067
1113
  const sid = entry?.sessionId;
@@ -1080,7 +1126,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1080
1126
  return getParsedSession(this.parseSessionHeadResult(filePath, projectDir));
1081
1127
  }
1082
1128
  parseSessionHeadResult(filePath, projectDir) {
1083
- const content = readFileSync3(filePath, "utf-8");
1129
+ const content = readFileSync2(filePath, "utf-8");
1084
1130
  const lines = content.split("\n").filter((l) => l.trim());
1085
1131
  if (lines.length === 0) return skippedSession("empty file");
1086
1132
  const sessionId = basename2(filePath, ".jsonl");
@@ -2137,7 +2183,7 @@ function kimiContentText(content) {
2137
2183
  }
2138
2184
  function extractFirstUserTitle(contextFile, wireFile) {
2139
2185
  if (contextFile && existsSync6(contextFile)) {
2140
- const content = readFileSync4(contextFile, "utf-8");
2186
+ const content = readFileSync3(contextFile, "utf-8");
2141
2187
  for (const record of parseJsonlLines(content)) {
2142
2188
  if (record.role !== "user") continue;
2143
2189
  const title = normalizeTitleText(kimiContentText(record.content));
@@ -2145,7 +2191,7 @@ function extractFirstUserTitle(contextFile, wireFile) {
2145
2191
  }
2146
2192
  }
2147
2193
  if (wireFile && existsSync6(wireFile)) {
2148
- const content = readFileSync4(wireFile, "utf-8");
2194
+ const content = readFileSync3(wireFile, "utf-8");
2149
2195
  for (const record of parseJsonlLines(content)) {
2150
2196
  const message = record.message ?? {};
2151
2197
  if (message.type !== "TurnBegin") continue;
@@ -2174,12 +2220,12 @@ var KimiAgent = class extends FileSystemSessionSource {
2174
2220
  const configPath = join6(roots.kimiRoot, "kimi.json");
2175
2221
  const tomlPath = join6(roots.kimiRoot, "config.toml");
2176
2222
  if (existsSync6(tomlPath)) {
2177
- const configText = readFileSync4(tomlPath, "utf-8");
2223
+ const configText = readFileSync3(tomlPath, "utf-8");
2178
2224
  this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
2179
2225
  }
2180
2226
  if (!existsSync6(configPath)) return;
2181
2227
  try {
2182
- const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
2228
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
2183
2229
  const workDirs = raw?.work_dirs;
2184
2230
  if (!Array.isArray(workDirs)) return;
2185
2231
  for (const wd of workDirs) {
@@ -2243,12 +2289,12 @@ var KimiAgent = class extends FileSystemSessionSource {
2243
2289
  let wireMtime = null;
2244
2290
  let metaFile = "";
2245
2291
  if (existsSync6(statePath)) {
2246
- const state = JSON.parse(readFileSync4(statePath, "utf-8"));
2292
+ const state = JSON.parse(readFileSync3(statePath, "utf-8"));
2247
2293
  title = String(state.custom_title ?? "");
2248
2294
  wireMtime = typeof state.wire_mtime === "number" ? state.wire_mtime : null;
2249
2295
  metaFile = statePath;
2250
2296
  } else if (existsSync6(metaPath)) {
2251
- const meta = JSON.parse(readFileSync4(metaPath, "utf-8"));
2297
+ const meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
2252
2298
  title = String(meta.title ?? "");
2253
2299
  wireMtime = typeof meta.wire_mtime === "number" ? meta.wire_mtime : null;
2254
2300
  metaFile = metaPath;
@@ -2316,12 +2362,12 @@ var KimiAgent = class extends FileSystemSessionSource {
2316
2362
  perf.end(scanMarker);
2317
2363
  return heads;
2318
2364
  }
2319
- listSessionSources() {
2365
+ listSessionSources(options) {
2320
2366
  if (!this.basePath) return [];
2321
2367
  const refs = [];
2322
2368
  for (const dir of this.listSessionDirs()) {
2323
2369
  const meta = getParsedSession(this.parseSessionDirResult(dir));
2324
- if (!meta) continue;
2370
+ if (!meta || !matchesScanWindow(meta.createdAt, options)) continue;
2325
2371
  refs.push({
2326
2372
  sessionId: meta.id,
2327
2373
  sourcePath: meta.sourcePath,
@@ -2356,7 +2402,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2356
2402
  }
2357
2403
  getSessionDataFromContext(meta) {
2358
2404
  if (!meta.contextFile) throw new Error("context.jsonl is missing");
2359
- const content = readFileSync4(meta.contextFile, "utf-8");
2405
+ const content = readFileSync3(meta.contextFile, "utf-8");
2360
2406
  const messages = [];
2361
2407
  const pendingToolCalls = /* @__PURE__ */ new Map();
2362
2408
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -2423,7 +2469,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2423
2469
  getSessionDataFromWire(meta) {
2424
2470
  const wirePath = meta.wireFile ?? join6(meta.sourcePath, "wire.jsonl");
2425
2471
  if (!existsSync6(wirePath)) throw new Error("wire.jsonl is missing");
2426
- const content = readFileSync4(wirePath, "utf-8");
2472
+ const content = readFileSync3(wirePath, "utf-8");
2427
2473
  const messages = [];
2428
2474
  const pendingToolCalls = /* @__PURE__ */ new Map();
2429
2475
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -2723,7 +2769,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2723
2769
  const wirePath = join6(sessionDir, "wire.jsonl");
2724
2770
  if (!existsSync6(wirePath)) return stats;
2725
2771
  try {
2726
- const content = readFileSync4(wirePath, "utf-8");
2772
+ const content = readFileSync3(wirePath, "utf-8");
2727
2773
  for (const line of content.split("\n").filter((l) => l.trim())) {
2728
2774
  try {
2729
2775
  const data = JSON.parse(line);
@@ -2747,7 +2793,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2747
2793
  const rawPath = existsSync6(contextPath) ? contextPath : wirePath;
2748
2794
  if (!existsSync6(rawPath)) return stats;
2749
2795
  try {
2750
- const rawContent = readFileSync4(rawPath, "utf-8");
2796
+ const rawContent = readFileSync3(rawPath, "utf-8");
2751
2797
  for (const line of rawContent.split("\n").filter((l) => l.trim())) {
2752
2798
  try {
2753
2799
  const data = JSON.parse(line);
@@ -2999,10 +3045,10 @@ var CodexAgent = class extends FileSystemSessionSource {
2999
3045
  perf.end(scanMarker);
3000
3046
  return heads;
3001
3047
  }
3002
- listSessionSources() {
3048
+ listSessionSources(options) {
3003
3049
  if (!this.basePath) return [];
3004
3050
  this.loadSessionIndex();
3005
- return this.listRolloutFiles().map((file) => ({
3051
+ return this.listRolloutFiles(options).map((file) => ({
3006
3052
  sessionId: extractSessionId(file),
3007
3053
  sourcePath: file,
3008
3054
  fingerprint: this.sourceFingerprint(file)
@@ -3020,7 +3066,6 @@ var CodexAgent = class extends FileSystemSessionSource {
3020
3066
  const meta = this.sessionMetaMap.get(sessionId);
3021
3067
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
3022
3068
  if (!existsSync7(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
3023
- const content = readFileSync5(meta.sourcePath, "utf-8");
3024
3069
  const messages = [];
3025
3070
  const pendingToolCalls = /* @__PURE__ */ new Map();
3026
3071
  let totalInputTokens = 0;
@@ -3036,7 +3081,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3036
3081
  let prevOutput = 0;
3037
3082
  let prevReasoning = 0;
3038
3083
  let prevCachedInput = 0;
3039
- for (const record of parseJsonlLines(content)) {
3084
+ for (const record of readJsonlFile(meta.sourcePath)) {
3040
3085
  try {
3041
3086
  const recordType = String(record["type"] ?? "");
3042
3087
  if (recordType === "turn_context") {
@@ -3224,7 +3269,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3224
3269
  this.sessionIndexMtime = mtime;
3225
3270
  if (mtime === null) return;
3226
3271
  try {
3227
- const content = readFileSync5(indexPath, "utf-8");
3272
+ const content = readFileSync4(indexPath, "utf-8");
3228
3273
  for (const record of parseJsonlLines(content)) {
3229
3274
  const sid = String(record["id"] ?? "").trim();
3230
3275
  const threadName = String(record["thread_name"] ?? "").trim();
@@ -3241,13 +3286,13 @@ var CodexAgent = class extends FileSystemSessionSource {
3241
3286
  }
3242
3287
  // ---- Session head parsing ----
3243
3288
  readFilePrefix(filePath, bytes = 64 * 1024) {
3244
- const fd = openSync(filePath, "r");
3289
+ const fd = openSync2(filePath, "r");
3245
3290
  try {
3246
3291
  const buffer = Buffer.alloc(bytes);
3247
- const bytesRead = readSync(fd, buffer, 0, bytes, 0);
3292
+ const bytesRead = readSync2(fd, buffer, 0, bytes, 0);
3248
3293
  return buffer.subarray(0, bytesRead).toString("utf-8");
3249
3294
  } finally {
3250
- closeSync(fd);
3295
+ closeSync2(fd);
3251
3296
  }
3252
3297
  }
3253
3298
  parseSessionHead(filePath, options) {
@@ -3257,23 +3302,12 @@ var CodexAgent = class extends FileSystemSessionSource {
3257
3302
  if (options?.fast) {
3258
3303
  return this.parseFastSessionHeadResult(filePath);
3259
3304
  }
3260
- const content = readFileSync5(filePath, "utf-8");
3261
- const lines = content.split("\n").filter((l) => l.trim());
3262
- if (lines.length === 0) return skippedSession("empty file");
3263
3305
  const sessionId = extractSessionId(filePath);
3264
- let firstRecord;
3265
- try {
3266
- firstRecord = JSON.parse(lines[0]);
3267
- } catch {
3268
- return skippedSession("malformed first record");
3269
- }
3270
- const payload = firstRecord["payload"] ?? {};
3271
- const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
3272
- const indexTitle = this.getTitleForSession(sessionId);
3273
- const messageTitle = this.extractTitleFromLines(lines);
3274
- const directoryTitle = basenameTitle(payload["cwd"] ? String(payload["cwd"]) : null);
3275
- const title = resolveSessionTitle(indexTitle, messageTitle, directoryTitle);
3276
- let updatedAt = createdAt;
3306
+ let firstPayload = {};
3307
+ let createdAt = 0;
3308
+ let lineCount = 0;
3309
+ const titleLines = [];
3310
+ let updatedAt = 0;
3277
3311
  let messageCount = 0;
3278
3312
  let model = null;
3279
3313
  let activeModel = null;
@@ -3289,18 +3323,31 @@ var CodexAgent = class extends FileSystemSessionSource {
3289
3323
  let scanPrevCachedInput = 0;
3290
3324
  const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
3291
3325
  let hasNonInternalRecord = false;
3292
- for (const line of lines) {
3326
+ for (const line of readJsonlFileLines(filePath)) {
3327
+ lineCount += 1;
3328
+ if (lineCount === 1) {
3329
+ let firstRecord;
3330
+ try {
3331
+ firstRecord = JSON.parse(line);
3332
+ } catch {
3333
+ return skippedSession("malformed first record");
3334
+ }
3335
+ firstPayload = firstRecord["payload"] ?? {};
3336
+ createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync4(filePath).mtimeMs;
3337
+ updatedAt = createdAt;
3338
+ }
3339
+ if (titleLines.length < 20) titleLines.push(line);
3293
3340
  try {
3294
3341
  const data = JSON.parse(line);
3295
3342
  const recordType = String(data["type"] ?? "");
3296
- const payload2 = data["payload"] ?? {};
3297
- const payloadType = String(payload2["type"] ?? "");
3343
+ const payload = data["payload"] ?? {};
3344
+ const payloadType = String(payload["type"] ?? "");
3298
3345
  if (isInternalEventType2(recordType) || isInternalEventType2(payloadType)) continue;
3299
3346
  hasNonInternalRecord = true;
3300
3347
  const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
3301
3348
  if (recordTs > updatedAt) updatedAt = recordTs;
3302
3349
  if (recordType === "session_meta" || recordType === "turn_context") {
3303
- const nextModel = extractModelName(payload2["model"]);
3350
+ const nextModel = extractModelName(payload["model"]);
3304
3351
  if (nextModel) {
3305
3352
  activeModel = nextModel;
3306
3353
  model ??= nextModel;
@@ -3308,7 +3355,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3308
3355
  continue;
3309
3356
  }
3310
3357
  if (recordType === "response_item") {
3311
- const p = payload2;
3358
+ const p = payload;
3312
3359
  const pType = String(p["type"] ?? "");
3313
3360
  if (COUNTED_TYPES.has(pType)) {
3314
3361
  messageCount++;
@@ -3370,8 +3417,12 @@ var CodexAgent = class extends FileSystemSessionSource {
3370
3417
  } catch {
3371
3418
  }
3372
3419
  }
3420
+ if (lineCount === 0) return skippedSession("empty file");
3373
3421
  if (!hasNonInternalRecord) return filteredSession("internal events only");
3374
- const directory = payload["cwd"] ? String(payload["cwd"]) : "";
3422
+ const indexTitle = this.getTitleForSession(sessionId);
3423
+ const messageTitle = this.extractTitleFromLines(titleLines);
3424
+ const directory = firstPayload["cwd"] ? String(firstPayload["cwd"]) : "";
3425
+ const title = resolveSessionTitle(indexTitle, messageTitle, basenameTitle(directory || null));
3375
3426
  return parsedSession({
3376
3427
  id: sessionId,
3377
3428
  slug: `codex/${sessionId}`,
@@ -4003,7 +4054,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4003
4054
  if (!existsSync8(wsJsonPath)) continue;
4004
4055
  let workspacePath;
4005
4056
  try {
4006
- const data = JSON.parse(readFileSync6(wsJsonPath, "utf-8"));
4057
+ const data = JSON.parse(readFileSync5(wsJsonPath, "utf-8"));
4007
4058
  const uri = data.folder ?? data.workspace ?? "";
4008
4059
  if (!uri) continue;
4009
4060
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
@@ -4606,9 +4657,9 @@ var PiAgent = class extends FileSystemSessionSource {
4606
4657
  perf.end(scanMarker);
4607
4658
  return heads;
4608
4659
  }
4609
- listSessionSources() {
4660
+ listSessionSources(options) {
4610
4661
  if (!this.basePath) return [];
4611
- return this.listSessionFiles().map((file) => ({
4662
+ return this.listSessionFiles(options).map((file) => ({
4612
4663
  sessionId: extractSessionIdFromFilename(file),
4613
4664
  sourcePath: file,
4614
4665
  fingerprint: this.sourceFingerprint(file)
@@ -4713,7 +4764,7 @@ var PiAgent = class extends FileSystemSessionSource {
4713
4764
  });
4714
4765
  }
4715
4766
  parsePiFile(filePath) {
4716
- const records = Array.from(parseJsonlLines(readFileSync7(filePath, "utf-8")));
4767
+ const records = Array.from(parseJsonlLines(readFileSync6(filePath, "utf-8")));
4717
4768
  if (records.length === 0) throw new Error("empty file");
4718
4769
  const header = records.find((record) => record["type"] === "session");
4719
4770
  if (!header) throw new Error("missing session header");
@@ -5078,7 +5129,7 @@ var realFs = {
5078
5129
  },
5079
5130
  readText(path2) {
5080
5131
  try {
5081
- return readFileSync8(path2, "utf8");
5132
+ return readFileSync7(path2, "utf8");
5082
5133
  } catch {
5083
5134
  return null;
5084
5135
  }
@@ -5091,43 +5142,6 @@ var realFs = {
5091
5142
  };
5092
5143
  }
5093
5144
  };
5094
- function getAgentName(session) {
5095
- return session.slug.split("/")[0]?.toLowerCase() || "unknown";
5096
- }
5097
- function buildProjectGroups(sessions) {
5098
- const groups = /* @__PURE__ */ new Map();
5099
- for (const session of sessions) {
5100
- const identity = session.project_identity;
5101
- if (!identity) continue;
5102
- const activity = session.time_updated ?? session.time_created;
5103
- const groupKey = `${identity.kind}:${identity.key}`;
5104
- const current = groups.get(groupKey);
5105
- if (current) {
5106
- current.sources.add(getAgentName(session));
5107
- current.sessionCount += 1;
5108
- current.lastActivity = Math.max(current.lastActivity, activity);
5109
- } else {
5110
- groups.set(groupKey, {
5111
- identity,
5112
- sources: /* @__PURE__ */ new Set([getAgentName(session)]),
5113
- sessionCount: 1,
5114
- lastActivity: activity
5115
- });
5116
- }
5117
- }
5118
- return [...groups.values()].map((group) => ({
5119
- identityKind: group.identity.kind,
5120
- identityKey: group.identity.key,
5121
- displayName: group.identity.displayName,
5122
- sources: [...group.sources].sort(),
5123
- sessionCount: group.sessionCount,
5124
- lastActivity: group.lastActivity || null
5125
- })).sort((a, b) => {
5126
- if (a.identityKind === "loose" && b.identityKind !== "loose") return 1;
5127
- if (b.identityKind === "loose" && a.identityKind !== "loose") return -1;
5128
- return (b.lastActivity ?? 0) - (a.lastActivity ?? 0);
5129
- });
5130
- }
5131
5145
  var MANIFESTS = [
5132
5146
  "package.json",
5133
5147
  "Cargo.toml",
@@ -5140,6 +5154,23 @@ var MANIFESTS = [
5140
5154
  var PARSEABLE_MANIFESTS = ["package.json", "Cargo.toml", "pyproject.toml"];
5141
5155
  var LOOSE_DIRS = /* @__PURE__ */ new Set(["/tmp", "/private/tmp"]);
5142
5156
  var LOOSE_HOME_DIRS = ["Desktop", "Downloads", "Documents"];
5157
+ var PROJECT_IDENTITY_KINDS = /* @__PURE__ */ new Set([
5158
+ "git_remote",
5159
+ "git_common_dir",
5160
+ "manifest_path",
5161
+ "synthetic",
5162
+ "path",
5163
+ "loose"
5164
+ ]);
5165
+ function isProjectIdentityKind(value) {
5166
+ return PROJECT_IDENTITY_KINDS.has(value);
5167
+ }
5168
+ function getProjectIdentityKey(identity) {
5169
+ return `${identity.kind}:${identity.key}`;
5170
+ }
5171
+ function matchesProjectIdentity(identity, expected) {
5172
+ return identity?.kind === expected.kind && identity.key === expected.key;
5173
+ }
5143
5174
  function normalizeGitRemote(url) {
5144
5175
  if (!url) return null;
5145
5176
  let value = url.trim().replace(/\.git$/, "");
@@ -5270,15 +5301,53 @@ function parseManifestName(file, text) {
5270
5301
  }
5271
5302
  return null;
5272
5303
  }
5304
+ function getAgentName(session) {
5305
+ return session.slug.split("/")[0]?.toLowerCase() || "unknown";
5306
+ }
5307
+ function buildProjectGroups(sessions) {
5308
+ const groups = /* @__PURE__ */ new Map();
5309
+ for (const session of sessions) {
5310
+ const identity = session.project_identity;
5311
+ if (!identity) continue;
5312
+ const activity = session.time_updated ?? session.time_created;
5313
+ const groupKey = getProjectIdentityKey(identity);
5314
+ const current = groups.get(groupKey);
5315
+ if (current) {
5316
+ current.sources.add(getAgentName(session));
5317
+ current.sessionCount += 1;
5318
+ current.lastActivity = Math.max(current.lastActivity, activity);
5319
+ } else {
5320
+ groups.set(groupKey, {
5321
+ identity,
5322
+ sources: /* @__PURE__ */ new Set([getAgentName(session)]),
5323
+ sessionCount: 1,
5324
+ lastActivity: activity
5325
+ });
5326
+ }
5327
+ }
5328
+ return [...groups.values()].map((group) => ({
5329
+ identityKind: group.identity.kind,
5330
+ identityKey: group.identity.key,
5331
+ displayName: group.identity.displayName,
5332
+ sources: [...group.sources].sort(),
5333
+ sessionCount: group.sessionCount,
5334
+ lastActivity: group.lastActivity || null
5335
+ })).sort((a, b) => {
5336
+ if (a.identityKind === "loose" && b.identityKind !== "loose") return 1;
5337
+ if (b.identityKind === "loose" && a.identityKind !== "loose") return -1;
5338
+ return (b.lastActivity ?? 0) - (a.lastActivity ?? 0);
5339
+ });
5340
+ }
5273
5341
  function createProjectScopeMatcher(queryPath, fs = realFs) {
5342
+ const identity = computeIdentity(queryPath, fs);
5274
5343
  return {
5275
- identityKey: computeIdentity(queryPath, fs).key,
5344
+ identity: { kind: identity.kind, key: identity.key },
5276
5345
  path: normalizeScopePath(queryPath)
5277
5346
  };
5278
5347
  }
5279
5348
  function matchesProjectScope(session, scope) {
5280
5349
  if (!session.directory) return false;
5281
- if (session.project_identity?.key === scope.identityKey) return true;
5350
+ if (matchesProjectIdentity(session.project_identity, scope.identity)) return true;
5282
5351
  return isPathScopeMatch(scope.path, session.directory);
5283
5352
  }
5284
5353
  function filterSessionsByProjectScope(sessions, queryPath, fs) {
@@ -5571,6 +5640,13 @@ function getFtsIntegrityCheckedPath() {
5571
5640
  function setFtsIntegrityCheckedPath(path2) {
5572
5641
  ftsIntegrityCheckedPath = path2;
5573
5642
  }
5643
+ var schemaEnsuredPath = null;
5644
+ function getSchemaEnsuredPath() {
5645
+ return schemaEnsuredPath;
5646
+ }
5647
+ function setSchemaEnsuredPath(path2) {
5648
+ schemaEnsuredPath = path2;
5649
+ }
5574
5650
  function getCacheDir2() {
5575
5651
  return join11(homedir4(), ".cache", "codesesh");
5576
5652
  }
@@ -6033,7 +6109,10 @@ function withCacheDb(fn) {
6033
6109
  const db = openDb(cachePath);
6034
6110
  if (!db) return null;
6035
6111
  try {
6036
- ensureSchema(db, cachePath);
6112
+ if (getSchemaEnsuredPath() !== cachePath) {
6113
+ ensureSchema(db, cachePath);
6114
+ setSchemaEnsuredPath(cachePath);
6115
+ }
6037
6116
  return fn(db);
6038
6117
  } catch {
6039
6118
  return null;
@@ -6877,30 +6956,6 @@ function ensureSchema(db, dbPath) {
6877
6956
  setCacheSchemaVersion(db);
6878
6957
  }
6879
6958
  }
6880
- function shouldBulkSyncSearchIndex(options, changedCount) {
6881
- if (options.isBulk != null) {
6882
- return options.isBulk;
6883
- }
6884
- const threshold = options.bulkThreshold ?? SEARCH_INDEX_BULK_SYNC_THRESHOLD;
6885
- return threshold > 0 && changedCount >= threshold;
6886
- }
6887
- function sessionContentHash(session) {
6888
- return JSON.stringify([
6889
- session.slug,
6890
- session.title,
6891
- session.directory,
6892
- session.time_created,
6893
- session.time_updated ?? session.time_created,
6894
- session.stats.message_count,
6895
- session.stats.total_input_tokens,
6896
- session.stats.total_output_tokens,
6897
- session.stats.total_cache_read_tokens ?? 0,
6898
- session.stats.total_cache_create_tokens ?? 0,
6899
- session.stats.total_cost,
6900
- session.stats.cost_source ?? "",
6901
- session.stats.total_tokens ?? 0
6902
- ]);
6903
- }
6904
6959
  function escapeFtsTerm(value) {
6905
6960
  return value.replaceAll('"', '""');
6906
6961
  }
@@ -6923,9 +6978,7 @@ function splitSearchTokens(input) {
6923
6978
  }
6924
6979
  token += char;
6925
6980
  }
6926
- if (token) {
6927
- tokens.push(token);
6928
- }
6981
+ if (token) tokens.push(token);
6929
6982
  return tokens;
6930
6983
  }
6931
6984
  function unwrapSearchValue(value) {
@@ -6985,7 +7038,10 @@ function parseSearchQuery(input) {
6985
7038
  if (key === "agent") filters.agent = value.toLowerCase();
6986
7039
  else if (key === "project") filters.project = value;
6987
7040
  else if (key === "projectkey" || key === "project-key") filters.projectKey = value;
6988
- else if (key === "cwd") filters.cwd = value;
7041
+ else if (key === "projectkind" || key === "project-kind") {
7042
+ if (isProjectIdentityKind(value)) filters.projectKind = value;
7043
+ else consumed = false;
7044
+ } else if (key === "cwd") filters.cwd = value;
6989
7045
  else if (key === "tool") filters.tools = appendUnique(filters.tools, value.toLowerCase());
6990
7046
  else if (key === "file" || key === "path") filters.file = value;
6991
7047
  else if (key === "kind" || key === "filekind" || key === "file-kind") {
@@ -6996,21 +7052,15 @@ function parseSearchQuery(input) {
6996
7052
  }
6997
7053
  } else if (key === "tag" || key === "signal") {
6998
7054
  const tag = value.toLowerCase();
6999
- if (isSmartTag(tag)) {
7000
- filters.tags = appendUnique(filters.tags, tag);
7001
- } else {
7002
- consumed = false;
7003
- }
7055
+ if (isSmartTag(tag)) filters.tags = appendUnique(filters.tags, tag);
7056
+ else consumed = false;
7004
7057
  } else if (key === "cost") {
7005
7058
  parseCostQualifier(value, filters);
7006
7059
  } else {
7007
7060
  consumed = false;
7008
7061
  }
7009
- if (consumed) {
7010
- hasQualifiers = true;
7011
- } else {
7012
- textTokens.push(token);
7013
- }
7062
+ if (consumed) hasQualifiers = true;
7063
+ else textTokens.push(token);
7014
7064
  }
7015
7065
  return {
7016
7066
  text: textTokens.join(" ").trim(),
@@ -7020,18 +7070,75 @@ function parseSearchQuery(input) {
7020
7070
  }
7021
7071
  function toFtsQuery(input) {
7022
7072
  const tokens = splitSearchTokens(input);
7023
- const mapped = tokens.map((token) => {
7024
- if (/^OR$/i.test(token)) {
7025
- return "OR";
7026
- }
7073
+ return tokens.map((token) => {
7074
+ if (/^OR$/i.test(token)) return "OR";
7027
7075
  if (token.startsWith('"') && token.endsWith('"')) {
7028
7076
  return `"${escapeFtsTerm(token.slice(1, -1))}"`;
7029
7077
  }
7030
7078
  return `"${escapeFtsTerm(token)}"`;
7031
7079
  }).filter(
7032
7080
  (token, index, values) => token !== "OR" || index > 0 && index < values.length - 1 && values[index - 1] !== "OR" && values[index + 1] !== "OR"
7033
- );
7034
- return mapped.join(" ");
7081
+ ).join(" ");
7082
+ }
7083
+ var SEARCH_INDEX_STATE_BATCH_SIZE = 900;
7084
+ function shouldBulkSyncSearchIndex(options, changedCount) {
7085
+ if (options.isBulk != null) {
7086
+ return options.isBulk;
7087
+ }
7088
+ const threshold = options.bulkThreshold ?? SEARCH_INDEX_BULK_SYNC_THRESHOLD;
7089
+ return threshold > 0 && changedCount >= threshold;
7090
+ }
7091
+ function sessionContentHash(session) {
7092
+ return JSON.stringify([
7093
+ session.slug,
7094
+ session.title,
7095
+ session.directory,
7096
+ session.time_created,
7097
+ session.time_updated ?? session.time_created,
7098
+ session.stats.message_count,
7099
+ session.stats.total_input_tokens,
7100
+ session.stats.total_output_tokens,
7101
+ session.stats.total_cache_read_tokens ?? 0,
7102
+ session.stats.total_cache_create_tokens ?? 0,
7103
+ session.stats.total_cost,
7104
+ session.stats.cost_source ?? "",
7105
+ session.stats.total_tokens ?? 0
7106
+ ]);
7107
+ }
7108
+ function searchIndexStateFromRows(indexedRows, messageCountRows) {
7109
+ return {
7110
+ contentHashBySessionId: new Map(
7111
+ indexedRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
7112
+ ),
7113
+ messageCountBySessionId: new Map(
7114
+ messageCountRows.map((row) => [String(row.session_id), Number(row.value ?? 0)])
7115
+ )
7116
+ };
7117
+ }
7118
+ function readSearchIndexState(db, agentName, sessionIds) {
7119
+ const rows = [];
7120
+ const uniqueSessionIds = [...new Set(sessionIds)];
7121
+ for (let offset = 0; offset < uniqueSessionIds.length; offset += SEARCH_INDEX_STATE_BATCH_SIZE) {
7122
+ const batch = uniqueSessionIds.slice(offset, offset + SEARCH_INDEX_STATE_BATCH_SIZE);
7123
+ const requestedRows = batch.map(() => "(?)").join(", ");
7124
+ const batchRows = db.prepare(
7125
+ `
7126
+ WITH requested_session_ids(session_id) AS (VALUES ${requestedRows})
7127
+ SELECT
7128
+ requested.session_id,
7129
+ documents.content_hash,
7130
+ COUNT(messages.message_index) AS value
7131
+ FROM requested_session_ids AS requested
7132
+ LEFT JOIN session_documents AS documents
7133
+ ON documents.agent_name = ? AND documents.session_id = requested.session_id
7134
+ LEFT JOIN messages
7135
+ ON messages.agent_name = ? AND messages.session_id = requested.session_id
7136
+ GROUP BY requested.session_id, documents.content_hash
7137
+ `
7138
+ ).all(...batch, agentName, agentName);
7139
+ rows.push(...batchRows);
7140
+ }
7141
+ return searchIndexStateFromRows(rows, rows);
7035
7142
  }
7036
7143
  function loadSearchIndexEntry(agentName, change, loadSessionData) {
7037
7144
  try {
@@ -7207,20 +7314,15 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
7207
7314
  const existingRows = db.prepare(
7208
7315
  "SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
7209
7316
  ).all(agentName);
7210
- const existingMap = new Map(
7211
- existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
7212
- );
7213
7317
  const sessionSortIndexMap = new Map(sessions.map((session, index) => [session.id, index]));
7214
7318
  const messageCountRows = db.prepare(
7215
7319
  "SELECT session_id, COUNT(*) AS value FROM messages WHERE agent_name = ? GROUP BY session_id"
7216
7320
  ).all(agentName);
7217
- const messageCountMap = new Map(
7218
- messageCountRows.map((row) => [String(row.session_id), Number(row.value ?? 0)])
7219
- );
7321
+ const searchIndexState = searchIndexStateFromRows(existingRows, messageCountRows);
7220
7322
  const sessionMap = new Map(sessions.map((session) => [session.id, session]));
7221
7323
  const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
7222
7324
  const toUpsert = sessions.filter(
7223
- (session) => existingMap.get(session.id) !== sessionContentHash(session) || messageCountMap.get(session.id) !== session.stats.message_count
7325
+ (session) => searchIndexState.contentHashBySessionId.get(session.id) !== sessionContentHash(session) || searchIndexState.messageCountBySessionId.get(session.id) !== session.stats.message_count
7224
7326
  );
7225
7327
  const changedCount = toDelete.length + toUpsert.length;
7226
7328
  const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
@@ -7278,17 +7380,14 @@ function syncSessionSearchIndexChanges(agentName, changes, removedSessionIds, lo
7278
7380
  return withCacheDb((db) => {
7279
7381
  ensureFtsConsistency(db);
7280
7382
  const startedAt = performance.now();
7281
- const getIndexedRow = db.prepare(
7282
- "SELECT content_hash FROM session_documents WHERE agent_name = ? AND session_id = ?"
7383
+ const searchIndexState = readSearchIndexState(
7384
+ db,
7385
+ agentName,
7386
+ changes.map(({ session }) => session.id)
7283
7387
  );
7284
- const getMessageCount = db.prepare(
7285
- "SELECT COUNT(*) AS value FROM messages WHERE agent_name = ? AND session_id = ?"
7388
+ const toUpsert = changes.filter(
7389
+ ({ session }) => (searchIndexState.contentHashBySessionId.get(session.id) ?? "") !== sessionContentHash(session) || (searchIndexState.messageCountBySessionId.get(session.id) ?? 0) !== session.stats.message_count
7286
7390
  );
7287
- const toUpsert = changes.filter(({ session }) => {
7288
- const indexed = getIndexedRow.get(agentName, session.id);
7289
- const messageCount = getMessageCount.get(agentName, session.id);
7290
- return String(indexed?.content_hash ?? "") !== sessionContentHash(session) || Number(messageCount?.value ?? 0) !== session.stats.message_count;
7291
- });
7292
7391
  const uniqueRemovedSessionIds = Array.from(new Set(removedSessionIds));
7293
7392
  const changedCount = uniqueRemovedSessionIds.length + toUpsert.length;
7294
7393
  const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
@@ -7339,6 +7438,7 @@ function mergeSearchQueryOptions(query, options) {
7339
7438
  ...options,
7340
7439
  agent: options.agent ?? parsed2.filters.agent,
7341
7440
  project: options.project ?? parsed2.filters.project,
7441
+ projectKind: options.projectKind ?? parsed2.filters.projectKind,
7342
7442
  projectKey: options.projectKey ?? parsed2.filters.projectKey,
7343
7443
  cwd: options.cwd ?? parsed2.filters.cwd,
7344
7444
  tags: mergeSearchLists(options.tags, parsed2.filters.tags),
@@ -7374,13 +7474,20 @@ function buildSessionSearchFilters(options) {
7374
7474
  clauses.push("s.agent_name = ?");
7375
7475
  params.push(options.agent);
7376
7476
  }
7377
- if (options.projectKey) {
7378
- clauses.push("s.project_identity_key = ?");
7379
- params.push(options.projectKey);
7477
+ if (options.projectKind || options.projectKey) {
7478
+ if (options.projectKind && options.projectKey) {
7479
+ clauses.push("s.project_identity_kind = ? AND s.project_identity_key = ?");
7480
+ params.push(options.projectKind, options.projectKey);
7481
+ } else {
7482
+ clauses.push("0");
7483
+ }
7380
7484
  }
7381
7485
  if (options.cwd) {
7382
- clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
7383
- params.push(computeIdentity(options.cwd, realFs).key, likePattern(options.cwd));
7486
+ const identity = computeIdentity(options.cwd, realFs);
7487
+ clauses.push(
7488
+ "((s.project_identity_kind = ? AND s.project_identity_key = ?) OR LOWER(s.directory) LIKE ? ESCAPE '\\')"
7489
+ );
7490
+ params.push(identity.kind, identity.key, likePattern(options.cwd));
7384
7491
  }
7385
7492
  if (options.project) {
7386
7493
  clauses.push(
@@ -7625,10 +7732,13 @@ function searchSessions(query, options = {}) {
7625
7732
  }
7626
7733
  function fileActivityFilters(options) {
7627
7734
  const path2 = options.path ? normalizeFilePathSearch(options.path) : "";
7735
+ const cwdIdentity = options.cwd ? computeIdentity(options.cwd, realFs) : null;
7628
7736
  return {
7737
+ projectKind: options.projectKind ?? null,
7629
7738
  projectKey: options.projectKey ?? null,
7630
7739
  projectLike: options.project ? likePattern(options.project) : null,
7631
- cwdKey: options.cwd ? computeIdentity(options.cwd, realFs).key : null,
7740
+ cwdKind: cwdIdentity?.kind ?? null,
7741
+ cwdKey: cwdIdentity?.key ?? null,
7632
7742
  cwdLike: options.cwd ? likePattern(options.cwd) : null,
7633
7743
  path: path2,
7634
7744
  pathLike: path2 ? likePattern(path2) : null
@@ -7657,9 +7767,13 @@ function buildFileActivityWhere(options) {
7657
7767
  clauses.push("fa.session_id = ?");
7658
7768
  params.push(options.sessionId);
7659
7769
  }
7660
- if (filters.projectKey != null) {
7661
- clauses.push("fa.project_identity_key = ?");
7662
- params.push(filters.projectKey);
7770
+ if (filters.projectKind != null || filters.projectKey != null) {
7771
+ if (filters.projectKind != null && filters.projectKey != null) {
7772
+ clauses.push("s.project_identity_kind = ? AND fa.project_identity_key = ?");
7773
+ params.push(filters.projectKind, filters.projectKey);
7774
+ } else {
7775
+ clauses.push("0");
7776
+ }
7663
7777
  }
7664
7778
  if (filters.projectLike != null) {
7665
7779
  clauses.push(
@@ -7668,8 +7782,10 @@ function buildFileActivityWhere(options) {
7668
7782
  params.push(filters.projectLike, filters.projectLike, filters.projectLike);
7669
7783
  }
7670
7784
  if (filters.cwdKey != null) {
7671
- clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
7672
- params.push(filters.cwdKey, filters.cwdLike);
7785
+ clauses.push(
7786
+ "((s.project_identity_kind = ? AND s.project_identity_key = ?) OR LOWER(s.directory) LIKE ? ESCAPE '\\')"
7787
+ );
7788
+ params.push(filters.cwdKind, filters.cwdKey, filters.cwdLike);
7673
7789
  }
7674
7790
  if (filters.pathLike != null) {
7675
7791
  const pathQuery = filePathFtsQuery(filters.path);
@@ -7767,6 +7883,7 @@ function searchFileActivitySessions(query, options = {}) {
7767
7883
  if (!path2) return [];
7768
7884
  const rows = listFileActivity({
7769
7885
  agent: search.options.agent,
7886
+ projectKind: search.options.projectKind,
7770
7887
  projectKey: search.options.projectKey,
7771
7888
  project: search.options.project,
7772
7889
  cwd: search.options.cwd,
@@ -7875,16 +7992,41 @@ function isAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATION_
7875
7992
  }
7876
7993
  function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATION_VERSION) {
7877
7994
  withCacheDb((db) => {
7878
- const now = Date.now();
7879
7995
  db.prepare(
7880
7996
  `
7881
7997
  INSERT INTO cache_initialization(agent_name, initialized_at, index_version, last_sync_at)
7882
- VALUES (?, ?, ?, ?)
7998
+ VALUES (?, ?, ?, 0)
7883
7999
  ON CONFLICT(agent_name) DO UPDATE SET
7884
- index_version = excluded.index_version,
7885
- last_sync_at = excluded.last_sync_at
8000
+ index_version = excluded.index_version
7886
8001
  `
7887
- ).run(agentName, now, indexVersion, now);
8002
+ ).run(agentName, Date.now(), indexVersion);
8003
+ });
8004
+ }
8005
+ function getAgentLastFullSyncAt(agentName) {
8006
+ if (!hasCacheStorage()) {
8007
+ return null;
8008
+ }
8009
+ return withCacheDbReadOnly((db) => {
8010
+ if (!tableExists(db, "cache_initialization")) return null;
8011
+ const row = db.prepare(
8012
+ `
8013
+ SELECT last_sync_at
8014
+ FROM cache_initialization
8015
+ WHERE agent_name = ?
8016
+ `
8017
+ ).get(agentName);
8018
+ return row?.last_sync_at || null;
8019
+ }) ?? null;
8020
+ }
8021
+ function markAgentFullSyncCompleted(agentName) {
8022
+ withCacheDb((db) => {
8023
+ db.prepare(
8024
+ `
8025
+ UPDATE cache_initialization
8026
+ SET last_sync_at = ?
8027
+ WHERE agent_name = ?
8028
+ `
8029
+ ).run(Date.now(), agentName);
7888
8030
  });
7889
8031
  }
7890
8032
  function loadCachedSessionData(agentName, sessionId) {
@@ -8101,6 +8243,7 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
8101
8243
  }
8102
8244
  function clearCache() {
8103
8245
  setFtsIntegrityCheckedPath(null);
8246
+ setSchemaEnsuredPath(null);
8104
8247
  if (!hasCacheStorage()) {
8105
8248
  deleteLegacyCacheFile();
8106
8249
  return;
@@ -8217,9 +8360,7 @@ function sessionSignature(session) {
8217
8360
  ]);
8218
8361
  }
8219
8362
  function sortSessions(sessions) {
8220
- return [...sessions].sort(
8221
- (a, b) => (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created)
8222
- );
8363
+ return sortSessionsByActivity(sessions);
8223
8364
  }
8224
8365
  function computeSessionDiff(cachedSessions, updatedSessions, changedIds = [], signature = sessionSignature) {
8225
8366
  const cachedMap = new Map(cachedSessions.map((session) => [session.id, session]));
@@ -8516,6 +8657,7 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
8516
8657
  if (options.writeCache !== false && options.from == null && options.to == null) {
8517
8658
  saveCachedSessions(agent.name, tagged.sessions, meta);
8518
8659
  markAgentCacheInitialized(agent.name);
8660
+ markAgentFullSyncCompleted(agent.name);
8519
8661
  }
8520
8662
  onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
8521
8663
  const filtered2 = filterSessions(tagged.sessions, options);
@@ -8932,10 +9074,11 @@ function buildDashboard(sessions, options) {
8932
9074
  for (const session of sessions) {
8933
9075
  const agentName = getSessionAgentName(session);
8934
9076
  if (scope.agent && agentName !== scope.agent) continue;
8935
- if (scope.projectKey) {
9077
+ if (scope.projectKind || scope.projectKey) {
8936
9078
  const identity = session.project_identity;
8937
- if (!identity || identity.key !== scope.projectKey) continue;
8938
- if (scope.projectKind && identity.kind !== scope.projectKind) continue;
9079
+ if (!identity || !scope.projectKind || !scope.projectKey || identity.kind !== scope.projectKind || identity.key !== scope.projectKey) {
9080
+ continue;
9081
+ }
8939
9082
  }
8940
9083
  const activity = getSessionActivityTime(session);
8941
9084
  if (from != null && activity < from) continue;
@@ -9034,6 +9177,90 @@ function buildDashboard(sessions, options) {
9034
9177
  recentSessions
9035
9178
  };
9036
9179
  }
9180
+ function executeSessionSearch(query, options, snapshot) {
9181
+ const merged = mergeSearchQueryOptions(query, options);
9182
+ if (!needsIndexedSearch(merged.text, merged.options)) {
9183
+ return searchRecentSessions(snapshot, merged.options);
9184
+ }
9185
+ return searchIndexedSessions(query, merged.text, merged.parsed, merged.options);
9186
+ }
9187
+ function needsIndexedSearch(textQuery, options) {
9188
+ return Boolean(textQuery || options.file || options.fileKind || options.tools?.length);
9189
+ }
9190
+ function filterSessionsByActivityWindow(sessions, from, to) {
9191
+ if (from == null && to == null) return sessions;
9192
+ return sessions.filter((session) => {
9193
+ const activity = getSessionActivityTime(session);
9194
+ if (from != null && activity < from) return false;
9195
+ if (to != null && activity > to) return false;
9196
+ return true;
9197
+ });
9198
+ }
9199
+ function matchesRecentSearchFilters(session, options, projectScope) {
9200
+ if (options.projectKind || options.projectKey) {
9201
+ if (!options.projectKind || !options.projectKey || !matchesProjectIdentity(session.project_identity, {
9202
+ kind: options.projectKind,
9203
+ key: options.projectKey
9204
+ })) {
9205
+ return false;
9206
+ }
9207
+ }
9208
+ if (projectScope && !matchesProjectScope(session, projectScope)) return false;
9209
+ if (options.project) {
9210
+ const projectNeedle = options.project.toLowerCase();
9211
+ const projectText = [
9212
+ session.project_identity?.key,
9213
+ session.project_identity?.displayName,
9214
+ session.directory
9215
+ ].filter(Boolean).join("\n").toLowerCase();
9216
+ if (!projectText.includes(projectNeedle)) return false;
9217
+ }
9218
+ if (options.tags?.length && !options.tags.every((tag) => session.smart_tags?.includes(tag))) {
9219
+ return false;
9220
+ }
9221
+ if (!sessionMatchesSearchCost(session, options)) return false;
9222
+ return true;
9223
+ }
9224
+ function searchRecentSessions(snapshot, options) {
9225
+ const projectScope = options.cwd ? createProjectScopeMatcher(options.cwd) : null;
9226
+ const entries = options.agent ? [[options.agent, snapshot.byAgent[options.agent] ?? []]] : Object.entries(snapshot.byAgent);
9227
+ return entries.flatMap(
9228
+ ([agentName, sessions]) => filterSessionsByActivityWindow(sessions, options.from, options.to).filter((session) => matchesRecentSearchFilters(session, options, projectScope)).map((session) => ({ agentName, session }))
9229
+ ).sort(
9230
+ (a, b) => (b.session.time_updated ?? b.session.time_created) - (a.session.time_updated ?? a.session.time_created)
9231
+ ).slice(0, options.limit ?? 50).map(({ agentName, session }) => ({
9232
+ agentName,
9233
+ session,
9234
+ snippet: `Recent session \xB7 ${session.directory}`,
9235
+ matchType: "recent"
9236
+ }));
9237
+ }
9238
+ function deriveFileQuery(query, parsed2, options) {
9239
+ return options.file ?? (!parsed2.text ? parsed2.filters.file : void 0) ?? (!parsed2.hasQualifiers && query ? parsed2.text || query : "");
9240
+ }
9241
+ function mergeSearchResultSources(results, limit) {
9242
+ const seen = /* @__PURE__ */ new Set();
9243
+ const merged = [];
9244
+ for (const result of results) {
9245
+ const key = `${result.agentName}/${result.session.id}`;
9246
+ if (seen.has(key)) continue;
9247
+ seen.add(key);
9248
+ merged.push(result);
9249
+ if (merged.length >= limit) break;
9250
+ }
9251
+ return merged;
9252
+ }
9253
+ function canSkipSessionsSearch(fileQuery, textQuery, options) {
9254
+ return Boolean(
9255
+ fileQuery && !textQuery && !options.tools?.length && !options.tags?.length && options.from == null && options.to == null
9256
+ );
9257
+ }
9258
+ function searchIndexedSessions(query, textQuery, parsed2, options) {
9259
+ const fileQuery = deriveFileQuery(query, parsed2, options);
9260
+ const fileResults = fileQuery ? searchFileActivitySessions(fileQuery, options) : [];
9261
+ const sessionResults = canSkipSessionsSearch(fileQuery, textQuery, options) ? [] : searchSessions(query, options);
9262
+ return mergeSearchResultSources([...fileResults, ...sessionResults], options.limit ?? 50);
9263
+ }
9037
9264
 
9038
9265
  export {
9039
9266
  registerAgent,
@@ -9045,6 +9272,7 @@ export {
9045
9272
  skippedSession,
9046
9273
  filteredSession,
9047
9274
  getParsedSession,
9275
+ matchesScanWindow,
9048
9276
  BaseAgent,
9049
9277
  FileSystemSessionSource,
9050
9278
  DatabaseSessionSource,
@@ -9082,9 +9310,12 @@ export {
9082
9310
  isSqliteAvailable,
9083
9311
  fallbackDisplayName,
9084
9312
  realFs,
9085
- buildProjectGroups,
9313
+ isProjectIdentityKind,
9314
+ getProjectIdentityKey,
9315
+ matchesProjectIdentity,
9086
9316
  normalizeGitRemote,
9087
9317
  computeIdentity,
9318
+ buildProjectGroups,
9088
9319
  createProjectScopeMatcher,
9089
9320
  matchesProjectScope,
9090
9321
  filterSessionsByProjectScope,
@@ -9093,6 +9324,8 @@ export {
9093
9324
  extractFileActivityOccurrences,
9094
9325
  summarizeFileActivity,
9095
9326
  extractSessionFileActivity,
9327
+ setFtsIntegrityCheckedPath,
9328
+ getCachePath2,
9096
9329
  parseSearchQuery,
9097
9330
  syncSessionSearchIndex,
9098
9331
  syncSessionSearchIndexChanges,
@@ -9103,6 +9336,8 @@ export {
9103
9336
  loadCachedSessions,
9104
9337
  isAgentCacheInitialized,
9105
9338
  markAgentCacheInitialized,
9339
+ getAgentLastFullSyncAt,
9340
+ markAgentFullSyncCompleted,
9106
9341
  loadCachedSessionData,
9107
9342
  saveCachedSessions,
9108
9343
  saveCachedSessionChanges,
@@ -9115,6 +9350,7 @@ export {
9115
9350
  sortSessions,
9116
9351
  computeSessionDiff,
9117
9352
  filterSessions,
9353
+ ensureSessionTagsSync,
9118
9354
  scanSessions,
9119
9355
  scanSessionsAsync,
9120
9356
  BookmarkStorageUnavailableError,
@@ -9128,6 +9364,7 @@ export {
9128
9364
  getSessionActivityTime,
9129
9365
  toLocalDateKey,
9130
9366
  startOfLocalDay,
9131
- buildDashboard
9367
+ buildDashboard,
9368
+ executeSessionSearch
9132
9369
  };
9133
- //# sourceMappingURL=chunk-BIXOP5QX.js.map
9370
+ //# sourceMappingURL=chunk-BV65IEWZ.js.map