@cookielab.io/klovi 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -38,6 +38,69 @@ function epochSecondsToIso(epochSeconds) {
38
38
  }
39
39
  var MS_PER_SECOND = 1000;
40
40
 
41
+ // ../../packages/plugin-core/src/jsonl-stream.ts
42
+ import { FileSystem } from "@effect/platform";
43
+ import { Effect, Ref, Stream } from "effect";
44
+ function parseAndVisit(line, lineIndex, visitor, onMalformed) {
45
+ if (!line.trim()) {
46
+ return;
47
+ }
48
+ const lineNumber = lineIndex + 1;
49
+ try {
50
+ const parsed = JSON.parse(line);
51
+ return visitor({ parsed, line, lineIndex, lineNumber });
52
+ } catch (error) {
53
+ onMalformed?.(line, lineNumber, error);
54
+ return;
55
+ }
56
+ }
57
+ function processLine(line, state) {
58
+ return Effect.gen(function* () {
59
+ if (yield* Ref.get(state.bailedRef)) {
60
+ return;
61
+ }
62
+ const lineIndex = yield* Ref.getAndUpdate(state.indexRef, (n) => n + 1);
63
+ const result = parseAndVisit(line, lineIndex, state.visitor, state.onMalformed);
64
+ if (result === false) {
65
+ yield* Ref.set(state.bailedRef, true);
66
+ }
67
+ });
68
+ }
69
+ function streamJsonlHead(filePath, visitor, options = {}) {
70
+ return Effect.gen(function* () {
71
+ const fs = yield* FileSystem.FileSystem;
72
+ const indexRef = yield* Ref.make(0);
73
+ const bailedRef = yield* Ref.make(false);
74
+ const chunkSize = options.chunkSize ?? DEFAULT_HEAD_CHUNK_SIZE;
75
+ const baseStream = fs.stream(filePath, { chunkSize });
76
+ const linesStream = baseStream.pipe(Stream.decodeText("utf-8"), Stream.splitLines);
77
+ const cappedStream = options.maxLines === undefined ? linesStream : linesStream.pipe(Stream.take(options.maxLines));
78
+ const state = {
79
+ visitor,
80
+ indexRef,
81
+ bailedRef,
82
+ onMalformed: options.onMalformed
83
+ };
84
+ yield* cappedStream.pipe(Stream.takeUntilEffect(() => Ref.get(bailedRef)), Stream.runForEach((line) => processLine(line, state)));
85
+ });
86
+ }
87
+ function streamJsonl(filePath, visitor, options = {}) {
88
+ return Effect.gen(function* () {
89
+ const fs = yield* FileSystem.FileSystem;
90
+ const indexRef = yield* Ref.make(0);
91
+ const chunkSize = options.chunkSize ?? DEFAULT_FULL_CHUNK_SIZE;
92
+ yield* fs.stream(filePath, { chunkSize }).pipe(Stream.decodeText("utf-8"), Stream.splitLines, Stream.runForEach((line) => Effect.gen(function* () {
93
+ const lineIndex = yield* Ref.getAndUpdate(indexRef, (n) => n + 1);
94
+ parseAndVisit(line, lineIndex, visitor, options.onMalformed);
95
+ })));
96
+ });
97
+ }
98
+ var KILOBYTE_BYTES = 1024, DEFAULT_HEAD_CHUNK_KILOBYTES = 8, DEFAULT_HEAD_CHUNK_SIZE, DEFAULT_FULL_CHUNK_KILOBYTES = 64, DEFAULT_FULL_CHUNK_SIZE;
99
+ var init_jsonl_stream = __esm(() => {
100
+ DEFAULT_HEAD_CHUNK_SIZE = DEFAULT_HEAD_CHUNK_KILOBYTES * KILOBYTE_BYTES;
101
+ DEFAULT_FULL_CHUNK_SIZE = DEFAULT_FULL_CHUNK_KILOBYTES * KILOBYTE_BYTES;
102
+ });
103
+
41
104
  // ../../packages/plugin-core/src/plugin-config.ts
42
105
  import { Context } from "effect";
43
106
  var PluginConfig;
