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