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