@claude-sessions/core 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +167 -53
- package/dist/index.js +1010 -451
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-C2fzbmg9.d.ts → types-BdPaAnP8.d.ts} +153 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var createLogger = (namespace) => ({
|
|
|
24
24
|
|
|
25
25
|
// src/paths.ts
|
|
26
26
|
var log = createLogger("paths");
|
|
27
|
-
var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
|
|
27
|
+
var getSessionsDir = () => process.env.CLAUDE_SESSIONS_DIR || path.join(os.homedir(), ".claude", "projects");
|
|
28
28
|
var getTodosDir = () => path.join(os.homedir(), ".claude", "todos");
|
|
29
29
|
var extractCwdFromContent = (content) => {
|
|
30
30
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
@@ -80,22 +80,22 @@ var pathToFolderName = (absolutePath) => {
|
|
|
80
80
|
return convertNonAscii(absolutePath).replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-").replace(/\./g, "-");
|
|
81
81
|
};
|
|
82
82
|
var tryGetCwdFromFile = (filePath, fileSystem = fs, logger2 = log) => {
|
|
83
|
-
const
|
|
83
|
+
const basename3 = path.basename(filePath);
|
|
84
84
|
try {
|
|
85
85
|
const content = fileSystem.readFileSync(filePath, "utf-8");
|
|
86
86
|
const cwd = extractCwdFromContent(content);
|
|
87
87
|
if (cwd === null) {
|
|
88
88
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
89
89
|
if (lines.length === 0) {
|
|
90
|
-
logger2.debug(`tryGetCwdFromFile: ${
|
|
90
|
+
logger2.debug(`tryGetCwdFromFile: ${basename3} -> empty file`);
|
|
91
91
|
} else {
|
|
92
|
-
logger2.debug(`tryGetCwdFromFile: ${
|
|
92
|
+
logger2.debug(`tryGetCwdFromFile: ${basename3} -> no cwd found in ${lines.length} lines`);
|
|
93
93
|
}
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
96
|
return cwd;
|
|
97
97
|
} catch (e) {
|
|
98
|
-
logger2.warn(`tryGetCwdFromFile: ${
|
|
98
|
+
logger2.warn(`tryGetCwdFromFile: ${basename3} -> read error: ${e}`);
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
};
|
|
@@ -254,6 +254,9 @@ var maskHomePath = (text, homeDir) => {
|
|
|
254
254
|
const regex = new RegExp(`${escapedHome}(?=[/\\\\]|$)`, "g");
|
|
255
255
|
return text.replace(regex, "~");
|
|
256
256
|
};
|
|
257
|
+
var getSessionSortTimestamp = (session) => {
|
|
258
|
+
return session.summaries?.[0]?.timestamp ?? session.createdAt;
|
|
259
|
+
};
|
|
257
260
|
var sortProjects = (projects, options = {}) => {
|
|
258
261
|
const { currentProjectName, homeDir, filterEmpty = true } = options;
|
|
259
262
|
const filtered = filterEmpty ? projects.filter((p) => p.sessionCount > 0) : projects;
|
|
@@ -298,46 +301,127 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
|
|
|
298
301
|
}
|
|
299
302
|
return linkedAgents;
|
|
300
303
|
});
|
|
301
|
-
var
|
|
304
|
+
var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
|
|
302
305
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
303
306
|
const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
|
|
304
307
|
const sessionIds = new Set(
|
|
305
308
|
files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
|
|
306
309
|
);
|
|
307
|
-
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
308
310
|
const orphanAgents = [];
|
|
309
|
-
|
|
311
|
+
const checkAgentFile = async (filePath) => {
|
|
312
|
+
try {
|
|
313
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
314
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
315
|
+
const firstLine = lines[0];
|
|
316
|
+
if (!firstLine) return null;
|
|
317
|
+
const parsed = JSON.parse(firstLine);
|
|
318
|
+
if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
|
|
319
|
+
const fileName = path2.basename(filePath);
|
|
320
|
+
return {
|
|
321
|
+
agentId: fileName.replace(".jsonl", ""),
|
|
322
|
+
sessionId: parsed.sessionId,
|
|
323
|
+
lineCount: lines.length
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
};
|
|
330
|
+
const rootAgentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
331
|
+
for (const agentFile of rootAgentFiles) {
|
|
310
332
|
const filePath = path2.join(projectPath, agentFile);
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
|
|
334
|
+
if (orphan) {
|
|
335
|
+
orphanAgents.push({ ...orphan, filePath });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const entry of files) {
|
|
339
|
+
const entryPath = path2.join(projectPath, entry);
|
|
340
|
+
const stat4 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
|
|
341
|
+
if (stat4?.isDirectory() && !entry.startsWith(".")) {
|
|
342
|
+
const subagentsPath = path2.join(entryPath, "subagents");
|
|
343
|
+
const subagentsExists = yield* Effect.tryPromise(
|
|
344
|
+
() => fs2.stat(subagentsPath).then(() => true).catch(() => false)
|
|
345
|
+
);
|
|
346
|
+
if (subagentsExists) {
|
|
347
|
+
const subagentFiles = yield* Effect.tryPromise(
|
|
348
|
+
() => fs2.readdir(subagentsPath).catch(() => [])
|
|
349
|
+
);
|
|
350
|
+
for (const subagentFile of subagentFiles) {
|
|
351
|
+
if (subagentFile.startsWith("agent-") && subagentFile.endsWith(".jsonl")) {
|
|
352
|
+
const filePath = path2.join(subagentsPath, subagentFile);
|
|
353
|
+
const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
|
|
354
|
+
if (orphan) {
|
|
355
|
+
orphanAgents.push({ ...orphan, filePath });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
321
358
|
}
|
|
322
|
-
} catch {
|
|
323
359
|
}
|
|
324
360
|
}
|
|
325
361
|
}
|
|
326
362
|
return orphanAgents;
|
|
327
363
|
});
|
|
364
|
+
var findOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
365
|
+
const orphans = yield* findOrphanAgentsWithPaths(projectName);
|
|
366
|
+
return orphans.map(({ agentId, sessionId }) => ({ agentId, sessionId }));
|
|
367
|
+
});
|
|
328
368
|
var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
329
369
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
330
|
-
const orphans = yield*
|
|
331
|
-
const backupDir = path2.join(projectPath, ".bak");
|
|
332
|
-
yield* Effect.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
|
|
370
|
+
const orphans = yield* findOrphanAgentsWithPaths(projectName);
|
|
333
371
|
const deletedAgents = [];
|
|
372
|
+
const backedUpAgents = [];
|
|
373
|
+
const cleanedFolders = [];
|
|
374
|
+
let backupDirCreated = false;
|
|
375
|
+
const foldersToCheck = /* @__PURE__ */ new Set();
|
|
334
376
|
for (const orphan of orphans) {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
377
|
+
const parentDir = path2.dirname(orphan.filePath);
|
|
378
|
+
if (parentDir.endsWith("/subagents") || parentDir.endsWith("\\subagents")) {
|
|
379
|
+
foldersToCheck.add(parentDir);
|
|
380
|
+
}
|
|
381
|
+
if (orphan.lineCount <= 2) {
|
|
382
|
+
yield* Effect.tryPromise(() => fs2.unlink(orphan.filePath));
|
|
383
|
+
deletedAgents.push(orphan.agentId);
|
|
384
|
+
} else {
|
|
385
|
+
if (!backupDirCreated) {
|
|
386
|
+
const backupDir2 = path2.join(projectPath, ".bak");
|
|
387
|
+
yield* Effect.tryPromise(() => fs2.mkdir(backupDir2, { recursive: true }));
|
|
388
|
+
backupDirCreated = true;
|
|
389
|
+
}
|
|
390
|
+
const backupDir = path2.join(projectPath, ".bak");
|
|
391
|
+
const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
|
|
392
|
+
yield* Effect.tryPromise(() => fs2.rename(orphan.filePath, agentBackupPath));
|
|
393
|
+
backedUpAgents.push(orphan.agentId);
|
|
394
|
+
}
|
|
339
395
|
}
|
|
340
|
-
|
|
396
|
+
for (const subagentsDir of foldersToCheck) {
|
|
397
|
+
const isEmpty = yield* Effect.tryPromise(async () => {
|
|
398
|
+
const files = await fs2.readdir(subagentsDir);
|
|
399
|
+
return files.length === 0;
|
|
400
|
+
});
|
|
401
|
+
if (isEmpty) {
|
|
402
|
+
yield* Effect.tryPromise(() => fs2.rmdir(subagentsDir));
|
|
403
|
+
cleanedFolders.push(subagentsDir);
|
|
404
|
+
const sessionDir = path2.dirname(subagentsDir);
|
|
405
|
+
const sessionDirEmpty = yield* Effect.tryPromise(async () => {
|
|
406
|
+
const files = await fs2.readdir(sessionDir);
|
|
407
|
+
return files.length === 0;
|
|
408
|
+
});
|
|
409
|
+
if (sessionDirEmpty) {
|
|
410
|
+
yield* Effect.tryPromise(() => fs2.rmdir(sessionDir));
|
|
411
|
+
cleanedFolders.push(sessionDir);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
success: true,
|
|
417
|
+
deletedAgents,
|
|
418
|
+
backedUpAgents,
|
|
419
|
+
cleanedFolders,
|
|
420
|
+
deletedCount: deletedAgents.length,
|
|
421
|
+
backedUpCount: backedUpAgents.length,
|
|
422
|
+
cleanedFolderCount: cleanedFolders.length,
|
|
423
|
+
count: deletedAgents.length + backedUpAgents.length
|
|
424
|
+
};
|
|
341
425
|
});
|
|
342
426
|
var loadAgentMessages = (projectName, _sessionId, agentId) => Effect.gen(function* () {
|
|
343
427
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
@@ -554,8 +638,8 @@ var deleteOrphanTodos = () => Effect2.gen(function* () {
|
|
|
554
638
|
return { success: true, deletedCount };
|
|
555
639
|
});
|
|
556
640
|
|
|
557
|
-
// src/session.ts
|
|
558
|
-
import { Effect as Effect3
|
|
641
|
+
// src/session/projects.ts
|
|
642
|
+
import { Effect as Effect3 } from "effect";
|
|
559
643
|
import * as fs4 from "fs/promises";
|
|
560
644
|
import * as path4 from "path";
|
|
561
645
|
var listProjects = Effect3.gen(function* () {
|
|
@@ -585,15 +669,42 @@ var listProjects = Effect3.gen(function* () {
|
|
|
585
669
|
);
|
|
586
670
|
return projects;
|
|
587
671
|
});
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
672
|
+
|
|
673
|
+
// src/session/crud.ts
|
|
674
|
+
import { Effect as Effect4, pipe, Array as A, Option as O } from "effect";
|
|
675
|
+
import * as fs5 from "fs/promises";
|
|
676
|
+
import * as path5 from "path";
|
|
677
|
+
import * as crypto from "crypto";
|
|
678
|
+
var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(function* () {
|
|
679
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
680
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
681
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
682
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
683
|
+
const summaryIdx = messages.findIndex((m) => m.type === "summary");
|
|
684
|
+
if (summaryIdx >= 0) {
|
|
685
|
+
messages[summaryIdx] = { ...messages[summaryIdx], summary: newSummary };
|
|
686
|
+
} else {
|
|
687
|
+
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
688
|
+
const summaryMsg = {
|
|
689
|
+
type: "summary",
|
|
690
|
+
summary: newSummary,
|
|
691
|
+
leafUuid: firstUserMsg?.uuid ?? null
|
|
692
|
+
};
|
|
693
|
+
messages.unshift(summaryMsg);
|
|
694
|
+
}
|
|
695
|
+
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
696
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
697
|
+
return { success: true };
|
|
698
|
+
});
|
|
699
|
+
var listSessions = (projectName) => Effect4.gen(function* () {
|
|
700
|
+
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
701
|
+
const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
591
702
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
592
|
-
const sessions = yield*
|
|
703
|
+
const sessions = yield* Effect4.all(
|
|
593
704
|
sessionFiles.map(
|
|
594
|
-
(file) =>
|
|
595
|
-
const filePath =
|
|
596
|
-
const content = yield*
|
|
705
|
+
(file) => Effect4.gen(function* () {
|
|
706
|
+
const filePath = path5.join(projectPath, file);
|
|
707
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
597
708
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
598
709
|
const messages = lines.map((line) => JSON.parse(line));
|
|
599
710
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -612,10 +723,25 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
612
723
|
}),
|
|
613
724
|
O.getOrElse(() => hasSummary ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`)
|
|
614
725
|
);
|
|
726
|
+
const currentSummary = pipe(
|
|
727
|
+
messages,
|
|
728
|
+
A.findFirst((m) => m.type === "summary"),
|
|
729
|
+
O.map((m) => m.summary),
|
|
730
|
+
O.getOrUndefined
|
|
731
|
+
);
|
|
732
|
+
const customTitle = pipe(
|
|
733
|
+
messages,
|
|
734
|
+
A.findFirst((m) => m.type === "custom-title"),
|
|
735
|
+
O.map((m) => m.customTitle),
|
|
736
|
+
O.flatMap(O.fromNullable),
|
|
737
|
+
O.getOrUndefined
|
|
738
|
+
);
|
|
615
739
|
return {
|
|
616
740
|
id: sessionId,
|
|
617
741
|
projectName,
|
|
618
742
|
title,
|
|
743
|
+
customTitle,
|
|
744
|
+
currentSummary,
|
|
619
745
|
// If session has summary but no user/assistant messages, count as 1
|
|
620
746
|
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,
|
|
621
747
|
createdAt: firstMessage?.timestamp,
|
|
@@ -631,15 +757,15 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
631
757
|
return dateB - dateA;
|
|
632
758
|
});
|
|
633
759
|
});
|
|
634
|
-
var readSession = (projectName, sessionId) =>
|
|
635
|
-
const filePath =
|
|
636
|
-
const content = yield*
|
|
760
|
+
var readSession = (projectName, sessionId) => Effect4.gen(function* () {
|
|
761
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
762
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
637
763
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
638
764
|
return lines.map((line) => JSON.parse(line));
|
|
639
765
|
});
|
|
640
|
-
var deleteMessage = (projectName, sessionId, messageUuid) =>
|
|
641
|
-
const filePath =
|
|
642
|
-
const content = yield*
|
|
766
|
+
var deleteMessage = (projectName, sessionId, messageUuid) => Effect4.gen(function* () {
|
|
767
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
768
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
643
769
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
644
770
|
const messages = lines.map((line) => JSON.parse(line));
|
|
645
771
|
const targetIndex = messages.findIndex(
|
|
@@ -658,12 +784,12 @@ var deleteMessage = (projectName, sessionId, messageUuid) => Effect3.gen(functio
|
|
|
658
784
|
}
|
|
659
785
|
messages.splice(targetIndex, 1);
|
|
660
786
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
661
|
-
yield*
|
|
787
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
662
788
|
return { success: true, deletedMessage: deletedMsg };
|
|
663
789
|
});
|
|
664
|
-
var restoreMessage = (projectName, sessionId, message, index) =>
|
|
665
|
-
const filePath =
|
|
666
|
-
const content = yield*
|
|
790
|
+
var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(function* () {
|
|
791
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
792
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
667
793
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
668
794
|
const messages = lines.map((line) => JSON.parse(line));
|
|
669
795
|
const msgUuid = message.uuid ?? message.messageId;
|
|
@@ -680,41 +806,41 @@ var restoreMessage = (projectName, sessionId, message, index) => Effect3.gen(fun
|
|
|
680
806
|
const insertIndex = Math.min(index, messages.length);
|
|
681
807
|
messages.splice(insertIndex, 0, message);
|
|
682
808
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
683
|
-
yield*
|
|
809
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
684
810
|
return { success: true };
|
|
685
811
|
});
|
|
686
|
-
var deleteSession = (projectName, sessionId) =>
|
|
812
|
+
var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
|
|
687
813
|
const sessionsDir = getSessionsDir();
|
|
688
|
-
const projectPath =
|
|
689
|
-
const filePath =
|
|
814
|
+
const projectPath = path5.join(sessionsDir, projectName);
|
|
815
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
690
816
|
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
691
|
-
const
|
|
692
|
-
if (
|
|
693
|
-
yield*
|
|
694
|
-
const agentBackupDir2 =
|
|
695
|
-
yield*
|
|
817
|
+
const stat4 = yield* Effect4.tryPromise(() => fs5.stat(filePath));
|
|
818
|
+
if (stat4.size === 0) {
|
|
819
|
+
yield* Effect4.tryPromise(() => fs5.unlink(filePath));
|
|
820
|
+
const agentBackupDir2 = path5.join(projectPath, ".bak");
|
|
821
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir2, { recursive: true }));
|
|
696
822
|
for (const agentId of linkedAgents) {
|
|
697
|
-
const agentPath =
|
|
698
|
-
const agentBackupPath =
|
|
699
|
-
yield*
|
|
823
|
+
const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
|
|
824
|
+
const agentBackupPath = path5.join(agentBackupDir2, `${agentId}.jsonl`);
|
|
825
|
+
yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
|
|
700
826
|
}));
|
|
701
827
|
}
|
|
702
828
|
yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
703
829
|
return { success: true, deletedAgents: linkedAgents.length };
|
|
704
830
|
}
|
|
705
|
-
const backupDir =
|
|
706
|
-
yield*
|
|
707
|
-
const agentBackupDir =
|
|
708
|
-
yield*
|
|
831
|
+
const backupDir = path5.join(sessionsDir, ".bak");
|
|
832
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(backupDir, { recursive: true }));
|
|
833
|
+
const agentBackupDir = path5.join(projectPath, ".bak");
|
|
834
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir, { recursive: true }));
|
|
709
835
|
for (const agentId of linkedAgents) {
|
|
710
|
-
const agentPath =
|
|
711
|
-
const agentBackupPath =
|
|
712
|
-
yield*
|
|
836
|
+
const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
|
|
837
|
+
const agentBackupPath = path5.join(agentBackupDir, `${agentId}.jsonl`);
|
|
838
|
+
yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
|
|
713
839
|
}));
|
|
714
840
|
}
|
|
715
841
|
const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
716
|
-
const backupPath =
|
|
717
|
-
yield*
|
|
842
|
+
const backupPath = path5.join(backupDir, `${projectName}_${sessionId}.jsonl`);
|
|
843
|
+
yield* Effect4.tryPromise(() => fs5.rename(filePath, backupPath));
|
|
718
844
|
return {
|
|
719
845
|
success: true,
|
|
720
846
|
backupPath,
|
|
@@ -722,10 +848,10 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
722
848
|
deletedTodos: todosResult.deletedCount
|
|
723
849
|
};
|
|
724
850
|
});
|
|
725
|
-
var renameSession = (projectName, sessionId, newTitle) =>
|
|
726
|
-
const projectPath =
|
|
727
|
-
const filePath =
|
|
728
|
-
const content = yield*
|
|
851
|
+
var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function* () {
|
|
852
|
+
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
853
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
854
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
729
855
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
730
856
|
if (lines.length === 0) {
|
|
731
857
|
return { success: false, error: "Empty session" };
|
|
@@ -749,14 +875,14 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
749
875
|
messages.unshift(customTitleRecord);
|
|
750
876
|
}
|
|
751
877
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
752
|
-
yield*
|
|
753
|
-
const projectFiles = yield*
|
|
878
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
879
|
+
const projectFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
754
880
|
const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
755
881
|
const summariesTargetingThis = [];
|
|
756
882
|
for (const file of allJsonlFiles) {
|
|
757
|
-
const otherFilePath =
|
|
883
|
+
const otherFilePath = path5.join(projectPath, file);
|
|
758
884
|
try {
|
|
759
|
-
const otherContent = yield*
|
|
885
|
+
const otherContent = yield* Effect4.tryPromise(() => fs5.readFile(otherFilePath, "utf-8"));
|
|
760
886
|
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
761
887
|
const otherMessages = otherLines.map((l) => JSON.parse(l));
|
|
762
888
|
for (let i = 0; i < otherMessages.length; i++) {
|
|
@@ -776,8 +902,8 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
776
902
|
if (summariesTargetingThis.length > 0) {
|
|
777
903
|
summariesTargetingThis.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
|
|
778
904
|
const firstSummary = summariesTargetingThis[0];
|
|
779
|
-
const summaryFilePath =
|
|
780
|
-
const summaryContent = yield*
|
|
905
|
+
const summaryFilePath = path5.join(projectPath, firstSummary.file);
|
|
906
|
+
const summaryContent = yield* Effect4.tryPromise(() => fs5.readFile(summaryFilePath, "utf-8"));
|
|
781
907
|
const summaryLines = summaryContent.trim().split("\n").filter(Boolean);
|
|
782
908
|
const summaryMessages = summaryLines.map((l) => JSON.parse(l));
|
|
783
909
|
summaryMessages[firstSummary.idx] = {
|
|
@@ -785,9 +911,9 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
785
911
|
summary: newTitle
|
|
786
912
|
};
|
|
787
913
|
const newSummaryContent = summaryMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
788
|
-
yield*
|
|
914
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
|
|
789
915
|
} else {
|
|
790
|
-
const currentContent = yield*
|
|
916
|
+
const currentContent = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
791
917
|
const currentLines = currentContent.trim().split("\n").filter(Boolean);
|
|
792
918
|
const currentMessages = currentLines.map((l) => JSON.parse(l));
|
|
793
919
|
const firstUserIdx = currentMessages.findIndex((m) => m.type === "user");
|
|
@@ -806,102 +932,50 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
806
932
|
|
|
807
933
|
${cleanedText}`;
|
|
808
934
|
const updatedContent = currentMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
809
|
-
yield*
|
|
935
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, updatedContent, "utf-8"));
|
|
810
936
|
}
|
|
811
937
|
}
|
|
812
938
|
}
|
|
813
939
|
}
|
|
814
940
|
return { success: true };
|
|
815
941
|
});
|
|
816
|
-
var
|
|
817
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
818
|
-
const fileChanges = [];
|
|
819
|
-
const seenFiles = /* @__PURE__ */ new Set();
|
|
820
|
-
for (const msg of messages) {
|
|
821
|
-
if (msg.type === "file-history-snapshot") {
|
|
822
|
-
const snapshot = msg;
|
|
823
|
-
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
824
|
-
if (backups && typeof backups === "object") {
|
|
825
|
-
for (const filePath of Object.keys(backups)) {
|
|
826
|
-
if (!seenFiles.has(filePath)) {
|
|
827
|
-
seenFiles.add(filePath);
|
|
828
|
-
fileChanges.push({
|
|
829
|
-
path: filePath,
|
|
830
|
-
action: "modified",
|
|
831
|
-
timestamp: snapshot.snapshot?.timestamp,
|
|
832
|
-
messageUuid: snapshot.messageId ?? msg.uuid
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
if (msg.type === "assistant" && msg.message?.content) {
|
|
839
|
-
const content = msg.message.content;
|
|
840
|
-
if (Array.isArray(content)) {
|
|
841
|
-
for (const item of content) {
|
|
842
|
-
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
843
|
-
const toolUse = item;
|
|
844
|
-
if ((toolUse.name === "Write" || toolUse.name === "Edit") && toolUse.input?.file_path) {
|
|
845
|
-
const filePath = toolUse.input.file_path;
|
|
846
|
-
if (!seenFiles.has(filePath)) {
|
|
847
|
-
seenFiles.add(filePath);
|
|
848
|
-
fileChanges.push({
|
|
849
|
-
path: filePath,
|
|
850
|
-
action: toolUse.name === "Write" ? "created" : "modified",
|
|
851
|
-
timestamp: msg.timestamp,
|
|
852
|
-
messageUuid: msg.uuid
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return {
|
|
862
|
-
sessionId,
|
|
863
|
-
projectName,
|
|
864
|
-
files: fileChanges,
|
|
865
|
-
totalChanges: fileChanges.length
|
|
866
|
-
};
|
|
867
|
-
});
|
|
868
|
-
var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(function* () {
|
|
942
|
+
var moveSession = (sourceProject, sessionId, targetProject) => Effect4.gen(function* () {
|
|
869
943
|
const sessionsDir = getSessionsDir();
|
|
870
|
-
const sourcePath =
|
|
871
|
-
const targetPath =
|
|
872
|
-
const sourceFile =
|
|
873
|
-
const targetFile =
|
|
874
|
-
const sourceExists = yield*
|
|
875
|
-
() =>
|
|
944
|
+
const sourcePath = path5.join(sessionsDir, sourceProject);
|
|
945
|
+
const targetPath = path5.join(sessionsDir, targetProject);
|
|
946
|
+
const sourceFile = path5.join(sourcePath, `${sessionId}.jsonl`);
|
|
947
|
+
const targetFile = path5.join(targetPath, `${sessionId}.jsonl`);
|
|
948
|
+
const sourceExists = yield* Effect4.tryPromise(
|
|
949
|
+
() => fs5.access(sourceFile).then(() => true).catch(() => false)
|
|
876
950
|
);
|
|
877
951
|
if (!sourceExists) {
|
|
878
952
|
return { success: false, error: "Source session not found" };
|
|
879
953
|
}
|
|
880
|
-
const targetExists = yield*
|
|
881
|
-
() =>
|
|
954
|
+
const targetExists = yield* Effect4.tryPromise(
|
|
955
|
+
() => fs5.access(targetFile).then(() => true).catch(() => false)
|
|
882
956
|
);
|
|
883
957
|
if (targetExists) {
|
|
884
958
|
return { success: false, error: "Session already exists in target project" };
|
|
885
959
|
}
|
|
886
|
-
yield*
|
|
960
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(targetPath, { recursive: true }));
|
|
887
961
|
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
888
|
-
yield*
|
|
962
|
+
yield* Effect4.tryPromise(() => fs5.rename(sourceFile, targetFile));
|
|
889
963
|
for (const agentId of linkedAgents) {
|
|
890
|
-
const sourceAgentFile =
|
|
891
|
-
const targetAgentFile =
|
|
892
|
-
const agentExists = yield*
|
|
893
|
-
() =>
|
|
964
|
+
const sourceAgentFile = path5.join(sourcePath, `${agentId}.jsonl`);
|
|
965
|
+
const targetAgentFile = path5.join(targetPath, `${agentId}.jsonl`);
|
|
966
|
+
const agentExists = yield* Effect4.tryPromise(
|
|
967
|
+
() => fs5.access(sourceAgentFile).then(() => true).catch(() => false)
|
|
894
968
|
);
|
|
895
969
|
if (agentExists) {
|
|
896
|
-
yield*
|
|
970
|
+
yield* Effect4.tryPromise(() => fs5.rename(sourceAgentFile, targetAgentFile));
|
|
897
971
|
}
|
|
898
972
|
}
|
|
899
973
|
return { success: true };
|
|
900
974
|
});
|
|
901
|
-
var splitSession = (projectName, sessionId, splitAtMessageUuid) =>
|
|
902
|
-
const projectPath =
|
|
903
|
-
const filePath =
|
|
904
|
-
const content = yield*
|
|
975
|
+
var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(function* () {
|
|
976
|
+
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
977
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
978
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
905
979
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
906
980
|
const allMessages = lines.map((line) => JSON.parse(line));
|
|
907
981
|
const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
|
|
@@ -949,15 +1023,15 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
949
1023
|
updatedMovedMessages.unshift(clonedSummary);
|
|
950
1024
|
}
|
|
951
1025
|
const keptContent = keptMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
952
|
-
yield*
|
|
953
|
-
const newFilePath =
|
|
1026
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, keptContent, "utf-8"));
|
|
1027
|
+
const newFilePath = path5.join(projectPath, `${newSessionId}.jsonl`);
|
|
954
1028
|
const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
955
|
-
yield*
|
|
956
|
-
const agentFiles = yield*
|
|
1029
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(newFilePath, newContent, "utf-8"));
|
|
1030
|
+
const agentFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
957
1031
|
const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
958
1032
|
for (const agentFile of agentJsonlFiles) {
|
|
959
|
-
const agentPath =
|
|
960
|
-
const agentContent = yield*
|
|
1033
|
+
const agentPath = path5.join(projectPath, agentFile);
|
|
1034
|
+
const agentContent = yield* Effect4.tryPromise(() => fs5.readFile(agentPath, "utf-8"));
|
|
961
1035
|
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
962
1036
|
if (agentLines.length === 0) continue;
|
|
963
1037
|
const firstAgentMsg = JSON.parse(agentLines[0]);
|
|
@@ -972,7 +1046,7 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
972
1046
|
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
973
1047
|
});
|
|
974
1048
|
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
975
|
-
yield*
|
|
1049
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
976
1050
|
}
|
|
977
1051
|
}
|
|
978
1052
|
}
|
|
@@ -984,96 +1058,692 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
984
1058
|
duplicatedSummary: shouldDuplicate
|
|
985
1059
|
};
|
|
986
1060
|
});
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1061
|
+
|
|
1062
|
+
// src/session/tree.ts
|
|
1063
|
+
import { Effect as Effect5 } from "effect";
|
|
1064
|
+
import * as fs6 from "fs/promises";
|
|
1065
|
+
import * as path6 from "path";
|
|
1066
|
+
var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect5.gen(function* () {
|
|
1067
|
+
const projectPath = path6.join(getSessionsDir(), projectName);
|
|
1068
|
+
const filePath = path6.join(projectPath, `${sessionId}.jsonl`);
|
|
1069
|
+
const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
|
|
990
1070
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
991
|
-
if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
|
|
992
1071
|
const messages = lines.map((line) => JSON.parse(line));
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1072
|
+
let summaries;
|
|
1073
|
+
if (summariesByTargetSession) {
|
|
1074
|
+
summaries = [...summariesByTargetSession.get(sessionId) ?? []].sort((a, b) => {
|
|
1075
|
+
const timestampCmp = (a.timestamp ?? "").localeCompare(b.timestamp ?? "");
|
|
1076
|
+
if (timestampCmp !== 0) return timestampCmp;
|
|
1077
|
+
return (b.sourceFile ?? "").localeCompare(a.sourceFile ?? "");
|
|
1078
|
+
});
|
|
1079
|
+
} else {
|
|
1080
|
+
summaries = [];
|
|
1081
|
+
const sessionUuids = /* @__PURE__ */ new Set();
|
|
1082
|
+
for (const msg of messages) {
|
|
1083
|
+
if (msg.uuid && typeof msg.uuid === "string") {
|
|
1084
|
+
sessionUuids.add(msg.uuid);
|
|
1085
|
+
}
|
|
997
1086
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1087
|
+
const projectFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
|
|
1088
|
+
const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
1089
|
+
for (const file of allJsonlFiles) {
|
|
1090
|
+
try {
|
|
1091
|
+
const otherFilePath = path6.join(projectPath, file);
|
|
1092
|
+
const otherContent = yield* Effect5.tryPromise(() => fs6.readFile(otherFilePath, "utf-8"));
|
|
1093
|
+
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
1094
|
+
for (const line of otherLines) {
|
|
1095
|
+
try {
|
|
1096
|
+
const msg = JSON.parse(line);
|
|
1097
|
+
if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
|
|
1098
|
+
const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
|
|
1099
|
+
summaries.push({
|
|
1100
|
+
summary: msg.summary,
|
|
1101
|
+
leafUuid: msg.leafUuid,
|
|
1102
|
+
timestamp: targetMsg?.timestamp ?? msg.timestamp,
|
|
1103
|
+
sourceFile: file
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
} catch {
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
} catch {
|
|
1110
|
+
}
|
|
1012
1111
|
}
|
|
1112
|
+
}
|
|
1113
|
+
summaries.sort((a, b) => {
|
|
1114
|
+
const timestampCmp = (a.timestamp ?? "").localeCompare(b.timestamp ?? "");
|
|
1115
|
+
if (timestampCmp !== 0) return timestampCmp;
|
|
1116
|
+
return (b.sourceFile ?? "").localeCompare(a.sourceFile ?? "");
|
|
1117
|
+
});
|
|
1118
|
+
let lastCompactBoundaryUuid;
|
|
1119
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1013
1120
|
const msg = messages[i];
|
|
1014
|
-
if (msg.
|
|
1015
|
-
|
|
1121
|
+
if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
1122
|
+
lastCompactBoundaryUuid = msg.uuid;
|
|
1123
|
+
break;
|
|
1016
1124
|
}
|
|
1017
|
-
filtered.push(msg);
|
|
1018
|
-
lastValidUuid = msg.uuid;
|
|
1019
1125
|
}
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1126
|
+
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
1127
|
+
const customTitleMsg = messages.find((m) => m.type === "custom-title");
|
|
1128
|
+
const customTitle = customTitleMsg?.customTitle;
|
|
1129
|
+
const title = firstUserMsg ? extractTitle(extractTextContent(firstUserMsg.message)) : summaries.length > 0 ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`;
|
|
1130
|
+
const userAssistantMessages = messages.filter(
|
|
1023
1131
|
(m) => m.type === "user" || m.type === "assistant"
|
|
1024
|
-
).length;
|
|
1025
|
-
const hasSummary = filtered.some((m) => m.type === "summary");
|
|
1026
|
-
const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
|
|
1027
|
-
return { removedCount: invalidIndices.length, remainingCount };
|
|
1028
|
-
});
|
|
1029
|
-
var previewCleanup = (projectName) => Effect3.gen(function* () {
|
|
1030
|
-
const projects = yield* listProjects;
|
|
1031
|
-
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1032
|
-
const orphanTodos = yield* findOrphanTodos();
|
|
1033
|
-
const orphanTodoCount = orphanTodos.length;
|
|
1034
|
-
const results = yield* Effect3.all(
|
|
1035
|
-
targetProjects.map(
|
|
1036
|
-
(project) => Effect3.gen(function* () {
|
|
1037
|
-
const sessions = yield* listSessions(project.name);
|
|
1038
|
-
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
1039
|
-
const invalidSessions = sessions.filter(
|
|
1040
|
-
(s) => s.title?.includes("Invalid API key") || s.title?.includes("API key")
|
|
1041
|
-
);
|
|
1042
|
-
let emptyWithTodosCount = 0;
|
|
1043
|
-
for (const session of emptySessions) {
|
|
1044
|
-
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
1045
|
-
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
1046
|
-
if (hasTodos) {
|
|
1047
|
-
emptyWithTodosCount++;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
const orphanAgents = yield* findOrphanAgents(project.name);
|
|
1051
|
-
return {
|
|
1052
|
-
project: project.name,
|
|
1053
|
-
emptySessions,
|
|
1054
|
-
invalidSessions,
|
|
1055
|
-
emptyWithTodosCount,
|
|
1056
|
-
orphanAgentCount: orphanAgents.length,
|
|
1057
|
-
orphanTodoCount: 0
|
|
1058
|
-
// Will set for first project only
|
|
1059
|
-
};
|
|
1060
|
-
})
|
|
1061
|
-
),
|
|
1062
|
-
{ concurrency: 5 }
|
|
1063
1132
|
);
|
|
1064
|
-
|
|
1065
|
-
|
|
1133
|
+
const firstMessage = userAssistantMessages[0];
|
|
1134
|
+
const lastMessage = userAssistantMessages[userAssistantMessages.length - 1];
|
|
1135
|
+
const linkedAgentIds = yield* findLinkedAgents(projectName, sessionId);
|
|
1136
|
+
const agents = [];
|
|
1137
|
+
for (const agentId of linkedAgentIds) {
|
|
1138
|
+
const agentPath = path6.join(projectPath, `${agentId}.jsonl`);
|
|
1139
|
+
try {
|
|
1140
|
+
const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
|
|
1141
|
+
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1142
|
+
const agentMsgs = agentLines.map((l) => JSON.parse(l));
|
|
1143
|
+
const agentUserAssistant = agentMsgs.filter(
|
|
1144
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
1145
|
+
);
|
|
1146
|
+
let agentName;
|
|
1147
|
+
const firstAgentMsg = agentMsgs.find((m) => m.type === "user");
|
|
1148
|
+
if (firstAgentMsg) {
|
|
1149
|
+
const text = extractTextContent(firstAgentMsg.message);
|
|
1150
|
+
if (text) {
|
|
1151
|
+
agentName = extractTitle(text);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
agents.push({
|
|
1155
|
+
id: agentId,
|
|
1156
|
+
name: agentName,
|
|
1157
|
+
messageCount: agentUserAssistant.length
|
|
1158
|
+
});
|
|
1159
|
+
} catch {
|
|
1160
|
+
agents.push({
|
|
1161
|
+
id: agentId,
|
|
1162
|
+
messageCount: 0
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1066
1165
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const {
|
|
1166
|
+
const todos = yield* findLinkedTodos(sessionId, linkedAgentIds);
|
|
1167
|
+
return {
|
|
1168
|
+
id: sessionId,
|
|
1071
1169
|
projectName,
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1170
|
+
title,
|
|
1171
|
+
customTitle,
|
|
1172
|
+
currentSummary: summaries[0]?.summary,
|
|
1173
|
+
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
|
|
1174
|
+
createdAt: firstMessage?.timestamp ?? void 0,
|
|
1175
|
+
updatedAt: lastMessage?.timestamp ?? void 0,
|
|
1176
|
+
fileMtime,
|
|
1177
|
+
summaries,
|
|
1178
|
+
agents,
|
|
1179
|
+
todos,
|
|
1180
|
+
lastCompactBoundaryUuid
|
|
1181
|
+
};
|
|
1182
|
+
});
|
|
1183
|
+
var loadSessionTreeData = (projectName, sessionId) => loadSessionTreeDataInternal(projectName, sessionId, void 0);
|
|
1184
|
+
var DEFAULT_SORT = { field: "summary", order: "desc" };
|
|
1185
|
+
var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* () {
|
|
1186
|
+
const project = (yield* listProjects).find((p) => p.name === projectName);
|
|
1187
|
+
if (!project) {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
const sort = sortOptions ?? DEFAULT_SORT;
|
|
1191
|
+
const projectPath = path6.join(getSessionsDir(), projectName);
|
|
1192
|
+
const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
|
|
1193
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1194
|
+
const fileMtimes = /* @__PURE__ */ new Map();
|
|
1195
|
+
yield* Effect5.all(
|
|
1196
|
+
sessionFiles.map(
|
|
1197
|
+
(file) => Effect5.gen(function* () {
|
|
1198
|
+
const filePath = path6.join(projectPath, file);
|
|
1199
|
+
try {
|
|
1200
|
+
const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
|
|
1201
|
+
fileMtimes.set(file.replace(".jsonl", ""), stat4.mtimeMs);
|
|
1202
|
+
} catch {
|
|
1203
|
+
}
|
|
1204
|
+
})
|
|
1205
|
+
),
|
|
1206
|
+
{ concurrency: 20 }
|
|
1207
|
+
);
|
|
1208
|
+
const globalUuidMap = /* @__PURE__ */ new Map();
|
|
1209
|
+
const allSummaries = [];
|
|
1210
|
+
const allJsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
1211
|
+
yield* Effect5.all(
|
|
1212
|
+
allJsonlFiles.map(
|
|
1213
|
+
(file) => Effect5.gen(function* () {
|
|
1214
|
+
const filePath = path6.join(projectPath, file);
|
|
1215
|
+
const fileSessionId = file.replace(".jsonl", "");
|
|
1216
|
+
try {
|
|
1217
|
+
const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
|
|
1218
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1219
|
+
for (const line of lines) {
|
|
1220
|
+
try {
|
|
1221
|
+
const msg = JSON.parse(line);
|
|
1222
|
+
if (msg.uuid && typeof msg.uuid === "string") {
|
|
1223
|
+
globalUuidMap.set(msg.uuid, {
|
|
1224
|
+
sessionId: fileSessionId,
|
|
1225
|
+
timestamp: msg.timestamp
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (msg.messageId && typeof msg.messageId === "string") {
|
|
1229
|
+
globalUuidMap.set(msg.messageId, {
|
|
1230
|
+
sessionId: fileSessionId,
|
|
1231
|
+
timestamp: msg.snapshot?.timestamp
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
if (msg.type === "summary" && typeof msg.summary === "string") {
|
|
1235
|
+
allSummaries.push({
|
|
1236
|
+
summary: msg.summary,
|
|
1237
|
+
leafUuid: msg.leafUuid,
|
|
1238
|
+
timestamp: msg.timestamp,
|
|
1239
|
+
sourceFile: file
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
} catch {
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
})
|
|
1248
|
+
),
|
|
1249
|
+
{ concurrency: 20 }
|
|
1250
|
+
);
|
|
1251
|
+
const summariesByTargetSession = /* @__PURE__ */ new Map();
|
|
1252
|
+
for (const summaryData of allSummaries) {
|
|
1253
|
+
if (summaryData.leafUuid) {
|
|
1254
|
+
const targetInfo = globalUuidMap.get(summaryData.leafUuid);
|
|
1255
|
+
if (targetInfo) {
|
|
1256
|
+
const targetSessionId = targetInfo.sessionId;
|
|
1257
|
+
if (!summariesByTargetSession.has(targetSessionId)) {
|
|
1258
|
+
summariesByTargetSession.set(targetSessionId, []);
|
|
1259
|
+
}
|
|
1260
|
+
summariesByTargetSession.get(targetSessionId).push({
|
|
1261
|
+
summary: summaryData.summary,
|
|
1262
|
+
leafUuid: summaryData.leafUuid,
|
|
1263
|
+
// Use summary's own timestamp for sorting, not the target message's timestamp
|
|
1264
|
+
timestamp: summaryData.timestamp ?? targetInfo.timestamp,
|
|
1265
|
+
sourceFile: summaryData.sourceFile
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
const sessions = yield* Effect5.all(
|
|
1271
|
+
sessionFiles.map((file) => {
|
|
1272
|
+
const sessionId = file.replace(".jsonl", "");
|
|
1273
|
+
const mtime = fileMtimes.get(sessionId);
|
|
1274
|
+
return loadSessionTreeDataInternal(projectName, sessionId, summariesByTargetSession, mtime);
|
|
1275
|
+
}),
|
|
1276
|
+
{ concurrency: 10 }
|
|
1277
|
+
);
|
|
1278
|
+
const sortedSessions = sessions.sort((a, b) => {
|
|
1279
|
+
let comparison = 0;
|
|
1280
|
+
switch (sort.field) {
|
|
1281
|
+
case "summary": {
|
|
1282
|
+
const timeA = getSessionSortTimestamp(a);
|
|
1283
|
+
const timeB = getSessionSortTimestamp(b);
|
|
1284
|
+
const dateA = timeA ? new Date(timeA).getTime() : a.fileMtime ?? 0;
|
|
1285
|
+
const dateB = timeB ? new Date(timeB).getTime() : b.fileMtime ?? 0;
|
|
1286
|
+
comparison = dateA - dateB;
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
case "modified": {
|
|
1290
|
+
comparison = (a.fileMtime ?? 0) - (b.fileMtime ?? 0);
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
1293
|
+
case "created": {
|
|
1294
|
+
const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
|
1295
|
+
const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
1296
|
+
comparison = createdA - createdB;
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
case "updated": {
|
|
1300
|
+
const updatedA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
1301
|
+
const updatedB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
1302
|
+
comparison = updatedA - updatedB;
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
case "messageCount": {
|
|
1306
|
+
comparison = a.messageCount - b.messageCount;
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
case "title": {
|
|
1310
|
+
const titleA = a.customTitle ?? a.currentSummary ?? a.title;
|
|
1311
|
+
const titleB = b.customTitle ?? b.currentSummary ?? b.title;
|
|
1312
|
+
comparison = titleA.localeCompare(titleB);
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return sort.order === "desc" ? -comparison : comparison;
|
|
1317
|
+
});
|
|
1318
|
+
const filteredSessions = sortedSessions.filter((s) => {
|
|
1319
|
+
if (isErrorSessionTitle(s.title)) return false;
|
|
1320
|
+
if (isErrorSessionTitle(s.customTitle)) return false;
|
|
1321
|
+
if (isErrorSessionTitle(s.currentSummary)) return false;
|
|
1322
|
+
return true;
|
|
1323
|
+
});
|
|
1324
|
+
return {
|
|
1325
|
+
name: project.name,
|
|
1326
|
+
displayName: project.displayName,
|
|
1327
|
+
path: project.path,
|
|
1328
|
+
sessionCount: filteredSessions.length,
|
|
1329
|
+
sessions: filteredSessions
|
|
1330
|
+
};
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// src/session/analysis.ts
|
|
1334
|
+
import { Effect as Effect6 } from "effect";
|
|
1335
|
+
import * as fs7 from "fs/promises";
|
|
1336
|
+
import * as path7 from "path";
|
|
1337
|
+
var analyzeSession = (projectName, sessionId) => Effect6.gen(function* () {
|
|
1338
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1339
|
+
let userMessages = 0;
|
|
1340
|
+
let assistantMessages = 0;
|
|
1341
|
+
let summaryCount = 0;
|
|
1342
|
+
let snapshotCount = 0;
|
|
1343
|
+
const toolUsageMap = /* @__PURE__ */ new Map();
|
|
1344
|
+
const filesChanged = /* @__PURE__ */ new Set();
|
|
1345
|
+
const patterns = [];
|
|
1346
|
+
const milestones = [];
|
|
1347
|
+
let firstTimestamp;
|
|
1348
|
+
let lastTimestamp;
|
|
1349
|
+
for (const msg of messages) {
|
|
1350
|
+
if (msg.timestamp) {
|
|
1351
|
+
if (!firstTimestamp) firstTimestamp = msg.timestamp;
|
|
1352
|
+
lastTimestamp = msg.timestamp;
|
|
1353
|
+
}
|
|
1354
|
+
if (msg.type === "user") {
|
|
1355
|
+
userMessages++;
|
|
1356
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
1357
|
+
if (content.toLowerCase().includes("commit") || content.toLowerCase().includes("\uC644\uB8CC")) {
|
|
1358
|
+
milestones.push({
|
|
1359
|
+
timestamp: msg.timestamp,
|
|
1360
|
+
description: `User checkpoint: ${content.slice(0, 50)}...`,
|
|
1361
|
+
messageUuid: msg.uuid
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
} else if (msg.type === "assistant") {
|
|
1365
|
+
assistantMessages++;
|
|
1366
|
+
if (msg.message?.content && Array.isArray(msg.message.content)) {
|
|
1367
|
+
for (const item of msg.message.content) {
|
|
1368
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
1369
|
+
const toolUse = item;
|
|
1370
|
+
const toolName = toolUse.name ?? "unknown";
|
|
1371
|
+
const existing = toolUsageMap.get(toolName) ?? { count: 0, errorCount: 0 };
|
|
1372
|
+
existing.count++;
|
|
1373
|
+
toolUsageMap.set(toolName, existing);
|
|
1374
|
+
if ((toolName === "Write" || toolName === "Edit") && toolUse.input?.file_path) {
|
|
1375
|
+
filesChanged.add(toolUse.input.file_path);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
} else if (msg.type === "summary") {
|
|
1381
|
+
summaryCount++;
|
|
1382
|
+
if (msg.summary) {
|
|
1383
|
+
milestones.push({
|
|
1384
|
+
timestamp: msg.timestamp,
|
|
1385
|
+
description: `Summary: ${msg.summary.slice(0, 100)}...`,
|
|
1386
|
+
messageUuid: msg.uuid
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
} else if (msg.type === "file-history-snapshot") {
|
|
1390
|
+
snapshotCount++;
|
|
1391
|
+
const snapshot = msg;
|
|
1392
|
+
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1393
|
+
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1394
|
+
filesChanged.add(filePath);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
for (const msg of messages) {
|
|
1400
|
+
if (msg.type === "user" && msg.content && Array.isArray(msg.content)) {
|
|
1401
|
+
for (const item of msg.content) {
|
|
1402
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_result" && "is_error" in item && item.is_error) {
|
|
1403
|
+
const toolResultItem = item;
|
|
1404
|
+
const toolUseId = toolResultItem.tool_use_id;
|
|
1405
|
+
if (toolUseId) {
|
|
1406
|
+
for (const prevMsg of messages) {
|
|
1407
|
+
if (prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
|
1408
|
+
for (const prevItem of prevMsg.message.content) {
|
|
1409
|
+
if (prevItem && typeof prevItem === "object" && "type" in prevItem && prevItem.type === "tool_use" && "id" in prevItem && prevItem.id === toolUseId) {
|
|
1410
|
+
const toolName = prevItem.name ?? "unknown";
|
|
1411
|
+
const existing = toolUsageMap.get(toolName);
|
|
1412
|
+
if (existing) {
|
|
1413
|
+
existing.errorCount++;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
let durationMinutes = 0;
|
|
1425
|
+
if (firstTimestamp && lastTimestamp) {
|
|
1426
|
+
const first = new Date(firstTimestamp).getTime();
|
|
1427
|
+
const last = new Date(lastTimestamp).getTime();
|
|
1428
|
+
durationMinutes = Math.round((last - first) / 1e3 / 60);
|
|
1429
|
+
}
|
|
1430
|
+
const toolUsageArray = Array.from(toolUsageMap.entries()).map(([name, stats]) => ({
|
|
1431
|
+
name,
|
|
1432
|
+
count: stats.count,
|
|
1433
|
+
errorCount: stats.errorCount
|
|
1434
|
+
}));
|
|
1435
|
+
for (const tool of toolUsageArray) {
|
|
1436
|
+
if (tool.count >= 3 && tool.errorCount / tool.count > 0.3) {
|
|
1437
|
+
patterns.push({
|
|
1438
|
+
type: "high_error_rate",
|
|
1439
|
+
description: `${tool.name} had ${tool.errorCount}/${tool.count} errors (${Math.round(tool.errorCount / tool.count * 100)}%)`,
|
|
1440
|
+
count: tool.errorCount
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (snapshotCount > 10) {
|
|
1445
|
+
patterns.push({
|
|
1446
|
+
type: "many_snapshots",
|
|
1447
|
+
description: `${snapshotCount} file-history-snapshots could be compressed`,
|
|
1448
|
+
count: snapshotCount
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
sessionId,
|
|
1453
|
+
projectName,
|
|
1454
|
+
durationMinutes,
|
|
1455
|
+
stats: {
|
|
1456
|
+
totalMessages: messages.length,
|
|
1457
|
+
userMessages,
|
|
1458
|
+
assistantMessages,
|
|
1459
|
+
summaryCount,
|
|
1460
|
+
snapshotCount
|
|
1461
|
+
},
|
|
1462
|
+
toolUsage: toolUsageArray.sort((a, b) => b.count - a.count),
|
|
1463
|
+
filesChanged: Array.from(filesChanged),
|
|
1464
|
+
patterns,
|
|
1465
|
+
milestones
|
|
1466
|
+
};
|
|
1467
|
+
});
|
|
1468
|
+
var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
|
|
1469
|
+
const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
|
|
1470
|
+
const filePath = path7.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1471
|
+
const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
|
|
1472
|
+
const originalSize = Buffer.byteLength(content, "utf-8");
|
|
1473
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1474
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
1475
|
+
let removedSnapshots = 0;
|
|
1476
|
+
let truncatedOutputs = 0;
|
|
1477
|
+
const snapshotIndices = [];
|
|
1478
|
+
messages.forEach((msg, idx) => {
|
|
1479
|
+
if (msg.type === "file-history-snapshot") {
|
|
1480
|
+
snapshotIndices.push(idx);
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
const filteredMessages = messages.filter((msg, idx) => {
|
|
1484
|
+
if (msg.type === "file-history-snapshot") {
|
|
1485
|
+
if (keepSnapshots === "none") {
|
|
1486
|
+
removedSnapshots++;
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1489
|
+
if (keepSnapshots === "first_last") {
|
|
1490
|
+
const isFirst = idx === snapshotIndices[0];
|
|
1491
|
+
const isLast = idx === snapshotIndices[snapshotIndices.length - 1];
|
|
1492
|
+
if (!isFirst && !isLast) {
|
|
1493
|
+
removedSnapshots++;
|
|
1494
|
+
return false;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return true;
|
|
1499
|
+
});
|
|
1500
|
+
for (const msg of filteredMessages) {
|
|
1501
|
+
if (msg.type === "user" && Array.isArray(msg.content)) {
|
|
1502
|
+
for (const item of msg.content) {
|
|
1503
|
+
if (item.type === "tool_result" && typeof item.content === "string") {
|
|
1504
|
+
if (maxToolOutputLength > 0 && item.content.length > maxToolOutputLength) {
|
|
1505
|
+
item.content = item.content.slice(0, maxToolOutputLength) + "\n... [truncated]";
|
|
1506
|
+
truncatedOutputs++;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1513
|
+
const compressedSize = Buffer.byteLength(newContent, "utf-8");
|
|
1514
|
+
yield* Effect6.tryPromise(() => fs7.writeFile(filePath, newContent, "utf-8"));
|
|
1515
|
+
return {
|
|
1516
|
+
success: true,
|
|
1517
|
+
originalSize,
|
|
1518
|
+
compressedSize,
|
|
1519
|
+
removedSnapshots,
|
|
1520
|
+
truncatedOutputs
|
|
1521
|
+
};
|
|
1522
|
+
});
|
|
1523
|
+
var extractProjectKnowledge = (projectName, sessionIds) => Effect6.gen(function* () {
|
|
1524
|
+
const sessionsDir = getSessionsDir();
|
|
1525
|
+
const projectDir = path7.join(sessionsDir, projectName);
|
|
1526
|
+
let targetSessionIds = sessionIds;
|
|
1527
|
+
if (!targetSessionIds) {
|
|
1528
|
+
const files = yield* Effect6.tryPromise(() => fs7.readdir(projectDir));
|
|
1529
|
+
targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
|
|
1530
|
+
}
|
|
1531
|
+
const fileModifyCount = /* @__PURE__ */ new Map();
|
|
1532
|
+
const toolSequences = [];
|
|
1533
|
+
const decisions = [];
|
|
1534
|
+
for (const sessionId of targetSessionIds) {
|
|
1535
|
+
try {
|
|
1536
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1537
|
+
for (const msg of messages) {
|
|
1538
|
+
if (msg.type === "file-history-snapshot") {
|
|
1539
|
+
const snapshot = msg;
|
|
1540
|
+
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1541
|
+
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1542
|
+
const existing = fileModifyCount.get(filePath) ?? { count: 0 };
|
|
1543
|
+
existing.count++;
|
|
1544
|
+
existing.lastModified = snapshot.snapshot.timestamp;
|
|
1545
|
+
fileModifyCount.set(filePath, existing);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (msg.type === "assistant" && msg.message?.content && Array.isArray(msg.message.content)) {
|
|
1550
|
+
const tools = [];
|
|
1551
|
+
for (const item of msg.message.content) {
|
|
1552
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
1553
|
+
const toolUse = item;
|
|
1554
|
+
if (toolUse.name) tools.push(toolUse.name);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (tools.length > 1) {
|
|
1558
|
+
toolSequences.push(tools);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (msg.type === "summary" && msg.summary) {
|
|
1562
|
+
decisions.push({
|
|
1563
|
+
context: "Session summary",
|
|
1564
|
+
decision: msg.summary.slice(0, 200),
|
|
1565
|
+
sessionId
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
} catch {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
const hotFiles = Array.from(fileModifyCount.entries()).map(([filePath, data]) => ({
|
|
1574
|
+
path: filePath,
|
|
1575
|
+
modifyCount: data.count,
|
|
1576
|
+
lastModified: data.lastModified
|
|
1577
|
+
})).sort((a, b) => b.modifyCount - a.modifyCount).slice(0, 20);
|
|
1578
|
+
const workflowMap = /* @__PURE__ */ new Map();
|
|
1579
|
+
for (const seq of toolSequences) {
|
|
1580
|
+
const key = seq.join(" -> ");
|
|
1581
|
+
workflowMap.set(key, (workflowMap.get(key) ?? 0) + 1);
|
|
1582
|
+
}
|
|
1583
|
+
const workflows = Array.from(workflowMap.entries()).filter(([, count]) => count >= 2).map(([sequence, count]) => ({
|
|
1584
|
+
sequence: sequence.split(" -> "),
|
|
1585
|
+
count
|
|
1586
|
+
})).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
1587
|
+
return {
|
|
1588
|
+
projectName,
|
|
1589
|
+
patterns: [],
|
|
1590
|
+
hotFiles,
|
|
1591
|
+
workflows,
|
|
1592
|
+
decisions: decisions.slice(0, 20)
|
|
1593
|
+
};
|
|
1594
|
+
});
|
|
1595
|
+
function truncateText(text, maxLen) {
|
|
1596
|
+
const cleaned = text.replace(/\n/g, " ");
|
|
1597
|
+
if (cleaned.length > maxLen) {
|
|
1598
|
+
return cleaned.slice(0, maxLen) + "...";
|
|
1599
|
+
}
|
|
1600
|
+
return cleaned;
|
|
1601
|
+
}
|
|
1602
|
+
var summarizeSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
|
|
1603
|
+
const { limit = 50, maxLength = 100 } = options;
|
|
1604
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1605
|
+
const lines = [];
|
|
1606
|
+
let count = 0;
|
|
1607
|
+
for (const msg of messages) {
|
|
1608
|
+
if (count >= limit) break;
|
|
1609
|
+
if (msg.type === "user" || msg.type === "human") {
|
|
1610
|
+
let timeStr;
|
|
1611
|
+
if (msg.timestamp) {
|
|
1612
|
+
try {
|
|
1613
|
+
const dt = new Date(msg.timestamp);
|
|
1614
|
+
timeStr = dt.toLocaleString("ko-KR", {
|
|
1615
|
+
month: "2-digit",
|
|
1616
|
+
day: "2-digit",
|
|
1617
|
+
hour: "2-digit",
|
|
1618
|
+
minute: "2-digit",
|
|
1619
|
+
hour12: false
|
|
1620
|
+
});
|
|
1621
|
+
} catch {
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
const text = extractTextContent(msg.message);
|
|
1625
|
+
if (text) {
|
|
1626
|
+
const truncated = truncateText(text, maxLength);
|
|
1627
|
+
lines.push({ role: "user", content: truncated, timestamp: timeStr });
|
|
1628
|
+
count++;
|
|
1629
|
+
}
|
|
1630
|
+
} else if (msg.type === "assistant") {
|
|
1631
|
+
const text = extractTextContent(msg.message);
|
|
1632
|
+
if (text) {
|
|
1633
|
+
const truncated = truncateText(text, maxLength);
|
|
1634
|
+
lines.push({ role: "assistant", content: truncated });
|
|
1635
|
+
count++;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const formatted = lines.map((line) => {
|
|
1640
|
+
if (line.role === "user") {
|
|
1641
|
+
return line.timestamp ? `user [${line.timestamp}]: ${line.content}` : `user: ${line.content}`;
|
|
1642
|
+
}
|
|
1643
|
+
return `assistant: ${line.content}`;
|
|
1644
|
+
}).join("\n");
|
|
1645
|
+
return {
|
|
1646
|
+
sessionId,
|
|
1647
|
+
projectName,
|
|
1648
|
+
lines,
|
|
1649
|
+
formatted
|
|
1650
|
+
};
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
// src/session/cleanup.ts
|
|
1654
|
+
import { Effect as Effect7 } from "effect";
|
|
1655
|
+
import * as fs8 from "fs/promises";
|
|
1656
|
+
import * as path8 from "path";
|
|
1657
|
+
var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* () {
|
|
1658
|
+
const filePath = path8.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1659
|
+
const content = yield* Effect7.tryPromise(() => fs8.readFile(filePath, "utf-8"));
|
|
1660
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1661
|
+
if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
|
|
1662
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
1663
|
+
const invalidIndices = [];
|
|
1664
|
+
messages.forEach((msg, idx) => {
|
|
1665
|
+
if (isInvalidApiKeyMessage(msg)) {
|
|
1666
|
+
invalidIndices.push(idx);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
if (invalidIndices.length === 0) {
|
|
1670
|
+
const userAssistantCount = messages.filter(
|
|
1671
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
1672
|
+
).length;
|
|
1673
|
+
const hasSummary2 = messages.some((m) => m.type === "summary");
|
|
1674
|
+
const remainingCount2 = userAssistantCount > 0 ? userAssistantCount : hasSummary2 ? 1 : 0;
|
|
1675
|
+
return { removedCount: 0, remainingCount: remainingCount2 };
|
|
1676
|
+
}
|
|
1677
|
+
const filtered = [];
|
|
1678
|
+
let lastValidUuid = null;
|
|
1679
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1680
|
+
if (invalidIndices.includes(i)) {
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
const msg = messages[i];
|
|
1684
|
+
if (msg.parentUuid && invalidIndices.some((idx) => messages[idx]?.uuid === msg.parentUuid)) {
|
|
1685
|
+
msg.parentUuid = lastValidUuid;
|
|
1686
|
+
}
|
|
1687
|
+
filtered.push(msg);
|
|
1688
|
+
lastValidUuid = msg.uuid;
|
|
1689
|
+
}
|
|
1690
|
+
const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
|
|
1691
|
+
yield* Effect7.tryPromise(() => fs8.writeFile(filePath, newContent, "utf-8"));
|
|
1692
|
+
const remainingUserAssistant = filtered.filter(
|
|
1693
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
1694
|
+
).length;
|
|
1695
|
+
const hasSummary = filtered.some((m) => m.type === "summary");
|
|
1696
|
+
const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
|
|
1697
|
+
return { removedCount: invalidIndices.length, remainingCount };
|
|
1698
|
+
});
|
|
1699
|
+
var previewCleanup = (projectName) => Effect7.gen(function* () {
|
|
1700
|
+
const projects = yield* listProjects;
|
|
1701
|
+
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1702
|
+
const orphanTodos = yield* findOrphanTodos();
|
|
1703
|
+
const orphanTodoCount = orphanTodos.length;
|
|
1704
|
+
const results = yield* Effect7.all(
|
|
1705
|
+
targetProjects.map(
|
|
1706
|
+
(project) => Effect7.gen(function* () {
|
|
1707
|
+
const sessions = yield* listSessions(project.name);
|
|
1708
|
+
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
1709
|
+
const invalidSessions = sessions.filter(
|
|
1710
|
+
(s) => s.title?.includes("Invalid API key") || s.title?.includes("API key")
|
|
1711
|
+
);
|
|
1712
|
+
let emptyWithTodosCount = 0;
|
|
1713
|
+
for (const session of emptySessions) {
|
|
1714
|
+
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
1715
|
+
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
1716
|
+
if (hasTodos) {
|
|
1717
|
+
emptyWithTodosCount++;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const orphanAgents = yield* findOrphanAgents(project.name);
|
|
1721
|
+
return {
|
|
1722
|
+
project: project.name,
|
|
1723
|
+
emptySessions,
|
|
1724
|
+
invalidSessions,
|
|
1725
|
+
emptyWithTodosCount,
|
|
1726
|
+
orphanAgentCount: orphanAgents.length,
|
|
1727
|
+
orphanTodoCount: 0
|
|
1728
|
+
// Will set for first project only
|
|
1729
|
+
};
|
|
1730
|
+
})
|
|
1731
|
+
),
|
|
1732
|
+
{ concurrency: 5 }
|
|
1733
|
+
);
|
|
1734
|
+
if (results.length > 0) {
|
|
1735
|
+
results[0] = { ...results[0], orphanTodoCount };
|
|
1736
|
+
}
|
|
1737
|
+
return results;
|
|
1738
|
+
});
|
|
1739
|
+
var clearSessions = (options) => Effect7.gen(function* () {
|
|
1740
|
+
const {
|
|
1741
|
+
projectName,
|
|
1742
|
+
clearEmpty = true,
|
|
1743
|
+
clearInvalid = true,
|
|
1744
|
+
skipWithTodos = true,
|
|
1745
|
+
clearOrphanAgents = true,
|
|
1746
|
+
clearOrphanTodos = false
|
|
1077
1747
|
} = options;
|
|
1078
1748
|
const projects = yield* listProjects;
|
|
1079
1749
|
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
@@ -1084,8 +1754,8 @@ var clearSessions = (options) => Effect3.gen(function* () {
|
|
|
1084
1754
|
const sessionsToDelete = [];
|
|
1085
1755
|
if (clearInvalid) {
|
|
1086
1756
|
for (const project of targetProjects) {
|
|
1087
|
-
const projectPath =
|
|
1088
|
-
const files = yield*
|
|
1757
|
+
const projectPath = path8.join(getSessionsDir(), project.name);
|
|
1758
|
+
const files = yield* Effect7.tryPromise(() => fs8.readdir(projectPath));
|
|
1089
1759
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1090
1760
|
for (const file of sessionFiles) {
|
|
1091
1761
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -1139,7 +1809,12 @@ var clearSessions = (options) => Effect3.gen(function* () {
|
|
|
1139
1809
|
deletedOrphanTodoCount
|
|
1140
1810
|
};
|
|
1141
1811
|
});
|
|
1142
|
-
|
|
1812
|
+
|
|
1813
|
+
// src/session/search.ts
|
|
1814
|
+
import { Effect as Effect8 } from "effect";
|
|
1815
|
+
import * as fs9 from "fs/promises";
|
|
1816
|
+
import * as path9 from "path";
|
|
1817
|
+
var searchSessions = (query, options = {}) => Effect8.gen(function* () {
|
|
1143
1818
|
const { projectName, searchContent = false } = options;
|
|
1144
1819
|
const results = [];
|
|
1145
1820
|
const queryLower = query.toLowerCase();
|
|
@@ -1162,16 +1837,16 @@ var searchSessions = (query, options = {}) => Effect3.gen(function* () {
|
|
|
1162
1837
|
}
|
|
1163
1838
|
if (searchContent) {
|
|
1164
1839
|
for (const project of targetProjects) {
|
|
1165
|
-
const projectPath =
|
|
1166
|
-
const files = yield*
|
|
1840
|
+
const projectPath = path9.join(getSessionsDir(), project.name);
|
|
1841
|
+
const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
|
|
1167
1842
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1168
1843
|
for (const file of sessionFiles) {
|
|
1169
1844
|
const sessionId = file.replace(".jsonl", "");
|
|
1170
1845
|
if (results.some((r) => r.sessionId === sessionId && r.projectName === project.name)) {
|
|
1171
1846
|
continue;
|
|
1172
1847
|
}
|
|
1173
|
-
const filePath =
|
|
1174
|
-
const content = yield*
|
|
1848
|
+
const filePath = path9.join(projectPath, file);
|
|
1849
|
+
const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
|
|
1175
1850
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1176
1851
|
for (const line of lines) {
|
|
1177
1852
|
try {
|
|
@@ -1207,232 +1882,109 @@ var searchSessions = (query, options = {}) => Effect3.gen(function* () {
|
|
|
1207
1882
|
return dateB - dateA;
|
|
1208
1883
|
});
|
|
1209
1884
|
});
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
for (const file of allJsonlFiles) {
|
|
1232
|
-
try {
|
|
1233
|
-
const otherFilePath = path4.join(projectPath, file);
|
|
1234
|
-
const otherContent = yield* Effect3.tryPromise(() => fs4.readFile(otherFilePath, "utf-8"));
|
|
1235
|
-
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
1236
|
-
for (const line of otherLines) {
|
|
1237
|
-
try {
|
|
1238
|
-
const msg = JSON.parse(line);
|
|
1239
|
-
if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
|
|
1240
|
-
const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
|
|
1241
|
-
summaries.push({
|
|
1242
|
-
summary: msg.summary,
|
|
1243
|
-
leafUuid: msg.leafUuid,
|
|
1244
|
-
timestamp: targetMsg?.timestamp ?? msg.timestamp
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
} catch {
|
|
1885
|
+
|
|
1886
|
+
// src/session/files.ts
|
|
1887
|
+
import { Effect as Effect9 } from "effect";
|
|
1888
|
+
var getSessionFiles = (projectName, sessionId) => Effect9.gen(function* () {
|
|
1889
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1890
|
+
const fileChanges = [];
|
|
1891
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
1892
|
+
for (const msg of messages) {
|
|
1893
|
+
if (msg.type === "file-history-snapshot") {
|
|
1894
|
+
const snapshot = msg;
|
|
1895
|
+
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
1896
|
+
if (backups && typeof backups === "object") {
|
|
1897
|
+
for (const filePath of Object.keys(backups)) {
|
|
1898
|
+
if (!seenFiles.has(filePath)) {
|
|
1899
|
+
seenFiles.add(filePath);
|
|
1900
|
+
fileChanges.push({
|
|
1901
|
+
path: filePath,
|
|
1902
|
+
action: "modified",
|
|
1903
|
+
timestamp: snapshot.snapshot?.timestamp,
|
|
1904
|
+
messageUuid: snapshot.messageId ?? msg.uuid
|
|
1905
|
+
});
|
|
1248
1906
|
}
|
|
1249
1907
|
}
|
|
1250
|
-
} catch {
|
|
1251
1908
|
}
|
|
1252
1909
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const linkedAgentIds = yield* findLinkedAgents(projectName, sessionId);
|
|
1273
|
-
const agents = [];
|
|
1274
|
-
for (const agentId of linkedAgentIds) {
|
|
1275
|
-
const agentPath = path4.join(projectPath, `${agentId}.jsonl`);
|
|
1276
|
-
try {
|
|
1277
|
-
const agentContent = yield* Effect3.tryPromise(() => fs4.readFile(agentPath, "utf-8"));
|
|
1278
|
-
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1279
|
-
const agentMsgs = agentLines.map((l) => JSON.parse(l));
|
|
1280
|
-
const agentUserAssistant = agentMsgs.filter(
|
|
1281
|
-
(m) => m.type === "user" || m.type === "assistant"
|
|
1282
|
-
);
|
|
1283
|
-
let agentName;
|
|
1284
|
-
const firstAgentMsg = agentMsgs.find((m) => m.type === "user");
|
|
1285
|
-
if (firstAgentMsg) {
|
|
1286
|
-
const text = extractTextContent(firstAgentMsg.message);
|
|
1287
|
-
if (text) {
|
|
1288
|
-
agentName = extractTitle(text);
|
|
1910
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
1911
|
+
const content = msg.message.content;
|
|
1912
|
+
if (Array.isArray(content)) {
|
|
1913
|
+
for (const item of content) {
|
|
1914
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
1915
|
+
const toolUse = item;
|
|
1916
|
+
if ((toolUse.name === "Write" || toolUse.name === "Edit") && toolUse.input?.file_path) {
|
|
1917
|
+
const filePath = toolUse.input.file_path;
|
|
1918
|
+
if (!seenFiles.has(filePath)) {
|
|
1919
|
+
seenFiles.add(filePath);
|
|
1920
|
+
fileChanges.push({
|
|
1921
|
+
path: filePath,
|
|
1922
|
+
action: toolUse.name === "Write" ? "created" : "modified",
|
|
1923
|
+
timestamp: msg.timestamp,
|
|
1924
|
+
messageUuid: msg.uuid
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1289
1929
|
}
|
|
1290
1930
|
}
|
|
1291
|
-
agents.push({
|
|
1292
|
-
id: agentId,
|
|
1293
|
-
name: agentName,
|
|
1294
|
-
messageCount: agentUserAssistant.length
|
|
1295
|
-
});
|
|
1296
|
-
} catch {
|
|
1297
|
-
agents.push({
|
|
1298
|
-
id: agentId,
|
|
1299
|
-
messageCount: 0
|
|
1300
|
-
});
|
|
1301
1931
|
}
|
|
1302
1932
|
}
|
|
1303
|
-
const todos = yield* findLinkedTodos(sessionId, linkedAgentIds);
|
|
1304
1933
|
return {
|
|
1305
|
-
|
|
1934
|
+
sessionId,
|
|
1306
1935
|
projectName,
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
currentSummary: summaries[0]?.summary,
|
|
1310
|
-
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
|
|
1311
|
-
createdAt: firstMessage?.timestamp ?? void 0,
|
|
1312
|
-
updatedAt: lastMessage?.timestamp ?? void 0,
|
|
1313
|
-
summaries,
|
|
1314
|
-
agents,
|
|
1315
|
-
todos,
|
|
1316
|
-
lastCompactBoundaryUuid
|
|
1936
|
+
files: fileChanges,
|
|
1937
|
+
totalChanges: fileChanges.length
|
|
1317
1938
|
};
|
|
1318
1939
|
});
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1940
|
+
|
|
1941
|
+
// src/session/index-file.ts
|
|
1942
|
+
import { Effect as Effect10 } from "effect";
|
|
1943
|
+
import * as fs10 from "fs/promises";
|
|
1944
|
+
import * as path10 from "path";
|
|
1945
|
+
var loadSessionsIndex = (projectName) => Effect10.gen(function* () {
|
|
1946
|
+
const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
|
|
1947
|
+
try {
|
|
1948
|
+
const content = yield* Effect10.tryPromise(() => fs10.readFile(indexPath, "utf-8"));
|
|
1949
|
+
const index = JSON.parse(content);
|
|
1950
|
+
return index;
|
|
1951
|
+
} catch {
|
|
1323
1952
|
return null;
|
|
1324
1953
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
sessionId: fileSessionId,
|
|
1345
|
-
timestamp: msg.timestamp
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
if (msg.messageId && typeof msg.messageId === "string") {
|
|
1349
|
-
globalUuidMap.set(msg.messageId, {
|
|
1350
|
-
sessionId: fileSessionId,
|
|
1351
|
-
timestamp: msg.snapshot?.timestamp
|
|
1352
|
-
});
|
|
1353
|
-
}
|
|
1354
|
-
if (msg.type === "summary" && typeof msg.summary === "string") {
|
|
1355
|
-
allSummaries.push({
|
|
1356
|
-
summary: msg.summary,
|
|
1357
|
-
leafUuid: msg.leafUuid,
|
|
1358
|
-
timestamp: msg.timestamp
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
|
-
} catch {
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
} catch {
|
|
1365
|
-
}
|
|
1366
|
-
})
|
|
1367
|
-
),
|
|
1368
|
-
{ concurrency: 20 }
|
|
1369
|
-
);
|
|
1370
|
-
const summariesByTargetSession = /* @__PURE__ */ new Map();
|
|
1371
|
-
for (const summaryData of allSummaries) {
|
|
1372
|
-
if (summaryData.leafUuid) {
|
|
1373
|
-
const targetInfo = globalUuidMap.get(summaryData.leafUuid);
|
|
1374
|
-
if (targetInfo) {
|
|
1375
|
-
const targetSessionId = targetInfo.sessionId;
|
|
1376
|
-
if (!summariesByTargetSession.has(targetSessionId)) {
|
|
1377
|
-
summariesByTargetSession.set(targetSessionId, []);
|
|
1378
|
-
}
|
|
1379
|
-
summariesByTargetSession.get(targetSessionId).push({
|
|
1380
|
-
summary: summaryData.summary,
|
|
1381
|
-
leafUuid: summaryData.leafUuid,
|
|
1382
|
-
timestamp: targetInfo.timestamp ?? summaryData.timestamp
|
|
1383
|
-
});
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
const sessions = yield* Effect3.all(
|
|
1388
|
-
sessionFiles.map((file) => {
|
|
1389
|
-
const sessionId = file.replace(".jsonl", "");
|
|
1390
|
-
return loadSessionTreeDataInternal(projectName, sessionId, summariesByTargetSession);
|
|
1391
|
-
}),
|
|
1392
|
-
{ concurrency: 10 }
|
|
1393
|
-
);
|
|
1394
|
-
const sortedSessions = sessions.sort((a, b) => {
|
|
1395
|
-
const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
1396
|
-
const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
1397
|
-
return dateB - dateA;
|
|
1954
|
+
});
|
|
1955
|
+
var getIndexEntryDisplayTitle = (entry) => {
|
|
1956
|
+
if (entry.customTitle) return entry.customTitle;
|
|
1957
|
+
if (entry.summary) return entry.summary;
|
|
1958
|
+
let prompt = entry.firstPrompt;
|
|
1959
|
+
if (prompt === "No prompt") return "Untitled";
|
|
1960
|
+
if (prompt.startsWith("[Request interrupted")) return "Untitled";
|
|
1961
|
+
prompt = prompt.replace(/<ide_[^>]*>[^<]*<\/ide_[^>]*>/g, "").trim();
|
|
1962
|
+
if (!prompt) return "Untitled";
|
|
1963
|
+
if (prompt.length > 60) {
|
|
1964
|
+
return prompt.slice(0, 57) + "...";
|
|
1965
|
+
}
|
|
1966
|
+
return prompt;
|
|
1967
|
+
};
|
|
1968
|
+
var sortIndexEntriesByModified = (entries) => {
|
|
1969
|
+
return [...entries].sort((a, b) => {
|
|
1970
|
+
const modA = new Date(a.modified).getTime();
|
|
1971
|
+
const modB = new Date(b.modified).getTime();
|
|
1972
|
+
return modB - modA;
|
|
1398
1973
|
});
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1974
|
+
};
|
|
1975
|
+
var hasSessionsIndex = (projectName) => Effect10.gen(function* () {
|
|
1976
|
+
const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
|
|
1977
|
+
try {
|
|
1978
|
+
yield* Effect10.tryPromise(() => fs10.access(indexPath));
|
|
1403
1979
|
return true;
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
name: project.name,
|
|
1407
|
-
displayName: project.displayName,
|
|
1408
|
-
path: project.path,
|
|
1409
|
-
sessionCount: filteredSessions.length,
|
|
1410
|
-
sessions: filteredSessions
|
|
1411
|
-
};
|
|
1412
|
-
});
|
|
1413
|
-
var updateSessionSummary = (projectName, sessionId, newSummary) => Effect3.gen(function* () {
|
|
1414
|
-
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1415
|
-
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
1416
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
1417
|
-
const messages = lines.map((line) => JSON.parse(line));
|
|
1418
|
-
const summaryIdx = messages.findIndex((m) => m.type === "summary");
|
|
1419
|
-
if (summaryIdx >= 0) {
|
|
1420
|
-
messages[summaryIdx] = { ...messages[summaryIdx], summary: newSummary };
|
|
1421
|
-
} else {
|
|
1422
|
-
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
1423
|
-
const summaryMsg = {
|
|
1424
|
-
type: "summary",
|
|
1425
|
-
summary: newSummary,
|
|
1426
|
-
leafUuid: firstUserMsg?.uuid ?? null
|
|
1427
|
-
};
|
|
1428
|
-
messages.unshift(summaryMsg);
|
|
1980
|
+
} catch {
|
|
1981
|
+
return false;
|
|
1429
1982
|
}
|
|
1430
|
-
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1431
|
-
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
1432
|
-
return { success: true };
|
|
1433
1983
|
});
|
|
1434
1984
|
export {
|
|
1985
|
+
analyzeSession,
|
|
1435
1986
|
clearSessions,
|
|
1987
|
+
compressSession,
|
|
1436
1988
|
createLogger,
|
|
1437
1989
|
deleteLinkedTodos,
|
|
1438
1990
|
deleteMessage,
|
|
@@ -1440,6 +1992,7 @@ export {
|
|
|
1440
1992
|
deleteOrphanTodos,
|
|
1441
1993
|
deleteSession,
|
|
1442
1994
|
displayPathToFolderName,
|
|
1995
|
+
extractProjectKnowledge,
|
|
1443
1996
|
extractTextContent,
|
|
1444
1997
|
extractTitle,
|
|
1445
1998
|
findLinkedAgents,
|
|
@@ -1450,11 +2003,14 @@ export {
|
|
|
1450
2003
|
folderNameToDisplayPath,
|
|
1451
2004
|
folderNameToPath,
|
|
1452
2005
|
getDisplayTitle,
|
|
2006
|
+
getIndexEntryDisplayTitle,
|
|
1453
2007
|
getLogger,
|
|
1454
2008
|
getRealPathFromSession,
|
|
1455
2009
|
getSessionFiles,
|
|
2010
|
+
getSessionSortTimestamp,
|
|
1456
2011
|
getSessionsDir,
|
|
1457
2012
|
getTodosDir,
|
|
2013
|
+
hasSessionsIndex,
|
|
1458
2014
|
isContinuationSummary,
|
|
1459
2015
|
isInvalidApiKeyMessage,
|
|
1460
2016
|
listProjects,
|
|
@@ -1462,6 +2018,7 @@ export {
|
|
|
1462
2018
|
loadAgentMessages,
|
|
1463
2019
|
loadProjectTreeData,
|
|
1464
2020
|
loadSessionTreeData,
|
|
2021
|
+
loadSessionsIndex,
|
|
1465
2022
|
maskHomePath,
|
|
1466
2023
|
moveSession,
|
|
1467
2024
|
pathToFolderName,
|
|
@@ -1472,8 +2029,10 @@ export {
|
|
|
1472
2029
|
searchSessions,
|
|
1473
2030
|
sessionHasTodos,
|
|
1474
2031
|
setLogger,
|
|
2032
|
+
sortIndexEntriesByModified,
|
|
1475
2033
|
sortProjects,
|
|
1476
2034
|
splitSession,
|
|
2035
|
+
summarizeSession,
|
|
1477
2036
|
updateSessionSummary
|
|
1478
2037
|
};
|
|
1479
2038
|
//# sourceMappingURL=index.js.map
|