@claude-sessions/core 0.3.7 → 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 +128 -80
- package/dist/index.js +744 -585
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-Cz8chaYQ.d.ts → types-BdPaAnP8.d.ts} +44 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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;
|
|
@@ -334,8 +337,8 @@ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
|
|
|
334
337
|
}
|
|
335
338
|
for (const entry of files) {
|
|
336
339
|
const entryPath = path2.join(projectPath, entry);
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
340
|
+
const stat4 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
|
|
341
|
+
if (stat4?.isDirectory() && !entry.startsWith(".")) {
|
|
339
342
|
const subagentsPath = path2.join(entryPath, "subagents");
|
|
340
343
|
const subagentsExists = yield* Effect.tryPromise(
|
|
341
344
|
() => fs2.stat(subagentsPath).then(() => true).catch(() => false)
|
|
@@ -635,8 +638,8 @@ var deleteOrphanTodos = () => Effect2.gen(function* () {
|
|
|
635
638
|
return { success: true, deletedCount };
|
|
636
639
|
});
|
|
637
640
|
|
|
638
|
-
// src/session.ts
|
|
639
|
-
import { Effect as Effect3
|
|
641
|
+
// src/session/projects.ts
|
|
642
|
+
import { Effect as Effect3 } from "effect";
|
|
640
643
|
import * as fs4 from "fs/promises";
|
|
641
644
|
import * as path4 from "path";
|
|
642
645
|
var listProjects = Effect3.gen(function* () {
|
|
@@ -666,15 +669,42 @@ var listProjects = Effect3.gen(function* () {
|
|
|
666
669
|
);
|
|
667
670
|
return projects;
|
|
668
671
|
});
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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));
|
|
672
702
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
673
|
-
const sessions = yield*
|
|
703
|
+
const sessions = yield* Effect4.all(
|
|
674
704
|
sessionFiles.map(
|
|
675
|
-
(file) =>
|
|
676
|
-
const filePath =
|
|
677
|
-
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"));
|
|
678
708
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
679
709
|
const messages = lines.map((line) => JSON.parse(line));
|
|
680
710
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -693,10 +723,25 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
693
723
|
}),
|
|
694
724
|
O.getOrElse(() => hasSummary ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`)
|
|
695
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
|
+
);
|
|
696
739
|
return {
|
|
697
740
|
id: sessionId,
|
|
698
741
|
projectName,
|
|
699
742
|
title,
|
|
743
|
+
customTitle,
|
|
744
|
+
currentSummary,
|
|
700
745
|
// If session has summary but no user/assistant messages, count as 1
|
|
701
746
|
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,
|
|
702
747
|
createdAt: firstMessage?.timestamp,
|
|
@@ -712,15 +757,15 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
712
757
|
return dateB - dateA;
|
|
713
758
|
});
|
|
714
759
|
});
|
|
715
|
-
var readSession = (projectName, sessionId) =>
|
|
716
|
-
const filePath =
|
|
717
|
-
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"));
|
|
718
763
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
719
764
|
return lines.map((line) => JSON.parse(line));
|
|
720
765
|
});
|
|
721
|
-
var deleteMessage = (projectName, sessionId, messageUuid) =>
|
|
722
|
-
const filePath =
|
|
723
|
-
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"));
|
|
724
769
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
725
770
|
const messages = lines.map((line) => JSON.parse(line));
|
|
726
771
|
const targetIndex = messages.findIndex(
|
|
@@ -739,12 +784,12 @@ var deleteMessage = (projectName, sessionId, messageUuid) => Effect3.gen(functio
|
|
|
739
784
|
}
|
|
740
785
|
messages.splice(targetIndex, 1);
|
|
741
786
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
742
|
-
yield*
|
|
787
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
743
788
|
return { success: true, deletedMessage: deletedMsg };
|
|
744
789
|
});
|
|
745
|
-
var restoreMessage = (projectName, sessionId, message, index) =>
|
|
746
|
-
const filePath =
|
|
747
|
-
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"));
|
|
748
793
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
749
794
|
const messages = lines.map((line) => JSON.parse(line));
|
|
750
795
|
const msgUuid = message.uuid ?? message.messageId;
|
|
@@ -761,41 +806,41 @@ var restoreMessage = (projectName, sessionId, message, index) => Effect3.gen(fun
|
|
|
761
806
|
const insertIndex = Math.min(index, messages.length);
|
|
762
807
|
messages.splice(insertIndex, 0, message);
|
|
763
808
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
764
|
-
yield*
|
|
809
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
765
810
|
return { success: true };
|
|
766
811
|
});
|
|
767
|
-
var deleteSession = (projectName, sessionId) =>
|
|
812
|
+
var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
|
|
768
813
|
const sessionsDir = getSessionsDir();
|
|
769
|
-
const projectPath =
|
|
770
|
-
const filePath =
|
|
814
|
+
const projectPath = path5.join(sessionsDir, projectName);
|
|
815
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
771
816
|
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
772
|
-
const
|
|
773
|
-
if (
|
|
774
|
-
yield*
|
|
775
|
-
const agentBackupDir2 =
|
|
776
|
-
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 }));
|
|
777
822
|
for (const agentId of linkedAgents) {
|
|
778
|
-
const agentPath =
|
|
779
|
-
const agentBackupPath =
|
|
780
|
-
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(() => {
|
|
781
826
|
}));
|
|
782
827
|
}
|
|
783
828
|
yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
784
829
|
return { success: true, deletedAgents: linkedAgents.length };
|
|
785
830
|
}
|
|
786
|
-
const backupDir =
|
|
787
|
-
yield*
|
|
788
|
-
const agentBackupDir =
|
|
789
|
-
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 }));
|
|
790
835
|
for (const agentId of linkedAgents) {
|
|
791
|
-
const agentPath =
|
|
792
|
-
const agentBackupPath =
|
|
793
|
-
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(() => {
|
|
794
839
|
}));
|
|
795
840
|
}
|
|
796
841
|
const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
797
|
-
const backupPath =
|
|
798
|
-
yield*
|
|
842
|
+
const backupPath = path5.join(backupDir, `${projectName}_${sessionId}.jsonl`);
|
|
843
|
+
yield* Effect4.tryPromise(() => fs5.rename(filePath, backupPath));
|
|
799
844
|
return {
|
|
800
845
|
success: true,
|
|
801
846
|
backupPath,
|
|
@@ -803,10 +848,10 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
803
848
|
deletedTodos: todosResult.deletedCount
|
|
804
849
|
};
|
|
805
850
|
});
|
|
806
|
-
var renameSession = (projectName, sessionId, newTitle) =>
|
|
807
|
-
const projectPath =
|
|
808
|
-
const filePath =
|
|
809
|
-
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"));
|
|
810
855
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
811
856
|
if (lines.length === 0) {
|
|
812
857
|
return { success: false, error: "Empty session" };
|
|
@@ -830,14 +875,14 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
830
875
|
messages.unshift(customTitleRecord);
|
|
831
876
|
}
|
|
832
877
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
833
|
-
yield*
|
|
834
|
-
const projectFiles = yield*
|
|
878
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
879
|
+
const projectFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
835
880
|
const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
836
881
|
const summariesTargetingThis = [];
|
|
837
882
|
for (const file of allJsonlFiles) {
|
|
838
|
-
const otherFilePath =
|
|
883
|
+
const otherFilePath = path5.join(projectPath, file);
|
|
839
884
|
try {
|
|
840
|
-
const otherContent = yield*
|
|
885
|
+
const otherContent = yield* Effect4.tryPromise(() => fs5.readFile(otherFilePath, "utf-8"));
|
|
841
886
|
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
842
887
|
const otherMessages = otherLines.map((l) => JSON.parse(l));
|
|
843
888
|
for (let i = 0; i < otherMessages.length; i++) {
|
|
@@ -857,8 +902,8 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
857
902
|
if (summariesTargetingThis.length > 0) {
|
|
858
903
|
summariesTargetingThis.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
|
|
859
904
|
const firstSummary = summariesTargetingThis[0];
|
|
860
|
-
const summaryFilePath =
|
|
861
|
-
const summaryContent = yield*
|
|
905
|
+
const summaryFilePath = path5.join(projectPath, firstSummary.file);
|
|
906
|
+
const summaryContent = yield* Effect4.tryPromise(() => fs5.readFile(summaryFilePath, "utf-8"));
|
|
862
907
|
const summaryLines = summaryContent.trim().split("\n").filter(Boolean);
|
|
863
908
|
const summaryMessages = summaryLines.map((l) => JSON.parse(l));
|
|
864
909
|
summaryMessages[firstSummary.idx] = {
|
|
@@ -866,9 +911,9 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
866
911
|
summary: newTitle
|
|
867
912
|
};
|
|
868
913
|
const newSummaryContent = summaryMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
869
|
-
yield*
|
|
914
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
|
|
870
915
|
} else {
|
|
871
|
-
const currentContent = yield*
|
|
916
|
+
const currentContent = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
872
917
|
const currentLines = currentContent.trim().split("\n").filter(Boolean);
|
|
873
918
|
const currentMessages = currentLines.map((l) => JSON.parse(l));
|
|
874
919
|
const firstUserIdx = currentMessages.findIndex((m) => m.type === "user");
|
|
@@ -887,233 +932,50 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
887
932
|
|
|
888
933
|
${cleanedText}`;
|
|
889
934
|
const updatedContent = currentMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
890
|
-
yield*
|
|
935
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, updatedContent, "utf-8"));
|
|
891
936
|
}
|
|
892
937
|
}
|
|
893
938
|
}
|
|
894
939
|
}
|
|
895
940
|
return { success: true };
|
|
896
941
|
});
|
|
897
|
-
var
|
|
898
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
899
|
-
const fileChanges = [];
|
|
900
|
-
const seenFiles = /* @__PURE__ */ new Set();
|
|
901
|
-
for (const msg of messages) {
|
|
902
|
-
if (msg.type === "file-history-snapshot") {
|
|
903
|
-
const snapshot = msg;
|
|
904
|
-
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
905
|
-
if (backups && typeof backups === "object") {
|
|
906
|
-
for (const filePath of Object.keys(backups)) {
|
|
907
|
-
if (!seenFiles.has(filePath)) {
|
|
908
|
-
seenFiles.add(filePath);
|
|
909
|
-
fileChanges.push({
|
|
910
|
-
path: filePath,
|
|
911
|
-
action: "modified",
|
|
912
|
-
timestamp: snapshot.snapshot?.timestamp,
|
|
913
|
-
messageUuid: snapshot.messageId ?? msg.uuid
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
if (msg.type === "assistant" && msg.message?.content) {
|
|
920
|
-
const content = msg.message.content;
|
|
921
|
-
if (Array.isArray(content)) {
|
|
922
|
-
for (const item of content) {
|
|
923
|
-
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
924
|
-
const toolUse = item;
|
|
925
|
-
if ((toolUse.name === "Write" || toolUse.name === "Edit") && toolUse.input?.file_path) {
|
|
926
|
-
const filePath = toolUse.input.file_path;
|
|
927
|
-
if (!seenFiles.has(filePath)) {
|
|
928
|
-
seenFiles.add(filePath);
|
|
929
|
-
fileChanges.push({
|
|
930
|
-
path: filePath,
|
|
931
|
-
action: toolUse.name === "Write" ? "created" : "modified",
|
|
932
|
-
timestamp: msg.timestamp,
|
|
933
|
-
messageUuid: msg.uuid
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
return {
|
|
943
|
-
sessionId,
|
|
944
|
-
projectName,
|
|
945
|
-
files: fileChanges,
|
|
946
|
-
totalChanges: fileChanges.length
|
|
947
|
-
};
|
|
948
|
-
});
|
|
949
|
-
var analyzeSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
950
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
951
|
-
let userMessages = 0;
|
|
952
|
-
let assistantMessages = 0;
|
|
953
|
-
let summaryCount = 0;
|
|
954
|
-
let snapshotCount = 0;
|
|
955
|
-
const toolUsageMap = /* @__PURE__ */ new Map();
|
|
956
|
-
const filesChanged = /* @__PURE__ */ new Set();
|
|
957
|
-
const patterns = [];
|
|
958
|
-
const milestones = [];
|
|
959
|
-
let firstTimestamp;
|
|
960
|
-
let lastTimestamp;
|
|
961
|
-
for (const msg of messages) {
|
|
962
|
-
if (msg.timestamp) {
|
|
963
|
-
if (!firstTimestamp) firstTimestamp = msg.timestamp;
|
|
964
|
-
lastTimestamp = msg.timestamp;
|
|
965
|
-
}
|
|
966
|
-
if (msg.type === "user") {
|
|
967
|
-
userMessages++;
|
|
968
|
-
const content = typeof msg.content === "string" ? msg.content : "";
|
|
969
|
-
if (content.toLowerCase().includes("commit") || content.toLowerCase().includes("\uC644\uB8CC")) {
|
|
970
|
-
milestones.push({
|
|
971
|
-
timestamp: msg.timestamp,
|
|
972
|
-
description: `User checkpoint: ${content.slice(0, 50)}...`,
|
|
973
|
-
messageUuid: msg.uuid
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
} else if (msg.type === "assistant") {
|
|
977
|
-
assistantMessages++;
|
|
978
|
-
if (msg.message?.content && Array.isArray(msg.message.content)) {
|
|
979
|
-
for (const item of msg.message.content) {
|
|
980
|
-
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
981
|
-
const toolUse = item;
|
|
982
|
-
const toolName = toolUse.name ?? "unknown";
|
|
983
|
-
const existing = toolUsageMap.get(toolName) ?? { count: 0, errorCount: 0 };
|
|
984
|
-
existing.count++;
|
|
985
|
-
toolUsageMap.set(toolName, existing);
|
|
986
|
-
if ((toolName === "Write" || toolName === "Edit") && toolUse.input?.file_path) {
|
|
987
|
-
filesChanged.add(toolUse.input.file_path);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
} else if (msg.type === "summary") {
|
|
993
|
-
summaryCount++;
|
|
994
|
-
if (msg.summary) {
|
|
995
|
-
milestones.push({
|
|
996
|
-
timestamp: msg.timestamp,
|
|
997
|
-
description: `Summary: ${msg.summary.slice(0, 100)}...`,
|
|
998
|
-
messageUuid: msg.uuid
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
} else if (msg.type === "file-history-snapshot") {
|
|
1002
|
-
snapshotCount++;
|
|
1003
|
-
const snapshot = msg;
|
|
1004
|
-
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1005
|
-
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1006
|
-
filesChanged.add(filePath);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
for (const msg of messages) {
|
|
1012
|
-
if (msg.type === "user" && msg.content && Array.isArray(msg.content)) {
|
|
1013
|
-
for (const item of msg.content) {
|
|
1014
|
-
if (item && typeof item === "object" && "type" in item && item.type === "tool_result" && "is_error" in item && item.is_error) {
|
|
1015
|
-
const toolResultItem = item;
|
|
1016
|
-
const toolUseId = toolResultItem.tool_use_id;
|
|
1017
|
-
if (toolUseId) {
|
|
1018
|
-
for (const prevMsg of messages) {
|
|
1019
|
-
if (prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
|
1020
|
-
for (const prevItem of prevMsg.message.content) {
|
|
1021
|
-
if (prevItem && typeof prevItem === "object" && "type" in prevItem && prevItem.type === "tool_use" && "id" in prevItem && prevItem.id === toolUseId) {
|
|
1022
|
-
const toolName = prevItem.name ?? "unknown";
|
|
1023
|
-
const existing = toolUsageMap.get(toolName);
|
|
1024
|
-
if (existing) {
|
|
1025
|
-
existing.errorCount++;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
let durationMinutes = 0;
|
|
1037
|
-
if (firstTimestamp && lastTimestamp) {
|
|
1038
|
-
const first = new Date(firstTimestamp).getTime();
|
|
1039
|
-
const last = new Date(lastTimestamp).getTime();
|
|
1040
|
-
durationMinutes = Math.round((last - first) / 1e3 / 60);
|
|
1041
|
-
}
|
|
1042
|
-
const toolUsageArray = Array.from(toolUsageMap.entries()).map(([name, stats]) => ({
|
|
1043
|
-
name,
|
|
1044
|
-
count: stats.count,
|
|
1045
|
-
errorCount: stats.errorCount
|
|
1046
|
-
}));
|
|
1047
|
-
for (const tool of toolUsageArray) {
|
|
1048
|
-
if (tool.count >= 3 && tool.errorCount / tool.count > 0.3) {
|
|
1049
|
-
patterns.push({
|
|
1050
|
-
type: "high_error_rate",
|
|
1051
|
-
description: `${tool.name} had ${tool.errorCount}/${tool.count} errors (${Math.round(tool.errorCount / tool.count * 100)}%)`,
|
|
1052
|
-
count: tool.errorCount
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
if (snapshotCount > 10) {
|
|
1057
|
-
patterns.push({
|
|
1058
|
-
type: "many_snapshots",
|
|
1059
|
-
description: `${snapshotCount} file-history-snapshots could be compressed`,
|
|
1060
|
-
count: snapshotCount
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
return {
|
|
1064
|
-
sessionId,
|
|
1065
|
-
projectName,
|
|
1066
|
-
durationMinutes,
|
|
1067
|
-
stats: {
|
|
1068
|
-
totalMessages: messages.length,
|
|
1069
|
-
userMessages,
|
|
1070
|
-
assistantMessages,
|
|
1071
|
-
summaryCount,
|
|
1072
|
-
snapshotCount
|
|
1073
|
-
},
|
|
1074
|
-
toolUsage: toolUsageArray.sort((a, b) => b.count - a.count),
|
|
1075
|
-
filesChanged: Array.from(filesChanged),
|
|
1076
|
-
patterns,
|
|
1077
|
-
milestones
|
|
1078
|
-
};
|
|
1079
|
-
});
|
|
1080
|
-
var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(function* () {
|
|
942
|
+
var moveSession = (sourceProject, sessionId, targetProject) => Effect4.gen(function* () {
|
|
1081
943
|
const sessionsDir = getSessionsDir();
|
|
1082
|
-
const sourcePath =
|
|
1083
|
-
const targetPath =
|
|
1084
|
-
const sourceFile =
|
|
1085
|
-
const targetFile =
|
|
1086
|
-
const sourceExists = yield*
|
|
1087
|
-
() =>
|
|
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)
|
|
1088
950
|
);
|
|
1089
951
|
if (!sourceExists) {
|
|
1090
952
|
return { success: false, error: "Source session not found" };
|
|
1091
953
|
}
|
|
1092
|
-
const targetExists = yield*
|
|
1093
|
-
() =>
|
|
954
|
+
const targetExists = yield* Effect4.tryPromise(
|
|
955
|
+
() => fs5.access(targetFile).then(() => true).catch(() => false)
|
|
1094
956
|
);
|
|
1095
957
|
if (targetExists) {
|
|
1096
958
|
return { success: false, error: "Session already exists in target project" };
|
|
1097
959
|
}
|
|
1098
|
-
yield*
|
|
960
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(targetPath, { recursive: true }));
|
|
1099
961
|
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
1100
|
-
yield*
|
|
962
|
+
yield* Effect4.tryPromise(() => fs5.rename(sourceFile, targetFile));
|
|
1101
963
|
for (const agentId of linkedAgents) {
|
|
1102
|
-
const sourceAgentFile =
|
|
1103
|
-
const targetAgentFile =
|
|
1104
|
-
const agentExists = yield*
|
|
1105
|
-
() =>
|
|
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)
|
|
1106
968
|
);
|
|
1107
969
|
if (agentExists) {
|
|
1108
|
-
yield*
|
|
970
|
+
yield* Effect4.tryPromise(() => fs5.rename(sourceAgentFile, targetAgentFile));
|
|
1109
971
|
}
|
|
1110
972
|
}
|
|
1111
973
|
return { success: true };
|
|
1112
974
|
});
|
|
1113
|
-
var splitSession = (projectName, sessionId, splitAtMessageUuid) =>
|
|
1114
|
-
const projectPath =
|
|
1115
|
-
const filePath =
|
|
1116
|
-
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"));
|
|
1117
979
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1118
980
|
const allMessages = lines.map((line) => JSON.parse(line));
|
|
1119
981
|
const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
|
|
@@ -1161,15 +1023,15 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
1161
1023
|
updatedMovedMessages.unshift(clonedSummary);
|
|
1162
1024
|
}
|
|
1163
1025
|
const keptContent = keptMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1164
|
-
yield*
|
|
1165
|
-
const newFilePath =
|
|
1026
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, keptContent, "utf-8"));
|
|
1027
|
+
const newFilePath = path5.join(projectPath, `${newSessionId}.jsonl`);
|
|
1166
1028
|
const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1167
|
-
yield*
|
|
1168
|
-
const agentFiles = yield*
|
|
1029
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(newFilePath, newContent, "utf-8"));
|
|
1030
|
+
const agentFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
1169
1031
|
const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
1170
1032
|
for (const agentFile of agentJsonlFiles) {
|
|
1171
|
-
const agentPath =
|
|
1172
|
-
const agentContent = yield*
|
|
1033
|
+
const agentPath = path5.join(projectPath, agentFile);
|
|
1034
|
+
const agentContent = yield* Effect4.tryPromise(() => fs5.readFile(agentPath, "utf-8"));
|
|
1173
1035
|
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1174
1036
|
if (agentLines.length === 0) continue;
|
|
1175
1037
|
const firstAgentMsg = JSON.parse(agentLines[0]);
|
|
@@ -1184,7 +1046,7 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
1184
1046
|
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
1185
1047
|
});
|
|
1186
1048
|
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
1187
|
-
yield*
|
|
1049
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
1188
1050
|
}
|
|
1189
1051
|
}
|
|
1190
1052
|
}
|
|
@@ -1196,281 +1058,70 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
1196
1058
|
duplicatedSummary: shouldDuplicate
|
|
1197
1059
|
};
|
|
1198
1060
|
});
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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"));
|
|
1202
1070
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1203
|
-
if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
|
|
1204
1071
|
const messages = lines.map((line) => JSON.parse(line));
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
+
}
|
|
1086
|
+
}
|
|
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
|
+
}
|
|
1209
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 ?? "");
|
|
1210
1117
|
});
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
)
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
const filtered = [];
|
|
1220
|
-
let lastValidUuid = null;
|
|
1221
|
-
for (let i = 0; i < messages.length; i++) {
|
|
1222
|
-
if (invalidIndices.includes(i)) {
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
const msg = messages[i];
|
|
1226
|
-
if (msg.parentUuid && invalidIndices.some((idx) => messages[idx]?.uuid === msg.parentUuid)) {
|
|
1227
|
-
msg.parentUuid = lastValidUuid;
|
|
1228
|
-
}
|
|
1229
|
-
filtered.push(msg);
|
|
1230
|
-
lastValidUuid = msg.uuid;
|
|
1231
|
-
}
|
|
1232
|
-
const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
|
|
1233
|
-
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
1234
|
-
const remainingUserAssistant = filtered.filter(
|
|
1235
|
-
(m) => m.type === "user" || m.type === "assistant"
|
|
1236
|
-
).length;
|
|
1237
|
-
const hasSummary = filtered.some((m) => m.type === "summary");
|
|
1238
|
-
const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
|
|
1239
|
-
return { removedCount: invalidIndices.length, remainingCount };
|
|
1240
|
-
});
|
|
1241
|
-
var previewCleanup = (projectName) => Effect3.gen(function* () {
|
|
1242
|
-
const projects = yield* listProjects;
|
|
1243
|
-
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1244
|
-
const orphanTodos = yield* findOrphanTodos();
|
|
1245
|
-
const orphanTodoCount = orphanTodos.length;
|
|
1246
|
-
const results = yield* Effect3.all(
|
|
1247
|
-
targetProjects.map(
|
|
1248
|
-
(project) => Effect3.gen(function* () {
|
|
1249
|
-
const sessions = yield* listSessions(project.name);
|
|
1250
|
-
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
1251
|
-
const invalidSessions = sessions.filter(
|
|
1252
|
-
(s) => s.title?.includes("Invalid API key") || s.title?.includes("API key")
|
|
1253
|
-
);
|
|
1254
|
-
let emptyWithTodosCount = 0;
|
|
1255
|
-
for (const session of emptySessions) {
|
|
1256
|
-
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
1257
|
-
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
1258
|
-
if (hasTodos) {
|
|
1259
|
-
emptyWithTodosCount++;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
const orphanAgents = yield* findOrphanAgents(project.name);
|
|
1263
|
-
return {
|
|
1264
|
-
project: project.name,
|
|
1265
|
-
emptySessions,
|
|
1266
|
-
invalidSessions,
|
|
1267
|
-
emptyWithTodosCount,
|
|
1268
|
-
orphanAgentCount: orphanAgents.length,
|
|
1269
|
-
orphanTodoCount: 0
|
|
1270
|
-
// Will set for first project only
|
|
1271
|
-
};
|
|
1272
|
-
})
|
|
1273
|
-
),
|
|
1274
|
-
{ concurrency: 5 }
|
|
1275
|
-
);
|
|
1276
|
-
if (results.length > 0) {
|
|
1277
|
-
results[0] = { ...results[0], orphanTodoCount };
|
|
1278
|
-
}
|
|
1279
|
-
return results;
|
|
1280
|
-
});
|
|
1281
|
-
var clearSessions = (options) => Effect3.gen(function* () {
|
|
1282
|
-
const {
|
|
1283
|
-
projectName,
|
|
1284
|
-
clearEmpty = true,
|
|
1285
|
-
clearInvalid = true,
|
|
1286
|
-
skipWithTodos = true,
|
|
1287
|
-
clearOrphanAgents = true,
|
|
1288
|
-
clearOrphanTodos = false
|
|
1289
|
-
} = options;
|
|
1290
|
-
const projects = yield* listProjects;
|
|
1291
|
-
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1292
|
-
let deletedSessionCount = 0;
|
|
1293
|
-
let removedMessageCount = 0;
|
|
1294
|
-
let deletedOrphanAgentCount = 0;
|
|
1295
|
-
let deletedOrphanTodoCount = 0;
|
|
1296
|
-
const sessionsToDelete = [];
|
|
1297
|
-
if (clearInvalid) {
|
|
1298
|
-
for (const project of targetProjects) {
|
|
1299
|
-
const projectPath = path4.join(getSessionsDir(), project.name);
|
|
1300
|
-
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
1301
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1302
|
-
for (const file of sessionFiles) {
|
|
1303
|
-
const sessionId = file.replace(".jsonl", "");
|
|
1304
|
-
const result = yield* cleanInvalidMessages(project.name, sessionId);
|
|
1305
|
-
removedMessageCount += result.removedCount;
|
|
1306
|
-
if (result.remainingCount === 0) {
|
|
1307
|
-
sessionsToDelete.push({ project: project.name, sessionId });
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
if (clearEmpty) {
|
|
1313
|
-
for (const project of targetProjects) {
|
|
1314
|
-
const sessions = yield* listSessions(project.name);
|
|
1315
|
-
for (const session of sessions) {
|
|
1316
|
-
if (session.messageCount === 0) {
|
|
1317
|
-
const alreadyMarked = sessionsToDelete.some(
|
|
1318
|
-
(s) => s.project === project.name && s.sessionId === session.id
|
|
1319
|
-
);
|
|
1320
|
-
if (!alreadyMarked) {
|
|
1321
|
-
if (skipWithTodos) {
|
|
1322
|
-
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
1323
|
-
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
1324
|
-
if (hasTodos) continue;
|
|
1325
|
-
}
|
|
1326
|
-
sessionsToDelete.push({ project: project.name, sessionId: session.id });
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
for (const { project, sessionId } of sessionsToDelete) {
|
|
1333
|
-
yield* deleteSession(project, sessionId);
|
|
1334
|
-
deletedSessionCount++;
|
|
1335
|
-
}
|
|
1336
|
-
if (clearOrphanAgents) {
|
|
1337
|
-
for (const project of targetProjects) {
|
|
1338
|
-
const result = yield* deleteOrphanAgents(project.name);
|
|
1339
|
-
deletedOrphanAgentCount += result.count;
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
if (clearOrphanTodos) {
|
|
1343
|
-
const result = yield* deleteOrphanTodos();
|
|
1344
|
-
deletedOrphanTodoCount = result.deletedCount;
|
|
1345
|
-
}
|
|
1346
|
-
return {
|
|
1347
|
-
success: true,
|
|
1348
|
-
deletedCount: deletedSessionCount,
|
|
1349
|
-
removedMessageCount,
|
|
1350
|
-
deletedOrphanAgentCount,
|
|
1351
|
-
deletedOrphanTodoCount
|
|
1352
|
-
};
|
|
1353
|
-
});
|
|
1354
|
-
var searchSessions = (query, options = {}) => Effect3.gen(function* () {
|
|
1355
|
-
const { projectName, searchContent = false } = options;
|
|
1356
|
-
const results = [];
|
|
1357
|
-
const queryLower = query.toLowerCase();
|
|
1358
|
-
const projects = yield* listProjects;
|
|
1359
|
-
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1360
|
-
for (const project of targetProjects) {
|
|
1361
|
-
const sessions = yield* listSessions(project.name);
|
|
1362
|
-
for (const session of sessions) {
|
|
1363
|
-
const titleLower = (session.title ?? "").toLowerCase();
|
|
1364
|
-
if (titleLower.includes(queryLower)) {
|
|
1365
|
-
results.push({
|
|
1366
|
-
sessionId: session.id,
|
|
1367
|
-
projectName: project.name,
|
|
1368
|
-
title: session.title ?? "Untitled",
|
|
1369
|
-
matchType: "title",
|
|
1370
|
-
timestamp: session.updatedAt
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
if (searchContent) {
|
|
1376
|
-
for (const project of targetProjects) {
|
|
1377
|
-
const projectPath = path4.join(getSessionsDir(), project.name);
|
|
1378
|
-
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
1379
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1380
|
-
for (const file of sessionFiles) {
|
|
1381
|
-
const sessionId = file.replace(".jsonl", "");
|
|
1382
|
-
if (results.some((r) => r.sessionId === sessionId && r.projectName === project.name)) {
|
|
1383
|
-
continue;
|
|
1384
|
-
}
|
|
1385
|
-
const filePath = path4.join(projectPath, file);
|
|
1386
|
-
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
1387
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
1388
|
-
for (const line of lines) {
|
|
1389
|
-
try {
|
|
1390
|
-
const msg = JSON.parse(line);
|
|
1391
|
-
if (msg.type !== "user" && msg.type !== "assistant") continue;
|
|
1392
|
-
const text = extractTextContent(msg.message);
|
|
1393
|
-
const textLower = text.toLowerCase();
|
|
1394
|
-
if (textLower.includes(queryLower)) {
|
|
1395
|
-
const matchIndex = textLower.indexOf(queryLower);
|
|
1396
|
-
const start = Math.max(0, matchIndex - 50);
|
|
1397
|
-
const end = Math.min(text.length, matchIndex + query.length + 50);
|
|
1398
|
-
const snippet = (start > 0 ? "..." : "") + text.slice(start, end).trim() + (end < text.length ? "..." : "");
|
|
1399
|
-
results.push({
|
|
1400
|
-
sessionId,
|
|
1401
|
-
projectName: project.name,
|
|
1402
|
-
title: extractTitle(extractTextContent(msg.message)) || `Session ${sessionId.slice(0, 8)}`,
|
|
1403
|
-
matchType: "content",
|
|
1404
|
-
snippet,
|
|
1405
|
-
messageUuid: msg.uuid,
|
|
1406
|
-
timestamp: msg.timestamp
|
|
1407
|
-
});
|
|
1408
|
-
break;
|
|
1409
|
-
}
|
|
1410
|
-
} catch {
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
return results.sort((a, b) => {
|
|
1417
|
-
const dateA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
|
|
1418
|
-
const dateB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
|
|
1419
|
-
return dateB - dateA;
|
|
1420
|
-
});
|
|
1421
|
-
});
|
|
1422
|
-
var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession) => Effect3.gen(function* () {
|
|
1423
|
-
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
1424
|
-
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
1425
|
-
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
1426
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
1427
|
-
const messages = lines.map((line) => JSON.parse(line));
|
|
1428
|
-
let summaries;
|
|
1429
|
-
if (summariesByTargetSession) {
|
|
1430
|
-
summaries = [...summariesByTargetSession.get(sessionId) ?? []].sort(
|
|
1431
|
-
(a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? "")
|
|
1432
|
-
);
|
|
1433
|
-
} else {
|
|
1434
|
-
summaries = [];
|
|
1435
|
-
const sessionUuids = /* @__PURE__ */ new Set();
|
|
1436
|
-
for (const msg of messages) {
|
|
1437
|
-
if (msg.uuid && typeof msg.uuid === "string") {
|
|
1438
|
-
sessionUuids.add(msg.uuid);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
const projectFiles = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
1442
|
-
const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
1443
|
-
for (const file of allJsonlFiles) {
|
|
1444
|
-
try {
|
|
1445
|
-
const otherFilePath = path4.join(projectPath, file);
|
|
1446
|
-
const otherContent = yield* Effect3.tryPromise(() => fs4.readFile(otherFilePath, "utf-8"));
|
|
1447
|
-
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
1448
|
-
for (const line of otherLines) {
|
|
1449
|
-
try {
|
|
1450
|
-
const msg = JSON.parse(line);
|
|
1451
|
-
if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
|
|
1452
|
-
const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
|
|
1453
|
-
summaries.push({
|
|
1454
|
-
summary: msg.summary,
|
|
1455
|
-
leafUuid: msg.leafUuid,
|
|
1456
|
-
timestamp: targetMsg?.timestamp ?? msg.timestamp
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
} catch {
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
} catch {
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
summaries.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
|
|
1467
|
-
let lastCompactBoundaryUuid;
|
|
1468
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1469
|
-
const msg = messages[i];
|
|
1470
|
-
if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
1471
|
-
lastCompactBoundaryUuid = msg.uuid;
|
|
1472
|
-
break;
|
|
1473
|
-
}
|
|
1118
|
+
let lastCompactBoundaryUuid;
|
|
1119
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1120
|
+
const msg = messages[i];
|
|
1121
|
+
if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
1122
|
+
lastCompactBoundaryUuid = msg.uuid;
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1474
1125
|
}
|
|
1475
1126
|
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
1476
1127
|
const customTitleMsg = messages.find((m) => m.type === "custom-title");
|
|
@@ -1484,9 +1135,9 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
|
|
|
1484
1135
|
const linkedAgentIds = yield* findLinkedAgents(projectName, sessionId);
|
|
1485
1136
|
const agents = [];
|
|
1486
1137
|
for (const agentId of linkedAgentIds) {
|
|
1487
|
-
const agentPath =
|
|
1138
|
+
const agentPath = path6.join(projectPath, `${agentId}.jsonl`);
|
|
1488
1139
|
try {
|
|
1489
|
-
const agentContent = yield*
|
|
1140
|
+
const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
|
|
1490
1141
|
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1491
1142
|
const agentMsgs = agentLines.map((l) => JSON.parse(l));
|
|
1492
1143
|
const agentUserAssistant = agentMsgs.filter(
|
|
@@ -1522,6 +1173,7 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
|
|
|
1522
1173
|
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
|
|
1523
1174
|
createdAt: firstMessage?.timestamp ?? void 0,
|
|
1524
1175
|
updatedAt: lastMessage?.timestamp ?? void 0,
|
|
1176
|
+
fileMtime,
|
|
1525
1177
|
summaries,
|
|
1526
1178
|
agents,
|
|
1527
1179
|
todos,
|
|
@@ -1529,24 +1181,40 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
|
|
|
1529
1181
|
};
|
|
1530
1182
|
});
|
|
1531
1183
|
var loadSessionTreeData = (projectName, sessionId) => loadSessionTreeDataInternal(projectName, sessionId, void 0);
|
|
1532
|
-
var
|
|
1184
|
+
var DEFAULT_SORT = { field: "summary", order: "desc" };
|
|
1185
|
+
var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* () {
|
|
1533
1186
|
const project = (yield* listProjects).find((p) => p.name === projectName);
|
|
1534
1187
|
if (!project) {
|
|
1535
1188
|
return null;
|
|
1536
1189
|
}
|
|
1537
|
-
const
|
|
1538
|
-
const
|
|
1190
|
+
const sort = sortOptions ?? DEFAULT_SORT;
|
|
1191
|
+
const projectPath = path6.join(getSessionsDir(), projectName);
|
|
1192
|
+
const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
|
|
1539
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
|
+
);
|
|
1540
1208
|
const globalUuidMap = /* @__PURE__ */ new Map();
|
|
1541
1209
|
const allSummaries = [];
|
|
1542
1210
|
const allJsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
1543
|
-
yield*
|
|
1211
|
+
yield* Effect5.all(
|
|
1544
1212
|
allJsonlFiles.map(
|
|
1545
|
-
(file) =>
|
|
1546
|
-
const filePath =
|
|
1213
|
+
(file) => Effect5.gen(function* () {
|
|
1214
|
+
const filePath = path6.join(projectPath, file);
|
|
1547
1215
|
const fileSessionId = file.replace(".jsonl", "");
|
|
1548
1216
|
try {
|
|
1549
|
-
const content = yield*
|
|
1217
|
+
const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
|
|
1550
1218
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1551
1219
|
for (const line of lines) {
|
|
1552
1220
|
try {
|
|
@@ -1567,7 +1235,8 @@ var loadProjectTreeData = (projectName) => Effect3.gen(function* () {
|
|
|
1567
1235
|
allSummaries.push({
|
|
1568
1236
|
summary: msg.summary,
|
|
1569
1237
|
leafUuid: msg.leafUuid,
|
|
1570
|
-
timestamp: msg.timestamp
|
|
1238
|
+
timestamp: msg.timestamp,
|
|
1239
|
+
sourceFile: file
|
|
1571
1240
|
});
|
|
1572
1241
|
}
|
|
1573
1242
|
} catch {
|
|
@@ -1591,22 +1260,60 @@ var loadProjectTreeData = (projectName) => Effect3.gen(function* () {
|
|
|
1591
1260
|
summariesByTargetSession.get(targetSessionId).push({
|
|
1592
1261
|
summary: summaryData.summary,
|
|
1593
1262
|
leafUuid: summaryData.leafUuid,
|
|
1594
|
-
|
|
1263
|
+
// Use summary's own timestamp for sorting, not the target message's timestamp
|
|
1264
|
+
timestamp: summaryData.timestamp ?? targetInfo.timestamp,
|
|
1265
|
+
sourceFile: summaryData.sourceFile
|
|
1595
1266
|
});
|
|
1596
1267
|
}
|
|
1597
1268
|
}
|
|
1598
1269
|
}
|
|
1599
|
-
const sessions = yield*
|
|
1270
|
+
const sessions = yield* Effect5.all(
|
|
1600
1271
|
sessionFiles.map((file) => {
|
|
1601
1272
|
const sessionId = file.replace(".jsonl", "");
|
|
1602
|
-
|
|
1273
|
+
const mtime = fileMtimes.get(sessionId);
|
|
1274
|
+
return loadSessionTreeDataInternal(projectName, sessionId, summariesByTargetSession, mtime);
|
|
1603
1275
|
}),
|
|
1604
1276
|
{ concurrency: 10 }
|
|
1605
1277
|
);
|
|
1606
1278
|
const sortedSessions = sessions.sort((a, b) => {
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
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;
|
|
1610
1317
|
});
|
|
1611
1318
|
const filteredSessions = sortedSessions.filter((s) => {
|
|
1612
1319
|
if (isErrorSessionTitle(s.title)) return false;
|
|
@@ -1622,31 +1329,146 @@ var loadProjectTreeData = (projectName) => Effect3.gen(function* () {
|
|
|
1622
1329
|
sessions: filteredSessions
|
|
1623
1330
|
};
|
|
1624
1331
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
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
|
+
}
|
|
1641
1398
|
}
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
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
|
+
};
|
|
1645
1467
|
});
|
|
1646
|
-
var compressSession = (projectName, sessionId, options = {}) =>
|
|
1468
|
+
var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
|
|
1647
1469
|
const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
|
|
1648
|
-
const filePath =
|
|
1649
|
-
const content = yield*
|
|
1470
|
+
const filePath = path7.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1471
|
+
const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
|
|
1650
1472
|
const originalSize = Buffer.byteLength(content, "utf-8");
|
|
1651
1473
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1652
1474
|
const messages = lines.map((line) => JSON.parse(line));
|
|
@@ -1689,7 +1511,7 @@ var compressSession = (projectName, sessionId, options = {}) => Effect3.gen(func
|
|
|
1689
1511
|
}
|
|
1690
1512
|
const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1691
1513
|
const compressedSize = Buffer.byteLength(newContent, "utf-8");
|
|
1692
|
-
yield*
|
|
1514
|
+
yield* Effect6.tryPromise(() => fs7.writeFile(filePath, newContent, "utf-8"));
|
|
1693
1515
|
return {
|
|
1694
1516
|
success: true,
|
|
1695
1517
|
originalSize,
|
|
@@ -1698,12 +1520,12 @@ var compressSession = (projectName, sessionId, options = {}) => Effect3.gen(func
|
|
|
1698
1520
|
truncatedOutputs
|
|
1699
1521
|
};
|
|
1700
1522
|
});
|
|
1701
|
-
var extractProjectKnowledge = (projectName, sessionIds) =>
|
|
1523
|
+
var extractProjectKnowledge = (projectName, sessionIds) => Effect6.gen(function* () {
|
|
1702
1524
|
const sessionsDir = getSessionsDir();
|
|
1703
|
-
const projectDir =
|
|
1525
|
+
const projectDir = path7.join(sessionsDir, projectName);
|
|
1704
1526
|
let targetSessionIds = sessionIds;
|
|
1705
1527
|
if (!targetSessionIds) {
|
|
1706
|
-
const files = yield*
|
|
1528
|
+
const files = yield* Effect6.tryPromise(() => fs7.readdir(projectDir));
|
|
1707
1529
|
targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
|
|
1708
1530
|
}
|
|
1709
1531
|
const fileModifyCount = /* @__PURE__ */ new Map();
|
|
@@ -1770,7 +1592,14 @@ var extractProjectKnowledge = (projectName, sessionIds) => Effect3.gen(function*
|
|
|
1770
1592
|
decisions: decisions.slice(0, 20)
|
|
1771
1593
|
};
|
|
1772
1594
|
});
|
|
1773
|
-
|
|
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* () {
|
|
1774
1603
|
const { limit = 50, maxLength = 100 } = options;
|
|
1775
1604
|
const messages = yield* readSession(projectName, sessionId);
|
|
1776
1605
|
const lines = [];
|
|
@@ -1820,13 +1649,338 @@ var summarizeSession = (projectName, sessionId, options = {}) => Effect3.gen(fun
|
|
|
1820
1649
|
formatted
|
|
1821
1650
|
};
|
|
1822
1651
|
});
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
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 };
|
|
1827
1676
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
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
|
|
1747
|
+
} = options;
|
|
1748
|
+
const projects = yield* listProjects;
|
|
1749
|
+
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1750
|
+
let deletedSessionCount = 0;
|
|
1751
|
+
let removedMessageCount = 0;
|
|
1752
|
+
let deletedOrphanAgentCount = 0;
|
|
1753
|
+
let deletedOrphanTodoCount = 0;
|
|
1754
|
+
const sessionsToDelete = [];
|
|
1755
|
+
if (clearInvalid) {
|
|
1756
|
+
for (const project of targetProjects) {
|
|
1757
|
+
const projectPath = path8.join(getSessionsDir(), project.name);
|
|
1758
|
+
const files = yield* Effect7.tryPromise(() => fs8.readdir(projectPath));
|
|
1759
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1760
|
+
for (const file of sessionFiles) {
|
|
1761
|
+
const sessionId = file.replace(".jsonl", "");
|
|
1762
|
+
const result = yield* cleanInvalidMessages(project.name, sessionId);
|
|
1763
|
+
removedMessageCount += result.removedCount;
|
|
1764
|
+
if (result.remainingCount === 0) {
|
|
1765
|
+
sessionsToDelete.push({ project: project.name, sessionId });
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if (clearEmpty) {
|
|
1771
|
+
for (const project of targetProjects) {
|
|
1772
|
+
const sessions = yield* listSessions(project.name);
|
|
1773
|
+
for (const session of sessions) {
|
|
1774
|
+
if (session.messageCount === 0) {
|
|
1775
|
+
const alreadyMarked = sessionsToDelete.some(
|
|
1776
|
+
(s) => s.project === project.name && s.sessionId === session.id
|
|
1777
|
+
);
|
|
1778
|
+
if (!alreadyMarked) {
|
|
1779
|
+
if (skipWithTodos) {
|
|
1780
|
+
const linkedAgents = yield* findLinkedAgents(project.name, session.id);
|
|
1781
|
+
const hasTodos = yield* sessionHasTodos(session.id, linkedAgents);
|
|
1782
|
+
if (hasTodos) continue;
|
|
1783
|
+
}
|
|
1784
|
+
sessionsToDelete.push({ project: project.name, sessionId: session.id });
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
for (const { project, sessionId } of sessionsToDelete) {
|
|
1791
|
+
yield* deleteSession(project, sessionId);
|
|
1792
|
+
deletedSessionCount++;
|
|
1793
|
+
}
|
|
1794
|
+
if (clearOrphanAgents) {
|
|
1795
|
+
for (const project of targetProjects) {
|
|
1796
|
+
const result = yield* deleteOrphanAgents(project.name);
|
|
1797
|
+
deletedOrphanAgentCount += result.count;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (clearOrphanTodos) {
|
|
1801
|
+
const result = yield* deleteOrphanTodos();
|
|
1802
|
+
deletedOrphanTodoCount = result.deletedCount;
|
|
1803
|
+
}
|
|
1804
|
+
return {
|
|
1805
|
+
success: true,
|
|
1806
|
+
deletedCount: deletedSessionCount,
|
|
1807
|
+
removedMessageCount,
|
|
1808
|
+
deletedOrphanAgentCount,
|
|
1809
|
+
deletedOrphanTodoCount
|
|
1810
|
+
};
|
|
1811
|
+
});
|
|
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* () {
|
|
1818
|
+
const { projectName, searchContent = false } = options;
|
|
1819
|
+
const results = [];
|
|
1820
|
+
const queryLower = query.toLowerCase();
|
|
1821
|
+
const projects = yield* listProjects;
|
|
1822
|
+
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1823
|
+
for (const project of targetProjects) {
|
|
1824
|
+
const sessions = yield* listSessions(project.name);
|
|
1825
|
+
for (const session of sessions) {
|
|
1826
|
+
const titleLower = (session.title ?? "").toLowerCase();
|
|
1827
|
+
if (titleLower.includes(queryLower)) {
|
|
1828
|
+
results.push({
|
|
1829
|
+
sessionId: session.id,
|
|
1830
|
+
projectName: project.name,
|
|
1831
|
+
title: session.title ?? "Untitled",
|
|
1832
|
+
matchType: "title",
|
|
1833
|
+
timestamp: session.updatedAt
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
if (searchContent) {
|
|
1839
|
+
for (const project of targetProjects) {
|
|
1840
|
+
const projectPath = path9.join(getSessionsDir(), project.name);
|
|
1841
|
+
const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
|
|
1842
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1843
|
+
for (const file of sessionFiles) {
|
|
1844
|
+
const sessionId = file.replace(".jsonl", "");
|
|
1845
|
+
if (results.some((r) => r.sessionId === sessionId && r.projectName === project.name)) {
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
const filePath = path9.join(projectPath, file);
|
|
1849
|
+
const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
|
|
1850
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1851
|
+
for (const line of lines) {
|
|
1852
|
+
try {
|
|
1853
|
+
const msg = JSON.parse(line);
|
|
1854
|
+
if (msg.type !== "user" && msg.type !== "assistant") continue;
|
|
1855
|
+
const text = extractTextContent(msg.message);
|
|
1856
|
+
const textLower = text.toLowerCase();
|
|
1857
|
+
if (textLower.includes(queryLower)) {
|
|
1858
|
+
const matchIndex = textLower.indexOf(queryLower);
|
|
1859
|
+
const start = Math.max(0, matchIndex - 50);
|
|
1860
|
+
const end = Math.min(text.length, matchIndex + query.length + 50);
|
|
1861
|
+
const snippet = (start > 0 ? "..." : "") + text.slice(start, end).trim() + (end < text.length ? "..." : "");
|
|
1862
|
+
results.push({
|
|
1863
|
+
sessionId,
|
|
1864
|
+
projectName: project.name,
|
|
1865
|
+
title: extractTitle(extractTextContent(msg.message)) || `Session ${sessionId.slice(0, 8)}`,
|
|
1866
|
+
matchType: "content",
|
|
1867
|
+
snippet,
|
|
1868
|
+
messageUuid: msg.uuid,
|
|
1869
|
+
timestamp: msg.timestamp
|
|
1870
|
+
});
|
|
1871
|
+
break;
|
|
1872
|
+
}
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
return results.sort((a, b) => {
|
|
1880
|
+
const dateA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
|
|
1881
|
+
const dateB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
|
|
1882
|
+
return dateB - dateA;
|
|
1883
|
+
});
|
|
1884
|
+
});
|
|
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
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
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
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
return {
|
|
1934
|
+
sessionId,
|
|
1935
|
+
projectName,
|
|
1936
|
+
files: fileChanges,
|
|
1937
|
+
totalChanges: fileChanges.length
|
|
1938
|
+
};
|
|
1939
|
+
});
|
|
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 {
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
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;
|
|
1973
|
+
});
|
|
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));
|
|
1979
|
+
return true;
|
|
1980
|
+
} catch {
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1830
1984
|
export {
|
|
1831
1985
|
analyzeSession,
|
|
1832
1986
|
clearSessions,
|
|
@@ -1849,11 +2003,14 @@ export {
|
|
|
1849
2003
|
folderNameToDisplayPath,
|
|
1850
2004
|
folderNameToPath,
|
|
1851
2005
|
getDisplayTitle,
|
|
2006
|
+
getIndexEntryDisplayTitle,
|
|
1852
2007
|
getLogger,
|
|
1853
2008
|
getRealPathFromSession,
|
|
1854
2009
|
getSessionFiles,
|
|
2010
|
+
getSessionSortTimestamp,
|
|
1855
2011
|
getSessionsDir,
|
|
1856
2012
|
getTodosDir,
|
|
2013
|
+
hasSessionsIndex,
|
|
1857
2014
|
isContinuationSummary,
|
|
1858
2015
|
isInvalidApiKeyMessage,
|
|
1859
2016
|
listProjects,
|
|
@@ -1861,6 +2018,7 @@ export {
|
|
|
1861
2018
|
loadAgentMessages,
|
|
1862
2019
|
loadProjectTreeData,
|
|
1863
2020
|
loadSessionTreeData,
|
|
2021
|
+
loadSessionsIndex,
|
|
1864
2022
|
maskHomePath,
|
|
1865
2023
|
moveSession,
|
|
1866
2024
|
pathToFolderName,
|
|
@@ -1871,6 +2029,7 @@ export {
|
|
|
1871
2029
|
searchSessions,
|
|
1872
2030
|
sessionHasTodos,
|
|
1873
2031
|
setLogger,
|
|
2032
|
+
sortIndexEntriesByModified,
|
|
1874
2033
|
sortProjects,
|
|
1875
2034
|
splitSession,
|
|
1876
2035
|
summarizeSession,
|