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