@@ -56,8 +119,8 @@ var init_plugin_errors = __esm(() => {
56
119
 
57
120
  // ../../packages/plugin-core/src/resolve-worktree.ts
58
121
  import { join } from "node:path";
59
- import { FileSystem } from "@effect/platform";
60
- import { Effect } from "effect";
122
+ import { FileSystem as FileSystem2 } from "@effect/platform";
123
+ import { Effect as Effect2 } from "effect";
61
124
  function stripT3CodeSuffix(path) {
62
125
  if (!T3CODE_SUFFIX_REGEX.test(path)) {
63
126
  return null;
@@ -68,14 +131,14 @@ function stripT3CodeSuffix(path) {
68
131
  return { path: parentPath, projectName };
69
132
  }
70
133
  function resolveGitWorktree(worktreePath) {
71
- return Effect.gen(function* () {
72
- const fs = yield* FileSystem.FileSystem;
134
+ return Effect2.gen(function* () {
135
+ const fs = yield* FileSystem2.FileSystem;
73
136
  const dotGitPath = join(worktreePath, ".git");
74
- const info = yield* fs.stat(dotGitPath).pipe(Effect.catchAll(() => Effect.succeed(null)));
137
+ const info = yield* fs.stat(dotGitPath).pipe(Effect2.catchAll(() => Effect2.succeed(null)));
75
138
  if (!info || info.type !== "File") {
76
139
  return worktreePath;
77
140
  }
78
- const content = yield* fs.readFileString(dotGitPath).pipe(Effect.catchAll(() => Effect.succeed("")));
141
+ const content = yield* fs.readFileString(dotGitPath).pipe(Effect2.catchAll(() => Effect2.succeed("")));
79
142
  if (!content) {
80
143
  return worktreePath;
81
144
  }
@@ -132,7 +195,7 @@ function buildNameMap(projects, t3codeProjects) {
132
195
  return nameToResolvedPaths;
133
196
  }
134
197
  function resolveEntry(entry, nameMap) {
135
- return Effect.gen(function* () {
198
+ return Effect2.gen(function* () {
136
199
  const candidates = nameMap.get(entry.projectName);
137
200
  if (!candidates || candidates.length === 0) {
138
201
  return;
@@ -151,7 +214,7 @@ function resolveEntry(entry, nameMap) {
151
214
  });
152
215
  }
153
216
  function resolveT3CodePaths(projects) {
154
- return Effect.gen(function* () {
217
+ return Effect2.gen(function* () {
155
218
  const t3codeEntries = collectT3CodeEntries(projects);
156
219
  if (t3codeEntries.length === 0) {
157
220
  return;
@@ -186,7 +249,7 @@ function parseSessionId(sessionId) {
186
249
  var SESSION_ID_SEPARATOR = "::";
187
250
 
188
251
  // ../../packages/plugin-core/src/plugin-registry.ts
189
- import { Effect as Effect2, Layer } from "effect";
252
+ import { Effect as Effect3, Layer } from "effect";
190
253
  function encodeResolvedPath(resolvedPath) {
191
254
  if (resolvedPath.startsWith("/")) {
192
255
  return resolvedPath.replace(/\//gu, "-");
@@ -224,13 +287,22 @@ class PluginRegistry {
224
287
  getAllPlugins() {
225
288
  return [...this.plugins.values()].map((entry) => entry.plugin);
226
289
  }
227
- discoverAllProjects() {
228
- return Effect2.gen(this, function* () {
229
- const allProjects = [];
230
- for (const { plugin, configLayer } of this.plugins.values()) {
231
- const projects = yield* plugin.discoverProjects.pipe(Effect2.provide(configLayer), Effect2.catchAll(() => Effect2.succeed([])));
232
- allProjects.push(...projects);
233
- }
290
+ discoverPluginStates(includeSessions) {
291
+ return Effect3.gen(this, function* () {
292
+ const entries = [...this.plugins.values()];
293
+ return yield* Effect3.forEach(entries, (entry) => Effect3.gen(function* () {
294
+ const discoveredIndex = includeSessions && entry.plugin.discoverIndex ? yield* entry.plugin.discoverIndex.pipe(Effect3.provide(entry.configLayer), Effect3.catchAll(() => Effect3.succeed(undefined))) : undefined;
295
+ const projects = discoveredIndex?.projects ?? (yield* entry.plugin.discoverProjects.pipe(Effect3.provide(entry.configLayer), Effect3.catchAll(() => Effect3.succeed([]))));
296
+ return {
297
+ entry,
298
+ projects,
299
+ ...discoveredIndex ? { sessionsByNativeId: discoveredIndex.sessionsByNativeId } : {}
300
+ };
301
+ }), { concurrency: "unbounded" });
302
+ });
303
+ }
304
+ mergeProjects(allProjects) {
305
+ return Effect3.gen(function* () {
234
306
  yield* resolveT3CodePaths(allProjects);
235
307
  const projectsByPath = new Map;
236
308
  for (const project of allProjects) {
@@ -260,20 +332,59 @@ class PluginRegistry {
260
332
  return merged;
261
333
  });
262
334
  }
335
+ encodeSessions(pluginId, sessions) {
336
+ return sessions.map((session) => ({
337
+ ...session,
338
+ sessionId: this.sessionIdEncoder(pluginId, session.sessionId),
339
+ pluginId
340
+ }));
341
+ }
342
+ loadSourceSessions(state, source) {
343
+ const discoveredSessions = state.sessionsByNativeId?.get(source.nativeId);
344
+ if (discoveredSessions) {
345
+ return Effect3.succeed(this.encodeSessions(source.pluginId, discoveredSessions));
346
+ }
347
+ return state.entry.plugin.listSessions(source.nativeId).pipe(Effect3.provide(state.entry.configLayer), Effect3.catchAll(() => Effect3.succeed([])), Effect3.map((sessions) => this.encodeSessions(source.pluginId, sessions)));
348
+ }
349
+ discoverAllProjects() {
350
+ return Effect3.gen(this, function* () {
351
+ const states = yield* this.discoverPluginStates(false);
352
+ return yield* this.mergeProjects(states.flatMap((state) => state.projects));
353
+ });
354
+ }
355
+ discoverAllProjectsWithSessions() {
356
+ return Effect3.gen(this, function* () {
357
+ const states = yield* this.discoverPluginStates(true);
358
+ const mergedProjects = yield* this.mergeProjects(states.flatMap((state) => state.projects));
359
+ const statesByPluginId = new Map(states.map((state) => [state.entry.plugin.id, state]));
360
+ const sessionsByEncodedPath = new Map;
361
+ for (const project of mergedProjects) {
362
+ const allSessions = [];
363
+ for (const source of project.sources) {
364
+ const state = statesByPluginId.get(source.pluginId);
365
+ if (!state) {
366
+ continue;
367
+ }
368
+ allSessions.push(...yield* this.loadSourceSessions(state, source));
369
+ }
370
+ sortByIsoDesc(allSessions, (session) => session.timestamp);
371
+ sessionsByEncodedPath.set(project.encodedPath, allSessions);
372
+ }
373
+ return {
374
+ projects: mergedProjects,
375
+ sessionsByEncodedPath
376
+ };
377
+ });
378
+ }
263
379
  listAllSessions(project) {
264
- return Effect2.gen(this, function* () {
380
+ return Effect3.gen(this, function* () {
265
381
  const allSessions = [];
266
382
  for (const source of project.sources) {
267
383
  const entry = this.plugins.get(source.pluginId);
268
384
  if (!entry) {
269
385
  continue;
270
386
  }
271
- const sessions = yield* entry.plugin.listSessions(source.nativeId).pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed([])));
272
- allSessions.push(...sessions.map((session) => ({
273
- ...session,
274
- sessionId: this.sessionIdEncoder(source.pluginId, session.sessionId),
275
- pluginId: source.pluginId
276
- })));
387
+ allSessions.push(...yield* entry.plugin.listSessions(source.nativeId).pipe(Effect3.provide(entry.configLayer), Effect3.catchAll(() => Effect3.succeed([])), Effect3.map((sessions) => this.encodeSessions(source.pluginId, sessions))));
277
388
  }
278
389
  sortByIsoDesc(allSessions, (session) => session.timestamp);
279
390
  return allSessions;
@@ -304,6 +415,7 @@ var init_sqlite_service = __esm(() => {
304
415
 
305
416
  // ../../packages/plugin-core/src/index.ts
306
417
  var init_src = __esm(() => {
418
+ init_jsonl_stream();
307
419
  init_plugin_config();
308
420
  init_plugin_errors();
309
421
  init_plugin_registry();
@@ -313,18 +425,18 @@ var init_src = __esm(() => {
313
425
 
314
426
  // ../../packages/plugin-opencode/src/db.ts
315
427
  import { join as join2 } from "node:path";
316
- import { FileSystem as FileSystem2 } from "@effect/platform";
317
- import { Effect as Effect3 } from "effect";
428
+ import { FileSystem as FileSystem3 } from "@effect/platform";
429
+ import { Effect as Effect4 } from "effect";
318
430
  function getOpenCodeDbPath() {
319
- return Effect3.gen(function* () {
431
+ return Effect4.gen(function* () {
320
432
  const config = yield* PluginConfig;
321
433
  return join2(config.dataDir, "opencode.db");
322
434
  });
323
435
  }
324
436
  function openOpenCodeDb() {
325
- return Effect3.gen(function* () {
437
+ return Effect4.gen(function* () {
326
438
  const dbPath = yield* getOpenCodeDbPath();
327
- const fs = yield* FileSystem2.FileSystem;
439
+ const fs = yield* FileSystem3.FileSystem;
328
440
  const exists = yield* fs.exists(dbPath);
329
441
  if (!exists) {
330
442
  return null;
@@ -347,7 +459,7 @@ function tryParseJson(value) {
347
459
  }
348
460
 
349
461
  // ../../packages/plugin-opencode/src/discovery.ts
350
- import { Effect as Effect4 } from "effect";
462
+ import { Effect as Effect5 } from "effect";
351
463
  function getColumns(db, tableName) {
352
464
  const rows = db.query(`PRAGMA table_info(${tableName})`).all();
353
465
  return new Set(rows.map((r) => r.name));
@@ -368,7 +480,7 @@ function inspectSchema(db) {
368
480
  };
369
481
  }
370
482
  function discoverOpenCodeProjects() {
371
- return Effect4.gen(function* () {
483
+ return Effect5.gen(function* () {
372
484
  const db = yield* openOpenCodeDb();
373
485
  if (!db) {
374
486
  return [];
@@ -459,7 +571,7 @@ function sessionRowToSummary(db, row) {
459
571
  };
460
572
  }
461
573
  function listOpenCodeSessions(nativeId) {
462
- return Effect4.gen(function* () {
574
+ return Effect5.gen(function* () {
463
575
  const db = yield* openOpenCodeDb();
464
576
  if (!db) {
465
577
  return [];
@@ -526,7 +638,7 @@ var init_discovery = __esm(() => {
526
638
  });
527
639
 
528
640
  // ../../packages/plugin-opencode/src/parser.ts
529
- import { Effect as Effect5 } from "effect";
641
+ import { Effect as Effect6 } from "effect";
530
642
  function partToContentBlock(partData, nextToolUseId) {
531
643
  switch (partData.type) {
532
644
  case "text": {
@@ -735,7 +847,7 @@ function loadSessionFromDb(db, nativeId, sessionId) {
735
847
  return { sessionId, project, turns, pluginId: "opencode" };
736
848
  }
737
849
  function loadOpenCodeSession(nativeId, sessionId) {
738
- return Effect5.gen(function* () {
850
+ return Effect6.gen(function* () {
739
851
  const db = yield* openOpenCodeDb();
740
852
  if (!db) {
741
853
  return emptySession(nativeId, sessionId);
@@ -770,12 +882,12 @@ var init_config = __esm(() => {
770
882
  });
771
883
 
772
884
  // ../../packages/plugin-opencode/src/runtime/bun-sqlite.ts
773
- import { Effect as Effect6, Layer as Layer3 } from "effect";
885
+ import { Effect as Effect7, Layer as Layer3 } from "effect";
774
886
  var bunSqliteClient, BunSqliteLayer;
775
887
  var init_bun_sqlite = __esm(() => {
776
888
  init_src();
777
889
  bunSqliteClient = {
778
- open: (dbPath, options) => Effect6.try({
890
+ open: (dbPath, options) => Effect7.try({
779
891
  try: () => {
780
892
  const sqlite = __require("bun:sqlite");
781
893
  return new sqlite.Database(dbPath, {
@@ -783,18 +895,18 @@ var init_bun_sqlite = __esm(() => {
783
895
  });
784
896
  },
785
897
  catch: () => null
786
- }).pipe(Effect6.catchAll(() => Effect6.succeed(null)))
898
+ }).pipe(Effect7.catchAll(() => Effect7.succeed(null)))
787
899
  };
788
900
  BunSqliteLayer = Layer3.succeed(SqliteClientTag, bunSqliteClient);
789
901
  });
790
902
 
791
903
  // ../../packages/plugin-opencode/src/runtime/node-sqlite.ts
792
- import { Effect as Effect7, Layer as Layer4 } from "effect";
904
+ import { Effect as Effect8, Layer as Layer4 } from "effect";
793
905
  var nodeSqliteClient, NodeSqliteLayer;
794
906
  var init_node_sqlite = __esm(() => {
795
907
  init_src();
796
908
  nodeSqliteClient = {
797
- open: (dbPath, options) => Effect7.tryPromise({
909
+ open: (dbPath, options) => Effect8.tryPromise({
798
910
  try: async () => {
799
911
  const sqlite = await import("node:sqlite");
800
912
  const db = new sqlite.DatabaseSync(dbPath, {
@@ -815,15 +927,15 @@ var init_node_sqlite = __esm(() => {
815
927
  };
816
928
  },
817
929
  catch: () => null
818
- }).pipe(Effect7.catchAll(() => Effect7.succeed(null)))
930
+ }).pipe(Effect8.catchAll(() => Effect8.succeed(null)))
819
931
  };
820
932
  NodeSqliteLayer = Layer4.succeed(SqliteClientTag, nodeSqliteClient);
821
933
  });
822
934
 
823
935
  // ../../packages/plugin-opencode/src/index.ts
824
936
  import { join as join4 } from "node:path";
825
- import { FileSystem as FileSystem3 } from "@effect/platform";
826
- import { Effect as Effect8 } from "effect";
937
+ import { FileSystem as FileSystem4 } from "@effect/platform";
938
+ import { Effect as Effect9 } from "effect";
827
939
  var openCodePlugin;
828
940
  var init_src2 = __esm(() => {
829
941
  init_src();
@@ -836,24 +948,24 @@ var init_src2 = __esm(() => {
836
948
  id: "opencode",
837
949
  displayName: "OpenCode",
838
950
  getDefaultDataDir: () => null,
839
- isDataAvailable: Effect8.gen(function* () {
951
+ isDataAvailable: Effect9.gen(function* () {
840
952
  const config = yield* PluginConfig;
841
- const fs = yield* FileSystem3.FileSystem;
842
- return yield* fs.exists(join4(config.dataDir, "opencode.db")).pipe(Effect8.catchAll(() => Effect8.succeed(false)));
953
+ const fs = yield* FileSystem4.FileSystem;
954
+ return yield* fs.exists(join4(config.dataDir, "opencode.db")).pipe(Effect9.catchAll(() => Effect9.succeed(false)));
843
955
  }),
844
- discoverProjects: discoverOpenCodeProjects().pipe(Effect8.catchAll((err) => Effect8.fail(new PluginError({
956
+ discoverProjects: discoverOpenCodeProjects().pipe(Effect9.catchAll((err) => Effect9.fail(new PluginError({
845
957
  pluginId: "opencode",
846
958
  operation: "discoverProjects",
847
959
  message: String(err),
848
960
  cause: err
849
961
  })))),
850
- listSessions: (nativeId) => listOpenCodeSessions(nativeId).pipe(Effect8.catchAll((err) => Effect8.fail(new PluginError({
962
+ listSessions: (nativeId) => listOpenCodeSessions(nativeId).pipe(Effect9.catchAll((err) => Effect9.fail(new PluginError({
851
963
  pluginId: "opencode",
852
964
  operation: "listSessions",
853
965
  message: String(err),
854
966
  cause: err
855
967
  })))),
856
- loadSession: (nativeId, sessionId) => loadOpenCodeSession(nativeId, sessionId).pipe(Effect8.catchAll((err) => Effect8.fail(new PluginError({
968
+ loadSession: (nativeId, sessionId) => loadOpenCodeSession(nativeId, sessionId).pipe(Effect9.catchAll((err) => Effect9.fail(new PluginError({
857
969
  pluginId: "opencode",
858
970
  operation: "loadSession",
859
971
  message: String(err),
@@ -881,9 +993,9 @@ var init_platform_node = __esm(() => {
881
993
  import { execFile } from "node:child_process";
882
994
 
883
995
  // ../../packages/server/src/effect/bootstrap.ts
884
- import { join as join14 } from "node:path";
996
+ import { join as join15 } from "node:path";
885
997
  import { HttpServer } from "@effect/platform";
886
- import { Cause, Effect as Effect32, Fiber, Layer as Layer8 } from "effect";
998
+ import { Cause, Effect as Effect33, Fiber, Layer as Layer8 } from "effect";
887
999
 
888
1000
  // ../../packages/server/src/effect/platform-bun.ts
889
1001
  init_src2();
@@ -899,20 +1011,20 @@ class ServerConfig extends Context3.Tag("@klovi/ServerConfig")() {
899
1011
  }
900
1012
 
901
1013
  // ../../packages/server/src/effect/server-services.ts
902
- import { Context as Context4, Effect as Effect31, Layer as Layer6 } from "effect";
1014
+ import { Context as Context4, Effect as Effect32, Layer as Layer6 } from "effect";
903
1015
 
904
1016
  // ../../packages/server/src/services/auto-discover.ts
905
1017
  init_src();
906
- import { Effect as Effect24 } from "effect";
1018
+ import { Effect as Effect25 } from "effect";
907
1019
 
908
1020
  // ../../packages/plugin-claude-code/src/index.ts
909
1021
  init_src();
910
- import { Effect as Effect12 } from "effect";
1022
+ import { Effect as Effect13 } from "effect";
911
1023
 
912
1024
  // ../../packages/plugin-claude-code/src/discovery.ts
913
1025
  init_src();
914
1026
  import { join as join6 } from "node:path";
915
- import { Effect as Effect10 } from "effect";
1027
+ import { Effect as Effect11 } from "effect";
916
1028
 
917
1029
  // ../../packages/plugin-claude-code/src/command-message.ts
918
1030
  var COMMAND_ARGS_REGEX = /<command-args>(?<content>[\s\S]*?)<\/command-args>/u;
@@ -950,62 +1062,44 @@ function parseCommandMessage(text) {
950
1062
  // ../../packages/plugin-claude-code/src/shared/discovery-utils.ts
951
1063
  init_src();
952
1064
  import { join as join5 } from "node:path";
953
- import { FileSystem as FileSystem4 } from "@effect/platform";
954
- import { Effect as Effect9 } from "effect";
1065
+ import { FileSystem as FileSystem5 } from "@effect/platform";
1066
+ import { Effect as Effect10 } from "effect";
955
1067
  var WINDOWS_DRIVE_LETTER_REGEX = /^[A-Za-z]\//u;
1068
+ var STAT_CONCURRENCY = 32;
956
1069
  function readDirEntriesSafe(dir) {
957
- return Effect9.gen(function* () {
958
- const fs = yield* FileSystem4.FileSystem;
959
- const names = yield* fs.readDirectory(dir).pipe(Effect9.catchAll(() => Effect9.succeed([])));
960
- const entries = [];
961
- for (const name of names) {
962
- const info = yield* fs.stat(join5(dir, name)).pipe(Effect9.catchAll(() => Effect9.succeed(null)));
963
- if (info) {
964
- entries.push({ name, isDirectory: info.type === "Directory" });
965
- }
966
- }
967
- return entries;
1070
+ return Effect10.gen(function* () {
1071
+ const fs = yield* FileSystem5.FileSystem;
1072
+ const names = yield* fs.readDirectory(dir).pipe(Effect10.catchAll(() => Effect10.succeed([])));
1073
+ const entries = yield* Effect10.forEach(names, (name) => Effect10.gen(function* () {
1074
+ const info = yield* fs.stat(join5(dir, name)).pipe(Effect10.catchAll(() => Effect10.succeed(null)));
1075
+ return info ? { name, isDirectory: info.type === "Directory" } : null;
1076
+ }), { concurrency: STAT_CONCURRENCY });
1077
+ return entries.filter((entry) => entry !== null);
968
1078
  });
969
1079
  }
970
1080
  function listFilesBySuffix(dir, suffix) {
971
- return Effect9.gen(function* () {
972
- const fs = yield* FileSystem4.FileSystem;
973
- const names = yield* fs.readDirectory(dir).pipe(Effect9.catchAll(() => Effect9.succeed([])));
1081
+ return Effect10.gen(function* () {
1082
+ const fs = yield* FileSystem5.FileSystem;
1083
+ const names = yield* fs.readDirectory(dir).pipe(Effect10.catchAll(() => Effect10.succeed([])));
974
1084
  return names.filter((name) => name.endsWith(suffix));
975
1085
  });
976
1086
  }
977
1087
  function listFilesWithMtime(dir, suffix) {
978
- return Effect9.gen(function* () {
979
- const fs = yield* FileSystem4.FileSystem;
1088
+ return Effect10.gen(function* () {
1089
+ const fs = yield* FileSystem5.FileSystem;
980
1090
  const names = yield* listFilesBySuffix(dir, suffix);
981
- const results = [];
982
- for (const fileName of names) {
983
- const info = yield* fs.stat(join5(dir, fileName)).pipe(Effect9.catchAll(() => Effect9.succeed(null)));
984
- if (info?.mtime._tag === "Some") {
985
- results.push({ fileName, mtime: info.mtime.value.toISOString() });
986
- }
987
- }
1091
+ const candidates = yield* Effect10.forEach(names, (fileName) => Effect10.gen(function* () {
1092
+ const info = yield* fs.stat(join5(dir, fileName)).pipe(Effect10.catchAll(() => Effect10.succeed(null)));
1093
+ return info?.mtime._tag === "Some" ? { fileName, mtime: info.mtime.value.toISOString() } : null;
1094
+ }), { concurrency: STAT_CONCURRENCY });
1095
+ const results = candidates.filter((r) => r !== null);
988
1096
  sortByIsoDesc(results, (item) => item.mtime);
989
1097
  return results;
990
1098
  });
991
1099
  }
992
- function readTextPrefix(filePath, maxBytes) {
993
- return Effect9.gen(function* () {
994
- const fs = yield* FileSystem4.FileSystem;
995
- const bytes = yield* fs.readFile(filePath);
996
- const slice = bytes.subarray(0, maxBytes);
997
- return new TextDecoder().decode(slice);
998
- });
999
- }
1000
- function readFileText(filePath) {
1001
- return Effect9.gen(function* () {
1002
- const fs = yield* FileSystem4.FileSystem;
1003
- return yield* fs.readFileString(filePath);
1004
- });
1005
- }
1006
1100
  function fileExists(filePath) {
1007
- return Effect9.gen(function* () {
1008
- const fs = yield* FileSystem4.FileSystem;
1101
+ return Effect10.gen(function* () {
1102
+ const fs = yield* FileSystem5.FileSystem;
1009
1103
  return yield* fs.exists(filePath);
1010
1104
  });
1011
1105
  }
@@ -1020,98 +1114,65 @@ function decodeEncodedPath(encoded) {
1020
1114
  return encoded.replace(/-/gu, "/");
1021
1115
  }
1022
1116
 
1023
- // ../../packages/plugin-claude-code/src/shared/jsonl-utils.ts
1024
- function iterateJsonl(text, visitor, options = {}) {
1025
- const lines = text.split(`
1026
- `);
1027
- const start = Math.max(0, options.startAt ?? 0);
1028
- const end = options.maxLines === undefined ? lines.length : Math.min(lines.length, start + options.maxLines);
1029
- for (let i = start;i < end; i++) {
1030
- const line = lines[i];
1031
- if (!line?.trim()) {
1032
- continue;
1033
- }
1034
- try {
1035
- const parsed = JSON.parse(line);
1036
- const shouldContinue = visitor({
1037
- parsed,
1038
- line,
1039
- lineIndex: i,
1040
- lineNumber: i + 1
1041
- });
1042
- if (shouldContinue === false) {
1043
- break;
1044
- }
1045
- } catch (error) {
1046
- options.onMalformed?.(line, i + 1, error);
1047
- }
1048
- }
1049
- }
1050
-
1051
1117
  // ../../packages/plugin-claude-code/src/discovery.ts
1052
- var BYTES_PER_KB = 1024;
1053
- var CWD_SCAN_KB = 64;
1054
- var CWD_SCAN_BYTES = CWD_SCAN_KB * BYTES_PER_KB;
1055
- var SESSION_META_SCAN_BYTES = BYTES_PER_KB * BYTES_PER_KB;
1056
1118
  var BRACKETED_TEXT_REGEX = /^\[.+\]$/u;
1119
+ var PROJECT_DIR_CONCURRENCY = 16;
1120
+ var SESSION_FILE_CONCURRENCY = 16;
1057
1121
  function inspectProjectSessions(projectDir, sessionFiles) {
1058
- return Effect10.gen(function* () {
1122
+ return Effect11.gen(function* () {
1059
1123
  const lastActivity = sessionFiles[0]?.mtime || "";
1060
1124
  let resolvedPath = "";
1061
1125
  for (const sessionFile of sessionFiles) {
1062
1126
  const filePath = join6(projectDir, sessionFile.fileName);
1063
- if (!resolvedPath) {
1064
- resolvedPath = yield* extractCwd(filePath);
1127
+ resolvedPath = yield* extractCwd(filePath);
1128
+ if (resolvedPath) {
1129
+ break;
1065
1130
  }
1066
1131
  }
1067
1132
  return { lastActivity, resolvedPath };
1068
1133
  });
1069
1134
  }
1070
1135
  function discoverClaudeProjects() {
1071
- return Effect10.gen(function* () {
1136
+ return Effect11.gen(function* () {
1072
1137
  const config = yield* PluginConfig;
1073
1138
  const projectsDir = join6(config.dataDir, "projects");
1074
1139
  const entries = yield* readDirEntriesSafe(projectsDir);
1075
- const projects = [];
1076
- for (const entry of entries) {
1077
- if (!entry.isDirectory) {
1078
- continue;
1079
- }
1140
+ const directories = entries.filter((entry) => entry.isDirectory);
1141
+ const projects = yield* Effect11.forEach(directories, (entry) => Effect11.gen(function* () {
1080
1142
  const projectDir = join6(projectsDir, entry.name);
1081
1143
  const sessionFiles = yield* listFilesWithMtime(projectDir, ".jsonl");
1082
1144
  if (sessionFiles.length === 0) {
1083
- continue;
1145
+ return null;
1084
1146
  }
1085
1147
  const projectInfo = yield* inspectProjectSessions(projectDir, sessionFiles);
1086
1148
  const resolvedPath = projectInfo.resolvedPath || decodeEncodedPath(entry.name);
1087
- projects.push({
1149
+ return {
1088
1150
  pluginId: "claude-code",
1089
1151
  nativeId: entry.name,
1090
1152
  resolvedPath,
1091
1153
  displayName: resolvedPath,
1092
1154
  sessionCount: sessionFiles.length,
1093
1155
  lastActivity: projectInfo.lastActivity
1094
- });
1095
- }
1096
- sortByIsoDesc(projects, (project) => project.lastActivity);
1097
- return projects;
1156
+ };
1157
+ }), { concurrency: PROJECT_DIR_CONCURRENCY });
1158
+ const filtered = projects.filter((p) => p !== null);
1159
+ sortByIsoDesc(filtered, (project) => project.lastActivity);
1160
+ return filtered;
1098
1161
  });
1099
1162
  }
1100
1163
  var PLAN_PREFIX = "Implement the following plan";
1101
1164
  function listClaudeSessions(nativeId) {
1102
- return Effect10.gen(function* () {
1165
+ return Effect11.gen(function* () {
1103
1166
  const config = yield* PluginConfig;
1104
1167
  const projectDir = join6(config.dataDir, "projects", nativeId);
1105
1168
  const files = yield* listFilesBySuffix(projectDir, ".jsonl");
1106
- const sessions = [];
1107
- for (const file of files) {
1169
+ const candidates = yield* Effect11.forEach(files, (file) => Effect11.gen(function* () {
1108
1170
  const filePath = join6(projectDir, file);
1109
1171
  const sessionId = file.replace(".jsonl", "");
1110
1172
  const meta = yield* extractSessionMeta(filePath);
1111
- if (meta) {
1112
- sessions.push({ sessionId, pluginId: "claude-code", ...meta });
1113
- }
1114
- }
1173
+ return meta ? { sessionId, pluginId: "claude-code", ...meta } : null;
1174
+ }), { concurrency: SESSION_FILE_CONCURRENCY });
1175
+ const sessions = candidates.filter((s) => s !== null);
1115
1176
  classifySessionTypes(sessions);
1116
1177
  sortByIsoDesc(sessions, (session) => session.timestamp);
1117
1178
  return sessions;
@@ -1134,20 +1195,16 @@ function classifySessionTypes(sessions) {
1134
1195
  }
1135
1196
  }
1136
1197
  function extractCwd(filePath) {
1137
- return Effect10.gen(function* () {
1138
- const text = yield* readTextPrefix(filePath, CWD_SCAN_BYTES).pipe(Effect10.catchAll(() => Effect10.succeed("")));
1139
- if (!text) {
1140
- return "";
1141
- }
1198
+ return Effect11.gen(function* () {
1142
1199
  let cwd = "";
1143
- iterateJsonl(text, ({ parsed }) => {
1200
+ yield* streamJsonlHead(filePath, ({ parsed }) => {
1144
1201
  const obj = parsed;
1145
1202
  if (obj.cwd) {
1146
1203
  ({ cwd } = obj);
1147
1204
  return false;
1148
1205
  }
1149
1206
  return;
1150
- }, { maxLines: 20 });
1207
+ }, { maxLines: 20 }).pipe(Effect11.catchAll(() => Effect11.void));
1151
1208
  return cwd;
1152
1209
  });
1153
1210
  }
@@ -1192,11 +1249,7 @@ function processMetaLine(obj, meta) {
1192
1249
  }
1193
1250
  }
1194
1251
  function extractSessionMeta(filePath) {
1195
- return Effect10.gen(function* () {
1196
- const text = yield* readTextPrefix(filePath, SESSION_META_SCAN_BYTES).pipe(Effect10.catchAll(() => Effect10.succeed("")));
1197
- if (!text) {
1198
- return null;
1199
- }
1252
+ return Effect11.gen(function* () {
1200
1253
  const meta = {
1201
1254
  timestamp: "",
1202
1255
  slug: "",
@@ -1204,7 +1257,7 @@ function extractSessionMeta(filePath) {
1204
1257
  model: "",
1205
1258
  gitBranch: ""
1206
1259
  };
1207
- iterateJsonl(text, ({ parsed }) => {
1260
+ yield* streamJsonlHead(filePath, ({ parsed }) => {
1208
1261
  const obj = parsed;
1209
1262
  processMetaLine(obj, meta);
1210
1263
  if (isMetaComplete(meta)) {
@@ -1214,7 +1267,7 @@ function extractSessionMeta(filePath) {
1214
1267
  }, {
1215
1268
  maxLines: 50,
1216
1269
  onMalformed: () => {}
1217
- });
1270
+ }).pipe(Effect11.catchAll(() => Effect11.void));
1218
1271
  if (!(meta.timestamp && meta.firstMessage)) {
1219
1272
  return null;
1220
1273
  }
@@ -1231,10 +1284,10 @@ function extractSessionMeta(filePath) {
1231
1284
  // ../../packages/plugin-claude-code/src/parser.ts
1232
1285
  init_src();
1233
1286
  import { join as join7 } from "node:path";
1234
- import { Effect as Effect11 } from "effect";
1287
+ import { Effect as Effect12 } from "effect";
1235
1288
  var MAX_RAW_LINE_LENGTH = 500;
1236
1289
  function loadClaudeSession(nativeId, sessionId) {
1237
- return Effect11.gen(function* () {
1290
+ return Effect12.gen(function* () {
1238
1291
  const config = yield* PluginConfig;
1239
1292
  const filePath = join7(config.dataDir, "projects", nativeId, `${sessionId}.jsonl`);
1240
1293
  const { rawLines, parseErrors } = yield* readJsonlLines(filePath);
@@ -1266,10 +1319,10 @@ function loadClaudeSession(nativeId, sessionId) {
1266
1319
  });
1267
1320
  }
1268
1321
  function parseSubAgentSession(sessionId, encodedPath, agentId) {
1269
- return Effect11.gen(function* () {
1322
+ return Effect12.gen(function* () {
1270
1323
  const config = yield* PluginConfig;
1271
1324
  const filePath = join7(config.dataDir, "projects", encodedPath, sessionId, "subagents", `agent-${agentId}.jsonl`);
1272
- const parsed = yield* readJsonlLines(filePath).pipe(Effect11.catchAll(() => Effect11.succeed({ rawLines: [], parseErrors: [] })));
1325
+ const parsed = yield* readJsonlLines(filePath).pipe(Effect12.catchAll(() => Effect12.succeed({ rawLines: [], parseErrors: [] })));
1273
1326
  const subAgentMap = extractSubAgentMap(parsed.rawLines);
1274
1327
  const turns = buildTurns(parsed.rawLines, parsed.parseErrors);
1275
1328
  for (const turn of turns) {
@@ -1355,11 +1408,10 @@ function findImplSessionId(slug, sessions, currentSessionId) {
1355
1408
  return match?.sessionId;
1356
1409
  }
1357
1410
  function readJsonlLines(filePath) {
1358
- return Effect11.gen(function* () {
1359
- const text = yield* readFileText(filePath);
1411
+ return Effect12.gen(function* () {
1360
1412
  const rawLines = [];
1361
1413
  const parseErrors = [];
1362
- iterateJsonl(text, ({ parsed }) => {
1414
+ yield* streamJsonl(filePath, ({ parsed }) => {
1363
1415
  rawLines.push(parsed);
1364
1416
  }, {
1365
1417
  onMalformed: (line, lineNumber, error) => {
@@ -1664,20 +1716,20 @@ var claudeCodePlugin = {
1664
1716
  id: "claude-code",
1665
1717
  displayName: "Claude Code",
1666
1718
  getDefaultDataDir: () => null,
1667
- isDataAvailable: Effect12.gen(function* () {
1719
+ isDataAvailable: Effect13.gen(function* () {
1668
1720
  const config = yield* PluginConfig;
1669
- return yield* fileExists(config.dataDir).pipe(Effect12.catchAll(() => Effect12.succeed(false)));
1721
+ return yield* fileExists(config.dataDir).pipe(Effect13.catchAll(() => Effect13.succeed(false)));
1670
1722
  }),
1671
1723
  discoverProjects: discoverClaudeProjects(),
1672
1724
  listSessions: (nativeId) => listClaudeSessions(nativeId),
1673
- loadSession: (nativeId, sessionId) => loadClaudeSession(nativeId, sessionId).pipe(Effect12.map((r) => r.session), Effect12.catchAll((err) => Effect12.fail(new PluginError({
1725
+ loadSession: (nativeId, sessionId) => loadClaudeSession(nativeId, sessionId).pipe(Effect13.map((r) => r.session), Effect13.catchAll((err) => Effect13.fail(new PluginError({
1674
1726
  pluginId: "claude-code",
1675
1727
  operation: "loadSession",
1676
1728
  message: String(err),
1677
1729
  cause: err
1678
1730
  })))),
1679
- loadSessionDetail: (nativeId, sessionId) => Effect12.gen(function* () {
1680
- const [{ session, slug }, sessions] = yield* Effect12.all([
1731
+ loadSessionDetail: (nativeId, sessionId) => Effect13.gen(function* () {
1732
+ const [{ session, slug }, sessions] = yield* Effect13.all([
1681
1733
  loadClaudeSession(nativeId, sessionId),
1682
1734
  listClaudeSessions(nativeId)
1683
1735
  ]);
@@ -1686,7 +1738,7 @@ var claudeCodePlugin = {
1686
1738
  planSessionId: findPlanSessionId(session.turns, slug, sessions, sessionId),
1687
1739
  implSessionId: findImplSessionId(slug, sessions, sessionId)
1688
1740
  };
1689
- }).pipe(Effect12.catchAll((err) => Effect12.fail(new PluginError({
1741
+ }).pipe(Effect13.catchAll((err) => Effect13.fail(new PluginError({
1690
1742
  pluginId: "claude-code",
1691
1743
  operation: "loadSessionDetail",
1692
1744
  message: String(err),
@@ -1698,74 +1750,17 @@ var claudeCodePlugin = {
1698
1750
 
1699
1751
  // ../../packages/plugin-codex/src/index.ts
1700
1752
  init_src();
1701
- import { Effect as Effect17 } from "effect";
1753
+ import { Effect as Effect18 } from "effect";
1702
1754
 
1703
1755
  // ../../packages/plugin-codex/src/discovery.ts
1704
1756
  init_src();
1705
- import { Effect as Effect15 } from "effect";
1757
+ import { Effect as Effect16 } from "effect";
1706
1758
 
1707
1759
  // ../../packages/plugin-codex/src/session-index.ts
1708
1760
  init_src();
1709
1761
  import { join as join9 } from "node:path";
1710
1762
  import { FileSystem as FileSystem6 } from "@effect/platform";
1711
1763
  import { Effect as Effect14 } from "effect";
1712
-
1713
- // ../../packages/plugin-codex/src/shared/discovery-utils.ts
1714
- import { FileSystem as FileSystem5 } from "@effect/platform";
1715
- import { Effect as Effect13 } from "effect";
1716
- function readTextPrefix2(filePath, maxBytes) {
1717
- return Effect13.gen(function* () {
1718
- const fs = yield* FileSystem5.FileSystem;
1719
- const bytes = yield* fs.readFile(filePath);
1720
- const slice = bytes.subarray(0, maxBytes);
1721
- return new TextDecoder().decode(slice);
1722
- });
1723
- }
1724
- function readFileText2(filePath) {
1725
- return Effect13.gen(function* () {
1726
- const fs = yield* FileSystem5.FileSystem;
1727
- return yield* fs.readFileString(filePath);
1728
- });
1729
- }
1730
- function fileExists2(filePath) {
1731
- return Effect13.gen(function* () {
1732
- const fs = yield* FileSystem5.FileSystem;
1733
- return yield* fs.exists(filePath);
1734
- });
1735
- }
1736
-
1737
- // ../../packages/plugin-codex/src/shared/jsonl-utils.ts
1738
- function iterateJsonl2(text, visitor, options = {}) {
1739
- const lines = text.split(`
1740
- `);
1741
- const start = Math.max(0, options.startAt ?? 0);
1742
- const end = options.maxLines === undefined ? lines.length : Math.min(lines.length, start + options.maxLines);
1743
- for (let i = start;i < end; i++) {
1744
- const line = lines[i];
1745
- if (!line?.trim()) {
1746
- continue;
1747
- }
1748
- try {
1749
- const parsed = JSON.parse(line);
1750
- const shouldContinue = visitor({
1751
- parsed,
1752
- line,
1753
- lineIndex: i,
1754
- lineNumber: i + 1
1755
- });
1756
- if (shouldContinue === false) {
1757
- break;
1758
- }
1759
- } catch (error) {
1760
- options.onMalformed?.(line, i + 1, error);
1761
- }
1762
- }
1763
- }
1764
-
1765
- // ../../packages/plugin-codex/src/session-index.ts
1766
- var BYTES_PER_KB2 = 1024;
1767
- var FIRST_LINE_SCAN_KB = 512;
1768
- var FIRST_LINE_SCAN_BYTES = FIRST_LINE_SCAN_KB * BYTES_PER_KB2;
1769
1764
  var MS_PER_SECOND2 = 1000;
1770
1765
  function isCodexSessionMeta(obj) {
1771
1766
  return typeof obj === "object" && obj !== null && "uuid" in obj && "cwd" in obj && "timestamps" in obj && typeof obj.uuid === "string" && typeof obj.cwd === "string";
@@ -1805,50 +1800,45 @@ function extractTurnContextModel(parsed) {
1805
1800
  }
1806
1801
  return typeof event.payload?.model === "string" ? event.payload.model : null;
1807
1802
  }
1808
- function inferModelFromPrefix(prefixText) {
1809
- let model = null;
1810
- iterateJsonl2(prefixText, ({ parsed }) => {
1811
- const extracted = extractTurnContextModel(parsed);
1812
- if (isKnownModel(extracted)) {
1813
- model = extracted;
1814
- return false;
1815
- }
1816
- return;
1817
- }, { startAt: 1, maxLines: 256 });
1818
- return model;
1803
+ function streamInferredModel(filePath) {
1804
+ return Effect14.gen(function* () {
1805
+ let model = null;
1806
+ yield* streamJsonlHead(filePath, ({ parsed, lineIndex }) => {
1807
+ if (lineIndex === 0) {
1808
+ return;
1809
+ }
1810
+ const extracted = extractTurnContextModel(parsed);
1811
+ if (isKnownModel(extracted)) {
1812
+ model = extracted;
1813
+ return false;
1814
+ }
1815
+ return;
1816
+ }, { maxLines: 256 }).pipe(Effect14.catchAll(() => Effect14.void));
1817
+ return model;
1818
+ });
1819
1819
  }
1820
1820
  function parseSessionMeta(filePath, fileMtimeEpoch) {
1821
1821
  return Effect14.gen(function* () {
1822
- const prefix = yield* readTextPrefix2(filePath, FIRST_LINE_SCAN_BYTES).pipe(Effect14.catchAll(() => Effect14.succeed("")));
1823
- if (!prefix) {
1822
+ const captured = { value: null };
1823
+ yield* streamJsonlHead(filePath, ({ parsed }) => {
1824
+ captured.value = normalizeSessionMeta(parsed, fileMtimeEpoch);
1825
+ return false;
1826
+ }, { maxLines: 1 }).pipe(Effect14.catchAll(() => Effect14.void));
1827
+ const firstLineMeta = captured.value;
1828
+ if (!firstLineMeta) {
1824
1829
  return null;
1825
1830
  }
1826
- const firstNewline = prefix.indexOf(`
1827
- `);
1828
- const firstLine = firstNewline === -1 ? prefix : prefix.slice(0, firstNewline);
1829
- const trimmedFirstLine = firstLine.trim();
1830
- if (!trimmedFirstLine) {
1831
- return null;
1831
+ if (isKnownModel(firstLineMeta.model)) {
1832
+ return firstLineMeta;
1832
1833
  }
1833
- try {
1834
- const parsed = JSON.parse(trimmedFirstLine);
1835
- const meta = normalizeSessionMeta(parsed, fileMtimeEpoch);
1836
- if (!meta) {
1837
- return null;
1838
- }
1839
- if (isKnownModel(meta.model)) {
1840
- return meta;
1841
- }
1842
- const inferred = inferModelFromPrefix(prefix);
1843
- if (isKnownModel(inferred)) {
1844
- return { ...meta, model: inferred };
1845
- }
1846
- if (isKnownModel(meta.provider_id)) {
1847
- return { ...meta, model: meta.provider_id };
1848
- }
1849
- return meta;
1850
- } catch {}
1851
- return null;
1834
+ const inferred = yield* streamInferredModel(filePath);
1835
+ if (isKnownModel(inferred)) {
1836
+ return { ...firstLineMeta, model: inferred };
1837
+ }
1838
+ if (isKnownModel(firstLineMeta.provider_id)) {
1839
+ return { ...firstLineMeta, model: firstLineMeta.provider_id };
1840
+ }
1841
+ return firstLineMeta;
1852
1842
  });
1853
1843
  }
1854
1844
  function walkJsonlFiles(dir, visit) {
@@ -1934,15 +1924,57 @@ function findCodexSessionFileById(sessionId) {
1934
1924
  });
1935
1925
  }
1936
1926
 
1937
- // ../../packages/plugin-codex/src/discovery.ts
1938
- var BYTES_PER_KB3 = 1024;
1939
- var SESSION_TITLE_SCAN_KB = 256;
1940
- var SESSION_TITLE_SCAN_BYTES = SESSION_TITLE_SCAN_KB * BYTES_PER_KB3;
1941
- function discoverCodexProjects() {
1927
+ // ../../packages/plugin-codex/src/shared/discovery-utils.ts
1928
+ import { FileSystem as FileSystem7 } from "@effect/platform";
1929
+ import { Effect as Effect15 } from "effect";
1930
+ function readFileText(filePath) {
1942
1931
  return Effect15.gen(function* () {
1943
- const sessions = yield* scanCodexSessions();
1944
- const byCwd = new Map;
1945
- for (const session of sessions) {
1932
+ const fs = yield* FileSystem7.FileSystem;
1933
+ return yield* fs.readFileString(filePath);
1934
+ });
1935
+ }
1936
+ function fileExists2(filePath) {
1937
+ return Effect15.gen(function* () {
1938
+ const fs = yield* FileSystem7.FileSystem;
1939
+ return yield* fs.exists(filePath);
1940
+ });
1941
+ }
1942
+
1943
+ // ../../packages/plugin-codex/src/shared/jsonl-utils.ts
1944
+ function iterateJsonl(text, visitor, options = {}) {
1945
+ const lines = text.split(`
1946
+ `);
1947
+ const start = Math.max(0, options.startAt ?? 0);
1948
+ const end = options.maxLines === undefined ? lines.length : Math.min(lines.length, start + options.maxLines);
1949
+ for (let i = start;i < end; i++) {
1950
+ const line = lines[i];
1951
+ if (!line?.trim()) {
1952
+ continue;
1953
+ }
1954
+ try {
1955
+ const parsed = JSON.parse(line);
1956
+ const shouldContinue = visitor({
1957
+ parsed,
1958
+ line,
1959
+ lineIndex: i,
1960
+ lineNumber: i + 1
1961
+ });
1962
+ if (shouldContinue === false) {
1963
+ break;
1964
+ }
1965
+ } catch (error) {
1966
+ options.onMalformed?.(line, i + 1, error);
1967
+ }
1968
+ }
1969
+ }
1970
+
1971
+ // ../../packages/plugin-codex/src/discovery.ts
1972
+ var SESSION_FILE_CONCURRENCY2 = 16;
1973
+ function discoverCodexProjects() {
1974
+ return Effect16.gen(function* () {
1975
+ const sessions = yield* scanCodexSessions();
1976
+ const byCwd = new Map;
1977
+ for (const session of sessions) {
1946
1978
  const existing = byCwd.get(session.meta.cwd);
1947
1979
  if (existing) {
1948
1980
  existing.push(session);
@@ -1971,9 +2003,41 @@ function discoverCodexProjects() {
1971
2003
  return projects;
1972
2004
  });
1973
2005
  }
1974
- function extractFirstUserMessage(text) {
2006
+ function visitForFirstUserMessage(parsed, captured) {
2007
+ const event = parsed;
2008
+ if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item.text) {
2009
+ const maxPreviewLength = 200;
2010
+ captured.value = event.item.text.slice(0, maxPreviewLength);
2011
+ return true;
2012
+ }
2013
+ if (event.type === "event_msg" && event.payload?.type === "user_message") {
2014
+ const payloadText = event.payload.message || event.payload.text;
2015
+ if (typeof payloadText === "string" && payloadText) {
2016
+ const maxMsgLength = 200;
2017
+ captured.value = payloadText.slice(0, maxMsgLength);
2018
+ return true;
2019
+ }
2020
+ }
2021
+ return false;
2022
+ }
2023
+ function streamFirstUserMessage(filePath) {
2024
+ return Effect16.gen(function* () {
2025
+ const captured = { value: null };
2026
+ yield* streamJsonlHead(filePath, ({ parsed, lineIndex }) => {
2027
+ if (lineIndex === 0) {
2028
+ return;
2029
+ }
2030
+ if (visitForFirstUserMessage(parsed, captured)) {
2031
+ return false;
2032
+ }
2033
+ return;
2034
+ }, { maxLines: 200 }).pipe(Effect16.catchAll(() => Effect16.void));
2035
+ return captured.value;
2036
+ });
2037
+ }
2038
+ function extractFirstUserMessageFromText(text) {
1975
2039
  let message = null;
1976
- iterateJsonl2(text, ({ parsed }) => {
2040
+ iterateJsonl(text, ({ parsed }) => {
1977
2041
  const event = parsed;
1978
2042
  if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item.text) {
1979
2043
  const maxPreviewLength = 200;
@@ -1993,23 +2057,21 @@ function extractFirstUserMessage(text) {
1993
2057
  return message;
1994
2058
  }
1995
2059
  function listCodexSessions(nativeId) {
1996
- return Effect15.gen(function* () {
2060
+ return Effect16.gen(function* () {
1997
2061
  const allSessions = yield* scanCodexSessions();
1998
2062
  const matching = allSessions.filter((s) => s.meta.cwd === nativeId);
1999
- const sessions = [];
2000
- for (const s of matching) {
2063
+ const sessions = yield* Effect16.forEach(matching, (s) => Effect16.gen(function* () {
2001
2064
  let firstMessage = s.meta.name ?? "";
2002
2065
  if (!firstMessage) {
2003
- const prefix = yield* readTextPrefix2(s.filePath, SESSION_TITLE_SCAN_BYTES).pipe(Effect15.catchAll(() => Effect15.succeed("")));
2004
- firstMessage = extractFirstUserMessage(prefix) ?? "";
2066
+ firstMessage = (yield* streamFirstUserMessage(s.filePath)) ?? "";
2005
2067
  if (!firstMessage) {
2006
- const fullText = yield* readFileText2(s.filePath).pipe(Effect15.catchAll(() => Effect15.succeed("")));
2007
- firstMessage = extractFirstUserMessage(fullText) ?? "";
2068
+ const fullText = yield* readFileText(s.filePath).pipe(Effect16.catchAll(() => Effect16.succeed("")));
2069
+ firstMessage = extractFirstUserMessageFromText(fullText) ?? "";
2008
2070
  }
2009
2071
  firstMessage ||= "Codex session";
2010
2072
  }
2011
2073
  const timestamp = epochSecondsToIso(s.meta.timestamps.created);
2012
- sessions.push({
2074
+ return {
2013
2075
  sessionId: s.meta.uuid,
2014
2076
  timestamp,
2015
2077
  slug: s.meta.uuid,
@@ -2017,8 +2079,8 @@ function listCodexSessions(nativeId) {
2017
2079
  model: s.meta.model || "unknown",
2018
2080
  gitBranch: "",
2019
2081
  pluginId: "codex-cli"
2020
- });
2021
- }
2082
+ };
2083
+ }), { concurrency: SESSION_FILE_CONCURRENCY2 });
2022
2084
  sortByIsoDesc(sessions, (session) => session.timestamp);
2023
2085
  return sessions;
2024
2086
  });
@@ -2026,7 +2088,7 @@ function listCodexSessions(nativeId) {
2026
2088
 
2027
2089
  // ../../packages/plugin-codex/src/parser.ts
2028
2090
  init_src();
2029
- import { Effect as Effect16 } from "effect";
2091
+ import { Effect as Effect17 } from "effect";
2030
2092
  function isKnownModel2(model) {
2031
2093
  return typeof model === "string" && model.length > 0 && model !== "unknown";
2032
2094
  }
@@ -2364,7 +2426,7 @@ function buildCodexTurns(events, model, timestamp) {
2364
2426
  return state.turns;
2365
2427
  }
2366
2428
  function loadCodexSession(_nativeId, sessionId) {
2367
- return Effect16.gen(function* () {
2429
+ return Effect17.gen(function* () {
2368
2430
  const filePath = yield* findCodexSessionFileById(sessionId);
2369
2431
  if (!filePath) {
2370
2432
  return {
@@ -2374,11 +2436,11 @@ function loadCodexSession(_nativeId, sessionId) {
2374
2436
  pluginId: "codex-cli"
2375
2437
  };
2376
2438
  }
2377
- const text = yield* readFileText2(filePath);
2439
+ const text = yield* readFileText(filePath);
2378
2440
  let meta = null;
2379
2441
  const events = [];
2380
2442
  let turnContextModel = null;
2381
- iterateJsonl2(text, ({ parsed, lineIndex }) => {
2443
+ iterateJsonl(text, ({ parsed, lineIndex }) => {
2382
2444
  if (lineIndex === 0) {
2383
2445
  const normalized = normalizeSessionMeta(parsed);
2384
2446
  if (normalized) {
@@ -2418,13 +2480,13 @@ var codexCliPlugin = {
2418
2480
  id: "codex-cli",
2419
2481
  displayName: "Codex",
2420
2482
  getDefaultDataDir: () => null,
2421
- isDataAvailable: Effect17.gen(function* () {
2483
+ isDataAvailable: Effect18.gen(function* () {
2422
2484
  const config = yield* PluginConfig;
2423
- return yield* fileExists2(config.dataDir).pipe(Effect17.catchAll(() => Effect17.succeed(false)));
2485
+ return yield* fileExists2(config.dataDir).pipe(Effect18.catchAll(() => Effect18.succeed(false)));
2424
2486
  }),
2425
2487
  discoverProjects: discoverCodexProjects(),
2426
2488
  listSessions: (nativeId) => listCodexSessions(nativeId),
2427
- loadSession: (nativeId, sessionId) => loadCodexSession(nativeId, sessionId).pipe(Effect17.catchAll((err) => Effect17.fail(new PluginError({
2489
+ loadSession: (nativeId, sessionId) => loadCodexSession(nativeId, sessionId).pipe(Effect18.catchAll((err) => Effect18.fail(new PluginError({
2428
2490
  pluginId: "codex-cli",
2429
2491
  operation: "loadSession",
2430
2492
  message: String(err),
@@ -2435,8 +2497,8 @@ var codexCliPlugin = {
2435
2497
 
2436
2498
  // ../../packages/plugin-cursor/src/index.ts
2437
2499
  init_src();
2438
- import { FileSystem as FileSystem9 } from "@effect/platform";
2439
- import { Effect as Effect23 } from "effect";
2500
+ import { FileSystem as FileSystem10 } from "@effect/platform";
2501
+ import { Effect as Effect24 } from "effect";
2440
2502
 
2441
2503
  // ../../packages/plugin-cursor/src/config.ts
2442
2504
  import { posix, win32 } from "node:path";
@@ -2494,16 +2556,16 @@ var DEFAULT_CURSOR_DIR = getDefaultCursorDir();
2494
2556
  init_src();
2495
2557
  import { join as join12 } from "node:path";
2496
2558
  import { fileURLToPath } from "node:url";
2497
- import { Effect as Effect21 } from "effect";
2559
+ import { Effect as Effect22 } from "effect";
2498
2560
 
2499
2561
  // ../../packages/plugin-cursor/src/db.ts
2500
2562
  init_src();
2501
- import { FileSystem as FileSystem7 } from "@effect/platform";
2502
- import { Effect as Effect18 } from "effect";
2563
+ import { FileSystem as FileSystem8 } from "@effect/platform";
2564
+ import { Effect as Effect19 } from "effect";
2503
2565
  function openCursorDbIfExists(dbPath) {
2504
- return Effect18.gen(function* () {
2505
- const fs = yield* FileSystem7.FileSystem;
2506
- const exists = yield* fs.exists(dbPath).pipe(Effect18.catchAll(() => Effect18.succeed(false)));
2566
+ return Effect19.gen(function* () {
2567
+ const fs = yield* FileSystem8.FileSystem;
2568
+ const exists = yield* fs.exists(dbPath).pipe(Effect19.catchAll(() => Effect19.succeed(false)));
2507
2569
  if (!exists) {
2508
2570
  return null;
2509
2571
  }
@@ -2515,25 +2577,25 @@ function openCursorGlobalDb() {
2515
2577
  return openCursorDbIfExists(getCursorGlobalDbPath());
2516
2578
  }
2517
2579
  function getCursorWorkspaceStorageDirEffect() {
2518
- return Effect18.succeed(getCursorWorkspaceStorageDir());
2580
+ return Effect19.succeed(getCursorWorkspaceStorageDir());
2519
2581
  }
2520
2582
 
2521
2583
  // ../../packages/plugin-cursor/src/plans.ts
2522
2584
  import { basename } from "node:path";
2523
- import { Effect as Effect20 } from "effect";
2585
+ import { Effect as Effect21 } from "effect";
2524
2586
 
2525
2587
  // ../../packages/plugin-cursor/src/shared/discovery-utils.ts
2526
2588
  init_src();
2527
2589
  import { join as join11 } from "node:path";
2528
- import { FileSystem as FileSystem8 } from "@effect/platform";
2529
- import { Effect as Effect19 } from "effect";
2590
+ import { FileSystem as FileSystem9 } from "@effect/platform";
2591
+ import { Effect as Effect20 } from "effect";
2530
2592
  function readDirEntriesSafe2(dir) {
2531
- return Effect19.gen(function* () {
2532
- const fs = yield* FileSystem8.FileSystem;
2533
- const names = yield* fs.readDirectory(dir).pipe(Effect19.catchAll(() => Effect19.succeed([])));
2593
+ return Effect20.gen(function* () {
2594
+ const fs = yield* FileSystem9.FileSystem;
2595
+ const names = yield* fs.readDirectory(dir).pipe(Effect20.catchAll(() => Effect20.succeed([])));
2534
2596
  const entries = [];
2535
2597
  for (const name of names) {
2536
- const info = yield* fs.stat(join11(dir, name)).pipe(Effect19.catchAll(() => Effect19.succeed(null)));
2598
+ const info = yield* fs.stat(join11(dir, name)).pipe(Effect20.catchAll(() => Effect20.succeed(null)));
2537
2599
  if (info) {
2538
2600
  entries.push({ name, isDirectory: info.type === "Directory" });
2539
2601
  }
@@ -2541,28 +2603,28 @@ function readDirEntriesSafe2(dir) {
2541
2603
  return entries;
2542
2604
  });
2543
2605
  }
2544
- function readFileText3(filePath) {
2545
- return Effect19.gen(function* () {
2546
- const fs = yield* FileSystem8.FileSystem;
2606
+ function readFileText2(filePath) {
2607
+ return Effect20.gen(function* () {
2608
+ const fs = yield* FileSystem9.FileSystem;
2547
2609
  return yield* fs.readFileString(filePath);
2548
2610
  });
2549
2611
  }
2550
2612
  function fileExists3(filePath) {
2551
- return Effect19.gen(function* () {
2552
- const fs = yield* FileSystem8.FileSystem;
2553
- return yield* fs.exists(filePath).pipe(Effect19.catchAll(() => Effect19.succeed(false)));
2613
+ return Effect20.gen(function* () {
2614
+ const fs = yield* FileSystem9.FileSystem;
2615
+ return yield* fs.exists(filePath).pipe(Effect20.catchAll(() => Effect20.succeed(false)));
2554
2616
  });
2555
2617
  }
2556
2618
  function listFilesWithMtime2(dir, suffix) {
2557
- return Effect19.gen(function* () {
2558
- const fs = yield* FileSystem8.FileSystem;
2559
- const names = yield* fs.readDirectory(dir).pipe(Effect19.catchAll(() => Effect19.succeed([])));
2619
+ return Effect20.gen(function* () {
2620
+ const fs = yield* FileSystem9.FileSystem;
2621
+ const names = yield* fs.readDirectory(dir).pipe(Effect20.catchAll(() => Effect20.succeed([])));
2560
2622
  const results = [];
2561
2623
  for (const name of names) {
2562
2624
  if (!name.endsWith(suffix)) {
2563
2625
  continue;
2564
2626
  }
2565
- const info = yield* fs.stat(join11(dir, name)).pipe(Effect19.catchAll(() => Effect19.succeed(null)));
2627
+ const info = yield* fs.stat(join11(dir, name)).pipe(Effect20.catchAll(() => Effect20.succeed(null)));
2566
2628
  if (info?.mtime._tag === "Some") {
2567
2629
  results.push({ fileName: name, mtime: info.mtime.value.toISOString() });
2568
2630
  }
@@ -2629,7 +2691,7 @@ function getPlanFallbackName(filePath) {
2629
2691
  return basename(filePath).replace(PLAN_FILE_SUFFIX_REGEX, "");
2630
2692
  }
2631
2693
  function readPlanDisplayName(filePath) {
2632
- return readFileText3(filePath).pipe(Effect20.map((text) => {
2694
+ return readFileText2(filePath).pipe(Effect21.map((text) => {
2633
2695
  const parsed = parsePlanFrontmatter(text);
2634
2696
  return parsed.name || extractFirstHeading(parsed.body) || getPlanFallbackName(filePath);
2635
2697
  }));
@@ -2643,7 +2705,7 @@ function makeSystemTurn(text, timestamp) {
2643
2705
  };
2644
2706
  }
2645
2707
  function loadCursorPlanSession(plan) {
2646
- return readFileText3(plan.filePath).pipe(Effect20.map((text) => {
2708
+ return readFileText2(plan.filePath).pipe(Effect21.map((text) => {
2647
2709
  const parsed = parsePlanFrontmatter(text);
2648
2710
  return {
2649
2711
  sessionId: plan.rawSessionId,
@@ -2664,7 +2726,7 @@ function tryParseJson2(value) {
2664
2726
  }
2665
2727
 
2666
2728
  // ../../packages/plugin-cursor/src/shared/jsonl-utils.ts
2667
- function iterateJsonl3(text, visitor, options = {}) {
2729
+ function iterateJsonl2(text, visitor, options = {}) {
2668
2730
  const lines = text.split(`
2669
2731
  `);
2670
2732
  const start = Math.max(0, options.startAt ?? 0);
@@ -2774,6 +2836,15 @@ function extractFirstUserTextFromHeaders(db, composerId, headers) {
2774
2836
  }
2775
2837
  return "";
2776
2838
  }
2839
+ function resolveComposerFallbackMessage(composer) {
2840
+ if (isNonEmptyString(composer.name)) {
2841
+ return composer.name.trim();
2842
+ }
2843
+ if (isNonEmptyString(composer.subtitle)) {
2844
+ return composer.subtitle.trim();
2845
+ }
2846
+ return "Cursor session";
2847
+ }
2777
2848
  function resolveComposerFirstMessage(globalDb, composer, composerId) {
2778
2849
  if (globalDb) {
2779
2850
  const rawComposerData = queryKeyValueRow(globalDb, `composerData:${composerId}`);
@@ -2787,13 +2858,7 @@ function resolveComposerFirstMessage(globalDb, composer, composerId) {
2787
2858
  return truncate(fromHeaders, SESSION_PREVIEW_MAX_LENGTH);
2788
2859
  }
2789
2860
  }
2790
- if (isNonEmptyString(composer.name)) {
2791
- return composer.name.trim();
2792
- }
2793
- if (isNonEmptyString(composer.subtitle)) {
2794
- return composer.subtitle.trim();
2795
- }
2796
- return "Cursor session";
2861
+ return resolveComposerFallbackMessage(composer);
2797
2862
  }
2798
2863
  function resolveComposerSessionType(unifiedMode) {
2799
2864
  if (unifiedMode === "plan") {
@@ -2804,7 +2869,13 @@ function resolveComposerSessionType(unifiedMode) {
2804
2869
  }
2805
2870
  return;
2806
2871
  }
2807
- function createComposerSummary(projectPath, workspaceDbPath, composer, globalDb) {
2872
+ function createComposerSummary({
2873
+ projectPath,
2874
+ workspaceDbPath,
2875
+ composer,
2876
+ globalDb,
2877
+ options = { resolveFirstMessage: true }
2878
+ }) {
2808
2879
  if (!isNonEmptyString(composer.composerId)) {
2809
2880
  return null;
2810
2881
  }
@@ -2814,7 +2885,7 @@ function createComposerSummary(projectPath, workspaceDbPath, composer, globalDb)
2814
2885
  }
2815
2886
  const { composerId } = composer;
2816
2887
  const unifiedMode = composer.unifiedMode ?? composer.forceMode ?? "chat";
2817
- const firstMessage = resolveComposerFirstMessage(globalDb, composer, composerId);
2888
+ const firstMessage = options.resolveFirstMessage ? resolveComposerFirstMessage(globalDb, composer, composerId) : resolveComposerFallbackMessage(composer);
2818
2889
  return {
2819
2890
  kind: "composer",
2820
2891
  rawSessionId: `composer:${composerId}`,
@@ -2841,6 +2912,31 @@ function parseProjectPathFromWorkspaceJson(content) {
2841
2912
  }
2842
2913
  return fileUrlToPath(parsed.folder);
2843
2914
  }
2915
+ function discoverWorkspaceDescriptors(targetProjectPath) {
2916
+ return Effect22.gen(function* () {
2917
+ const workspaceStorageDir = yield* getCursorWorkspaceStorageDirEffect();
2918
+ const workspaceEntries = yield* readDirEntriesSafe2(workspaceStorageDir);
2919
+ const workspaceDescriptors = [];
2920
+ for (const entry of workspaceEntries) {
2921
+ if (!entry.isDirectory) {
2922
+ continue;
2923
+ }
2924
+ const workspaceDir = join12(workspaceStorageDir, entry.name);
2925
+ const workspaceJsonPath = join12(workspaceDir, "workspace.json");
2926
+ const workspaceDbPath = join12(workspaceDir, "state.vscdb");
2927
+ const exists = yield* fileExists3(workspaceJsonPath);
2928
+ if (!exists) {
2929
+ continue;
2930
+ }
2931
+ const projectPath = yield* readFileText2(workspaceJsonPath).pipe(Effect22.map(parseProjectPathFromWorkspaceJson), Effect22.catchAll(() => Effect22.succeed(null)));
2932
+ if (!projectPath || targetProjectPath && projectPath !== targetProjectPath) {
2933
+ continue;
2934
+ }
2935
+ workspaceDescriptors.push({ projectPath, workspaceDbPath });
2936
+ }
2937
+ return workspaceDescriptors;
2938
+ });
2939
+ }
2844
2940
  function findProjectSessions(sessionsByProject, projectPath) {
2845
2941
  const existing = sessionsByProject.get(projectPath);
2846
2942
  if (existing) {
@@ -2853,9 +2949,49 @@ function findProjectSessions(sessionsByProject, projectPath) {
2853
2949
  function pushSession(sessionsByProject, session) {
2854
2950
  findProjectSessions(sessionsByProject, session.projectPath).push(session);
2855
2951
  }
2952
+ function pushSessions(sessionsByProject, sessions) {
2953
+ for (const session of sessions) {
2954
+ pushSession(sessionsByProject, session);
2955
+ }
2956
+ }
2957
+ function collectComposerSessions({
2958
+ workspaceDescriptors,
2959
+ globalDb,
2960
+ sessionsByProject,
2961
+ composersById,
2962
+ options
2963
+ }) {
2964
+ return Effect22.gen(function* () {
2965
+ for (const descriptor of workspaceDescriptors) {
2966
+ const workspaceDb = yield* openCursorDbIfExists(descriptor.workspaceDbPath);
2967
+ if (!workspaceDb) {
2968
+ continue;
2969
+ }
2970
+ try {
2971
+ const composers = readWorkspaceComposerEntries(workspaceDb);
2972
+ for (const composer of composers) {
2973
+ const summary = createComposerSummary({
2974
+ projectPath: descriptor.projectPath,
2975
+ workspaceDbPath: descriptor.workspaceDbPath,
2976
+ composer,
2977
+ globalDb,
2978
+ options
2979
+ });
2980
+ if (!summary) {
2981
+ continue;
2982
+ }
2983
+ composersById.set(summary.composerId, summary);
2984
+ pushSession(sessionsByProject, summary);
2985
+ }
2986
+ } finally {
2987
+ workspaceDb.close();
2988
+ }
2989
+ }
2990
+ });
2991
+ }
2856
2992
  function readFirstTranscriptUserMessage(text) {
2857
2993
  let firstMessage = "";
2858
- iterateJsonl3(text, ({ parsed }) => {
2994
+ iterateJsonl2(text, ({ parsed }) => {
2859
2995
  const line = parsed;
2860
2996
  if (line.role !== "user" || !Array.isArray(line.message?.content)) {
2861
2997
  return;
@@ -2872,7 +3008,7 @@ function readFirstTranscriptUserMessage(text) {
2872
3008
  return firstMessage;
2873
3009
  }
2874
3010
  function listTranscriptFiles(agentTranscriptsDir) {
2875
- return Effect21.gen(function* () {
3011
+ return Effect22.gen(function* () {
2876
3012
  const agentDirs = yield* readDirEntriesSafe2(agentTranscriptsDir);
2877
3013
  const files = [];
2878
3014
  for (const entry of agentDirs) {
@@ -2896,45 +3032,50 @@ function listTranscriptFiles(agentTranscriptsDir) {
2896
3032
  return files;
2897
3033
  });
2898
3034
  }
2899
- function discoverBackgroundAgents(workspaceProjectPaths) {
2900
- return Effect21.gen(function* () {
3035
+ function createBackgroundAgentSummary(projectPath, transcript, firstMessage) {
3036
+ return {
3037
+ kind: "agent",
3038
+ rawSessionId: `agent:${transcript.agentId}`,
3039
+ projectPath,
3040
+ agentId: transcript.agentId,
3041
+ filePath: transcript.filePath,
3042
+ timestamp: transcript.mtimeIso,
3043
+ timestampMs: new Date(transcript.mtimeIso).getTime(),
3044
+ firstMessage,
3045
+ slug: transcript.agentId,
3046
+ model: "unknown",
3047
+ gitBranch: "",
3048
+ sessionType: "implementation"
3049
+ };
3050
+ }
3051
+ function discoverBackgroundAgentsForProject(projectPath, options = { readPreviewText: true }) {
3052
+ return Effect22.gen(function* () {
2901
3053
  const config = yield* PluginConfig;
2902
- const projectsDir = join12(config.dataDir, "projects");
2903
- const entries = yield* readDirEntriesSafe2(projectsDir);
2904
- const encodedProjectPaths = new Map(workspaceProjectPaths.map((projectPath) => [encodeCursorProjectPath(projectPath), projectPath]));
3054
+ const agentTranscriptsDir = join12(config.dataDir, "projects", encodeCursorProjectPath(projectPath), "agent-transcripts");
2905
3055
  const agentsById = new Map;
2906
- for (const entry of entries) {
2907
- if (!entry.isDirectory) {
2908
- continue;
2909
- }
2910
- const projectPath = encodedProjectPaths.get(entry.name);
2911
- if (!projectPath) {
2912
- continue;
3056
+ const transcriptDirExists = yield* fileExists3(agentTranscriptsDir);
3057
+ if (!transcriptDirExists) {
3058
+ return agentsById;
3059
+ }
3060
+ const transcripts = yield* listTranscriptFiles(agentTranscriptsDir);
3061
+ for (const transcript of transcripts) {
3062
+ let firstMessage = "Cursor background agent";
3063
+ if (options.readPreviewText) {
3064
+ const text = yield* readFileText2(transcript.filePath).pipe(Effect22.catchAll(() => Effect22.succeed("")));
3065
+ firstMessage = readFirstTranscriptUserMessage(text) || firstMessage;
2913
3066
  }
2914
- const agentTranscriptsDir = join12(projectsDir, entry.name, "agent-transcripts");
2915
- const transcriptDirExists = yield* fileExists3(agentTranscriptsDir);
2916
- if (!transcriptDirExists) {
2917
- continue;
2918
- }
2919
- const transcripts = yield* listTranscriptFiles(agentTranscriptsDir);
2920
- for (const transcript of transcripts) {
2921
- const text = yield* readFileText3(transcript.filePath).pipe(Effect21.catchAll(() => Effect21.succeed("")));
2922
- const firstMessage = readFirstTranscriptUserMessage(text) || "Cursor background agent";
2923
- const timestampMs = new Date(transcript.mtimeIso).getTime();
2924
- agentsById.set(transcript.agentId, {
2925
- kind: "agent",
2926
- rawSessionId: `agent:${transcript.agentId}`,
2927
- projectPath,
2928
- agentId: transcript.agentId,
2929
- filePath: transcript.filePath,
2930
- timestamp: transcript.mtimeIso,
2931
- timestampMs,
2932
- firstMessage,
2933
- slug: transcript.agentId,
2934
- model: "unknown",
2935
- gitBranch: "",
2936
- sessionType: "implementation"
2937
- });
3067
+ agentsById.set(transcript.agentId, createBackgroundAgentSummary(projectPath, transcript, firstMessage));
3068
+ }
3069
+ return agentsById;
3070
+ });
3071
+ }
3072
+ function discoverBackgroundAgents(projectPaths, options = { readPreviewText: true }) {
3073
+ return Effect22.gen(function* () {
3074
+ const agentsById = new Map;
3075
+ for (const projectPath of new Set(projectPaths)) {
3076
+ const projectAgents = yield* discoverBackgroundAgentsForProject(projectPath, options);
3077
+ for (const agent of projectAgents.values()) {
3078
+ agentsById.set(agent.agentId, agent);
2938
3079
  }
2939
3080
  }
2940
3081
  return agentsById;
@@ -2971,8 +3112,8 @@ function createPlanSummary(entry, projectPath, filePath, displayName) {
2971
3112
  sessionType: "plan"
2972
3113
  };
2973
3114
  }
2974
- function discoverMappedPlans(globalDb, composersById, agentsById) {
2975
- return Effect21.gen(function* () {
3115
+ function discoverMappedPlans(globalDb, composersById, agentsById, options = { loadDisplayName: true }) {
3116
+ return Effect22.gen(function* () {
2976
3117
  const plansById = new Map;
2977
3118
  if (!globalDb) {
2978
3119
  return plansById;
@@ -2993,8 +3134,8 @@ function discoverMappedPlans(globalDb, composersById, agentsById) {
2993
3134
  if (!exists) {
2994
3135
  continue;
2995
3136
  }
2996
- const displayName = yield* readPlanDisplayName(filePath).pipe(Effect21.catchAll(() => Effect21.succeed("")));
2997
- const summary = createPlanSummary(entry, projectPath, filePath, displayName || entry.name?.trim() || "");
3137
+ const displayName = options.loadDisplayName ? yield* readPlanDisplayName(filePath).pipe(Effect22.catchAll(() => Effect22.succeed(""))) : "";
3138
+ const summary = createPlanSummary(entry, projectPath, filePath, displayName || entry.name?.trim() || "Cursor plan");
2998
3139
  if (!summary) {
2999
3140
  continue;
3000
3141
  }
@@ -3025,63 +3166,57 @@ function buildProjects(sessionsByProject) {
3025
3166
  sortByIsoDesc(projects, (project) => project.lastActivity);
3026
3167
  return projects;
3027
3168
  }
3169
+ function buildCursorProjectIndex(nativeId) {
3170
+ return Effect22.gen(function* () {
3171
+ const globalDb = yield* openCursorGlobalDb();
3172
+ const sessionsByProject = new Map;
3173
+ const composersById = new Map;
3174
+ try {
3175
+ const workspaceDescriptors = yield* discoverWorkspaceDescriptors(nativeId);
3176
+ yield* collectComposerSessions({
3177
+ workspaceDescriptors,
3178
+ globalDb,
3179
+ sessionsByProject,
3180
+ composersById,
3181
+ options: { resolveFirstMessage: true }
3182
+ });
3183
+ const agentsById = yield* discoverBackgroundAgents([nativeId], { readPreviewText: true });
3184
+ pushSessions(sessionsByProject, agentsById.values());
3185
+ const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById, { loadDisplayName: true });
3186
+ pushSessions(sessionsByProject, plansById.values());
3187
+ return {
3188
+ projects: buildProjects(sessionsByProject),
3189
+ sessionsByProject,
3190
+ composersById,
3191
+ agentsById,
3192
+ plansById
3193
+ };
3194
+ } finally {
3195
+ globalDb?.close();
3196
+ }
3197
+ });
3198
+ }
3028
3199
  function buildCursorIndex() {
3029
- return Effect21.gen(function* () {
3030
- const workspaceStorageDir = yield* getCursorWorkspaceStorageDirEffect();
3031
- const workspaceEntries = yield* readDirEntriesSafe2(workspaceStorageDir);
3200
+ return Effect22.gen(function* () {
3032
3201
  const globalDb = yield* openCursorGlobalDb();
3033
3202
  const sessionsByProject = new Map;
3034
3203
  const composersById = new Map;
3035
3204
  try {
3036
- const workspaceDescriptors = [];
3037
- for (const entry of workspaceEntries) {
3038
- if (!entry.isDirectory) {
3039
- continue;
3040
- }
3041
- const workspaceDir = join12(workspaceStorageDir, entry.name);
3042
- const workspaceJsonPath = join12(workspaceDir, "workspace.json");
3043
- const workspaceDbPath = join12(workspaceDir, "state.vscdb");
3044
- const exists = yield* fileExists3(workspaceJsonPath);
3045
- if (!exists) {
3046
- continue;
3047
- }
3048
- const projectPath = yield* readFileText3(workspaceJsonPath).pipe(Effect21.map(parseProjectPathFromWorkspaceJson), Effect21.catchAll(() => Effect21.succeed(null)));
3049
- if (!projectPath) {
3050
- continue;
3051
- }
3052
- workspaceDescriptors.push({ projectPath, workspaceDbPath });
3053
- }
3205
+ const workspaceDescriptors = yield* discoverWorkspaceDescriptors();
3054
3206
  const workspaceProjectPaths = [...new Set(workspaceDescriptors.map((descriptor) => descriptor.projectPath))];
3055
- for (const descriptor of workspaceDescriptors) {
3056
- const workspaceDb = yield* openCursorDbIfExists(descriptor.workspaceDbPath);
3057
- if (!workspaceDb) {
3058
- continue;
3059
- }
3060
- try {
3061
- const composers = readWorkspaceComposerEntries(workspaceDb);
3062
- for (const composer of composers) {
3063
- const summary = createComposerSummary(descriptor.projectPath, descriptor.workspaceDbPath, composer, globalDb);
3064
- if (!summary) {
3065
- continue;
3066
- }
3067
- composersById.set(summary.composerId, summary);
3068
- pushSession(sessionsByProject, summary);
3069
- }
3070
- } finally {
3071
- workspaceDb.close();
3072
- }
3073
- }
3207
+ yield* collectComposerSessions({
3208
+ workspaceDescriptors,
3209
+ globalDb,
3210
+ sessionsByProject,
3211
+ composersById,
3212
+ options: { resolveFirstMessage: true }
3213
+ });
3074
3214
  const agentsById = yield* discoverBackgroundAgents(workspaceProjectPaths);
3075
- for (const agent of agentsById.values()) {
3076
- pushSession(sessionsByProject, agent);
3077
- }
3215
+ pushSessions(sessionsByProject, agentsById.values());
3078
3216
  const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById);
3079
- for (const plan of plansById.values()) {
3080
- pushSession(sessionsByProject, plan);
3081
- }
3082
- const projects = buildProjects(sessionsByProject);
3217
+ pushSessions(sessionsByProject, plansById.values());
3083
3218
  return {
3084
- projects,
3219
+ projects: buildProjects(sessionsByProject),
3085
3220
  sessionsByProject,
3086
3221
  composersById,
3087
3222
  agentsById,
@@ -3104,11 +3239,44 @@ function toSessionSummary(session) {
3104
3239
  sessionType: session.sessionType
3105
3240
  };
3106
3241
  }
3242
+ function buildCursorDiscoveryIndex() {
3243
+ return buildCursorIndex().pipe(Effect22.map((index) => ({
3244
+ projects: index.projects,
3245
+ sessionsByNativeId: new Map([...index.sessionsByProject.entries()].map(([projectPath, sessions]) => [
3246
+ projectPath,
3247
+ sessions.map(toSessionSummary)
3248
+ ]))
3249
+ })));
3250
+ }
3107
3251
  function discoverCursorProjects() {
3108
- return buildCursorIndex().pipe(Effect21.map((index) => index.projects));
3252
+ return Effect22.gen(function* () {
3253
+ const globalDb = yield* openCursorGlobalDb();
3254
+ const sessionsByProject = new Map;
3255
+ const composersById = new Map;
3256
+ try {
3257
+ const workspaceDescriptors = yield* discoverWorkspaceDescriptors();
3258
+ const workspaceProjectPaths = [...new Set(workspaceDescriptors.map((descriptor) => descriptor.projectPath))];
3259
+ yield* collectComposerSessions({
3260
+ workspaceDescriptors,
3261
+ globalDb,
3262
+ sessionsByProject,
3263
+ composersById,
3264
+ options: { resolveFirstMessage: false }
3265
+ });
3266
+ const agentsById = yield* discoverBackgroundAgents(workspaceProjectPaths, { readPreviewText: false });
3267
+ pushSessions(sessionsByProject, agentsById.values());
3268
+ const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById, {
3269
+ loadDisplayName: false
3270
+ });
3271
+ pushSessions(sessionsByProject, plansById.values());
3272
+ return buildProjects(sessionsByProject);
3273
+ } finally {
3274
+ globalDb?.close();
3275
+ }
3276
+ });
3109
3277
  }
3110
3278
  function listCursorSessions(nativeId) {
3111
- return buildCursorIndex().pipe(Effect21.map((index) => {
3279
+ return buildCursorProjectIndex(nativeId).pipe(Effect22.map((index) => {
3112
3280
  const sessions = index.sessionsByProject.get(nativeId) ?? [];
3113
3281
  sortByIsoDesc(sessions, (session) => session.timestamp);
3114
3282
  return sessions.map(toSessionSummary);
@@ -3116,7 +3284,7 @@ function listCursorSessions(nativeId) {
3116
3284
  }
3117
3285
 
3118
3286
  // ../../packages/plugin-cursor/src/parser.ts
3119
- import { Effect as Effect22 } from "effect";
3287
+ import { Effect as Effect23 } from "effect";
3120
3288
  var COMPOSER_PREFIX_REGEX = /^composer:/u;
3121
3289
  var AGENT_PREFIX_REGEX = /^agent:/u;
3122
3290
  var PLAN_PREFIX_REGEX = /^plan:/u;
@@ -3255,7 +3423,7 @@ function partialCursorSessionNotice(timestamp) {
3255
3423
  Klovi could not reconstruct the full Composer transcript from Cursor state. Showing recovered content only.`);
3256
3424
  }
3257
3425
  function loadCursorComposerSession(summary) {
3258
- return Effect22.gen(function* () {
3426
+ return Effect23.gen(function* () {
3259
3427
  const globalDb = yield* openCursorGlobalDb();
3260
3428
  const baseTimestampMs = summary.createdAtMs;
3261
3429
  try {
@@ -3305,11 +3473,11 @@ function loadCursorComposerSession(summary) {
3305
3473
  });
3306
3474
  }
3307
3475
  function loadCursorAgentSession(summary) {
3308
- return Effect22.gen(function* () {
3309
- const text = yield* readFileText3(summary.filePath);
3476
+ return Effect23.gen(function* () {
3477
+ const text = yield* readFileText2(summary.filePath);
3310
3478
  const turns = [];
3311
3479
  let turnIndex = 0;
3312
- iterateJsonl3(text, ({ parsed }) => {
3480
+ iterateJsonl2(text, ({ parsed }) => {
3313
3481
  const line = parsed;
3314
3482
  if (!Array.isArray(line.message?.content)) {
3315
3483
  return;
@@ -3341,11 +3509,11 @@ function loadCursorAgentSession(summary) {
3341
3509
  });
3342
3510
  }
3343
3511
  function loadCursorSession(nativeId, sessionId) {
3344
- return Effect22.gen(function* () {
3345
- const index = yield* buildCursorIndex();
3512
+ return Effect23.gen(function* () {
3513
+ const index = yield* buildCursorProjectIndex(nativeId);
3346
3514
  const session = index.composersById.get(sessionId.replace(COMPOSER_PREFIX_REGEX, "")) ?? index.agentsById.get(sessionId.replace(AGENT_PREFIX_REGEX, "")) ?? index.plansById.get(sessionId.replace(PLAN_PREFIX_REGEX, ""));
3347
3515
  if (!session || session.projectPath !== nativeId) {
3348
- return yield* Effect22.fail(new Error(`Cursor session not found: ${sessionId}`));
3516
+ return yield* Effect23.fail(new Error(`Cursor session not found: ${sessionId}`));
3349
3517
  }
3350
3518
  if (session.kind === "composer") {
3351
3519
  return yield* loadCursorComposerSession(session);
@@ -3361,32 +3529,38 @@ var cursorPlugin = {
3361
3529
  id: "cursor",
3362
3530
  displayName: "Cursor",
3363
3531
  getDefaultDataDir: () => null,
3364
- isDataAvailable: Effect23.gen(function* () {
3532
+ isDataAvailable: Effect24.gen(function* () {
3365
3533
  const config = yield* PluginConfig;
3366
- const fs = yield* FileSystem9.FileSystem;
3367
- const userDataExists = yield* fileExists3(config.dataDir).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
3534
+ const fs = yield* FileSystem10.FileSystem;
3535
+ const userDataExists = yield* fileExists3(config.dataDir).pipe(Effect24.catchAll(() => Effect24.succeed(false)));
3368
3536
  if (userDataExists) {
3369
3537
  return true;
3370
3538
  }
3371
- const globalDbExists = yield* fs.exists(getCursorGlobalDbPath()).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
3539
+ const globalDbExists = yield* fs.exists(getCursorGlobalDbPath()).pipe(Effect24.catchAll(() => Effect24.succeed(false)));
3372
3540
  if (globalDbExists) {
3373
3541
  return true;
3374
3542
  }
3375
- return yield* fs.exists(getCursorWorkspaceStorageDir()).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
3543
+ return yield* fs.exists(getCursorWorkspaceStorageDir()).pipe(Effect24.catchAll(() => Effect24.succeed(false)));
3376
3544
  }),
3377
- discoverProjects: discoverCursorProjects().pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3545
+ discoverProjects: discoverCursorProjects().pipe(Effect24.catchAll((err) => Effect24.fail(new PluginError({
3378
3546
  pluginId: "cursor",
3379
3547
  operation: "discoverProjects",
3380
3548
  message: String(err),
3381
3549
  cause: err
3382
3550
  })))),
3383
- listSessions: (nativeId) => listCursorSessions(nativeId).pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3551
+ discoverIndex: buildCursorDiscoveryIndex().pipe(Effect24.catchAll((err) => Effect24.fail(new PluginError({
3552
+ pluginId: "cursor",
3553
+ operation: "discoverIndex",
3554
+ message: String(err),
3555
+ cause: err
3556
+ })))),
3557
+ listSessions: (nativeId) => listCursorSessions(nativeId).pipe(Effect24.catchAll((err) => Effect24.fail(new PluginError({
3384
3558
  pluginId: "cursor",
3385
3559
  operation: "listSessions",
3386
3560
  message: String(err),
3387
3561
  cause: err
3388
3562
  })))),
3389
- loadSession: (nativeId, sessionId) => loadCursorSession(nativeId, sessionId).pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3563
+ loadSession: (nativeId, sessionId) => loadCursorSession(nativeId, sessionId).pipe(Effect24.catchAll((err) => Effect24.fail(new PluginError({
3390
3564
  pluginId: "cursor",
3391
3565
  operation: "loadSession",
3392
3566
  message: String(err),
@@ -3427,7 +3601,7 @@ class PluginRegistry2 extends PluginRegistry {
3427
3601
 
3428
3602
  // ../../packages/server/src/services/auto-discover.ts
3429
3603
  function createRegistry(settings) {
3430
- return Effect24.gen(function* () {
3604
+ return Effect25.gen(function* () {
3431
3605
  const registry = new PluginRegistry2;
3432
3606
  for (const { plugin, defaultDir, defaultEnabled } of BUILTIN_PLUGIN_DESCRIPTORS) {
3433
3607
  const pluginSettings = settings?.plugins[plugin.id];
@@ -3437,7 +3611,7 @@ function createRegistry(settings) {
3437
3611
  }
3438
3612
  const dataDir = pluginSettings?.dataDir ?? defaultDir;
3439
3613
  const configLayer = makePluginConfigLayer({ dataDir });
3440
- const available = yield* plugin.isDataAvailable.pipe(Effect24.provide(configLayer), Effect24.catchAll(() => Effect24.succeed(false)));
3614
+ const available = yield* plugin.isDataAvailable.pipe(Effect25.provide(configLayer), Effect25.catchAll(() => Effect25.succeed(false)));
3441
3615
  if (available) {
3442
3616
  registry.register(plugin, { dataDir });
3443
3617
  }
@@ -3447,12 +3621,12 @@ function createRegistry(settings) {
3447
3621
  }
3448
3622
 
3449
3623
  // ../../packages/server/src/services/onboarding-service.ts
3450
- import { Effect as Effect26 } from "effect";
3624
+ import { Effect as Effect27 } from "effect";
3451
3625
 
3452
3626
  // ../../packages/server/src/services/settings.ts
3453
3627
  import { dirname, join as join13 } from "node:path";
3454
- import { FileSystem as FileSystem10 } from "@effect/platform";
3455
- import { Effect as Effect25 } from "effect";
3628
+ import { FileSystem as FileSystem11 } from "@effect/platform";
3629
+ import { Effect as Effect26 } from "effect";
3456
3630
 
3457
3631
  // ../../packages/server/src/services/errors.ts
3458
3632
  import { Data as Data2 } from "effect";
@@ -3497,16 +3671,16 @@ function getDefaultSettings() {
3497
3671
  };
3498
3672
  }
3499
3673
  function loadSettings(path) {
3500
- return Effect25.gen(function* () {
3501
- const fs = yield* FileSystem10.FileSystem;
3502
- const content = yield* fs.readFileString(path).pipe(Effect25.catchAll(() => Effect25.succeed(null)));
3674
+ return Effect26.gen(function* () {
3675
+ const fs = yield* FileSystem11.FileSystem;
3676
+ const content = yield* fs.readFileString(path).pipe(Effect26.catchAll(() => Effect26.succeed(null)));
3503
3677
  if (content === null) {
3504
3678
  return getDefaultSettings();
3505
3679
  }
3506
- const parsed = yield* Effect25.try({
3680
+ const parsed = yield* Effect26.try({
3507
3681
  try: () => JSON.parse(content),
3508
3682
  catch: () => null
3509
- }).pipe(Effect25.catchAll(() => Effect25.succeed(null)));
3683
+ }).pipe(Effect26.catchAll(() => Effect26.succeed(null)));
3510
3684
  if (parsed === null || parsed["version"] !== 1 || typeof parsed["plugins"] !== "object") {
3511
3685
  return getDefaultSettings();
3512
3686
  }
@@ -3514,37 +3688,37 @@ function loadSettings(path) {
3514
3688
  });
3515
3689
  }
3516
3690
  function saveSettings(path, settings) {
3517
- return Effect25.gen(function* () {
3518
- const fs = yield* FileSystem10.FileSystem;
3691
+ return Effect26.gen(function* () {
3692
+ const fs = yield* FileSystem11.FileSystem;
3519
3693
  const dir = dirname(path);
3520
- yield* fs.makeDirectory(dir, { recursive: true }).pipe(Effect25.mapError((cause) => new SettingsWriteError({ path, cause })));
3694
+ yield* fs.makeDirectory(dir, { recursive: true }).pipe(Effect26.mapError((cause) => new SettingsWriteError({ path, cause })));
3521
3695
  const tmpPath = join13(dir, `.settings-${Date.now()}.tmp`);
3522
- yield* fs.writeFileString(tmpPath, JSON.stringify(settings, null, 2)).pipe(Effect25.mapError((cause) => new SettingsWriteError({ path, cause })));
3523
- yield* fs.rename(tmpPath, path).pipe(Effect25.mapError((cause) => new SettingsWriteError({ path, cause })));
3696
+ yield* fs.writeFileString(tmpPath, JSON.stringify(settings, null, 2)).pipe(Effect26.mapError((cause) => new SettingsWriteError({ path, cause })));
3697
+ yield* fs.rename(tmpPath, path).pipe(Effect26.mapError((cause) => new SettingsWriteError({ path, cause })));
3524
3698
  });
3525
3699
  }
3526
3700
  function settingsFileExists(path) {
3527
- return Effect25.gen(function* () {
3528
- const fs = yield* FileSystem10.FileSystem;
3529
- return yield* fs.exists(path).pipe(Effect25.catchAll(() => Effect25.succeed(false)));
3701
+ return Effect26.gen(function* () {
3702
+ const fs = yield* FileSystem11.FileSystem;
3703
+ return yield* fs.exists(path).pipe(Effect26.catchAll(() => Effect26.succeed(false)));
3530
3704
  });
3531
3705
  }
3532
3706
  function deleteSettingsFile(path) {
3533
- return Effect25.gen(function* () {
3534
- const fs = yield* FileSystem10.FileSystem;
3535
- yield* fs.remove(path).pipe(Effect25.catchAll(() => Effect25.void));
3707
+ return Effect26.gen(function* () {
3708
+ const fs = yield* FileSystem11.FileSystem;
3709
+ yield* fs.remove(path).pipe(Effect26.catchAll(() => Effect26.void));
3536
3710
  });
3537
3711
  }
3538
3712
 
3539
3713
  // ../../packages/server/src/services/onboarding-service.ts
3540
3714
  function isFirstLaunch(settingsPath) {
3541
- return Effect26.gen(function* () {
3715
+ return Effect27.gen(function* () {
3542
3716
  const exists = yield* settingsFileExists(settingsPath);
3543
3717
  return { firstLaunch: !exists };
3544
3718
  });
3545
3719
  }
3546
3720
  function completeOnboarding(settingsPath) {
3547
- return Effect26.gen(function* () {
3721
+ return Effect27.gen(function* () {
3548
3722
  const { firstLaunch } = yield* isFirstLaunch(settingsPath);
3549
3723
  if (firstLaunch) {
3550
3724
  yield* saveSettings(settingsPath, getDefaultSettings());
@@ -3553,7 +3727,7 @@ function completeOnboarding(settingsPath) {
3553
3727
  });
3554
3728
  }
3555
3729
  function resetSettings(settingsPath) {
3556
- return Effect26.gen(function* () {
3730
+ return Effect27.gen(function* () {
3557
3731
  yield* deleteSettingsFile(settingsPath);
3558
3732
  return { ok: true };
3559
3733
  });
@@ -3561,62 +3735,81 @@ function resetSettings(settingsPath) {
3561
3735
 
3562
3736
  // ../../packages/server/src/services/sessions-service.ts
3563
3737
  init_src();
3564
- import { Effect as Effect27 } from "effect";
3738
+ import { Effect as Effect28 } from "effect";
3739
+ var DEFAULT_HEAD_SIZE = 100;
3565
3740
  function getProjects(registry) {
3566
- return Effect27.gen(function* () {
3741
+ return Effect28.gen(function* () {
3567
3742
  const projects = yield* registry.discoverAllProjects();
3568
3743
  return { projects };
3569
3744
  });
3570
3745
  }
3571
3746
  function getSessions(registry, params) {
3572
- return Effect27.gen(function* () {
3573
- const projects = yield* registry.discoverAllProjects();
3574
- const project = projects.find((p) => p.encodedPath === params.encodedPath);
3575
- if (!project) {
3747
+ return Effect28.gen(function* () {
3748
+ const discovered = yield* registry.discoverAllProjectsWithSessions();
3749
+ if (!discovered.projects.some((project) => project.encodedPath === params.encodedPath)) {
3576
3750
  return { sessions: [] };
3577
3751
  }
3578
- const sessions = yield* registry.listAllSessions(project);
3579
- return { sessions };
3752
+ return { sessions: discovered.sessionsByEncodedPath.get(params.encodedPath) ?? [] };
3580
3753
  });
3581
3754
  }
3582
- function getSession(registry, params) {
3583
- return Effect27.gen(function* () {
3755
+ function loadSessionInternal(registry, params) {
3756
+ return Effect28.gen(function* () {
3584
3757
  const parsed = parseSessionId(params.sessionId);
3585
3758
  if (!(parsed.pluginId && parsed.rawSessionId)) {
3586
- return yield* Effect27.fail(new InvalidSessionIdError({ value: params.sessionId }));
3759
+ return yield* Effect28.fail(new InvalidSessionIdError({ value: params.sessionId }));
3587
3760
  }
3588
3761
  const { pluginId } = parsed;
3589
3762
  const { rawSessionId } = parsed;
3590
3763
  const projects = yield* registry.discoverAllProjects();
3591
3764
  const project = projects.find((p) => p.encodedPath === params.project);
3592
3765
  if (!project) {
3593
- return yield* Effect27.fail(new ProjectNotFoundError({ encodedPath: params.project }));
3766
+ return yield* Effect28.fail(new ProjectNotFoundError({ encodedPath: params.project }));
3594
3767
  }
3595
3768
  const source = project.sources.find((s) => s.pluginId === pluginId);
3596
3769
  if (!source) {
3597
- return yield* Effect27.fail(new PluginSourceNotFoundError({ pluginId, project: params.project }));
3770
+ return yield* Effect28.fail(new PluginSourceNotFoundError({ pluginId, project: params.project }));
3598
3771
  }
3599
3772
  const plugin = registry.getPlugin(pluginId);
3600
3773
  const pluginConfig = registry.getPluginConfig(pluginId);
3601
3774
  const configLayer = makePluginConfigLayer(pluginConfig);
3602
- const sessionDetail = plugin.loadSessionDetail ? yield* plugin.loadSessionDetail(source.nativeId, rawSessionId).pipe(Effect27.provide(configLayer), Effect27.catchAll(() => Effect27.succeed(undefined))) : undefined;
3603
- const session = sessionDetail?.session ?? (yield* plugin.loadSession(source.nativeId, rawSessionId).pipe(Effect27.provide(configLayer), Effect27.catchAll(() => Effect27.die("loadSession failed"))));
3775
+ const sessionDetail = plugin.loadSessionDetail ? yield* plugin.loadSessionDetail(source.nativeId, rawSessionId).pipe(Effect28.provide(configLayer), Effect28.catchAll(() => Effect28.succeed(undefined))) : undefined;
3776
+ const session = sessionDetail?.session ?? (yield* plugin.loadSession(source.nativeId, rawSessionId).pipe(Effect28.provide(configLayer), Effect28.catchAll(() => Effect28.die("loadSession failed"))));
3604
3777
  session.sessionId = encodeSessionId(pluginId, rawSessionId);
3605
3778
  session.pluginId = pluginId;
3606
3779
  session.planSessionId = sessionDetail?.planSessionId ? encodeSessionId(pluginId, sessionDetail.planSessionId) : undefined;
3607
3780
  session.implSessionId = sessionDetail?.implSessionId ? encodeSessionId(pluginId, sessionDetail.implSessionId) : undefined;
3608
- return { session };
3781
+ return { session, pluginId, rawSessionId };
3782
+ });
3783
+ }
3784
+ function getSession(registry, params) {
3785
+ return loadSessionInternal(registry, params).pipe(Effect28.map(({ session }) => ({ session })));
3786
+ }
3787
+ function getSessionHead(registry, params) {
3788
+ return Effect28.gen(function* () {
3789
+ const { session } = yield* loadSessionInternal(registry, params);
3790
+ const headSize = params.headSize ?? DEFAULT_HEAD_SIZE;
3791
+ const totalTurns = session.turns.length;
3792
+ const headSession = { ...session, turns: session.turns.slice(0, headSize) };
3793
+ return { session: headSession, totalTurns };
3794
+ });
3795
+ }
3796
+ function getSessionTail(registry, params) {
3797
+ return Effect28.gen(function* () {
3798
+ const { session } = yield* loadSessionInternal(registry, params);
3799
+ const fromTurn = Math.max(0, params.fromTurn);
3800
+ const totalTurns = session.turns.length;
3801
+ return { turns: fromTurn >= totalTurns ? [] : session.turns.slice(fromTurn) };
3609
3802
  });
3610
3803
  }
3611
3804
  function getSubAgent(registry, params) {
3612
- return Effect27.gen(function* () {
3805
+ return Effect28.gen(function* () {
3613
3806
  const parsed = parseSessionId(params.sessionId);
3614
3807
  if (!(parsed.pluginId && parsed.rawSessionId)) {
3615
- return yield* Effect27.fail(new InvalidSessionIdError({ value: params.sessionId }));
3808
+ return yield* Effect28.fail(new InvalidSessionIdError({ value: params.sessionId }));
3616
3809
  }
3617
3810
  const plugin = registry.getPlugin(parsed.pluginId);
3618
3811
  if (!plugin.loadSubAgentSession) {
3619
- return yield* Effect27.fail(new SubAgentNotSupportedError({ pluginId: parsed.pluginId }));
3812
+ return yield* Effect28.fail(new SubAgentNotSupportedError({ pluginId: parsed.pluginId }));
3620
3813
  }
3621
3814
  const pluginConfig = registry.getPluginConfig(parsed.pluginId);
3622
3815
  const configLayer = makePluginConfigLayer(pluginConfig);
@@ -3624,7 +3817,7 @@ function getSubAgent(registry, params) {
3624
3817
  sessionId: parsed.rawSessionId,
3625
3818
  project: params.project,
3626
3819
  agentId: params.agentId
3627
- }).pipe(Effect27.provide(configLayer), Effect27.catchAll(() => Effect27.die("loadSubAgentSession failed")));
3820
+ }).pipe(Effect28.provide(configLayer), Effect28.catchAll(() => Effect28.die("loadSubAgentSession failed")));
3628
3821
  return { session };
3629
3822
  });
3630
3823
  }
@@ -3633,11 +3826,11 @@ function projectNameFromPath(fullPath) {
3633
3826
  return parts.slice(-2).join("/");
3634
3827
  }
3635
3828
  function searchSessions(registry) {
3636
- return Effect27.gen(function* () {
3637
- const projects = yield* registry.discoverAllProjects();
3638
- const perProject = yield* Effect27.forEach(projects, (project) => registry.listAllSessions(project).pipe(Effect27.map((sessions) => ({ project, sessions }))), { concurrency: "unbounded" });
3829
+ return Effect28.gen(function* () {
3830
+ const discovered = yield* registry.discoverAllProjectsWithSessions();
3639
3831
  const allSessions = [];
3640
- for (const { project, sessions } of perProject) {
3832
+ for (const project of discovered.projects) {
3833
+ const sessions = discovered.sessionsByEncodedPath.get(project.encodedPath) ?? [];
3641
3834
  const projectName = projectNameFromPath(project.name);
3642
3835
  for (const session of sessions) {
3643
3836
  allSessions.push({
@@ -3653,10 +3846,10 @@ function searchSessions(registry) {
3653
3846
  }
3654
3847
 
3655
3848
  // ../../packages/server/src/services/settings-service.ts
3656
- import { Effect as Effect28 } from "effect";
3849
+ import { Effect as Effect29 } from "effect";
3657
3850
  var DEFAULT_CHECK_INTERVAL_HOURS = 6;
3658
3851
  function buildPluginSettingsResponse(settingsPath) {
3659
- return Effect28.gen(function* () {
3852
+ return Effect29.gen(function* () {
3660
3853
  const settings = yield* loadSettings(settingsPath);
3661
3854
  const plugins = BUILTIN_PLUGIN_DESCRIPTORS.map(({ plugin, defaultDir, defaultEnabled }) => {
3662
3855
  const { id } = plugin;
@@ -3680,13 +3873,13 @@ function getPluginSettings(settingsPath) {
3680
3873
  return buildPluginSettingsResponse(settingsPath);
3681
3874
  }
3682
3875
  function getGeneralSettings(settingsPath) {
3683
- return Effect28.gen(function* () {
3876
+ return Effect29.gen(function* () {
3684
3877
  const settings = yield* loadSettings(settingsPath);
3685
3878
  return { showSecurityWarning: settings.general?.showSecurityWarning ?? true };
3686
3879
  });
3687
3880
  }
3688
3881
  function updateGeneralSettings(settingsPath, params) {
3689
- return Effect28.gen(function* () {
3882
+ return Effect29.gen(function* () {
3690
3883
  const settings = yield* loadSettings(settingsPath);
3691
3884
  if (!settings.general) {
3692
3885
  settings.general = {};
@@ -3699,9 +3892,9 @@ function updateGeneralSettings(settingsPath, params) {
3699
3892
  });
3700
3893
  }
3701
3894
  function updatePluginSetting(settingsPath, params) {
3702
- return Effect28.gen(function* () {
3895
+ return Effect29.gen(function* () {
3703
3896
  if (!BUILTIN_PLUGIN_ID_SET.has(params.pluginId)) {
3704
- return yield* Effect28.fail(new UnknownPluginError({ pluginId: params.pluginId }));
3897
+ return yield* Effect29.fail(new UnknownPluginError({ pluginId: params.pluginId }));
3705
3898
  }
3706
3899
  const settings = yield* loadSettings(settingsPath);
3707
3900
  const descriptor = BUILTIN_PLUGIN_DESCRIPTORS.find(({ plugin }) => plugin.id === params.pluginId);
@@ -3721,7 +3914,7 @@ function updatePluginSetting(settingsPath, params) {
3721
3914
  });
3722
3915
  }
3723
3916
  function getUpdateSettings(settingsPath) {
3724
- return Effect28.gen(function* () {
3917
+ return Effect29.gen(function* () {
3725
3918
  const settings = yield* loadSettings(settingsPath);
3726
3919
  return {
3727
3920
  channel: settings.updates?.channel ?? "stable",
@@ -3731,7 +3924,7 @@ function getUpdateSettings(settingsPath) {
3731
3924
  });
3732
3925
  }
3733
3926
  function updateUpdateSettings(settingsPath, params) {
3734
- return Effect28.gen(function* () {
3927
+ return Effect29.gen(function* () {
3735
3928
  const settings = yield* loadSettings(settingsPath);
3736
3929
  if (!settings.updates) {
3737
3930
  settings.updates = { channel: "stable", checkIntervalHours: 6, autoDownload: true };
@@ -3756,11 +3949,13 @@ function updateUpdateSettings(settingsPath, params) {
3756
3949
  }
3757
3950
 
3758
3951
  // ../../packages/server/src/services/stats-service.ts
3759
- import { Effect as Effect30 } from "effect";
3952
+ import { dirname as dirname2, join as join14 } from "node:path";
3953
+ import { FileSystem as FileSystem12 } from "@effect/platform";
3954
+ import { Effect as Effect31 } from "effect";
3760
3955
 
3761
3956
  // ../../packages/server/src/services/stats.ts
3762
3957
  init_src();
3763
- import { Effect as Effect29 } from "effect";
3958
+ import { Effect as Effect30 } from "effect";
3764
3959
  function emptyStats(projects = 0) {
3765
3960
  return {
3766
3961
  projects,
@@ -3820,12 +4015,12 @@ function countVisibleMessages(turns) {
3820
4015
  return turns.filter((turn) => turn.kind !== "parse_error").length;
3821
4016
  }
3822
4017
  function collectSessionsWithProjects(registry, stats) {
3823
- return Effect29.gen(function* () {
3824
- const projects = yield* registry.discoverAllProjects();
3825
- stats.projects = projects.length;
4018
+ return Effect30.gen(function* () {
4019
+ const discovered = yield* registry.discoverAllProjectsWithSessions();
4020
+ stats.projects = discovered.projects.length;
3826
4021
  const sessionsWithProject = [];
3827
- for (const project of projects) {
3828
- const sessions = yield* registry.listAllSessions(project);
4022
+ for (const project of discovered.projects) {
4023
+ const sessions = discovered.sessionsByEncodedPath.get(project.encodedPath) ?? [];
3829
4024
  stats.sessions += sessions.length;
3830
4025
  for (const session of sessions) {
3831
4026
  sessionsWithProject.push({ project, session });
@@ -3840,7 +4035,7 @@ function applyRecentSessionStats(stats, sessionsWithProject) {
3840
4035
  stats.thisWeekSessions = recent.thisWeekSessions;
3841
4036
  }
3842
4037
  function loadSessionForStats(registry, project, session) {
3843
- return Effect29.gen(function* () {
4038
+ return Effect30.gen(function* () {
3844
4039
  if (!session.pluginId) {
3845
4040
  return null;
3846
4041
  }
@@ -3852,7 +4047,7 @@ function loadSessionForStats(registry, project, session) {
3852
4047
  const pluginConfig = registry.getPluginConfig(session.pluginId);
3853
4048
  const { rawSessionId } = parseSessionId(session.sessionId);
3854
4049
  const configLayer = makePluginConfigLayer(pluginConfig);
3855
- const loaded = yield* plugin.loadSession(source.nativeId, rawSessionId).pipe(Effect29.provide(configLayer), Effect29.catchAll(() => Effect29.succeed(null)));
4050
+ const loaded = yield* plugin.loadSession(source.nativeId, rawSessionId).pipe(Effect30.provide(configLayer), Effect30.catchAll(() => Effect30.succeed(null)));
3856
4051
  return loaded?.turns ?? null;
3857
4052
  });
3858
4053
  }
@@ -3866,6 +4061,9 @@ function applyUsageStats(stats, modelUsage, usage) {
3866
4061
  modelUsage.cacheReadTokens += usage.cacheReadTokens ?? 0;
3867
4062
  modelUsage.cacheCreationTokens += usage.cacheCreationTokens ?? 0;
3868
4063
  }
4064
+ function totalUsageTokens(usage) {
4065
+ return usage.inputTokens + usage.outputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0);
4066
+ }
3869
4067
  function applyTurnStats(stats, turns, fallbackModel) {
3870
4068
  stats.messages += countVisibleMessages(turns);
3871
4069
  for (const turn of turns) {
@@ -3873,19 +4071,19 @@ function applyTurnStats(stats, turns, fallbackModel) {
3873
4071
  continue;
3874
4072
  }
3875
4073
  stats.toolCalls += turn.contentBlocks.filter((block) => block.type === "tool_call").length;
3876
- const modelUsage = ensureModelUsage(stats.models, turn.model || fallbackModel || "unknown");
3877
- if (!turn.usage) {
4074
+ if (!turn.usage || totalUsageTokens(turn.usage) <= 0) {
3878
4075
  continue;
3879
4076
  }
4077
+ const modelUsage = ensureModelUsage(stats.models, turn.model || fallbackModel || "unknown");
3880
4078
  applyUsageStats(stats, modelUsage, turn.usage);
3881
4079
  }
3882
4080
  }
3883
4081
  function computeStats(registry) {
3884
- return Effect29.gen(function* () {
4082
+ return Effect30.gen(function* () {
3885
4083
  const stats = emptyStats();
3886
4084
  const sessionsWithProject = yield* collectSessionsWithProjects(registry, stats);
3887
4085
  applyRecentSessionStats(stats, sessionsWithProject);
3888
- const turnsPerSession = yield* Effect29.forEach(sessionsWithProject, (item) => loadSessionForStats(registry, item.project, item.session).pipe(Effect29.map((turns) => ({ item, turns }))), { concurrency: "unbounded" });
4086
+ const turnsPerSession = yield* Effect30.forEach(sessionsWithProject, (item) => loadSessionForStats(registry, item.project, item.session).pipe(Effect30.map((turns) => ({ item, turns }))), { concurrency: "unbounded" });
3889
4087
  for (const { item, turns } of turnsPerSession) {
3890
4088
  if (!turns) {
3891
4089
  continue;
@@ -3900,10 +4098,163 @@ function scanStats(registry) {
3900
4098
  }
3901
4099
 
3902
4100
  // ../../packages/server/src/services/stats-service.ts
3903
- function getStats(registry) {
3904
- return Effect30.gen(function* () {
4101
+ var STATS_CACHE_FILENAME = "stats-cache.json";
4102
+ var refreshBootTimes = new Map;
4103
+ var refreshEpochs = new Map;
4104
+ var refreshingCachePaths = new Set;
4105
+ function getStatsCachePath(settingsPath) {
4106
+ return join14(dirname2(settingsPath), STATS_CACHE_FILENAME);
4107
+ }
4108
+ function getRefreshBootTime(cachePath) {
4109
+ const existing = refreshBootTimes.get(cachePath);
4110
+ if (existing) {
4111
+ return existing;
4112
+ }
4113
+ const bootTime = new Date().toISOString();
4114
+ refreshBootTimes.set(cachePath, bootTime);
4115
+ return bootTime;
4116
+ }
4117
+ function getRefreshEpoch(cachePath) {
4118
+ return refreshEpochs.get(cachePath) ?? 0;
4119
+ }
4120
+ function bumpRefreshEpoch(cachePath) {
4121
+ const nextEpoch = getRefreshEpoch(cachePath) + 1;
4122
+ refreshEpochs.set(cachePath, nextEpoch);
4123
+ return nextEpoch;
4124
+ }
4125
+ function isDashboardStats(value) {
4126
+ if (typeof value !== "object" || value === null) {
4127
+ return false;
4128
+ }
4129
+ const candidate = value;
4130
+ const requiredNumberFields = [
4131
+ "projects",
4132
+ "sessions",
4133
+ "messages",
4134
+ "todaySessions",
4135
+ "thisWeekSessions",
4136
+ "inputTokens",
4137
+ "outputTokens",
4138
+ "cacheReadTokens",
4139
+ "cacheCreationTokens",
4140
+ "toolCalls"
4141
+ ];
4142
+ for (const field of requiredNumberFields) {
4143
+ if (typeof candidate[field] !== "number") {
4144
+ return false;
4145
+ }
4146
+ }
4147
+ return typeof candidate["models"] === "object" && candidate["models"] !== null;
4148
+ }
4149
+ function isStatsCacheFile(value) {
4150
+ if (typeof value !== "object" || value === null) {
4151
+ return false;
4152
+ }
4153
+ const candidate = value;
4154
+ return candidate["version"] === 1 && typeof candidate["cachedAt"] === "string" && isDashboardStats(candidate["stats"]);
4155
+ }
4156
+ function loadStatsCache(settingsPath) {
4157
+ return Effect31.gen(function* () {
4158
+ const fs = yield* FileSystem12.FileSystem;
4159
+ const cachePath = getStatsCachePath(settingsPath);
4160
+ const raw = yield* fs.readFileString(cachePath).pipe(Effect31.catchAll(() => Effect31.succeed(null)));
4161
+ if (raw === null) {
4162
+ return null;
4163
+ }
4164
+ const parsed = yield* Effect31.try({
4165
+ try: () => JSON.parse(raw),
4166
+ catch: () => null
4167
+ }).pipe(Effect31.catchAll(() => Effect31.succeed(null)));
4168
+ return parsed !== null && isStatsCacheFile(parsed) ? parsed : null;
4169
+ });
4170
+ }
4171
+ function persistStatsCache(settingsPath, stats, expectedEpoch) {
4172
+ return Effect31.gen(function* () {
4173
+ const fs = yield* FileSystem12.FileSystem;
4174
+ const cachePath = getStatsCachePath(settingsPath);
4175
+ const cacheDir = dirname2(cachePath);
4176
+ const cachedAt = new Date().toISOString();
4177
+ yield* fs.makeDirectory(cacheDir, { recursive: true }).pipe(Effect31.catchAll(() => Effect31.void));
4178
+ if (getRefreshEpoch(cachePath) !== expectedEpoch) {
4179
+ return null;
4180
+ }
4181
+ const tmpPath = join14(cacheDir, `.stats-cache-${Date.now()}.tmp`);
4182
+ const payload = {
4183
+ version: 1,
4184
+ cachedAt,
4185
+ stats
4186
+ };
4187
+ const wroteTempFile = yield* fs.writeFileString(tmpPath, JSON.stringify(payload, null, 2)).pipe(Effect31.map(() => true), Effect31.catchAll(() => Effect31.succeed(false)));
4188
+ if (!wroteTempFile) {
4189
+ return null;
4190
+ }
4191
+ if (getRefreshEpoch(cachePath) !== expectedEpoch) {
4192
+ yield* fs.remove(tmpPath).pipe(Effect31.catchAll(() => Effect31.void));
4193
+ return null;
4194
+ }
4195
+ const renamed = yield* fs.rename(tmpPath, cachePath).pipe(Effect31.map(() => true), Effect31.catchAll(() => fs.remove(tmpPath).pipe(Effect31.catchAll(() => Effect31.void), Effect31.map(() => false))));
4196
+ if (!renamed) {
4197
+ return null;
4198
+ }
4199
+ return getRefreshEpoch(cachePath) === expectedEpoch ? cachedAt : null;
4200
+ });
4201
+ }
4202
+ function computeAndPersistStats(settingsPath, registry, expectedEpoch) {
4203
+ return Effect31.gen(function* () {
3905
4204
  const stats = yield* scanStats(registry);
3906
- return { stats };
4205
+ const cachedAt = yield* persistStatsCache(settingsPath, stats, expectedEpoch);
4206
+ return { stats, ...cachedAt ? { cachedAt } : {} };
4207
+ });
4208
+ }
4209
+ function refreshStats(settingsPath, registry) {
4210
+ return Effect31.gen(function* () {
4211
+ const cachePath = getStatsCachePath(settingsPath);
4212
+ const expectedEpoch = getRefreshEpoch(cachePath);
4213
+ refreshingCachePaths.add(cachePath);
4214
+ const result = yield* computeAndPersistStats(settingsPath, registry, expectedEpoch);
4215
+ return { ...result, refreshing: false };
4216
+ }).pipe(Effect31.ensuring(Effect31.sync(() => {
4217
+ refreshingCachePaths.delete(getStatsCachePath(settingsPath));
4218
+ })));
4219
+ }
4220
+ function scheduleRefresh(settingsPath, registry) {
4221
+ return Effect31.gen(function* () {
4222
+ const cachePath = getStatsCachePath(settingsPath);
4223
+ if (refreshingCachePaths.has(cachePath)) {
4224
+ return;
4225
+ }
4226
+ const expectedEpoch = getRefreshEpoch(cachePath);
4227
+ refreshingCachePaths.add(cachePath);
4228
+ yield* computeAndPersistStats(settingsPath, registry, expectedEpoch).pipe(Effect31.catchAllCause(() => Effect31.void), Effect31.ensuring(Effect31.sync(() => {
4229
+ refreshingCachePaths.delete(cachePath);
4230
+ })), Effect31.forkDaemon);
4231
+ });
4232
+ }
4233
+ function getStats(settingsPath, registry) {
4234
+ return Effect31.gen(function* () {
4235
+ const cachePath = getStatsCachePath(settingsPath);
4236
+ const cached = yield* loadStatsCache(settingsPath);
4237
+ if (!cached) {
4238
+ return yield* refreshStats(settingsPath, registry);
4239
+ }
4240
+ const bootTime = getRefreshBootTime(cachePath);
4241
+ const shouldRefresh = cached.cachedAt < bootTime;
4242
+ if (shouldRefresh) {
4243
+ yield* scheduleRefresh(settingsPath, registry);
4244
+ }
4245
+ return {
4246
+ stats: cached.stats,
4247
+ cachedAt: cached.cachedAt,
4248
+ refreshing: shouldRefresh || refreshingCachePaths.has(cachePath)
4249
+ };
4250
+ });
4251
+ }
4252
+ function invalidateStatsCache(settingsPath) {
4253
+ return Effect31.gen(function* () {
4254
+ const fs = yield* FileSystem12.FileSystem;
4255
+ const cachePath = getStatsCachePath(settingsPath);
4256
+ bumpRefreshEpoch(cachePath);
4257
+ yield* fs.remove(cachePath).pipe(Effect31.catchAll(() => Effect31.void));
3907
4258
  });
3908
4259
  }
3909
4260
 
@@ -3919,37 +4270,41 @@ function getVersion(state) {
3919
4270
  // ../../packages/server/src/effect/server-services.ts
3920
4271
  class KloviServices extends Context4.Tag("@klovi/KloviServices")() {
3921
4272
  }
3922
- var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
4273
+ var KloviServicesLive = Layer6.effect(KloviServices, Effect32.gen(function* () {
3923
4274
  const config = yield* ServerConfig;
3924
4275
  const { settingsPath } = config;
3925
4276
  const versionState = makeVersionState(config.version, config.commit);
3926
4277
  const settings = yield* loadSettings(settingsPath);
3927
4278
  let registry = yield* createRegistry(settings);
3928
- const refreshRegistry = () => Effect31.gen(function* () {
4279
+ const refreshRegistry = () => Effect32.gen(function* () {
3929
4280
  const freshSettings = yield* loadSettings(settingsPath);
3930
4281
  registry = yield* createRegistry(freshSettings);
3931
4282
  });
3932
4283
  return {
3933
4284
  acceptRisks: () => completeOnboarding(settingsPath),
3934
- getVersion: () => Effect31.succeed(getVersion(versionState)),
3935
- getStats: () => getStats(registry),
4285
+ getVersion: () => Effect32.succeed(getVersion(versionState)),
4286
+ getStats: () => getStats(settingsPath, registry),
3936
4287
  getProjects: () => getProjects(registry),
3937
4288
  getSessions: (params) => getSessions(registry, params),
3938
4289
  getSession: (params) => getSession(registry, params),
4290
+ getSessionHead: (params) => getSessionHead(registry, params),
4291
+ getSessionTail: (params) => getSessionTail(registry, params),
3939
4292
  getSubAgent: (params) => getSubAgent(registry, params),
3940
4293
  searchSessions: () => searchSessions(registry),
3941
4294
  getPluginSettings: () => getPluginSettings(settingsPath),
3942
- updatePluginSetting: (params) => Effect31.gen(function* () {
4295
+ updatePluginSetting: (params) => Effect32.gen(function* () {
3943
4296
  const result = yield* updatePluginSetting(settingsPath, params);
3944
4297
  yield* refreshRegistry();
4298
+ yield* invalidateStatsCache(settingsPath);
3945
4299
  return result;
3946
4300
  }),
3947
4301
  getGeneralSettings: () => getGeneralSettings(settingsPath),
3948
4302
  updateGeneralSettings: (params) => updateGeneralSettings(settingsPath, params),
3949
4303
  isFirstLaunch: () => isFirstLaunch(settingsPath),
3950
- resetSettings: () => Effect31.gen(function* () {
4304
+ resetSettings: () => Effect32.gen(function* () {
3951
4305
  const result = yield* resetSettings(settingsPath);
3952
4306
  yield* refreshRegistry();
4307
+ yield* invalidateStatsCache(settingsPath);
3953
4308
  return result;
3954
4309
  }),
3955
4310
  getUpdateSettings: () => getUpdateSettings(settingsPath),
@@ -3962,7 +4317,7 @@ var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
3962
4317
  // ../../packages/server/src/effect/bootstrap.ts
3963
4318
  function getDefaultSettingsPath() {
3964
4319
  const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
3965
- return join14(home, ".klovi", "settings.json");
4320
+ return join15(home, ".klovi", "settings.json");
3966
4321
  }
3967
4322
  function detectRuntime(requested = "auto") {
3968
4323
  if (requested !== "auto") {
@@ -3998,28 +4353,28 @@ async function bootstrapServer(options, makeServe) {
3998
4353
  resolveAddress = resolve;
3999
4354
  rejectAddress = reject;
4000
4355
  });
4001
- const addressCapture = Layer8.effectDiscard(HttpServer.addressWith((address) => Effect32.gen(function* () {
4356
+ const addressCapture = Layer8.effectDiscard(HttpServer.addressWith((address) => Effect33.gen(function* () {
4002
4357
  const addr = address;
4003
4358
  const url2 = `http://${addr.hostname}:${addr.port}`;
4004
- yield* Effect32.log(`Klovi server listening on ${url2}`);
4359
+ yield* Effect33.log(`Klovi server listening on ${url2}`);
4005
4360
  resolveAddress(url2);
4006
4361
  })));
4007
4362
  const serveLayer = makeServe();
4008
4363
  const fullLayer = Layer8.merge(serveLayer, addressCapture).pipe(Layer8.provide(servicesLayer), Layer8.provide(configLayer), Layer8.provide(platformLayer));
4009
- const fiber = Effect32.runFork(Layer8.launch(fullLayer));
4010
- Effect32.runFork(Fiber.join(fiber).pipe(Effect32.catchAllCause((cause) => Effect32.sync(() => rejectAddress(Cause.squash(cause))))));
4364
+ const fiber = Effect33.runFork(Layer8.launch(fullLayer));
4365
+ Effect33.runFork(Fiber.join(fiber).pipe(Effect33.catchAllCause((cause) => Effect33.sync(() => rejectAddress(Cause.squash(cause))))));
4011
4366
  const url = await addressPromise;
4012
4367
  return {
4013
4368
  url,
4014
4369
  stop: () => {
4015
- Effect32.runFork(Fiber.interrupt(fiber));
4370
+ Effect33.runFork(Fiber.interrupt(fiber));
4016
4371
  }
4017
4372
  };
4018
4373
  }
4019
4374
 
4020
4375
  // ../../packages/server/src/effect/http-app.ts
4021
4376
  import { HttpRouter, HttpServer as HttpServer2, HttpServerRequest, HttpServerResponse } from "@effect/platform";
4022
- import { Effect as Effect33 } from "effect";
4377
+ import { Effect as Effect34 } from "effect";
4023
4378
 
4024
4379
  // ../../packages/server/src/rpc-error.ts
4025
4380
  class RPCError extends Error {
@@ -4060,7 +4415,7 @@ function mapDomainErrorToStatus(err) {
4060
4415
  const message = err instanceof Error ? err.message : "Internal server error";
4061
4416
  return { status: 500, message };
4062
4417
  }
4063
- var rpcHandler = Effect33.gen(function* () {
4418
+ var rpcHandler = Effect34.gen(function* () {
4064
4419
  const services = yield* KloviServices;
4065
4420
  const routeParams = yield* HttpRouter.params;
4066
4421
  const req = yield* HttpServerRequest.HttpServerRequest;
@@ -4070,7 +4425,7 @@ var rpcHandler = Effect33.gen(function* () {
4070
4425
  }
4071
4426
  if (!isRpcMethod(method, services)) {
4072
4427
  const httpNotFound = 404;
4073
- return yield* Effect33.fail(new RPCError(httpNotFound, `Unknown method: ${method}`));
4428
+ return yield* Effect34.fail(new RPCError(httpNotFound, `Unknown method: ${method}`));
4074
4429
  }
4075
4430
  let params = {};
4076
4431
  const bodyText = yield* req.text;
@@ -4084,34 +4439,39 @@ var rpcHandler = Effect33.gen(function* () {
4084
4439
  const handler = services[method];
4085
4440
  const value = yield* handler(params);
4086
4441
  return HttpServerResponse.unsafeJson(value);
4087
- }).pipe(Effect33.catchAll((err) => {
4442
+ }).pipe(Effect34.catchAll((err) => {
4088
4443
  const { status, message } = mapDomainErrorToStatus(err);
4089
- return Effect33.succeed(HttpServerResponse.unsafeJson({ error: message }, { status }));
4444
+ return Effect34.succeed(HttpServerResponse.unsafeJson({ error: message }, { status }));
4090
4445
  }));
4091
- var emptyMethodHandler = Effect33.succeed(HttpServerResponse.unsafeJson({ error: "Method name required" }, { status: 400 }));
4446
+ var emptyMethodHandler = Effect34.succeed(HttpServerResponse.unsafeJson({ error: "Method name required" }, { status: 400 }));
4092
4447
  var makeRpcRouter = () => HttpRouter.empty.pipe(HttpRouter.post("/api/rpc/", emptyMethodHandler), HttpRouter.post("/api/rpc/:method", rpcHandler));
4093
- var notFoundHandler = Effect33.succeed(HttpServerResponse.unsafeJson({ error: "Not found" }, { status: 404 }));
4094
- var makeHttpApp = () => makeRpcRouter().pipe(Effect33.catchTag("RouteNotFound", () => notFoundHandler));
4448
+ var notFoundHandler = Effect34.succeed(HttpServerResponse.unsafeJson({ error: "Not found" }, { status: 404 }));
4449
+ var makeHttpApp = () => makeRpcRouter().pipe(Effect34.catchTag("RouteNotFound", () => notFoundHandler));
4095
4450
  var makeServeLayer = () => makeHttpApp().pipe(HttpServer2.serve());
4096
4451
 
4097
4452
  // src/http-app.ts
4098
4453
  import { HttpServer as HttpServer3 } from "@effect/platform";
4099
- import { Effect as Effect35 } from "effect";
4454
+ import { Effect as Effect36 } from "effect";
4100
4455
 
4101
4456
  // src/static-handler.ts
4102
4457
  import { HttpServerRequest as HttpServerRequest2, HttpServerResponse as HttpServerResponse2 } from "@effect/platform";
4103
- import { Effect as Effect34 } from "effect";
4104
- var makeStaticHandler = (staticDir) => Effect34.gen(function* () {
4458
+ import { Effect as Effect35 } from "effect";
4459
+ var notFound = HttpServerResponse2.unsafeJson({ error: "Not found" }, { status: 404 });
4460
+ var isNavigationRequest = (pathname) => {
4461
+ const lastSegment2 = pathname.split("/").pop() ?? "";
4462
+ return !lastSegment2.includes(".");
4463
+ };
4464
+ var makeStaticHandler = (staticDir) => Effect35.gen(function* () {
4105
4465
  const req = yield* HttpServerRequest2.HttpServerRequest;
4106
4466
  const url = new URL(req.url, "http://localhost");
4107
4467
  const filePath = url.pathname === "/" ? "/index.html" : url.pathname;
4108
- return yield* HttpServerResponse2.file(`${staticDir}${filePath}`).pipe(Effect34.orElse(() => HttpServerResponse2.file(`${staticDir}/index.html`)), Effect34.orElse(() => Effect34.succeed(HttpServerResponse2.unsafeJson({ error: "Not found" }, { status: 404 }))));
4468
+ return yield* HttpServerResponse2.file(`${staticDir}${filePath}`).pipe(Effect35.orElse(() => isNavigationRequest(url.pathname) ? HttpServerResponse2.file(`${staticDir}/index.html`).pipe(Effect35.orElse(() => Effect35.succeed(notFound))) : Effect35.succeed(notFound)));
4109
4469
  });
4110
4470
 
4111
4471
  // src/http-app.ts
4112
4472
  var makePackageHttpApp = (staticDir) => {
4113
4473
  const router = makeRpcRouter();
4114
- return router.pipe(Effect35.catchTag("RouteNotFound", () => makeStaticHandler(staticDir)));
4474
+ return router.pipe(Effect36.catchTag("RouteNotFound", () => makeStaticHandler(staticDir)));
4115
4475
  };
4116
4476
  var makePackageServeLayer = (staticDir) => {
4117
4477
  if (!staticDir) {