@cookielab.io/klovi 3.2.0 → 3.3.0

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