@claude-sessions/core 0.1.0 → 0.1.2
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 +4 -1
- package/dist/index.js +117 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -107,6 +107,9 @@ declare const getSessionsDir: () => string;
|
|
|
107
107
|
declare const getTodosDir: () => string;
|
|
108
108
|
declare const folderNameToDisplayPath: (folderName: string) => string;
|
|
109
109
|
declare const displayPathToFolderName: (displayPath: string) => string;
|
|
110
|
+
declare const pathToFolderName: (absolutePath: string) => string;
|
|
111
|
+
declare const folderNameToPath: (folderName: string) => string;
|
|
112
|
+
declare const getRealPathFromSession: (folderName: string) => string | null;
|
|
110
113
|
|
|
111
114
|
/**
|
|
112
115
|
* Utility functions for message processing
|
|
@@ -241,4 +244,4 @@ declare const clearSessions: (options: {
|
|
|
241
244
|
deletedOrphanTodoCount: number;
|
|
242
245
|
}, effect_Cause.UnknownException, never>;
|
|
243
246
|
|
|
244
|
-
export { type CleanupPreview, type ClearSessionsResult, type ContentItem, type DeleteSessionResult, type FileChange, type Message, type MessagePayload, type MoveSessionResult, type Project, type RenameSessionResult, type SessionFilesSummary, type SessionMeta, type SessionTodos, type SplitSessionResult, type TodoItem, clearSessions, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, folderNameToDisplayPath, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, moveSession, previewCleanup, readSession, renameSession, sessionHasTodos, splitSession };
|
|
247
|
+
export { type CleanupPreview, type ClearSessionsResult, type ContentItem, type DeleteSessionResult, type FileChange, type Message, type MessagePayload, type MoveSessionResult, type Project, type RenameSessionResult, type SessionFilesSummary, type SessionMeta, type SessionTodos, type SplitSessionResult, type TodoItem, clearSessions, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, folderNameToDisplayPath, folderNameToPath, getRealPathFromSession, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, moveSession, pathToFolderName, previewCleanup, readSession, renameSession, sessionHasTodos, splitSession };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/paths.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as fs from "fs";
|
|
3
3
|
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
4
5
|
var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
|
|
5
6
|
var getTodosDir = () => path.join(os.homedir(), ".claude", "todos");
|
|
6
7
|
var folderNameToDisplayPath = (folderName) => {
|
|
@@ -9,6 +10,52 @@ var folderNameToDisplayPath = (folderName) => {
|
|
|
9
10
|
var displayPathToFolderName = (displayPath) => {
|
|
10
11
|
return displayPath.replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-");
|
|
11
12
|
};
|
|
13
|
+
var pathToFolderName = (absolutePath) => {
|
|
14
|
+
return absolutePath.replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-");
|
|
15
|
+
};
|
|
16
|
+
var folderNameToPath = (folderName) => {
|
|
17
|
+
const realPath = getRealPathFromSession(folderName);
|
|
18
|
+
if (realPath) {
|
|
19
|
+
const home2 = os.homedir();
|
|
20
|
+
if (realPath.startsWith(home2)) {
|
|
21
|
+
return "~" + realPath.slice(home2.length);
|
|
22
|
+
}
|
|
23
|
+
return realPath;
|
|
24
|
+
}
|
|
25
|
+
const absolutePath = folderNameToDisplayPath(folderName);
|
|
26
|
+
const home = os.homedir();
|
|
27
|
+
if (absolutePath.startsWith(home)) {
|
|
28
|
+
return "~" + absolutePath.slice(home.length);
|
|
29
|
+
}
|
|
30
|
+
return absolutePath;
|
|
31
|
+
};
|
|
32
|
+
var tryGetCwdFromFile = (filePath) => {
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
35
|
+
const firstLine = content.split("\n")[0];
|
|
36
|
+
if (!firstLine) return null;
|
|
37
|
+
const message = JSON.parse(firstLine);
|
|
38
|
+
return message.cwd ?? null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var getRealPathFromSession = (folderName) => {
|
|
44
|
+
const projectDir = path.join(getSessionsDir(), folderName);
|
|
45
|
+
try {
|
|
46
|
+
const files = fs.readdirSync(projectDir);
|
|
47
|
+
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
48
|
+
for (const sessionFile of sessionFiles) {
|
|
49
|
+
const cwd = tryGetCwdFromFile(path.join(projectDir, sessionFile));
|
|
50
|
+
if (cwd && pathToFolderName(cwd) === folderName) {
|
|
51
|
+
return cwd;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
12
59
|
|
|
13
60
|
// src/utils.ts
|
|
14
61
|
var extractTextContent = (message) => {
|
|
@@ -49,16 +96,16 @@ var isContinuationSummary = (msg) => {
|
|
|
49
96
|
|
|
50
97
|
// src/agents.ts
|
|
51
98
|
import { Effect } from "effect";
|
|
52
|
-
import * as
|
|
99
|
+
import * as fs2 from "fs/promises";
|
|
53
100
|
import * as path2 from "path";
|
|
54
101
|
var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
|
|
55
102
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
56
|
-
const files = yield* Effect.tryPromise(() =>
|
|
103
|
+
const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
|
|
57
104
|
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
58
105
|
const linkedAgents = [];
|
|
59
106
|
for (const agentFile of agentFiles) {
|
|
60
107
|
const filePath = path2.join(projectPath, agentFile);
|
|
61
|
-
const content = yield* Effect.tryPromise(() =>
|
|
108
|
+
const content = yield* Effect.tryPromise(() => fs2.readFile(filePath, "utf-8"));
|
|
62
109
|
const firstLine = content.split("\n")[0];
|
|
63
110
|
if (firstLine) {
|
|
64
111
|
try {
|
|
@@ -74,7 +121,7 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
|
|
|
74
121
|
});
|
|
75
122
|
var findOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
76
123
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
77
|
-
const files = yield* Effect.tryPromise(() =>
|
|
124
|
+
const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
|
|
78
125
|
const sessionIds = new Set(
|
|
79
126
|
files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
|
|
80
127
|
);
|
|
@@ -82,7 +129,7 @@ var findOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
|
82
129
|
const orphanAgents = [];
|
|
83
130
|
for (const agentFile of agentFiles) {
|
|
84
131
|
const filePath = path2.join(projectPath, agentFile);
|
|
85
|
-
const content = yield* Effect.tryPromise(() =>
|
|
132
|
+
const content = yield* Effect.tryPromise(() => fs2.readFile(filePath, "utf-8"));
|
|
86
133
|
const firstLine = content.split("\n")[0];
|
|
87
134
|
if (firstLine) {
|
|
88
135
|
try {
|
|
@@ -103,12 +150,12 @@ var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
|
103
150
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
104
151
|
const orphans = yield* findOrphanAgents(projectName);
|
|
105
152
|
const backupDir = path2.join(projectPath, ".bak");
|
|
106
|
-
yield* Effect.tryPromise(() =>
|
|
153
|
+
yield* Effect.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
|
|
107
154
|
const deletedAgents = [];
|
|
108
155
|
for (const orphan of orphans) {
|
|
109
156
|
const agentPath = path2.join(projectPath, `${orphan.agentId}.jsonl`);
|
|
110
157
|
const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
|
|
111
|
-
yield* Effect.tryPromise(() =>
|
|
158
|
+
yield* Effect.tryPromise(() => fs2.rename(agentPath, agentBackupPath));
|
|
112
159
|
deletedAgents.push(orphan.agentId);
|
|
113
160
|
}
|
|
114
161
|
return { success: true, deletedAgents, count: deletedAgents.length };
|
|
@@ -116,12 +163,12 @@ var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
|
116
163
|
|
|
117
164
|
// src/todos.ts
|
|
118
165
|
import { Effect as Effect2 } from "effect";
|
|
119
|
-
import * as
|
|
166
|
+
import * as fs3 from "fs/promises";
|
|
120
167
|
import * as path3 from "path";
|
|
121
168
|
var findLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
122
169
|
const todosDir = getTodosDir();
|
|
123
170
|
const exists = yield* Effect2.tryPromise(
|
|
124
|
-
() =>
|
|
171
|
+
() => fs3.access(todosDir).then(() => true).catch(() => false)
|
|
125
172
|
);
|
|
126
173
|
if (!exists) {
|
|
127
174
|
return {
|
|
@@ -134,10 +181,10 @@ var findLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
|
134
181
|
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
135
182
|
let sessionTodos = [];
|
|
136
183
|
const sessionTodoExists = yield* Effect2.tryPromise(
|
|
137
|
-
() =>
|
|
184
|
+
() => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
138
185
|
);
|
|
139
186
|
if (sessionTodoExists) {
|
|
140
|
-
const content = yield* Effect2.tryPromise(() =>
|
|
187
|
+
const content = yield* Effect2.tryPromise(() => fs3.readFile(sessionTodoPath, "utf-8"));
|
|
141
188
|
try {
|
|
142
189
|
sessionTodos = JSON.parse(content);
|
|
143
190
|
} catch {
|
|
@@ -148,10 +195,10 @@ var findLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
|
148
195
|
const shortAgentId = agentId.replace("agent-", "");
|
|
149
196
|
const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
150
197
|
const agentTodoExists = yield* Effect2.tryPromise(
|
|
151
|
-
() =>
|
|
198
|
+
() => fs3.access(agentTodoPath).then(() => true).catch(() => false)
|
|
152
199
|
);
|
|
153
200
|
if (agentTodoExists) {
|
|
154
|
-
const content = yield* Effect2.tryPromise(() =>
|
|
201
|
+
const content = yield* Effect2.tryPromise(() => fs3.readFile(agentTodoPath, "utf-8"));
|
|
155
202
|
try {
|
|
156
203
|
const todos = JSON.parse(content);
|
|
157
204
|
agentTodos.push({ agentId, todos });
|
|
@@ -170,15 +217,15 @@ var findLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
|
170
217
|
var sessionHasTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
171
218
|
const todosDir = getTodosDir();
|
|
172
219
|
const exists = yield* Effect2.tryPromise(
|
|
173
|
-
() =>
|
|
220
|
+
() => fs3.access(todosDir).then(() => true).catch(() => false)
|
|
174
221
|
);
|
|
175
222
|
if (!exists) return false;
|
|
176
223
|
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
177
224
|
const sessionTodoExists = yield* Effect2.tryPromise(
|
|
178
|
-
() =>
|
|
225
|
+
() => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
179
226
|
);
|
|
180
227
|
if (sessionTodoExists) {
|
|
181
|
-
const content = yield* Effect2.tryPromise(() =>
|
|
228
|
+
const content = yield* Effect2.tryPromise(() => fs3.readFile(sessionTodoPath, "utf-8"));
|
|
182
229
|
try {
|
|
183
230
|
const todos = JSON.parse(content);
|
|
184
231
|
if (todos.length > 0) return true;
|
|
@@ -189,10 +236,10 @@ var sessionHasTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
|
189
236
|
const shortAgentId = agentId.replace("agent-", "");
|
|
190
237
|
const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
191
238
|
const agentTodoExists = yield* Effect2.tryPromise(
|
|
192
|
-
() =>
|
|
239
|
+
() => fs3.access(agentTodoPath).then(() => true).catch(() => false)
|
|
193
240
|
);
|
|
194
241
|
if (agentTodoExists) {
|
|
195
|
-
const content = yield* Effect2.tryPromise(() =>
|
|
242
|
+
const content = yield* Effect2.tryPromise(() => fs3.readFile(agentTodoPath, "utf-8"));
|
|
196
243
|
try {
|
|
197
244
|
const todos = JSON.parse(content);
|
|
198
245
|
if (todos.length > 0) return true;
|
|
@@ -205,30 +252,30 @@ var sessionHasTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
|
205
252
|
var deleteLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
|
|
206
253
|
const todosDir = getTodosDir();
|
|
207
254
|
const exists = yield* Effect2.tryPromise(
|
|
208
|
-
() =>
|
|
255
|
+
() => fs3.access(todosDir).then(() => true).catch(() => false)
|
|
209
256
|
);
|
|
210
257
|
if (!exists) return { deletedCount: 0 };
|
|
211
258
|
const backupDir = path3.join(todosDir, ".bak");
|
|
212
|
-
yield* Effect2.tryPromise(() =>
|
|
259
|
+
yield* Effect2.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
|
|
213
260
|
let deletedCount = 0;
|
|
214
261
|
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
215
262
|
const sessionTodoExists = yield* Effect2.tryPromise(
|
|
216
|
-
() =>
|
|
263
|
+
() => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
217
264
|
);
|
|
218
265
|
if (sessionTodoExists) {
|
|
219
266
|
const backupPath = path3.join(backupDir, `${sessionId}.json`);
|
|
220
|
-
yield* Effect2.tryPromise(() =>
|
|
267
|
+
yield* Effect2.tryPromise(() => fs3.rename(sessionTodoPath, backupPath));
|
|
221
268
|
deletedCount++;
|
|
222
269
|
}
|
|
223
270
|
for (const agentId of agentIds) {
|
|
224
271
|
const shortAgentId = agentId.replace("agent-", "");
|
|
225
272
|
const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
226
273
|
const agentTodoExists = yield* Effect2.tryPromise(
|
|
227
|
-
() =>
|
|
274
|
+
() => fs3.access(agentTodoPath).then(() => true).catch(() => false)
|
|
228
275
|
);
|
|
229
276
|
if (agentTodoExists) {
|
|
230
277
|
const backupPath = path3.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`);
|
|
231
|
-
yield* Effect2.tryPromise(() =>
|
|
278
|
+
yield* Effect2.tryPromise(() => fs3.rename(agentTodoPath, backupPath));
|
|
232
279
|
deletedCount++;
|
|
233
280
|
}
|
|
234
281
|
}
|
|
@@ -239,23 +286,23 @@ var findOrphanTodos = () => Effect2.gen(function* () {
|
|
|
239
286
|
const sessionsDir = getSessionsDir();
|
|
240
287
|
const [todosExists, sessionsExists] = yield* Effect2.all([
|
|
241
288
|
Effect2.tryPromise(
|
|
242
|
-
() =>
|
|
289
|
+
() => fs3.access(todosDir).then(() => true).catch(() => false)
|
|
243
290
|
),
|
|
244
291
|
Effect2.tryPromise(
|
|
245
|
-
() =>
|
|
292
|
+
() => fs3.access(sessionsDir).then(() => true).catch(() => false)
|
|
246
293
|
)
|
|
247
294
|
]);
|
|
248
295
|
if (!todosExists || !sessionsExists) return [];
|
|
249
|
-
const todoFiles = yield* Effect2.tryPromise(() =>
|
|
296
|
+
const todoFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
|
|
250
297
|
const jsonFiles = todoFiles.filter((f) => f.endsWith(".json"));
|
|
251
298
|
const validSessionIds = /* @__PURE__ */ new Set();
|
|
252
299
|
const projectEntries = yield* Effect2.tryPromise(
|
|
253
|
-
() =>
|
|
300
|
+
() => fs3.readdir(sessionsDir, { withFileTypes: true })
|
|
254
301
|
);
|
|
255
302
|
for (const entry of projectEntries) {
|
|
256
303
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
257
304
|
const projectPath = path3.join(sessionsDir, entry.name);
|
|
258
|
-
const files = yield* Effect2.tryPromise(() =>
|
|
305
|
+
const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
|
|
259
306
|
for (const f of files) {
|
|
260
307
|
if (f.endsWith(".jsonl") && !f.startsWith("agent-")) {
|
|
261
308
|
validSessionIds.add(f.replace(".jsonl", ""));
|
|
@@ -279,12 +326,12 @@ var deleteOrphanTodos = () => Effect2.gen(function* () {
|
|
|
279
326
|
const orphans = yield* findOrphanTodos();
|
|
280
327
|
if (orphans.length === 0) return { success: true, deletedCount: 0 };
|
|
281
328
|
const backupDir = path3.join(todosDir, ".bak");
|
|
282
|
-
yield* Effect2.tryPromise(() =>
|
|
329
|
+
yield* Effect2.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
|
|
283
330
|
let deletedCount = 0;
|
|
284
331
|
for (const orphan of orphans) {
|
|
285
332
|
const filePath = path3.join(todosDir, orphan);
|
|
286
333
|
const backupPath = path3.join(backupDir, orphan);
|
|
287
|
-
yield* Effect2.tryPromise(() =>
|
|
334
|
+
yield* Effect2.tryPromise(() => fs3.rename(filePath, backupPath));
|
|
288
335
|
deletedCount++;
|
|
289
336
|
}
|
|
290
337
|
return { success: true, deletedCount };
|
|
@@ -292,26 +339,26 @@ var deleteOrphanTodos = () => Effect2.gen(function* () {
|
|
|
292
339
|
|
|
293
340
|
// src/session.ts
|
|
294
341
|
import { Effect as Effect3, pipe, Array as A, Option as O } from "effect";
|
|
295
|
-
import * as
|
|
342
|
+
import * as fs4 from "fs/promises";
|
|
296
343
|
import * as path4 from "path";
|
|
297
344
|
var listProjects = Effect3.gen(function* () {
|
|
298
345
|
const sessionsDir = getSessionsDir();
|
|
299
346
|
const exists = yield* Effect3.tryPromise(
|
|
300
|
-
() =>
|
|
347
|
+
() => fs4.access(sessionsDir).then(() => true).catch(() => false)
|
|
301
348
|
);
|
|
302
349
|
if (!exists) {
|
|
303
350
|
return [];
|
|
304
351
|
}
|
|
305
|
-
const entries = yield* Effect3.tryPromise(() =>
|
|
352
|
+
const entries = yield* Effect3.tryPromise(() => fs4.readdir(sessionsDir, { withFileTypes: true }));
|
|
306
353
|
const projects = yield* Effect3.all(
|
|
307
354
|
entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map(
|
|
308
355
|
(entry) => Effect3.gen(function* () {
|
|
309
356
|
const projectPath = path4.join(sessionsDir, entry.name);
|
|
310
|
-
const files = yield* Effect3.tryPromise(() =>
|
|
357
|
+
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
311
358
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
312
359
|
return {
|
|
313
360
|
name: entry.name,
|
|
314
|
-
displayName:
|
|
361
|
+
displayName: folderNameToPath(entry.name),
|
|
315
362
|
path: projectPath,
|
|
316
363
|
sessionCount: sessionFiles.length
|
|
317
364
|
};
|
|
@@ -323,13 +370,13 @@ var listProjects = Effect3.gen(function* () {
|
|
|
323
370
|
});
|
|
324
371
|
var listSessions = (projectName) => Effect3.gen(function* () {
|
|
325
372
|
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
326
|
-
const files = yield* Effect3.tryPromise(() =>
|
|
373
|
+
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
327
374
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
328
375
|
const sessions = yield* Effect3.all(
|
|
329
376
|
sessionFiles.map(
|
|
330
377
|
(file) => Effect3.gen(function* () {
|
|
331
378
|
const filePath = path4.join(projectPath, file);
|
|
332
|
-
const content = yield* Effect3.tryPromise(() =>
|
|
379
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
333
380
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
334
381
|
const messages = lines.map((line) => JSON.parse(line));
|
|
335
382
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -369,13 +416,13 @@ var listSessions = (projectName) => Effect3.gen(function* () {
|
|
|
369
416
|
});
|
|
370
417
|
var readSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
371
418
|
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
372
|
-
const content = yield* Effect3.tryPromise(() =>
|
|
419
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
373
420
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
374
421
|
return lines.map((line) => JSON.parse(line));
|
|
375
422
|
});
|
|
376
423
|
var deleteMessage = (projectName, sessionId, messageUuid) => Effect3.gen(function* () {
|
|
377
424
|
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
378
|
-
const content = yield* Effect3.tryPromise(() =>
|
|
425
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
379
426
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
380
427
|
const messages = lines.map((line) => JSON.parse(line));
|
|
381
428
|
const targetIndex = messages.findIndex(
|
|
@@ -394,7 +441,7 @@ var deleteMessage = (projectName, sessionId, messageUuid) => Effect3.gen(functio
|
|
|
394
441
|
}
|
|
395
442
|
messages.splice(targetIndex, 1);
|
|
396
443
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
397
|
-
yield* Effect3.tryPromise(() =>
|
|
444
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
398
445
|
return { success: true };
|
|
399
446
|
});
|
|
400
447
|
var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
@@ -402,33 +449,33 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
402
449
|
const projectPath = path4.join(sessionsDir, projectName);
|
|
403
450
|
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
404
451
|
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
405
|
-
const stat2 = yield* Effect3.tryPromise(() =>
|
|
452
|
+
const stat2 = yield* Effect3.tryPromise(() => fs4.stat(filePath));
|
|
406
453
|
if (stat2.size === 0) {
|
|
407
|
-
yield* Effect3.tryPromise(() =>
|
|
454
|
+
yield* Effect3.tryPromise(() => fs4.unlink(filePath));
|
|
408
455
|
const agentBackupDir2 = path4.join(projectPath, ".bak");
|
|
409
|
-
yield* Effect3.tryPromise(() =>
|
|
456
|
+
yield* Effect3.tryPromise(() => fs4.mkdir(agentBackupDir2, { recursive: true }));
|
|
410
457
|
for (const agentId of linkedAgents) {
|
|
411
458
|
const agentPath = path4.join(projectPath, `${agentId}.jsonl`);
|
|
412
459
|
const agentBackupPath = path4.join(agentBackupDir2, `${agentId}.jsonl`);
|
|
413
|
-
yield* Effect3.tryPromise(() =>
|
|
460
|
+
yield* Effect3.tryPromise(() => fs4.rename(agentPath, agentBackupPath).catch(() => {
|
|
414
461
|
}));
|
|
415
462
|
}
|
|
416
463
|
yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
417
464
|
return { success: true, deletedAgents: linkedAgents.length };
|
|
418
465
|
}
|
|
419
466
|
const backupDir = path4.join(sessionsDir, ".bak");
|
|
420
|
-
yield* Effect3.tryPromise(() =>
|
|
467
|
+
yield* Effect3.tryPromise(() => fs4.mkdir(backupDir, { recursive: true }));
|
|
421
468
|
const agentBackupDir = path4.join(projectPath, ".bak");
|
|
422
|
-
yield* Effect3.tryPromise(() =>
|
|
469
|
+
yield* Effect3.tryPromise(() => fs4.mkdir(agentBackupDir, { recursive: true }));
|
|
423
470
|
for (const agentId of linkedAgents) {
|
|
424
471
|
const agentPath = path4.join(projectPath, `${agentId}.jsonl`);
|
|
425
472
|
const agentBackupPath = path4.join(agentBackupDir, `${agentId}.jsonl`);
|
|
426
|
-
yield* Effect3.tryPromise(() =>
|
|
473
|
+
yield* Effect3.tryPromise(() => fs4.rename(agentPath, agentBackupPath).catch(() => {
|
|
427
474
|
}));
|
|
428
475
|
}
|
|
429
476
|
const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
|
|
430
477
|
const backupPath = path4.join(backupDir, `${projectName}_${sessionId}.jsonl`);
|
|
431
|
-
yield* Effect3.tryPromise(() =>
|
|
478
|
+
yield* Effect3.tryPromise(() => fs4.rename(filePath, backupPath));
|
|
432
479
|
return {
|
|
433
480
|
success: true,
|
|
434
481
|
backupPath,
|
|
@@ -438,7 +485,7 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
438
485
|
});
|
|
439
486
|
var renameSession = (projectName, sessionId, newTitle) => Effect3.gen(function* () {
|
|
440
487
|
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
441
|
-
const content = yield* Effect3.tryPromise(() =>
|
|
488
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
442
489
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
443
490
|
if (lines.length === 0) {
|
|
444
491
|
return { success: false, error: "Empty session" };
|
|
@@ -463,7 +510,7 @@ ${cleanedText}`;
|
|
|
463
510
|
}
|
|
464
511
|
}
|
|
465
512
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
466
|
-
yield* Effect3.tryPromise(() =>
|
|
513
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
467
514
|
return { success: true };
|
|
468
515
|
});
|
|
469
516
|
var getSessionFiles = (projectName, sessionId) => Effect3.gen(function* () {
|
|
@@ -525,28 +572,28 @@ var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(funct
|
|
|
525
572
|
const sourceFile = path4.join(sourcePath, `${sessionId}.jsonl`);
|
|
526
573
|
const targetFile = path4.join(targetPath, `${sessionId}.jsonl`);
|
|
527
574
|
const sourceExists = yield* Effect3.tryPromise(
|
|
528
|
-
() =>
|
|
575
|
+
() => fs4.access(sourceFile).then(() => true).catch(() => false)
|
|
529
576
|
);
|
|
530
577
|
if (!sourceExists) {
|
|
531
578
|
return { success: false, error: "Source session not found" };
|
|
532
579
|
}
|
|
533
580
|
const targetExists = yield* Effect3.tryPromise(
|
|
534
|
-
() =>
|
|
581
|
+
() => fs4.access(targetFile).then(() => true).catch(() => false)
|
|
535
582
|
);
|
|
536
583
|
if (targetExists) {
|
|
537
584
|
return { success: false, error: "Session already exists in target project" };
|
|
538
585
|
}
|
|
539
|
-
yield* Effect3.tryPromise(() =>
|
|
586
|
+
yield* Effect3.tryPromise(() => fs4.mkdir(targetPath, { recursive: true }));
|
|
540
587
|
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
541
|
-
yield* Effect3.tryPromise(() =>
|
|
588
|
+
yield* Effect3.tryPromise(() => fs4.rename(sourceFile, targetFile));
|
|
542
589
|
for (const agentId of linkedAgents) {
|
|
543
590
|
const sourceAgentFile = path4.join(sourcePath, `${agentId}.jsonl`);
|
|
544
591
|
const targetAgentFile = path4.join(targetPath, `${agentId}.jsonl`);
|
|
545
592
|
const agentExists = yield* Effect3.tryPromise(
|
|
546
|
-
() =>
|
|
593
|
+
() => fs4.access(sourceAgentFile).then(() => true).catch(() => false)
|
|
547
594
|
);
|
|
548
595
|
if (agentExists) {
|
|
549
|
-
yield* Effect3.tryPromise(() =>
|
|
596
|
+
yield* Effect3.tryPromise(() => fs4.rename(sourceAgentFile, targetAgentFile));
|
|
550
597
|
}
|
|
551
598
|
}
|
|
552
599
|
return { success: true };
|
|
@@ -554,7 +601,7 @@ var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(funct
|
|
|
554
601
|
var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(function* () {
|
|
555
602
|
const projectPath = path4.join(getSessionsDir(), projectName);
|
|
556
603
|
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
557
|
-
const content = yield* Effect3.tryPromise(() =>
|
|
604
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
558
605
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
559
606
|
const allMessages = lines.map((line) => JSON.parse(line));
|
|
560
607
|
const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
|
|
@@ -588,15 +635,15 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
588
635
|
return updated;
|
|
589
636
|
});
|
|
590
637
|
const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
591
|
-
yield* Effect3.tryPromise(() =>
|
|
638
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, remainingContent, "utf-8"));
|
|
592
639
|
const newFilePath = path4.join(projectPath, `${newSessionId}.jsonl`);
|
|
593
640
|
const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
594
|
-
yield* Effect3.tryPromise(() =>
|
|
595
|
-
const agentFiles = yield* Effect3.tryPromise(() =>
|
|
641
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(newFilePath, newContent, "utf-8"));
|
|
642
|
+
const agentFiles = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
596
643
|
const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
597
644
|
for (const agentFile of agentJsonlFiles) {
|
|
598
645
|
const agentPath = path4.join(projectPath, agentFile);
|
|
599
|
-
const agentContent = yield* Effect3.tryPromise(() =>
|
|
646
|
+
const agentContent = yield* Effect3.tryPromise(() => fs4.readFile(agentPath, "utf-8"));
|
|
600
647
|
const agentLines = agentContent.trim().split("\n").filter(Boolean);
|
|
601
648
|
if (agentLines.length === 0) continue;
|
|
602
649
|
const firstAgentMsg = JSON.parse(agentLines[0]);
|
|
@@ -611,7 +658,7 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
611
658
|
return JSON.stringify({ ...msg, sessionId: newSessionId });
|
|
612
659
|
});
|
|
613
660
|
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
614
|
-
yield* Effect3.tryPromise(() =>
|
|
661
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(agentPath, updatedAgentContent, "utf-8"));
|
|
615
662
|
}
|
|
616
663
|
}
|
|
617
664
|
}
|
|
@@ -625,7 +672,7 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect3.gen(f
|
|
|
625
672
|
});
|
|
626
673
|
var cleanInvalidMessages = (projectName, sessionId) => Effect3.gen(function* () {
|
|
627
674
|
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
628
|
-
const content = yield* Effect3.tryPromise(() =>
|
|
675
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
629
676
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
630
677
|
if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
|
|
631
678
|
const messages = lines.map((line) => JSON.parse(line));
|
|
@@ -657,7 +704,7 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect3.gen(function* ()
|
|
|
657
704
|
lastValidUuid = msg.uuid;
|
|
658
705
|
}
|
|
659
706
|
const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
|
|
660
|
-
yield* Effect3.tryPromise(() =>
|
|
707
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
661
708
|
const remainingUserAssistant = filtered.filter(
|
|
662
709
|
(m) => m.type === "user" || m.type === "assistant"
|
|
663
710
|
).length;
|
|
@@ -724,7 +771,7 @@ var clearSessions = (options) => Effect3.gen(function* () {
|
|
|
724
771
|
if (clearInvalid) {
|
|
725
772
|
for (const project of targetProjects) {
|
|
726
773
|
const projectPath = path4.join(getSessionsDir(), project.name);
|
|
727
|
-
const files = yield* Effect3.tryPromise(() =>
|
|
774
|
+
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
|
|
728
775
|
const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
729
776
|
for (const file of sessionFiles) {
|
|
730
777
|
const sessionId = file.replace(".jsonl", "");
|
|
@@ -793,6 +840,8 @@ export {
|
|
|
793
840
|
findOrphanAgents,
|
|
794
841
|
findOrphanTodos,
|
|
795
842
|
folderNameToDisplayPath,
|
|
843
|
+
folderNameToPath,
|
|
844
|
+
getRealPathFromSession,
|
|
796
845
|
getSessionFiles,
|
|
797
846
|
getSessionsDir,
|
|
798
847
|
getTodosDir,
|
|
@@ -801,6 +850,7 @@ export {
|
|
|
801
850
|
listProjects,
|
|
802
851
|
listSessions,
|
|
803
852
|
moveSession,
|
|
853
|
+
pathToFolderName,
|
|
804
854
|
previewCleanup,
|
|
805
855
|
readSession,
|
|
806
856
|
renameSession,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/paths.ts","../src/utils.ts","../src/agents.ts","../src/todos.ts","../src/session.ts"],"sourcesContent":["/**\n * Path utilities for Claude Code session management\n */\nimport * as path from 'node:path'\nimport * as os from 'node:os'\n\n// Get Claude sessions directory (~/.claude/projects)\nexport const getSessionsDir = (): string => path.join(os.homedir(), '.claude', 'projects')\n\n// Get Claude todos directory (~/.claude/todos)\nexport const getTodosDir = (): string => path.join(os.homedir(), '.claude', 'todos')\n\n// Convert project folder name to display path\n// e.g., -Users-david-works -> /Users/david/works\n// Handle dot-prefixed folders: --claude -> /.claude, -works--vscode -> /works/.vscode\nexport const folderNameToDisplayPath = (folderName: string): string => {\n return folderName\n .replace(/^-/, '/')\n .replace(/--/g, '/.') // double dash means dot-prefixed folder\n .replace(/-/g, '/')\n}\n\n// Convert display path to folder name (reverse of above)\nexport const displayPathToFolderName = (displayPath: string): string => {\n return displayPath\n .replace(/^\\//g, '-')\n .replace(/\\/\\./g, '--') // dot-prefixed folder becomes double dash\n .replace(/\\//g, '-')\n}\n","/**\n * Utility functions for message processing\n */\nimport type { ContentItem, Message, MessagePayload } from './types.js'\n\n// Extract text content from message payload\nexport const extractTextContent = (message: MessagePayload | undefined): string => {\n if (!message) return ''\n\n const content = message.content\n if (!content) return ''\n\n // If content is string, return directly\n if (typeof content === 'string') return content\n\n // If content is array, extract text items\n if (Array.isArray(content)) {\n return content\n .filter((item): item is ContentItem => typeof item === 'object' && item?.type === 'text')\n .map((item) => item.text ?? '')\n .join('')\n }\n\n return ''\n}\n\n// Extract title from text content (remove IDE tags, use first line)\nexport const extractTitle = (text: string): string => {\n if (!text) return 'Untitled'\n\n // Remove IDE tags (<ide_opened_file>, <ide_selection>, etc.)\n let cleaned = text.replace(/<ide_[^>]*>[\\s\\S]*?<\\/ide_[^>]*>/g, '').trim()\n\n if (!cleaned) return 'Untitled'\n\n // Use only content before \\n\\n or \\n as title\n if (cleaned.includes('\\n\\n')) {\n cleaned = cleaned.split('\\n\\n')[0]\n } else if (cleaned.includes('\\n')) {\n cleaned = cleaned.split('\\n')[0]\n }\n\n // Limit to 100 characters\n if (cleaned.length > 100) {\n return cleaned.slice(0, 100) + '...'\n }\n\n return cleaned || 'Untitled'\n}\n\n// Check if message contains \"Invalid API key\"\nexport const isInvalidApiKeyMessage = (msg: Message): boolean => {\n const text = extractTextContent(msg.message)\n return text.includes('Invalid API key')\n}\n\n// Check if a message is a continuation summary (from compact)\nexport const isContinuationSummary = (msg: Record<string, unknown>): boolean => {\n // isCompactSummary flag is set by Claude Code for continuation summaries\n if (msg.isCompactSummary === true) return true\n\n // Fallback: check message content\n if (msg.type !== 'user') return false\n const message = msg.message as { content?: string } | undefined\n const content = message?.content ?? ''\n return content.startsWith('This session is being continued from')\n}\n","/**\n * Agent file management utilities\n */\nimport { Effect } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir } from './paths.js'\n\n// Find agent files linked to a session\nexport const findLinkedAgents = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n\n const linkedAgents: string[] = []\n\n for (const agentFile of agentFiles) {\n const filePath = path.join(projectPath, agentFile)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const firstLine = content.split('\\n')[0]\n\n if (firstLine) {\n try {\n const parsed = JSON.parse(firstLine) as { sessionId?: string }\n if (parsed.sessionId === sessionId) {\n linkedAgents.push(agentFile.replace('.jsonl', ''))\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n return linkedAgents\n })\n\n// Find orphan agent files (agents whose parent session no longer exists)\nexport const findOrphanAgents = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n\n const sessionIds = new Set(\n files\n .filter((f) => !f.startsWith('agent-') && f.endsWith('.jsonl'))\n .map((f) => f.replace('.jsonl', ''))\n )\n\n const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n const orphanAgents: Array<{ agentId: string; sessionId: string }> = []\n\n for (const agentFile of agentFiles) {\n const filePath = path.join(projectPath, agentFile)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const firstLine = content.split('\\n')[0]\n\n if (firstLine) {\n try {\n const parsed = JSON.parse(firstLine) as { sessionId?: string }\n if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {\n orphanAgents.push({\n agentId: agentFile.replace('.jsonl', ''),\n sessionId: parsed.sessionId,\n })\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n return orphanAgents\n })\n\n// Delete orphan agent files (move to .bak)\nexport const deleteOrphanAgents = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const orphans = yield* findOrphanAgents(projectName)\n\n // Create backup directory\n const backupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n const deletedAgents: string[] = []\n\n for (const orphan of orphans) {\n const agentPath = path.join(projectPath, `${orphan.agentId}.jsonl`)\n const agentBackupPath = path.join(backupDir, `${orphan.agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath))\n deletedAgents.push(orphan.agentId)\n }\n\n return { success: true, deletedAgents, count: deletedAgents.length }\n })\n","/**\n * Todo file management utilities\n */\nimport { Effect } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir, getTodosDir } from './paths.js'\nimport type { TodoItem, SessionTodos } from './types.js'\n\n// Find linked todo files for a session and its agents\nexport const findLinkedTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) {\n return {\n sessionId,\n sessionTodos: [],\n agentTodos: [],\n hasTodos: false,\n } satisfies SessionTodos\n }\n\n // Read session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n let sessionTodos: TodoItem[] = []\n\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(sessionTodoPath, 'utf-8'))\n try {\n sessionTodos = JSON.parse(content) as TodoItem[]\n } catch {\n // Invalid JSON, treat as empty\n }\n }\n\n // Read agent todo files\n const agentTodos: { agentId: string; todos: TodoItem[] }[] = []\n\n for (const agentId of agentIds) {\n // Agent todo files are named: {sessionId}-{agentId}.json\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(agentTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n agentTodos.push({ agentId, todos })\n } catch {\n // Invalid JSON, skip\n }\n }\n }\n\n const hasTodos = sessionTodos.length > 0 || agentTodos.some((at) => at.todos.length > 0)\n\n return {\n sessionId,\n sessionTodos,\n agentTodos,\n hasTodos,\n } satisfies SessionTodos\n })\n\n// Check if session has any todos (quick check)\nexport const sessionHasTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) return false\n\n // Check session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(sessionTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n if (todos.length > 0) return true\n } catch {\n // Invalid JSON, continue\n }\n }\n\n // Check agent todo files\n for (const agentId of agentIds) {\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(agentTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n if (todos.length > 0) return true\n } catch {\n // Invalid JSON, continue\n }\n }\n }\n\n return false\n })\n\n// Delete linked todo files for a session (move to .bak)\nexport const deleteLinkedTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) return { deletedCount: 0 }\n\n // Create backup directory\n const backupDir = path.join(todosDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n let deletedCount = 0\n\n // Delete session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const backupPath = path.join(backupDir, `${sessionId}.json`)\n yield* Effect.tryPromise(() => fs.rename(sessionTodoPath, backupPath))\n deletedCount++\n }\n\n // Delete agent todo files\n for (const agentId of agentIds) {\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const backupPath = path.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`)\n yield* Effect.tryPromise(() => fs.rename(agentTodoPath, backupPath))\n deletedCount++\n }\n }\n\n return { deletedCount }\n })\n\n// Find all orphan todo files (session no longer exists)\nexport const findOrphanTodos = () =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n const sessionsDir = getSessionsDir()\n\n // Check if directories exist\n const [todosExists, sessionsExists] = yield* Effect.all([\n Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n ),\n Effect.tryPromise(() =>\n fs\n .access(sessionsDir)\n .then(() => true)\n .catch(() => false)\n ),\n ])\n\n if (!todosExists || !sessionsExists) return []\n\n // Get all todo files\n const todoFiles = yield* Effect.tryPromise(() => fs.readdir(todosDir))\n const jsonFiles = todoFiles.filter((f) => f.endsWith('.json'))\n\n // Build set of all valid session IDs across all projects\n const validSessionIds = new Set<string>()\n const projectEntries = yield* Effect.tryPromise(() =>\n fs.readdir(sessionsDir, { withFileTypes: true })\n )\n\n for (const entry of projectEntries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue\n const projectPath = path.join(sessionsDir, entry.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n for (const f of files) {\n if (f.endsWith('.jsonl') && !f.startsWith('agent-')) {\n validSessionIds.add(f.replace('.jsonl', ''))\n }\n }\n }\n\n // Find orphan todo files\n const orphans: string[] = []\n for (const todoFile of jsonFiles) {\n // Parse session ID from todo filename\n // Format: {sessionId}.json or {sessionId}-agent-{agentId}.json\n const match = todoFile.match(/^([a-f0-9-]+)(?:-agent-[a-f0-9]+)?\\.json$/)\n if (match) {\n const sessionId = match[1]\n if (!validSessionIds.has(sessionId)) {\n orphans.push(todoFile)\n }\n }\n }\n\n return orphans\n })\n\n// Delete orphan todo files\nexport const deleteOrphanTodos = () =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n const orphans = yield* findOrphanTodos()\n\n if (orphans.length === 0) return { success: true, deletedCount: 0 }\n\n // Create backup directory\n const backupDir = path.join(todosDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n let deletedCount = 0\n\n for (const orphan of orphans) {\n const filePath = path.join(todosDir, orphan)\n const backupPath = path.join(backupDir, orphan)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n deletedCount++\n }\n\n return { success: true, deletedCount }\n })\n","/**\n * Session management operations\n */\nimport { Effect, pipe, Array as A, Option as O } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir, folderNameToDisplayPath } from './paths.js'\nimport { extractTextContent, extractTitle, isInvalidApiKeyMessage, isContinuationSummary } from './utils.js'\nimport { findLinkedAgents, findOrphanAgents, deleteOrphanAgents } from './agents.js'\nimport { deleteLinkedTodos, sessionHasTodos, findOrphanTodos, deleteOrphanTodos } from './todos.js'\nimport type {\n Message,\n SessionMeta,\n Project,\n FileChange,\n SessionFilesSummary,\n DeleteSessionResult,\n RenameSessionResult,\n SplitSessionResult,\n MoveSessionResult,\n ClearSessionsResult,\n CleanupPreview,\n ContentItem,\n} from './types.js'\n\n// List all project directories\nexport const listProjects = Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionsDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) {\n return [] as Project[]\n }\n\n const entries = yield* Effect.tryPromise(() => fs.readdir(sessionsDir, { withFileTypes: true }))\n\n const projects = yield* Effect.all(\n entries\n .filter((e) => e.isDirectory() && !e.name.startsWith('.'))\n .map((entry) =>\n Effect.gen(function* () {\n const projectPath = path.join(sessionsDir, entry.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n // Exclude agent- files (subagent logs)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n return {\n name: entry.name,\n displayName: folderNameToDisplayPath(entry.name),\n path: projectPath,\n sessionCount: sessionFiles.length,\n } satisfies Project\n })\n ),\n { concurrency: 10 }\n )\n\n return projects\n})\n\n// List sessions in a project\nexport const listSessions = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n // Exclude agent- files (subagent logs)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n const sessions = yield* Effect.all(\n sessionFiles.map((file) =>\n Effect.gen(function* () {\n const filePath = path.join(projectPath, file)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n const sessionId = file.replace('.jsonl', '')\n\n // Filter only user/assistant messages for counting\n const userAssistantMessages = messages.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n )\n\n // Check if session has summary (for preserved sessions without user/assistant messages)\n const hasSummary = messages.some((m) => m.type === 'summary')\n\n const firstMessage = userAssistantMessages[0]\n const lastMessage = userAssistantMessages[userAssistantMessages.length - 1]\n\n // Extract title from first user message\n const title = pipe(\n messages,\n A.findFirst((m) => m.type === 'user'),\n O.map((m) => {\n const text = extractTextContent(m.message)\n return extractTitle(text)\n }),\n O.getOrElse(() => (hasSummary ? '[Summary Only]' : `Session ${sessionId.slice(0, 8)}`))\n )\n\n return {\n id: sessionId,\n projectName,\n title,\n // If session has summary but no user/assistant messages, count as 1\n messageCount:\n userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,\n createdAt: firstMessage?.timestamp,\n updatedAt: lastMessage?.timestamp,\n } satisfies SessionMeta\n })\n ),\n { concurrency: 10 }\n )\n\n // Sort by newest first\n return sessions.sort((a, b) => {\n const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0\n const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0\n return dateB - dateA\n })\n })\n\n// Read session messages\nexport const readSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n return lines.map((line) => JSON.parse(line) as Message)\n })\n\n// Delete a message from session and repair parentUuid chain\nexport const deleteMessage = (projectName: string, sessionId: string, messageUuid: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n const messages = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n // Find by uuid or messageId (for file-history-snapshot type)\n const targetIndex = messages.findIndex(\n (m) => m.uuid === messageUuid || m.messageId === messageUuid\n )\n if (targetIndex === -1) {\n return { success: false, error: 'Message not found' }\n }\n\n // Get the deleted message's uuid and parentUuid\n const deletedMsg = messages[targetIndex]\n const deletedUuid = deletedMsg?.uuid ?? deletedMsg?.messageId\n const parentUuid = deletedMsg?.parentUuid\n\n // Find all messages that reference the deleted message as their parent\n // and update them to point to the deleted message's parent\n for (const msg of messages) {\n if (msg.parentUuid === deletedUuid) {\n msg.parentUuid = parentUuid\n }\n }\n\n // Remove the message\n messages.splice(targetIndex, 1)\n\n const newContent = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n return { success: true }\n })\n\n// Delete a session and its linked agent/todo files\nexport const deleteSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const projectPath = path.join(sessionsDir, projectName)\n const filePath = path.join(projectPath, `${sessionId}.jsonl`)\n\n // Find linked agents first (before any deletion)\n const linkedAgents = yield* findLinkedAgents(projectName, sessionId)\n\n // Check file size - if empty (0 bytes), just delete without backup\n const stat = yield* Effect.tryPromise(() => fs.stat(filePath))\n if (stat.size === 0) {\n yield* Effect.tryPromise(() => fs.unlink(filePath))\n // Still delete linked agents and todos for empty sessions\n const agentBackupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(agentBackupDir, { recursive: true }))\n for (const agentId of linkedAgents) {\n const agentPath = path.join(projectPath, `${agentId}.jsonl`)\n const agentBackupPath = path.join(agentBackupDir, `${agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath).catch(() => {}))\n }\n yield* deleteLinkedTodos(sessionId, linkedAgents)\n return { success: true, deletedAgents: linkedAgents.length } satisfies DeleteSessionResult\n }\n\n // Create backup directory\n const backupDir = path.join(sessionsDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n // Delete linked agent files (move to .bak in project folder)\n const agentBackupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(agentBackupDir, { recursive: true }))\n for (const agentId of linkedAgents) {\n const agentPath = path.join(projectPath, `${agentId}.jsonl`)\n const agentBackupPath = path.join(agentBackupDir, `${agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath).catch(() => {}))\n }\n\n // Delete linked todo files\n const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents)\n\n // Move session to backup (format: project_name_session_id.jsonl)\n const backupPath = path.join(backupDir, `${projectName}_${sessionId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n\n return {\n success: true,\n backupPath,\n deletedAgents: linkedAgents.length,\n deletedTodos: todosResult.deletedCount,\n } satisfies DeleteSessionResult\n })\n\n// Rename session by adding title prefix\nexport const renameSession = (projectName: string, sessionId: string, newTitle: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n if (lines.length === 0) {\n return { success: false, error: 'Empty session' } satisfies RenameSessionResult\n }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n // Find first user message\n const firstUserIdx = messages.findIndex((m) => m.type === 'user')\n if (firstUserIdx === -1) {\n return { success: false, error: 'No user message found' } satisfies RenameSessionResult\n }\n\n const firstMsg = messages[firstUserIdx]\n if (firstMsg?.message?.content && Array.isArray(firstMsg.message.content)) {\n // Find first non-IDE text content\n const textIdx = firstMsg.message.content.findIndex(\n (item): item is ContentItem =>\n typeof item === 'object' &&\n item?.type === 'text' &&\n !item.text?.trim().startsWith('<ide_')\n )\n\n if (textIdx >= 0) {\n const item = firstMsg.message.content[textIdx] as ContentItem\n const oldText = item.text ?? ''\n // Remove existing title pattern (first line ending with \\n\\n)\n const cleanedText = oldText.replace(/^[^\\n]+\\n\\n/, '')\n item.text = `${newTitle}\\n\\n${cleanedText}`\n }\n }\n\n const newContent = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n return { success: true } satisfies RenameSessionResult\n })\n\n// Get files changed in a session (from file-history-snapshot and tool_use)\nexport const getSessionFiles = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const messages = yield* readSession(projectName, sessionId)\n const fileChanges: FileChange[] = []\n const seenFiles = new Set<string>()\n\n for (const msg of messages) {\n // Check file-history-snapshot type\n if (msg.type === 'file-history-snapshot') {\n const snapshot = msg as unknown as {\n type: string\n messageId?: string\n snapshot?: {\n trackedFileBackups?: Record<string, unknown>\n timestamp?: string\n }\n }\n const backups = snapshot.snapshot?.trackedFileBackups\n if (backups && typeof backups === 'object') {\n for (const filePath of Object.keys(backups)) {\n if (!seenFiles.has(filePath)) {\n seenFiles.add(filePath)\n fileChanges.push({\n path: filePath,\n action: 'modified',\n timestamp: snapshot.snapshot?.timestamp,\n messageUuid: snapshot.messageId ?? msg.uuid,\n })\n }\n }\n }\n }\n\n // Check tool_use for Write/Edit operations\n if (msg.type === 'assistant' && msg.message?.content) {\n const content = msg.message.content\n if (Array.isArray(content)) {\n for (const item of content) {\n if (item && typeof item === 'object' && 'type' in item && item.type === 'tool_use') {\n const toolUse = item as { name?: string; input?: { file_path?: string } }\n if (\n (toolUse.name === 'Write' || toolUse.name === 'Edit') &&\n toolUse.input?.file_path\n ) {\n const filePath = toolUse.input.file_path\n if (!seenFiles.has(filePath)) {\n seenFiles.add(filePath)\n fileChanges.push({\n path: filePath,\n action: toolUse.name === 'Write' ? 'created' : 'modified',\n timestamp: msg.timestamp,\n messageUuid: msg.uuid,\n })\n }\n }\n }\n }\n }\n }\n }\n\n return {\n sessionId,\n projectName,\n files: fileChanges,\n totalChanges: fileChanges.length,\n } satisfies SessionFilesSummary\n })\n\n// Move session to another project\nexport const moveSession = (\n sourceProject: string,\n sessionId: string,\n targetProject: string\n): Effect.Effect<MoveSessionResult, Error> =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const sourcePath = path.join(sessionsDir, sourceProject)\n const targetPath = path.join(sessionsDir, targetProject)\n\n const sourceFile = path.join(sourcePath, `${sessionId}.jsonl`)\n const targetFile = path.join(targetPath, `${sessionId}.jsonl`)\n\n // Check source file exists\n const sourceExists = yield* Effect.tryPromise(() =>\n fs\n .access(sourceFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!sourceExists) {\n return { success: false, error: 'Source session not found' }\n }\n\n // Check target file does not exist\n const targetExists = yield* Effect.tryPromise(() =>\n fs\n .access(targetFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (targetExists) {\n return { success: false, error: 'Session already exists in target project' }\n }\n\n // Create target directory if needed\n yield* Effect.tryPromise(() => fs.mkdir(targetPath, { recursive: true }))\n\n // Find linked agents before moving\n const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId)\n\n // Move session file\n yield* Effect.tryPromise(() => fs.rename(sourceFile, targetFile))\n\n // Move linked agent files\n for (const agentId of linkedAgents) {\n const sourceAgentFile = path.join(sourcePath, `${agentId}.jsonl`)\n const targetAgentFile = path.join(targetPath, `${agentId}.jsonl`)\n\n const agentExists = yield* Effect.tryPromise(() =>\n fs\n .access(sourceAgentFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentExists) {\n yield* Effect.tryPromise(() => fs.rename(sourceAgentFile, targetAgentFile))\n }\n }\n\n return { success: true }\n })\n\n// Split session at a specific message\nexport const splitSession = (projectName: string, sessionId: string, splitAtMessageUuid: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const filePath = path.join(projectPath, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n // Parse all messages preserving their full structure\n const allMessages = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n // Find the split point\n const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid)\n if (splitIndex === -1) {\n return { success: false, error: 'Message not found' } satisfies SplitSessionResult\n }\n\n if (splitIndex === 0) {\n return { success: false, error: 'Cannot split at first message' } satisfies SplitSessionResult\n }\n\n // Generate new session ID\n const newSessionId = crypto.randomUUID()\n\n // Check if the split message is a continuation summary\n const splitMessage = allMessages[splitIndex]\n const shouldDuplicate = isContinuationSummary(splitMessage)\n\n // Split messages - if continuation summary, include it in both sessions\n let remainingMessages: Record<string, unknown>[]\n const movedMessages = allMessages.slice(splitIndex)\n\n if (shouldDuplicate) {\n // Create a copy of the continuation message with new UUID for the original session\n const duplicatedMessage: Record<string, unknown> = {\n ...splitMessage,\n uuid: crypto.randomUUID(),\n sessionId: sessionId, // Keep original session ID\n }\n remainingMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage]\n } else {\n remainingMessages = allMessages.slice(0, splitIndex)\n }\n\n // Update moved messages with new sessionId and fix first message's parentUuid\n const updatedMovedMessages = movedMessages.map((msg, index) => {\n const updated: Record<string, unknown> = { ...msg, sessionId: newSessionId }\n if (index === 0) {\n // First message of new session should have no parent\n updated.parentUuid = null\n }\n return updated\n })\n\n // Write remaining messages to original file\n const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, remainingContent, 'utf-8'))\n\n // Write moved messages to new session file\n const newFilePath = path.join(projectPath, `${newSessionId}.jsonl`)\n const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(newFilePath, newContent, 'utf-8'))\n\n // Update linked agent files that reference the old sessionId\n const agentFiles = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const agentJsonlFiles = agentFiles.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n\n for (const agentFile of agentJsonlFiles) {\n const agentPath = path.join(projectPath, agentFile)\n const agentContent = yield* Effect.tryPromise(() => fs.readFile(agentPath, 'utf-8'))\n const agentLines = agentContent.trim().split('\\n').filter(Boolean)\n\n if (agentLines.length === 0) continue\n\n const firstAgentMsg = JSON.parse(agentLines[0]) as { sessionId?: string }\n\n // If this agent belongs to the original session, check if it should be moved\n if (firstAgentMsg.sessionId === sessionId) {\n // Check if any message in moved messages is related to this agent\n const agentId = agentFile.replace('agent-', '').replace('.jsonl', '')\n const isRelatedToMoved = movedMessages.some(\n (msg) => (msg as { agentId?: string }).agentId === agentId\n )\n\n if (isRelatedToMoved) {\n // Update all messages in this agent file to reference new sessionId\n const updatedAgentMessages = agentLines.map((line) => {\n const msg = JSON.parse(line) as Record<string, unknown>\n return JSON.stringify({ ...msg, sessionId: newSessionId })\n })\n const updatedAgentContent = updatedAgentMessages.join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(agentPath, updatedAgentContent, 'utf-8'))\n }\n }\n }\n\n return {\n success: true,\n newSessionId,\n newSessionPath: newFilePath,\n movedMessageCount: movedMessages.length,\n duplicatedSummary: shouldDuplicate,\n } satisfies SplitSessionResult\n })\n\n// Remove invalid API key messages from a session, returns remaining message count\nconst cleanInvalidMessages = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n if (lines.length === 0) return { removedCount: 0, remainingCount: 0 }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n const invalidIndices: number[] = []\n\n // Find all invalid API key messages\n messages.forEach((msg, idx) => {\n if (isInvalidApiKeyMessage(msg)) {\n invalidIndices.push(idx)\n }\n })\n\n if (invalidIndices.length === 0) {\n const userAssistantCount = messages.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n ).length\n const hasSummary = messages.some((m) => m.type === 'summary')\n // Count summary-only sessions as having 1 message\n const remainingCount = userAssistantCount > 0 ? userAssistantCount : hasSummary ? 1 : 0\n return { removedCount: 0, remainingCount }\n }\n\n // Remove invalid messages and fix parentUuid chain\n const filtered: Message[] = []\n let lastValidUuid: string | null = null\n\n for (let i = 0; i < messages.length; i++) {\n if (invalidIndices.includes(i)) {\n continue // Skip invalid message\n }\n\n const msg = messages[i]\n // Update parentUuid to point to last valid message\n if (msg.parentUuid && invalidIndices.some((idx) => messages[idx]?.uuid === msg.parentUuid)) {\n msg.parentUuid = lastValidUuid\n }\n filtered.push(msg)\n lastValidUuid = msg.uuid\n }\n\n const newContent =\n filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join('\\n') + '\\n' : ''\n\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n const remainingUserAssistant = filtered.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n ).length\n const hasSummary = filtered.some((m) => m.type === 'summary')\n // Count summary-only sessions as having 1 message\n const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0\n return { removedCount: invalidIndices.length, remainingCount }\n })\n\n// Preview cleanup - find empty and invalid sessions\nexport const previewCleanup = (projectName?: string) =>\n Effect.gen(function* () {\n const projects = yield* listProjects\n const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects\n\n // Get orphan todos count (global, not per-project)\n const orphanTodos = yield* findOrphanTodos()\n const orphanTodoCount = orphanTodos.length\n\n const results = yield* Effect.all(\n targetProjects.map((project) =>\n Effect.gen(function* () {\n const sessions = yield* listSessions(project.name)\n const emptySessions = sessions.filter((s) => s.messageCount === 0)\n const invalidSessions = sessions.filter(\n (s) => s.title?.includes('Invalid API key') || s.title?.includes('API key')\n )\n\n // Count empty sessions that have todos\n let emptyWithTodosCount = 0\n for (const session of emptySessions) {\n const linkedAgents = yield* findLinkedAgents(project.name, session.id)\n const hasTodos = yield* sessionHasTodos(session.id, linkedAgents)\n if (hasTodos) {\n emptyWithTodosCount++\n }\n }\n\n // Count orphan agents\n const orphanAgents = yield* findOrphanAgents(project.name)\n\n return {\n project: project.name,\n emptySessions,\n invalidSessions,\n emptyWithTodosCount,\n orphanAgentCount: orphanAgents.length,\n orphanTodoCount: 0, // Will set for first project only\n } satisfies CleanupPreview\n })\n ),\n { concurrency: 5 }\n )\n\n // Add orphanTodoCount only to the first result to avoid double counting\n if (results.length > 0) {\n results[0] = { ...results[0], orphanTodoCount }\n }\n\n return results\n })\n\n// Clear sessions\nexport const clearSessions = (options: {\n projectName?: string\n clearEmpty?: boolean\n clearInvalid?: boolean\n skipWithTodos?: boolean\n clearOrphanAgents?: boolean\n clearOrphanTodos?: boolean\n}) =>\n Effect.gen(function* () {\n const {\n projectName,\n clearEmpty = true,\n clearInvalid = true,\n skipWithTodos = true,\n clearOrphanAgents = false,\n clearOrphanTodos = false,\n } = options\n const projects = yield* listProjects\n const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects\n\n let deletedSessionCount = 0\n let removedMessageCount = 0\n let deletedOrphanAgentCount = 0\n let deletedOrphanTodoCount = 0\n const sessionsToDelete: { project: string; sessionId: string }[] = []\n\n // Step 1: Clean invalid API key messages from all sessions (if clearInvalid)\n if (clearInvalid) {\n for (const project of targetProjects) {\n const projectPath = path.join(getSessionsDir(), project.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n for (const file of sessionFiles) {\n const sessionId = file.replace('.jsonl', '')\n const result = yield* cleanInvalidMessages(project.name, sessionId)\n removedMessageCount += result.removedCount\n\n // Mark for deletion if now empty\n if (result.remainingCount === 0) {\n sessionsToDelete.push({ project: project.name, sessionId })\n }\n }\n }\n }\n\n // Step 2: Also find originally empty sessions (if clearEmpty is true)\n if (clearEmpty) {\n for (const project of targetProjects) {\n const sessions = yield* listSessions(project.name)\n for (const session of sessions) {\n if (session.messageCount === 0) {\n const alreadyMarked = sessionsToDelete.some(\n (s) => s.project === project.name && s.sessionId === session.id\n )\n if (!alreadyMarked) {\n // Skip sessions with todos if skipWithTodos is true\n if (skipWithTodos) {\n const linkedAgents = yield* findLinkedAgents(project.name, session.id)\n const hasTodos = yield* sessionHasTodos(session.id, linkedAgents)\n if (hasTodos) continue\n }\n sessionsToDelete.push({ project: project.name, sessionId: session.id })\n }\n }\n }\n }\n }\n\n // Step 3: Delete all empty sessions (this also deletes linked agents and todos)\n for (const { project, sessionId } of sessionsToDelete) {\n yield* deleteSession(project, sessionId)\n deletedSessionCount++\n }\n\n // Step 4: Delete orphan agents if requested\n if (clearOrphanAgents) {\n for (const project of targetProjects) {\n const result = yield* deleteOrphanAgents(project.name)\n deletedOrphanAgentCount += result.count\n }\n }\n\n // Step 5: Delete orphan todos if requested (global, not per-project)\n if (clearOrphanTodos) {\n const result = yield* deleteOrphanTodos()\n deletedOrphanTodoCount = result.deletedCount\n }\n\n return {\n success: true,\n deletedCount: deletedSessionCount,\n removedMessageCount,\n deletedOrphanAgentCount,\n deletedOrphanTodoCount,\n } satisfies ClearSessionsResult\n })\n"],"mappings":"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","names":["path","Effect","fs","path","Effect","Effect","fs","path","Effect","stat","agentBackupDir","hasSummary","remainingCount"]}
|
|
1
|
+
{"version":3,"sources":["../src/paths.ts","../src/utils.ts","../src/agents.ts","../src/todos.ts","../src/session.ts"],"sourcesContent":["/**\n * Path utilities for Claude Code session management\n */\nimport * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\n\n// Get Claude sessions directory (~/.claude/projects)\nexport const getSessionsDir = (): string => path.join(os.homedir(), '.claude', 'projects')\n\n// Get Claude todos directory (~/.claude/todos)\nexport const getTodosDir = (): string => path.join(os.homedir(), '.claude', 'todos')\n\n// Convert project folder name to display path\n// e.g., -Users-david-works -> /Users/david/works\n// Handle dot-prefixed folders: --claude -> /.claude, -works--vscode -> /works/.vscode\nexport const folderNameToDisplayPath = (folderName: string): string => {\n return folderName\n .replace(/^-/, '/')\n .replace(/--/g, '/.') // double dash means dot-prefixed folder\n .replace(/-/g, '/')\n}\n\n// Convert display path to folder name (reverse of above)\nexport const displayPathToFolderName = (displayPath: string): string => {\n return displayPath\n .replace(/^\\//g, '-')\n .replace(/\\/\\./g, '--') // dot-prefixed folder becomes double dash\n .replace(/\\//g, '-')\n}\n\n// Convert absolute path to project folder name\n// e.g., /Users/young/works/.vscode -> -Users-young-works--vscode\nexport const pathToFolderName = (absolutePath: string): string => {\n return absolutePath.replace(/^\\//g, '-').replace(/\\/\\./g, '--').replace(/\\//g, '-')\n}\n\n// Convert folder name to relative or absolute path for display\n// If path is under home directory, show relative (~/...)\n// Otherwise show absolute path\nexport const folderNameToPath = (folderName: string): string => {\n // First try to get real path from session cwd\n const realPath = getRealPathFromSession(folderName)\n if (realPath) {\n const home = os.homedir()\n if (realPath.startsWith(home)) {\n return '~' + realPath.slice(home.length)\n }\n return realPath\n }\n\n // Fallback to pattern-based conversion\n const absolutePath = folderNameToDisplayPath(folderName)\n const home = os.homedir()\n\n if (absolutePath.startsWith(home)) {\n return '~' + absolutePath.slice(home.length)\n }\n return absolutePath\n}\n\n// Try to extract cwd from a single session file\nconst tryGetCwdFromFile = (filePath: string): string | null => {\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const firstLine = content.split('\\n')[0]\n if (!firstLine) return null\n const message = JSON.parse(firstLine) as { cwd?: string }\n return message.cwd ?? null\n } catch {\n return null\n }\n}\n\n// Extract real cwd path from session files in a project\n// This handles edge cases like es~kr -> es.kr (tilde in hostname)\n// Iterates through all session files to find one with matching cwd\n// Returns null if no session has cwd matching the folder name (all sessions moved from other projects)\nexport const getRealPathFromSession = (folderName: string): string | null => {\n const projectDir = path.join(getSessionsDir(), folderName)\n\n try {\n const files = fs.readdirSync(projectDir)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n for (const sessionFile of sessionFiles) {\n const cwd = tryGetCwdFromFile(path.join(projectDir, sessionFile))\n // Verify cwd matches the folder name (session wasn't moved from another project)\n if (cwd && pathToFolderName(cwd) === folderName) {\n return cwd\n }\n }\n\n return null\n } catch {\n return null\n }\n}\n","/**\n * Utility functions for message processing\n */\nimport type { ContentItem, Message, MessagePayload } from './types.js'\n\n// Extract text content from message payload\nexport const extractTextContent = (message: MessagePayload | undefined): string => {\n if (!message) return ''\n\n const content = message.content\n if (!content) return ''\n\n // If content is string, return directly\n if (typeof content === 'string') return content\n\n // If content is array, extract text items\n if (Array.isArray(content)) {\n return content\n .filter((item): item is ContentItem => typeof item === 'object' && item?.type === 'text')\n .map((item) => item.text ?? '')\n .join('')\n }\n\n return ''\n}\n\n// Extract title from text content (remove IDE tags, use first line)\nexport const extractTitle = (text: string): string => {\n if (!text) return 'Untitled'\n\n // Remove IDE tags (<ide_opened_file>, <ide_selection>, etc.)\n let cleaned = text.replace(/<ide_[^>]*>[\\s\\S]*?<\\/ide_[^>]*>/g, '').trim()\n\n if (!cleaned) return 'Untitled'\n\n // Use only content before \\n\\n or \\n as title\n if (cleaned.includes('\\n\\n')) {\n cleaned = cleaned.split('\\n\\n')[0]\n } else if (cleaned.includes('\\n')) {\n cleaned = cleaned.split('\\n')[0]\n }\n\n // Limit to 100 characters\n if (cleaned.length > 100) {\n return cleaned.slice(0, 100) + '...'\n }\n\n return cleaned || 'Untitled'\n}\n\n// Check if message contains \"Invalid API key\"\nexport const isInvalidApiKeyMessage = (msg: Message): boolean => {\n const text = extractTextContent(msg.message)\n return text.includes('Invalid API key')\n}\n\n// Check if a message is a continuation summary (from compact)\nexport const isContinuationSummary = (msg: Record<string, unknown>): boolean => {\n // isCompactSummary flag is set by Claude Code for continuation summaries\n if (msg.isCompactSummary === true) return true\n\n // Fallback: check message content\n if (msg.type !== 'user') return false\n const message = msg.message as { content?: string } | undefined\n const content = message?.content ?? ''\n return content.startsWith('This session is being continued from')\n}\n","/**\n * Agent file management utilities\n */\nimport { Effect } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir } from './paths.js'\n\n// Find agent files linked to a session\nexport const findLinkedAgents = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n\n const linkedAgents: string[] = []\n\n for (const agentFile of agentFiles) {\n const filePath = path.join(projectPath, agentFile)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const firstLine = content.split('\\n')[0]\n\n if (firstLine) {\n try {\n const parsed = JSON.parse(firstLine) as { sessionId?: string }\n if (parsed.sessionId === sessionId) {\n linkedAgents.push(agentFile.replace('.jsonl', ''))\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n return linkedAgents\n })\n\n// Find orphan agent files (agents whose parent session no longer exists)\nexport const findOrphanAgents = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n\n const sessionIds = new Set(\n files\n .filter((f) => !f.startsWith('agent-') && f.endsWith('.jsonl'))\n .map((f) => f.replace('.jsonl', ''))\n )\n\n const agentFiles = files.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n const orphanAgents: Array<{ agentId: string; sessionId: string }> = []\n\n for (const agentFile of agentFiles) {\n const filePath = path.join(projectPath, agentFile)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const firstLine = content.split('\\n')[0]\n\n if (firstLine) {\n try {\n const parsed = JSON.parse(firstLine) as { sessionId?: string }\n if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {\n orphanAgents.push({\n agentId: agentFile.replace('.jsonl', ''),\n sessionId: parsed.sessionId,\n })\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n return orphanAgents\n })\n\n// Delete orphan agent files (move to .bak)\nexport const deleteOrphanAgents = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const orphans = yield* findOrphanAgents(projectName)\n\n // Create backup directory\n const backupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n const deletedAgents: string[] = []\n\n for (const orphan of orphans) {\n const agentPath = path.join(projectPath, `${orphan.agentId}.jsonl`)\n const agentBackupPath = path.join(backupDir, `${orphan.agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath))\n deletedAgents.push(orphan.agentId)\n }\n\n return { success: true, deletedAgents, count: deletedAgents.length }\n })\n","/**\n * Todo file management utilities\n */\nimport { Effect } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir, getTodosDir } from './paths.js'\nimport type { TodoItem, SessionTodos } from './types.js'\n\n// Find linked todo files for a session and its agents\nexport const findLinkedTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) {\n return {\n sessionId,\n sessionTodos: [],\n agentTodos: [],\n hasTodos: false,\n } satisfies SessionTodos\n }\n\n // Read session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n let sessionTodos: TodoItem[] = []\n\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(sessionTodoPath, 'utf-8'))\n try {\n sessionTodos = JSON.parse(content) as TodoItem[]\n } catch {\n // Invalid JSON, treat as empty\n }\n }\n\n // Read agent todo files\n const agentTodos: { agentId: string; todos: TodoItem[] }[] = []\n\n for (const agentId of agentIds) {\n // Agent todo files are named: {sessionId}-{agentId}.json\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(agentTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n agentTodos.push({ agentId, todos })\n } catch {\n // Invalid JSON, skip\n }\n }\n }\n\n const hasTodos = sessionTodos.length > 0 || agentTodos.some((at) => at.todos.length > 0)\n\n return {\n sessionId,\n sessionTodos,\n agentTodos,\n hasTodos,\n } satisfies SessionTodos\n })\n\n// Check if session has any todos (quick check)\nexport const sessionHasTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) return false\n\n // Check session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(sessionTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n if (todos.length > 0) return true\n } catch {\n // Invalid JSON, continue\n }\n }\n\n // Check agent todo files\n for (const agentId of agentIds) {\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const content = yield* Effect.tryPromise(() => fs.readFile(agentTodoPath, 'utf-8'))\n try {\n const todos = JSON.parse(content) as TodoItem[]\n if (todos.length > 0) return true\n } catch {\n // Invalid JSON, continue\n }\n }\n }\n\n return false\n })\n\n// Delete linked todo files for a session (move to .bak)\nexport const deleteLinkedTodos = (sessionId: string, agentIds: string[]) =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n\n // Check if todos directory exists\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) return { deletedCount: 0 }\n\n // Create backup directory\n const backupDir = path.join(todosDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n let deletedCount = 0\n\n // Delete session's own todo file\n const sessionTodoPath = path.join(todosDir, `${sessionId}.json`)\n const sessionTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (sessionTodoExists) {\n const backupPath = path.join(backupDir, `${sessionId}.json`)\n yield* Effect.tryPromise(() => fs.rename(sessionTodoPath, backupPath))\n deletedCount++\n }\n\n // Delete agent todo files\n for (const agentId of agentIds) {\n const shortAgentId = agentId.replace('agent-', '')\n const agentTodoPath = path.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`)\n\n const agentTodoExists = yield* Effect.tryPromise(() =>\n fs\n .access(agentTodoPath)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentTodoExists) {\n const backupPath = path.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`)\n yield* Effect.tryPromise(() => fs.rename(agentTodoPath, backupPath))\n deletedCount++\n }\n }\n\n return { deletedCount }\n })\n\n// Find all orphan todo files (session no longer exists)\nexport const findOrphanTodos = () =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n const sessionsDir = getSessionsDir()\n\n // Check if directories exist\n const [todosExists, sessionsExists] = yield* Effect.all([\n Effect.tryPromise(() =>\n fs\n .access(todosDir)\n .then(() => true)\n .catch(() => false)\n ),\n Effect.tryPromise(() =>\n fs\n .access(sessionsDir)\n .then(() => true)\n .catch(() => false)\n ),\n ])\n\n if (!todosExists || !sessionsExists) return []\n\n // Get all todo files\n const todoFiles = yield* Effect.tryPromise(() => fs.readdir(todosDir))\n const jsonFiles = todoFiles.filter((f) => f.endsWith('.json'))\n\n // Build set of all valid session IDs across all projects\n const validSessionIds = new Set<string>()\n const projectEntries = yield* Effect.tryPromise(() =>\n fs.readdir(sessionsDir, { withFileTypes: true })\n )\n\n for (const entry of projectEntries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue\n const projectPath = path.join(sessionsDir, entry.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n for (const f of files) {\n if (f.endsWith('.jsonl') && !f.startsWith('agent-')) {\n validSessionIds.add(f.replace('.jsonl', ''))\n }\n }\n }\n\n // Find orphan todo files\n const orphans: string[] = []\n for (const todoFile of jsonFiles) {\n // Parse session ID from todo filename\n // Format: {sessionId}.json or {sessionId}-agent-{agentId}.json\n const match = todoFile.match(/^([a-f0-9-]+)(?:-agent-[a-f0-9]+)?\\.json$/)\n if (match) {\n const sessionId = match[1]\n if (!validSessionIds.has(sessionId)) {\n orphans.push(todoFile)\n }\n }\n }\n\n return orphans\n })\n\n// Delete orphan todo files\nexport const deleteOrphanTodos = () =>\n Effect.gen(function* () {\n const todosDir = getTodosDir()\n const orphans = yield* findOrphanTodos()\n\n if (orphans.length === 0) return { success: true, deletedCount: 0 }\n\n // Create backup directory\n const backupDir = path.join(todosDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n let deletedCount = 0\n\n for (const orphan of orphans) {\n const filePath = path.join(todosDir, orphan)\n const backupPath = path.join(backupDir, orphan)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n deletedCount++\n }\n\n return { success: true, deletedCount }\n })\n","/**\n * Session management operations\n */\nimport { Effect, pipe, Array as A, Option as O } from 'effect'\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { getSessionsDir, folderNameToPath } from './paths.js'\nimport { extractTextContent, extractTitle, isInvalidApiKeyMessage, isContinuationSummary } from './utils.js'\nimport { findLinkedAgents, findOrphanAgents, deleteOrphanAgents } from './agents.js'\nimport { deleteLinkedTodos, sessionHasTodos, findOrphanTodos, deleteOrphanTodos } from './todos.js'\nimport type {\n Message,\n SessionMeta,\n Project,\n FileChange,\n SessionFilesSummary,\n DeleteSessionResult,\n RenameSessionResult,\n SplitSessionResult,\n MoveSessionResult,\n ClearSessionsResult,\n CleanupPreview,\n ContentItem,\n} from './types.js'\n\n// List all project directories\nexport const listProjects = Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n\n const exists = yield* Effect.tryPromise(() =>\n fs\n .access(sessionsDir)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!exists) {\n return [] as Project[]\n }\n\n const entries = yield* Effect.tryPromise(() => fs.readdir(sessionsDir, { withFileTypes: true }))\n\n const projects = yield* Effect.all(\n entries\n .filter((e) => e.isDirectory() && !e.name.startsWith('.'))\n .map((entry) =>\n Effect.gen(function* () {\n const projectPath = path.join(sessionsDir, entry.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n // Exclude agent- files (subagent logs)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n return {\n name: entry.name,\n displayName: folderNameToPath(entry.name),\n path: projectPath,\n sessionCount: sessionFiles.length,\n } satisfies Project\n })\n ),\n { concurrency: 10 }\n )\n\n return projects\n})\n\n// List sessions in a project\nexport const listSessions = (projectName: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n // Exclude agent- files (subagent logs)\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n const sessions = yield* Effect.all(\n sessionFiles.map((file) =>\n Effect.gen(function* () {\n const filePath = path.join(projectPath, file)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n const sessionId = file.replace('.jsonl', '')\n\n // Filter only user/assistant messages for counting\n const userAssistantMessages = messages.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n )\n\n // Check if session has summary (for preserved sessions without user/assistant messages)\n const hasSummary = messages.some((m) => m.type === 'summary')\n\n const firstMessage = userAssistantMessages[0]\n const lastMessage = userAssistantMessages[userAssistantMessages.length - 1]\n\n // Extract title from first user message\n const title = pipe(\n messages,\n A.findFirst((m) => m.type === 'user'),\n O.map((m) => {\n const text = extractTextContent(m.message)\n return extractTitle(text)\n }),\n O.getOrElse(() => (hasSummary ? '[Summary Only]' : `Session ${sessionId.slice(0, 8)}`))\n )\n\n return {\n id: sessionId,\n projectName,\n title,\n // If session has summary but no user/assistant messages, count as 1\n messageCount:\n userAssistantMessages.length > 0 ? userAssistantMessages.length : hasSummary ? 1 : 0,\n createdAt: firstMessage?.timestamp,\n updatedAt: lastMessage?.timestamp,\n } satisfies SessionMeta\n })\n ),\n { concurrency: 10 }\n )\n\n // Sort by newest first\n return sessions.sort((a, b) => {\n const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0\n const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0\n return dateB - dateA\n })\n })\n\n// Read session messages\nexport const readSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n return lines.map((line) => JSON.parse(line) as Message)\n })\n\n// Delete a message from session and repair parentUuid chain\nexport const deleteMessage = (projectName: string, sessionId: string, messageUuid: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n const messages = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n // Find by uuid or messageId (for file-history-snapshot type)\n const targetIndex = messages.findIndex(\n (m) => m.uuid === messageUuid || m.messageId === messageUuid\n )\n if (targetIndex === -1) {\n return { success: false, error: 'Message not found' }\n }\n\n // Get the deleted message's uuid and parentUuid\n const deletedMsg = messages[targetIndex]\n const deletedUuid = deletedMsg?.uuid ?? deletedMsg?.messageId\n const parentUuid = deletedMsg?.parentUuid\n\n // Find all messages that reference the deleted message as their parent\n // and update them to point to the deleted message's parent\n for (const msg of messages) {\n if (msg.parentUuid === deletedUuid) {\n msg.parentUuid = parentUuid\n }\n }\n\n // Remove the message\n messages.splice(targetIndex, 1)\n\n const newContent = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n return { success: true }\n })\n\n// Delete a session and its linked agent/todo files\nexport const deleteSession = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const projectPath = path.join(sessionsDir, projectName)\n const filePath = path.join(projectPath, `${sessionId}.jsonl`)\n\n // Find linked agents first (before any deletion)\n const linkedAgents = yield* findLinkedAgents(projectName, sessionId)\n\n // Check file size - if empty (0 bytes), just delete without backup\n const stat = yield* Effect.tryPromise(() => fs.stat(filePath))\n if (stat.size === 0) {\n yield* Effect.tryPromise(() => fs.unlink(filePath))\n // Still delete linked agents and todos for empty sessions\n const agentBackupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(agentBackupDir, { recursive: true }))\n for (const agentId of linkedAgents) {\n const agentPath = path.join(projectPath, `${agentId}.jsonl`)\n const agentBackupPath = path.join(agentBackupDir, `${agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath).catch(() => {}))\n }\n yield* deleteLinkedTodos(sessionId, linkedAgents)\n return { success: true, deletedAgents: linkedAgents.length } satisfies DeleteSessionResult\n }\n\n // Create backup directory\n const backupDir = path.join(sessionsDir, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(backupDir, { recursive: true }))\n\n // Delete linked agent files (move to .bak in project folder)\n const agentBackupDir = path.join(projectPath, '.bak')\n yield* Effect.tryPromise(() => fs.mkdir(agentBackupDir, { recursive: true }))\n for (const agentId of linkedAgents) {\n const agentPath = path.join(projectPath, `${agentId}.jsonl`)\n const agentBackupPath = path.join(agentBackupDir, `${agentId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(agentPath, agentBackupPath).catch(() => {}))\n }\n\n // Delete linked todo files\n const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents)\n\n // Move session to backup (format: project_name_session_id.jsonl)\n const backupPath = path.join(backupDir, `${projectName}_${sessionId}.jsonl`)\n yield* Effect.tryPromise(() => fs.rename(filePath, backupPath))\n\n return {\n success: true,\n backupPath,\n deletedAgents: linkedAgents.length,\n deletedTodos: todosResult.deletedCount,\n } satisfies DeleteSessionResult\n })\n\n// Rename session by adding title prefix\nexport const renameSession = (projectName: string, sessionId: string, newTitle: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n if (lines.length === 0) {\n return { success: false, error: 'Empty session' } satisfies RenameSessionResult\n }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n\n // Find first user message\n const firstUserIdx = messages.findIndex((m) => m.type === 'user')\n if (firstUserIdx === -1) {\n return { success: false, error: 'No user message found' } satisfies RenameSessionResult\n }\n\n const firstMsg = messages[firstUserIdx]\n if (firstMsg?.message?.content && Array.isArray(firstMsg.message.content)) {\n // Find first non-IDE text content\n const textIdx = firstMsg.message.content.findIndex(\n (item): item is ContentItem =>\n typeof item === 'object' &&\n item?.type === 'text' &&\n !item.text?.trim().startsWith('<ide_')\n )\n\n if (textIdx >= 0) {\n const item = firstMsg.message.content[textIdx] as ContentItem\n const oldText = item.text ?? ''\n // Remove existing title pattern (first line ending with \\n\\n)\n const cleanedText = oldText.replace(/^[^\\n]+\\n\\n/, '')\n item.text = `${newTitle}\\n\\n${cleanedText}`\n }\n }\n\n const newContent = messages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n return { success: true } satisfies RenameSessionResult\n })\n\n// Get files changed in a session (from file-history-snapshot and tool_use)\nexport const getSessionFiles = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const messages = yield* readSession(projectName, sessionId)\n const fileChanges: FileChange[] = []\n const seenFiles = new Set<string>()\n\n for (const msg of messages) {\n // Check file-history-snapshot type\n if (msg.type === 'file-history-snapshot') {\n const snapshot = msg as unknown as {\n type: string\n messageId?: string\n snapshot?: {\n trackedFileBackups?: Record<string, unknown>\n timestamp?: string\n }\n }\n const backups = snapshot.snapshot?.trackedFileBackups\n if (backups && typeof backups === 'object') {\n for (const filePath of Object.keys(backups)) {\n if (!seenFiles.has(filePath)) {\n seenFiles.add(filePath)\n fileChanges.push({\n path: filePath,\n action: 'modified',\n timestamp: snapshot.snapshot?.timestamp,\n messageUuid: snapshot.messageId ?? msg.uuid,\n })\n }\n }\n }\n }\n\n // Check tool_use for Write/Edit operations\n if (msg.type === 'assistant' && msg.message?.content) {\n const content = msg.message.content\n if (Array.isArray(content)) {\n for (const item of content) {\n if (item && typeof item === 'object' && 'type' in item && item.type === 'tool_use') {\n const toolUse = item as { name?: string; input?: { file_path?: string } }\n if (\n (toolUse.name === 'Write' || toolUse.name === 'Edit') &&\n toolUse.input?.file_path\n ) {\n const filePath = toolUse.input.file_path\n if (!seenFiles.has(filePath)) {\n seenFiles.add(filePath)\n fileChanges.push({\n path: filePath,\n action: toolUse.name === 'Write' ? 'created' : 'modified',\n timestamp: msg.timestamp,\n messageUuid: msg.uuid,\n })\n }\n }\n }\n }\n }\n }\n }\n\n return {\n sessionId,\n projectName,\n files: fileChanges,\n totalChanges: fileChanges.length,\n } satisfies SessionFilesSummary\n })\n\n// Move session to another project\nexport const moveSession = (\n sourceProject: string,\n sessionId: string,\n targetProject: string\n): Effect.Effect<MoveSessionResult, Error> =>\n Effect.gen(function* () {\n const sessionsDir = getSessionsDir()\n const sourcePath = path.join(sessionsDir, sourceProject)\n const targetPath = path.join(sessionsDir, targetProject)\n\n const sourceFile = path.join(sourcePath, `${sessionId}.jsonl`)\n const targetFile = path.join(targetPath, `${sessionId}.jsonl`)\n\n // Check source file exists\n const sourceExists = yield* Effect.tryPromise(() =>\n fs\n .access(sourceFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (!sourceExists) {\n return { success: false, error: 'Source session not found' }\n }\n\n // Check target file does not exist\n const targetExists = yield* Effect.tryPromise(() =>\n fs\n .access(targetFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (targetExists) {\n return { success: false, error: 'Session already exists in target project' }\n }\n\n // Create target directory if needed\n yield* Effect.tryPromise(() => fs.mkdir(targetPath, { recursive: true }))\n\n // Find linked agents before moving\n const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId)\n\n // Move session file\n yield* Effect.tryPromise(() => fs.rename(sourceFile, targetFile))\n\n // Move linked agent files\n for (const agentId of linkedAgents) {\n const sourceAgentFile = path.join(sourcePath, `${agentId}.jsonl`)\n const targetAgentFile = path.join(targetPath, `${agentId}.jsonl`)\n\n const agentExists = yield* Effect.tryPromise(() =>\n fs\n .access(sourceAgentFile)\n .then(() => true)\n .catch(() => false)\n )\n\n if (agentExists) {\n yield* Effect.tryPromise(() => fs.rename(sourceAgentFile, targetAgentFile))\n }\n }\n\n return { success: true }\n })\n\n// Split session at a specific message\nexport const splitSession = (projectName: string, sessionId: string, splitAtMessageUuid: string) =>\n Effect.gen(function* () {\n const projectPath = path.join(getSessionsDir(), projectName)\n const filePath = path.join(projectPath, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n // Parse all messages preserving their full structure\n const allMessages = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n // Find the split point\n const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid)\n if (splitIndex === -1) {\n return { success: false, error: 'Message not found' } satisfies SplitSessionResult\n }\n\n if (splitIndex === 0) {\n return { success: false, error: 'Cannot split at first message' } satisfies SplitSessionResult\n }\n\n // Generate new session ID\n const newSessionId = crypto.randomUUID()\n\n // Check if the split message is a continuation summary\n const splitMessage = allMessages[splitIndex]\n const shouldDuplicate = isContinuationSummary(splitMessage)\n\n // Split messages - if continuation summary, include it in both sessions\n let remainingMessages: Record<string, unknown>[]\n const movedMessages = allMessages.slice(splitIndex)\n\n if (shouldDuplicate) {\n // Create a copy of the continuation message with new UUID for the original session\n const duplicatedMessage: Record<string, unknown> = {\n ...splitMessage,\n uuid: crypto.randomUUID(),\n sessionId: sessionId, // Keep original session ID\n }\n remainingMessages = [...allMessages.slice(0, splitIndex), duplicatedMessage]\n } else {\n remainingMessages = allMessages.slice(0, splitIndex)\n }\n\n // Update moved messages with new sessionId and fix first message's parentUuid\n const updatedMovedMessages = movedMessages.map((msg, index) => {\n const updated: Record<string, unknown> = { ...msg, sessionId: newSessionId }\n if (index === 0) {\n // First message of new session should have no parent\n updated.parentUuid = null\n }\n return updated\n })\n\n // Write remaining messages to original file\n const remainingContent = remainingMessages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(filePath, remainingContent, 'utf-8'))\n\n // Write moved messages to new session file\n const newFilePath = path.join(projectPath, `${newSessionId}.jsonl`)\n const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(newFilePath, newContent, 'utf-8'))\n\n // Update linked agent files that reference the old sessionId\n const agentFiles = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const agentJsonlFiles = agentFiles.filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))\n\n for (const agentFile of agentJsonlFiles) {\n const agentPath = path.join(projectPath, agentFile)\n const agentContent = yield* Effect.tryPromise(() => fs.readFile(agentPath, 'utf-8'))\n const agentLines = agentContent.trim().split('\\n').filter(Boolean)\n\n if (agentLines.length === 0) continue\n\n const firstAgentMsg = JSON.parse(agentLines[0]) as { sessionId?: string }\n\n // If this agent belongs to the original session, check if it should be moved\n if (firstAgentMsg.sessionId === sessionId) {\n // Check if any message in moved messages is related to this agent\n const agentId = agentFile.replace('agent-', '').replace('.jsonl', '')\n const isRelatedToMoved = movedMessages.some(\n (msg) => (msg as { agentId?: string }).agentId === agentId\n )\n\n if (isRelatedToMoved) {\n // Update all messages in this agent file to reference new sessionId\n const updatedAgentMessages = agentLines.map((line) => {\n const msg = JSON.parse(line) as Record<string, unknown>\n return JSON.stringify({ ...msg, sessionId: newSessionId })\n })\n const updatedAgentContent = updatedAgentMessages.join('\\n') + '\\n'\n yield* Effect.tryPromise(() => fs.writeFile(agentPath, updatedAgentContent, 'utf-8'))\n }\n }\n }\n\n return {\n success: true,\n newSessionId,\n newSessionPath: newFilePath,\n movedMessageCount: movedMessages.length,\n duplicatedSummary: shouldDuplicate,\n } satisfies SplitSessionResult\n })\n\n// Remove invalid API key messages from a session, returns remaining message count\nconst cleanInvalidMessages = (projectName: string, sessionId: string) =>\n Effect.gen(function* () {\n const filePath = path.join(getSessionsDir(), projectName, `${sessionId}.jsonl`)\n const content = yield* Effect.tryPromise(() => fs.readFile(filePath, 'utf-8'))\n const lines = content.trim().split('\\n').filter(Boolean)\n\n if (lines.length === 0) return { removedCount: 0, remainingCount: 0 }\n\n const messages = lines.map((line) => JSON.parse(line) as Message)\n const invalidIndices: number[] = []\n\n // Find all invalid API key messages\n messages.forEach((msg, idx) => {\n if (isInvalidApiKeyMessage(msg)) {\n invalidIndices.push(idx)\n }\n })\n\n if (invalidIndices.length === 0) {\n const userAssistantCount = messages.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n ).length\n const hasSummary = messages.some((m) => m.type === 'summary')\n // Count summary-only sessions as having 1 message\n const remainingCount = userAssistantCount > 0 ? userAssistantCount : hasSummary ? 1 : 0\n return { removedCount: 0, remainingCount }\n }\n\n // Remove invalid messages and fix parentUuid chain\n const filtered: Message[] = []\n let lastValidUuid: string | null = null\n\n for (let i = 0; i < messages.length; i++) {\n if (invalidIndices.includes(i)) {\n continue // Skip invalid message\n }\n\n const msg = messages[i]\n // Update parentUuid to point to last valid message\n if (msg.parentUuid && invalidIndices.some((idx) => messages[idx]?.uuid === msg.parentUuid)) {\n msg.parentUuid = lastValidUuid\n }\n filtered.push(msg)\n lastValidUuid = msg.uuid\n }\n\n const newContent =\n filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join('\\n') + '\\n' : ''\n\n yield* Effect.tryPromise(() => fs.writeFile(filePath, newContent, 'utf-8'))\n\n const remainingUserAssistant = filtered.filter(\n (m) => m.type === 'user' || m.type === 'assistant'\n ).length\n const hasSummary = filtered.some((m) => m.type === 'summary')\n // Count summary-only sessions as having 1 message\n const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0\n return { removedCount: invalidIndices.length, remainingCount }\n })\n\n// Preview cleanup - find empty and invalid sessions\nexport const previewCleanup = (projectName?: string) =>\n Effect.gen(function* () {\n const projects = yield* listProjects\n const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects\n\n // Get orphan todos count (global, not per-project)\n const orphanTodos = yield* findOrphanTodos()\n const orphanTodoCount = orphanTodos.length\n\n const results = yield* Effect.all(\n targetProjects.map((project) =>\n Effect.gen(function* () {\n const sessions = yield* listSessions(project.name)\n const emptySessions = sessions.filter((s) => s.messageCount === 0)\n const invalidSessions = sessions.filter(\n (s) => s.title?.includes('Invalid API key') || s.title?.includes('API key')\n )\n\n // Count empty sessions that have todos\n let emptyWithTodosCount = 0\n for (const session of emptySessions) {\n const linkedAgents = yield* findLinkedAgents(project.name, session.id)\n const hasTodos = yield* sessionHasTodos(session.id, linkedAgents)\n if (hasTodos) {\n emptyWithTodosCount++\n }\n }\n\n // Count orphan agents\n const orphanAgents = yield* findOrphanAgents(project.name)\n\n return {\n project: project.name,\n emptySessions,\n invalidSessions,\n emptyWithTodosCount,\n orphanAgentCount: orphanAgents.length,\n orphanTodoCount: 0, // Will set for first project only\n } satisfies CleanupPreview\n })\n ),\n { concurrency: 5 }\n )\n\n // Add orphanTodoCount only to the first result to avoid double counting\n if (results.length > 0) {\n results[0] = { ...results[0], orphanTodoCount }\n }\n\n return results\n })\n\n// Clear sessions\nexport const clearSessions = (options: {\n projectName?: string\n clearEmpty?: boolean\n clearInvalid?: boolean\n skipWithTodos?: boolean\n clearOrphanAgents?: boolean\n clearOrphanTodos?: boolean\n}) =>\n Effect.gen(function* () {\n const {\n projectName,\n clearEmpty = true,\n clearInvalid = true,\n skipWithTodos = true,\n clearOrphanAgents = false,\n clearOrphanTodos = false,\n } = options\n const projects = yield* listProjects\n const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects\n\n let deletedSessionCount = 0\n let removedMessageCount = 0\n let deletedOrphanAgentCount = 0\n let deletedOrphanTodoCount = 0\n const sessionsToDelete: { project: string; sessionId: string }[] = []\n\n // Step 1: Clean invalid API key messages from all sessions (if clearInvalid)\n if (clearInvalid) {\n for (const project of targetProjects) {\n const projectPath = path.join(getSessionsDir(), project.name)\n const files = yield* Effect.tryPromise(() => fs.readdir(projectPath))\n const sessionFiles = files.filter((f) => f.endsWith('.jsonl') && !f.startsWith('agent-'))\n\n for (const file of sessionFiles) {\n const sessionId = file.replace('.jsonl', '')\n const result = yield* cleanInvalidMessages(project.name, sessionId)\n removedMessageCount += result.removedCount\n\n // Mark for deletion if now empty\n if (result.remainingCount === 0) {\n sessionsToDelete.push({ project: project.name, sessionId })\n }\n }\n }\n }\n\n // Step 2: Also find originally empty sessions (if clearEmpty is true)\n if (clearEmpty) {\n for (const project of targetProjects) {\n const sessions = yield* listSessions(project.name)\n for (const session of sessions) {\n if (session.messageCount === 0) {\n const alreadyMarked = sessionsToDelete.some(\n (s) => s.project === project.name && s.sessionId === session.id\n )\n if (!alreadyMarked) {\n // Skip sessions with todos if skipWithTodos is true\n if (skipWithTodos) {\n const linkedAgents = yield* findLinkedAgents(project.name, session.id)\n const hasTodos = yield* sessionHasTodos(session.id, linkedAgents)\n if (hasTodos) continue\n }\n sessionsToDelete.push({ project: project.name, sessionId: session.id })\n }\n }\n }\n }\n }\n\n // Step 3: Delete all empty sessions (this also deletes linked agents and todos)\n for (const { project, sessionId } of sessionsToDelete) {\n yield* deleteSession(project, sessionId)\n deletedSessionCount++\n }\n\n // Step 4: Delete orphan agents if requested\n if (clearOrphanAgents) {\n for (const project of targetProjects) {\n const result = yield* deleteOrphanAgents(project.name)\n deletedOrphanAgentCount += result.count\n }\n }\n\n // Step 5: Delete orphan todos if requested (global, not per-project)\n if (clearOrphanTodos) {\n const result = yield* deleteOrphanTodos()\n deletedOrphanTodoCount = result.deletedCount\n }\n\n return {\n success: true,\n deletedCount: deletedSessionCount,\n removedMessageCount,\n deletedOrphanAgentCount,\n deletedOrphanTodoCount,\n } satisfies ClearSessionsResult\n })\n"],"mappings":"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","names":["home","fs","path","Effect","fs","path","Effect","Effect","fs","path","Effect","stat","agentBackupDir","hasSummary","remainingCount"]}
|