codesesh 0.12.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
  }
@@ -945,7 +986,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
945
986
  if (!existsSync4(meta.sourcePath)) {
946
987
  throw new Error(`Session file missing: ${meta.sourcePath}`);
947
988
  }
948
- const content = readFileSync3(meta.sourcePath, "utf-8");
989
+ const content = readFileSync2(meta.sourcePath, "utf-8");
949
990
  const messages = [];
950
991
  const pendingToolCalls = /* @__PURE__ */ new Map();
951
992
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -1066,7 +1107,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1066
1107
  const map = /* @__PURE__ */ new Map();
1067
1108
  if (existsSync4(indexPath)) {
1068
1109
  try {
1069
- const data = JSON.parse(readFileSync3(indexPath, "utf-8"));
1110
+ const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
1070
1111
  const entries = data?.entries ?? [];
1071
1112
  for (const entry of entries) {
1072
1113
  const sid = entry?.sessionId;
@@ -1085,7 +1126,7 @@ var ClaudeCodeAgent = class extends FileSystemSessionSource {
1085
1126
  return getParsedSession(this.parseSessionHeadResult(filePath, projectDir));
1086
1127
  }
1087
1128
  parseSessionHeadResult(filePath, projectDir) {
1088
- const content = readFileSync3(filePath, "utf-8");
1129
+ const content = readFileSync2(filePath, "utf-8");
1089
1130
  const lines = content.split("\n").filter((l) => l.trim());
1090
1131
  if (lines.length === 0) return skippedSession("empty file");
1091
1132
  const sessionId = basename2(filePath, ".jsonl");
@@ -2142,7 +2183,7 @@ function kimiContentText(content) {
2142
2183
  }
2143
2184
  function extractFirstUserTitle(contextFile, wireFile) {
2144
2185
  if (contextFile && existsSync6(contextFile)) {
2145
- const content = readFileSync4(contextFile, "utf-8");
2186
+ const content = readFileSync3(contextFile, "utf-8");
2146
2187
  for (const record of parseJsonlLines(content)) {
2147
2188
  if (record.role !== "user") continue;
2148
2189
  const title = normalizeTitleText(kimiContentText(record.content));
@@ -2150,7 +2191,7 @@ function extractFirstUserTitle(contextFile, wireFile) {
2150
2191
  }
2151
2192
  }
2152
2193
  if (wireFile && existsSync6(wireFile)) {
2153
- const content = readFileSync4(wireFile, "utf-8");
2194
+ const content = readFileSync3(wireFile, "utf-8");
2154
2195
  for (const record of parseJsonlLines(content)) {
2155
2196
  const message = record.message ?? {};
2156
2197
  if (message.type !== "TurnBegin") continue;
@@ -2179,12 +2220,12 @@ var KimiAgent = class extends FileSystemSessionSource {
2179
2220
  const configPath = join6(roots.kimiRoot, "kimi.json");
2180
2221
  const tomlPath = join6(roots.kimiRoot, "config.toml");
2181
2222
  if (existsSync6(tomlPath)) {
2182
- const configText = readFileSync4(tomlPath, "utf-8");
2223
+ const configText = readFileSync3(tomlPath, "utf-8");
2183
2224
  this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
2184
2225
  }
2185
2226
  if (!existsSync6(configPath)) return;
2186
2227
  try {
2187
- const raw = JSON.parse(readFileSync4(configPath, "utf-8"));
2228
+ const raw = JSON.parse(readFileSync3(configPath, "utf-8"));
2188
2229
  const workDirs = raw?.work_dirs;
2189
2230
  if (!Array.isArray(workDirs)) return;
2190
2231
  for (const wd of workDirs) {
@@ -2248,12 +2289,12 @@ var KimiAgent = class extends FileSystemSessionSource {
2248
2289
  let wireMtime = null;
2249
2290
  let metaFile = "";
2250
2291
  if (existsSync6(statePath)) {
2251
- const state = JSON.parse(readFileSync4(statePath, "utf-8"));
2292
+ const state = JSON.parse(readFileSync3(statePath, "utf-8"));
2252
2293
  title = String(state.custom_title ?? "");
2253
2294
  wireMtime = typeof state.wire_mtime === "number" ? state.wire_mtime : null;
2254
2295
  metaFile = statePath;
2255
2296
  } else if (existsSync6(metaPath)) {
2256
- const meta = JSON.parse(readFileSync4(metaPath, "utf-8"));
2297
+ const meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
2257
2298
  title = String(meta.title ?? "");
2258
2299
  wireMtime = typeof meta.wire_mtime === "number" ? meta.wire_mtime : null;
2259
2300
  metaFile = metaPath;
@@ -2361,7 +2402,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2361
2402
  }
2362
2403
  getSessionDataFromContext(meta) {
2363
2404
  if (!meta.contextFile) throw new Error("context.jsonl is missing");
2364
- const content = readFileSync4(meta.contextFile, "utf-8");
2405
+ const content = readFileSync3(meta.contextFile, "utf-8");
2365
2406
  const messages = [];
2366
2407
  const pendingToolCalls = /* @__PURE__ */ new Map();
2367
2408
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -2428,7 +2469,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2428
2469
  getSessionDataFromWire(meta) {
2429
2470
  const wirePath = meta.wireFile ?? join6(meta.sourcePath, "wire.jsonl");
2430
2471
  if (!existsSync6(wirePath)) throw new Error("wire.jsonl is missing");
2431
- const content = readFileSync4(wirePath, "utf-8");
2472
+ const content = readFileSync3(wirePath, "utf-8");
2432
2473
  const messages = [];
2433
2474
  const pendingToolCalls = /* @__PURE__ */ new Map();
2434
2475
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
@@ -2728,7 +2769,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2728
2769
  const wirePath = join6(sessionDir, "wire.jsonl");
2729
2770
  if (!existsSync6(wirePath)) return stats;
2730
2771
  try {
2731
- const content = readFileSync4(wirePath, "utf-8");
2772
+ const content = readFileSync3(wirePath, "utf-8");
2732
2773
  for (const line of content.split("\n").filter((l) => l.trim())) {
2733
2774
  try {
2734
2775
  const data = JSON.parse(line);
@@ -2752,7 +2793,7 @@ var KimiAgent = class extends FileSystemSessionSource {
2752
2793
  const rawPath = existsSync6(contextPath) ? contextPath : wirePath;
2753
2794
  if (!existsSync6(rawPath)) return stats;
2754
2795
  try {
2755
- const rawContent = readFileSync4(rawPath, "utf-8");
2796
+ const rawContent = readFileSync3(rawPath, "utf-8");
2756
2797
  for (const line of rawContent.split("\n").filter((l) => l.trim())) {
2757
2798
  try {
2758
2799
  const data = JSON.parse(line);
@@ -3025,7 +3066,6 @@ var CodexAgent = class extends FileSystemSessionSource {
3025
3066
  const meta = this.sessionMetaMap.get(sessionId);
3026
3067
  if (!meta) throw new Error(`Session not found: ${sessionId}`);
3027
3068
  if (!existsSync7(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
3028
- const content = readFileSync5(meta.sourcePath, "utf-8");
3029
3069
  const messages = [];
3030
3070
  const pendingToolCalls = /* @__PURE__ */ new Map();
3031
3071
  let totalInputTokens = 0;
@@ -3041,7 +3081,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3041
3081
  let prevOutput = 0;
3042
3082
  let prevReasoning = 0;
3043
3083
  let prevCachedInput = 0;
3044
- for (const record of parseJsonlLines(content)) {
3084
+ for (const record of readJsonlFile(meta.sourcePath)) {
3045
3085
  try {
3046
3086
  const recordType = String(record["type"] ?? "");
3047
3087
  if (recordType === "turn_context") {
@@ -3229,7 +3269,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3229
3269
  this.sessionIndexMtime = mtime;
3230
3270
  if (mtime === null) return;
3231
3271
  try {
3232
- const content = readFileSync5(indexPath, "utf-8");
3272
+ const content = readFileSync4(indexPath, "utf-8");
3233
3273
  for (const record of parseJsonlLines(content)) {
3234
3274
  const sid = String(record["id"] ?? "").trim();
3235
3275
  const threadName = String(record["thread_name"] ?? "").trim();
@@ -3246,13 +3286,13 @@ var CodexAgent = class extends FileSystemSessionSource {
3246
3286
  }
3247
3287
  // ---- Session head parsing ----
3248
3288
  readFilePrefix(filePath, bytes = 64 * 1024) {
3249
- const fd = openSync(filePath, "r");
3289
+ const fd = openSync2(filePath, "r");
3250
3290
  try {
3251
3291
  const buffer = Buffer.alloc(bytes);
3252
- const bytesRead = readSync(fd, buffer, 0, bytes, 0);
3292
+ const bytesRead = readSync2(fd, buffer, 0, bytes, 0);
3253
3293
  return buffer.subarray(0, bytesRead).toString("utf-8");
3254
3294
  } finally {
3255
- closeSync(fd);
3295
+ closeSync2(fd);
3256
3296
  }
3257
3297
  }
3258
3298
  parseSessionHead(filePath, options) {
@@ -3262,23 +3302,12 @@ var CodexAgent = class extends FileSystemSessionSource {
3262
3302
  if (options?.fast) {
3263
3303
  return this.parseFastSessionHeadResult(filePath);
3264
3304
  }
3265
- const content = readFileSync5(filePath, "utf-8");
3266
- const lines = content.split("\n").filter((l) => l.trim());
3267
- if (lines.length === 0) return skippedSession("empty file");
3268
3305
  const sessionId = extractSessionId(filePath);
3269
- let firstRecord;
3270
- try {
3271
- firstRecord = JSON.parse(lines[0]);
3272
- } catch {
3273
- return skippedSession("malformed first record");
3274
- }
3275
- const payload = firstRecord["payload"] ?? {};
3276
- const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || statSync4(filePath).mtimeMs;
3277
- const indexTitle = this.getTitleForSession(sessionId);
3278
- const messageTitle = this.extractTitleFromLines(lines);
3279
- const directoryTitle = basenameTitle(payload["cwd"] ? String(payload["cwd"]) : null);
3280
- const title = resolveSessionTitle(indexTitle, messageTitle, directoryTitle);
3281
- let updatedAt = createdAt;
3306
+ let firstPayload = {};
3307
+ let createdAt = 0;
3308
+ let lineCount = 0;
3309
+ const titleLines = [];
3310
+ let updatedAt = 0;
3282
3311
  let messageCount = 0;
3283
3312
  let model = null;
3284
3313
  let activeModel = null;
@@ -3294,18 +3323,31 @@ var CodexAgent = class extends FileSystemSessionSource {
3294
3323
  let scanPrevCachedInput = 0;
3295
3324
  const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
3296
3325
  let hasNonInternalRecord = false;
3297
- 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);
3298
3340
  try {
3299
3341
  const data = JSON.parse(line);
3300
3342
  const recordType = String(data["type"] ?? "");
3301
- const payload2 = data["payload"] ?? {};
3302
- const payloadType = String(payload2["type"] ?? "");
3343
+ const payload = data["payload"] ?? {};
3344
+ const payloadType = String(payload["type"] ?? "");
3303
3345
  if (isInternalEventType2(recordType) || isInternalEventType2(payloadType)) continue;
3304
3346
  hasNonInternalRecord = true;
3305
3347
  const recordTs = parseTimestampMs2(data) || parseTimestampMs2(data["payload"] ?? {});
3306
3348
  if (recordTs > updatedAt) updatedAt = recordTs;
3307
3349
  if (recordType === "session_meta" || recordType === "turn_context") {
3308
- const nextModel = extractModelName(payload2["model"]);
3350
+ const nextModel = extractModelName(payload["model"]);
3309
3351
  if (nextModel) {
3310
3352
  activeModel = nextModel;
3311
3353
  model ??= nextModel;
@@ -3313,7 +3355,7 @@ var CodexAgent = class extends FileSystemSessionSource {
3313
3355
  continue;
3314
3356
  }
3315
3357
  if (recordType === "response_item") {
3316
- const p = payload2;
3358
+ const p = payload;
3317
3359
  const pType = String(p["type"] ?? "");
3318
3360
  if (COUNTED_TYPES.has(pType)) {
3319
3361
  messageCount++;
@@ -3375,8 +3417,12 @@ var CodexAgent = class extends FileSystemSessionSource {
3375
3417
  } catch {
3376
3418
  }
3377
3419
  }
3420
+ if (lineCount === 0) return skippedSession("empty file");
3378
3421
  if (!hasNonInternalRecord) return filteredSession("internal events only");
3379
- 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));
3380
3426
  return parsedSession({
3381
3427
  id: sessionId,
3382
3428
  slug: `codex/${sessionId}`,
@@ -4008,7 +4054,7 @@ var CursorAgent = class extends DatabaseSessionSource {
4008
4054
  if (!existsSync8(wsJsonPath)) continue;
4009
4055
  let workspacePath;
4010
4056
  try {
4011
- const data = JSON.parse(readFileSync6(wsJsonPath, "utf-8"));
4057
+ const data = JSON.parse(readFileSync5(wsJsonPath, "utf-8"));
4012
4058
  const uri = data.folder ?? data.workspace ?? "";
4013
4059
  if (!uri) continue;
4014
4060
  workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
@@ -4718,7 +4764,7 @@ var PiAgent = class extends FileSystemSessionSource {
4718
4764
  });
4719
4765
  }
4720
4766
  parsePiFile(filePath) {
4721
- const records = Array.from(parseJsonlLines(readFileSync7(filePath, "utf-8")));
4767
+ const records = Array.from(parseJsonlLines(readFileSync6(filePath, "utf-8")));
4722
4768
  if (records.length === 0) throw new Error("empty file");
4723
4769
  const header = records.find((record) => record["type"] === "session");
4724
4770
  if (!header) throw new Error("missing session header");
@@ -5083,7 +5129,7 @@ var realFs = {
5083
5129
  },
5084
5130
  readText(path2) {
5085
5131
  try {
5086
- return readFileSync8(path2, "utf8");
5132
+ return readFileSync7(path2, "utf8");
5087
5133
  } catch {
5088
5134
  return null;
5089
5135
  }
@@ -5096,43 +5142,6 @@ var realFs = {
5096
5142
  };
5097
5143
  }
5098
5144
  };
5099
- function getAgentName(session) {
5100
- return session.slug.split("/")[0]?.toLowerCase() || "unknown";
5101
- }
5102
- function buildProjectGroups(sessions) {
5103
- const groups = /* @__PURE__ */ new Map();
5104
- for (const session of sessions) {
5105
- const identity = session.project_identity;
5106
- if (!identity) continue;
5107
- const activity = session.time_updated ?? session.time_created;
5108
- const groupKey = `${identity.kind}:${identity.key}`;
5109
- const current = groups.get(groupKey);
5110
- if (current) {
5111
- current.sources.add(getAgentName(session));
5112
- current.sessionCount += 1;
5113
- current.lastActivity = Math.max(current.lastActivity, activity);
5114
- } else {
5115
- groups.set(groupKey, {
5116
- identity,
5117
- sources: /* @__PURE__ */ new Set([getAgentName(session)]),
5118
- sessionCount: 1,
5119
- lastActivity: activity
5120
- });
5121
- }
5122
- }
5123
- return [...groups.values()].map((group) => ({
5124
- identityKind: group.identity.kind,
5125
- identityKey: group.identity.key,
5126
- displayName: group.identity.displayName,
5127
- sources: [...group.sources].sort(),
5128
- sessionCount: group.sessionCount,
5129
- lastActivity: group.lastActivity || null
5130
- })).sort((a, b) => {
5131
- if (a.identityKind === "loose" && b.identityKind !== "loose") return 1;
5132
- if (b.identityKind === "loose" && a.identityKind !== "loose") return -1;
5133
- return (b.lastActivity ?? 0) - (a.lastActivity ?? 0);
5134
- });
5135
- }
5136
5145
  var MANIFESTS = [
5137
5146
  "package.json",
5138
5147
  "Cargo.toml",
@@ -5145,6 +5154,23 @@ var MANIFESTS = [
5145
5154
  var PARSEABLE_MANIFESTS = ["package.json", "Cargo.toml", "pyproject.toml"];
5146
5155
  var LOOSE_DIRS = /* @__PURE__ */ new Set(["/tmp", "/private/tmp"]);
5147
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
+ }
5148
5174
  function normalizeGitRemote(url) {
5149
5175
  if (!url) return null;
5150
5176
  let value = url.trim().replace(/\.git$/, "");
@@ -5275,15 +5301,53 @@ function parseManifestName(file, text) {
5275
5301
  }
5276
5302
  return null;
5277
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
+ }
5278
5341
  function createProjectScopeMatcher(queryPath, fs = realFs) {
5342
+ const identity = computeIdentity(queryPath, fs);
5279
5343
  return {
5280
- identityKey: computeIdentity(queryPath, fs).key,
5344
+ identity: { kind: identity.kind, key: identity.key },
5281
5345
  path: normalizeScopePath(queryPath)
5282
5346
  };
5283
5347
  }
5284
5348
  function matchesProjectScope(session, scope) {
5285
5349
  if (!session.directory) return false;
5286
- if (session.project_identity?.key === scope.identityKey) return true;
5350
+ if (matchesProjectIdentity(session.project_identity, scope.identity)) return true;
5287
5351
  return isPathScopeMatch(scope.path, session.directory);
5288
5352
  }
5289
5353
  function filterSessionsByProjectScope(sessions, queryPath, fs) {
@@ -6892,30 +6956,6 @@ function ensureSchema(db, dbPath) {
6892
6956
  setCacheSchemaVersion(db);
6893
6957
  }
6894
6958
  }
6895
- function shouldBulkSyncSearchIndex(options, changedCount) {
6896
- if (options.isBulk != null) {
6897
- return options.isBulk;
6898
- }
6899
- const threshold = options.bulkThreshold ?? SEARCH_INDEX_BULK_SYNC_THRESHOLD;
6900
- return threshold > 0 && changedCount >= threshold;
6901
- }
6902
- function sessionContentHash(session) {
6903
- return JSON.stringify([
6904
- session.slug,
6905
- session.title,
6906
- session.directory,
6907
- session.time_created,
6908
- session.time_updated ?? session.time_created,
6909
- session.stats.message_count,
6910
- session.stats.total_input_tokens,
6911
- session.stats.total_output_tokens,
6912
- session.stats.total_cache_read_tokens ?? 0,
6913
- session.stats.total_cache_create_tokens ?? 0,
6914
- session.stats.total_cost,
6915
- session.stats.cost_source ?? "",
6916
- session.stats.total_tokens ?? 0
6917
- ]);
6918
- }
6919
6959
  function escapeFtsTerm(value) {
6920
6960
  return value.replaceAll('"', '""');
6921
6961
  }
@@ -6938,9 +6978,7 @@ function splitSearchTokens(input) {
6938
6978
  }
6939
6979
  token += char;
6940
6980
  }
6941
- if (token) {
6942
- tokens.push(token);
6943
- }
6981
+ if (token) tokens.push(token);
6944
6982
  return tokens;
6945
6983
  }
6946
6984
  function unwrapSearchValue(value) {
@@ -7000,7 +7038,10 @@ function parseSearchQuery(input) {
7000
7038
  if (key === "agent") filters.agent = value.toLowerCase();
7001
7039
  else if (key === "project") filters.project = value;
7002
7040
  else if (key === "projectkey" || key === "project-key") filters.projectKey = value;
7003
- 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;
7004
7045
  else if (key === "tool") filters.tools = appendUnique(filters.tools, value.toLowerCase());
7005
7046
  else if (key === "file" || key === "path") filters.file = value;
7006
7047
  else if (key === "kind" || key === "filekind" || key === "file-kind") {
@@ -7011,21 +7052,15 @@ function parseSearchQuery(input) {
7011
7052
  }
7012
7053
  } else if (key === "tag" || key === "signal") {
7013
7054
  const tag = value.toLowerCase();
7014
- if (isSmartTag(tag)) {
7015
- filters.tags = appendUnique(filters.tags, tag);
7016
- } else {
7017
- consumed = false;
7018
- }
7055
+ if (isSmartTag(tag)) filters.tags = appendUnique(filters.tags, tag);
7056
+ else consumed = false;
7019
7057
  } else if (key === "cost") {
7020
7058
  parseCostQualifier(value, filters);
7021
7059
  } else {
7022
7060
  consumed = false;
7023
7061
  }
7024
- if (consumed) {
7025
- hasQualifiers = true;
7026
- } else {
7027
- textTokens.push(token);
7028
- }
7062
+ if (consumed) hasQualifiers = true;
7063
+ else textTokens.push(token);
7029
7064
  }
7030
7065
  return {
7031
7066
  text: textTokens.join(" ").trim(),
@@ -7035,18 +7070,75 @@ function parseSearchQuery(input) {
7035
7070
  }
7036
7071
  function toFtsQuery(input) {
7037
7072
  const tokens = splitSearchTokens(input);
7038
- const mapped = tokens.map((token) => {
7039
- if (/^OR$/i.test(token)) {
7040
- return "OR";
7041
- }
7073
+ return tokens.map((token) => {
7074
+ if (/^OR$/i.test(token)) return "OR";
7042
7075
  if (token.startsWith('"') && token.endsWith('"')) {
7043
7076
  return `"${escapeFtsTerm(token.slice(1, -1))}"`;
7044
7077
  }
7045
7078
  return `"${escapeFtsTerm(token)}"`;
7046
7079
  }).filter(
7047
7080
  (token, index, values) => token !== "OR" || index > 0 && index < values.length - 1 && values[index - 1] !== "OR" && values[index + 1] !== "OR"
7048
- );
7049
- 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);
7050
7142
  }
7051
7143
  function loadSearchIndexEntry(agentName, change, loadSessionData) {
7052
7144
  try {
@@ -7222,20 +7314,15 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
7222
7314
  const existingRows = db.prepare(
7223
7315
  "SELECT session_id, content_hash FROM session_documents WHERE agent_name = ? ORDER BY id"
7224
7316
  ).all(agentName);
7225
- const existingMap = new Map(
7226
- existingRows.map((row) => [String(row.session_id), String(row.content_hash ?? "")])
7227
- );
7228
7317
  const sessionSortIndexMap = new Map(sessions.map((session, index) => [session.id, index]));
7229
7318
  const messageCountRows = db.prepare(
7230
7319
  "SELECT session_id, COUNT(*) AS value FROM messages WHERE agent_name = ? GROUP BY session_id"
7231
7320
  ).all(agentName);
7232
- const messageCountMap = new Map(
7233
- messageCountRows.map((row) => [String(row.session_id), Number(row.value ?? 0)])
7234
- );
7321
+ const searchIndexState = searchIndexStateFromRows(existingRows, messageCountRows);
7235
7322
  const sessionMap = new Map(sessions.map((session) => [session.id, session]));
7236
7323
  const toDelete = existingRows.map((row) => String(row.session_id)).filter((sessionId) => !sessionMap.has(sessionId));
7237
7324
  const toUpsert = sessions.filter(
7238
- (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
7239
7326
  );
7240
7327
  const changedCount = toDelete.length + toUpsert.length;
7241
7328
  const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
@@ -7293,17 +7380,14 @@ function syncSessionSearchIndexChanges(agentName, changes, removedSessionIds, lo
7293
7380
  return withCacheDb((db) => {
7294
7381
  ensureFtsConsistency(db);
7295
7382
  const startedAt = performance.now();
7296
- const getIndexedRow = db.prepare(
7297
- "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)
7298
7387
  );
7299
- const getMessageCount = db.prepare(
7300
- "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
7301
7390
  );
7302
- const toUpsert = changes.filter(({ session }) => {
7303
- const indexed = getIndexedRow.get(agentName, session.id);
7304
- const messageCount = getMessageCount.get(agentName, session.id);
7305
- return String(indexed?.content_hash ?? "") !== sessionContentHash(session) || Number(messageCount?.value ?? 0) !== session.stats.message_count;
7306
- });
7307
7391
  const uniqueRemovedSessionIds = Array.from(new Set(removedSessionIds));
7308
7392
  const changedCount = uniqueRemovedSessionIds.length + toUpsert.length;
7309
7393
  const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
@@ -7354,6 +7438,7 @@ function mergeSearchQueryOptions(query, options) {
7354
7438
  ...options,
7355
7439
  agent: options.agent ?? parsed2.filters.agent,
7356
7440
  project: options.project ?? parsed2.filters.project,
7441
+ projectKind: options.projectKind ?? parsed2.filters.projectKind,
7357
7442
  projectKey: options.projectKey ?? parsed2.filters.projectKey,
7358
7443
  cwd: options.cwd ?? parsed2.filters.cwd,
7359
7444
  tags: mergeSearchLists(options.tags, parsed2.filters.tags),
@@ -7389,13 +7474,20 @@ function buildSessionSearchFilters(options) {
7389
7474
  clauses.push("s.agent_name = ?");
7390
7475
  params.push(options.agent);
7391
7476
  }
7392
- if (options.projectKey) {
7393
- clauses.push("s.project_identity_key = ?");
7394
- 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
+ }
7395
7484
  }
7396
7485
  if (options.cwd) {
7397
- clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
7398
- 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));
7399
7491
  }
7400
7492
  if (options.project) {
7401
7493
  clauses.push(
@@ -7640,10 +7732,13 @@ function searchSessions(query, options = {}) {
7640
7732
  }
7641
7733
  function fileActivityFilters(options) {
7642
7734
  const path2 = options.path ? normalizeFilePathSearch(options.path) : "";
7735
+ const cwdIdentity = options.cwd ? computeIdentity(options.cwd, realFs) : null;
7643
7736
  return {
7737
+ projectKind: options.projectKind ?? null,
7644
7738
  projectKey: options.projectKey ?? null,
7645
7739
  projectLike: options.project ? likePattern(options.project) : null,
7646
- cwdKey: options.cwd ? computeIdentity(options.cwd, realFs).key : null,
7740
+ cwdKind: cwdIdentity?.kind ?? null,
7741
+ cwdKey: cwdIdentity?.key ?? null,
7647
7742
  cwdLike: options.cwd ? likePattern(options.cwd) : null,
7648
7743
  path: path2,
7649
7744
  pathLike: path2 ? likePattern(path2) : null
@@ -7672,9 +7767,13 @@ function buildFileActivityWhere(options) {
7672
7767
  clauses.push("fa.session_id = ?");
7673
7768
  params.push(options.sessionId);
7674
7769
  }
7675
- if (filters.projectKey != null) {
7676
- clauses.push("fa.project_identity_key = ?");
7677
- 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
+ }
7678
7777
  }
7679
7778
  if (filters.projectLike != null) {
7680
7779
  clauses.push(
@@ -7683,8 +7782,10 @@ function buildFileActivityWhere(options) {
7683
7782
  params.push(filters.projectLike, filters.projectLike, filters.projectLike);
7684
7783
  }
7685
7784
  if (filters.cwdKey != null) {
7686
- clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
7687
- 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);
7688
7789
  }
7689
7790
  if (filters.pathLike != null) {
7690
7791
  const pathQuery = filePathFtsQuery(filters.path);
@@ -7782,6 +7883,7 @@ function searchFileActivitySessions(query, options = {}) {
7782
7883
  if (!path2) return [];
7783
7884
  const rows = listFileActivity({
7784
7885
  agent: search.options.agent,
7886
+ projectKind: search.options.projectKind,
7785
7887
  projectKey: search.options.projectKey,
7786
7888
  project: search.options.project,
7787
7889
  cwd: search.options.cwd,
@@ -8258,9 +8360,7 @@ function sessionSignature(session) {
8258
8360
  ]);
8259
8361
  }
8260
8362
  function sortSessions(sessions) {
8261
- return [...sessions].sort(
8262
- (a, b) => (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created)
8263
- );
8363
+ return sortSessionsByActivity(sessions);
8264
8364
  }
8265
8365
  function computeSessionDiff(cachedSessions, updatedSessions, changedIds = [], signature = sessionSignature) {
8266
8366
  const cachedMap = new Map(cachedSessions.map((session) => [session.id, session]));
@@ -8974,10 +9074,11 @@ function buildDashboard(sessions, options) {
8974
9074
  for (const session of sessions) {
8975
9075
  const agentName = getSessionAgentName(session);
8976
9076
  if (scope.agent && agentName !== scope.agent) continue;
8977
- if (scope.projectKey) {
9077
+ if (scope.projectKind || scope.projectKey) {
8978
9078
  const identity = session.project_identity;
8979
- if (!identity || identity.key !== scope.projectKey) continue;
8980
- 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
+ }
8981
9082
  }
8982
9083
  const activity = getSessionActivityTime(session);
8983
9084
  if (from != null && activity < from) continue;
@@ -9076,6 +9177,90 @@ function buildDashboard(sessions, options) {
9076
9177
  recentSessions
9077
9178
  };
9078
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
+ }
9079
9264
 
9080
9265
  export {
9081
9266
  registerAgent,
@@ -9125,9 +9310,12 @@ export {
9125
9310
  isSqliteAvailable,
9126
9311
  fallbackDisplayName,
9127
9312
  realFs,
9128
- buildProjectGroups,
9313
+ isProjectIdentityKind,
9314
+ getProjectIdentityKey,
9315
+ matchesProjectIdentity,
9129
9316
  normalizeGitRemote,
9130
9317
  computeIdentity,
9318
+ buildProjectGroups,
9131
9319
  createProjectScopeMatcher,
9132
9320
  matchesProjectScope,
9133
9321
  filterSessionsByProjectScope,
@@ -9176,6 +9364,7 @@ export {
9176
9364
  getSessionActivityTime,
9177
9365
  toLocalDateKey,
9178
9366
  startOfLocalDay,
9179
- buildDashboard
9367
+ buildDashboard,
9368
+ executeSessionSearch
9180
9369
  };
9181
- //# sourceMappingURL=chunk-GCOAE7KI.js.map
9370
+ //# sourceMappingURL=chunk-BV65IEWZ.js.map