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