@claude-sessions/core 0.3.7 → 0.4.1
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 +189 -113
- package/dist/index.js +992 -660
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-Cz8chaYQ.d.ts → types-DZsLFIFK.d.ts} +47 -2
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -178,14 +178,19 @@ var extractTextContent = (message) => {
|
|
|
178
178
|
}
|
|
179
179
|
return "";
|
|
180
180
|
};
|
|
181
|
+
var parseCommandMessage = (content) => {
|
|
182
|
+
const name = content?.match(/<command-name>([^<]+)<\/command-name>/)?.[1] ?? "";
|
|
183
|
+
const message = content?.match(/<command-message>([^<]+)<\/command-message>/)?.[1] ?? "";
|
|
184
|
+
return { name, message };
|
|
185
|
+
};
|
|
181
186
|
var extractTitle = (text) => {
|
|
182
187
|
if (!text) return "Untitled";
|
|
188
|
+
const { name } = parseCommandMessage(text);
|
|
189
|
+
if (name) return name;
|
|
183
190
|
let cleaned = text.replace(/<ide_[^>]*>[\s\S]*?<\/ide_[^>]*>/g, "").trim();
|
|
184
191
|
if (!cleaned) return "Untitled";
|
|
185
192
|
if (cleaned.includes("\n\n")) {
|
|
186
193
|
cleaned = cleaned.split("\n\n")[0];
|
|
187
|
-
} else if (cleaned.includes("\n")) {
|
|
188
|
-
cleaned = cleaned.split("\n")[0];
|
|
189
194
|
}
|
|
190
195
|
if (cleaned.length > 100) {
|
|
191
196
|
return cleaned.slice(0, 100) + "...";
|
|
@@ -218,7 +223,13 @@ var getDisplayTitle = (customTitle, currentSummary, title, maxLength = 60, fallb
|
|
|
218
223
|
if (currentSummary) {
|
|
219
224
|
return currentSummary.length > maxLength ? currentSummary.slice(0, maxLength - 3) + "..." : currentSummary;
|
|
220
225
|
}
|
|
221
|
-
if (title && title !== "Untitled")
|
|
226
|
+
if (title && title !== "Untitled") {
|
|
227
|
+
if (title.includes("<command-name>")) {
|
|
228
|
+
const { name } = parseCommandMessage(title);
|
|
229
|
+
if (name) return name;
|
|
230
|
+
}
|
|
231
|
+
return title;
|
|
232
|
+
}
|
|
222
233
|
return fallback;
|
|
223
234
|
};
|
|
224
235
|
var replaceMessageContent = (msg, text) => ({
|
|
@@ -272,6 +283,10 @@ var sortProjects = (projects, options = {}) => {
|
|
|
272
283
|
return a.displayName.localeCompare(b.displayName);
|
|
273
284
|
});
|
|
274
285
|
};
|
|
286
|
+
var getSessionSortTimestamp = (session) => {
|
|
287
|
+
const timestampStr = session.summaries?.[0]?.timestamp ?? session.createdAt;
|
|
288
|
+
return timestampStr ? new Date(timestampStr).getTime() : 0;
|
|
289
|
+
};
|
|
275
290
|
|
|
276
291
|
// src/agents.ts
|
|
277
292
|
import { Effect } from "effect";
|
|
@@ -334,8 +349,8 @@ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
|
|
|
334
349
|
}
|
|
335
350
|
for (const entry of files) {
|
|
336
351
|
const entryPath = path2.join(projectPath, entry);
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
352
|
+
const stat4 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
|
|
353
|
+
if (stat4?.isDirectory() && !entry.startsWith(".")) {
|
|
339
354
|
const subagentsPath = path2.join(entryPath, "subagents");
|
|
340
355
|
const subagentsExists = yield* Effect.tryPromise(
|
|
341
356
|
() => fs2.stat(subagentsPath).then(() => true).catch(() => false)
|
|
@@ -449,12 +464,7 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
|
|
|
449
464
|
() => fs3.access(todosDir).then(() => true).catch(() => false)
|
|
450
465
|
);
|
|
451
466
|
if (!exists) {
|
|
452
|
-
return
|
|
453
|
-
sessionId,
|
|
454
|
-
sessionTodos: [],
|
|
455
|
-
agentTodos: [],
|
|
456
|
-
hasTodos: false
|
|
457
|
-
};
|
|
467
|
+
return void 0;
|
|
458
468
|
}
|
|
459
469
|
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
460
470
|
let sessionTodos = [];
|
|
@@ -635,8 +645,8 @@ var deleteOrphanTodos = () => Effect2.gen(function* () {
|
|
|
635
645
|
return { success: true, deletedCount };
|
|
636
646
|
});
|
|
637
647
|
|
|
638
|
-
// src/session.ts
|
|
639
|
-
import { Effect as Effect3
|
|
648
|
+
// src/session/projects.ts
|
|
649
|
+
import { Effect as Effect3 } from "effect";
|
|
640
650
|
import * as fs4 from "fs/promises";
|
|
641
651
|
import * as path4 from "path";
|
|
642
652
|
var listProjects = Effect3.gen(function* () {
|
|
@@ -666,15 +676,213 @@ var listProjects = Effect3.gen(function* () {
|
|
|
666
676
|
);
|
|
667
677
|
return projects;
|
|
668
678
|
});
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
679
|
+
|
|
680
|
+
// src/session/crud.ts
|
|
681
|
+
import { Effect as Effect4, pipe, Array as A, Option as O } from "effect";
|
|
682
|
+
import * as fs5 from "fs/promises";
|
|
683
|
+
import * as path5 from "path";
|
|
684
|
+
import * as crypto from "crypto";
|
|
685
|
+
|
|
686
|
+
// src/session/validation.ts
|
|
687
|
+
function validateChain(messages) {
|
|
688
|
+
const errors = [];
|
|
689
|
+
const uuids = /* @__PURE__ */ new Set();
|
|
690
|
+
for (const msg of messages) {
|
|
691
|
+
if (msg.uuid) {
|
|
692
|
+
uuids.add(msg.uuid);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
let foundFirstMessage = false;
|
|
696
|
+
for (let i = 0; i < messages.length; i++) {
|
|
697
|
+
const msg = messages[i];
|
|
698
|
+
if (msg.type === "file-history-snapshot") {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (!msg.uuid) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (!foundFirstMessage) {
|
|
705
|
+
foundFirstMessage = true;
|
|
706
|
+
if (msg.parentUuid === null) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (msg.parentUuid === void 0) {
|
|
710
|
+
errors.push({
|
|
711
|
+
type: "broken_chain",
|
|
712
|
+
uuid: msg.uuid,
|
|
713
|
+
line: i + 1,
|
|
714
|
+
parentUuid: null
|
|
715
|
+
});
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (msg.parentUuid === null || msg.parentUuid === void 0) {
|
|
720
|
+
errors.push({
|
|
721
|
+
type: "broken_chain",
|
|
722
|
+
uuid: msg.uuid,
|
|
723
|
+
line: i + 1,
|
|
724
|
+
parentUuid: null
|
|
725
|
+
});
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (!uuids.has(msg.parentUuid)) {
|
|
729
|
+
errors.push({
|
|
730
|
+
type: "orphan_parent",
|
|
731
|
+
uuid: msg.uuid,
|
|
732
|
+
line: i + 1,
|
|
733
|
+
parentUuid: msg.parentUuid
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
valid: errors.length === 0,
|
|
739
|
+
errors
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function validateToolUseResult(messages) {
|
|
743
|
+
const errors = [];
|
|
744
|
+
const toolUseIds = /* @__PURE__ */ new Set();
|
|
745
|
+
for (const msg of messages) {
|
|
746
|
+
const content = msg.message?.content;
|
|
747
|
+
if (Array.isArray(content)) {
|
|
748
|
+
for (const item of content) {
|
|
749
|
+
if (item.type === "tool_use" && item.id) {
|
|
750
|
+
toolUseIds.add(item.id);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
for (let i = 0; i < messages.length; i++) {
|
|
756
|
+
const msg = messages[i];
|
|
757
|
+
const content = msg.message?.content;
|
|
758
|
+
if (!Array.isArray(content)) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
for (const item of content) {
|
|
762
|
+
if (item.type === "tool_result" && item.tool_use_id) {
|
|
763
|
+
if (!toolUseIds.has(item.tool_use_id)) {
|
|
764
|
+
errors.push({
|
|
765
|
+
type: "orphan_tool_result",
|
|
766
|
+
uuid: msg.uuid || "",
|
|
767
|
+
line: i + 1,
|
|
768
|
+
toolUseId: item.tool_use_id
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
valid: errors.length === 0,
|
|
776
|
+
errors
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function deleteMessageWithChainRepair(messages, targetId, targetType) {
|
|
780
|
+
let targetIndex = -1;
|
|
781
|
+
if (targetType === "file-history-snapshot") {
|
|
782
|
+
targetIndex = messages.findIndex(
|
|
783
|
+
(m) => m.type === "file-history-snapshot" && m.messageId === targetId
|
|
784
|
+
);
|
|
785
|
+
} else if (targetType === "summary") {
|
|
786
|
+
targetIndex = messages.findIndex(
|
|
787
|
+
(m) => m.leafUuid === targetId
|
|
788
|
+
);
|
|
789
|
+
} else {
|
|
790
|
+
targetIndex = messages.findIndex((m) => m.uuid === targetId);
|
|
791
|
+
if (targetIndex === -1) {
|
|
792
|
+
targetIndex = messages.findIndex(
|
|
793
|
+
(m) => m.leafUuid === targetId
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
if (targetIndex === -1) {
|
|
797
|
+
targetIndex = messages.findIndex(
|
|
798
|
+
(m) => m.type === "file-history-snapshot" && m.messageId === targetId
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (targetIndex === -1) {
|
|
803
|
+
return { deleted: null, alsoDeleted: [] };
|
|
804
|
+
}
|
|
805
|
+
const deletedMsg = messages[targetIndex];
|
|
806
|
+
const toolUseIds = [];
|
|
807
|
+
if (deletedMsg.type === "assistant") {
|
|
808
|
+
const content = deletedMsg.message?.content;
|
|
809
|
+
if (Array.isArray(content)) {
|
|
810
|
+
for (const item of content) {
|
|
811
|
+
if (item.type === "tool_use" && item.id) {
|
|
812
|
+
toolUseIds.push(item.id);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const toolResultIndices = [];
|
|
818
|
+
if (toolUseIds.length > 0) {
|
|
819
|
+
for (let i = 0; i < messages.length; i++) {
|
|
820
|
+
const msg = messages[i];
|
|
821
|
+
if (msg.type === "user") {
|
|
822
|
+
const content = msg.message?.content;
|
|
823
|
+
if (Array.isArray(content)) {
|
|
824
|
+
for (const item of content) {
|
|
825
|
+
if (item.type === "tool_result" && item.tool_use_id && toolUseIds.includes(item.tool_use_id)) {
|
|
826
|
+
toolResultIndices.push(i);
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const indicesToDelete = [targetIndex, ...toolResultIndices].sort((a, b) => b - a);
|
|
835
|
+
for (const idx of indicesToDelete) {
|
|
836
|
+
const msg = messages[idx];
|
|
837
|
+
const isInParentChain = msg.type !== "file-history-snapshot" && msg.uuid;
|
|
838
|
+
if (isInParentChain) {
|
|
839
|
+
const deletedUuid = msg.uuid;
|
|
840
|
+
const parentUuid = msg.parentUuid;
|
|
841
|
+
for (const m of messages) {
|
|
842
|
+
if (m.parentUuid === deletedUuid) {
|
|
843
|
+
m.parentUuid = parentUuid;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const alsoDeleted = toolResultIndices.map((i) => messages[i]);
|
|
849
|
+
for (const idx of indicesToDelete) {
|
|
850
|
+
messages.splice(idx, 1);
|
|
851
|
+
}
|
|
852
|
+
return { deleted: deletedMsg, alsoDeleted };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/session/crud.ts
|
|
856
|
+
var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(function* () {
|
|
857
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
858
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
859
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
860
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
861
|
+
const summaryIdx = messages.findIndex((m) => m.type === "summary");
|
|
862
|
+
if (summaryIdx >= 0) {
|
|
863
|
+
messages[summaryIdx] = { ...messages[summaryIdx], summary: newSummary };
|
|
864
|
+
} else {
|
|
865
|
+
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
866
|
+
const summaryMsg = {
|
|
867
|
+
type: "summary",
|
|
868
|
+
summary: newSummary,
|
|
869
|
+
leafUuid: firstUserMsg?.uuid ?? null
|
|
870
|
+
};
|
|
871
|
+
messages.unshift(summaryMsg);
|
|
872
|
+
}
|
|
873
|
+
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
874
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
875
|
+
return { success: true };
|
|
876
|
+
});
|
|
877
|
+
var listSessions = (projectName) => Effect4.gen(function* () {
|
|
878
|
+
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
879
|
+
const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
672
880
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
673
|
-
const sessions = yield*
|
|
881
|
+
const sessions = yield* Effect4.all(
|
|
674
882
|
sessionFiles.map(
|
|
675
|
-
(file) =>
|
|
676
|
-
const filePath =
|
|
677
|
-
const content = yield*
|
|
883
|
+
(file) => Effect4.gen(function* () {
|
|
884
|
+
const filePath = path5.join(projectPath, file);
|
|
885
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
678
886
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
679
887
|
const messages = lines.map((line) => JSON.parse(line));
|
|
680
888
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -693,10 +901,25 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
693
901
|
}),
|
|
694
902
|
O.getOrElse(() => hasSummary ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`)
|
|
695
903
|
);
|
|
904
|
+
const currentSummary = pipe(
|
|
905
|
+
messages,
|
|
906
|
+
A.findFirst((m) => m.type === "summary"),
|
|
907
|
+
O.map((m) => m.summary),
|
|
908
|
+
O.getOrUndefined
|
|
909
|
+
);
|
|
910
|
+
const customTitle = pipe(
|
|
911
|
+
messages,
|
|
912
|
+
A.findFirst((m) => m.type === "custom-title"),
|
|
913
|
+
O.map((m) => m.customTitle),
|
|
914
|
+
O.flatMap(O.fromNullable),
|
|
915
|
+
O.getOrUndefined
|
|
916
|
+
);
|
|
696
917
|
return {
|
|
697
918
|
id: sessionId,
|
|
698
919
|
projectName,
|
|
699
920
|
title,
|
|
921
|
+
customTitle,
|
|
922
|
+
currentSummary,
|
|
700
923
|
// If session has summary but no user/assistant messages, count as 1
|
|
701
924
|
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,
|
|
702
925
|
createdAt: firstMessage?.timestamp,
|
|
@@ -712,39 +935,28 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
712
935
|
return dateB - dateA;
|
|
713
936
|
});
|
|
714
937
|
});
|
|
715
|
-
var readSession = (projectName, sessionId) =>
|
|
716
|
-
const filePath =
|
|
717
|
-
const content = yield*
|
|
938
|
+
var readSession = (projectName, sessionId) => Effect4.gen(function* () {
|
|
939
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
940
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
718
941
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
719
942
|
return lines.map((line) => JSON.parse(line));
|
|
720
943
|
});
|
|
721
|
-
var deleteMessage = (projectName, sessionId, messageUuid) =>
|
|
722
|
-
const filePath =
|
|
723
|
-
const content = yield*
|
|
944
|
+
var deleteMessage = (projectName, sessionId, messageUuid, targetType) => Effect4.gen(function* () {
|
|
945
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
946
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
724
947
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
725
948
|
const messages = lines.map((line) => JSON.parse(line));
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
);
|
|
729
|
-
if (targetIndex === -1) {
|
|
949
|
+
const result = deleteMessageWithChainRepair(messages, messageUuid, targetType);
|
|
950
|
+
if (!result.deleted) {
|
|
730
951
|
return { success: false, error: "Message not found" };
|
|
731
952
|
}
|
|
732
|
-
const deletedMsg = messages[targetIndex];
|
|
733
|
-
const deletedUuid = deletedMsg?.uuid ?? deletedMsg?.messageId;
|
|
734
|
-
const parentUuid = deletedMsg?.parentUuid;
|
|
735
|
-
for (const msg of messages) {
|
|
736
|
-
if (msg.parentUuid === deletedUuid) {
|
|
737
|
-
msg.parentUuid = parentUuid;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
messages.splice(targetIndex, 1);
|
|
741
953
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
742
|
-
yield*
|
|
743
|
-
return { success: true, deletedMessage:
|
|
954
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
955
|
+
return { success: true, deletedMessage: result.deleted };
|
|
744
956
|
});
|
|
745
|
-
var restoreMessage = (projectName, sessionId, message, index) =>
|
|
746
|
-
const filePath =
|
|
747
|
-
const content = yield*
|
|
957
|
+
var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(function* () {
|
|
958
|
+
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
959
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
748
960
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
749
961
|
const messages = lines.map((line) => JSON.parse(line));
|
|
750
962
|
const msgUuid = message.uuid ?? message.messageId;
|
|
@@ -761,41 +973,41 @@ var restoreMessage = (projectName, sessionId, message, index) => Effect3.gen(fun
|
|
|
761
973
|
const insertIndex = Math.min(index, messages.length);
|
|
762
974
|
messages.splice(insertIndex, 0, message);
|
|
763
975
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
764
|
-
yield*
|
|
976
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
765
977
|
return { success: true };
|
|
766
978
|
});
|
|
767
|
-
var deleteSession = (projectName, sessionId) =>
|
|
979
|
+
var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
|
|
768
980
|
const sessionsDir = getSessionsDir();
|
|
769
|
-
const projectPath =
|
|
770
|
-
const filePath =
|
|
981
|
+
const projectPath = path5.join(sessionsDir, projectName);
|
|
982
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
771
983
|
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
772
|
-
const
|
|
773
|
-
if (
|
|
774
|
-
yield*
|
|
775
|
-
const agentBackupDir2 =
|
|
776
|
-
yield*
|
|
984
|
+
const stat4 = yield* Effect4.tryPromise(() => fs5.stat(filePath));
|
|
985
|
+
if (stat4.size === 0) {
|
|
986
|
+
yield* Effect4.tryPromise(() => fs5.unlink(filePath));
|
|
987
|
+
const agentBackupDir2 = path5.join(projectPath, ".bak");
|
|
988
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir2, { recursive: true }));
|
|
777
989
|
for (const agentId of linkedAgents) {
|
|
778
|
-
const agentPath =
|
|
779
|
-
const agentBackupPath =
|
|
780
|
-
yield*
|
|
990
|
+
const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
|
|
991
|
+
const agentBackupPath = path5.join(agentBackupDir2, `${agentId}.jsonl`);
|
|
992
|
+
yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
|
|
781
993
|
}));
|
|
782
994
|
}
|
|
783
995
|
yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
784
996
|
return { success: true, deletedAgents: linkedAgents.length };
|
|
785
997
|
}
|
|
786
|
-
const backupDir =
|
|
787
|
-
yield*
|
|
788
|
-
const agentBackupDir =
|
|
789
|
-
yield*
|
|
998
|
+
const backupDir = path5.join(sessionsDir, ".bak");
|
|
999
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(backupDir, { recursive: true }));
|
|
1000
|
+
const agentBackupDir = path5.join(projectPath, ".bak");
|
|
1001
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir, { recursive: true }));
|
|
790
1002
|
for (const agentId of linkedAgents) {
|
|
791
|
-
const agentPath =
|
|
792
|
-
const agentBackupPath =
|
|
793
|
-
yield*
|
|
1003
|
+
const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
|
|
1004
|
+
const agentBackupPath = path5.join(agentBackupDir, `${agentId}.jsonl`);
|
|
1005
|
+
yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
|
|
794
1006
|
}));
|
|
795
1007
|
}
|
|
796
1008
|
const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
797
|
-
const backupPath =
|
|
798
|
-
yield*
|
|
1009
|
+
const backupPath = path5.join(backupDir, `${projectName}_${sessionId}.jsonl`);
|
|
1010
|
+
yield* Effect4.tryPromise(() => fs5.rename(filePath, backupPath));
|
|
799
1011
|
return {
|
|
800
1012
|
success: true,
|
|
801
1013
|
backupPath,
|
|
@@ -803,10 +1015,10 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
803
1015
|
deletedTodos: todosResult.deletedCount
|
|
804
1016
|
};
|
|
805
1017
|
});
|
|
806
|
-
var renameSession = (projectName, sessionId, newTitle) =>
|
|
807
|
-
const projectPath =
|
|
808
|
-
const filePath =
|
|
809
|
-
const content = yield*
|
|
1018
|
+
var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function* () {
|
|
1019
|
+
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
1020
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
1021
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
810
1022
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
811
1023
|
if (lines.length === 0) {
|
|
812
1024
|
return { success: false, error: "Empty session" };
|
|
@@ -830,14 +1042,14 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
830
1042
|
messages.unshift(customTitleRecord);
|
|
831
1043
|
}
|
|
832
1044
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
833
|
-
yield*
|
|
834
|
-
const projectFiles = yield*
|
|
1045
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
1046
|
+
const projectFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
835
1047
|
const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
836
1048
|
const summariesTargetingThis = [];
|
|
837
1049
|
for (const file of allJsonlFiles) {
|
|
838
|
-
const otherFilePath =
|
|
1050
|
+
const otherFilePath = path5.join(projectPath, file);
|
|
839
1051
|
try {
|
|
840
|
-
const otherContent = yield*
|
|
1052
|
+
const otherContent = yield* Effect4.tryPromise(() => fs5.readFile(otherFilePath, "utf-8"));
|
|
841
1053
|
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
842
1054
|
const otherMessages = otherLines.map((l) => JSON.parse(l));
|
|
843
1055
|
for (let i = 0; i < otherMessages.length; i++) {
|
|
@@ -857,8 +1069,8 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
857
1069
|
if (summariesTargetingThis.length > 0) {
|
|
858
1070
|
summariesTargetingThis.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
|
|
859
1071
|
const firstSummary = summariesTargetingThis[0];
|
|
860
|
-
const summaryFilePath =
|
|
861
|
-
const summaryContent = yield*
|
|
1072
|
+
const summaryFilePath = path5.join(projectPath, firstSummary.file);
|
|
1073
|
+
const summaryContent = yield* Effect4.tryPromise(() => fs5.readFile(summaryFilePath, "utf-8"));
|
|
862
1074
|
const summaryLines = summaryContent.trim().split("\n").filter(Boolean);
|
|
863
1075
|
const summaryMessages = summaryLines.map((l) => JSON.parse(l));
|
|
864
1076
|
summaryMessages[firstSummary.idx] = {
|
|
@@ -866,9 +1078,9 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
866
1078
|
summary: newTitle
|
|
867
1079
|
};
|
|
868
1080
|
const newSummaryContent = summaryMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
869
|
-
yield*
|
|
1081
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
|
|
870
1082
|
} else {
|
|
871
|
-
const currentContent = yield*
|
|
1083
|
+
const currentContent = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
872
1084
|
const currentLines = currentContent.trim().split("\n").filter(Boolean);
|
|
873
1085
|
const currentMessages = currentLines.map((l) => JSON.parse(l));
|
|
874
1086
|
const firstUserIdx = currentMessages.findIndex((m) => m.type === "user");
|
|
@@ -887,88 +1099,433 @@ var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function*
|
|
|
887
1099
|
|
|
888
1100
|
${cleanedText}`;
|
|
889
1101
|
const updatedContent = currentMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
890
|
-
yield*
|
|
1102
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, updatedContent, "utf-8"));
|
|
891
1103
|
}
|
|
892
1104
|
}
|
|
893
1105
|
}
|
|
894
1106
|
}
|
|
895
1107
|
return { success: true };
|
|
896
1108
|
});
|
|
897
|
-
var
|
|
898
|
-
const
|
|
899
|
-
const
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1109
|
+
var moveSession = (sourceProject, sessionId, targetProject) => Effect4.gen(function* () {
|
|
1110
|
+
const sessionsDir = getSessionsDir();
|
|
1111
|
+
const sourcePath = path5.join(sessionsDir, sourceProject);
|
|
1112
|
+
const targetPath = path5.join(sessionsDir, targetProject);
|
|
1113
|
+
const sourceFile = path5.join(sourcePath, `${sessionId}.jsonl`);
|
|
1114
|
+
const targetFile = path5.join(targetPath, `${sessionId}.jsonl`);
|
|
1115
|
+
const sourceExists = yield* Effect4.tryPromise(
|
|
1116
|
+
() => fs5.access(sourceFile).then(() => true).catch(() => false)
|
|
1117
|
+
);
|
|
1118
|
+
if (!sourceExists) {
|
|
1119
|
+
return { success: false, error: "Source session not found" };
|
|
1120
|
+
}
|
|
1121
|
+
const targetExists = yield* Effect4.tryPromise(
|
|
1122
|
+
() => fs5.access(targetFile).then(() => true).catch(() => false)
|
|
1123
|
+
);
|
|
1124
|
+
if (targetExists) {
|
|
1125
|
+
return { success: false, error: "Session already exists in target project" };
|
|
1126
|
+
}
|
|
1127
|
+
yield* Effect4.tryPromise(() => fs5.mkdir(targetPath, { recursive: true }));
|
|
1128
|
+
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
1129
|
+
yield* Effect4.tryPromise(() => fs5.rename(sourceFile, targetFile));
|
|
1130
|
+
for (const agentId of linkedAgents) {
|
|
1131
|
+
const sourceAgentFile = path5.join(sourcePath, `${agentId}.jsonl`);
|
|
1132
|
+
const targetAgentFile = path5.join(targetPath, `${agentId}.jsonl`);
|
|
1133
|
+
const agentExists = yield* Effect4.tryPromise(
|
|
1134
|
+
() => fs5.access(sourceAgentFile).then(() => true).catch(() => false)
|
|
1135
|
+
);
|
|
1136
|
+
if (agentExists) {
|
|
1137
|
+
yield* Effect4.tryPromise(() => fs5.rename(sourceAgentFile, targetAgentFile));
|
|
918
1138
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1139
|
+
}
|
|
1140
|
+
return { success: true };
|
|
1141
|
+
});
|
|
1142
|
+
var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(function* () {
|
|
1143
|
+
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
1144
|
+
const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
|
|
1145
|
+
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
1146
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1147
|
+
const allMessages = lines.map((line) => JSON.parse(line));
|
|
1148
|
+
const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
|
|
1149
|
+
if (splitIndex === -1) {
|
|
1150
|
+
return { success: false, error: "Message not found" };
|
|
1151
|
+
}
|
|
1152
|
+
if (splitIndex === 0) {
|
|
1153
|
+
return { success: false, error: "Cannot split at first message" };
|
|
1154
|
+
}
|
|
1155
|
+
const newSessionId = crypto.randomUUID();
|
|
1156
|
+
const summaryMessages = allMessages.filter((m) => m.type === "summary");
|
|
1157
|
+
const summaryMessage = summaryMessages.length > 0 ? summaryMessages[summaryMessages.length - 1] : null;
|
|
1158
|
+
const splitMessage = allMessages[splitIndex];
|
|
1159
|
+
const shouldDuplicate = isContinuationSummary(splitMessage);
|
|
1160
|
+
let keptMessages = allMessages.slice(splitIndex);
|
|
1161
|
+
let movedMessages;
|
|
1162
|
+
if (shouldDuplicate) {
|
|
1163
|
+
const duplicatedMessage = {
|
|
1164
|
+
...splitMessage,
|
|
1165
|
+
uuid: crypto.randomUUID(),
|
|
1166
|
+
sessionId: newSessionId
|
|
1167
|
+
};
|
|
1168
|
+
movedMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage];
|
|
1169
|
+
} else {
|
|
1170
|
+
movedMessages = allMessages.slice(0, splitIndex);
|
|
1171
|
+
}
|
|
1172
|
+
keptMessages = keptMessages.map((msg, index) => {
|
|
1173
|
+
let updated = { ...msg };
|
|
1174
|
+
if (index === 0) {
|
|
1175
|
+
updated.parentUuid = null;
|
|
1176
|
+
updated = cleanupSplitFirstMessage(updated);
|
|
1177
|
+
}
|
|
1178
|
+
return updated;
|
|
1179
|
+
});
|
|
1180
|
+
const updatedMovedMessages = movedMessages.map((msg) => ({
|
|
1181
|
+
...msg,
|
|
1182
|
+
sessionId: newSessionId
|
|
1183
|
+
}));
|
|
1184
|
+
if (summaryMessage) {
|
|
1185
|
+
const clonedSummary = {
|
|
1186
|
+
...summaryMessage,
|
|
1187
|
+
sessionId: newSessionId,
|
|
1188
|
+
leafUuid: updatedMovedMessages[0]?.uuid ?? null
|
|
1189
|
+
};
|
|
1190
|
+
updatedMovedMessages.unshift(clonedSummary);
|
|
1191
|
+
}
|
|
1192
|
+
const keptContent = keptMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1193
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, keptContent, "utf-8"));
|
|
1194
|
+
const newFilePath = path5.join(projectPath, `${newSessionId}.jsonl`);
|
|
1195
|
+
const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1196
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(newFilePath, newContent, "utf-8"));
|
|
1197
|
+
const agentFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
|
|
1198
|
+
const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
1199
|
+
for (const agentFile of agentJsonlFiles) {
|
|
1200
|
+
const agentPath = path5.join(projectPath, agentFile);
|
|
1201
|
+
const agentContent = yield* Effect4.tryPromise(() => fs5.readFile(agentPath, "utf-8"));
|
|
1202
|
+
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1203
|
+
if (agentLines.length === 0) continue;
|
|
1204
|
+
const firstAgentMsg = JSON.parse(agentLines[0]);
|
|
1205
|
+
if (firstAgentMsg.sessionId === sessionId) {
|
|
1206
|
+
const agentId = agentFile.replace("agent-", "").replace(".jsonl", "");
|
|
1207
|
+
const isRelatedToMoved = movedMessages.some(
|
|
1208
|
+
(msg) => msg.agentId === agentId
|
|
1209
|
+
);
|
|
1210
|
+
if (isRelatedToMoved) {
|
|
1211
|
+
const updatedAgentMessages = agentLines.map((line) => {
|
|
1212
|
+
const msg = JSON.parse(line);
|
|
1213
|
+
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
1214
|
+
});
|
|
1215
|
+
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
1216
|
+
yield* Effect4.tryPromise(() => fs5.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
939
1217
|
}
|
|
940
1218
|
}
|
|
941
1219
|
}
|
|
942
1220
|
return {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1221
|
+
success: true,
|
|
1222
|
+
newSessionId,
|
|
1223
|
+
newSessionPath: newFilePath,
|
|
1224
|
+
movedMessageCount: movedMessages.length,
|
|
1225
|
+
duplicatedSummary: shouldDuplicate
|
|
947
1226
|
};
|
|
948
1227
|
});
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1228
|
+
|
|
1229
|
+
// src/session/tree.ts
|
|
1230
|
+
import { Effect as Effect5 } from "effect";
|
|
1231
|
+
import * as fs6 from "fs/promises";
|
|
1232
|
+
import * as path6 from "path";
|
|
1233
|
+
var sortSessions = (sessions, sort) => {
|
|
1234
|
+
return sessions.sort((a, b) => {
|
|
1235
|
+
let comparison = 0;
|
|
1236
|
+
switch (sort.field) {
|
|
1237
|
+
case "summary": {
|
|
1238
|
+
comparison = a.sortTimestamp - b.sortTimestamp;
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
case "modified": {
|
|
1242
|
+
comparison = (a.fileMtime ?? 0) - (b.fileMtime ?? 0);
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
case "created": {
|
|
1246
|
+
const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
|
1247
|
+
const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
1248
|
+
comparison = createdA - createdB;
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
case "updated": {
|
|
1252
|
+
const updatedA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
1253
|
+
const updatedB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
1254
|
+
comparison = updatedA - updatedB;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
case "messageCount": {
|
|
1258
|
+
comparison = a.messageCount - b.messageCount;
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
case "title": {
|
|
1262
|
+
const titleA = a.customTitle ?? a.currentSummary ?? a.title;
|
|
1263
|
+
const titleB = b.customTitle ?? b.currentSummary ?? b.title;
|
|
1264
|
+
comparison = titleA.localeCompare(titleB);
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return sort.order === "desc" ? -comparison : comparison;
|
|
1269
|
+
});
|
|
1270
|
+
};
|
|
1271
|
+
var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect5.gen(function* () {
|
|
1272
|
+
const projectPath = path6.join(getSessionsDir(), projectName);
|
|
1273
|
+
const filePath = path6.join(projectPath, `${sessionId}.jsonl`);
|
|
1274
|
+
const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
|
|
1275
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1276
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
1277
|
+
let summaries;
|
|
1278
|
+
if (summariesByTargetSession) {
|
|
1279
|
+
summaries = [...summariesByTargetSession.get(sessionId) ?? []].sort((a, b) => {
|
|
1280
|
+
const timestampCmp = (a.timestamp ?? "").localeCompare(b.timestamp ?? "");
|
|
1281
|
+
if (timestampCmp !== 0) return timestampCmp;
|
|
1282
|
+
return (b.sourceFile ?? "").localeCompare(a.sourceFile ?? "");
|
|
1283
|
+
});
|
|
1284
|
+
} else {
|
|
1285
|
+
summaries = [];
|
|
1286
|
+
const sessionUuids = /* @__PURE__ */ new Set();
|
|
1287
|
+
for (const msg of messages) {
|
|
1288
|
+
if (msg.uuid && typeof msg.uuid === "string") {
|
|
1289
|
+
sessionUuids.add(msg.uuid);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const projectFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
|
|
1293
|
+
const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
1294
|
+
for (const file of allJsonlFiles) {
|
|
1295
|
+
try {
|
|
1296
|
+
const otherFilePath = path6.join(projectPath, file);
|
|
1297
|
+
const otherContent = yield* Effect5.tryPromise(() => fs6.readFile(otherFilePath, "utf-8"));
|
|
1298
|
+
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
1299
|
+
for (const line of otherLines) {
|
|
1300
|
+
try {
|
|
1301
|
+
const msg = JSON.parse(line);
|
|
1302
|
+
if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
|
|
1303
|
+
const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
|
|
1304
|
+
summaries.push({
|
|
1305
|
+
summary: msg.summary,
|
|
1306
|
+
leafUuid: msg.leafUuid,
|
|
1307
|
+
timestamp: targetMsg?.timestamp ?? msg.timestamp,
|
|
1308
|
+
sourceFile: file
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
summaries.sort((a, b) => {
|
|
1319
|
+
const timestampCmp = (a.timestamp ?? "").localeCompare(b.timestamp ?? "");
|
|
1320
|
+
if (timestampCmp !== 0) return timestampCmp;
|
|
1321
|
+
return (b.sourceFile ?? "").localeCompare(a.sourceFile ?? "");
|
|
1322
|
+
});
|
|
1323
|
+
let lastCompactBoundaryUuid;
|
|
1324
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1325
|
+
const msg = messages[i];
|
|
1326
|
+
if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
1327
|
+
lastCompactBoundaryUuid = msg.uuid;
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
1332
|
+
const customTitleMsg = messages.find((m) => m.type === "custom-title");
|
|
1333
|
+
const customTitle = customTitleMsg?.customTitle;
|
|
1334
|
+
const title = firstUserMsg ? extractTitle(extractTextContent(firstUserMsg.message)) : summaries.length > 0 ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`;
|
|
1335
|
+
const userAssistantMessages = messages.filter(
|
|
1336
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
1337
|
+
);
|
|
1338
|
+
const firstMessage = userAssistantMessages[0];
|
|
1339
|
+
const lastMessage = userAssistantMessages[userAssistantMessages.length - 1];
|
|
1340
|
+
const linkedAgentIds = yield* findLinkedAgents(projectName, sessionId);
|
|
1341
|
+
const agents = [];
|
|
1342
|
+
for (const agentId of linkedAgentIds) {
|
|
1343
|
+
const agentPath = path6.join(projectPath, `${agentId}.jsonl`);
|
|
1344
|
+
try {
|
|
1345
|
+
const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
|
|
1346
|
+
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1347
|
+
const agentMsgs = agentLines.map((l) => JSON.parse(l));
|
|
1348
|
+
const agentUserAssistant = agentMsgs.filter(
|
|
1349
|
+
(m) => m.type === "user" || m.type === "assistant"
|
|
1350
|
+
);
|
|
1351
|
+
let agentName;
|
|
1352
|
+
const firstAgentMsg = agentMsgs.find((m) => m.type === "user");
|
|
1353
|
+
if (firstAgentMsg) {
|
|
1354
|
+
const text = extractTextContent(firstAgentMsg.message);
|
|
1355
|
+
if (text) {
|
|
1356
|
+
agentName = extractTitle(text);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
agents.push({
|
|
1360
|
+
id: agentId,
|
|
1361
|
+
name: agentName,
|
|
1362
|
+
messageCount: agentUserAssistant.length
|
|
1363
|
+
});
|
|
1364
|
+
} catch {
|
|
1365
|
+
agents.push({
|
|
1366
|
+
id: agentId,
|
|
1367
|
+
messageCount: 0
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const todos = yield* findLinkedTodos(sessionId, linkedAgentIds);
|
|
1372
|
+
const createdAt = firstMessage?.timestamp ?? void 0;
|
|
1373
|
+
const sortTimestamp = getSessionSortTimestamp({ summaries, createdAt });
|
|
1374
|
+
return {
|
|
1375
|
+
id: sessionId,
|
|
1376
|
+
projectName,
|
|
1377
|
+
title,
|
|
1378
|
+
customTitle,
|
|
1379
|
+
currentSummary: summaries[0]?.summary,
|
|
1380
|
+
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
|
|
1381
|
+
createdAt,
|
|
1382
|
+
updatedAt: lastMessage?.timestamp ?? void 0,
|
|
1383
|
+
fileMtime,
|
|
1384
|
+
sortTimestamp,
|
|
1385
|
+
summaries,
|
|
1386
|
+
agents,
|
|
1387
|
+
todos,
|
|
1388
|
+
lastCompactBoundaryUuid
|
|
1389
|
+
};
|
|
1390
|
+
});
|
|
1391
|
+
var loadSessionTreeData = (projectName, sessionId) => loadSessionTreeDataInternal(projectName, sessionId, void 0);
|
|
1392
|
+
var DEFAULT_SORT = { field: "summary", order: "desc" };
|
|
1393
|
+
var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* () {
|
|
1394
|
+
const project = (yield* listProjects).find((p) => p.name === projectName);
|
|
1395
|
+
if (!project) {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
const sort = sortOptions ?? DEFAULT_SORT;
|
|
1399
|
+
const projectPath = path6.join(getSessionsDir(), projectName);
|
|
1400
|
+
const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
|
|
1401
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1402
|
+
const fileMtimes = /* @__PURE__ */ new Map();
|
|
1403
|
+
yield* Effect5.all(
|
|
1404
|
+
sessionFiles.map(
|
|
1405
|
+
(file) => Effect5.gen(function* () {
|
|
1406
|
+
const filePath = path6.join(projectPath, file);
|
|
1407
|
+
try {
|
|
1408
|
+
const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
|
|
1409
|
+
fileMtimes.set(file.replace(".jsonl", ""), stat4.mtimeMs);
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
})
|
|
1413
|
+
),
|
|
1414
|
+
{ concurrency: 20 }
|
|
1415
|
+
);
|
|
1416
|
+
const globalUuidMap = /* @__PURE__ */ new Map();
|
|
1417
|
+
const allSummaries = [];
|
|
1418
|
+
const allJsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
1419
|
+
yield* Effect5.all(
|
|
1420
|
+
allJsonlFiles.map(
|
|
1421
|
+
(file) => Effect5.gen(function* () {
|
|
1422
|
+
const filePath = path6.join(projectPath, file);
|
|
1423
|
+
const fileSessionId = file.replace(".jsonl", "");
|
|
1424
|
+
try {
|
|
1425
|
+
const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
|
|
1426
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1427
|
+
for (const line of lines) {
|
|
1428
|
+
try {
|
|
1429
|
+
const msg = JSON.parse(line);
|
|
1430
|
+
if (msg.uuid && typeof msg.uuid === "string") {
|
|
1431
|
+
globalUuidMap.set(msg.uuid, {
|
|
1432
|
+
sessionId: fileSessionId,
|
|
1433
|
+
timestamp: msg.timestamp
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (msg.messageId && typeof msg.messageId === "string") {
|
|
1437
|
+
globalUuidMap.set(msg.messageId, {
|
|
1438
|
+
sessionId: fileSessionId,
|
|
1439
|
+
timestamp: msg.snapshot?.timestamp
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
if (msg.type === "summary" && typeof msg.summary === "string") {
|
|
1443
|
+
allSummaries.push({
|
|
1444
|
+
summary: msg.summary,
|
|
1445
|
+
leafUuid: msg.leafUuid,
|
|
1446
|
+
timestamp: msg.timestamp,
|
|
1447
|
+
sourceFile: file
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
} catch {
|
|
1454
|
+
}
|
|
1455
|
+
})
|
|
1456
|
+
),
|
|
1457
|
+
{ concurrency: 20 }
|
|
1458
|
+
);
|
|
1459
|
+
const summariesByTargetSession = /* @__PURE__ */ new Map();
|
|
1460
|
+
for (const summaryData of allSummaries) {
|
|
1461
|
+
if (summaryData.leafUuid) {
|
|
1462
|
+
const targetInfo = globalUuidMap.get(summaryData.leafUuid);
|
|
1463
|
+
if (targetInfo) {
|
|
1464
|
+
const targetSessionId = targetInfo.sessionId;
|
|
1465
|
+
if (!summariesByTargetSession.has(targetSessionId)) {
|
|
1466
|
+
summariesByTargetSession.set(targetSessionId, []);
|
|
1467
|
+
}
|
|
1468
|
+
summariesByTargetSession.get(targetSessionId).push({
|
|
1469
|
+
summary: summaryData.summary,
|
|
1470
|
+
leafUuid: summaryData.leafUuid,
|
|
1471
|
+
// Use summary's own timestamp for sorting, not the target message's timestamp
|
|
1472
|
+
timestamp: summaryData.timestamp ?? targetInfo.timestamp,
|
|
1473
|
+
sourceFile: summaryData.sourceFile
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
const sessions = yield* Effect5.all(
|
|
1479
|
+
sessionFiles.map((file) => {
|
|
1480
|
+
const sessionId = file.replace(".jsonl", "");
|
|
1481
|
+
const mtime = fileMtimes.get(sessionId);
|
|
1482
|
+
return loadSessionTreeDataInternal(projectName, sessionId, summariesByTargetSession, mtime);
|
|
1483
|
+
}),
|
|
1484
|
+
{ concurrency: 10 }
|
|
1485
|
+
);
|
|
1486
|
+
const sortedSessions = sortSessions(sessions, sort);
|
|
1487
|
+
const filteredSessions = sortedSessions.filter((s) => {
|
|
1488
|
+
if (isErrorSessionTitle(s.title)) return false;
|
|
1489
|
+
if (isErrorSessionTitle(s.customTitle)) return false;
|
|
1490
|
+
if (isErrorSessionTitle(s.currentSummary)) return false;
|
|
1491
|
+
return true;
|
|
1492
|
+
});
|
|
1493
|
+
return {
|
|
1494
|
+
name: project.name,
|
|
1495
|
+
displayName: project.displayName,
|
|
1496
|
+
path: project.path,
|
|
1497
|
+
sessionCount: filteredSessions.length,
|
|
1498
|
+
sessions: filteredSessions
|
|
1499
|
+
};
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
// src/session/analysis.ts
|
|
1503
|
+
import { Effect as Effect6 } from "effect";
|
|
1504
|
+
import * as fs7 from "fs/promises";
|
|
1505
|
+
import * as path7 from "path";
|
|
1506
|
+
var analyzeSession = (projectName, sessionId) => Effect6.gen(function* () {
|
|
1507
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1508
|
+
let userMessages = 0;
|
|
1509
|
+
let assistantMessages = 0;
|
|
1510
|
+
let summaryCount = 0;
|
|
1511
|
+
let snapshotCount = 0;
|
|
1512
|
+
const toolUsageMap = /* @__PURE__ */ new Map();
|
|
1513
|
+
const filesChanged = /* @__PURE__ */ new Set();
|
|
1514
|
+
const patterns = [];
|
|
1515
|
+
const milestones = [];
|
|
1516
|
+
let firstTimestamp;
|
|
1517
|
+
let lastTimestamp;
|
|
1518
|
+
for (const msg of messages) {
|
|
1519
|
+
if (msg.timestamp) {
|
|
1520
|
+
if (!firstTimestamp) firstTimestamp = msg.timestamp;
|
|
1521
|
+
lastTimestamp = msg.timestamp;
|
|
1522
|
+
}
|
|
1523
|
+
if (msg.type === "user") {
|
|
1524
|
+
userMessages++;
|
|
1525
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
1526
|
+
if (content.toLowerCase().includes("commit") || content.toLowerCase().includes("\uC644\uB8CC")) {
|
|
1527
|
+
milestones.push({
|
|
1528
|
+
timestamp: msg.timestamp,
|
|
972
1529
|
description: `User checkpoint: ${content.slice(0, 50)}...`,
|
|
973
1530
|
messageUuid: msg.uuid
|
|
974
1531
|
});
|
|
@@ -1077,128 +1634,198 @@ var analyzeSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
1077
1634
|
milestones
|
|
1078
1635
|
};
|
|
1079
1636
|
});
|
|
1080
|
-
var
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
const targetFile = path4.join(targetPath, `${sessionId}.jsonl`);
|
|
1086
|
-
const sourceExists = yield* Effect3.tryPromise(
|
|
1087
|
-
() => fs4.access(sourceFile).then(() => true).catch(() => false)
|
|
1088
|
-
);
|
|
1089
|
-
if (!sourceExists) {
|
|
1090
|
-
return { success: false, error: "Source session not found" };
|
|
1091
|
-
}
|
|
1092
|
-
const targetExists = yield* Effect3.tryPromise(
|
|
1093
|
-
() => fs4.access(targetFile).then(() => true).catch(() => false)
|
|
1094
|
-
);
|
|
1095
|
-
if (targetExists) {
|
|
1096
|
-
return { success: false, error: "Session already exists in target project" };
|
|
1097
|
-
}
|
|
1098
|
-
yield* Effect3.tryPromise(() => fs4.mkdir(targetPath, { recursive: true }));
|
|
1099
|
-
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
1100
|
-
yield* Effect3.tryPromise(() => fs4.rename(sourceFile, targetFile));
|
|
1101
|
-
for (const agentId of linkedAgents) {
|
|
1102
|
-
const sourceAgentFile = path4.join(sourcePath, `${agentId}.jsonl`);
|
|
1103
|
-
const targetAgentFile = path4.join(targetPath, `${agentId}.jsonl`);
|
|
1104
|
-
const agentExists = yield* Effect3.tryPromise(
|
|
1105
|
-
() => fs4.access(sourceAgentFile).then(() => true).catch(() => false)
|
|
1106
|
-
);
|
|
1107
|
-
if (agentExists) {
|
|
1108
|
-
yield* Effect3.tryPromise(() => fs4.rename(sourceAgentFile, targetAgentFile));
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
return { success: true };
|
|
1112
|
-
});
|
|
1113
|
-
var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(function* () {
|
|
1114
|
-
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
1115
|
-
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
1116
|
-
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
1637
|
+
var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
|
|
1638
|
+
const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
|
|
1639
|
+
const filePath = path7.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1640
|
+
const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
|
|
1641
|
+
const originalSize = Buffer.byteLength(content, "utf-8");
|
|
1117
1642
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1126
|
-
const newSessionId = crypto.randomUUID();
|
|
1127
|
-
const summaryMessages = allMessages.filter((m) => m.type === "summary");
|
|
1128
|
-
const summaryMessage = summaryMessages.length > 0 ? summaryMessages[summaryMessages.length - 1] : null;
|
|
1129
|
-
const splitMessage = allMessages[splitIndex];
|
|
1130
|
-
const shouldDuplicate = isContinuationSummary(splitMessage);
|
|
1131
|
-
let keptMessages = allMessages.slice(splitIndex);
|
|
1132
|
-
let movedMessages;
|
|
1133
|
-
if (shouldDuplicate) {
|
|
1134
|
-
const duplicatedMessage = {
|
|
1135
|
-
...splitMessage,
|
|
1136
|
-
uuid: crypto.randomUUID(),
|
|
1137
|
-
sessionId: newSessionId
|
|
1138
|
-
};
|
|
1139
|
-
movedMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage];
|
|
1140
|
-
} else {
|
|
1141
|
-
movedMessages = allMessages.slice(0, splitIndex);
|
|
1142
|
-
}
|
|
1143
|
-
keptMessages = keptMessages.map((msg, index) => {
|
|
1144
|
-
let updated = { ...msg };
|
|
1145
|
-
if (index === 0) {
|
|
1146
|
-
updated.parentUuid = null;
|
|
1147
|
-
updated = cleanupSplitFirstMessage(updated);
|
|
1643
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
1644
|
+
let removedSnapshots = 0;
|
|
1645
|
+
let truncatedOutputs = 0;
|
|
1646
|
+
const snapshotIndices = [];
|
|
1647
|
+
messages.forEach((msg, idx) => {
|
|
1648
|
+
if (msg.type === "file-history-snapshot") {
|
|
1649
|
+
snapshotIndices.push(idx);
|
|
1148
1650
|
}
|
|
1149
|
-
return updated;
|
|
1150
1651
|
});
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
const agentId = agentFile.replace("agent-", "").replace(".jsonl", "");
|
|
1178
|
-
const isRelatedToMoved = movedMessages.some(
|
|
1179
|
-
(msg) => msg.agentId === agentId
|
|
1180
|
-
);
|
|
1181
|
-
if (isRelatedToMoved) {
|
|
1182
|
-
const updatedAgentMessages = agentLines.map((line) => {
|
|
1183
|
-
const msg = JSON.parse(line);
|
|
1184
|
-
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
1185
|
-
});
|
|
1186
|
-
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
1187
|
-
yield* Effect3.tryPromise(() => fs4.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
1652
|
+
const filteredMessages = messages.filter((msg, idx) => {
|
|
1653
|
+
if (msg.type === "file-history-snapshot") {
|
|
1654
|
+
if (keepSnapshots === "none") {
|
|
1655
|
+
removedSnapshots++;
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
if (keepSnapshots === "first_last") {
|
|
1659
|
+
const isFirst = idx === snapshotIndices[0];
|
|
1660
|
+
const isLast = idx === snapshotIndices[snapshotIndices.length - 1];
|
|
1661
|
+
if (!isFirst && !isLast) {
|
|
1662
|
+
removedSnapshots++;
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return true;
|
|
1668
|
+
});
|
|
1669
|
+
for (const msg of filteredMessages) {
|
|
1670
|
+
if (msg.type === "user" && Array.isArray(msg.content)) {
|
|
1671
|
+
for (const item of msg.content) {
|
|
1672
|
+
if (item.type === "tool_result" && typeof item.content === "string") {
|
|
1673
|
+
if (maxToolOutputLength > 0 && item.content.length > maxToolOutputLength) {
|
|
1674
|
+
item.content = item.content.slice(0, maxToolOutputLength) + "\n... [truncated]";
|
|
1675
|
+
truncatedOutputs++;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1188
1678
|
}
|
|
1189
1679
|
}
|
|
1190
1680
|
}
|
|
1681
|
+
const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1682
|
+
const compressedSize = Buffer.byteLength(newContent, "utf-8");
|
|
1683
|
+
yield* Effect6.tryPromise(() => fs7.writeFile(filePath, newContent, "utf-8"));
|
|
1191
1684
|
return {
|
|
1192
1685
|
success: true,
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1686
|
+
originalSize,
|
|
1687
|
+
compressedSize,
|
|
1688
|
+
removedSnapshots,
|
|
1689
|
+
truncatedOutputs
|
|
1690
|
+
};
|
|
1691
|
+
});
|
|
1692
|
+
var extractProjectKnowledge = (projectName, sessionIds) => Effect6.gen(function* () {
|
|
1693
|
+
const sessionsDir = getSessionsDir();
|
|
1694
|
+
const projectDir = path7.join(sessionsDir, projectName);
|
|
1695
|
+
let targetSessionIds = sessionIds;
|
|
1696
|
+
if (!targetSessionIds) {
|
|
1697
|
+
const files = yield* Effect6.tryPromise(() => fs7.readdir(projectDir));
|
|
1698
|
+
targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
|
|
1699
|
+
}
|
|
1700
|
+
const fileModifyCount = /* @__PURE__ */ new Map();
|
|
1701
|
+
const toolSequences = [];
|
|
1702
|
+
const decisions = [];
|
|
1703
|
+
for (const sessionId of targetSessionIds) {
|
|
1704
|
+
try {
|
|
1705
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1706
|
+
for (const msg of messages) {
|
|
1707
|
+
if (msg.type === "file-history-snapshot") {
|
|
1708
|
+
const snapshot = msg;
|
|
1709
|
+
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1710
|
+
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1711
|
+
const existing = fileModifyCount.get(filePath) ?? { count: 0 };
|
|
1712
|
+
existing.count++;
|
|
1713
|
+
existing.lastModified = snapshot.snapshot.timestamp;
|
|
1714
|
+
fileModifyCount.set(filePath, existing);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (msg.type === "assistant" && msg.message?.content && Array.isArray(msg.message.content)) {
|
|
1719
|
+
const tools = [];
|
|
1720
|
+
for (const item of msg.message.content) {
|
|
1721
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
1722
|
+
const toolUse = item;
|
|
1723
|
+
if (toolUse.name) tools.push(toolUse.name);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (tools.length > 1) {
|
|
1727
|
+
toolSequences.push(tools);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (msg.type === "summary" && msg.summary) {
|
|
1731
|
+
decisions.push({
|
|
1732
|
+
context: "Session summary",
|
|
1733
|
+
decision: msg.summary.slice(0, 200),
|
|
1734
|
+
sessionId
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
} catch {
|
|
1739
|
+
continue;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
const hotFiles = Array.from(fileModifyCount.entries()).map(([filePath, data]) => ({
|
|
1743
|
+
path: filePath,
|
|
1744
|
+
modifyCount: data.count,
|
|
1745
|
+
lastModified: data.lastModified
|
|
1746
|
+
})).sort((a, b) => b.modifyCount - a.modifyCount).slice(0, 20);
|
|
1747
|
+
const workflowMap = /* @__PURE__ */ new Map();
|
|
1748
|
+
for (const seq of toolSequences) {
|
|
1749
|
+
const key = seq.join(" -> ");
|
|
1750
|
+
workflowMap.set(key, (workflowMap.get(key) ?? 0) + 1);
|
|
1751
|
+
}
|
|
1752
|
+
const workflows = Array.from(workflowMap.entries()).filter(([, count]) => count >= 2).map(([sequence, count]) => ({
|
|
1753
|
+
sequence: sequence.split(" -> "),
|
|
1754
|
+
count
|
|
1755
|
+
})).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
1756
|
+
return {
|
|
1757
|
+
projectName,
|
|
1758
|
+
patterns: [],
|
|
1759
|
+
hotFiles,
|
|
1760
|
+
workflows,
|
|
1761
|
+
decisions: decisions.slice(0, 20)
|
|
1762
|
+
};
|
|
1763
|
+
});
|
|
1764
|
+
function truncateText(text, maxLen) {
|
|
1765
|
+
const cleaned = text.replace(/\n/g, " ");
|
|
1766
|
+
if (cleaned.length > maxLen) {
|
|
1767
|
+
return cleaned.slice(0, maxLen) + "...";
|
|
1768
|
+
}
|
|
1769
|
+
return cleaned;
|
|
1770
|
+
}
|
|
1771
|
+
var summarizeSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
|
|
1772
|
+
const { limit = 50, maxLength = 100 } = options;
|
|
1773
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1774
|
+
const lines = [];
|
|
1775
|
+
let count = 0;
|
|
1776
|
+
for (const msg of messages) {
|
|
1777
|
+
if (count >= limit) break;
|
|
1778
|
+
if (msg.type === "user" || msg.type === "human") {
|
|
1779
|
+
let timeStr;
|
|
1780
|
+
if (msg.timestamp) {
|
|
1781
|
+
try {
|
|
1782
|
+
const dt = new Date(msg.timestamp);
|
|
1783
|
+
timeStr = dt.toLocaleString("ko-KR", {
|
|
1784
|
+
month: "2-digit",
|
|
1785
|
+
day: "2-digit",
|
|
1786
|
+
hour: "2-digit",
|
|
1787
|
+
minute: "2-digit",
|
|
1788
|
+
hour12: false
|
|
1789
|
+
});
|
|
1790
|
+
} catch {
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
const text = extractTextContent(msg.message);
|
|
1794
|
+
if (text) {
|
|
1795
|
+
const truncated = truncateText(text, maxLength);
|
|
1796
|
+
lines.push({ role: "user", content: truncated, timestamp: timeStr });
|
|
1797
|
+
count++;
|
|
1798
|
+
}
|
|
1799
|
+
} else if (msg.type === "assistant") {
|
|
1800
|
+
const text = extractTextContent(msg.message);
|
|
1801
|
+
if (text) {
|
|
1802
|
+
const truncated = truncateText(text, maxLength);
|
|
1803
|
+
lines.push({ role: "assistant", content: truncated });
|
|
1804
|
+
count++;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
const formatted = lines.map((line) => {
|
|
1809
|
+
if (line.role === "user") {
|
|
1810
|
+
return line.timestamp ? `user [${line.timestamp}]: ${line.content}` : `user: ${line.content}`;
|
|
1811
|
+
}
|
|
1812
|
+
return `assistant: ${line.content}`;
|
|
1813
|
+
}).join("\n");
|
|
1814
|
+
return {
|
|
1815
|
+
sessionId,
|
|
1816
|
+
projectName,
|
|
1817
|
+
lines,
|
|
1818
|
+
formatted
|
|
1197
1819
|
};
|
|
1198
1820
|
});
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1821
|
+
|
|
1822
|
+
// src/session/cleanup.ts
|
|
1823
|
+
import { Effect as Effect7 } from "effect";
|
|
1824
|
+
import * as fs8 from "fs/promises";
|
|
1825
|
+
import * as path8 from "path";
|
|
1826
|
+
var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* () {
|
|
1827
|
+
const filePath = path8.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1828
|
+
const content = yield* Effect7.tryPromise(() => fs8.readFile(filePath, "utf-8"));
|
|
1202
1829
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1203
1830
|
if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
|
|
1204
1831
|
const messages = lines.map((line) => JSON.parse(line));
|
|
@@ -1230,7 +1857,7 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect3.gen(function* ()
|
|
|
1230
1857
|
lastValidUuid = msg.uuid;
|
|
1231
1858
|
}
|
|
1232
1859
|
const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
|
|
1233
|
-
yield*
|
|
1860
|
+
yield* Effect7.tryPromise(() => fs8.writeFile(filePath, newContent, "utf-8"));
|
|
1234
1861
|
const remainingUserAssistant = filtered.filter(
|
|
1235
1862
|
(m) => m.type === "user" || m.type === "assistant"
|
|
1236
1863
|
).length;
|
|
@@ -1238,14 +1865,14 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect3.gen(function* ()
|
|
|
1238
1865
|
const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
|
|
1239
1866
|
return { removedCount: invalidIndices.length, remainingCount };
|
|
1240
1867
|
});
|
|
1241
|
-
var previewCleanup = (projectName) =>
|
|
1868
|
+
var previewCleanup = (projectName) => Effect7.gen(function* () {
|
|
1242
1869
|
const projects = yield* listProjects;
|
|
1243
1870
|
const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
|
|
1244
1871
|
const orphanTodos = yield* findOrphanTodos();
|
|
1245
1872
|
const orphanTodoCount = orphanTodos.length;
|
|
1246
|
-
const results = yield*
|
|
1873
|
+
const results = yield* Effect7.all(
|
|
1247
1874
|
targetProjects.map(
|
|
1248
|
-
(project) =>
|
|
1875
|
+
(project) => Effect7.gen(function* () {
|
|
1249
1876
|
const sessions = yield* listSessions(project.name);
|
|
1250
1877
|
const emptySessions = sessions.filter((s) => s.messageCount === 0);
|
|
1251
1878
|
const invalidSessions = sessions.filter(
|
|
@@ -1278,7 +1905,7 @@ var previewCleanup = (projectName) => Effect3.gen(function* () {
|
|
|
1278
1905
|
}
|
|
1279
1906
|
return results;
|
|
1280
1907
|
});
|
|
1281
|
-
var clearSessions = (options) =>
|
|
1908
|
+
var clearSessions = (options) => Effect7.gen(function* () {
|
|
1282
1909
|
const {
|
|
1283
1910
|
projectName,
|
|
1284
1911
|
clearEmpty = true,
|
|
@@ -1296,8 +1923,8 @@ var clearSessions = (options) => Effect3.gen(function* () {
|
|
|
1296
1923
|
const sessionsToDelete = [];
|
|
1297
1924
|
if (clearInvalid) {
|
|
1298
1925
|
for (const project of targetProjects) {
|
|
1299
|
-
const projectPath =
|
|
1300
|
-
const files = yield*
|
|
1926
|
+
const projectPath = path8.join(getSessionsDir(), project.name);
|
|
1927
|
+
const files = yield* Effect7.tryPromise(() => fs8.readdir(projectPath));
|
|
1301
1928
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1302
1929
|
for (const file of sessionFiles) {
|
|
1303
1930
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -1351,7 +1978,12 @@ var clearSessions = (options) => Effect3.gen(function* () {
|
|
|
1351
1978
|
deletedOrphanTodoCount
|
|
1352
1979
|
};
|
|
1353
1980
|
});
|
|
1354
|
-
|
|
1981
|
+
|
|
1982
|
+
// src/session/search.ts
|
|
1983
|
+
import { Effect as Effect8 } from "effect";
|
|
1984
|
+
import * as fs9 from "fs/promises";
|
|
1985
|
+
import * as path9 from "path";
|
|
1986
|
+
var searchSessions = (query, options = {}) => Effect8.gen(function* () {
|
|
1355
1987
|
const { projectName, searchContent = false } = options;
|
|
1356
1988
|
const results = [];
|
|
1357
1989
|
const queryLower = query.toLowerCase();
|
|
@@ -1374,16 +2006,16 @@ var searchSessions = (query, options = {}) => Effect3.gen(function* () {
|
|
|
1374
2006
|
}
|
|
1375
2007
|
if (searchContent) {
|
|
1376
2008
|
for (const project of targetProjects) {
|
|
1377
|
-
const projectPath =
|
|
1378
|
-
const files = yield*
|
|
2009
|
+
const projectPath = path9.join(getSessionsDir(), project.name);
|
|
2010
|
+
const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
|
|
1379
2011
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1380
2012
|
for (const file of sessionFiles) {
|
|
1381
2013
|
const sessionId = file.replace(".jsonl", "");
|
|
1382
2014
|
if (results.some((r) => r.sessionId === sessionId && r.projectName === project.name)) {
|
|
1383
2015
|
continue;
|
|
1384
2016
|
}
|
|
1385
|
-
const filePath =
|
|
1386
|
-
const content = yield*
|
|
2017
|
+
const filePath = path9.join(projectPath, file);
|
|
2018
|
+
const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
|
|
1387
2019
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1388
2020
|
for (const line of lines) {
|
|
1389
2021
|
try {
|
|
@@ -1419,414 +2051,105 @@ var searchSessions = (query, options = {}) => Effect3.gen(function* () {
|
|
|
1419
2051
|
return dateB - dateA;
|
|
1420
2052
|
});
|
|
1421
2053
|
});
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
2054
|
+
|
|
2055
|
+
// src/session/files.ts
|
|
2056
|
+
import { Effect as Effect9 } from "effect";
|
|
2057
|
+
var getSessionFiles = (projectName, sessionId) => Effect9.gen(function* () {
|
|
2058
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
2059
|
+
const fileChanges = [];
|
|
2060
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
2061
|
+
for (const msg of messages) {
|
|
2062
|
+
if (msg.type === "file-history-snapshot") {
|
|
2063
|
+
const snapshot = msg;
|
|
2064
|
+
const backups = snapshot.snapshot?.trackedFileBackups;
|
|
2065
|
+
if (backups && typeof backups === "object") {
|
|
2066
|
+
for (const filePath of Object.keys(backups)) {
|
|
2067
|
+
if (!seenFiles.has(filePath)) {
|
|
2068
|
+
seenFiles.add(filePath);
|
|
2069
|
+
fileChanges.push({
|
|
2070
|
+
path: filePath,
|
|
2071
|
+
action: "modified",
|
|
2072
|
+
timestamp: snapshot.snapshot?.timestamp,
|
|
2073
|
+
messageUuid: snapshot.messageId ?? msg.uuid
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
1439
2077
|
}
|
|
1440
2078
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
const firstUserMsg = messages.find((m) => m.type === "user");
|
|
1476
|
-
const customTitleMsg = messages.find((m) => m.type === "custom-title");
|
|
1477
|
-
const customTitle = customTitleMsg?.customTitle;
|
|
1478
|
-
const title = firstUserMsg ? extractTitle(extractTextContent(firstUserMsg.message)) : summaries.length > 0 ? "[Summary Only]" : `Session ${sessionId.slice(0, 8)}`;
|
|
1479
|
-
const userAssistantMessages = messages.filter(
|
|
1480
|
-
(m) => m.type === "user" || m.type === "assistant"
|
|
1481
|
-
);
|
|
1482
|
-
const firstMessage = userAssistantMessages[0];
|
|
1483
|
-
const lastMessage = userAssistantMessages[userAssistantMessages.length - 1];
|
|
1484
|
-
const linkedAgentIds = yield* findLinkedAgents(projectName, sessionId);
|
|
1485
|
-
const agents = [];
|
|
1486
|
-
for (const agentId of linkedAgentIds) {
|
|
1487
|
-
const agentPath = path4.join(projectPath, `${agentId}.jsonl`);
|
|
1488
|
-
try {
|
|
1489
|
-
const agentContent = yield* Effect3.tryPromise(() => fs4.readFile(agentPath, "utf-8"));
|
|
1490
|
-
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
1491
|
-
const agentMsgs = agentLines.map((l) => JSON.parse(l));
|
|
1492
|
-
const agentUserAssistant = agentMsgs.filter(
|
|
1493
|
-
(m) => m.type === "user" || m.type === "assistant"
|
|
1494
|
-
);
|
|
1495
|
-
let agentName;
|
|
1496
|
-
const firstAgentMsg = agentMsgs.find((m) => m.type === "user");
|
|
1497
|
-
if (firstAgentMsg) {
|
|
1498
|
-
const text = extractTextContent(firstAgentMsg.message);
|
|
1499
|
-
if (text) {
|
|
1500
|
-
agentName = extractTitle(text);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
agents.push({
|
|
1504
|
-
id: agentId,
|
|
1505
|
-
name: agentName,
|
|
1506
|
-
messageCount: agentUserAssistant.length
|
|
1507
|
-
});
|
|
1508
|
-
} catch {
|
|
1509
|
-
agents.push({
|
|
1510
|
-
id: agentId,
|
|
1511
|
-
messageCount: 0
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
const todos = yield* findLinkedTodos(sessionId, linkedAgentIds);
|
|
1516
|
-
return {
|
|
1517
|
-
id: sessionId,
|
|
1518
|
-
projectName,
|
|
1519
|
-
title,
|
|
1520
|
-
customTitle,
|
|
1521
|
-
currentSummary: summaries[0]?.summary,
|
|
1522
|
-
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
|
|
1523
|
-
createdAt: firstMessage?.timestamp ?? void 0,
|
|
1524
|
-
updatedAt: lastMessage?.timestamp ?? void 0,
|
|
1525
|
-
summaries,
|
|
1526
|
-
agents,
|
|
1527
|
-
todos,
|
|
1528
|
-
lastCompactBoundaryUuid
|
|
1529
|
-
};
|
|
1530
|
-
});
|
|
1531
|
-
var loadSessionTreeData = (projectName, sessionId) => loadSessionTreeDataInternal(projectName, sessionId, void 0);
|
|
1532
|
-
var loadProjectTreeData = (projectName) => Effect3.gen(function* () {
|
|
1533
|
-
const project = (yield* listProjects).find((p) => p.name === projectName);
|
|
1534
|
-
if (!project) {
|
|
1535
|
-
return null;
|
|
1536
|
-
}
|
|
1537
|
-
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
1538
|
-
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
1539
|
-
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
1540
|
-
const globalUuidMap = /* @__PURE__ */ new Map();
|
|
1541
|
-
const allSummaries = [];
|
|
1542
|
-
const allJsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
1543
|
-
yield* Effect3.all(
|
|
1544
|
-
allJsonlFiles.map(
|
|
1545
|
-
(file) => Effect3.gen(function* () {
|
|
1546
|
-
const filePath = path4.join(projectPath, file);
|
|
1547
|
-
const fileSessionId = file.replace(".jsonl", "");
|
|
1548
|
-
try {
|
|
1549
|
-
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
1550
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
1551
|
-
for (const line of lines) {
|
|
1552
|
-
try {
|
|
1553
|
-
const msg = JSON.parse(line);
|
|
1554
|
-
if (msg.uuid && typeof msg.uuid === "string") {
|
|
1555
|
-
globalUuidMap.set(msg.uuid, {
|
|
1556
|
-
sessionId: fileSessionId,
|
|
1557
|
-
timestamp: msg.timestamp
|
|
1558
|
-
});
|
|
1559
|
-
}
|
|
1560
|
-
if (msg.messageId && typeof msg.messageId === "string") {
|
|
1561
|
-
globalUuidMap.set(msg.messageId, {
|
|
1562
|
-
sessionId: fileSessionId,
|
|
1563
|
-
timestamp: msg.snapshot?.timestamp
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
if (msg.type === "summary" && typeof msg.summary === "string") {
|
|
1567
|
-
allSummaries.push({
|
|
1568
|
-
summary: msg.summary,
|
|
1569
|
-
leafUuid: msg.leafUuid,
|
|
1570
|
-
timestamp: msg.timestamp
|
|
2079
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
2080
|
+
const content = msg.message.content;
|
|
2081
|
+
if (Array.isArray(content)) {
|
|
2082
|
+
for (const item of content) {
|
|
2083
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
2084
|
+
const toolUse = item;
|
|
2085
|
+
if ((toolUse.name === "Write" || toolUse.name === "Edit") && toolUse.input?.file_path) {
|
|
2086
|
+
const filePath = toolUse.input.file_path;
|
|
2087
|
+
if (!seenFiles.has(filePath)) {
|
|
2088
|
+
seenFiles.add(filePath);
|
|
2089
|
+
fileChanges.push({
|
|
2090
|
+
path: filePath,
|
|
2091
|
+
action: toolUse.name === "Write" ? "created" : "modified",
|
|
2092
|
+
timestamp: msg.timestamp,
|
|
2093
|
+
messageUuid: msg.uuid
|
|
1571
2094
|
});
|
|
1572
2095
|
}
|
|
1573
|
-
} catch {
|
|
1574
2096
|
}
|
|
1575
2097
|
}
|
|
1576
|
-
} catch {
|
|
1577
|
-
}
|
|
1578
|
-
})
|
|
1579
|
-
),
|
|
1580
|
-
{ concurrency: 20 }
|
|
1581
|
-
);
|
|
1582
|
-
const summariesByTargetSession = /* @__PURE__ */ new Map();
|
|
1583
|
-
for (const summaryData of allSummaries) {
|
|
1584
|
-
if (summaryData.leafUuid) {
|
|
1585
|
-
const targetInfo = globalUuidMap.get(summaryData.leafUuid);
|
|
1586
|
-
if (targetInfo) {
|
|
1587
|
-
const targetSessionId = targetInfo.sessionId;
|
|
1588
|
-
if (!summariesByTargetSession.has(targetSessionId)) {
|
|
1589
|
-
summariesByTargetSession.set(targetSessionId, []);
|
|
1590
2098
|
}
|
|
1591
|
-
summariesByTargetSession.get(targetSessionId).push({
|
|
1592
|
-
summary: summaryData.summary,
|
|
1593
|
-
leafUuid: summaryData.leafUuid,
|
|
1594
|
-
timestamp: targetInfo.timestamp ?? summaryData.timestamp
|
|
1595
|
-
});
|
|
1596
2099
|
}
|
|
1597
2100
|
}
|
|
1598
2101
|
}
|
|
1599
|
-
const sessions = yield* Effect3.all(
|
|
1600
|
-
sessionFiles.map((file) => {
|
|
1601
|
-
const sessionId = file.replace(".jsonl", "");
|
|
1602
|
-
return loadSessionTreeDataInternal(projectName, sessionId, summariesByTargetSession);
|
|
1603
|
-
}),
|
|
1604
|
-
{ concurrency: 10 }
|
|
1605
|
-
);
|
|
1606
|
-
const sortedSessions = sessions.sort((a, b) => {
|
|
1607
|
-
const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
1608
|
-
const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
1609
|
-
return dateB - dateA;
|
|
1610
|
-
});
|
|
1611
|
-
const filteredSessions = sortedSessions.filter((s) => {
|
|
1612
|
-
if (isErrorSessionTitle(s.title)) return false;
|
|
1613
|
-
if (isErrorSessionTitle(s.customTitle)) return false;
|
|
1614
|
-
if (isErrorSessionTitle(s.currentSummary)) return false;
|
|
1615
|
-
return true;
|
|
1616
|
-
});
|
|
1617
2102
|
return {
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
sessions: filteredSessions
|
|
2103
|
+
sessionId,
|
|
2104
|
+
projectName,
|
|
2105
|
+
files: fileChanges,
|
|
2106
|
+
totalChanges: fileChanges.length
|
|
1623
2107
|
};
|
|
1624
2108
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
leafUuid: firstUserMsg?.uuid ?? null
|
|
1639
|
-
};
|
|
1640
|
-
messages.unshift(summaryMsg);
|
|
2109
|
+
|
|
2110
|
+
// src/session/index-file.ts
|
|
2111
|
+
import { Effect as Effect10 } from "effect";
|
|
2112
|
+
import * as fs10 from "fs/promises";
|
|
2113
|
+
import * as path10 from "path";
|
|
2114
|
+
var loadSessionsIndex = (projectName) => Effect10.gen(function* () {
|
|
2115
|
+
const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
|
|
2116
|
+
try {
|
|
2117
|
+
const content = yield* Effect10.tryPromise(() => fs10.readFile(indexPath, "utf-8"));
|
|
2118
|
+
const index = JSON.parse(content);
|
|
2119
|
+
return index;
|
|
2120
|
+
} catch {
|
|
2121
|
+
return null;
|
|
1641
2122
|
}
|
|
1642
|
-
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1643
|
-
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
1644
|
-
return { success: true };
|
|
1645
2123
|
});
|
|
1646
|
-
var
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
2124
|
+
var getIndexEntryDisplayTitle = (entry) => {
|
|
2125
|
+
if (entry.customTitle) return entry.customTitle;
|
|
2126
|
+
if (entry.summary) return entry.summary;
|
|
2127
|
+
let prompt = entry.firstPrompt;
|
|
2128
|
+
if (prompt === "No prompt") return "Untitled";
|
|
2129
|
+
if (prompt.startsWith("[Request interrupted")) return "Untitled";
|
|
2130
|
+
prompt = prompt.replace(/<ide_[^>]*>[^<]*<\/ide_[^>]*>/g, "").trim();
|
|
2131
|
+
if (!prompt) return "Untitled";
|
|
2132
|
+
if (prompt.length > 60) {
|
|
2133
|
+
return prompt.slice(0, 57) + "...";
|
|
2134
|
+
}
|
|
2135
|
+
return prompt;
|
|
2136
|
+
};
|
|
2137
|
+
var sortIndexEntriesByModified = (entries) => {
|
|
2138
|
+
return [...entries].sort((a, b) => {
|
|
2139
|
+
const modA = new Date(a.modified).getTime();
|
|
2140
|
+
const modB = new Date(b.modified).getTime();
|
|
2141
|
+
return modB - modA;
|
|
1660
2142
|
});
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
if (keepSnapshots === "first_last") {
|
|
1668
|
-
const isFirst = idx === snapshotIndices[0];
|
|
1669
|
-
const isLast = idx === snapshotIndices[snapshotIndices.length - 1];
|
|
1670
|
-
if (!isFirst && !isLast) {
|
|
1671
|
-
removedSnapshots++;
|
|
1672
|
-
return false;
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
2143
|
+
};
|
|
2144
|
+
var hasSessionsIndex = (projectName) => Effect10.gen(function* () {
|
|
2145
|
+
const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
|
|
2146
|
+
try {
|
|
2147
|
+
yield* Effect10.tryPromise(() => fs10.access(indexPath));
|
|
1676
2148
|
return true;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
if (msg.type === "user" && Array.isArray(msg.content)) {
|
|
1680
|
-
for (const item of msg.content) {
|
|
1681
|
-
if (item.type === "tool_result" && typeof item.content === "string") {
|
|
1682
|
-
if (maxToolOutputLength > 0 && item.content.length > maxToolOutputLength) {
|
|
1683
|
-
item.content = item.content.slice(0, maxToolOutputLength) + "\n... [truncated]";
|
|
1684
|
-
truncatedOutputs++;
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1691
|
-
const compressedSize = Buffer.byteLength(newContent, "utf-8");
|
|
1692
|
-
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
1693
|
-
return {
|
|
1694
|
-
success: true,
|
|
1695
|
-
originalSize,
|
|
1696
|
-
compressedSize,
|
|
1697
|
-
removedSnapshots,
|
|
1698
|
-
truncatedOutputs
|
|
1699
|
-
};
|
|
1700
|
-
});
|
|
1701
|
-
var extractProjectKnowledge = (projectName, sessionIds) => Effect3.gen(function* () {
|
|
1702
|
-
const sessionsDir = getSessionsDir();
|
|
1703
|
-
const projectDir = path4.join(sessionsDir, projectName);
|
|
1704
|
-
let targetSessionIds = sessionIds;
|
|
1705
|
-
if (!targetSessionIds) {
|
|
1706
|
-
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectDir));
|
|
1707
|
-
targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
|
|
1708
|
-
}
|
|
1709
|
-
const fileModifyCount = /* @__PURE__ */ new Map();
|
|
1710
|
-
const toolSequences = [];
|
|
1711
|
-
const decisions = [];
|
|
1712
|
-
for (const sessionId of targetSessionIds) {
|
|
1713
|
-
try {
|
|
1714
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
1715
|
-
for (const msg of messages) {
|
|
1716
|
-
if (msg.type === "file-history-snapshot") {
|
|
1717
|
-
const snapshot = msg;
|
|
1718
|
-
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1719
|
-
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1720
|
-
const existing = fileModifyCount.get(filePath) ?? { count: 0 };
|
|
1721
|
-
existing.count++;
|
|
1722
|
-
existing.lastModified = snapshot.snapshot.timestamp;
|
|
1723
|
-
fileModifyCount.set(filePath, existing);
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
if (msg.type === "assistant" && msg.message?.content && Array.isArray(msg.message.content)) {
|
|
1728
|
-
const tools = [];
|
|
1729
|
-
for (const item of msg.message.content) {
|
|
1730
|
-
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
1731
|
-
const toolUse = item;
|
|
1732
|
-
if (toolUse.name) tools.push(toolUse.name);
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
if (tools.length > 1) {
|
|
1736
|
-
toolSequences.push(tools);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
if (msg.type === "summary" && msg.summary) {
|
|
1740
|
-
decisions.push({
|
|
1741
|
-
context: "Session summary",
|
|
1742
|
-
decision: msg.summary.slice(0, 200),
|
|
1743
|
-
sessionId
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
} catch {
|
|
1748
|
-
continue;
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
const hotFiles = Array.from(fileModifyCount.entries()).map(([filePath, data]) => ({
|
|
1752
|
-
path: filePath,
|
|
1753
|
-
modifyCount: data.count,
|
|
1754
|
-
lastModified: data.lastModified
|
|
1755
|
-
})).sort((a, b) => b.modifyCount - a.modifyCount).slice(0, 20);
|
|
1756
|
-
const workflowMap = /* @__PURE__ */ new Map();
|
|
1757
|
-
for (const seq of toolSequences) {
|
|
1758
|
-
const key = seq.join(" -> ");
|
|
1759
|
-
workflowMap.set(key, (workflowMap.get(key) ?? 0) + 1);
|
|
1760
|
-
}
|
|
1761
|
-
const workflows = Array.from(workflowMap.entries()).filter(([, count]) => count >= 2).map(([sequence, count]) => ({
|
|
1762
|
-
sequence: sequence.split(" -> "),
|
|
1763
|
-
count
|
|
1764
|
-
})).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
1765
|
-
return {
|
|
1766
|
-
projectName,
|
|
1767
|
-
patterns: [],
|
|
1768
|
-
hotFiles,
|
|
1769
|
-
workflows,
|
|
1770
|
-
decisions: decisions.slice(0, 20)
|
|
1771
|
-
};
|
|
1772
|
-
});
|
|
1773
|
-
var summarizeSession = (projectName, sessionId, options = {}) => Effect3.gen(function* () {
|
|
1774
|
-
const { limit = 50, maxLength = 100 } = options;
|
|
1775
|
-
const messages = yield* readSession(projectName, sessionId);
|
|
1776
|
-
const lines = [];
|
|
1777
|
-
let count = 0;
|
|
1778
|
-
for (const msg of messages) {
|
|
1779
|
-
if (count >= limit) break;
|
|
1780
|
-
if (msg.type === "user" || msg.type === "human") {
|
|
1781
|
-
let timeStr;
|
|
1782
|
-
if (msg.timestamp) {
|
|
1783
|
-
try {
|
|
1784
|
-
const dt = new Date(msg.timestamp);
|
|
1785
|
-
timeStr = dt.toLocaleString("ko-KR", {
|
|
1786
|
-
month: "2-digit",
|
|
1787
|
-
day: "2-digit",
|
|
1788
|
-
hour: "2-digit",
|
|
1789
|
-
minute: "2-digit",
|
|
1790
|
-
hour12: false
|
|
1791
|
-
});
|
|
1792
|
-
} catch {
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
const text = extractTextContent(msg.message);
|
|
1796
|
-
if (text) {
|
|
1797
|
-
const truncated = truncateText(text, maxLength);
|
|
1798
|
-
lines.push({ role: "user", content: truncated, timestamp: timeStr });
|
|
1799
|
-
count++;
|
|
1800
|
-
}
|
|
1801
|
-
} else if (msg.type === "assistant") {
|
|
1802
|
-
const text = extractTextContent(msg.message);
|
|
1803
|
-
if (text) {
|
|
1804
|
-
const truncated = truncateText(text, maxLength);
|
|
1805
|
-
lines.push({ role: "assistant", content: truncated });
|
|
1806
|
-
count++;
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
2149
|
+
} catch {
|
|
2150
|
+
return false;
|
|
1809
2151
|
}
|
|
1810
|
-
const formatted = lines.map((line) => {
|
|
1811
|
-
if (line.role === "user") {
|
|
1812
|
-
return line.timestamp ? `user [${line.timestamp}]: ${line.content}` : `user: ${line.content}`;
|
|
1813
|
-
}
|
|
1814
|
-
return `assistant: ${line.content}`;
|
|
1815
|
-
}).join("\n");
|
|
1816
|
-
return {
|
|
1817
|
-
sessionId,
|
|
1818
|
-
projectName,
|
|
1819
|
-
lines,
|
|
1820
|
-
formatted
|
|
1821
|
-
};
|
|
1822
2152
|
});
|
|
1823
|
-
function truncateText(text, maxLen) {
|
|
1824
|
-
const cleaned = text.replace(/\n/g, " ");
|
|
1825
|
-
if (cleaned.length > maxLen) {
|
|
1826
|
-
return cleaned.slice(0, maxLen) + "...";
|
|
1827
|
-
}
|
|
1828
|
-
return cleaned;
|
|
1829
|
-
}
|
|
1830
2153
|
export {
|
|
1831
2154
|
analyzeSession,
|
|
1832
2155
|
clearSessions,
|
|
@@ -1834,6 +2157,7 @@ export {
|
|
|
1834
2157
|
createLogger,
|
|
1835
2158
|
deleteLinkedTodos,
|
|
1836
2159
|
deleteMessage,
|
|
2160
|
+
deleteMessageWithChainRepair,
|
|
1837
2161
|
deleteOrphanAgents,
|
|
1838
2162
|
deleteOrphanTodos,
|
|
1839
2163
|
deleteSession,
|
|
@@ -1849,11 +2173,14 @@ export {
|
|
|
1849
2173
|
folderNameToDisplayPath,
|
|
1850
2174
|
folderNameToPath,
|
|
1851
2175
|
getDisplayTitle,
|
|
2176
|
+
getIndexEntryDisplayTitle,
|
|
1852
2177
|
getLogger,
|
|
1853
2178
|
getRealPathFromSession,
|
|
1854
2179
|
getSessionFiles,
|
|
2180
|
+
getSessionSortTimestamp,
|
|
1855
2181
|
getSessionsDir,
|
|
1856
2182
|
getTodosDir,
|
|
2183
|
+
hasSessionsIndex,
|
|
1857
2184
|
isContinuationSummary,
|
|
1858
2185
|
isInvalidApiKeyMessage,
|
|
1859
2186
|
listProjects,
|
|
@@ -1861,8 +2188,10 @@ export {
|
|
|
1861
2188
|
loadAgentMessages,
|
|
1862
2189
|
loadProjectTreeData,
|
|
1863
2190
|
loadSessionTreeData,
|
|
2191
|
+
loadSessionsIndex,
|
|
1864
2192
|
maskHomePath,
|
|
1865
2193
|
moveSession,
|
|
2194
|
+
parseCommandMessage,
|
|
1866
2195
|
pathToFolderName,
|
|
1867
2196
|
previewCleanup,
|
|
1868
2197
|
readSession,
|
|
@@ -1871,9 +2200,12 @@ export {
|
|
|
1871
2200
|
searchSessions,
|
|
1872
2201
|
sessionHasTodos,
|
|
1873
2202
|
setLogger,
|
|
2203
|
+
sortIndexEntriesByModified,
|
|
1874
2204
|
sortProjects,
|
|
1875
2205
|
splitSession,
|
|
1876
2206
|
summarizeSession,
|
|
1877
|
-
updateSessionSummary
|
|
2207
|
+
updateSessionSummary,
|
|
2208
|
+
validateChain,
|
|
2209
|
+
validateToolUseResult
|
|
1878
2210
|
};
|
|
1879
2211
|
//# sourceMappingURL=index.js.map
|