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