@cookielab.io/klovi 3.2.1 → 3.4.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
@@ -16,12 +16,6 @@ var __export = (target, all) => {
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
18
18
 
19
- // ../../packages/plugin-core/src/ids.ts
20
- var BUILTIN_KLOVI_PLUGIN_IDS;
21
- var init_ids = __esm(() => {
22
- BUILTIN_KLOVI_PLUGIN_IDS = ["claude-code", "codex-cli", "opencode"];
23
- });
24
-
25
19
  // ../../packages/plugin-core/src/iso-time.ts
26
20
  function sortByIsoDesc(items, select) {
27
21
  items.sort((a, b) => select(b).localeCompare(select(a)));
@@ -29,8 +23,9 @@ function sortByIsoDesc(items, select) {
29
23
  function maxIso(values) {
30
24
  let latest = "";
31
25
  for (const value of values) {
32
- if (value > latest)
26
+ if (value > latest) {
33
27
  latest = value;
28
+ }
34
29
  }
35
30
  return latest;
36
31
  }
@@ -38,8 +33,9 @@ function epochMsToIso(epochMs) {
38
33
  return new Date(epochMs).toISOString();
39
34
  }
40
35
  function epochSecondsToIso(epochSeconds) {
41
- return new Date(epochSeconds * 1000).toISOString();
36
+ return new Date(epochSeconds * MS_PER_SECOND).toISOString();
42
37
  }
38
+ var MS_PER_SECOND = 1000;
43
39
 
44
40
  // ../../packages/plugin-core/src/plugin-config.ts
45
41
  import { Context } from "effect";
@@ -62,8 +58,9 @@ import { join } from "node:path";
62
58
  import { FileSystem } from "@effect/platform";
63
59
  import { Effect } from "effect";
64
60
  function stripT3CodeSuffix(path) {
65
- if (!T3CODE_SUFFIX_REGEX.test(path))
61
+ if (!T3CODE_SUFFIX_REGEX.test(path)) {
66
62
  return null;
63
+ }
67
64
  const parentPath = path.replace(T3CODE_SUFFIX_REGEX, "");
68
65
  const lastSlash = parentPath.lastIndexOf("/");
69
66
  const projectName = lastSlash === -1 ? parentPath : parentPath.slice(lastSlash + 1);
@@ -74,18 +71,22 @@ function resolveGitWorktree(worktreePath) {
74
71
  const fs = yield* FileSystem.FileSystem;
75
72
  const dotGitPath = join(worktreePath, ".git");
76
73
  const info = yield* fs.stat(dotGitPath).pipe(Effect.catchAll(() => Effect.succeed(null)));
77
- if (!info || info.type !== "File")
74
+ if (!info || info.type !== "File") {
78
75
  return worktreePath;
76
+ }
79
77
  const content = yield* fs.readFileString(dotGitPath).pipe(Effect.catchAll(() => Effect.succeed("")));
80
- if (!content)
78
+ if (!content) {
81
79
  return worktreePath;
80
+ }
82
81
  const match = GITDIR_WORKTREE_REGEX.exec(content);
83
- if (!match?.[1])
82
+ if (!match?.groups?.["gitdir"]) {
84
83
  return worktreePath;
85
- const gitdir = match[1];
84
+ }
85
+ const gitdir = match.groups["gitdir"];
86
86
  const worktreeIdx = gitdir.indexOf(GIT_WORKTREES_SEGMENT);
87
- if (worktreeIdx === -1)
87
+ if (worktreeIdx === -1) {
88
88
  return worktreePath;
89
+ }
89
90
  return gitdir.slice(0, worktreeIdx);
90
91
  });
91
92
  }
@@ -111,15 +112,18 @@ function collectT3CodeEntries(projects) {
111
112
  function buildNameMap(projects, t3codeProjects) {
112
113
  const nameToResolvedPaths = new Map;
113
114
  for (const project of projects) {
114
- if (t3codeProjects.has(project))
115
+ if (t3codeProjects.has(project)) {
115
116
  continue;
117
+ }
116
118
  const name = lastSegment(project.resolvedPath);
117
- if (!name)
119
+ if (!name) {
118
120
  continue;
121
+ }
119
122
  const existing = nameToResolvedPaths.get(name);
120
123
  if (existing) {
121
- if (!existing.includes(project.resolvedPath))
124
+ if (!existing.includes(project.resolvedPath)) {
122
125
  existing.push(project.resolvedPath);
126
+ }
123
127
  } else {
124
128
  nameToResolvedPaths.set(name, [project.resolvedPath]);
125
129
  }
@@ -129,12 +133,14 @@ function buildNameMap(projects, t3codeProjects) {
129
133
  function resolveEntry(entry, nameMap) {
130
134
  return Effect.gen(function* () {
131
135
  const candidates = nameMap.get(entry.projectName);
132
- if (!candidates || candidates.length === 0)
136
+ if (!candidates || candidates.length === 0) {
133
137
  return;
138
+ }
134
139
  if (candidates.length === 1) {
135
- const resolvedPath = candidates[0];
136
- if (resolvedPath)
140
+ const [resolvedPath] = candidates;
141
+ if (resolvedPath) {
137
142
  entry.project.resolvedPath = resolvedPath;
143
+ }
138
144
  return;
139
145
  }
140
146
  const resolved = yield* resolveGitWorktree(entry.originalPath);
@@ -146,8 +152,9 @@ function resolveEntry(entry, nameMap) {
146
152
  function resolveT3CodePaths(projects) {
147
153
  return Effect.gen(function* () {
148
154
  const t3codeEntries = collectT3CodeEntries(projects);
149
- if (t3codeEntries.length === 0)
155
+ if (t3codeEntries.length === 0) {
150
156
  return;
157
+ }
151
158
  const t3codeProjects = new Set(t3codeEntries.map((e) => e.project));
152
159
  const nameMap = buildNameMap(projects, t3codeProjects);
153
160
  for (const entry of t3codeEntries) {
@@ -157,8 +164,8 @@ function resolveT3CodePaths(projects) {
157
164
  }
158
165
  var T3CODE_SUFFIX_REGEX, GITDIR_WORKTREE_REGEX, GIT_WORKTREES_SEGMENT = "/.git/worktrees/";
159
166
  var init_resolve_worktree = __esm(() => {
160
- T3CODE_SUFFIX_REGEX = /\/t3code-[a-f0-9]+$/;
161
- GITDIR_WORKTREE_REGEX = /^gitdir:\s*(.+?)\s*$/;
167
+ T3CODE_SUFFIX_REGEX = /\/t3code-[a-f0-9]+$/u;
168
+ GITDIR_WORKTREE_REGEX = /^gitdir:\s*(?<gitdir>.+?)\s*$/u;
162
169
  });
163
170
 
164
171
  // ../../packages/plugin-core/src/session-id.ts
@@ -181,9 +188,9 @@ var SESSION_ID_SEPARATOR = "::";
181
188
  import { Effect as Effect2, Layer } from "effect";
182
189
  function encodeResolvedPath(resolvedPath) {
183
190
  if (resolvedPath.startsWith("/")) {
184
- return resolvedPath.replace(/\//g, "-");
191
+ return resolvedPath.replace(/\//gu, "-");
185
192
  }
186
- return resolvedPath.replace(/[/\\:]/g, "-");
193
+ return resolvedPath.replace(/[/\\:]/gu, "-");
187
194
  }
188
195
 
189
196
  class PluginRegistry {
@@ -201,26 +208,38 @@ class PluginRegistry {
201
208
  }
202
209
  getPlugin(id) {
203
210
  const entry = this.plugins.get(id);
204
- if (!entry)
211
+ if (!entry) {
205
212
  throw new Error(`Plugin not found: ${id}`);
213
+ }
206
214
  return entry.plugin;
207
215
  }
208
216
  getPluginConfig(id) {
209
217
  const entry = this.plugins.get(id);
210
- if (!entry)
218
+ if (!entry) {
211
219
  throw new Error(`Plugin not found: ${id}`);
220
+ }
212
221
  return entry.config;
213
222
  }
214
223
  getAllPlugins() {
215
224
  return [...this.plugins.values()].map((entry) => entry.plugin);
216
225
  }
217
- discoverAllProjects() {
226
+ discoverPluginStates(includeSessions) {
218
227
  return Effect2.gen(this, function* () {
219
- const allProjects = [];
220
- for (const { plugin, configLayer } of this.plugins.values()) {
221
- const projects = yield* plugin.discoverProjects.pipe(Effect2.provide(configLayer), Effect2.catchAll(() => Effect2.succeed([])));
222
- allProjects.push(...projects);
228
+ const states = [];
229
+ for (const entry of this.plugins.values()) {
230
+ const discoveredIndex = includeSessions && entry.plugin.discoverIndex ? yield* entry.plugin.discoverIndex.pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed(undefined))) : undefined;
231
+ const projects = discoveredIndex?.projects ?? (yield* entry.plugin.discoverProjects.pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed([]))));
232
+ states.push({
233
+ entry,
234
+ projects,
235
+ ...discoveredIndex ? { sessionsByNativeId: discoveredIndex.sessionsByNativeId } : {}
236
+ });
223
237
  }
238
+ return states;
239
+ });
240
+ }
241
+ mergeProjects(allProjects) {
242
+ return Effect2.gen(function* () {
224
243
  yield* resolveT3CodePaths(allProjects);
225
244
  const projectsByPath = new Map;
226
245
  for (const project of allProjects) {
@@ -250,19 +269,59 @@ class PluginRegistry {
250
269
  return merged;
251
270
  });
252
271
  }
272
+ encodeSessions(pluginId, sessions) {
273
+ return sessions.map((session) => ({
274
+ ...session,
275
+ sessionId: this.sessionIdEncoder(pluginId, session.sessionId),
276
+ pluginId
277
+ }));
278
+ }
279
+ loadSourceSessions(state, source) {
280
+ const discoveredSessions = state.sessionsByNativeId?.get(source.nativeId);
281
+ if (discoveredSessions) {
282
+ return Effect2.succeed(this.encodeSessions(source.pluginId, discoveredSessions));
283
+ }
284
+ return state.entry.plugin.listSessions(source.nativeId).pipe(Effect2.provide(state.entry.configLayer), Effect2.catchAll(() => Effect2.succeed([])), Effect2.map((sessions) => this.encodeSessions(source.pluginId, sessions)));
285
+ }
286
+ discoverAllProjects() {
287
+ return Effect2.gen(this, function* () {
288
+ const states = yield* this.discoverPluginStates(false);
289
+ return yield* this.mergeProjects(states.flatMap((state) => state.projects));
290
+ });
291
+ }
292
+ discoverAllProjectsWithSessions() {
293
+ return Effect2.gen(this, function* () {
294
+ const states = yield* this.discoverPluginStates(true);
295
+ const mergedProjects = yield* this.mergeProjects(states.flatMap((state) => state.projects));
296
+ const statesByPluginId = new Map(states.map((state) => [state.entry.plugin.id, state]));
297
+ const sessionsByEncodedPath = new Map;
298
+ for (const project of mergedProjects) {
299
+ const allSessions = [];
300
+ for (const source of project.sources) {
301
+ const state = statesByPluginId.get(source.pluginId);
302
+ if (!state) {
303
+ continue;
304
+ }
305
+ allSessions.push(...yield* this.loadSourceSessions(state, source));
306
+ }
307
+ sortByIsoDesc(allSessions, (session) => session.timestamp);
308
+ sessionsByEncodedPath.set(project.encodedPath, allSessions);
309
+ }
310
+ return {
311
+ projects: mergedProjects,
312
+ sessionsByEncodedPath
313
+ };
314
+ });
315
+ }
253
316
  listAllSessions(project) {
254
317
  return Effect2.gen(this, function* () {
255
318
  const allSessions = [];
256
319
  for (const source of project.sources) {
257
320
  const entry = this.plugins.get(source.pluginId);
258
- if (!entry)
321
+ if (!entry) {
259
322
  continue;
260
- const sessions = yield* entry.plugin.listSessions(source.nativeId).pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed([])));
261
- allSessions.push(...sessions.map((session) => ({
262
- ...session,
263
- sessionId: this.sessionIdEncoder(source.pluginId, session.sessionId),
264
- pluginId: source.pluginId
265
- })));
323
+ }
324
+ allSessions.push(...yield* entry.plugin.listSessions(source.nativeId).pipe(Effect2.provide(entry.configLayer), Effect2.catchAll(() => Effect2.succeed([])), Effect2.map((sessions) => this.encodeSessions(source.pluginId, sessions))));
266
325
  }
267
326
  sortByIsoDesc(allSessions, (session) => session.timestamp);
268
327
  return allSessions;
@@ -293,7 +352,6 @@ var init_sqlite_service = __esm(() => {
293
352
 
294
353
  // ../../packages/plugin-core/src/index.ts
295
354
  var init_src = __esm(() => {
296
- init_ids();
297
355
  init_plugin_config();
298
356
  init_plugin_errors();
299
357
  init_plugin_registry();
@@ -316,8 +374,9 @@ function openOpenCodeDb() {
316
374
  const dbPath = yield* getOpenCodeDbPath();
317
375
  const fs = yield* FileSystem2.FileSystem;
318
376
  const exists = yield* fs.exists(dbPath);
319
- if (!exists)
377
+ if (!exists) {
320
378
  return null;
379
+ }
321
380
  const client = yield* SqliteClientTag;
322
381
  return yield* client.open(dbPath, { readonly: true });
323
382
  });
@@ -359,12 +418,14 @@ function inspectSchema(db) {
359
418
  function discoverOpenCodeProjects() {
360
419
  return Effect4.gen(function* () {
361
420
  const db = yield* openOpenCodeDb();
362
- if (!db)
421
+ if (!db) {
363
422
  return [];
423
+ }
364
424
  try {
365
425
  const schema = inspectSchema(db);
366
- if (!schema.hasRequiredTables)
426
+ if (!schema.hasRequiredTables) {
367
427
  return [];
428
+ }
368
429
  if (schema.hasProjectTable) {
369
430
  return discoverFromProjectTable(db, schema);
370
431
  }
@@ -403,8 +464,9 @@ function discoverFromProjectTable(db, schema) {
403
464
  function discoverFromSessions(db, schema) {
404
465
  const hasDirectory = schema.sessionColumns.has("directory");
405
466
  const hasProjectId = schema.sessionColumns.has("project_id");
406
- if (!hasDirectory && !hasProjectId)
467
+ if (!(hasDirectory || hasProjectId)) {
407
468
  return [];
469
+ }
408
470
  const groupCol = hasDirectory ? "directory" : "project_id";
409
471
  const rows = db.query(`SELECT ${groupCol} as group_key,
410
472
  count(*) as session_count,
@@ -447,12 +509,14 @@ function sessionRowToSummary(db, row) {
447
509
  function listOpenCodeSessions(nativeId) {
448
510
  return Effect4.gen(function* () {
449
511
  const db = yield* openOpenCodeDb();
450
- if (!db)
512
+ if (!db) {
451
513
  return [];
514
+ }
452
515
  try {
453
516
  const schema = inspectSchema(db);
454
- if (!schema.hasRequiredTables)
517
+ if (!schema.hasRequiredTables) {
455
518
  return [];
519
+ }
456
520
  const sessionRows = querySessionRows(db, schema, nativeId);
457
521
  return sessionRows.map((row) => sessionRowToSummary(db, row));
458
522
  } catch {
@@ -470,23 +534,27 @@ function getSessionPreview(db, sessionId) {
470
534
  LIMIT 10`).all(sessionId);
471
535
  for (const msg of msgRow) {
472
536
  const data = tryParseJson(msg.data);
473
- if (!data)
537
+ if (!data) {
474
538
  continue;
539
+ }
475
540
  assignPreviewModel(preview, data);
476
541
  assignPreviewFirstMessage(preview, data, db, msg.id);
477
- if (preview.firstMessage && preview.model)
542
+ if (preview.firstMessage && preview.model) {
478
543
  break;
544
+ }
479
545
  }
480
546
  return preview;
481
547
  }
482
548
  function assignPreviewModel(preview, data) {
483
- if (preview.model || data.role !== "assistant" || !data.modelID)
549
+ if (preview.model || data.role !== "assistant" || !data.modelID) {
484
550
  return;
551
+ }
485
552
  preview.model = data.modelID;
486
553
  }
487
554
  function assignPreviewFirstMessage(preview, data, db, messageId) {
488
- if (preview.firstMessage || data.role !== "user")
555
+ if (preview.firstMessage || data.role !== "user") {
489
556
  return;
557
+ }
490
558
  preview.firstMessage = getFirstUserTextPart(db, messageId);
491
559
  }
492
560
  function getFirstUserTextPart(db, messageId) {
@@ -494,7 +562,8 @@ function getFirstUserTextPart(db, messageId) {
494
562
  for (const part of parts) {
495
563
  const partData = tryParseJson(part.data);
496
564
  if (partData?.type === "text" && partData.text) {
497
- return partData.text.slice(0, 200);
565
+ const maxPreviewLength = 200;
566
+ return partData.text.slice(0, maxPreviewLength);
498
567
  }
499
568
  }
500
569
  return "";
@@ -510,8 +579,9 @@ function partToContentBlock(partData, nextToolUseId) {
510
579
  switch (partData.type) {
511
580
  case "text": {
512
581
  const textPart = partData;
513
- if (textPart.ignored)
582
+ if (textPart.ignored) {
514
583
  return null;
584
+ }
515
585
  return { type: "text", text: textPart.text };
516
586
  }
517
587
  case "reasoning": {
@@ -527,7 +597,7 @@ function partToContentBlock(partData, nextToolUseId) {
527
597
  }
528
598
  }
529
599
  function buildToolCall(toolPart, nextToolUseId) {
530
- const state = toolPart.state;
600
+ const { state } = toolPart;
531
601
  const toolId = toolPart.callID || nextToolUseId();
532
602
  switch (state.status) {
533
603
  case "completed":
@@ -555,6 +625,10 @@ function buildToolCall(toolPart, nextToolUseId) {
555
625
  result: "[Tool execution was interrupted]",
556
626
  isError: true
557
627
  };
628
+ default: {
629
+ const _exhaustive = state;
630
+ return _exhaustive;
631
+ }
558
632
  }
559
633
  }
560
634
  function createUserTurn(text, timestamp, uuid) {
@@ -565,15 +639,15 @@ function createUserTurn(text, timestamp, uuid) {
565
639
  text
566
640
  };
567
641
  }
568
- function createAssistantTurn(model, timestamp, uuid, contentBlocks, usage, stopReason) {
642
+ function createAssistantTurn(options) {
569
643
  return {
570
644
  kind: "assistant",
571
- uuid,
572
- timestamp,
573
- model,
574
- contentBlocks,
575
- usage,
576
- stopReason
645
+ uuid: options.uuid,
646
+ timestamp: options.timestamp,
647
+ model: options.model,
648
+ contentBlocks: options.contentBlocks,
649
+ usage: options.usage,
650
+ stopReason: options.stopReason
577
651
  };
578
652
  }
579
653
  function collectUserText(parts) {
@@ -590,14 +664,16 @@ function collectContentBlocks(parts, nextToolUseId) {
590
664
  const blocks = [];
591
665
  for (const part of parts) {
592
666
  const block = partToContentBlock(part, nextToolUseId);
593
- if (block)
667
+ if (block) {
594
668
  blocks.push(block);
669
+ }
595
670
  }
596
671
  return blocks;
597
672
  }
598
673
  function tokensToUsage(tokens) {
599
- if (!tokens)
674
+ if (!tokens) {
600
675
  return;
676
+ }
601
677
  return {
602
678
  inputTokens: tokens.input,
603
679
  outputTokens: tokens.output,
@@ -627,12 +703,19 @@ function buildAssistantTurnFromMsg(msg, timestamp, nextToolUseId) {
627
703
  const model = data.modelID || "unknown";
628
704
  const contentBlocks = collectContentBlocks(msg.parts, nextToolUseId);
629
705
  const usage = tokensToUsage(data.tokens) ?? extractStepFinishUsage(msg.parts);
630
- return createAssistantTurn(model, timestamp, msg.id, contentBlocks, usage, data.finish);
706
+ return createAssistantTurn({
707
+ model,
708
+ timestamp,
709
+ uuid: msg.id,
710
+ contentBlocks,
711
+ ...usage === undefined ? {} : { usage },
712
+ ...data.finish === undefined ? {} : { stopReason: data.finish }
713
+ });
631
714
  }
632
715
  function buildOpenCodeTurns(messages) {
633
716
  let toolUseCounter = 0;
634
717
  const nextToolUseId = () => {
635
- toolUseCounter++;
718
+ toolUseCounter += 1;
636
719
  return `opencode-tool-${toolUseCounter}`;
637
720
  };
638
721
  const turns = [];
@@ -767,18 +850,14 @@ var init_node_sqlite = __esm(() => {
767
850
  readOnly: options?.readonly ?? true
768
851
  });
769
852
  return {
770
- query(sql) {
853
+ query: (sql) => {
771
854
  const stmt = db.prepare(sql);
772
855
  return {
773
- all(...params) {
774
- return stmt.all(...params);
775
- },
776
- get(...params) {
777
- return stmt.get(...params);
778
- }
856
+ all: (...params) => stmt.all(...params),
857
+ get: (...params) => stmt.get(...params)
779
858
  };
780
859
  },
781
- close() {
860
+ close: () => {
782
861
  db.close();
783
862
  }
784
863
  };
@@ -840,7 +919,7 @@ __export(exports_platform_node, {
840
919
  import { createServer } from "node:http";
841
920
  import { NodeContext, NodeHttpServer } from "@effect/platform-node";
842
921
  import { Layer as Layer7 } from "effect";
843
- var NodePluginLayer, makeNodeServerLayer = (options) => Layer7.merge(NodeHttpServer.layer(() => createServer(), options), NodeContext.layer);
922
+ var NodePluginLayer, makeNodeServerLayer = (options) => Layer7.mergeAll(NodeHttpServer.layer(() => createServer(), options), NodeContext.layer, NodeSqliteLayer);
844
923
  var init_platform_node = __esm(() => {
845
924
  init_src2();
846
925
  NodePluginLayer = Layer7.merge(NodeContext.layer, NodeSqliteLayer);
@@ -850,34 +929,16 @@ var init_platform_node = __esm(() => {
850
929
  import { execFile } from "node:child_process";
851
930
 
852
931
  // ../../packages/server/src/effect/bootstrap.ts
853
- import { join as join12 } from "node:path";
932
+ import { join as join15 } from "node:path";
854
933
  import { HttpServer } from "@effect/platform";
855
- import { Cause, Effect as Effect20, Fiber, Layer as Layer8 } from "effect";
934
+ import { Cause, Effect as Effect32, Fiber, Layer as Layer8 } from "effect";
856
935
 
857
936
  // ../../packages/server/src/effect/platform-bun.ts
858
937
  init_src2();
859
938
  import { BunContext, BunHttpServer } from "@effect/platform-bun";
860
939
  import { Layer as Layer5 } from "effect";
861
940
  var BunPluginLayer = Layer5.merge(BunContext.layer, BunSqliteLayer);
862
- var makeBunServerLayer = (options) => Layer5.merge(BunHttpServer.layer(options), BunContext.layer);
863
-
864
- // ../../packages/server/src/effect/plugin-runtime.ts
865
- init_src();
866
- import {
867
- Effect as Effect9,
868
- ManagedRuntime
869
- } from "effect";
870
- var pluginRuntime = ManagedRuntime.make(BunPluginLayer);
871
- function setPluginLayer(layer) {
872
- pluginRuntime = ManagedRuntime.make(layer);
873
- }
874
- function runPluginEffect(effect, config) {
875
- const provided = effect.pipe(Effect9.provide(makePluginConfigLayer(config)));
876
- return pluginRuntime.runPromise(provided);
877
- }
878
- function runRegistryEffect(effect) {
879
- return pluginRuntime.runPromise(effect);
880
- }
941
+ var makeBunServerLayer = (options) => Layer5.mergeAll(BunHttpServer.layer(options), BunContext.layer, BunSqliteLayer);
881
942
 
882
943
  // ../../packages/server/src/effect/server-config.ts
883
944
  import { Context as Context3 } from "effect";
@@ -886,46 +947,51 @@ class ServerConfig extends Context3.Tag("@klovi/ServerConfig")() {
886
947
  }
887
948
 
888
949
  // ../../packages/server/src/effect/server-services.ts
889
- import { Context as Context4, Effect as Effect19, Layer as Layer6 } from "effect";
950
+ import { Context as Context4, Effect as Effect31, Layer as Layer6 } from "effect";
890
951
 
891
- // ../../packages/server/src/services/app-services.ts
952
+ // ../../packages/server/src/services/auto-discover.ts
892
953
  init_src();
893
- import { access, rm } from "node:fs/promises";
954
+ import { Effect as Effect24 } from "effect";
894
955
 
895
956
  // ../../packages/plugin-claude-code/src/index.ts
896
957
  init_src();
897
- import { Effect as Effect13 } from "effect";
958
+ import { Effect as Effect12 } from "effect";
898
959
 
899
960
  // ../../packages/plugin-claude-code/src/discovery.ts
900
961
  init_src();
901
962
  import { join as join6 } from "node:path";
902
- import { Effect as Effect11 } from "effect";
963
+ import { Effect as Effect10 } from "effect";
903
964
 
904
965
  // ../../packages/plugin-claude-code/src/command-message.ts
905
- var COMMAND_ARGS_REGEX = /<command-args>([\s\S]*?)<\/command-args>/;
906
- var COMMAND_NAME_REGEX = /<command-name>([\s\S]*?)<\/command-name>/;
907
- var COMMAND_MESSAGE_TAG_REGEX = /<command-message>[\s\S]*?<\/command-message>/g;
908
- var COMMAND_NAME_TAG_REGEX = /<command-name>[\s\S]*?<\/command-name>/g;
966
+ var COMMAND_ARGS_REGEX = /<command-args>(?<content>[\s\S]*?)<\/command-args>/u;
967
+ var COMMAND_NAME_REGEX = /<command-name>(?<content>[\s\S]*?)<\/command-name>/u;
968
+ var COMMAND_MESSAGE_TAG_REGEX = /<command-message>[\s\S]*?<\/command-message>/gu;
969
+ var COMMAND_NAME_TAG_REGEX = /<command-name>[\s\S]*?<\/command-name>/gu;
909
970
  function cleanCommandMessage(text) {
910
- if (!text.includes("<command-message>"))
971
+ if (!text.includes("<command-message>")) {
911
972
  return text;
912
- const argsMatch = text.match(COMMAND_ARGS_REGEX);
913
- if (argsMatch?.[1])
914
- return argsMatch[1].trim();
915
- const nameMatch = text.match(COMMAND_NAME_REGEX);
916
- if (nameMatch?.[1])
917
- return nameMatch[1].trim();
973
+ }
974
+ const argsMatch = COMMAND_ARGS_REGEX.exec(text);
975
+ if (argsMatch?.groups?.["content"]) {
976
+ return argsMatch.groups["content"].trim();
977
+ }
978
+ const nameMatch = COMMAND_NAME_REGEX.exec(text);
979
+ if (nameMatch?.groups?.["content"]) {
980
+ return nameMatch.groups["content"].trim();
981
+ }
918
982
  return text.replace(COMMAND_MESSAGE_TAG_REGEX, "").replace(COMMAND_NAME_TAG_REGEX, "").trim();
919
983
  }
920
984
  function parseCommandMessage(text) {
921
- if (!text.includes("<command-message>"))
985
+ if (!text.includes("<command-message>")) {
922
986
  return null;
923
- const nameMatch = text.match(COMMAND_NAME_REGEX);
924
- const argsMatch = text.match(COMMAND_ARGS_REGEX);
925
- const name = nameMatch?.[1]?.trim() ?? "";
926
- const args = argsMatch?.[1]?.trim() ?? "";
927
- if (!name && !args)
987
+ }
988
+ const nameMatch = COMMAND_NAME_REGEX.exec(text);
989
+ const argsMatch = COMMAND_ARGS_REGEX.exec(text);
990
+ const name = nameMatch?.groups?.["content"]?.trim() ?? "";
991
+ const args = argsMatch?.groups?.["content"]?.trim() ?? "";
992
+ if (!(name || args)) {
928
993
  return null;
994
+ }
929
995
  return { name, args };
930
996
  }
931
997
 
@@ -933,15 +999,15 @@ function parseCommandMessage(text) {
933
999
  init_src();
934
1000
  import { join as join5 } from "node:path";
935
1001
  import { FileSystem as FileSystem4 } from "@effect/platform";
936
- import { Effect as Effect10 } from "effect";
937
- var WINDOWS_DRIVE_LETTER_REGEX = /^[A-Za-z]\//;
1002
+ import { Effect as Effect9 } from "effect";
1003
+ var WINDOWS_DRIVE_LETTER_REGEX = /^[A-Za-z]\//u;
938
1004
  function readDirEntriesSafe(dir) {
939
- return Effect10.gen(function* () {
1005
+ return Effect9.gen(function* () {
940
1006
  const fs = yield* FileSystem4.FileSystem;
941
- const names = yield* fs.readDirectory(dir).pipe(Effect10.catchAll(() => Effect10.succeed([])));
1007
+ const names = yield* fs.readDirectory(dir).pipe(Effect9.catchAll(() => Effect9.succeed([])));
942
1008
  const entries = [];
943
1009
  for (const name of names) {
944
- const info = yield* fs.stat(join5(dir, name)).pipe(Effect10.catchAll(() => Effect10.succeed(null)));
1010
+ const info = yield* fs.stat(join5(dir, name)).pipe(Effect9.catchAll(() => Effect9.succeed(null)));
945
1011
  if (info) {
946
1012
  entries.push({ name, isDirectory: info.type === "Directory" });
947
1013
  }
@@ -950,19 +1016,19 @@ function readDirEntriesSafe(dir) {
950
1016
  });
951
1017
  }
952
1018
  function listFilesBySuffix(dir, suffix) {
953
- return Effect10.gen(function* () {
1019
+ return Effect9.gen(function* () {
954
1020
  const fs = yield* FileSystem4.FileSystem;
955
- const names = yield* fs.readDirectory(dir).pipe(Effect10.catchAll(() => Effect10.succeed([])));
1021
+ const names = yield* fs.readDirectory(dir).pipe(Effect9.catchAll(() => Effect9.succeed([])));
956
1022
  return names.filter((name) => name.endsWith(suffix));
957
1023
  });
958
1024
  }
959
1025
  function listFilesWithMtime(dir, suffix) {
960
- return Effect10.gen(function* () {
1026
+ return Effect9.gen(function* () {
961
1027
  const fs = yield* FileSystem4.FileSystem;
962
1028
  const names = yield* listFilesBySuffix(dir, suffix);
963
1029
  const results = [];
964
1030
  for (const fileName of names) {
965
- const info = yield* fs.stat(join5(dir, fileName)).pipe(Effect10.catchAll(() => Effect10.succeed(null)));
1031
+ const info = yield* fs.stat(join5(dir, fileName)).pipe(Effect9.catchAll(() => Effect9.succeed(null)));
966
1032
  if (info?.mtime._tag === "Some") {
967
1033
  results.push({ fileName, mtime: info.mtime.value.toISOString() });
968
1034
  }
@@ -972,7 +1038,7 @@ function listFilesWithMtime(dir, suffix) {
972
1038
  });
973
1039
  }
974
1040
  function readTextPrefix(filePath, maxBytes) {
975
- return Effect10.gen(function* () {
1041
+ return Effect9.gen(function* () {
976
1042
  const fs = yield* FileSystem4.FileSystem;
977
1043
  const bytes = yield* fs.readFile(filePath);
978
1044
  const slice = bytes.subarray(0, maxBytes);
@@ -980,26 +1046,26 @@ function readTextPrefix(filePath, maxBytes) {
980
1046
  });
981
1047
  }
982
1048
  function readFileText(filePath) {
983
- return Effect10.gen(function* () {
1049
+ return Effect9.gen(function* () {
984
1050
  const fs = yield* FileSystem4.FileSystem;
985
1051
  return yield* fs.readFileString(filePath);
986
1052
  });
987
1053
  }
988
1054
  function fileExists(filePath) {
989
- return Effect10.gen(function* () {
1055
+ return Effect9.gen(function* () {
990
1056
  const fs = yield* FileSystem4.FileSystem;
991
1057
  return yield* fs.exists(filePath);
992
1058
  });
993
1059
  }
994
1060
  function decodeEncodedPath(encoded) {
995
1061
  if (encoded.startsWith("-")) {
996
- const withSlashes = encoded.slice(1).replace(/-/g, "/");
1062
+ const withSlashes = encoded.slice(1).replace(/-/gu, "/");
997
1063
  if (process.platform === "win32" && WINDOWS_DRIVE_LETTER_REGEX.test(withSlashes)) {
998
1064
  return `${withSlashes[0]}:${withSlashes.slice(1)}`;
999
1065
  }
1000
1066
  return `/${withSlashes}`;
1001
1067
  }
1002
- return encoded.replace(/-/g, "/");
1068
+ return encoded.replace(/-/gu, "/");
1003
1069
  }
1004
1070
 
1005
1071
  // ../../packages/plugin-claude-code/src/shared/jsonl-utils.ts
@@ -1010,8 +1076,9 @@ function iterateJsonl(text, visitor, options = {}) {
1010
1076
  const end = options.maxLines === undefined ? lines.length : Math.min(lines.length, start + options.maxLines);
1011
1077
  for (let i = start;i < end; i++) {
1012
1078
  const line = lines[i];
1013
- if (!line || !line.trim())
1079
+ if (!line?.trim()) {
1014
1080
  continue;
1081
+ }
1015
1082
  try {
1016
1083
  const parsed = JSON.parse(line);
1017
1084
  const shouldContinue = visitor({
@@ -1020,8 +1087,9 @@ function iterateJsonl(text, visitor, options = {}) {
1020
1087
  lineIndex: i,
1021
1088
  lineNumber: i + 1
1022
1089
  });
1023
- if (shouldContinue === false)
1090
+ if (shouldContinue === false) {
1024
1091
  break;
1092
+ }
1025
1093
  } catch (error) {
1026
1094
  options.onMalformed?.(line, i + 1, error);
1027
1095
  }
@@ -1029,11 +1097,13 @@ function iterateJsonl(text, visitor, options = {}) {
1029
1097
  }
1030
1098
 
1031
1099
  // ../../packages/plugin-claude-code/src/discovery.ts
1032
- var CWD_SCAN_BYTES = 64 * 1024;
1033
- var SESSION_META_SCAN_BYTES = 1024 * 1024;
1034
- var BRACKETED_TEXT_REGEX = /^\[.+\]$/;
1100
+ var BYTES_PER_KB = 1024;
1101
+ var CWD_SCAN_KB = 64;
1102
+ var CWD_SCAN_BYTES = CWD_SCAN_KB * BYTES_PER_KB;
1103
+ var SESSION_META_SCAN_BYTES = BYTES_PER_KB * BYTES_PER_KB;
1104
+ var BRACKETED_TEXT_REGEX = /^\[.+\]$/u;
1035
1105
  function inspectProjectSessions(projectDir, sessionFiles) {
1036
- return Effect11.gen(function* () {
1106
+ return Effect10.gen(function* () {
1037
1107
  const lastActivity = sessionFiles[0]?.mtime || "";
1038
1108
  let resolvedPath = "";
1039
1109
  for (const sessionFile of sessionFiles) {
@@ -1046,18 +1116,20 @@ function inspectProjectSessions(projectDir, sessionFiles) {
1046
1116
  });
1047
1117
  }
1048
1118
  function discoverClaudeProjects() {
1049
- return Effect11.gen(function* () {
1119
+ return Effect10.gen(function* () {
1050
1120
  const config = yield* PluginConfig;
1051
1121
  const projectsDir = join6(config.dataDir, "projects");
1052
1122
  const entries = yield* readDirEntriesSafe(projectsDir);
1053
1123
  const projects = [];
1054
1124
  for (const entry of entries) {
1055
- if (!entry.isDirectory)
1125
+ if (!entry.isDirectory) {
1056
1126
  continue;
1127
+ }
1057
1128
  const projectDir = join6(projectsDir, entry.name);
1058
1129
  const sessionFiles = yield* listFilesWithMtime(projectDir, ".jsonl");
1059
- if (sessionFiles.length === 0)
1130
+ if (sessionFiles.length === 0) {
1060
1131
  continue;
1132
+ }
1061
1133
  const projectInfo = yield* inspectProjectSessions(projectDir, sessionFiles);
1062
1134
  const resolvedPath = projectInfo.resolvedPath || decodeEncodedPath(entry.name);
1063
1135
  projects.push({
@@ -1075,7 +1147,7 @@ function discoverClaudeProjects() {
1075
1147
  }
1076
1148
  var PLAN_PREFIX = "Implement the following plan";
1077
1149
  function listClaudeSessions(nativeId) {
1078
- return Effect11.gen(function* () {
1150
+ return Effect10.gen(function* () {
1079
1151
  const config = yield* PluginConfig;
1080
1152
  const projectDir = join6(config.dataDir, "projects", nativeId);
1081
1153
  const files = yield* listFilesBySuffix(projectDir, ".jsonl");
@@ -1084,8 +1156,9 @@ function listClaudeSessions(nativeId) {
1084
1156
  const filePath = join6(projectDir, file);
1085
1157
  const sessionId = file.replace(".jsonl", "");
1086
1158
  const meta = yield* extractSessionMeta(filePath);
1087
- if (meta)
1159
+ if (meta) {
1088
1160
  sessions.push({ sessionId, pluginId: "claude-code", ...meta });
1161
+ }
1089
1162
  }
1090
1163
  classifySessionTypes(sessions);
1091
1164
  sortByIsoDesc(sessions, (session) => session.timestamp);
@@ -1097,8 +1170,9 @@ function classifySessionTypes(sessions) {
1097
1170
  for (const session of sessions) {
1098
1171
  if (session.firstMessage.startsWith(PLAN_PREFIX)) {
1099
1172
  session.sessionType = "implementation";
1100
- if (session.slug)
1173
+ if (session.slug) {
1101
1174
  implSlugs.add(session.slug);
1175
+ }
1102
1176
  }
1103
1177
  }
1104
1178
  for (const session of sessions) {
@@ -1108,15 +1182,16 @@ function classifySessionTypes(sessions) {
1108
1182
  }
1109
1183
  }
1110
1184
  function extractCwd(filePath) {
1111
- return Effect11.gen(function* () {
1112
- const text = yield* readTextPrefix(filePath, CWD_SCAN_BYTES).pipe(Effect11.catchAll(() => Effect11.succeed("")));
1113
- if (!text)
1185
+ return Effect10.gen(function* () {
1186
+ const text = yield* readTextPrefix(filePath, CWD_SCAN_BYTES).pipe(Effect10.catchAll(() => Effect10.succeed("")));
1187
+ if (!text) {
1114
1188
  return "";
1189
+ }
1115
1190
  let cwd = "";
1116
1191
  iterateJsonl(text, ({ parsed }) => {
1117
1192
  const obj = parsed;
1118
1193
  if (obj.cwd) {
1119
- cwd = obj.cwd;
1194
+ ({ cwd } = obj);
1120
1195
  return false;
1121
1196
  }
1122
1197
  return;
@@ -1125,12 +1200,14 @@ function extractCwd(filePath) {
1125
1200
  });
1126
1201
  }
1127
1202
  function extractTextFromContent(content) {
1128
- if (typeof content === "string")
1203
+ if (typeof content === "string") {
1129
1204
  return content;
1205
+ }
1130
1206
  if (Array.isArray(content)) {
1131
1207
  for (const block of content) {
1132
- if (block.type === "text" && "text" in block)
1208
+ if (block.type === "text" && "text" in block) {
1133
1209
  return block.text;
1210
+ }
1134
1211
  }
1135
1212
  }
1136
1213
  return "";
@@ -1139,29 +1216,35 @@ function isInternalMessage(text) {
1139
1216
  return text.startsWith("<local-command") || text.startsWith("<command-name") || BRACKETED_TEXT_REGEX.test(text.trim());
1140
1217
  }
1141
1218
  function isMetaComplete(meta) {
1142
- return !!(meta.timestamp && meta.slug && meta.firstMessage && meta.model && meta.gitBranch);
1219
+ return Boolean(meta.timestamp && meta.slug && meta.firstMessage && meta.model && meta.gitBranch);
1143
1220
  }
1144
1221
  function processMetaLine(obj, meta) {
1145
- if (obj.timestamp && !meta.timestamp)
1222
+ if (obj.timestamp && !meta.timestamp) {
1146
1223
  meta.timestamp = obj.timestamp;
1147
- if (obj.slug && !meta.slug)
1224
+ }
1225
+ if (obj.slug && !meta.slug) {
1148
1226
  meta.slug = obj.slug;
1149
- if (obj.gitBranch && !meta.gitBranch)
1227
+ }
1228
+ if (obj.gitBranch && !meta.gitBranch) {
1150
1229
  meta.gitBranch = obj.gitBranch;
1151
- if (obj.message?.model && !meta.model)
1230
+ }
1231
+ if (obj.message?.model && !meta.model) {
1152
1232
  meta.model = obj.message.model;
1233
+ }
1153
1234
  if (!meta.firstMessage && obj.type === "user" && !obj.isMeta && obj.message) {
1154
1235
  const raw = extractTextFromContent(obj.message.content);
1155
1236
  if (raw && !isInternalMessage(raw)) {
1156
- meta.firstMessage = cleanCommandMessage(raw).slice(0, 200);
1237
+ const maxPreviewLength = 200;
1238
+ meta.firstMessage = cleanCommandMessage(raw).slice(0, maxPreviewLength);
1157
1239
  }
1158
1240
  }
1159
1241
  }
1160
1242
  function extractSessionMeta(filePath) {
1161
- return Effect11.gen(function* () {
1162
- const text = yield* readTextPrefix(filePath, SESSION_META_SCAN_BYTES).pipe(Effect11.catchAll(() => Effect11.succeed("")));
1163
- if (!text)
1243
+ return Effect10.gen(function* () {
1244
+ const text = yield* readTextPrefix(filePath, SESSION_META_SCAN_BYTES).pipe(Effect10.catchAll(() => Effect10.succeed("")));
1245
+ if (!text) {
1164
1246
  return null;
1247
+ }
1165
1248
  const meta = {
1166
1249
  timestamp: "",
1167
1250
  slug: "",
@@ -1172,15 +1255,17 @@ function extractSessionMeta(filePath) {
1172
1255
  iterateJsonl(text, ({ parsed }) => {
1173
1256
  const obj = parsed;
1174
1257
  processMetaLine(obj, meta);
1175
- if (isMetaComplete(meta))
1258
+ if (isMetaComplete(meta)) {
1176
1259
  return false;
1260
+ }
1177
1261
  return;
1178
1262
  }, {
1179
1263
  maxLines: 50,
1180
1264
  onMalformed: () => {}
1181
1265
  });
1182
- if (!meta.timestamp || !meta.firstMessage)
1266
+ if (!(meta.timestamp && meta.firstMessage)) {
1183
1267
  return null;
1268
+ }
1184
1269
  return {
1185
1270
  timestamp: meta.timestamp,
1186
1271
  slug: meta.slug || "unknown",
@@ -1194,9 +1279,10 @@ function extractSessionMeta(filePath) {
1194
1279
  // ../../packages/plugin-claude-code/src/parser.ts
1195
1280
  init_src();
1196
1281
  import { join as join7 } from "node:path";
1197
- import { Effect as Effect12 } from "effect";
1282
+ import { Effect as Effect11 } from "effect";
1283
+ var MAX_RAW_LINE_LENGTH = 500;
1198
1284
  function loadClaudeSession(nativeId, sessionId) {
1199
- return Effect12.gen(function* () {
1285
+ return Effect11.gen(function* () {
1200
1286
  const config = yield* PluginConfig;
1201
1287
  const filePath = join7(config.dataDir, "projects", nativeId, `${sessionId}.jsonl`);
1202
1288
  const { rawLines, parseErrors } = yield* readJsonlLines(filePath);
@@ -1204,8 +1290,9 @@ function loadClaudeSession(nativeId, sessionId) {
1204
1290
  const slug = extractSlug(rawLines);
1205
1291
  const turns = buildTurns(rawLines, parseErrors);
1206
1292
  for (const turn of turns) {
1207
- if (turn.kind !== "assistant")
1293
+ if (turn.kind !== "assistant") {
1208
1294
  continue;
1295
+ }
1209
1296
  for (const block of turn.contentBlocks) {
1210
1297
  if (block.type === "tool_call" && block.call.name === "Task") {
1211
1298
  const agentId = subAgentMap.get(block.call.toolUseId);
@@ -1227,15 +1314,16 @@ function loadClaudeSession(nativeId, sessionId) {
1227
1314
  });
1228
1315
  }
1229
1316
  function parseSubAgentSession(sessionId, encodedPath, agentId) {
1230
- return Effect12.gen(function* () {
1317
+ return Effect11.gen(function* () {
1231
1318
  const config = yield* PluginConfig;
1232
1319
  const filePath = join7(config.dataDir, "projects", encodedPath, sessionId, "subagents", `agent-${agentId}.jsonl`);
1233
- const parsed = yield* readJsonlLines(filePath).pipe(Effect12.catchAll(() => Effect12.succeed({ rawLines: [], parseErrors: [] })));
1320
+ const parsed = yield* readJsonlLines(filePath).pipe(Effect11.catchAll(() => Effect11.succeed({ rawLines: [], parseErrors: [] })));
1234
1321
  const subAgentMap = extractSubAgentMap(parsed.rawLines);
1235
1322
  const turns = buildTurns(parsed.rawLines, parsed.parseErrors);
1236
1323
  for (const turn of turns) {
1237
- if (turn.kind !== "assistant")
1324
+ if (turn.kind !== "assistant") {
1238
1325
  continue;
1326
+ }
1239
1327
  for (const block of turn.contentBlocks) {
1240
1328
  if (block.type === "tool_call" && block.call.name === "Task") {
1241
1329
  const nestedAgentId = subAgentMap.get(block.call.toolUseId);
@@ -1248,26 +1336,29 @@ function parseSubAgentSession(sessionId, encodedPath, agentId) {
1248
1336
  return { sessionId, project: encodedPath, turns, pluginId: "claude-code" };
1249
1337
  });
1250
1338
  }
1251
- var AGENT_ID_RE = /agentId:\s*(\w+)/;
1339
+ var AGENT_ID_RE = /agentId:\s*(?<id>\w+)/u;
1252
1340
  function extractFromProgressEvent(line, map) {
1253
1341
  if (line.type === "progress" && line.parentToolUseID && line.data?.type === "agent_progress" && line.data.agentId) {
1254
1342
  map.set(line.parentToolUseID, line.data.agentId);
1255
1343
  }
1256
1344
  }
1257
1345
  function extractToolResultText(tr) {
1258
- if (typeof tr.content === "string")
1346
+ if (typeof tr.content === "string") {
1259
1347
  return tr.content;
1348
+ }
1260
1349
  if (Array.isArray(tr.content)) {
1261
1350
  return tr.content.filter((c) => c.type === "text" && ("text" in c)).map((c) => ("text" in c) ? c.text : "").join("");
1262
1351
  }
1263
1352
  return "";
1264
1353
  }
1265
1354
  function extractFromToolResult(line, map) {
1266
- if (line.type !== "user" || !line.message || !Array.isArray(line.message.content))
1355
+ if (line.type !== "user" || !line.message || !Array.isArray(line.message.content)) {
1267
1356
  return;
1357
+ }
1268
1358
  for (const block of line.message.content) {
1269
- if (block.type !== "tool_result")
1359
+ if (block.type !== "tool_result") {
1270
1360
  continue;
1361
+ }
1271
1362
  const tr = block;
1272
1363
  const match = AGENT_ID_RE.exec(extractToolResultText(tr));
1273
1364
  if (match?.[1]) {
@@ -1285,30 +1376,34 @@ function extractSubAgentMap(lines) {
1285
1376
  }
1286
1377
  function extractSlug(lines) {
1287
1378
  for (const line of lines) {
1288
- if (line.slug)
1379
+ if (line.slug) {
1289
1380
  return line.slug;
1381
+ }
1290
1382
  }
1291
1383
  return;
1292
1384
  }
1293
1385
  var PLAN_PREFIX2 = "Implement the following plan";
1294
- var STATUS_RE = /^\[.+\]$/;
1386
+ var STATUS_RE = /^\[.+\]$/u;
1295
1387
  function findPlanSessionId(turns, slug, sessions, currentSessionId) {
1296
1388
  const planTurn = turns.find((t) => t.kind === "user" && !STATUS_RE.test(t.text.trim()));
1297
- if (!planTurn || !planTurn.text.startsWith(PLAN_PREFIX2))
1389
+ if (!planTurn?.text.startsWith(PLAN_PREFIX2)) {
1298
1390
  return;
1299
- if (!slug)
1391
+ }
1392
+ if (!slug) {
1300
1393
  return;
1394
+ }
1301
1395
  const match = sessions.find((s) => s.slug === slug && s.sessionId !== currentSessionId);
1302
1396
  return match?.sessionId;
1303
1397
  }
1304
1398
  function findImplSessionId(slug, sessions, currentSessionId) {
1305
- if (!slug)
1399
+ if (!slug) {
1306
1400
  return;
1401
+ }
1307
1402
  const match = sessions.find((s) => s.slug === slug && s.sessionId !== currentSessionId && s.firstMessage.startsWith(PLAN_PREFIX2));
1308
1403
  return match?.sessionId;
1309
1404
  }
1310
1405
  function readJsonlLines(filePath) {
1311
- return Effect12.gen(function* () {
1406
+ return Effect11.gen(function* () {
1312
1407
  const text = yield* readFileText(filePath);
1313
1408
  const rawLines = [];
1314
1409
  const parseErrors = [];
@@ -1319,9 +1414,9 @@ function readJsonlLines(filePath) {
1319
1414
  parseErrors.push({
1320
1415
  kind: "parse_error",
1321
1416
  uuid: `parse-error-line-${lineNumber}`,
1322
- timestamp: rawLines[rawLines.length - 1]?.timestamp ?? "",
1417
+ timestamp: rawLines.at(-1)?.timestamp ?? "",
1323
1418
  lineNumber,
1324
- rawLine: line.length > 500 ? `${line.slice(0, 500)}… (truncated)` : line,
1419
+ rawLine: line.length > MAX_RAW_LINE_LENGTH ? `${line.slice(0, MAX_RAW_LINE_LENGTH)}… (truncated)` : line,
1325
1420
  errorType: "json_parse",
1326
1421
  errorDetails: error instanceof Error ? error.message : undefined
1327
1422
  });
@@ -1331,26 +1426,33 @@ function readJsonlLines(filePath) {
1331
1426
  });
1332
1427
  }
1333
1428
  function isDisplayableLine(l) {
1334
- if (l.type === "progress")
1429
+ if (l.type === "progress") {
1335
1430
  return false;
1336
- if (l.type === "file-history-snapshot")
1431
+ }
1432
+ if (l.type === "file-history-snapshot") {
1337
1433
  return false;
1338
- if (l.type === "summary")
1434
+ }
1435
+ if (l.type === "summary") {
1339
1436
  return false;
1340
- if (l.isMeta)
1437
+ }
1438
+ if (l.isMeta) {
1341
1439
  return false;
1342
- if (!l.message)
1440
+ }
1441
+ if (!l.message) {
1343
1442
  return false;
1443
+ }
1344
1444
  return true;
1345
1445
  }
1346
1446
  function collectToolResults(displayable) {
1347
1447
  const toolResults = new Map;
1348
1448
  for (const line of displayable) {
1349
- if (line.type !== "user" || !line.message)
1449
+ if (line.type !== "user" || !line.message) {
1350
1450
  continue;
1351
- const content = line.message.content;
1352
- if (!Array.isArray(content))
1451
+ }
1452
+ const { content } = line.message;
1453
+ if (!Array.isArray(content)) {
1353
1454
  continue;
1455
+ }
1354
1456
  for (const block of content) {
1355
1457
  if (block.type === "tool_result") {
1356
1458
  const tr = block;
@@ -1366,8 +1468,9 @@ function collectToolResults(displayable) {
1366
1468
  return toolResults;
1367
1469
  }
1368
1470
  function extractUserContent(content) {
1369
- if (typeof content === "string")
1471
+ if (typeof content === "string") {
1370
1472
  return { text: content, attachments: [] };
1473
+ }
1371
1474
  const textBlocks = content.filter((b) => b.type === "text");
1372
1475
  const text = textBlocks.map((b) => ("text" in b) ? b.text : "").join(`
1373
1476
  `);
@@ -1385,25 +1488,29 @@ function extractUserContent(content) {
1385
1488
  function isSkippedUserText(text) {
1386
1489
  return text.startsWith("<local-command") || text.startsWith("<command-name") || text.startsWith("<task-notification") || text.startsWith("<system-reminder");
1387
1490
  }
1388
- var BASH_INPUT_RE = /<bash-input>([\s\S]*?)<\/bash-input>/;
1389
- var BASH_OUTPUT_RE = /^<bash-stdout>([\s\S]*?)<\/bash-stdout>(?:<bash-stderr>([\s\S]*?)<\/bash-stderr>)?$/;
1390
- var IDE_OPENED_FILE_RE = /<ide_opened_file>[\s\S]*?opened the file (.*?) in the IDE[\s\S]*?<\/ide_opened_file>/;
1491
+ var BASH_INPUT_RE = /<bash-input>(?<input>[\s\S]*?)<\/bash-input>/u;
1492
+ var BASH_OUTPUT_RE = /^<bash-stdout>(?<stdout>[\s\S]*?)<\/bash-stdout>(?:<bash-stderr>(?<stderr>[\s\S]*?)<\/bash-stderr>)?$/u;
1493
+ var IDE_OPENED_FILE_RE = /<ide_opened_file>[\s\S]*?opened the file (?<file>.*?) in the IDE[\s\S]*?<\/ide_opened_file>/u;
1391
1494
  function parseSpecialUserContent(text) {
1392
1495
  const bashMatch = BASH_INPUT_RE.exec(text);
1393
- if (bashMatch?.[1] !== undefined)
1394
- return { bashInput: bashMatch[1] };
1496
+ if (bashMatch?.groups?.["input"] !== undefined) {
1497
+ return { bashInput: bashMatch.groups["input"] };
1498
+ }
1395
1499
  const outputMatch = BASH_OUTPUT_RE.exec(text);
1396
- if (outputMatch?.[1] !== undefined)
1397
- return { bashStdout: outputMatch[1], bashStderr: outputMatch[2] };
1500
+ if (outputMatch?.groups?.["stdout"] !== undefined) {
1501
+ return { bashStdout: outputMatch.groups["stdout"], bashStderr: outputMatch.groups?.["stderr"] };
1502
+ }
1398
1503
  const ideMatch = IDE_OPENED_FILE_RE.exec(text);
1399
- if (ideMatch?.[1] !== undefined)
1400
- return { ideOpenedFile: ideMatch[1] };
1504
+ if (ideMatch?.groups?.["file"] !== undefined) {
1505
+ return { ideOpenedFile: ideMatch.groups?.["file"] ?? "" };
1506
+ }
1401
1507
  return null;
1402
1508
  }
1403
1509
  function processUserLine(line) {
1404
- if (!line.message)
1510
+ if (!line.message) {
1405
1511
  return null;
1406
- const content = line.message.content;
1512
+ }
1513
+ const { content } = line.message;
1407
1514
  if (Array.isArray(content) && content.every((b) => b.type === "tool_result")) {
1408
1515
  return "tool_result_only";
1409
1516
  }
@@ -1412,19 +1519,20 @@ function processUserLine(line) {
1412
1519
  if (special) {
1413
1520
  return {
1414
1521
  kind: "user",
1415
- uuid: line.uuid || "",
1416
- timestamp: line.timestamp || "",
1522
+ uuid: line.uuid ?? "",
1523
+ timestamp: line.timestamp ?? "",
1417
1524
  text: "",
1418
1525
  ...special
1419
1526
  };
1420
1527
  }
1421
- if (isSkippedUserText(text))
1528
+ if (isSkippedUserText(text)) {
1422
1529
  return null;
1530
+ }
1423
1531
  const command = parseCommandMessage(text);
1424
1532
  return {
1425
1533
  kind: "user",
1426
- uuid: line.uuid || "",
1427
- timestamp: line.timestamp || "",
1534
+ uuid: line.uuid ?? "",
1535
+ timestamp: line.timestamp ?? "",
1428
1536
  text: command ? command.args : text,
1429
1537
  command: command ?? undefined,
1430
1538
  attachments: attachments.length > 0 ? attachments : undefined
@@ -1433,9 +1541,9 @@ function processUserLine(line) {
1433
1541
  function createAssistantTurn2(line) {
1434
1542
  return {
1435
1543
  kind: "assistant",
1436
- uuid: line.uuid || "",
1437
- timestamp: line.timestamp || "",
1438
- model: line.message?.model || "",
1544
+ uuid: line.uuid ?? "",
1545
+ timestamp: line.timestamp ?? "",
1546
+ model: line.message?.model ?? "",
1439
1547
  contentBlocks: []
1440
1548
  };
1441
1549
  }
@@ -1464,8 +1572,9 @@ function processContentBlock(block, current, toolResults) {
1464
1572
  }
1465
1573
  function processAssistantLine(line, current, toolResults) {
1466
1574
  const msg = line.message;
1467
- if (!msg)
1575
+ if (!msg) {
1468
1576
  return;
1577
+ }
1469
1578
  if (msg.usage) {
1470
1579
  current.usage = {
1471
1580
  inputTokens: msg.usage.input_tokens ?? 0,
@@ -1482,20 +1591,22 @@ function processAssistantLine(line, current, toolResults) {
1482
1591
  }
1483
1592
  }
1484
1593
  function flushAssistant(current, turns) {
1485
- if (current)
1594
+ if (current) {
1486
1595
  turns.push(current);
1596
+ }
1487
1597
  return null;
1488
1598
  }
1489
1599
  function mergeBashTurns(turns) {
1490
1600
  const merged = [];
1491
1601
  for (let i = 0;i < turns.length; i++) {
1492
1602
  const turn = turns[i];
1493
- if (!turn)
1603
+ if (!turn) {
1494
1604
  continue;
1605
+ }
1495
1606
  const next = turns[i + 1];
1496
1607
  if (turn.kind === "user" && turn.bashInput !== undefined && next?.kind === "user" && next.bashStdout !== undefined) {
1497
1608
  merged.push({ ...turn, bashStdout: next.bashStdout, bashStderr: next.bashStderr });
1498
- i++;
1609
+ i += 1;
1499
1610
  } else {
1500
1611
  merged.push(turn);
1501
1612
  }
@@ -1504,23 +1615,26 @@ function mergeBashTurns(turns) {
1504
1615
  }
1505
1616
  function handleUserLine(line, currentAssistant, turns) {
1506
1617
  const result = processUserLine(line);
1507
- if (result === "tool_result_only")
1618
+ if (result === "tool_result_only") {
1508
1619
  return currentAssistant;
1620
+ }
1509
1621
  const flushed = flushAssistant(currentAssistant, turns);
1510
- if (result)
1622
+ if (result) {
1511
1623
  turns.push(result);
1624
+ }
1512
1625
  return flushed;
1513
1626
  }
1514
1627
  function handleAssistantLine(line, currentAssistant, toolResults, structureErrors) {
1515
- if (!line.message)
1628
+ if (!line.message) {
1516
1629
  return currentAssistant;
1630
+ }
1517
1631
  if (!Array.isArray(line.message.content)) {
1518
1632
  structureErrors.push({
1519
1633
  kind: "parse_error",
1520
- uuid: `parse-error-${line.uuid || "unknown"}`,
1521
- timestamp: line.timestamp || "",
1634
+ uuid: `parse-error-${line.uuid ?? "unknown"}`,
1635
+ timestamp: line.timestamp ?? "",
1522
1636
  lineNumber: 0,
1523
- rawLine: JSON.stringify(line.message.content).slice(0, 500),
1637
+ rawLine: JSON.stringify(line.message.content).slice(0, MAX_RAW_LINE_LENGTH),
1524
1638
  errorType: "invalid_structure",
1525
1639
  errorDetails: `Assistant message content is ${typeof line.message.content}, expected array`
1526
1640
  });
@@ -1532,13 +1646,14 @@ function handleAssistantLine(line, currentAssistant, toolResults, structureError
1532
1646
  }
1533
1647
  function handleSystemLine(line, currentAssistant, turns) {
1534
1648
  flushAssistant(currentAssistant, turns);
1535
- if (!line.message)
1649
+ if (!line.message) {
1536
1650
  return null;
1651
+ }
1537
1652
  const text = typeof line.message.content === "string" ? line.message.content : "";
1538
1653
  turns.push({
1539
1654
  kind: "system",
1540
- uuid: line.uuid || "",
1541
- timestamp: line.timestamp || "",
1655
+ uuid: line.uuid ?? "",
1656
+ timestamp: line.timestamp ?? "",
1542
1657
  text
1543
1658
  });
1544
1659
  return null;
@@ -1567,8 +1682,9 @@ function buildTurns(lines, parseErrors = []) {
1567
1682
  return mergedTurns;
1568
1683
  }
1569
1684
  function extractToolResult(tr) {
1570
- if (typeof tr.content === "string")
1685
+ if (typeof tr.content === "string") {
1571
1686
  return { text: tr.content, images: [] };
1687
+ }
1572
1688
  if (Array.isArray(tr.content)) {
1573
1689
  const textParts = [];
1574
1690
  const images = [];
@@ -1596,20 +1712,20 @@ var claudeCodePlugin = {
1596
1712
  id: "claude-code",
1597
1713
  displayName: "Claude Code",
1598
1714
  getDefaultDataDir: () => null,
1599
- isDataAvailable: Effect13.gen(function* () {
1715
+ isDataAvailable: Effect12.gen(function* () {
1600
1716
  const config = yield* PluginConfig;
1601
- return yield* fileExists(config.dataDir).pipe(Effect13.catchAll(() => Effect13.succeed(false)));
1717
+ return yield* fileExists(config.dataDir).pipe(Effect12.catchAll(() => Effect12.succeed(false)));
1602
1718
  }),
1603
1719
  discoverProjects: discoverClaudeProjects(),
1604
1720
  listSessions: (nativeId) => listClaudeSessions(nativeId),
1605
- loadSession: (nativeId, sessionId) => loadClaudeSession(nativeId, sessionId).pipe(Effect13.map((r) => r.session), Effect13.catchAll((err) => Effect13.fail(new PluginError({
1721
+ loadSession: (nativeId, sessionId) => loadClaudeSession(nativeId, sessionId).pipe(Effect12.map((r) => r.session), Effect12.catchAll((err) => Effect12.fail(new PluginError({
1606
1722
  pluginId: "claude-code",
1607
1723
  operation: "loadSession",
1608
1724
  message: String(err),
1609
1725
  cause: err
1610
1726
  })))),
1611
- loadSessionDetail: (nativeId, sessionId) => Effect13.gen(function* () {
1612
- const [{ session, slug }, sessions] = yield* Effect13.all([
1727
+ loadSessionDetail: (nativeId, sessionId) => Effect12.gen(function* () {
1728
+ const [{ session, slug }, sessions] = yield* Effect12.all([
1613
1729
  loadClaudeSession(nativeId, sessionId),
1614
1730
  listClaudeSessions(nativeId)
1615
1731
  ]);
@@ -1618,7 +1734,7 @@ var claudeCodePlugin = {
1618
1734
  planSessionId: findPlanSessionId(session.turns, slug, sessions, sessionId),
1619
1735
  implSessionId: findImplSessionId(slug, sessions, sessionId)
1620
1736
  };
1621
- }).pipe(Effect13.catchAll((err) => Effect13.fail(new PluginError({
1737
+ }).pipe(Effect12.catchAll((err) => Effect12.fail(new PluginError({
1622
1738
  pluginId: "claude-code",
1623
1739
  operation: "loadSessionDetail",
1624
1740
  message: String(err),
@@ -1630,23 +1746,23 @@ var claudeCodePlugin = {
1630
1746
 
1631
1747
  // ../../packages/plugin-codex/src/index.ts
1632
1748
  init_src();
1633
- import { Effect as Effect18 } from "effect";
1749
+ import { Effect as Effect17 } from "effect";
1634
1750
 
1635
1751
  // ../../packages/plugin-codex/src/discovery.ts
1636
1752
  init_src();
1637
- import { Effect as Effect16 } from "effect";
1753
+ import { Effect as Effect15 } from "effect";
1638
1754
 
1639
1755
  // ../../packages/plugin-codex/src/session-index.ts
1640
1756
  init_src();
1641
1757
  import { join as join9 } from "node:path";
1642
1758
  import { FileSystem as FileSystem6 } from "@effect/platform";
1643
- import { Effect as Effect15 } from "effect";
1759
+ import { Effect as Effect14 } from "effect";
1644
1760
 
1645
1761
  // ../../packages/plugin-codex/src/shared/discovery-utils.ts
1646
1762
  import { FileSystem as FileSystem5 } from "@effect/platform";
1647
- import { Effect as Effect14 } from "effect";
1763
+ import { Effect as Effect13 } from "effect";
1648
1764
  function readTextPrefix2(filePath, maxBytes) {
1649
- return Effect14.gen(function* () {
1765
+ return Effect13.gen(function* () {
1650
1766
  const fs = yield* FileSystem5.FileSystem;
1651
1767
  const bytes = yield* fs.readFile(filePath);
1652
1768
  const slice = bytes.subarray(0, maxBytes);
@@ -1654,13 +1770,13 @@ function readTextPrefix2(filePath, maxBytes) {
1654
1770
  });
1655
1771
  }
1656
1772
  function readFileText2(filePath) {
1657
- return Effect14.gen(function* () {
1773
+ return Effect13.gen(function* () {
1658
1774
  const fs = yield* FileSystem5.FileSystem;
1659
1775
  return yield* fs.readFileString(filePath);
1660
1776
  });
1661
1777
  }
1662
1778
  function fileExists2(filePath) {
1663
- return Effect14.gen(function* () {
1779
+ return Effect13.gen(function* () {
1664
1780
  const fs = yield* FileSystem5.FileSystem;
1665
1781
  return yield* fs.exists(filePath);
1666
1782
  });
@@ -1674,8 +1790,9 @@ function iterateJsonl2(text, visitor, options = {}) {
1674
1790
  const end = options.maxLines === undefined ? lines.length : Math.min(lines.length, start + options.maxLines);
1675
1791
  for (let i = start;i < end; i++) {
1676
1792
  const line = lines[i];
1677
- if (!line || !line.trim())
1793
+ if (!line?.trim()) {
1678
1794
  continue;
1795
+ }
1679
1796
  try {
1680
1797
  const parsed = JSON.parse(line);
1681
1798
  const shouldContinue = visitor({
@@ -1684,8 +1801,9 @@ function iterateJsonl2(text, visitor, options = {}) {
1684
1801
  lineIndex: i,
1685
1802
  lineNumber: i + 1
1686
1803
  });
1687
- if (shouldContinue === false)
1804
+ if (shouldContinue === false) {
1688
1805
  break;
1806
+ }
1689
1807
  } catch (error) {
1690
1808
  options.onMalformed?.(line, i + 1, error);
1691
1809
  }
@@ -1693,7 +1811,10 @@ function iterateJsonl2(text, visitor, options = {}) {
1693
1811
  }
1694
1812
 
1695
1813
  // ../../packages/plugin-codex/src/session-index.ts
1696
- var FIRST_LINE_SCAN_BYTES = 512 * 1024;
1814
+ var BYTES_PER_KB2 = 1024;
1815
+ var FIRST_LINE_SCAN_KB = 512;
1816
+ var FIRST_LINE_SCAN_BYTES = FIRST_LINE_SCAN_KB * BYTES_PER_KB2;
1817
+ var MS_PER_SECOND2 = 1000;
1697
1818
  function isCodexSessionMeta(obj) {
1698
1819
  return typeof obj === "object" && obj !== null && "uuid" in obj && "cwd" in obj && "timestamps" in obj && typeof obj.uuid === "string" && typeof obj.cwd === "string";
1699
1820
  }
@@ -1701,12 +1822,13 @@ function isNewFormatMeta(obj) {
1701
1822
  return typeof obj === "object" && obj !== null && "type" in obj && obj.type === "session_meta" && "payload" in obj && typeof obj.payload === "object" && obj.payload !== null && typeof obj.payload.id === "string" && typeof obj.payload.cwd === "string";
1702
1823
  }
1703
1824
  function normalizeSessionMeta(parsed, fileMtimeEpoch) {
1704
- if (isCodexSessionMeta(parsed))
1825
+ if (isCodexSessionMeta(parsed)) {
1705
1826
  return parsed;
1827
+ }
1706
1828
  if (isNewFormatMeta(parsed)) {
1707
1829
  const { payload } = parsed;
1708
1830
  const isoTimestamp = payload.timestamp || parsed.timestamp;
1709
- const createdEpoch = isoTimestamp ? new Date(isoTimestamp).getTime() / 1000 : 0;
1831
+ const createdEpoch = isoTimestamp ? new Date(isoTimestamp).getTime() / MS_PER_SECOND2 : 0;
1710
1832
  const updatedEpoch = fileMtimeEpoch ?? createdEpoch;
1711
1833
  return {
1712
1834
  uuid: payload.id,
@@ -1722,11 +1844,13 @@ function isKnownModel(model) {
1722
1844
  return typeof model === "string" && model.length > 0 && model !== "unknown";
1723
1845
  }
1724
1846
  function extractTurnContextModel(parsed) {
1725
- if (typeof parsed !== "object" || parsed === null)
1847
+ if (typeof parsed !== "object" || parsed === null) {
1726
1848
  return null;
1849
+ }
1727
1850
  const event = parsed;
1728
- if (event.type !== "turn_context")
1851
+ if (event.type !== "turn_context") {
1729
1852
  return null;
1853
+ }
1730
1854
  return typeof event.payload?.model === "string" ? event.payload.model : null;
1731
1855
  }
1732
1856
  function inferModelFromPrefix(prefixText) {
@@ -1742,42 +1866,49 @@ function inferModelFromPrefix(prefixText) {
1742
1866
  return model;
1743
1867
  }
1744
1868
  function parseSessionMeta(filePath, fileMtimeEpoch) {
1745
- return Effect15.gen(function* () {
1746
- const prefix = yield* readTextPrefix2(filePath, FIRST_LINE_SCAN_BYTES).pipe(Effect15.catchAll(() => Effect15.succeed("")));
1747
- if (!prefix)
1869
+ return Effect14.gen(function* () {
1870
+ const prefix = yield* readTextPrefix2(filePath, FIRST_LINE_SCAN_BYTES).pipe(Effect14.catchAll(() => Effect14.succeed("")));
1871
+ if (!prefix) {
1748
1872
  return null;
1873
+ }
1749
1874
  const firstNewline = prefix.indexOf(`
1750
1875
  `);
1751
1876
  const firstLine = firstNewline === -1 ? prefix : prefix.slice(0, firstNewline);
1752
1877
  const trimmedFirstLine = firstLine.trim();
1753
- if (!trimmedFirstLine)
1878
+ if (!trimmedFirstLine) {
1754
1879
  return null;
1880
+ }
1755
1881
  try {
1756
1882
  const parsed = JSON.parse(trimmedFirstLine);
1757
1883
  const meta = normalizeSessionMeta(parsed, fileMtimeEpoch);
1758
- if (!meta)
1884
+ if (!meta) {
1759
1885
  return null;
1760
- if (isKnownModel(meta.model))
1886
+ }
1887
+ if (isKnownModel(meta.model)) {
1761
1888
  return meta;
1889
+ }
1762
1890
  const inferred = inferModelFromPrefix(prefix);
1763
- if (isKnownModel(inferred))
1891
+ if (isKnownModel(inferred)) {
1764
1892
  return { ...meta, model: inferred };
1765
- if (isKnownModel(meta.provider_id))
1893
+ }
1894
+ if (isKnownModel(meta.provider_id)) {
1766
1895
  return { ...meta, model: meta.provider_id };
1896
+ }
1767
1897
  return meta;
1768
1898
  } catch {}
1769
1899
  return null;
1770
1900
  });
1771
1901
  }
1772
1902
  function walkJsonlFiles(dir, visit) {
1773
- return Effect15.gen(function* () {
1903
+ return Effect14.gen(function* () {
1774
1904
  const fs = yield* FileSystem6.FileSystem;
1775
- const names = yield* fs.readDirectory(dir).pipe(Effect15.catchAll(() => Effect15.succeed([])));
1905
+ const names = yield* fs.readDirectory(dir).pipe(Effect14.catchAll(() => Effect14.succeed([])));
1776
1906
  for (const name of names) {
1777
1907
  const fullPath = join9(dir, name);
1778
- const info = yield* fs.stat(fullPath).pipe(Effect15.catchAll(() => Effect15.succeed(null)));
1779
- if (!info)
1908
+ const info = yield* fs.stat(fullPath).pipe(Effect14.catchAll(() => Effect14.succeed(null)));
1909
+ if (!info) {
1780
1910
  continue;
1911
+ }
1781
1912
  if (info.type === "Directory") {
1782
1913
  yield* walkJsonlFiles(fullPath, visit);
1783
1914
  continue;
@@ -1789,17 +1920,18 @@ function walkJsonlFiles(dir, visit) {
1789
1920
  });
1790
1921
  }
1791
1922
  function scanCodexSessions() {
1792
- return Effect15.gen(function* () {
1923
+ return Effect14.gen(function* () {
1793
1924
  const config = yield* PluginConfig;
1794
1925
  const fs = yield* FileSystem6.FileSystem;
1795
1926
  const sessionsDir = join9(config.dataDir, "sessions");
1796
1927
  const sessions = [];
1797
- yield* walkJsonlFiles(sessionsDir, (filePath, _fileName) => Effect15.gen(function* () {
1798
- const info = yield* fs.stat(filePath).pipe(Effect15.catchAll(() => Effect15.succeed(null)));
1799
- const fileMtimeEpoch = info?.mtime._tag === "Some" ? info.mtime.value.getTime() / 1000 : undefined;
1928
+ yield* walkJsonlFiles(sessionsDir, (filePath, _fileName) => Effect14.gen(function* () {
1929
+ const info = yield* fs.stat(filePath).pipe(Effect14.catchAll(() => Effect14.succeed(null)));
1930
+ const fileMtimeEpoch = info?.mtime._tag === "Some" ? info.mtime.value.getTime() / MS_PER_SECOND2 : undefined;
1800
1931
  const meta = yield* parseSessionMeta(filePath, fileMtimeEpoch);
1801
- if (!meta)
1932
+ if (!meta) {
1802
1933
  return;
1934
+ }
1803
1935
  if (info?.mtime._tag === "Some") {
1804
1936
  sessions.push({
1805
1937
  filePath,
@@ -1812,44 +1944,50 @@ function scanCodexSessions() {
1812
1944
  });
1813
1945
  }
1814
1946
  function walkForFile(dir, match) {
1815
- return Effect15.gen(function* () {
1947
+ return Effect14.gen(function* () {
1816
1948
  const fs = yield* FileSystem6.FileSystem;
1817
- const names = yield* fs.readDirectory(dir).pipe(Effect15.catchAll(() => Effect15.succeed([])));
1949
+ const names = yield* fs.readDirectory(dir).pipe(Effect14.catchAll(() => Effect14.succeed([])));
1818
1950
  for (const name of names) {
1819
1951
  const fullPath = join9(dir, name);
1820
- const info = yield* fs.stat(fullPath).pipe(Effect15.catchAll(() => Effect15.succeed(null)));
1821
- if (!info)
1952
+ const info = yield* fs.stat(fullPath).pipe(Effect14.catchAll(() => Effect14.succeed(null)));
1953
+ if (!info) {
1822
1954
  continue;
1823
- if (info.type !== "Directory" && match(name))
1955
+ }
1956
+ if (info.type !== "Directory" && match(name)) {
1824
1957
  return fullPath;
1958
+ }
1825
1959
  if (info.type === "Directory") {
1826
1960
  const found = yield* walkForFile(fullPath, match);
1827
- if (found)
1961
+ if (found) {
1828
1962
  return found;
1963
+ }
1829
1964
  }
1830
1965
  }
1831
1966
  return null;
1832
1967
  });
1833
1968
  }
1834
1969
  function findCodexSessionFileById(sessionId) {
1835
- return Effect15.gen(function* () {
1970
+ return Effect14.gen(function* () {
1836
1971
  const config = yield* PluginConfig;
1837
1972
  const fs = yield* FileSystem6.FileSystem;
1838
1973
  const sessionsDir = join9(config.dataDir, "sessions");
1839
1974
  const exactName = `${sessionId}.jsonl`;
1840
1975
  const suffix = `-${sessionId}.jsonl`;
1841
1976
  const filePath = yield* walkForFile(sessionsDir, (name) => name === exactName || name.endsWith(suffix));
1842
- if (!filePath)
1977
+ if (!filePath) {
1843
1978
  return null;
1844
- const info = yield* fs.stat(filePath).pipe(Effect15.catchAll(() => Effect15.succeed(null)));
1979
+ }
1980
+ const info = yield* fs.stat(filePath).pipe(Effect14.catchAll(() => Effect14.succeed(null)));
1845
1981
  return info?.type === "File" ? filePath : null;
1846
1982
  });
1847
1983
  }
1848
1984
 
1849
1985
  // ../../packages/plugin-codex/src/discovery.ts
1850
- var SESSION_TITLE_SCAN_BYTES = 256 * 1024;
1986
+ var BYTES_PER_KB3 = 1024;
1987
+ var SESSION_TITLE_SCAN_KB = 256;
1988
+ var SESSION_TITLE_SCAN_BYTES = SESSION_TITLE_SCAN_KB * BYTES_PER_KB3;
1851
1989
  function discoverCodexProjects() {
1852
- return Effect16.gen(function* () {
1990
+ return Effect15.gen(function* () {
1853
1991
  const sessions = yield* scanCodexSessions();
1854
1992
  const byCwd = new Map;
1855
1993
  for (const session of sessions) {
@@ -1864,8 +2002,9 @@ function discoverCodexProjects() {
1864
2002
  for (const [cwd, cwdSessions] of byCwd) {
1865
2003
  let lastActivity = "";
1866
2004
  for (const s of cwdSessions) {
1867
- if (s.mtime > lastActivity)
2005
+ if (s.mtime > lastActivity) {
1868
2006
  lastActivity = s.mtime;
2007
+ }
1869
2008
  }
1870
2009
  projects.push({
1871
2010
  pluginId: "codex-cli",
@@ -1885,13 +2024,15 @@ function extractFirstUserMessage(text) {
1885
2024
  iterateJsonl2(text, ({ parsed }) => {
1886
2025
  const event = parsed;
1887
2026
  if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item.text) {
1888
- message = event.item.text.slice(0, 200);
2027
+ const maxPreviewLength = 200;
2028
+ message = event.item.text.slice(0, maxPreviewLength);
1889
2029
  return false;
1890
2030
  }
1891
2031
  if (event.type === "event_msg" && event.payload?.type === "user_message") {
1892
- const text2 = event.payload.message || event.payload.text;
1893
- if (typeof text2 === "string" && text2) {
1894
- message = text2.slice(0, 200);
2032
+ const payloadText = event.payload.message || event.payload.text;
2033
+ if (typeof payloadText === "string" && payloadText) {
2034
+ const maxMsgLength = 200;
2035
+ message = payloadText.slice(0, maxMsgLength);
1895
2036
  return false;
1896
2037
  }
1897
2038
  }
@@ -1900,18 +2041,18 @@ function extractFirstUserMessage(text) {
1900
2041
  return message;
1901
2042
  }
1902
2043
  function listCodexSessions(nativeId) {
1903
- return Effect16.gen(function* () {
2044
+ return Effect15.gen(function* () {
1904
2045
  const allSessions = yield* scanCodexSessions();
1905
2046
  const matching = allSessions.filter((s) => s.meta.cwd === nativeId);
1906
2047
  const sessions = [];
1907
2048
  for (const s of matching) {
1908
- let firstMessage = s.meta.name || "";
2049
+ let firstMessage = s.meta.name ?? "";
1909
2050
  if (!firstMessage) {
1910
- const prefix = yield* readTextPrefix2(s.filePath, SESSION_TITLE_SCAN_BYTES).pipe(Effect16.catchAll(() => Effect16.succeed("")));
1911
- firstMessage = extractFirstUserMessage(prefix) || "";
2051
+ const prefix = yield* readTextPrefix2(s.filePath, SESSION_TITLE_SCAN_BYTES).pipe(Effect15.catchAll(() => Effect15.succeed("")));
2052
+ firstMessage = extractFirstUserMessage(prefix) ?? "";
1912
2053
  if (!firstMessage) {
1913
- const fullText = yield* readFileText2(s.filePath).pipe(Effect16.catchAll(() => Effect16.succeed("")));
1914
- firstMessage = extractFirstUserMessage(fullText) || "";
2054
+ const fullText = yield* readFileText2(s.filePath).pipe(Effect15.catchAll(() => Effect15.succeed("")));
2055
+ firstMessage = extractFirstUserMessage(fullText) ?? "";
1915
2056
  }
1916
2057
  firstMessage ||= "Codex session";
1917
2058
  }
@@ -1933,16 +2074,30 @@ function listCodexSessions(nativeId) {
1933
2074
 
1934
2075
  // ../../packages/plugin-codex/src/parser.ts
1935
2076
  init_src();
1936
- import { Effect as Effect17 } from "effect";
2077
+ import { Effect as Effect16 } from "effect";
1937
2078
  function isKnownModel2(model) {
1938
2079
  return typeof model === "string" && model.length > 0 && model !== "unknown";
1939
2080
  }
2081
+ function resolveCodexModel(metaInfo, turnContextModel) {
2082
+ if (isKnownModel2(metaInfo?.model)) {
2083
+ return metaInfo.model;
2084
+ }
2085
+ if (isKnownModel2(turnContextModel)) {
2086
+ return turnContextModel;
2087
+ }
2088
+ if (isKnownModel2(metaInfo?.provider_id)) {
2089
+ return metaInfo.provider_id;
2090
+ }
2091
+ return "unknown";
2092
+ }
1940
2093
  function extractTurnContextModel2(parsed) {
1941
- if (typeof parsed !== "object" || parsed === null)
2094
+ if (typeof parsed !== "object" || parsed === null) {
1942
2095
  return null;
2096
+ }
1943
2097
  const event = parsed;
1944
- if (event.type !== "turn_context")
2098
+ if (event.type !== "turn_context") {
1945
2099
  return null;
2100
+ }
1946
2101
  return typeof event.payload?.model === "string" ? event.payload.model : null;
1947
2102
  }
1948
2103
  function normalizeEventMsg(payload) {
@@ -1950,16 +2105,16 @@ function normalizeEventMsg(payload) {
1950
2105
  case "task_started":
1951
2106
  return { type: "turn.started" };
1952
2107
  case "user_message":
1953
- return { type: "user_message", text: payload.message || payload.text || "" };
2108
+ return { type: "user_message", text: payload.message ?? payload.text ?? "" };
1954
2109
  case "agent_message":
1955
2110
  return {
1956
2111
  type: "item.completed",
1957
- item: { type: "agent_message", text: payload.message || payload.text || "" }
2112
+ item: { type: "agent_message", text: payload.message ?? payload.text ?? "" }
1958
2113
  };
1959
2114
  case "agent_reasoning":
1960
2115
  return {
1961
2116
  type: "item.completed",
1962
- item: { type: "reasoning", text: payload.text || "" }
2117
+ item: { type: "reasoning", text: payload.text ?? "" }
1963
2118
  };
1964
2119
  case "token_count": {
1965
2120
  const src = payload.info?.last_token_usage ?? payload;
@@ -1979,8 +2134,9 @@ function normalizeEventMsg(payload) {
1979
2134
  }
1980
2135
  }
1981
2136
  function parseArguments(args) {
1982
- if (!args)
2137
+ if (!args) {
1983
2138
  return {};
2139
+ }
1984
2140
  if (typeof args === "string") {
1985
2141
  try {
1986
2142
  return JSON.parse(args);
@@ -1992,7 +2148,7 @@ function parseArguments(args) {
1992
2148
  }
1993
2149
  function normalizeResponseItem(payload) {
1994
2150
  if (payload.type === "function_call" || payload.type === "custom_tool_call") {
1995
- const name = payload.name || "unknown";
2151
+ const name = payload.name ?? "unknown";
1996
2152
  const args = parseArguments(payload.arguments);
1997
2153
  return {
1998
2154
  type: "item.completed",
@@ -2011,30 +2167,30 @@ function normalizeResponseItem(payload) {
2011
2167
  return {
2012
2168
  type: "tool_output",
2013
2169
  callId: payload.call_id,
2014
- text: payload.output || ""
2170
+ text: payload.output ?? ""
2015
2171
  };
2016
2172
  }
2017
2173
  return null;
2018
2174
  }
2019
- var OLD_FORMAT_TYPES = new Set([
2020
- "turn.started",
2021
- "turn.completed",
2022
- "item.completed",
2023
- "thread.started"
2024
- ]);
2175
+ var OLD_FORMAT_TYPES = new Set(["turn.started", "turn.completed", "item.completed", "thread.started"]);
2025
2176
  function normalizeEvent(raw) {
2026
- if (typeof raw !== "object" || raw === null || !("type" in raw))
2177
+ if (typeof raw !== "object" || raw === null || !("type" in raw)) {
2027
2178
  return null;
2179
+ }
2028
2180
  const obj = raw;
2029
- if (OLD_FORMAT_TYPES.has(obj.type))
2181
+ if (OLD_FORMAT_TYPES.has(obj.type)) {
2030
2182
  return raw;
2031
- const payload = obj.payload;
2032
- if (!payload)
2183
+ }
2184
+ const { payload } = obj;
2185
+ if (!payload) {
2033
2186
  return null;
2034
- if (obj.type === "event_msg")
2187
+ }
2188
+ if (obj.type === "event_msg") {
2035
2189
  return normalizeEventMsg(payload);
2036
- if (obj.type === "response_item")
2190
+ }
2191
+ if (obj.type === "response_item") {
2037
2192
  return normalizeResponseItem(payload);
2193
+ }
2038
2194
  return null;
2039
2195
  }
2040
2196
  function buildToolCallFromItem(item, nextToolUseId) {
@@ -2044,7 +2200,7 @@ function buildToolCallFromItem(item, nextToolUseId) {
2044
2200
  toolUseId: nextToolUseId(),
2045
2201
  name: "command_execution",
2046
2202
  input: { command: item.command },
2047
- result: item.aggregated_output || "",
2203
+ result: item.aggregated_output ?? "",
2048
2204
  isError: item.exit_code !== undefined && item.exit_code !== 0
2049
2205
  };
2050
2206
  case "file_change":
@@ -2060,7 +2216,7 @@ function buildToolCallFromItem(item, nextToolUseId) {
2060
2216
  toolUseId: nextToolUseId(),
2061
2217
  name: item.tool,
2062
2218
  input: item.arguments,
2063
- result: item.result || "",
2219
+ result: item.result ?? "",
2064
2220
  isError: false
2065
2221
  };
2066
2222
  case "web_search":
@@ -2107,9 +2263,9 @@ function itemToContentBlock(item, nextToolUseId) {
2107
2263
  }
2108
2264
  function handleTurnStarted(state, event, timestamp, nextUserTurnId) {
2109
2265
  flushAssistant2(state);
2110
- state.turnCount++;
2266
+ state.turnCount += 1;
2111
2267
  if (state.turnCount > 1) {
2112
- state.turns.push(createUserTurn2(event.text || "", timestamp, nextUserTurnId()));
2268
+ state.turns.push(createUserTurn2(event.text ?? "", timestamp, nextUserTurnId()));
2113
2269
  }
2114
2270
  }
2115
2271
  function handleTurnCompleted(state, event) {
@@ -2123,13 +2279,14 @@ function handleTurnCompleted(state, event) {
2123
2279
  }
2124
2280
  flushAssistant2(state);
2125
2281
  }
2126
- function handleItemCompleted(state, event, model, timestamp, nextToolUseId, nextAssistantTurnId) {
2127
- if (!event.item)
2282
+ function handleItemCompleted(state, event, ctx) {
2283
+ if (!event.item) {
2128
2284
  return;
2285
+ }
2129
2286
  if (!state.currentAssistant) {
2130
- state.currentAssistant = createAssistantTurn3(model, timestamp, nextAssistantTurnId());
2287
+ state.currentAssistant = createAssistantTurn3(ctx.model, ctx.timestamp, ctx.nextAssistantTurnId());
2131
2288
  }
2132
- const block = itemToContentBlock(event.item, nextToolUseId);
2289
+ const block = itemToContentBlock(event.item, ctx.nextToolUseId);
2133
2290
  if (block) {
2134
2291
  state.currentAssistant.contentBlocks.push(block);
2135
2292
  }
@@ -2143,17 +2300,18 @@ function flushAssistant2(state) {
2143
2300
  function handleUserMessage(state, event) {
2144
2301
  for (let i = state.turns.length - 1;i >= 0; i--) {
2145
2302
  const turn = state.turns[i];
2146
- if (!turn)
2303
+ if (!turn) {
2147
2304
  continue;
2305
+ }
2148
2306
  if (turn.kind === "user" && !turn.text) {
2149
- turn.text = event.text || "";
2307
+ turn.text = event.text ?? "";
2150
2308
  return;
2151
2309
  }
2152
2310
  }
2153
2311
  if (state.turnCount === 0) {
2154
- state.turnCount++;
2312
+ state.turnCount += 1;
2155
2313
  }
2156
- state.turns.push(createUserTurn2(event.text || "", "", "codex-user-first"));
2314
+ state.turns.push(createUserTurn2(event.text ?? "", "", "codex-user-first"));
2157
2315
  }
2158
2316
  function handleUsageUpdate(state, event) {
2159
2317
  if (state.currentAssistant && event.usage) {
@@ -2166,21 +2324,22 @@ function handleUsageUpdate(state, event) {
2166
2324
  }
2167
2325
  }
2168
2326
  function handleToolOutput(state, event) {
2169
- if (!event.callId)
2327
+ if (!event.callId) {
2170
2328
  return;
2329
+ }
2171
2330
  const toolCall = state.pendingToolCalls.get(event.callId);
2172
2331
  if (toolCall) {
2173
- toolCall.result = event.text || "";
2332
+ toolCall.result = event.text ?? "";
2174
2333
  }
2175
2334
  }
2176
- function handleGenericToolCall(state, event, model, timestamp, nextToolUseId, nextAssistantTurnId) {
2335
+ function handleGenericToolCall(state, event, ctx) {
2177
2336
  if (!state.currentAssistant) {
2178
- state.currentAssistant = createAssistantTurn3(model, timestamp, nextAssistantTurnId());
2337
+ state.currentAssistant = createAssistantTurn3(ctx.model, ctx.timestamp, ctx.nextAssistantTurnId());
2179
2338
  }
2180
2339
  const toolCall = {
2181
- toolUseId: event.callId || nextToolUseId(),
2182
- name: event.toolName || "unknown",
2183
- input: event.toolInput || {},
2340
+ toolUseId: event.callId ?? ctx.nextToolUseId(),
2341
+ name: event.toolName ?? "unknown",
2342
+ input: event.toolInput ?? {},
2184
2343
  result: "",
2185
2344
  isError: false
2186
2345
  };
@@ -2189,19 +2348,19 @@ function handleGenericToolCall(state, event, model, timestamp, nextToolUseId, ne
2189
2348
  }
2190
2349
  state.currentAssistant.contentBlocks.push({ type: "tool_call", call: toolCall });
2191
2350
  }
2192
- function dispatchEvent(state, event, model, timestamp, nextToolUseId, nextUserTurnId, nextAssistantTurnId) {
2351
+ function dispatchEvent(state, event, ctx) {
2193
2352
  switch (event.type) {
2194
2353
  case "turn.started":
2195
- handleTurnStarted(state, event, timestamp, nextUserTurnId);
2354
+ handleTurnStarted(state, event, ctx.timestamp, ctx.nextUserTurnId);
2196
2355
  break;
2197
2356
  case "turn.completed":
2198
2357
  handleTurnCompleted(state, event);
2199
2358
  break;
2200
2359
  case "item.completed":
2201
2360
  if (event.toolName) {
2202
- handleGenericToolCall(state, event, model, timestamp, nextToolUseId, nextAssistantTurnId);
2361
+ handleGenericToolCall(state, event, ctx);
2203
2362
  } else {
2204
- handleItemCompleted(state, event, model, timestamp, nextToolUseId, nextAssistantTurnId);
2363
+ handleItemCompleted(state, event, ctx);
2205
2364
  }
2206
2365
  break;
2207
2366
  case "usage_update":
@@ -2213,6 +2372,8 @@ function dispatchEvent(state, event, model, timestamp, nextToolUseId, nextUserTu
2213
2372
  case "tool_output":
2214
2373
  handleToolOutput(state, event);
2215
2374
  break;
2375
+ default:
2376
+ break;
2216
2377
  }
2217
2378
  }
2218
2379
  function buildCodexTurns(events, model, timestamp) {
@@ -2220,15 +2381,15 @@ function buildCodexTurns(events, model, timestamp) {
2220
2381
  let userTurnCounter = 0;
2221
2382
  let assistantTurnCounter = 0;
2222
2383
  const nextToolUseId = () => {
2223
- toolUseCounter++;
2384
+ toolUseCounter += 1;
2224
2385
  return `codex-tool-${toolUseCounter}`;
2225
2386
  };
2226
2387
  const nextUserTurnId = () => {
2227
- userTurnCounter++;
2388
+ userTurnCounter += 1;
2228
2389
  return `codex-user-${userTurnCounter}`;
2229
2390
  };
2230
2391
  const nextAssistantTurnId = () => {
2231
- assistantTurnCounter++;
2392
+ assistantTurnCounter += 1;
2232
2393
  return `codex-assistant-${assistantTurnCounter}`;
2233
2394
  };
2234
2395
  const state = {
@@ -2237,14 +2398,21 @@ function buildCodexTurns(events, model, timestamp) {
2237
2398
  turnCount: 0,
2238
2399
  pendingToolCalls: new Map
2239
2400
  };
2401
+ const ctx = {
2402
+ model,
2403
+ timestamp,
2404
+ nextToolUseId,
2405
+ nextUserTurnId,
2406
+ nextAssistantTurnId
2407
+ };
2240
2408
  for (const event of events) {
2241
- dispatchEvent(state, event, model, timestamp, nextToolUseId, nextUserTurnId, nextAssistantTurnId);
2409
+ dispatchEvent(state, event, ctx);
2242
2410
  }
2243
2411
  flushAssistant2(state);
2244
2412
  return state.turns;
2245
2413
  }
2246
2414
  function loadCodexSession(_nativeId, sessionId) {
2247
- return Effect17.gen(function* () {
2415
+ return Effect16.gen(function* () {
2248
2416
  const filePath = yield* findCodexSessionFileById(sessionId);
2249
2417
  if (!filePath) {
2250
2418
  return {
@@ -2278,7 +2446,7 @@ function loadCodexSession(_nativeId, sessionId) {
2278
2446
  }
2279
2447
  });
2280
2448
  const metaInfo = normalizeSessionMeta(meta);
2281
- const model = isKnownModel2(metaInfo?.model) ? metaInfo.model : isKnownModel2(turnContextModel) ? turnContextModel : isKnownModel2(metaInfo?.provider_id) ? metaInfo.provider_id : "unknown";
2449
+ const model = resolveCodexModel(metaInfo, turnContextModel);
2282
2450
  const timestamp = metaInfo ? epochSecondsToIso(metaInfo.timestamps.created) : "";
2283
2451
  const turns = buildCodexTurns(events, model, timestamp);
2284
2452
  return {
@@ -2298,13 +2466,13 @@ var codexCliPlugin = {
2298
2466
  id: "codex-cli",
2299
2467
  displayName: "Codex",
2300
2468
  getDefaultDataDir: () => null,
2301
- isDataAvailable: Effect18.gen(function* () {
2469
+ isDataAvailable: Effect17.gen(function* () {
2302
2470
  const config = yield* PluginConfig;
2303
- return yield* fileExists2(config.dataDir).pipe(Effect18.catchAll(() => Effect18.succeed(false)));
2471
+ return yield* fileExists2(config.dataDir).pipe(Effect17.catchAll(() => Effect17.succeed(false)));
2304
2472
  }),
2305
2473
  discoverProjects: discoverCodexProjects(),
2306
2474
  listSessions: (nativeId) => listCodexSessions(nativeId),
2307
- loadSession: (nativeId, sessionId) => loadCodexSession(nativeId, sessionId).pipe(Effect18.catchAll((err) => Effect18.fail(new PluginError({
2475
+ loadSession: (nativeId, sessionId) => loadCodexSession(nativeId, sessionId).pipe(Effect17.catchAll((err) => Effect17.fail(new PluginError({
2308
2476
  pluginId: "codex-cli",
2309
2477
  operation: "loadSession",
2310
2478
  message: String(err),
@@ -2313,68 +2481,1447 @@ var codexCliPlugin = {
2313
2481
  getResumeCommand: (sessionId) => `codex resume ${sessionId}`
2314
2482
  };
2315
2483
 
2316
- // ../../packages/server/src/services/catalog.ts
2317
- init_src2();
2318
- var BUILTIN_PLUGIN_DESCRIPTORS = [
2319
- {
2320
- plugin: claudeCodePlugin,
2321
- defaultDir: DEFAULT_CLAUDE_CODE_DIR
2322
- },
2323
- {
2324
- plugin: codexCliPlugin,
2325
- defaultDir: DEFAULT_CODEX_CLI_DIR
2326
- },
2327
- {
2328
- plugin: openCodePlugin,
2329
- defaultDir: DEFAULT_OPENCODE_DIR
2330
- }
2331
- ];
2332
- var BUILTIN_PLUGIN_ID_SET = new Set(BUILTIN_PLUGIN_DESCRIPTORS.map((descriptor) => descriptor.plugin.id));
2333
-
2334
- // ../../packages/server/src/services/settings.ts
2484
+ // ../../packages/plugin-cursor/src/index.ts
2335
2485
  init_src();
2336
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2337
- import { dirname, join as join11 } from "node:path";
2338
- function createDefaultPluginStates() {
2339
- return Object.fromEntries(BUILTIN_KLOVI_PLUGIN_IDS.map((pluginId) => [pluginId, { enabled: true, dataDir: null }]));
2486
+ import { FileSystem as FileSystem9 } from "@effect/platform";
2487
+ import { Effect as Effect23 } from "effect";
2488
+
2489
+ // ../../packages/plugin-cursor/src/config.ts
2490
+ import { posix, win32 } from "node:path";
2491
+ var LEADING_SLASHES_REGEX = /^\/+/u;
2492
+ var PATH_SEPARATOR_REGEX = /[:/\\]/gu;
2493
+ function getPlatform(options) {
2494
+ return options?.platform ?? process.platform;
2495
+ }
2496
+ function getEnv(options) {
2497
+ return options?.env ?? process.env;
2498
+ }
2499
+ function getPathApi(platform) {
2500
+ return platform === "win32" ? win32 : posix;
2501
+ }
2502
+ function getHomeDir(options) {
2503
+ const platform = getPlatform(options);
2504
+ const env = getEnv(options);
2505
+ if (platform === "win32") {
2506
+ return env["USERPROFILE"] ?? env["HOME"] ?? "";
2507
+ }
2508
+ return env["HOME"] ?? env["USERPROFILE"] ?? "";
2509
+ }
2510
+ function getDefaultCursorDir(options) {
2511
+ const platform = getPlatform(options);
2512
+ return getPathApi(platform).join(getHomeDir(options), ".cursor");
2513
+ }
2514
+ function getCursorAppSupportRoot(options) {
2515
+ const platform = getPlatform(options);
2516
+ const env = getEnv(options);
2517
+ const pathApi = getPathApi(platform);
2518
+ if (platform === "darwin") {
2519
+ return pathApi.join(getHomeDir(options), "Library", "Application Support", "Cursor");
2520
+ }
2521
+ if (platform === "win32") {
2522
+ const appData = env["APPDATA"] ?? pathApi.join(getHomeDir(options), "AppData", "Roaming");
2523
+ return pathApi.join(appData, "Cursor");
2524
+ }
2525
+ const xdgConfigHome = env["XDG_CONFIG_HOME"] ?? pathApi.join(getHomeDir(options), ".config");
2526
+ return pathApi.join(xdgConfigHome, "Cursor");
2340
2527
  }
2341
- function getDefaultSettings() {
2342
- return {
2343
- version: 1,
2344
- plugins: createDefaultPluginStates(),
2345
- general: {
2346
- showSecurityWarning: true
2347
- },
2348
- updates: {
2349
- channel: "stable",
2350
- checkIntervalHours: 6,
2351
- autoDownload: true
2352
- }
2353
- };
2528
+ function getCursorGlobalDbPath(options) {
2529
+ const platform = getPlatform(options);
2530
+ return getPathApi(platform).join(getCursorAppSupportRoot(options), "User", "globalStorage", "state.vscdb");
2354
2531
  }
2355
- async function loadSettings(path) {
2356
- try {
2357
- const content = await readFile(path, "utf-8");
2358
- const parsed = JSON.parse(content);
2359
- if (parsed["version"] !== 1 || typeof parsed["plugins"] !== "object") {
2360
- return getDefaultSettings();
2361
- }
2362
- return parsed;
2363
- } catch {
2364
- return getDefaultSettings();
2365
- }
2532
+ function getCursorWorkspaceStorageDir(options) {
2533
+ const platform = getPlatform(options);
2534
+ return getPathApi(platform).join(getCursorAppSupportRoot(options), "User", "workspaceStorage");
2366
2535
  }
2367
- async function saveSettings(path, settings) {
2368
- const dir = dirname(path);
2369
- await mkdir(dir, { recursive: true });
2370
- const tmpPath = join11(dir, `.settings-${Date.now()}.tmp`);
2371
- await writeFile(tmpPath, JSON.stringify(settings, null, 2));
2372
- await rename(tmpPath, path);
2536
+ function encodeCursorProjectPath(projectPath) {
2537
+ return projectPath.replace(LEADING_SLASHES_REGEX, "").replace(PATH_SEPARATOR_REGEX, "-");
2373
2538
  }
2539
+ var DEFAULT_CURSOR_DIR = getDefaultCursorDir();
2374
2540
 
2375
- // ../../packages/server/src/services/stats.ts
2541
+ // ../../packages/plugin-cursor/src/discovery.ts
2376
2542
  init_src();
2377
- function emptyStats(projects = 0) {
2543
+ import { join as join12 } from "node:path";
2544
+ import { fileURLToPath } from "node:url";
2545
+ import { Effect as Effect21 } from "effect";
2546
+
2547
+ // ../../packages/plugin-cursor/src/db.ts
2548
+ init_src();
2549
+ import { FileSystem as FileSystem7 } from "@effect/platform";
2550
+ import { Effect as Effect18 } from "effect";
2551
+ function openCursorDbIfExists(dbPath) {
2552
+ return Effect18.gen(function* () {
2553
+ const fs = yield* FileSystem7.FileSystem;
2554
+ const exists = yield* fs.exists(dbPath).pipe(Effect18.catchAll(() => Effect18.succeed(false)));
2555
+ if (!exists) {
2556
+ return null;
2557
+ }
2558
+ const client = yield* SqliteClientTag;
2559
+ return yield* client.open(dbPath, { readonly: true });
2560
+ });
2561
+ }
2562
+ function openCursorGlobalDb() {
2563
+ return openCursorDbIfExists(getCursorGlobalDbPath());
2564
+ }
2565
+ function getCursorWorkspaceStorageDirEffect() {
2566
+ return Effect18.succeed(getCursorWorkspaceStorageDir());
2567
+ }
2568
+
2569
+ // ../../packages/plugin-cursor/src/plans.ts
2570
+ import { basename } from "node:path";
2571
+ import { Effect as Effect20 } from "effect";
2572
+
2573
+ // ../../packages/plugin-cursor/src/shared/discovery-utils.ts
2574
+ init_src();
2575
+ import { join as join11 } from "node:path";
2576
+ import { FileSystem as FileSystem8 } from "@effect/platform";
2577
+ import { Effect as Effect19 } from "effect";
2578
+ function readDirEntriesSafe2(dir) {
2579
+ return Effect19.gen(function* () {
2580
+ const fs = yield* FileSystem8.FileSystem;
2581
+ const names = yield* fs.readDirectory(dir).pipe(Effect19.catchAll(() => Effect19.succeed([])));
2582
+ const entries = [];
2583
+ for (const name of names) {
2584
+ const info = yield* fs.stat(join11(dir, name)).pipe(Effect19.catchAll(() => Effect19.succeed(null)));
2585
+ if (info) {
2586
+ entries.push({ name, isDirectory: info.type === "Directory" });
2587
+ }
2588
+ }
2589
+ return entries;
2590
+ });
2591
+ }
2592
+ function readFileText3(filePath) {
2593
+ return Effect19.gen(function* () {
2594
+ const fs = yield* FileSystem8.FileSystem;
2595
+ return yield* fs.readFileString(filePath);
2596
+ });
2597
+ }
2598
+ function fileExists3(filePath) {
2599
+ return Effect19.gen(function* () {
2600
+ const fs = yield* FileSystem8.FileSystem;
2601
+ return yield* fs.exists(filePath).pipe(Effect19.catchAll(() => Effect19.succeed(false)));
2602
+ });
2603
+ }
2604
+ function listFilesWithMtime2(dir, suffix) {
2605
+ return Effect19.gen(function* () {
2606
+ const fs = yield* FileSystem8.FileSystem;
2607
+ const names = yield* fs.readDirectory(dir).pipe(Effect19.catchAll(() => Effect19.succeed([])));
2608
+ const results = [];
2609
+ for (const name of names) {
2610
+ if (!name.endsWith(suffix)) {
2611
+ continue;
2612
+ }
2613
+ const info = yield* fs.stat(join11(dir, name)).pipe(Effect19.catchAll(() => Effect19.succeed(null)));
2614
+ if (info?.mtime._tag === "Some") {
2615
+ results.push({ fileName: name, mtime: info.mtime.value.toISOString() });
2616
+ }
2617
+ }
2618
+ sortByIsoDesc(results, (item) => item.mtime);
2619
+ return results;
2620
+ });
2621
+ }
2622
+
2623
+ // ../../packages/plugin-cursor/src/plans.ts
2624
+ var LEADING_NEWLINES_REGEX = /^\n+/u;
2625
+ var PLAN_FILE_SUFFIX_REGEX = /\.plan\.md$/u;
2626
+ function parseFrontmatterValue(rawValue) {
2627
+ const trimmed = rawValue.trim();
2628
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
2629
+ return trimmed.slice(1, -1);
2630
+ }
2631
+ return trimmed;
2632
+ }
2633
+ function parsePlanFrontmatter(text) {
2634
+ if (!text.startsWith(`---
2635
+ `)) {
2636
+ return { body: text, name: "" };
2637
+ }
2638
+ const lines = text.split(`
2639
+ `);
2640
+ const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
2641
+ if (endIndex === -1) {
2642
+ return { body: text, name: "" };
2643
+ }
2644
+ let name = "";
2645
+ for (let i = 1;i < endIndex; i++) {
2646
+ const line = lines[i]?.trim() ?? "";
2647
+ if (!line || line.startsWith("#")) {
2648
+ continue;
2649
+ }
2650
+ const separator = line.indexOf(":");
2651
+ if (separator === -1) {
2652
+ continue;
2653
+ }
2654
+ const key = line.slice(0, separator).trim();
2655
+ const value = parseFrontmatterValue(line.slice(separator + 1));
2656
+ if (key === "name") {
2657
+ name = value;
2658
+ }
2659
+ }
2660
+ return {
2661
+ body: lines.slice(endIndex + 1).join(`
2662
+ `).replace(LEADING_NEWLINES_REGEX, ""),
2663
+ name
2664
+ };
2665
+ }
2666
+ function extractFirstHeading(text) {
2667
+ for (const line of text.split(`
2668
+ `)) {
2669
+ const trimmed = line.trim();
2670
+ if (trimmed.startsWith("# ")) {
2671
+ return trimmed.slice(2).trim();
2672
+ }
2673
+ }
2674
+ return "";
2675
+ }
2676
+ function getPlanFallbackName(filePath) {
2677
+ return basename(filePath).replace(PLAN_FILE_SUFFIX_REGEX, "");
2678
+ }
2679
+ function readPlanDisplayName(filePath) {
2680
+ return readFileText3(filePath).pipe(Effect20.map((text) => {
2681
+ const parsed = parsePlanFrontmatter(text);
2682
+ return parsed.name || extractFirstHeading(parsed.body) || getPlanFallbackName(filePath);
2683
+ }));
2684
+ }
2685
+ function makeSystemTurn(text, timestamp) {
2686
+ return {
2687
+ kind: "system",
2688
+ uuid: "cursor-plan",
2689
+ timestamp,
2690
+ text
2691
+ };
2692
+ }
2693
+ function loadCursorPlanSession(plan) {
2694
+ return readFileText3(plan.filePath).pipe(Effect20.map((text) => {
2695
+ const parsed = parsePlanFrontmatter(text);
2696
+ return {
2697
+ sessionId: plan.rawSessionId,
2698
+ project: plan.projectPath,
2699
+ pluginId: "cursor",
2700
+ turns: [makeSystemTurn(parsed.body, plan.timestamp)]
2701
+ };
2702
+ }));
2703
+ }
2704
+
2705
+ // ../../packages/plugin-cursor/src/shared/json-utils.ts
2706
+ function tryParseJson2(value) {
2707
+ try {
2708
+ return JSON.parse(value);
2709
+ } catch {
2710
+ return;
2711
+ }
2712
+ }
2713
+
2714
+ // ../../packages/plugin-cursor/src/shared/jsonl-utils.ts
2715
+ function iterateJsonl3(text, visitor, options = {}) {
2716
+ const lines = text.split(`
2717
+ `);
2718
+ const start = Math.max(0, options.startAt ?? 0);
2719
+ const end = options.maxLines === undefined ? lines.length : Math.min(lines.length, start + options.maxLines);
2720
+ for (let i = start;i < end; i++) {
2721
+ const line = lines[i];
2722
+ if (!line?.trim()) {
2723
+ continue;
2724
+ }
2725
+ try {
2726
+ const parsed = JSON.parse(line);
2727
+ const shouldContinue = visitor({
2728
+ parsed,
2729
+ line,
2730
+ lineIndex: i,
2731
+ lineNumber: i + 1
2732
+ });
2733
+ if (shouldContinue === false) {
2734
+ break;
2735
+ }
2736
+ } catch (error) {
2737
+ options.onMalformed?.(line, i + 1, error);
2738
+ }
2739
+ }
2740
+ }
2741
+
2742
+ // ../../packages/plugin-cursor/src/shared/text-utils.ts
2743
+ function truncate(text, maxLength) {
2744
+ return text.length <= maxLength ? text : `${text.slice(0, maxLength)}...`;
2745
+ }
2746
+
2747
+ // ../../packages/plugin-cursor/src/discovery.ts
2748
+ var SESSION_PREVIEW_MAX_LENGTH = 200;
2749
+ function isNonEmptyString(value) {
2750
+ return typeof value === "string" && value.trim().length > 0;
2751
+ }
2752
+ function toIsoOrEmpty(epochMs) {
2753
+ return typeof epochMs === "number" && Number.isFinite(epochMs) && epochMs > 0 ? epochMsToIso(epochMs) : "";
2754
+ }
2755
+ function fileUrlToPath(url) {
2756
+ try {
2757
+ return fileURLToPath(url);
2758
+ } catch {
2759
+ return null;
2760
+ }
2761
+ }
2762
+ function querySingleValue(db, sql, ...params) {
2763
+ try {
2764
+ const row = db.query(sql).get(...params);
2765
+ return typeof row?.value === "string" ? row.value : null;
2766
+ } catch {
2767
+ return null;
2768
+ }
2769
+ }
2770
+ function queryKeyValueRow(db, key) {
2771
+ return querySingleValue(db, "SELECT value FROM cursorDiskKV WHERE key = ?", key);
2772
+ }
2773
+ function queryItemTableRow(db, key) {
2774
+ return querySingleValue(db, "SELECT value FROM ItemTable WHERE key = ?", key);
2775
+ }
2776
+ function readWorkspaceComposerEntries(db) {
2777
+ const rawValue = queryItemTableRow(db, "composer.composerData");
2778
+ if (!rawValue) {
2779
+ return [];
2780
+ }
2781
+ const parsed = tryParseJson2(rawValue);
2782
+ return Array.isArray(parsed?.allComposers) ? parsed.allComposers : [];
2783
+ }
2784
+ function extractBubbleUserText(bubble) {
2785
+ return isNonEmptyString(bubble?.text) ? bubble.text.trim() : "";
2786
+ }
2787
+ function extractFirstUserTextFromConversation(conversation) {
2788
+ if (!Array.isArray(conversation)) {
2789
+ return "";
2790
+ }
2791
+ for (const bubble of conversation) {
2792
+ if (bubble?.type !== 1) {
2793
+ continue;
2794
+ }
2795
+ const text = extractBubbleUserText(bubble);
2796
+ if (text) {
2797
+ return text;
2798
+ }
2799
+ }
2800
+ return "";
2801
+ }
2802
+ function extractFirstUserTextFromHeaders(db, composerId, headers) {
2803
+ if (!Array.isArray(headers)) {
2804
+ return "";
2805
+ }
2806
+ for (const header of headers) {
2807
+ if (!header?.bubbleId) {
2808
+ continue;
2809
+ }
2810
+ if (header.type !== undefined && header.type !== 1) {
2811
+ continue;
2812
+ }
2813
+ const rawBubble = queryKeyValueRow(db, `bubbleId:${composerId}:${header.bubbleId}`);
2814
+ if (!rawBubble) {
2815
+ continue;
2816
+ }
2817
+ const bubble = tryParseJson2(rawBubble);
2818
+ const text = extractBubbleUserText(bubble);
2819
+ if (text) {
2820
+ return text;
2821
+ }
2822
+ }
2823
+ return "";
2824
+ }
2825
+ function resolveComposerFallbackMessage(composer) {
2826
+ if (isNonEmptyString(composer.name)) {
2827
+ return composer.name.trim();
2828
+ }
2829
+ if (isNonEmptyString(composer.subtitle)) {
2830
+ return composer.subtitle.trim();
2831
+ }
2832
+ return "Cursor session";
2833
+ }
2834
+ function resolveComposerFirstMessage(globalDb, composer, composerId) {
2835
+ if (globalDb) {
2836
+ const rawComposerData = queryKeyValueRow(globalDb, `composerData:${composerId}`);
2837
+ const parsed = rawComposerData ? tryParseJson2(rawComposerData) : undefined;
2838
+ const fromConversation = extractFirstUserTextFromConversation(parsed?.conversation);
2839
+ if (fromConversation) {
2840
+ return truncate(fromConversation, SESSION_PREVIEW_MAX_LENGTH);
2841
+ }
2842
+ const fromHeaders = extractFirstUserTextFromHeaders(globalDb, composerId, parsed?.fullConversationHeadersOnly);
2843
+ if (fromHeaders) {
2844
+ return truncate(fromHeaders, SESSION_PREVIEW_MAX_LENGTH);
2845
+ }
2846
+ }
2847
+ return resolveComposerFallbackMessage(composer);
2848
+ }
2849
+ function resolveComposerSessionType(unifiedMode) {
2850
+ if (unifiedMode === "plan") {
2851
+ return "plan";
2852
+ }
2853
+ if (unifiedMode === "agent") {
2854
+ return "implementation";
2855
+ }
2856
+ return;
2857
+ }
2858
+ function createComposerSummary({
2859
+ projectPath,
2860
+ workspaceDbPath,
2861
+ composer,
2862
+ globalDb,
2863
+ options = { resolveFirstMessage: true }
2864
+ }) {
2865
+ if (!isNonEmptyString(composer.composerId)) {
2866
+ return null;
2867
+ }
2868
+ const createdAtMs = typeof composer.createdAt === "number" ? composer.createdAt : 0;
2869
+ if (!createdAtMs) {
2870
+ return null;
2871
+ }
2872
+ const { composerId } = composer;
2873
+ const unifiedMode = composer.unifiedMode ?? composer.forceMode ?? "chat";
2874
+ const firstMessage = options.resolveFirstMessage ? resolveComposerFirstMessage(globalDb, composer, composerId) : resolveComposerFallbackMessage(composer);
2875
+ return {
2876
+ kind: "composer",
2877
+ rawSessionId: `composer:${composerId}`,
2878
+ projectPath,
2879
+ composerId,
2880
+ workspaceDbPath,
2881
+ createdAtMs,
2882
+ lastUpdatedAtMs: typeof composer.lastUpdatedAt === "number" ? composer.lastUpdatedAt : createdAtMs,
2883
+ name: composer.name?.trim() ?? "",
2884
+ subtitle: composer.subtitle?.trim() ?? "",
2885
+ unifiedMode,
2886
+ timestamp: toIsoOrEmpty(createdAtMs),
2887
+ firstMessage,
2888
+ slug: composer.name?.trim() || composerId,
2889
+ model: "unknown",
2890
+ gitBranch: "",
2891
+ sessionType: resolveComposerSessionType(unifiedMode)
2892
+ };
2893
+ }
2894
+ function parseProjectPathFromWorkspaceJson(content) {
2895
+ const parsed = tryParseJson2(content);
2896
+ if (!isNonEmptyString(parsed?.folder)) {
2897
+ return null;
2898
+ }
2899
+ return fileUrlToPath(parsed.folder);
2900
+ }
2901
+ function discoverWorkspaceDescriptors(targetProjectPath) {
2902
+ return Effect21.gen(function* () {
2903
+ const workspaceStorageDir = yield* getCursorWorkspaceStorageDirEffect();
2904
+ const workspaceEntries = yield* readDirEntriesSafe2(workspaceStorageDir);
2905
+ const workspaceDescriptors = [];
2906
+ for (const entry of workspaceEntries) {
2907
+ if (!entry.isDirectory) {
2908
+ continue;
2909
+ }
2910
+ const workspaceDir = join12(workspaceStorageDir, entry.name);
2911
+ const workspaceJsonPath = join12(workspaceDir, "workspace.json");
2912
+ const workspaceDbPath = join12(workspaceDir, "state.vscdb");
2913
+ const exists = yield* fileExists3(workspaceJsonPath);
2914
+ if (!exists) {
2915
+ continue;
2916
+ }
2917
+ const projectPath = yield* readFileText3(workspaceJsonPath).pipe(Effect21.map(parseProjectPathFromWorkspaceJson), Effect21.catchAll(() => Effect21.succeed(null)));
2918
+ if (!projectPath || targetProjectPath && projectPath !== targetProjectPath) {
2919
+ continue;
2920
+ }
2921
+ workspaceDescriptors.push({ projectPath, workspaceDbPath });
2922
+ }
2923
+ return workspaceDescriptors;
2924
+ });
2925
+ }
2926
+ function findProjectSessions(sessionsByProject, projectPath) {
2927
+ const existing = sessionsByProject.get(projectPath);
2928
+ if (existing) {
2929
+ return existing;
2930
+ }
2931
+ const created = [];
2932
+ sessionsByProject.set(projectPath, created);
2933
+ return created;
2934
+ }
2935
+ function pushSession(sessionsByProject, session) {
2936
+ findProjectSessions(sessionsByProject, session.projectPath).push(session);
2937
+ }
2938
+ function pushSessions(sessionsByProject, sessions) {
2939
+ for (const session of sessions) {
2940
+ pushSession(sessionsByProject, session);
2941
+ }
2942
+ }
2943
+ function collectComposerSessions({
2944
+ workspaceDescriptors,
2945
+ globalDb,
2946
+ sessionsByProject,
2947
+ composersById,
2948
+ options
2949
+ }) {
2950
+ return Effect21.gen(function* () {
2951
+ for (const descriptor of workspaceDescriptors) {
2952
+ const workspaceDb = yield* openCursorDbIfExists(descriptor.workspaceDbPath);
2953
+ if (!workspaceDb) {
2954
+ continue;
2955
+ }
2956
+ try {
2957
+ const composers = readWorkspaceComposerEntries(workspaceDb);
2958
+ for (const composer of composers) {
2959
+ const summary = createComposerSummary({
2960
+ projectPath: descriptor.projectPath,
2961
+ workspaceDbPath: descriptor.workspaceDbPath,
2962
+ composer,
2963
+ globalDb,
2964
+ options
2965
+ });
2966
+ if (!summary) {
2967
+ continue;
2968
+ }
2969
+ composersById.set(summary.composerId, summary);
2970
+ pushSession(sessionsByProject, summary);
2971
+ }
2972
+ } finally {
2973
+ workspaceDb.close();
2974
+ }
2975
+ }
2976
+ });
2977
+ }
2978
+ function readFirstTranscriptUserMessage(text) {
2979
+ let firstMessage = "";
2980
+ iterateJsonl3(text, ({ parsed }) => {
2981
+ const line = parsed;
2982
+ if (line.role !== "user" || !Array.isArray(line.message?.content)) {
2983
+ return;
2984
+ }
2985
+ const parts = line.message.content.filter((block) => block?.type === "text" && isNonEmptyString(block.text)).map((block) => block.text?.trim() ?? "").filter(Boolean);
2986
+ if (parts.length === 0) {
2987
+ return;
2988
+ }
2989
+ firstMessage = truncate(parts.join(`
2990
+
2991
+ `), SESSION_PREVIEW_MAX_LENGTH);
2992
+ return false;
2993
+ }, { onMalformed: () => {} });
2994
+ return firstMessage;
2995
+ }
2996
+ function listTranscriptFiles(agentTranscriptsDir) {
2997
+ return Effect21.gen(function* () {
2998
+ const agentDirs = yield* readDirEntriesSafe2(agentTranscriptsDir);
2999
+ const files = [];
3000
+ for (const entry of agentDirs) {
3001
+ if (!entry.isDirectory) {
3002
+ continue;
3003
+ }
3004
+ const agentId = entry.name;
3005
+ const transcriptDir = join12(agentTranscriptsDir, agentId);
3006
+ const transcriptFiles = yield* listFilesWithMtime2(transcriptDir, ".jsonl");
3007
+ const transcript = transcriptFiles.find((candidate) => candidate.fileName === `${agentId}.jsonl`) ?? transcriptFiles[0];
3008
+ if (!transcript) {
3009
+ continue;
3010
+ }
3011
+ files.push({
3012
+ agentId,
3013
+ filePath: join12(transcriptDir, transcript.fileName),
3014
+ mtimeIso: transcript.mtime
3015
+ });
3016
+ }
3017
+ sortByIsoDesc(files, (item) => item.mtimeIso);
3018
+ return files;
3019
+ });
3020
+ }
3021
+ function createBackgroundAgentSummary(projectPath, transcript, firstMessage) {
3022
+ return {
3023
+ kind: "agent",
3024
+ rawSessionId: `agent:${transcript.agentId}`,
3025
+ projectPath,
3026
+ agentId: transcript.agentId,
3027
+ filePath: transcript.filePath,
3028
+ timestamp: transcript.mtimeIso,
3029
+ timestampMs: new Date(transcript.mtimeIso).getTime(),
3030
+ firstMessage,
3031
+ slug: transcript.agentId,
3032
+ model: "unknown",
3033
+ gitBranch: "",
3034
+ sessionType: "implementation"
3035
+ };
3036
+ }
3037
+ function discoverBackgroundAgentsForProject(projectPath, options = { readPreviewText: true }) {
3038
+ return Effect21.gen(function* () {
3039
+ const config = yield* PluginConfig;
3040
+ const agentTranscriptsDir = join12(config.dataDir, "projects", encodeCursorProjectPath(projectPath), "agent-transcripts");
3041
+ const agentsById = new Map;
3042
+ const transcriptDirExists = yield* fileExists3(agentTranscriptsDir);
3043
+ if (!transcriptDirExists) {
3044
+ return agentsById;
3045
+ }
3046
+ const transcripts = yield* listTranscriptFiles(agentTranscriptsDir);
3047
+ for (const transcript of transcripts) {
3048
+ let firstMessage = "Cursor background agent";
3049
+ if (options.readPreviewText) {
3050
+ const text = yield* readFileText3(transcript.filePath).pipe(Effect21.catchAll(() => Effect21.succeed("")));
3051
+ firstMessage = readFirstTranscriptUserMessage(text) || firstMessage;
3052
+ }
3053
+ agentsById.set(transcript.agentId, createBackgroundAgentSummary(projectPath, transcript, firstMessage));
3054
+ }
3055
+ return agentsById;
3056
+ });
3057
+ }
3058
+ function discoverBackgroundAgents(projectPaths, options = { readPreviewText: true }) {
3059
+ return Effect21.gen(function* () {
3060
+ const agentsById = new Map;
3061
+ for (const projectPath of new Set(projectPaths)) {
3062
+ const projectAgents = yield* discoverBackgroundAgentsForProject(projectPath, options);
3063
+ for (const agent of projectAgents.values()) {
3064
+ agentsById.set(agent.agentId, agent);
3065
+ }
3066
+ }
3067
+ return agentsById;
3068
+ });
3069
+ }
3070
+ function parsePlanRegistry(rawValue) {
3071
+ if (!rawValue) {
3072
+ return [];
3073
+ }
3074
+ const parsed = tryParseJson2(rawValue);
3075
+ if (!parsed) {
3076
+ return [];
3077
+ }
3078
+ return Object.values(parsed);
3079
+ }
3080
+ function createPlanSummary(entry, projectPath, filePath, displayName) {
3081
+ if (!(isNonEmptyString(entry.id) && isNonEmptyString(entry.createdBy)) || typeof entry.createdAt !== "number") {
3082
+ return null;
3083
+ }
3084
+ return {
3085
+ kind: "plan",
3086
+ rawSessionId: `plan:${entry.id}`,
3087
+ projectPath,
3088
+ planId: entry.id,
3089
+ filePath,
3090
+ createdAtMs: entry.createdAt,
3091
+ lastUpdatedAtMs: typeof entry.lastUpdatedAt === "number" ? entry.lastUpdatedAt : entry.createdAt,
3092
+ createdBy: entry.createdBy,
3093
+ timestamp: toIsoOrEmpty(entry.createdAt),
3094
+ firstMessage: displayName,
3095
+ slug: entry.id,
3096
+ model: "unknown",
3097
+ gitBranch: "",
3098
+ sessionType: "plan"
3099
+ };
3100
+ }
3101
+ function discoverMappedPlans(globalDb, composersById, agentsById, options = { loadDisplayName: true }) {
3102
+ return Effect21.gen(function* () {
3103
+ const plansById = new Map;
3104
+ if (!globalDb) {
3105
+ return plansById;
3106
+ }
3107
+ const entries = parsePlanRegistry(queryItemTableRow(globalDb, "composer.planRegistry"));
3108
+ for (const entry of entries) {
3109
+ const filePath = entry.uri?.fsPath;
3110
+ if (!isNonEmptyString(filePath)) {
3111
+ continue;
3112
+ }
3113
+ const composer = isNonEmptyString(entry.createdBy) ? composersById.get(entry.createdBy) : undefined;
3114
+ const agent = isNonEmptyString(entry.createdBy) ? agentsById.get(entry.createdBy) : undefined;
3115
+ const projectPath = composer?.projectPath ?? agent?.projectPath;
3116
+ if (!projectPath) {
3117
+ continue;
3118
+ }
3119
+ const exists = yield* fileExists3(filePath);
3120
+ if (!exists) {
3121
+ continue;
3122
+ }
3123
+ const displayName = options.loadDisplayName ? yield* readPlanDisplayName(filePath).pipe(Effect21.catchAll(() => Effect21.succeed(""))) : "";
3124
+ const summary = createPlanSummary(entry, projectPath, filePath, displayName || entry.name?.trim() || "Cursor plan");
3125
+ if (!summary) {
3126
+ continue;
3127
+ }
3128
+ plansById.set(summary.planId, summary);
3129
+ if (agent) {
3130
+ agent.sessionType = "plan";
3131
+ }
3132
+ }
3133
+ return plansById;
3134
+ });
3135
+ }
3136
+ function buildProjects(sessionsByProject) {
3137
+ const projects = [];
3138
+ for (const [projectPath, sessions] of sessionsByProject) {
3139
+ if (sessions.length === 0) {
3140
+ continue;
3141
+ }
3142
+ sortByIsoDesc(sessions, (session) => session.timestamp);
3143
+ projects.push({
3144
+ pluginId: "cursor",
3145
+ nativeId: projectPath,
3146
+ resolvedPath: projectPath,
3147
+ displayName: projectPath,
3148
+ sessionCount: sessions.length,
3149
+ lastActivity: sessions[0]?.timestamp ?? ""
3150
+ });
3151
+ }
3152
+ sortByIsoDesc(projects, (project) => project.lastActivity);
3153
+ return projects;
3154
+ }
3155
+ function buildCursorProjectIndex(nativeId) {
3156
+ return Effect21.gen(function* () {
3157
+ const globalDb = yield* openCursorGlobalDb();
3158
+ const sessionsByProject = new Map;
3159
+ const composersById = new Map;
3160
+ try {
3161
+ const workspaceDescriptors = yield* discoverWorkspaceDescriptors(nativeId);
3162
+ yield* collectComposerSessions({
3163
+ workspaceDescriptors,
3164
+ globalDb,
3165
+ sessionsByProject,
3166
+ composersById,
3167
+ options: { resolveFirstMessage: true }
3168
+ });
3169
+ const agentsById = yield* discoverBackgroundAgents([nativeId], { readPreviewText: true });
3170
+ pushSessions(sessionsByProject, agentsById.values());
3171
+ const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById, { loadDisplayName: true });
3172
+ pushSessions(sessionsByProject, plansById.values());
3173
+ return {
3174
+ projects: buildProjects(sessionsByProject),
3175
+ sessionsByProject,
3176
+ composersById,
3177
+ agentsById,
3178
+ plansById
3179
+ };
3180
+ } finally {
3181
+ globalDb?.close();
3182
+ }
3183
+ });
3184
+ }
3185
+ function buildCursorIndex() {
3186
+ return Effect21.gen(function* () {
3187
+ const globalDb = yield* openCursorGlobalDb();
3188
+ const sessionsByProject = new Map;
3189
+ const composersById = new Map;
3190
+ try {
3191
+ const workspaceDescriptors = yield* discoverWorkspaceDescriptors();
3192
+ const workspaceProjectPaths = [...new Set(workspaceDescriptors.map((descriptor) => descriptor.projectPath))];
3193
+ yield* collectComposerSessions({
3194
+ workspaceDescriptors,
3195
+ globalDb,
3196
+ sessionsByProject,
3197
+ composersById,
3198
+ options: { resolveFirstMessage: true }
3199
+ });
3200
+ const agentsById = yield* discoverBackgroundAgents(workspaceProjectPaths);
3201
+ pushSessions(sessionsByProject, agentsById.values());
3202
+ const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById);
3203
+ pushSessions(sessionsByProject, plansById.values());
3204
+ return {
3205
+ projects: buildProjects(sessionsByProject),
3206
+ sessionsByProject,
3207
+ composersById,
3208
+ agentsById,
3209
+ plansById
3210
+ };
3211
+ } finally {
3212
+ globalDb?.close();
3213
+ }
3214
+ });
3215
+ }
3216
+ function toSessionSummary(session) {
3217
+ return {
3218
+ sessionId: session.rawSessionId,
3219
+ timestamp: session.timestamp,
3220
+ slug: session.slug,
3221
+ firstMessage: session.firstMessage,
3222
+ model: session.model,
3223
+ gitBranch: session.gitBranch,
3224
+ pluginId: "cursor",
3225
+ sessionType: session.sessionType
3226
+ };
3227
+ }
3228
+ function buildCursorDiscoveryIndex() {
3229
+ return buildCursorIndex().pipe(Effect21.map((index) => ({
3230
+ projects: index.projects,
3231
+ sessionsByNativeId: new Map([...index.sessionsByProject.entries()].map(([projectPath, sessions]) => [
3232
+ projectPath,
3233
+ sessions.map(toSessionSummary)
3234
+ ]))
3235
+ })));
3236
+ }
3237
+ function discoverCursorProjects() {
3238
+ return Effect21.gen(function* () {
3239
+ const globalDb = yield* openCursorGlobalDb();
3240
+ const sessionsByProject = new Map;
3241
+ const composersById = new Map;
3242
+ try {
3243
+ const workspaceDescriptors = yield* discoverWorkspaceDescriptors();
3244
+ const workspaceProjectPaths = [...new Set(workspaceDescriptors.map((descriptor) => descriptor.projectPath))];
3245
+ yield* collectComposerSessions({
3246
+ workspaceDescriptors,
3247
+ globalDb,
3248
+ sessionsByProject,
3249
+ composersById,
3250
+ options: { resolveFirstMessage: false }
3251
+ });
3252
+ const agentsById = yield* discoverBackgroundAgents(workspaceProjectPaths, { readPreviewText: false });
3253
+ pushSessions(sessionsByProject, agentsById.values());
3254
+ const plansById = yield* discoverMappedPlans(globalDb, composersById, agentsById, {
3255
+ loadDisplayName: false
3256
+ });
3257
+ pushSessions(sessionsByProject, plansById.values());
3258
+ return buildProjects(sessionsByProject);
3259
+ } finally {
3260
+ globalDb?.close();
3261
+ }
3262
+ });
3263
+ }
3264
+ function listCursorSessions(nativeId) {
3265
+ return buildCursorProjectIndex(nativeId).pipe(Effect21.map((index) => {
3266
+ const sessions = index.sessionsByProject.get(nativeId) ?? [];
3267
+ sortByIsoDesc(sessions, (session) => session.timestamp);
3268
+ return sessions.map(toSessionSummary);
3269
+ }));
3270
+ }
3271
+
3272
+ // ../../packages/plugin-cursor/src/parser.ts
3273
+ import { Effect as Effect22 } from "effect";
3274
+ var COMPOSER_PREFIX_REGEX = /^composer:/u;
3275
+ var AGENT_PREFIX_REGEX = /^agent:/u;
3276
+ var PLAN_PREFIX_REGEX = /^plan:/u;
3277
+ function makeUserTurn(uuid, timestamp, text) {
3278
+ return {
3279
+ kind: "user",
3280
+ uuid,
3281
+ timestamp,
3282
+ text
3283
+ };
3284
+ }
3285
+ function makeAssistantTurn(uuid, timestamp, contentBlocks) {
3286
+ return {
3287
+ kind: "assistant",
3288
+ uuid,
3289
+ timestamp,
3290
+ model: "unknown",
3291
+ contentBlocks
3292
+ };
3293
+ }
3294
+ function makeSystemTurn2(uuid, timestamp, text) {
3295
+ return {
3296
+ kind: "system",
3297
+ uuid,
3298
+ timestamp,
3299
+ text
3300
+ };
3301
+ }
3302
+ function synthesizedTimestamp(baseTimestampMs, index) {
3303
+ return new Date(baseTimestampMs + index).toISOString();
3304
+ }
3305
+ function normalizeToolInput(params) {
3306
+ if (typeof params === "string") {
3307
+ const parsed = tryParseJson2(params);
3308
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3309
+ return parsed;
3310
+ }
3311
+ if (parsed !== undefined) {
3312
+ return { value: parsed };
3313
+ }
3314
+ return { raw: params };
3315
+ }
3316
+ if (params && typeof params === "object" && !Array.isArray(params)) {
3317
+ return params;
3318
+ }
3319
+ if (params === undefined) {
3320
+ return {};
3321
+ }
3322
+ return { value: params };
3323
+ }
3324
+ function normalizeToolResult(result) {
3325
+ if (typeof result === "string") {
3326
+ return result;
3327
+ }
3328
+ if (result === undefined || result === null) {
3329
+ return "";
3330
+ }
3331
+ if (typeof result === "object") {
3332
+ try {
3333
+ return JSON.stringify(result, null, 2);
3334
+ } catch {
3335
+ return String(result);
3336
+ }
3337
+ }
3338
+ return String(result);
3339
+ }
3340
+ function bubbleToContentBlock(bubble) {
3341
+ const tool = bubble.toolFormerData;
3342
+ if (tool?.name) {
3343
+ const call = {
3344
+ toolUseId: tool.toolCallId ?? bubble.bubbleId ?? tool.name,
3345
+ name: tool.name,
3346
+ input: normalizeToolInput(tool.params),
3347
+ result: normalizeToolResult(tool.result),
3348
+ isError: tool.status !== "completed" && tool.status !== "success"
3349
+ };
3350
+ return { type: "tool_call", call };
3351
+ }
3352
+ const thinkingText = bubble.thinking?.text?.trim();
3353
+ if (thinkingText) {
3354
+ return { type: "thinking", block: { text: thinkingText } };
3355
+ }
3356
+ const text = bubble.text?.trim();
3357
+ if (text) {
3358
+ return { type: "text", text };
3359
+ }
3360
+ return null;
3361
+ }
3362
+ function buildTurnsFromBubbles(bubbles, baseTimestampMs) {
3363
+ const turns = [];
3364
+ let assistantBlocks = [];
3365
+ const flushAssistant3 = () => {
3366
+ if (assistantBlocks.length === 0) {
3367
+ return;
3368
+ }
3369
+ turns.push(makeAssistantTurn(`cursor-assistant-${turns.length}`, synthesizedTimestamp(baseTimestampMs, turns.length), assistantBlocks));
3370
+ assistantBlocks = [];
3371
+ };
3372
+ for (const bubble of bubbles) {
3373
+ if (bubble.type === 1) {
3374
+ flushAssistant3();
3375
+ turns.push(makeUserTurn(bubble.bubbleId ?? `cursor-user-${turns.length}`, synthesizedTimestamp(baseTimestampMs, turns.length), bubble.text?.trim() ?? ""));
3376
+ continue;
3377
+ }
3378
+ const block = bubbleToContentBlock(bubble);
3379
+ if (block) {
3380
+ assistantBlocks.push(block);
3381
+ }
3382
+ }
3383
+ flushAssistant3();
3384
+ return turns;
3385
+ }
3386
+ function readComposerBubblesFromHeaders(rawComposerData, queryBubble) {
3387
+ const headers = rawComposerData.fullConversationHeadersOnly;
3388
+ if (!Array.isArray(headers) || headers.length === 0) {
3389
+ return { bubbles: [], partial: false };
3390
+ }
3391
+ const bubbles = [];
3392
+ let partial = false;
3393
+ for (const header of headers) {
3394
+ if (!header?.bubbleId) {
3395
+ partial = true;
3396
+ continue;
3397
+ }
3398
+ const bubble = queryBubble(header.bubbleId);
3399
+ if (!bubble) {
3400
+ partial = true;
3401
+ continue;
3402
+ }
3403
+ bubbles.push(bubble);
3404
+ }
3405
+ return { bubbles, partial };
3406
+ }
3407
+ function partialCursorSessionNotice(timestamp) {
3408
+ return makeSystemTurn2("cursor-partial-session", timestamp, `**Partial Cursor session**
3409
+ Klovi could not reconstruct the full Composer transcript from Cursor state. Showing recovered content only.`);
3410
+ }
3411
+ function loadCursorComposerSession(summary) {
3412
+ return Effect22.gen(function* () {
3413
+ const globalDb = yield* openCursorGlobalDb();
3414
+ const baseTimestampMs = summary.createdAtMs;
3415
+ try {
3416
+ if (!globalDb) {
3417
+ return {
3418
+ sessionId: summary.rawSessionId,
3419
+ project: summary.projectPath,
3420
+ pluginId: "cursor",
3421
+ turns: [partialCursorSessionNotice(summary.timestamp)]
3422
+ };
3423
+ }
3424
+ const rawComposer = globalDb.query("SELECT value FROM cursorDiskKV WHERE key = ?").get(`composerData:${summary.composerId}`);
3425
+ const composerData = rawComposer ? tryParseJson2(rawComposer.value) : undefined;
3426
+ if (!composerData) {
3427
+ return {
3428
+ sessionId: summary.rawSessionId,
3429
+ project: summary.projectPath,
3430
+ pluginId: "cursor",
3431
+ turns: [partialCursorSessionNotice(summary.timestamp)]
3432
+ };
3433
+ }
3434
+ const queryBubble = (bubbleId) => {
3435
+ try {
3436
+ const row = globalDb.query("SELECT value FROM cursorDiskKV WHERE key = ?").get(`bubbleId:${summary.composerId}:${bubbleId}`);
3437
+ if (!row) {
3438
+ return null;
3439
+ }
3440
+ return tryParseJson2(row.value) ?? null;
3441
+ } catch {
3442
+ return null;
3443
+ }
3444
+ };
3445
+ const fromHeaders = readComposerBubblesFromHeaders(composerData, queryBubble);
3446
+ const inlineConversation = Array.isArray(composerData.conversation) ? composerData.conversation : [];
3447
+ const recoveredBubbles = fromHeaders.bubbles.length > 0 ? fromHeaders.bubbles : inlineConversation;
3448
+ const partial = fromHeaders.partial || fromHeaders.bubbles.length === 0 && inlineConversation.length === 0;
3449
+ const turns = buildTurnsFromBubbles(recoveredBubbles, baseTimestampMs);
3450
+ return {
3451
+ sessionId: summary.rawSessionId,
3452
+ project: summary.projectPath,
3453
+ pluginId: "cursor",
3454
+ turns: partial ? [partialCursorSessionNotice(summary.timestamp), ...turns] : turns
3455
+ };
3456
+ } finally {
3457
+ globalDb?.close();
3458
+ }
3459
+ });
3460
+ }
3461
+ function loadCursorAgentSession(summary) {
3462
+ return Effect22.gen(function* () {
3463
+ const text = yield* readFileText3(summary.filePath);
3464
+ const turns = [];
3465
+ let turnIndex = 0;
3466
+ iterateJsonl3(text, ({ parsed }) => {
3467
+ const line = parsed;
3468
+ if (!Array.isArray(line.message?.content)) {
3469
+ return;
3470
+ }
3471
+ const textBlocks = line.message.content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text?.trim() ?? "").filter(Boolean);
3472
+ if (textBlocks.length === 0) {
3473
+ return;
3474
+ }
3475
+ const timestamp = synthesizedTimestamp(summary.timestampMs, turnIndex);
3476
+ const content = textBlocks.join(`
3477
+
3478
+ `);
3479
+ if (line.role === "user") {
3480
+ turns.push(makeUserTurn(`cursor-agent-user-${turnIndex}`, timestamp, content));
3481
+ turnIndex += 1;
3482
+ return;
3483
+ }
3484
+ if (line.role === "assistant") {
3485
+ turns.push(makeAssistantTurn(`cursor-agent-assistant-${turnIndex}`, timestamp, [{ type: "text", text: content }]));
3486
+ turnIndex += 1;
3487
+ }
3488
+ }, { onMalformed: () => {} });
3489
+ return {
3490
+ sessionId: summary.rawSessionId,
3491
+ project: summary.projectPath,
3492
+ pluginId: "cursor",
3493
+ turns
3494
+ };
3495
+ });
3496
+ }
3497
+ function loadCursorSession(nativeId, sessionId) {
3498
+ return Effect22.gen(function* () {
3499
+ const index = yield* buildCursorProjectIndex(nativeId);
3500
+ 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, ""));
3501
+ if (!session || session.projectPath !== nativeId) {
3502
+ return yield* Effect22.fail(new Error(`Cursor session not found: ${sessionId}`));
3503
+ }
3504
+ if (session.kind === "composer") {
3505
+ return yield* loadCursorComposerSession(session);
3506
+ }
3507
+ if (session.kind === "agent") {
3508
+ return yield* loadCursorAgentSession(session);
3509
+ }
3510
+ return yield* loadCursorPlanSession(session);
3511
+ });
3512
+ }
3513
+ // ../../packages/plugin-cursor/src/index.ts
3514
+ var cursorPlugin = {
3515
+ id: "cursor",
3516
+ displayName: "Cursor",
3517
+ getDefaultDataDir: () => null,
3518
+ isDataAvailable: Effect23.gen(function* () {
3519
+ const config = yield* PluginConfig;
3520
+ const fs = yield* FileSystem9.FileSystem;
3521
+ const userDataExists = yield* fileExists3(config.dataDir).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
3522
+ if (userDataExists) {
3523
+ return true;
3524
+ }
3525
+ const globalDbExists = yield* fs.exists(getCursorGlobalDbPath()).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
3526
+ if (globalDbExists) {
3527
+ return true;
3528
+ }
3529
+ return yield* fs.exists(getCursorWorkspaceStorageDir()).pipe(Effect23.catchAll(() => Effect23.succeed(false)));
3530
+ }),
3531
+ discoverProjects: discoverCursorProjects().pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3532
+ pluginId: "cursor",
3533
+ operation: "discoverProjects",
3534
+ message: String(err),
3535
+ cause: err
3536
+ })))),
3537
+ discoverIndex: buildCursorDiscoveryIndex().pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3538
+ pluginId: "cursor",
3539
+ operation: "discoverIndex",
3540
+ message: String(err),
3541
+ cause: err
3542
+ })))),
3543
+ listSessions: (nativeId) => listCursorSessions(nativeId).pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3544
+ pluginId: "cursor",
3545
+ operation: "listSessions",
3546
+ message: String(err),
3547
+ cause: err
3548
+ })))),
3549
+ loadSession: (nativeId, sessionId) => loadCursorSession(nativeId, sessionId).pipe(Effect23.catchAll((err) => Effect23.fail(new PluginError({
3550
+ pluginId: "cursor",
3551
+ operation: "loadSession",
3552
+ message: String(err),
3553
+ cause: err
3554
+ }))))
3555
+ };
3556
+
3557
+ // ../../packages/server/src/services/catalog.ts
3558
+ init_src2();
3559
+ var BUILTIN_PLUGIN_DESCRIPTORS = [
3560
+ {
3561
+ plugin: claudeCodePlugin,
3562
+ defaultDir: DEFAULT_CLAUDE_CODE_DIR,
3563
+ defaultEnabled: true
3564
+ },
3565
+ {
3566
+ plugin: codexCliPlugin,
3567
+ defaultDir: DEFAULT_CODEX_CLI_DIR,
3568
+ defaultEnabled: true
3569
+ },
3570
+ {
3571
+ plugin: openCodePlugin,
3572
+ defaultDir: DEFAULT_OPENCODE_DIR,
3573
+ defaultEnabled: true
3574
+ },
3575
+ {
3576
+ plugin: cursorPlugin,
3577
+ defaultDir: DEFAULT_CURSOR_DIR,
3578
+ defaultEnabled: false
3579
+ }
3580
+ ];
3581
+ var BUILTIN_PLUGIN_ID_SET = new Set(BUILTIN_PLUGIN_DESCRIPTORS.map((descriptor) => descriptor.plugin.id));
3582
+
3583
+ // ../../packages/server/src/services/registry.ts
3584
+ init_src();
3585
+ class PluginRegistry2 extends PluginRegistry {
3586
+ }
3587
+
3588
+ // ../../packages/server/src/services/auto-discover.ts
3589
+ function createRegistry(settings) {
3590
+ return Effect24.gen(function* () {
3591
+ const registry = new PluginRegistry2;
3592
+ for (const { plugin, defaultDir, defaultEnabled } of BUILTIN_PLUGIN_DESCRIPTORS) {
3593
+ const pluginSettings = settings?.plugins[plugin.id];
3594
+ const enabled = pluginSettings?.enabled ?? defaultEnabled;
3595
+ if (!enabled) {
3596
+ continue;
3597
+ }
3598
+ const dataDir = pluginSettings?.dataDir ?? defaultDir;
3599
+ const configLayer = makePluginConfigLayer({ dataDir });
3600
+ const available = yield* plugin.isDataAvailable.pipe(Effect24.provide(configLayer), Effect24.catchAll(() => Effect24.succeed(false)));
3601
+ if (available) {
3602
+ registry.register(plugin, { dataDir });
3603
+ }
3604
+ }
3605
+ return registry;
3606
+ });
3607
+ }
3608
+
3609
+ // ../../packages/server/src/services/onboarding-service.ts
3610
+ import { Effect as Effect26 } from "effect";
3611
+
3612
+ // ../../packages/server/src/services/settings.ts
3613
+ import { dirname, join as join13 } from "node:path";
3614
+ import { FileSystem as FileSystem10 } from "@effect/platform";
3615
+ import { Effect as Effect25 } from "effect";
3616
+
3617
+ // ../../packages/server/src/services/errors.ts
3618
+ import { Data as Data2 } from "effect";
3619
+
3620
+ class InvalidSessionIdError extends Data2.TaggedError("InvalidSessionIdError") {
3621
+ }
3622
+
3623
+ class ProjectNotFoundError extends Data2.TaggedError("ProjectNotFoundError") {
3624
+ }
3625
+
3626
+ class PluginSourceNotFoundError extends Data2.TaggedError("PluginSourceNotFoundError") {
3627
+ }
3628
+
3629
+ class UnknownPluginError extends Data2.TaggedError("UnknownPluginError") {
3630
+ }
3631
+
3632
+ class SubAgentNotSupportedError extends Data2.TaggedError("SubAgentNotSupportedError") {
3633
+ }
3634
+
3635
+ class SettingsWriteError extends Data2.TaggedError("SettingsWriteError") {
3636
+ }
3637
+
3638
+ // ../../packages/server/src/services/settings.ts
3639
+ function createDefaultPluginStates() {
3640
+ return Object.fromEntries(BUILTIN_PLUGIN_DESCRIPTORS.map(({ plugin, defaultEnabled }) => [
3641
+ plugin.id,
3642
+ { enabled: defaultEnabled, dataDir: null }
3643
+ ]));
3644
+ }
3645
+ function getDefaultSettings() {
3646
+ return {
3647
+ version: 1,
3648
+ plugins: createDefaultPluginStates(),
3649
+ general: {
3650
+ showSecurityWarning: true
3651
+ },
3652
+ updates: {
3653
+ channel: "stable",
3654
+ checkIntervalHours: 6,
3655
+ autoDownload: true
3656
+ }
3657
+ };
3658
+ }
3659
+ function loadSettings(path) {
3660
+ return Effect25.gen(function* () {
3661
+ const fs = yield* FileSystem10.FileSystem;
3662
+ const content = yield* fs.readFileString(path).pipe(Effect25.catchAll(() => Effect25.succeed(null)));
3663
+ if (content === null) {
3664
+ return getDefaultSettings();
3665
+ }
3666
+ const parsed = yield* Effect25.try({
3667
+ try: () => JSON.parse(content),
3668
+ catch: () => null
3669
+ }).pipe(Effect25.catchAll(() => Effect25.succeed(null)));
3670
+ if (parsed === null || parsed["version"] !== 1 || typeof parsed["plugins"] !== "object") {
3671
+ return getDefaultSettings();
3672
+ }
3673
+ return parsed;
3674
+ });
3675
+ }
3676
+ function saveSettings(path, settings) {
3677
+ return Effect25.gen(function* () {
3678
+ const fs = yield* FileSystem10.FileSystem;
3679
+ const dir = dirname(path);
3680
+ yield* fs.makeDirectory(dir, { recursive: true }).pipe(Effect25.mapError((cause) => new SettingsWriteError({ path, cause })));
3681
+ const tmpPath = join13(dir, `.settings-${Date.now()}.tmp`);
3682
+ yield* fs.writeFileString(tmpPath, JSON.stringify(settings, null, 2)).pipe(Effect25.mapError((cause) => new SettingsWriteError({ path, cause })));
3683
+ yield* fs.rename(tmpPath, path).pipe(Effect25.mapError((cause) => new SettingsWriteError({ path, cause })));
3684
+ });
3685
+ }
3686
+ function settingsFileExists(path) {
3687
+ return Effect25.gen(function* () {
3688
+ const fs = yield* FileSystem10.FileSystem;
3689
+ return yield* fs.exists(path).pipe(Effect25.catchAll(() => Effect25.succeed(false)));
3690
+ });
3691
+ }
3692
+ function deleteSettingsFile(path) {
3693
+ return Effect25.gen(function* () {
3694
+ const fs = yield* FileSystem10.FileSystem;
3695
+ yield* fs.remove(path).pipe(Effect25.catchAll(() => Effect25.void));
3696
+ });
3697
+ }
3698
+
3699
+ // ../../packages/server/src/services/onboarding-service.ts
3700
+ function isFirstLaunch(settingsPath) {
3701
+ return Effect26.gen(function* () {
3702
+ const exists = yield* settingsFileExists(settingsPath);
3703
+ return { firstLaunch: !exists };
3704
+ });
3705
+ }
3706
+ function completeOnboarding(settingsPath) {
3707
+ return Effect26.gen(function* () {
3708
+ const { firstLaunch } = yield* isFirstLaunch(settingsPath);
3709
+ if (firstLaunch) {
3710
+ yield* saveSettings(settingsPath, getDefaultSettings());
3711
+ }
3712
+ return { ok: true };
3713
+ });
3714
+ }
3715
+ function resetSettings(settingsPath) {
3716
+ return Effect26.gen(function* () {
3717
+ yield* deleteSettingsFile(settingsPath);
3718
+ return { ok: true };
3719
+ });
3720
+ }
3721
+
3722
+ // ../../packages/server/src/services/sessions-service.ts
3723
+ init_src();
3724
+ import { Effect as Effect27 } from "effect";
3725
+ function getProjects(registry) {
3726
+ return Effect27.gen(function* () {
3727
+ const projects = yield* registry.discoverAllProjects();
3728
+ return { projects };
3729
+ });
3730
+ }
3731
+ function getSessions(registry, params) {
3732
+ return Effect27.gen(function* () {
3733
+ const discovered = yield* registry.discoverAllProjectsWithSessions();
3734
+ if (!discovered.projects.find((project) => project.encodedPath === params.encodedPath)) {
3735
+ return { sessions: [] };
3736
+ }
3737
+ return { sessions: discovered.sessionsByEncodedPath.get(params.encodedPath) ?? [] };
3738
+ });
3739
+ }
3740
+ function getSession(registry, params) {
3741
+ return Effect27.gen(function* () {
3742
+ const parsed = parseSessionId(params.sessionId);
3743
+ if (!(parsed.pluginId && parsed.rawSessionId)) {
3744
+ return yield* Effect27.fail(new InvalidSessionIdError({ value: params.sessionId }));
3745
+ }
3746
+ const { pluginId } = parsed;
3747
+ const { rawSessionId } = parsed;
3748
+ const projects = yield* registry.discoverAllProjects();
3749
+ const project = projects.find((p) => p.encodedPath === params.project);
3750
+ if (!project) {
3751
+ return yield* Effect27.fail(new ProjectNotFoundError({ encodedPath: params.project }));
3752
+ }
3753
+ const source = project.sources.find((s) => s.pluginId === pluginId);
3754
+ if (!source) {
3755
+ return yield* Effect27.fail(new PluginSourceNotFoundError({ pluginId, project: params.project }));
3756
+ }
3757
+ const plugin = registry.getPlugin(pluginId);
3758
+ const pluginConfig = registry.getPluginConfig(pluginId);
3759
+ const configLayer = makePluginConfigLayer(pluginConfig);
3760
+ const sessionDetail = plugin.loadSessionDetail ? yield* plugin.loadSessionDetail(source.nativeId, rawSessionId).pipe(Effect27.provide(configLayer), Effect27.catchAll(() => Effect27.succeed(undefined))) : undefined;
3761
+ const session = sessionDetail?.session ?? (yield* plugin.loadSession(source.nativeId, rawSessionId).pipe(Effect27.provide(configLayer), Effect27.catchAll(() => Effect27.die("loadSession failed"))));
3762
+ session.sessionId = encodeSessionId(pluginId, rawSessionId);
3763
+ session.pluginId = pluginId;
3764
+ session.planSessionId = sessionDetail?.planSessionId ? encodeSessionId(pluginId, sessionDetail.planSessionId) : undefined;
3765
+ session.implSessionId = sessionDetail?.implSessionId ? encodeSessionId(pluginId, sessionDetail.implSessionId) : undefined;
3766
+ return { session };
3767
+ });
3768
+ }
3769
+ function getSubAgent(registry, params) {
3770
+ return Effect27.gen(function* () {
3771
+ const parsed = parseSessionId(params.sessionId);
3772
+ if (!(parsed.pluginId && parsed.rawSessionId)) {
3773
+ return yield* Effect27.fail(new InvalidSessionIdError({ value: params.sessionId }));
3774
+ }
3775
+ const plugin = registry.getPlugin(parsed.pluginId);
3776
+ if (!plugin.loadSubAgentSession) {
3777
+ return yield* Effect27.fail(new SubAgentNotSupportedError({ pluginId: parsed.pluginId }));
3778
+ }
3779
+ const pluginConfig = registry.getPluginConfig(parsed.pluginId);
3780
+ const configLayer = makePluginConfigLayer(pluginConfig);
3781
+ const session = yield* plugin.loadSubAgentSession({
3782
+ sessionId: parsed.rawSessionId,
3783
+ project: params.project,
3784
+ agentId: params.agentId
3785
+ }).pipe(Effect27.provide(configLayer), Effect27.catchAll(() => Effect27.die("loadSubAgentSession failed")));
3786
+ return { session };
3787
+ });
3788
+ }
3789
+ function projectNameFromPath(fullPath) {
3790
+ const parts = fullPath.split("/").filter(Boolean);
3791
+ return parts.slice(-2).join("/");
3792
+ }
3793
+ function searchSessions(registry) {
3794
+ return Effect27.gen(function* () {
3795
+ const discovered = yield* registry.discoverAllProjectsWithSessions();
3796
+ const allSessions = [];
3797
+ for (const project of discovered.projects) {
3798
+ const sessions = discovered.sessionsByEncodedPath.get(project.encodedPath) ?? [];
3799
+ const projectName = projectNameFromPath(project.name);
3800
+ for (const session of sessions) {
3801
+ allSessions.push({
3802
+ ...session,
3803
+ encodedPath: project.encodedPath,
3804
+ projectName
3805
+ });
3806
+ }
3807
+ }
3808
+ sortByIsoDesc(allSessions, (session) => session.timestamp);
3809
+ return { sessions: allSessions };
3810
+ });
3811
+ }
3812
+
3813
+ // ../../packages/server/src/services/settings-service.ts
3814
+ import { Effect as Effect28 } from "effect";
3815
+ var DEFAULT_CHECK_INTERVAL_HOURS = 6;
3816
+ function buildPluginSettingsResponse(settingsPath) {
3817
+ return Effect28.gen(function* () {
3818
+ const settings = yield* loadSettings(settingsPath);
3819
+ const plugins = BUILTIN_PLUGIN_DESCRIPTORS.map(({ plugin, defaultDir, defaultEnabled }) => {
3820
+ const { id } = plugin;
3821
+ const { displayName } = plugin;
3822
+ const pluginConf = settings.plugins[id] ?? { enabled: defaultEnabled, dataDir: null };
3823
+ const defaultDataDir = defaultDir;
3824
+ const isCustomDir = pluginConf.dataDir !== null;
3825
+ return {
3826
+ id,
3827
+ displayName,
3828
+ enabled: pluginConf.enabled,
3829
+ dataDir: pluginConf.dataDir ?? defaultDataDir,
3830
+ defaultDataDir,
3831
+ isCustomDir
3832
+ };
3833
+ });
3834
+ return { plugins };
3835
+ });
3836
+ }
3837
+ function getPluginSettings(settingsPath) {
3838
+ return buildPluginSettingsResponse(settingsPath);
3839
+ }
3840
+ function getGeneralSettings(settingsPath) {
3841
+ return Effect28.gen(function* () {
3842
+ const settings = yield* loadSettings(settingsPath);
3843
+ return { showSecurityWarning: settings.general?.showSecurityWarning ?? true };
3844
+ });
3845
+ }
3846
+ function updateGeneralSettings(settingsPath, params) {
3847
+ return Effect28.gen(function* () {
3848
+ const settings = yield* loadSettings(settingsPath);
3849
+ if (!settings.general) {
3850
+ settings.general = {};
3851
+ }
3852
+ if (params.showSecurityWarning !== undefined) {
3853
+ settings.general.showSecurityWarning = params.showSecurityWarning;
3854
+ }
3855
+ yield* saveSettings(settingsPath, settings);
3856
+ return { showSecurityWarning: settings.general.showSecurityWarning ?? true };
3857
+ });
3858
+ }
3859
+ function updatePluginSetting(settingsPath, params) {
3860
+ return Effect28.gen(function* () {
3861
+ if (!BUILTIN_PLUGIN_ID_SET.has(params.pluginId)) {
3862
+ return yield* Effect28.fail(new UnknownPluginError({ pluginId: params.pluginId }));
3863
+ }
3864
+ const settings = yield* loadSettings(settingsPath);
3865
+ const descriptor = BUILTIN_PLUGIN_DESCRIPTORS.find(({ plugin }) => plugin.id === params.pluginId);
3866
+ const existing = settings.plugins[params.pluginId] ?? {
3867
+ enabled: descriptor?.defaultEnabled ?? true,
3868
+ dataDir: null
3869
+ };
3870
+ if (params.enabled !== undefined) {
3871
+ existing.enabled = params.enabled;
3872
+ }
3873
+ if (params.dataDir !== undefined) {
3874
+ existing.dataDir = params.dataDir;
3875
+ }
3876
+ settings.plugins[params.pluginId] = existing;
3877
+ yield* saveSettings(settingsPath, settings);
3878
+ return yield* buildPluginSettingsResponse(settingsPath);
3879
+ });
3880
+ }
3881
+ function getUpdateSettings(settingsPath) {
3882
+ return Effect28.gen(function* () {
3883
+ const settings = yield* loadSettings(settingsPath);
3884
+ return {
3885
+ channel: settings.updates?.channel ?? "stable",
3886
+ checkIntervalHours: settings.updates?.checkIntervalHours ?? DEFAULT_CHECK_INTERVAL_HOURS,
3887
+ autoDownload: settings.updates?.autoDownload ?? true
3888
+ };
3889
+ });
3890
+ }
3891
+ function updateUpdateSettings(settingsPath, params) {
3892
+ return Effect28.gen(function* () {
3893
+ const settings = yield* loadSettings(settingsPath);
3894
+ if (!settings.updates) {
3895
+ settings.updates = { channel: "stable", checkIntervalHours: 6, autoDownload: true };
3896
+ }
3897
+ if (params.channel !== undefined) {
3898
+ settings.updates.channel = params.channel;
3899
+ }
3900
+ if (params.checkIntervalHours !== undefined) {
3901
+ const clamped = Math.max(1, Math.min(24, Math.round(params.checkIntervalHours)));
3902
+ settings.updates.checkIntervalHours = clamped;
3903
+ }
3904
+ if (params.autoDownload !== undefined) {
3905
+ settings.updates.autoDownload = params.autoDownload;
3906
+ }
3907
+ yield* saveSettings(settingsPath, settings);
3908
+ return {
3909
+ channel: settings.updates.channel,
3910
+ checkIntervalHours: settings.updates.checkIntervalHours,
3911
+ autoDownload: settings.updates.autoDownload
3912
+ };
3913
+ });
3914
+ }
3915
+
3916
+ // ../../packages/server/src/services/stats-service.ts
3917
+ import { dirname as dirname2, join as join14 } from "node:path";
3918
+ import { FileSystem as FileSystem11 } from "@effect/platform";
3919
+ import { Effect as Effect30 } from "effect";
3920
+
3921
+ // ../../packages/server/src/services/stats.ts
3922
+ init_src();
3923
+ import { Effect as Effect29 } from "effect";
3924
+ function emptyStats(projects = 0) {
2378
3925
  return {
2379
3926
  projects,
2380
3927
  sessions: 0,
@@ -2395,26 +3942,31 @@ function toDateString(d) {
2395
3942
  function countRecentSessions(sessions) {
2396
3943
  const today = toDateString(new Date);
2397
3944
  const now = new Date;
2398
- const weekAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
3945
+ const daysPerWeek = 7;
3946
+ const weekAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysPerWeek);
2399
3947
  const weekAgoStr = toDateString(weekAgo);
2400
3948
  let todaySessions = 0;
2401
3949
  let thisWeekSessions = 0;
2402
3950
  for (const session of sessions) {
2403
3951
  const d = new Date(session.timestamp);
2404
- if (Number.isNaN(d.getTime()))
3952
+ if (Number.isNaN(d.getTime())) {
2405
3953
  continue;
3954
+ }
2406
3955
  const sessionDay = toDateString(d);
2407
- if (sessionDay === today)
2408
- todaySessions++;
2409
- if (sessionDay >= weekAgoStr)
2410
- thisWeekSessions++;
3956
+ if (sessionDay === today) {
3957
+ todaySessions += 1;
3958
+ }
3959
+ if (sessionDay >= weekAgoStr) {
3960
+ thisWeekSessions += 1;
3961
+ }
2411
3962
  }
2412
3963
  return { todaySessions, thisWeekSessions };
2413
3964
  }
2414
3965
  function ensureModelUsage(models, model) {
2415
3966
  const existing = models[model];
2416
- if (existing)
3967
+ if (existing) {
2417
3968
  return existing;
3969
+ }
2418
3970
  const usage = {
2419
3971
  inputTokens: 0,
2420
3972
  outputTokens: 0,
@@ -2427,35 +3979,42 @@ function ensureModelUsage(models, model) {
2427
3979
  function countVisibleMessages(turns) {
2428
3980
  return turns.filter((turn) => turn.kind !== "parse_error").length;
2429
3981
  }
2430
- async function collectSessionsWithProjects(registry, stats) {
2431
- const projects = await runRegistryEffect(registry.discoverAllProjects()).catch(() => []);
2432
- stats.projects = projects.length;
2433
- const sessionsWithProject = [];
2434
- for (const project of projects) {
2435
- const sessions = await runRegistryEffect(registry.listAllSessions(project)).catch(() => []);
2436
- stats.sessions += sessions.length;
2437
- for (const session of sessions) {
2438
- sessionsWithProject.push({ project, session });
3982
+ function collectSessionsWithProjects(registry, stats) {
3983
+ return Effect29.gen(function* () {
3984
+ const discovered = yield* registry.discoverAllProjectsWithSessions();
3985
+ stats.projects = discovered.projects.length;
3986
+ const sessionsWithProject = [];
3987
+ for (const project of discovered.projects) {
3988
+ const sessions = discovered.sessionsByEncodedPath.get(project.encodedPath) ?? [];
3989
+ stats.sessions += sessions.length;
3990
+ for (const session of sessions) {
3991
+ sessionsWithProject.push({ project, session });
3992
+ }
2439
3993
  }
2440
- }
2441
- return sessionsWithProject;
3994
+ return sessionsWithProject;
3995
+ });
2442
3996
  }
2443
3997
  function applyRecentSessionStats(stats, sessionsWithProject) {
2444
3998
  const recent = countRecentSessions(sessionsWithProject.map((item) => item.session));
2445
3999
  stats.todaySessions = recent.todaySessions;
2446
4000
  stats.thisWeekSessions = recent.thisWeekSessions;
2447
4001
  }
2448
- async function loadSessionForStats(registry, project, session) {
2449
- if (!session.pluginId)
2450
- return null;
2451
- const source = project.sources.find((item) => item.pluginId === session.pluginId);
2452
- if (!source)
2453
- return null;
2454
- const plugin = registry.getPlugin(session.pluginId);
2455
- const pluginConfig = registry.getPluginConfig(session.pluginId);
2456
- const { rawSessionId } = parseSessionId(session.sessionId);
2457
- const loaded = await runPluginEffect(plugin.loadSession(source.nativeId, rawSessionId), pluginConfig).catch(() => null);
2458
- return loaded?.turns ?? null;
4002
+ function loadSessionForStats(registry, project, session) {
4003
+ return Effect29.gen(function* () {
4004
+ if (!session.pluginId) {
4005
+ return null;
4006
+ }
4007
+ const source = project.sources.find((item) => item.pluginId === session.pluginId);
4008
+ if (!source) {
4009
+ return null;
4010
+ }
4011
+ const plugin = registry.getPlugin(session.pluginId);
4012
+ const pluginConfig = registry.getPluginConfig(session.pluginId);
4013
+ const { rawSessionId } = parseSessionId(session.sessionId);
4014
+ const configLayer = makePluginConfigLayer(pluginConfig);
4015
+ const loaded = yield* plugin.loadSession(source.nativeId, rawSessionId).pipe(Effect29.provide(configLayer), Effect29.catchAll(() => Effect29.succeed(null)));
4016
+ return loaded?.turns ?? null;
4017
+ });
2459
4018
  }
2460
4019
  function applyUsageStats(stats, modelUsage, usage) {
2461
4020
  stats.inputTokens += usage.inputTokens;
@@ -2467,275 +4026,250 @@ function applyUsageStats(stats, modelUsage, usage) {
2467
4026
  modelUsage.cacheReadTokens += usage.cacheReadTokens ?? 0;
2468
4027
  modelUsage.cacheCreationTokens += usage.cacheCreationTokens ?? 0;
2469
4028
  }
4029
+ function totalUsageTokens(usage) {
4030
+ return usage.inputTokens + usage.outputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0);
4031
+ }
2470
4032
  function applyTurnStats(stats, turns, fallbackModel) {
2471
4033
  stats.messages += countVisibleMessages(turns);
2472
4034
  for (const turn of turns) {
2473
- if (turn.kind !== "assistant")
4035
+ if (turn.kind !== "assistant") {
2474
4036
  continue;
4037
+ }
2475
4038
  stats.toolCalls += turn.contentBlocks.filter((block) => block.type === "tool_call").length;
2476
- const modelUsage = ensureModelUsage(stats.models, turn.model || fallbackModel || "unknown");
2477
- if (!turn.usage)
4039
+ if (!turn.usage || totalUsageTokens(turn.usage) <= 0) {
2478
4040
  continue;
4041
+ }
4042
+ const modelUsage = ensureModelUsage(stats.models, turn.model || fallbackModel || "unknown");
2479
4043
  applyUsageStats(stats, modelUsage, turn.usage);
2480
4044
  }
2481
4045
  }
2482
- async function computeStats(registry) {
2483
- const stats = emptyStats();
2484
- const sessionsWithProject = await collectSessionsWithProjects(registry, stats);
2485
- applyRecentSessionStats(stats, sessionsWithProject);
2486
- for (const item of sessionsWithProject) {
2487
- const turns = await loadSessionForStats(registry, item.project, item.session);
2488
- if (!turns)
2489
- continue;
2490
- applyTurnStats(stats, turns, item.session.model);
2491
- }
2492
- return stats;
4046
+ function computeStats(registry) {
4047
+ return Effect29.gen(function* () {
4048
+ const stats = emptyStats();
4049
+ const sessionsWithProject = yield* collectSessionsWithProjects(registry, stats);
4050
+ applyRecentSessionStats(stats, sessionsWithProject);
4051
+ const turnsPerSession = yield* Effect29.forEach(sessionsWithProject, (item) => loadSessionForStats(registry, item.project, item.session).pipe(Effect29.map((turns) => ({ item, turns }))), { concurrency: "unbounded" });
4052
+ for (const { item, turns } of turnsPerSession) {
4053
+ if (!turns) {
4054
+ continue;
4055
+ }
4056
+ applyTurnStats(stats, turns, item.session.model);
4057
+ }
4058
+ return stats;
4059
+ });
2493
4060
  }
2494
4061
  function scanStats(registry) {
2495
4062
  return computeStats(registry);
2496
4063
  }
2497
4064
 
2498
- // ../../packages/server/src/services/app-services.ts
2499
- async function getStats(registry) {
2500
- const stats = await scanStats(registry);
2501
- return { stats };
2502
- }
2503
- async function getProjects(registry) {
2504
- const projects = await runRegistryEffect(registry.discoverAllProjects());
2505
- return { projects };
2506
- }
2507
- async function getSessions(registry, params) {
2508
- const projects = await runRegistryEffect(registry.discoverAllProjects());
2509
- const project = projects.find((p) => p.encodedPath === params.encodedPath);
2510
- if (!project)
2511
- return { sessions: [] };
2512
- const sessions = await runRegistryEffect(registry.listAllSessions(project));
2513
- return { sessions };
2514
- }
2515
- async function getSession(registry, params) {
2516
- const parsed = parseSessionId(params.sessionId);
2517
- if (!parsed.pluginId || !parsed.rawSessionId) {
2518
- throw new Error("Invalid sessionId format");
2519
- }
2520
- const pluginId = parsed.pluginId;
2521
- const rawSessionId = parsed.rawSessionId;
2522
- const projects = await runRegistryEffect(registry.discoverAllProjects());
2523
- const project = projects.find((p) => p.encodedPath === params.project);
2524
- if (!project)
2525
- throw new Error("Project not found");
2526
- const source = project.sources.find((s) => s.pluginId === pluginId);
2527
- if (!source)
2528
- throw new Error("Plugin source not found");
2529
- const plugin = registry.getPlugin(pluginId);
2530
- const pluginConfig = registry.getPluginConfig(pluginId);
2531
- const sessionDetail = plugin.loadSessionDetail ? await runPluginEffect(plugin.loadSessionDetail(source.nativeId, rawSessionId), pluginConfig) : undefined;
2532
- const session = sessionDetail?.session ?? await runPluginEffect(plugin.loadSession(source.nativeId, rawSessionId), pluginConfig);
2533
- session.sessionId = encodeSessionId(pluginId, rawSessionId);
2534
- session.pluginId = pluginId;
2535
- session.planSessionId = sessionDetail?.planSessionId ? encodeSessionId(pluginId, sessionDetail.planSessionId) : undefined;
2536
- session.implSessionId = sessionDetail?.implSessionId ? encodeSessionId(pluginId, sessionDetail.implSessionId) : undefined;
2537
- return { session };
2538
- }
2539
- async function getSubAgent(registry, params) {
2540
- const parsed = parseSessionId(params.sessionId);
2541
- if (!parsed.pluginId || !parsed.rawSessionId) {
2542
- throw new Error("Invalid sessionId format");
2543
- }
2544
- const plugin = registry.getPlugin(parsed.pluginId);
2545
- if (!plugin.loadSubAgentSession) {
2546
- throw new Error(`Sub-agent sessions are not supported by plugin: ${parsed.pluginId}`);
2547
- }
2548
- const pluginConfig = registry.getPluginConfig(parsed.pluginId);
2549
- const session = await runPluginEffect(plugin.loadSubAgentSession({
2550
- sessionId: parsed.rawSessionId,
2551
- project: params.project,
2552
- agentId: params.agentId
2553
- }), pluginConfig);
2554
- return { session };
2555
- }
2556
- function projectNameFromPath(fullPath) {
2557
- const parts = fullPath.split("/").filter(Boolean);
2558
- return parts.slice(-2).join("/");
2559
- }
2560
- async function searchSessions(registry) {
2561
- const projects = await runRegistryEffect(registry.discoverAllProjects());
2562
- const allSessions = [];
2563
- for (const project of projects) {
2564
- const sessions = await runRegistryEffect(registry.listAllSessions(project));
2565
- const projectName = projectNameFromPath(project.name);
2566
- for (const session of sessions) {
2567
- allSessions.push({
2568
- ...session,
2569
- encodedPath: project.encodedPath,
2570
- projectName
2571
- });
2572
- }
4065
+ // ../../packages/server/src/services/stats-service.ts
4066
+ var STATS_CACHE_FILENAME = "stats-cache.json";
4067
+ var refreshBootTimes = new Map;
4068
+ var refreshEpochs = new Map;
4069
+ var refreshingCachePaths = new Set;
4070
+ function getStatsCachePath(settingsPath) {
4071
+ return join14(dirname2(settingsPath), STATS_CACHE_FILENAME);
4072
+ }
4073
+ function getRefreshBootTime(cachePath) {
4074
+ const existing = refreshBootTimes.get(cachePath);
4075
+ if (existing) {
4076
+ return existing;
2573
4077
  }
2574
- sortByIsoDesc(allSessions, (session) => session.timestamp);
2575
- return { sessions: allSessions };
4078
+ const bootTime = new Date().toISOString();
4079
+ refreshBootTimes.set(cachePath, bootTime);
4080
+ return bootTime;
2576
4081
  }
2577
- async function buildPluginSettingsResponse(settingsPath) {
2578
- const settings = await loadSettings(settingsPath);
2579
- const plugins = BUILTIN_PLUGIN_DESCRIPTORS.map(({ plugin, defaultDir }) => {
2580
- const id = plugin.id;
2581
- const displayName = plugin.displayName;
2582
- const pluginConf = settings.plugins[id] ?? { enabled: true, dataDir: null };
2583
- const defaultDataDir = defaultDir;
2584
- const isCustomDir = pluginConf.dataDir !== null;
2585
- return {
2586
- id,
2587
- displayName,
2588
- enabled: pluginConf.enabled,
2589
- dataDir: pluginConf.dataDir ?? defaultDataDir,
2590
- defaultDataDir,
2591
- isCustomDir
2592
- };
2593
- });
2594
- return { plugins };
2595
- }
2596
- function getPluginSettings(settingsPath) {
2597
- return buildPluginSettingsResponse(settingsPath);
4082
+ function getRefreshEpoch(cachePath) {
4083
+ return refreshEpochs.get(cachePath) ?? 0;
2598
4084
  }
2599
- async function getGeneralSettings(settingsPath) {
2600
- const settings = await loadSettings(settingsPath);
2601
- return { showSecurityWarning: settings.general?.showSecurityWarning ?? true };
4085
+ function bumpRefreshEpoch(cachePath) {
4086
+ const nextEpoch = getRefreshEpoch(cachePath) + 1;
4087
+ refreshEpochs.set(cachePath, nextEpoch);
4088
+ return nextEpoch;
2602
4089
  }
2603
- async function isFirstLaunch(settingsPath) {
2604
- try {
2605
- await access(settingsPath);
2606
- return { firstLaunch: false };
2607
- } catch {
2608
- return { firstLaunch: true };
4090
+ function isDashboardStats(value) {
4091
+ if (typeof value !== "object" || value === null) {
4092
+ return false;
4093
+ }
4094
+ const candidate = value;
4095
+ const requiredNumberFields = [
4096
+ "projects",
4097
+ "sessions",
4098
+ "messages",
4099
+ "todaySessions",
4100
+ "thisWeekSessions",
4101
+ "inputTokens",
4102
+ "outputTokens",
4103
+ "cacheReadTokens",
4104
+ "cacheCreationTokens",
4105
+ "toolCalls"
4106
+ ];
4107
+ for (const field of requiredNumberFields) {
4108
+ if (typeof candidate[field] !== "number") {
4109
+ return false;
4110
+ }
2609
4111
  }
4112
+ return typeof candidate["models"] === "object" && candidate["models"] !== null;
2610
4113
  }
2611
- async function completeOnboarding(settingsPath) {
2612
- const { firstLaunch } = await isFirstLaunch(settingsPath);
2613
- if (firstLaunch) {
2614
- await saveSettings(settingsPath, getDefaultSettings());
4114
+ function isStatsCacheFile(value) {
4115
+ if (typeof value !== "object" || value === null) {
4116
+ return false;
2615
4117
  }
2616
- return { ok: true };
4118
+ const candidate = value;
4119
+ return candidate["version"] === 1 && typeof candidate["cachedAt"] === "string" && isDashboardStats(candidate["stats"]);
4120
+ }
4121
+ function loadStatsCache(settingsPath) {
4122
+ return Effect30.gen(function* () {
4123
+ const fs = yield* FileSystem11.FileSystem;
4124
+ const cachePath = getStatsCachePath(settingsPath);
4125
+ const raw = yield* fs.readFileString(cachePath).pipe(Effect30.catchAll(() => Effect30.succeed(null)));
4126
+ if (raw === null) {
4127
+ return null;
4128
+ }
4129
+ const parsed = yield* Effect30.try({
4130
+ try: () => JSON.parse(raw),
4131
+ catch: () => null
4132
+ }).pipe(Effect30.catchAll(() => Effect30.succeed(null)));
4133
+ return parsed !== null && isStatsCacheFile(parsed) ? parsed : null;
4134
+ });
2617
4135
  }
2618
- async function resetSettings(settingsPath) {
2619
- try {
2620
- await rm(settingsPath);
2621
- } catch {}
2622
- return { ok: true };
4136
+ function persistStatsCache(settingsPath, stats, expectedEpoch) {
4137
+ return Effect30.gen(function* () {
4138
+ const fs = yield* FileSystem11.FileSystem;
4139
+ const cachePath = getStatsCachePath(settingsPath);
4140
+ const cacheDir = dirname2(cachePath);
4141
+ const cachedAt = new Date().toISOString();
4142
+ yield* fs.makeDirectory(cacheDir, { recursive: true }).pipe(Effect30.catchAll(() => Effect30.void));
4143
+ if (getRefreshEpoch(cachePath) !== expectedEpoch) {
4144
+ return null;
4145
+ }
4146
+ const tmpPath = join14(cacheDir, `.stats-cache-${Date.now()}.tmp`);
4147
+ const payload = {
4148
+ version: 1,
4149
+ cachedAt,
4150
+ stats
4151
+ };
4152
+ const wroteTempFile = yield* fs.writeFileString(tmpPath, JSON.stringify(payload, null, 2)).pipe(Effect30.map(() => true), Effect30.catchAll(() => Effect30.succeed(false)));
4153
+ if (!wroteTempFile) {
4154
+ return null;
4155
+ }
4156
+ if (getRefreshEpoch(cachePath) !== expectedEpoch) {
4157
+ yield* fs.remove(tmpPath).pipe(Effect30.catchAll(() => Effect30.void));
4158
+ return null;
4159
+ }
4160
+ const renamed = yield* fs.rename(tmpPath, cachePath).pipe(Effect30.map(() => true), Effect30.catchAll(() => fs.remove(tmpPath).pipe(Effect30.catchAll(() => Effect30.void), Effect30.map(() => false))));
4161
+ if (!renamed) {
4162
+ return null;
4163
+ }
4164
+ return getRefreshEpoch(cachePath) === expectedEpoch ? cachedAt : null;
4165
+ });
2623
4166
  }
2624
- async function updateGeneralSettings(settingsPath, params) {
2625
- const settings = await loadSettings(settingsPath);
2626
- if (!settings.general) {
2627
- settings.general = {};
2628
- }
2629
- if (params.showSecurityWarning !== undefined) {
2630
- settings.general.showSecurityWarning = params.showSecurityWarning;
2631
- }
2632
- await saveSettings(settingsPath, settings);
2633
- return { showSecurityWarning: settings.general.showSecurityWarning ?? true };
4167
+ function computeAndPersistStats(settingsPath, registry, expectedEpoch) {
4168
+ return Effect30.gen(function* () {
4169
+ const stats = yield* scanStats(registry);
4170
+ const cachedAt = yield* persistStatsCache(settingsPath, stats, expectedEpoch);
4171
+ return { stats, ...cachedAt ? { cachedAt } : {} };
4172
+ });
2634
4173
  }
2635
- async function updatePluginSetting(settingsPath, params) {
2636
- if (!BUILTIN_PLUGIN_ID_SET.has(params.pluginId)) {
2637
- throw new Error(`Unknown plugin: ${params.pluginId}`);
2638
- }
2639
- const settings = await loadSettings(settingsPath);
2640
- const existing = settings.plugins[params.pluginId] ?? { enabled: true, dataDir: null };
2641
- if (params.enabled !== undefined) {
2642
- existing.enabled = params.enabled;
2643
- }
2644
- if (params.dataDir !== undefined) {
2645
- existing.dataDir = params.dataDir;
2646
- }
2647
- settings.plugins[params.pluginId] = existing;
2648
- await saveSettings(settingsPath, settings);
2649
- return buildPluginSettingsResponse(settingsPath);
4174
+ function refreshStats(settingsPath, registry) {
4175
+ return Effect30.gen(function* () {
4176
+ const cachePath = getStatsCachePath(settingsPath);
4177
+ const expectedEpoch = getRefreshEpoch(cachePath);
4178
+ refreshingCachePaths.add(cachePath);
4179
+ const result = yield* computeAndPersistStats(settingsPath, registry, expectedEpoch);
4180
+ return { ...result, refreshing: false };
4181
+ }).pipe(Effect30.ensuring(Effect30.sync(() => {
4182
+ refreshingCachePaths.delete(getStatsCachePath(settingsPath));
4183
+ })));
2650
4184
  }
2651
- async function getUpdateSettings(settingsPath) {
2652
- const settings = await loadSettings(settingsPath);
2653
- return {
2654
- channel: settings.updates?.channel ?? "stable",
2655
- checkIntervalHours: settings.updates?.checkIntervalHours ?? 6,
2656
- autoDownload: settings.updates?.autoDownload ?? true
2657
- };
4185
+ function scheduleRefresh(settingsPath, registry) {
4186
+ return Effect30.gen(function* () {
4187
+ const cachePath = getStatsCachePath(settingsPath);
4188
+ if (refreshingCachePaths.has(cachePath)) {
4189
+ return;
4190
+ }
4191
+ const expectedEpoch = getRefreshEpoch(cachePath);
4192
+ refreshingCachePaths.add(cachePath);
4193
+ yield* computeAndPersistStats(settingsPath, registry, expectedEpoch).pipe(Effect30.catchAllCause(() => Effect30.void), Effect30.ensuring(Effect30.sync(() => {
4194
+ refreshingCachePaths.delete(cachePath);
4195
+ })), Effect30.forkDaemon);
4196
+ });
2658
4197
  }
2659
- async function updateUpdateSettings(settingsPath, params) {
2660
- const settings = await loadSettings(settingsPath);
2661
- if (!settings.updates) {
2662
- settings.updates = { channel: "stable", checkIntervalHours: 6, autoDownload: true };
2663
- }
2664
- if (params.channel !== undefined) {
2665
- settings.updates.channel = params.channel;
2666
- }
2667
- if (params.checkIntervalHours !== undefined) {
2668
- const clamped = Math.max(1, Math.min(24, Math.round(params.checkIntervalHours)));
2669
- settings.updates.checkIntervalHours = clamped;
2670
- }
2671
- if (params.autoDownload !== undefined) {
2672
- settings.updates.autoDownload = params.autoDownload;
2673
- }
2674
- await saveSettings(settingsPath, settings);
2675
- return {
2676
- channel: settings.updates.channel,
2677
- checkIntervalHours: settings.updates.checkIntervalHours,
2678
- autoDownload: settings.updates.autoDownload
2679
- };
4198
+ function getStats(settingsPath, registry) {
4199
+ return Effect30.gen(function* () {
4200
+ const cachePath = getStatsCachePath(settingsPath);
4201
+ const cached = yield* loadStatsCache(settingsPath);
4202
+ if (!cached) {
4203
+ return yield* refreshStats(settingsPath, registry);
4204
+ }
4205
+ const bootTime = getRefreshBootTime(cachePath);
4206
+ const shouldRefresh = cached.cachedAt < bootTime;
4207
+ if (shouldRefresh) {
4208
+ yield* scheduleRefresh(settingsPath, registry);
4209
+ }
4210
+ return {
4211
+ stats: cached.stats,
4212
+ cachedAt: cached.cachedAt,
4213
+ refreshing: shouldRefresh || refreshingCachePaths.has(cachePath)
4214
+ };
4215
+ });
2680
4216
  }
2681
-
2682
- // ../../packages/server/src/services/registry.ts
2683
- init_src();
2684
- class PluginRegistry2 extends PluginRegistry {
4217
+ function invalidateStatsCache(settingsPath) {
4218
+ return Effect30.gen(function* () {
4219
+ const fs = yield* FileSystem11.FileSystem;
4220
+ const cachePath = getStatsCachePath(settingsPath);
4221
+ bumpRefreshEpoch(cachePath);
4222
+ yield* fs.remove(cachePath).pipe(Effect30.catchAll(() => Effect30.void));
4223
+ });
2685
4224
  }
2686
4225
 
2687
- // ../../packages/server/src/services/auto-discover.ts
2688
- async function createRegistry(settings) {
2689
- const registry = new PluginRegistry2;
2690
- for (const { plugin, defaultDir } of BUILTIN_PLUGIN_DESCRIPTORS) {
2691
- const pluginSettings = settings?.plugins[plugin.id];
2692
- if (pluginSettings && !pluginSettings.enabled)
2693
- continue;
2694
- const dataDir = pluginSettings?.dataDir ?? defaultDir;
2695
- const available = await runPluginEffect(plugin.isDataAvailable, { dataDir }).catch(() => false);
2696
- if (available) {
2697
- registry.register(plugin, { dataDir });
2698
- }
2699
- }
2700
- return registry;
4226
+ // ../../packages/server/src/services/version-service.ts
4227
+ function makeVersionState(version, commit) {
4228
+ const normalizedVersion = version == null || version === "0.0.0" ? "dev" : version;
4229
+ return { version: normalizedVersion, commit: commit ?? "" };
4230
+ }
4231
+ function getVersion(state) {
4232
+ return { version: state.version, commit: state.commit };
2701
4233
  }
2702
4234
 
2703
4235
  // ../../packages/server/src/effect/server-services.ts
2704
4236
  class KloviServices extends Context4.Tag("@klovi/KloviServices")() {
2705
4237
  }
2706
- var KloviServicesLive = Layer6.effect(KloviServices, Effect19.gen(function* () {
4238
+ var KloviServicesLive = Layer6.effect(KloviServices, Effect31.gen(function* () {
2707
4239
  const config = yield* ServerConfig;
2708
4240
  const { settingsPath } = config;
2709
- const version = config.version === "0.0.0" ? "dev" : config.version;
2710
- const settings = yield* Effect19.promise(() => loadSettings(settingsPath));
2711
- let registry = yield* Effect19.promise(() => createRegistry(settings));
2712
- async function refreshRegistry() {
2713
- const freshSettings = await loadSettings(settingsPath);
2714
- registry = await createRegistry(freshSettings);
2715
- }
4241
+ const versionState = makeVersionState(config.version, config.commit);
4242
+ const settings = yield* loadSettings(settingsPath);
4243
+ let registry = yield* createRegistry(settings);
4244
+ const refreshRegistry = () => Effect31.gen(function* () {
4245
+ const freshSettings = yield* loadSettings(settingsPath);
4246
+ registry = yield* createRegistry(freshSettings);
4247
+ });
2716
4248
  return {
2717
4249
  acceptRisks: () => completeOnboarding(settingsPath),
2718
- getVersion: () => ({ version, commit: config.commit }),
2719
- getStats: () => getStats(registry),
4250
+ getVersion: () => Effect31.succeed(getVersion(versionState)),
4251
+ getStats: () => getStats(settingsPath, registry),
2720
4252
  getProjects: () => getProjects(registry),
2721
4253
  getSessions: (params) => getSessions(registry, params),
2722
4254
  getSession: (params) => getSession(registry, params),
2723
4255
  getSubAgent: (params) => getSubAgent(registry, params),
2724
4256
  searchSessions: () => searchSessions(registry),
2725
4257
  getPluginSettings: () => getPluginSettings(settingsPath),
2726
- updatePluginSetting: async (params) => {
2727
- const result = await updatePluginSetting(settingsPath, params);
2728
- await refreshRegistry();
4258
+ updatePluginSetting: (params) => Effect31.gen(function* () {
4259
+ const result = yield* updatePluginSetting(settingsPath, params);
4260
+ yield* refreshRegistry();
4261
+ yield* invalidateStatsCache(settingsPath);
2729
4262
  return result;
2730
- },
4263
+ }),
2731
4264
  getGeneralSettings: () => getGeneralSettings(settingsPath),
2732
4265
  updateGeneralSettings: (params) => updateGeneralSettings(settingsPath, params),
2733
4266
  isFirstLaunch: () => isFirstLaunch(settingsPath),
2734
- resetSettings: async () => {
2735
- const result = await resetSettings(settingsPath);
2736
- await refreshRegistry();
4267
+ resetSettings: () => Effect31.gen(function* () {
4268
+ const result = yield* resetSettings(settingsPath);
4269
+ yield* refreshRegistry();
4270
+ yield* invalidateStatsCache(settingsPath);
2737
4271
  return result;
2738
- },
4272
+ }),
2739
4273
  getUpdateSettings: () => getUpdateSettings(settingsPath),
2740
4274
  updateUpdateSettings: (params) => updateUpdateSettings(settingsPath, params),
2741
4275
  getRegistry: () => registry,
@@ -2746,12 +4280,13 @@ var KloviServicesLive = Layer6.effect(KloviServices, Effect19.gen(function* () {
2746
4280
  // ../../packages/server/src/effect/bootstrap.ts
2747
4281
  function getDefaultSettingsPath() {
2748
4282
  const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
2749
- return join12(home, ".klovi", "settings.json");
4283
+ return join15(home, ".klovi", "settings.json");
2750
4284
  }
2751
4285
  function detectRuntime(requested = "auto") {
2752
- if (requested !== "auto")
4286
+ if (requested !== "auto") {
2753
4287
  return requested;
2754
- return typeof globalThis.Bun !== "undefined" ? "bun" : "node";
4288
+ }
4289
+ return typeof globalThis.Bun === "undefined" ? "node" : "bun";
2755
4290
  }
2756
4291
  async function bootstrapServer(options, makeServe) {
2757
4292
  const host = options.host ?? "127.0.0.1";
@@ -2762,8 +4297,7 @@ async function bootstrapServer(options, makeServe) {
2762
4297
  const rt = detectRuntime(options.runtime);
2763
4298
  let platformLayer;
2764
4299
  if (rt === "node") {
2765
- const { NodePluginLayer: NodePluginLayer2, makeNodeServerLayer: makeNodeServerLayer2 } = await Promise.resolve().then(() => (init_platform_node(), exports_platform_node));
2766
- setPluginLayer(NodePluginLayer2);
4300
+ const { makeNodeServerLayer: makeNodeServerLayer2 } = await Promise.resolve().then(() => (init_platform_node(), exports_platform_node));
2767
4301
  platformLayer = makeNodeServerLayer2({ host, port });
2768
4302
  } else {
2769
4303
  platformLayer = makeBunServerLayer({ hostname: host, port });
@@ -2782,26 +4316,28 @@ async function bootstrapServer(options, makeServe) {
2782
4316
  resolveAddress = resolve;
2783
4317
  rejectAddress = reject;
2784
4318
  });
2785
- const addressCapture = Layer8.effectDiscard(HttpServer.addressWith((address) => Effect20.sync(() => {
4319
+ const addressCapture = Layer8.effectDiscard(HttpServer.addressWith((address) => Effect32.gen(function* () {
2786
4320
  const addr = address;
2787
- resolveAddress(`http://${addr.hostname}:${addr.port}`);
4321
+ const url2 = `http://${addr.hostname}:${addr.port}`;
4322
+ yield* Effect32.log(`Klovi server listening on ${url2}`);
4323
+ resolveAddress(url2);
2788
4324
  })));
2789
4325
  const serveLayer = makeServe();
2790
4326
  const fullLayer = Layer8.merge(serveLayer, addressCapture).pipe(Layer8.provide(servicesLayer), Layer8.provide(configLayer), Layer8.provide(platformLayer));
2791
- const fiber = Effect20.runFork(Layer8.launch(fullLayer));
2792
- Effect20.runFork(Fiber.join(fiber).pipe(Effect20.catchAllCause((cause) => Effect20.sync(() => rejectAddress(Cause.squash(cause))))));
4327
+ const fiber = Effect32.runFork(Layer8.launch(fullLayer));
4328
+ Effect32.runFork(Fiber.join(fiber).pipe(Effect32.catchAllCause((cause) => Effect32.sync(() => rejectAddress(Cause.squash(cause))))));
2793
4329
  const url = await addressPromise;
2794
4330
  return {
2795
4331
  url,
2796
- stop() {
2797
- Effect20.runFork(Fiber.interrupt(fiber));
4332
+ stop: () => {
4333
+ Effect32.runFork(Fiber.interrupt(fiber));
2798
4334
  }
2799
4335
  };
2800
4336
  }
2801
4337
 
2802
4338
  // ../../packages/server/src/effect/http-app.ts
2803
4339
  import { HttpRouter, HttpServer as HttpServer2, HttpServerRequest, HttpServerResponse } from "@effect/platform";
2804
- import { Effect as Effect21 } from "effect";
4340
+ import { Effect as Effect33 } from "effect";
2805
4341
 
2806
4342
  // ../../packages/server/src/rpc-error.ts
2807
4343
  class RPCError extends Error {
@@ -2814,10 +4350,35 @@ class RPCError extends Error {
2814
4350
 
2815
4351
  // ../../packages/server/src/effect/http-app.ts
2816
4352
  var NON_RPC_KEYS = new Set(["getRegistry", "settingsPath"]);
2817
- function isRPCMethod(method, services) {
4353
+ function isRpcMethod(method, services) {
2818
4354
  return Object.hasOwn(services, method) && !NON_RPC_KEYS.has(method);
2819
4355
  }
2820
- var rpcHandler = Effect21.gen(function* () {
4356
+ function mapDomainErrorToStatus(err) {
4357
+ if (err instanceof InvalidSessionIdError) {
4358
+ return { status: 400, message: "Invalid sessionId format" };
4359
+ }
4360
+ if (err instanceof ProjectNotFoundError) {
4361
+ return { status: 404, message: "Project not found" };
4362
+ }
4363
+ if (err instanceof PluginSourceNotFoundError) {
4364
+ return { status: 404, message: "Plugin source not found" };
4365
+ }
4366
+ if (err instanceof UnknownPluginError) {
4367
+ return { status: 400, message: `Unknown plugin: ${err.pluginId}` };
4368
+ }
4369
+ if (err instanceof SubAgentNotSupportedError) {
4370
+ return { status: 400, message: `Sub-agent sessions are not supported by plugin: ${err.pluginId}` };
4371
+ }
4372
+ if (err instanceof SettingsWriteError) {
4373
+ return { status: 500, message: "Failed to write settings" };
4374
+ }
4375
+ if (err instanceof RPCError) {
4376
+ return { status: err.status, message: err.message };
4377
+ }
4378
+ const message = err instanceof Error ? err.message : "Internal server error";
4379
+ return { status: 500, message };
4380
+ }
4381
+ var rpcHandler = Effect33.gen(function* () {
2821
4382
  const services = yield* KloviServices;
2822
4383
  const routeParams = yield* HttpRouter.params;
2823
4384
  const req = yield* HttpServerRequest.HttpServerRequest;
@@ -2825,8 +4386,9 @@ var rpcHandler = Effect21.gen(function* () {
2825
4386
  if (!method) {
2826
4387
  return HttpServerResponse.unsafeJson({ error: "Method name required" }, { status: 400 });
2827
4388
  }
2828
- if (!isRPCMethod(method, services)) {
2829
- return yield* Effect21.fail(new RPCError(404, `Unknown method: ${method}`));
4389
+ if (!isRpcMethod(method, services)) {
4390
+ const httpNotFound = 404;
4391
+ return yield* Effect33.fail(new RPCError(httpNotFound, `Unknown method: ${method}`));
2830
4392
  }
2831
4393
  let params = {};
2832
4394
  const bodyText = yield* req.text;
@@ -2838,44 +4400,41 @@ var rpcHandler = Effect21.gen(function* () {
2838
4400
  }
2839
4401
  }
2840
4402
  const handler = services[method];
2841
- return yield* Effect21.tryPromise({
2842
- try: async () => {
2843
- const result = await Promise.resolve(handler(params));
2844
- return HttpServerResponse.unsafeJson(result);
2845
- },
2846
- catch: (err) => err
2847
- });
2848
- }).pipe(Effect21.catchAll((err) => {
2849
- if (err instanceof RPCError) {
2850
- return Effect21.succeed(HttpServerResponse.unsafeJson({ error: err.message }, { status: err.status }));
2851
- }
2852
- const message = err instanceof Error ? err.message : "Internal server error";
2853
- return Effect21.succeed(HttpServerResponse.unsafeJson({ error: message }, { status: 500 }));
4403
+ const value = yield* handler(params);
4404
+ return HttpServerResponse.unsafeJson(value);
4405
+ }).pipe(Effect33.catchAll((err) => {
4406
+ const { status, message } = mapDomainErrorToStatus(err);
4407
+ return Effect33.succeed(HttpServerResponse.unsafeJson({ error: message }, { status }));
2854
4408
  }));
2855
- var emptyMethodHandler = Effect21.succeed(HttpServerResponse.unsafeJson({ error: "Method name required" }, { status: 400 }));
4409
+ var emptyMethodHandler = Effect33.succeed(HttpServerResponse.unsafeJson({ error: "Method name required" }, { status: 400 }));
2856
4410
  var makeRpcRouter = () => HttpRouter.empty.pipe(HttpRouter.post("/api/rpc/", emptyMethodHandler), HttpRouter.post("/api/rpc/:method", rpcHandler));
2857
- var notFoundHandler = Effect21.succeed(HttpServerResponse.unsafeJson({ error: "Not found" }, { status: 404 }));
2858
- var makeHttpApp = () => makeRpcRouter().pipe(Effect21.catchTag("RouteNotFound", () => notFoundHandler));
4411
+ var notFoundHandler = Effect33.succeed(HttpServerResponse.unsafeJson({ error: "Not found" }, { status: 404 }));
4412
+ var makeHttpApp = () => makeRpcRouter().pipe(Effect33.catchTag("RouteNotFound", () => notFoundHandler));
2859
4413
  var makeServeLayer = () => makeHttpApp().pipe(HttpServer2.serve());
2860
4414
 
2861
4415
  // src/http-app.ts
2862
4416
  import { HttpServer as HttpServer3 } from "@effect/platform";
2863
- import { Effect as Effect23 } from "effect";
4417
+ import { Effect as Effect35 } from "effect";
2864
4418
 
2865
4419
  // src/static-handler.ts
2866
4420
  import { HttpServerRequest as HttpServerRequest2, HttpServerResponse as HttpServerResponse2 } from "@effect/platform";
2867
- import { Effect as Effect22 } from "effect";
2868
- var makeStaticHandler = (staticDir) => Effect22.gen(function* () {
4421
+ import { Effect as Effect34 } from "effect";
4422
+ var notFound = HttpServerResponse2.unsafeJson({ error: "Not found" }, { status: 404 });
4423
+ var isNavigationRequest = (pathname) => {
4424
+ const lastSegment2 = pathname.split("/").pop() ?? "";
4425
+ return !lastSegment2.includes(".");
4426
+ };
4427
+ var makeStaticHandler = (staticDir) => Effect34.gen(function* () {
2869
4428
  const req = yield* HttpServerRequest2.HttpServerRequest;
2870
4429
  const url = new URL(req.url, "http://localhost");
2871
4430
  const filePath = url.pathname === "/" ? "/index.html" : url.pathname;
2872
- return yield* HttpServerResponse2.file(`${staticDir}${filePath}`).pipe(Effect22.orElse(() => HttpServerResponse2.file(`${staticDir}/index.html`)), Effect22.orElse(() => Effect22.succeed(HttpServerResponse2.unsafeJson({ error: "Not found" }, { status: 404 }))));
4431
+ return yield* HttpServerResponse2.file(`${staticDir}${filePath}`).pipe(Effect34.orElse(() => isNavigationRequest(url.pathname) ? HttpServerResponse2.file(`${staticDir}/index.html`).pipe(Effect34.orElse(() => Effect34.succeed(notFound))) : Effect34.succeed(notFound)));
2873
4432
  });
2874
4433
 
2875
4434
  // src/http-app.ts
2876
4435
  var makePackageHttpApp = (staticDir) => {
2877
4436
  const router = makeRpcRouter();
2878
- return router.pipe(Effect23.catchTag("RouteNotFound", () => makeStaticHandler(staticDir)));
4437
+ return router.pipe(Effect35.catchTag("RouteNotFound", () => makeStaticHandler(staticDir)));
2879
4438
  };
2880
4439
  var makePackageServeLayer = (staticDir) => {
2881
4440
  if (!staticDir) {
@@ -2891,7 +4450,8 @@ function startKloviServer(options = {}) {
2891
4450
 
2892
4451
  // src/server.ts
2893
4452
  function openInBrowser(url) {
2894
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
4453
+ const commands = { darwin: "open", win32: "cmd" };
4454
+ const cmd = commands[process.platform] ?? "xdg-open";
2895
4455
  const args = process.platform === "win32" ? ["/c", "start", url] : [url];
2896
4456
  execFile(cmd, args, () => {});
2897
4457
  }