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