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