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