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