@claude-sessions/core 0.4.0 → 0.4.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/paths.ts
2
- import * as fs from "fs";
2
+ import * as fs2 from "fs";
3
3
  import * as os from "os";
4
4
  import * as path from "path";
5
5
 
@@ -22,19 +22,153 @@ var createLogger = (namespace) => ({
22
22
  error: (msg, ...args) => currentLogger.error(`[${namespace}] ${msg}`, ...args)
23
23
  });
24
24
 
25
+ // src/utils.ts
26
+ import { Effect } from "effect";
27
+ import * as fs from "fs/promises";
28
+ var logger = createLogger("utils");
29
+ var extractTextContent = (message) => {
30
+ if (!message) return "";
31
+ const content = message.content;
32
+ if (!content) return "";
33
+ if (typeof content === "string") return content;
34
+ if (Array.isArray(content)) {
35
+ return content.filter((item) => typeof item === "object" && item?.type === "text").map((item) => {
36
+ if (item.text == null) {
37
+ logger.warn("TextContent item has undefined or null text property");
38
+ return "";
39
+ }
40
+ return item.text;
41
+ }).join("");
42
+ }
43
+ return "";
44
+ };
45
+ var parseCommandMessage = (content) => {
46
+ const name = content?.match(/<command-name>([^<]+)<\/command-name>/)?.[1] ?? "";
47
+ const message = content?.match(/<command-message>([^<]+)<\/command-message>/)?.[1] ?? "";
48
+ return { name, message };
49
+ };
50
+ var extractTitle = (text) => {
51
+ if (!text) return "Untitled";
52
+ const { name } = parseCommandMessage(text);
53
+ if (name) return name;
54
+ let cleaned = text.replace(/<ide_[^>]*>[\s\S]*?<\/ide_[^>]*>/g, "").trim();
55
+ if (!cleaned) return "Untitled";
56
+ if (cleaned.includes("\n\n")) {
57
+ cleaned = cleaned.split("\n\n")[0];
58
+ }
59
+ if (cleaned.length > 100) {
60
+ return cleaned.slice(0, 100) + "...";
61
+ }
62
+ return cleaned || "Untitled";
63
+ };
64
+ var isInvalidApiKeyMessage = (msg) => {
65
+ const text = extractTextContent(msg.message);
66
+ return text.includes("Invalid API key");
67
+ };
68
+ var ERROR_SESSION_PATTERNS = [
69
+ "API Error",
70
+ "authentication_error",
71
+ "Invalid API key",
72
+ "OAuth token has expired",
73
+ "Please run /login"
74
+ ];
75
+ var isErrorSessionTitle = (title) => {
76
+ if (!title) return false;
77
+ return ERROR_SESSION_PATTERNS.some((pattern) => title.includes(pattern));
78
+ };
79
+ var isContinuationSummary = (msg) => {
80
+ if (msg.isCompactSummary === true) return true;
81
+ if (msg.type !== "user") return false;
82
+ const text = extractTextContent(msg.message);
83
+ return text.startsWith("This session is being continued from");
84
+ };
85
+ var getDisplayTitle = (customTitle, currentSummary, title, maxLength = 60, fallback = "Untitled") => {
86
+ if (customTitle) return customTitle;
87
+ if (currentSummary) {
88
+ return currentSummary.length > maxLength ? currentSummary.slice(0, maxLength - 3) + "..." : currentSummary;
89
+ }
90
+ if (title && title !== "Untitled") {
91
+ if (title.includes("<command-name>")) {
92
+ const { name } = parseCommandMessage(title);
93
+ if (name) return name;
94
+ }
95
+ return title;
96
+ }
97
+ return fallback;
98
+ };
99
+ var replaceMessageContent = (msg, text) => ({
100
+ ...msg,
101
+ message: {
102
+ ...msg.message,
103
+ content: [{ type: "text", text }]
104
+ },
105
+ toolUseResult: void 0
106
+ });
107
+ var cleanupSplitFirstMessage = (msg) => {
108
+ const toolUseResult = msg.toolUseResult;
109
+ if (!toolUseResult) return msg;
110
+ if (typeof toolUseResult === "object" && "answers" in toolUseResult) {
111
+ const answers = toolUseResult.answers;
112
+ const qaText = Object.entries(answers).map(([q, a]) => `Q: ${q}
113
+ A: ${a}`).join("\n\n");
114
+ return replaceMessageContent(msg, qaText);
115
+ }
116
+ if (typeof toolUseResult === "string") {
117
+ const rejectionMarker = "The user provided the following reason for the rejection:";
118
+ const rejectionIndex = toolUseResult.indexOf(rejectionMarker);
119
+ if (rejectionIndex === -1) return msg;
120
+ const text = toolUseResult.slice(rejectionIndex + rejectionMarker.length).trim();
121
+ if (!text) return msg;
122
+ return replaceMessageContent(msg, text);
123
+ }
124
+ return msg;
125
+ };
126
+ var maskHomePath = (text, homeDir) => {
127
+ if (!homeDir) return text;
128
+ const escapedHome = homeDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
129
+ const regex = new RegExp(`${escapedHome}(?=[/\\\\]|$)`, "g");
130
+ return text.replace(regex, "~");
131
+ };
132
+ var getSessionSortTimestamp = (session) => {
133
+ const timestampStr = session.summaries?.[0]?.timestamp ?? session.createdAt;
134
+ return timestampStr ? new Date(timestampStr).getTime() : 0;
135
+ };
136
+ var tryParseJsonLine = (line, lineNumber, filePath) => {
137
+ try {
138
+ return JSON.parse(line);
139
+ } catch {
140
+ if (filePath) {
141
+ console.warn(`Skipping invalid JSON at line ${lineNumber} in ${filePath}`);
142
+ }
143
+ return null;
144
+ }
145
+ };
146
+ var parseJsonlLines = (lines, filePath) => {
147
+ return lines.map((line, idx) => {
148
+ try {
149
+ return JSON.parse(line);
150
+ } catch (e) {
151
+ const err = e;
152
+ throw new Error(`Failed to parse line ${idx + 1} in ${filePath}: ${err.message}`);
153
+ }
154
+ });
155
+ };
156
+ var readJsonlFile = (filePath) => Effect.gen(function* () {
157
+ const content = yield* Effect.tryPromise(() => fs.readFile(filePath, "utf-8"));
158
+ const lines = content.trim().split("\n").filter(Boolean);
159
+ return parseJsonlLines(lines, filePath);
160
+ });
161
+
25
162
  // src/paths.ts
26
163
  var log = createLogger("paths");
27
164
  var getSessionsDir = () => process.env.CLAUDE_SESSIONS_DIR || path.join(os.homedir(), ".claude", "projects");
28
165
  var getTodosDir = () => path.join(os.homedir(), ".claude", "todos");
29
- var extractCwdFromContent = (content) => {
166
+ var extractCwdFromContent = (content, filePath) => {
30
167
  const lines = content.split("\n").filter((l) => l.trim());
31
- for (const line of lines) {
32
- try {
33
- const parsed = JSON.parse(line);
34
- if (parsed?.cwd) {
35
- return parsed.cwd;
36
- }
37
- } catch {
168
+ for (let i = 0; i < lines.length; i++) {
169
+ const parsed = tryParseJsonLine(lines[i], i + 1, filePath);
170
+ if (parsed?.cwd) {
171
+ return parsed.cwd;
38
172
  }
39
173
  }
40
174
  return null;
@@ -79,7 +213,7 @@ var pathToFolderName = (absolutePath) => {
79
213
  }
80
214
  return convertNonAscii(absolutePath).replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-").replace(/\./g, "-");
81
215
  };
82
- var tryGetCwdFromFile = (filePath, fileSystem = fs, logger2 = log) => {
216
+ var tryGetCwdFromFile = (filePath, fileSystem = fs2, logger2 = log) => {
83
217
  const basename3 = path.basename(filePath);
84
218
  try {
85
219
  const content = fileSystem.readFileSync(filePath, "utf-8");
@@ -99,7 +233,7 @@ var tryGetCwdFromFile = (filePath, fileSystem = fs, logger2 = log) => {
99
233
  return null;
100
234
  }
101
235
  };
102
- var getRealPathFromSession = (folderName, sessionsDir = getSessionsDir(), fileSystem = fs, logger2 = log) => {
236
+ var getRealPathFromSession = (folderName, sessionsDir = getSessionsDir(), fileSystem = fs2, logger2 = log) => {
103
237
  const projectDir = path.join(sessionsDir, folderName);
104
238
  try {
105
239
  const files = fileSystem.readdirSync(projectDir).filter(isSessionFile);
@@ -135,7 +269,7 @@ var folderNameToPath = (folderName) => {
135
269
  const absolutePath = folderNameToDisplayPath(folderName);
136
270
  return toRelativePath(absolutePath, homeDir);
137
271
  };
138
- var findProjectByWorkspacePath = (workspacePath, projectNames, sessionsDir = getSessionsDir(), fileSystem = fs, logger2 = log) => {
272
+ var findProjectByWorkspacePath = (workspacePath, projectNames, sessionsDir = getSessionsDir(), fileSystem = fs2, logger2 = log) => {
139
273
  const directMatch = pathToFolderName(workspacePath);
140
274
  if (projectNames.includes(directMatch)) {
141
275
  return directMatch;
@@ -160,103 +294,7 @@ var findProjectByWorkspacePath = (workspacePath, projectNames, sessionsDir = get
160
294
  return null;
161
295
  };
162
296
 
163
- // src/utils.ts
164
- var logger = createLogger("utils");
165
- var extractTextContent = (message) => {
166
- if (!message) return "";
167
- const content = message.content;
168
- if (!content) return "";
169
- if (typeof content === "string") return content;
170
- if (Array.isArray(content)) {
171
- return content.filter((item) => typeof item === "object" && item?.type === "text").map((item) => {
172
- if (item.text == null) {
173
- logger.warn("TextContent item has undefined or null text property");
174
- return "";
175
- }
176
- return item.text;
177
- }).join("");
178
- }
179
- return "";
180
- };
181
- var extractTitle = (text) => {
182
- if (!text) return "Untitled";
183
- let cleaned = text.replace(/<ide_[^>]*>[\s\S]*?<\/ide_[^>]*>/g, "").trim();
184
- if (!cleaned) return "Untitled";
185
- if (cleaned.includes("\n\n")) {
186
- cleaned = cleaned.split("\n\n")[0];
187
- } else if (cleaned.includes("\n")) {
188
- cleaned = cleaned.split("\n")[0];
189
- }
190
- if (cleaned.length > 100) {
191
- return cleaned.slice(0, 100) + "...";
192
- }
193
- return cleaned || "Untitled";
194
- };
195
- var isInvalidApiKeyMessage = (msg) => {
196
- const text = extractTextContent(msg.message);
197
- return text.includes("Invalid API key");
198
- };
199
- var ERROR_SESSION_PATTERNS = [
200
- "API Error",
201
- "authentication_error",
202
- "Invalid API key",
203
- "OAuth token has expired",
204
- "Please run /login"
205
- ];
206
- var isErrorSessionTitle = (title) => {
207
- if (!title) return false;
208
- return ERROR_SESSION_PATTERNS.some((pattern) => title.includes(pattern));
209
- };
210
- var isContinuationSummary = (msg) => {
211
- if (msg.isCompactSummary === true) return true;
212
- if (msg.type !== "user") return false;
213
- const text = extractTextContent(msg.message);
214
- return text.startsWith("This session is being continued from");
215
- };
216
- var getDisplayTitle = (customTitle, currentSummary, title, maxLength = 60, fallback = "Untitled") => {
217
- if (customTitle) return customTitle;
218
- if (currentSummary) {
219
- return currentSummary.length > maxLength ? currentSummary.slice(0, maxLength - 3) + "..." : currentSummary;
220
- }
221
- if (title && title !== "Untitled") return title;
222
- return fallback;
223
- };
224
- var replaceMessageContent = (msg, text) => ({
225
- ...msg,
226
- message: {
227
- ...msg.message,
228
- content: [{ type: "text", text }]
229
- },
230
- toolUseResult: void 0
231
- });
232
- var cleanupSplitFirstMessage = (msg) => {
233
- const toolUseResult = msg.toolUseResult;
234
- if (!toolUseResult) return msg;
235
- if (typeof toolUseResult === "object" && "answers" in toolUseResult) {
236
- const answers = toolUseResult.answers;
237
- const qaText = Object.entries(answers).map(([q, a]) => `Q: ${q}
238
- A: ${a}`).join("\n\n");
239
- return replaceMessageContent(msg, qaText);
240
- }
241
- if (typeof toolUseResult === "string") {
242
- const rejectionMarker = "The user provided the following reason for the rejection:";
243
- const rejectionIndex = toolUseResult.indexOf(rejectionMarker);
244
- if (rejectionIndex === -1) return msg;
245
- const text = toolUseResult.slice(rejectionIndex + rejectionMarker.length).trim();
246
- if (!text) return msg;
247
- return replaceMessageContent(msg, text);
248
- }
249
- return msg;
250
- };
251
- var maskHomePath = (text, homeDir) => {
252
- if (!homeDir) return text;
253
- const escapedHome = homeDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
254
- const regex = new RegExp(`${escapedHome}(?=[/\\\\]|$)`, "g");
255
- return text.replace(regex, "~");
256
- };
257
- var getSessionSortTimestamp = (session) => {
258
- return session.summaries?.[0]?.timestamp ?? session.createdAt;
259
- };
297
+ // src/projects.ts
260
298
  var sortProjects = (projects, options = {}) => {
261
299
  const { currentProjectName, homeDir, filterEmpty = true } = options;
262
300
  const filtered = filterEmpty ? projects.filter((p) => p.sessionCount > 0) : projects;
@@ -277,17 +315,17 @@ var sortProjects = (projects, options = {}) => {
277
315
  };
278
316
 
279
317
  // src/agents.ts
280
- import { Effect } from "effect";
281
- import * as fs2 from "fs/promises";
318
+ import { Effect as Effect2 } from "effect";
319
+ import * as fs3 from "fs/promises";
282
320
  import * as path2 from "path";
283
- var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
321
+ var findLinkedAgents = (projectName, sessionId) => Effect2.gen(function* () {
284
322
  const projectPath = path2.join(getSessionsDir(), projectName);
285
- const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
323
+ const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
286
324
  const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
287
325
  const linkedAgents = [];
288
326
  for (const agentFile of agentFiles) {
289
327
  const filePath = path2.join(projectPath, agentFile);
290
- const content = yield* Effect.tryPromise(() => fs2.readFile(filePath, "utf-8"));
328
+ const content = yield* Effect2.tryPromise(() => fs3.readFile(filePath, "utf-8"));
291
329
  const firstLine = content.split("\n")[0];
292
330
  if (firstLine) {
293
331
  try {
@@ -301,16 +339,16 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
301
339
  }
302
340
  return linkedAgents;
303
341
  });
304
- var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
342
+ var findOrphanAgentsWithPaths = (projectName) => Effect2.gen(function* () {
305
343
  const projectPath = path2.join(getSessionsDir(), projectName);
306
- const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
344
+ const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
307
345
  const sessionIds = new Set(
308
346
  files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
309
347
  );
310
348
  const orphanAgents = [];
311
349
  const checkAgentFile = async (filePath) => {
312
350
  try {
313
- const content = await fs2.readFile(filePath, "utf-8");
351
+ const content = await fs3.readFile(filePath, "utf-8");
314
352
  const lines = content.split("\n").filter((l) => l.trim());
315
353
  const firstLine = lines[0];
316
354
  if (!firstLine) return null;
@@ -330,27 +368,27 @@ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
330
368
  const rootAgentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
331
369
  for (const agentFile of rootAgentFiles) {
332
370
  const filePath = path2.join(projectPath, agentFile);
333
- const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
371
+ const orphan = yield* Effect2.tryPromise(() => checkAgentFile(filePath));
334
372
  if (orphan) {
335
373
  orphanAgents.push({ ...orphan, filePath });
336
374
  }
337
375
  }
338
376
  for (const entry of files) {
339
377
  const entryPath = path2.join(projectPath, entry);
340
- const stat4 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
378
+ const stat4 = yield* Effect2.tryPromise(() => fs3.stat(entryPath).catch(() => null));
341
379
  if (stat4?.isDirectory() && !entry.startsWith(".")) {
342
380
  const subagentsPath = path2.join(entryPath, "subagents");
343
- const subagentsExists = yield* Effect.tryPromise(
344
- () => fs2.stat(subagentsPath).then(() => true).catch(() => false)
381
+ const subagentsExists = yield* Effect2.tryPromise(
382
+ () => fs3.stat(subagentsPath).then(() => true).catch(() => false)
345
383
  );
346
384
  if (subagentsExists) {
347
- const subagentFiles = yield* Effect.tryPromise(
348
- () => fs2.readdir(subagentsPath).catch(() => [])
385
+ const subagentFiles = yield* Effect2.tryPromise(
386
+ () => fs3.readdir(subagentsPath).catch(() => [])
349
387
  );
350
388
  for (const subagentFile of subagentFiles) {
351
389
  if (subagentFile.startsWith("agent-") && subagentFile.endsWith(".jsonl")) {
352
390
  const filePath = path2.join(subagentsPath, subagentFile);
353
- const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
391
+ const orphan = yield* Effect2.tryPromise(() => checkAgentFile(filePath));
354
392
  if (orphan) {
355
393
  orphanAgents.push({ ...orphan, filePath });
356
394
  }
@@ -361,11 +399,11 @@ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
361
399
  }
362
400
  return orphanAgents;
363
401
  });
364
- var findOrphanAgents = (projectName) => Effect.gen(function* () {
402
+ var findOrphanAgents = (projectName) => Effect2.gen(function* () {
365
403
  const orphans = yield* findOrphanAgentsWithPaths(projectName);
366
404
  return orphans.map(({ agentId, sessionId }) => ({ agentId, sessionId }));
367
405
  });
368
- var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
406
+ var deleteOrphanAgents = (projectName) => Effect2.gen(function* () {
369
407
  const projectPath = path2.join(getSessionsDir(), projectName);
370
408
  const orphans = yield* findOrphanAgentsWithPaths(projectName);
371
409
  const deletedAgents = [];
@@ -379,35 +417,35 @@ var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
379
417
  foldersToCheck.add(parentDir);
380
418
  }
381
419
  if (orphan.lineCount <= 2) {
382
- yield* Effect.tryPromise(() => fs2.unlink(orphan.filePath));
420
+ yield* Effect2.tryPromise(() => fs3.unlink(orphan.filePath));
383
421
  deletedAgents.push(orphan.agentId);
384
422
  } else {
385
423
  if (!backupDirCreated) {
386
424
  const backupDir2 = path2.join(projectPath, ".bak");
387
- yield* Effect.tryPromise(() => fs2.mkdir(backupDir2, { recursive: true }));
425
+ yield* Effect2.tryPromise(() => fs3.mkdir(backupDir2, { recursive: true }));
388
426
  backupDirCreated = true;
389
427
  }
390
428
  const backupDir = path2.join(projectPath, ".bak");
391
429
  const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
392
- yield* Effect.tryPromise(() => fs2.rename(orphan.filePath, agentBackupPath));
430
+ yield* Effect2.tryPromise(() => fs3.rename(orphan.filePath, agentBackupPath));
393
431
  backedUpAgents.push(orphan.agentId);
394
432
  }
395
433
  }
396
434
  for (const subagentsDir of foldersToCheck) {
397
- const isEmpty = yield* Effect.tryPromise(async () => {
398
- const files = await fs2.readdir(subagentsDir);
435
+ const isEmpty = yield* Effect2.tryPromise(async () => {
436
+ const files = await fs3.readdir(subagentsDir);
399
437
  return files.length === 0;
400
438
  });
401
439
  if (isEmpty) {
402
- yield* Effect.tryPromise(() => fs2.rmdir(subagentsDir));
440
+ yield* Effect2.tryPromise(() => fs3.rmdir(subagentsDir));
403
441
  cleanedFolders.push(subagentsDir);
404
442
  const sessionDir = path2.dirname(subagentsDir);
405
- const sessionDirEmpty = yield* Effect.tryPromise(async () => {
406
- const files = await fs2.readdir(sessionDir);
443
+ const sessionDirEmpty = yield* Effect2.tryPromise(async () => {
444
+ const files = await fs3.readdir(sessionDir);
407
445
  return files.length === 0;
408
446
  });
409
447
  if (sessionDirEmpty) {
410
- yield* Effect.tryPromise(() => fs2.rmdir(sessionDir));
448
+ yield* Effect2.tryPromise(() => fs3.rmdir(sessionDir));
411
449
  cleanedFolders.push(sessionDir);
412
450
  }
413
451
  }
@@ -423,33 +461,31 @@ var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
423
461
  count: deletedAgents.length + backedUpAgents.length
424
462
  };
425
463
  });
426
- var loadAgentMessages = (projectName, _sessionId, agentId) => Effect.gen(function* () {
464
+ var loadAgentMessages = (projectName, _sessionId, agentId) => Effect2.gen(function* () {
427
465
  const projectPath = path2.join(getSessionsDir(), projectName);
428
466
  const agentFilePath = path2.join(projectPath, `${agentId}.jsonl`);
429
- const content = yield* Effect.tryPromise(() => fs2.readFile(agentFilePath, "utf-8"));
467
+ const content = yield* Effect2.tryPromise(() => fs3.readFile(agentFilePath, "utf-8"));
430
468
  const lines = content.split("\n").filter((line) => line.trim());
431
469
  const messages = [];
432
- for (const line of lines) {
433
- try {
434
- const parsed = JSON.parse(line);
435
- if ("sessionId" in parsed && !("type" in parsed)) {
436
- continue;
437
- }
438
- messages.push(parsed);
439
- } catch {
470
+ for (let i = 0; i < lines.length; i++) {
471
+ const parsed = tryParseJsonLine(lines[i], i + 1, agentFilePath);
472
+ if (!parsed) continue;
473
+ if ("sessionId" in parsed && !("type" in parsed)) {
474
+ continue;
440
475
  }
476
+ messages.push(parsed);
441
477
  }
442
478
  return messages;
443
479
  });
444
480
 
445
481
  // src/todos.ts
446
- import { Effect as Effect2 } from "effect";
447
- import * as fs3 from "fs/promises";
482
+ import { Effect as Effect3 } from "effect";
483
+ import * as fs4 from "fs/promises";
448
484
  import * as path3 from "path";
449
- var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
485
+ var findLinkedTodos = (sessionId, agentIds = []) => Effect3.gen(function* () {
450
486
  const todosDir = getTodosDir();
451
- const exists = yield* Effect2.tryPromise(
452
- () => fs3.access(todosDir).then(() => true).catch(() => false)
487
+ const exists = yield* Effect3.tryPromise(
488
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
453
489
  );
454
490
  if (!exists) {
455
491
  return {
@@ -461,17 +497,17 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
461
497
  }
462
498
  const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
463
499
  let sessionTodos = [];
464
- const sessionTodoExists = yield* Effect2.tryPromise(
465
- () => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
500
+ const sessionTodoExists = yield* Effect3.tryPromise(
501
+ () => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
466
502
  );
467
503
  if (sessionTodoExists) {
468
- const content = yield* Effect2.tryPromise(() => fs3.readFile(sessionTodoPath, "utf-8"));
504
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(sessionTodoPath, "utf-8"));
469
505
  try {
470
506
  sessionTodos = JSON.parse(content);
471
507
  } catch {
472
508
  }
473
509
  }
474
- const allFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
510
+ const allFiles = yield* Effect3.tryPromise(() => fs4.readdir(todosDir));
475
511
  const agentTodoPattern = new RegExp(`^${sessionId}-agent-([a-f0-9-]+)\\.json$`);
476
512
  const discoveredAgentIds = new Set(agentIds);
477
513
  for (const file of allFiles) {
@@ -484,11 +520,11 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
484
520
  for (const agentId of discoveredAgentIds) {
485
521
  const shortAgentId = agentId.replace("agent-", "");
486
522
  const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
487
- const agentTodoExists = yield* Effect2.tryPromise(
488
- () => fs3.access(agentTodoPath).then(() => true).catch(() => false)
523
+ const agentTodoExists = yield* Effect3.tryPromise(
524
+ () => fs4.access(agentTodoPath).then(() => true).catch(() => false)
489
525
  );
490
526
  if (agentTodoExists) {
491
- const content = yield* Effect2.tryPromise(() => fs3.readFile(agentTodoPath, "utf-8"));
527
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(agentTodoPath, "utf-8"));
492
528
  try {
493
529
  const todos = JSON.parse(content);
494
530
  if (todos.length > 0) {
@@ -506,25 +542,25 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
506
542
  hasTodos
507
543
  };
508
544
  });
509
- var sessionHasTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
545
+ var sessionHasTodos = (sessionId, agentIds = []) => Effect3.gen(function* () {
510
546
  const todosDir = getTodosDir();
511
- const exists = yield* Effect2.tryPromise(
512
- () => fs3.access(todosDir).then(() => true).catch(() => false)
547
+ const exists = yield* Effect3.tryPromise(
548
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
513
549
  );
514
550
  if (!exists) return false;
515
551
  const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
516
- const sessionTodoExists = yield* Effect2.tryPromise(
517
- () => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
552
+ const sessionTodoExists = yield* Effect3.tryPromise(
553
+ () => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
518
554
  );
519
555
  if (sessionTodoExists) {
520
- const content = yield* Effect2.tryPromise(() => fs3.readFile(sessionTodoPath, "utf-8"));
556
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(sessionTodoPath, "utf-8"));
521
557
  try {
522
558
  const todos = JSON.parse(content);
523
559
  if (todos.length > 0) return true;
524
560
  } catch {
525
561
  }
526
562
  }
527
- const allFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
563
+ const allFiles = yield* Effect3.tryPromise(() => fs4.readdir(todosDir));
528
564
  const agentTodoPattern = new RegExp(`^${sessionId}-agent-([a-f0-9-]+)\\.json$`);
529
565
  const discoveredAgentIds = new Set(agentIds);
530
566
  for (const file of allFiles) {
@@ -536,11 +572,11 @@ var sessionHasTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
536
572
  for (const agentId of discoveredAgentIds) {
537
573
  const shortAgentId = agentId.replace("agent-", "");
538
574
  const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
539
- const agentTodoExists = yield* Effect2.tryPromise(
540
- () => fs3.access(agentTodoPath).then(() => true).catch(() => false)
575
+ const agentTodoExists = yield* Effect3.tryPromise(
576
+ () => fs4.access(agentTodoPath).then(() => true).catch(() => false)
541
577
  );
542
578
  if (agentTodoExists) {
543
- const content = yield* Effect2.tryPromise(() => fs3.readFile(agentTodoPath, "utf-8"));
579
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(agentTodoPath, "utf-8"));
544
580
  try {
545
581
  const todos = JSON.parse(content);
546
582
  if (todos.length > 0) return true;
@@ -550,60 +586,60 @@ var sessionHasTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
550
586
  }
551
587
  return false;
552
588
  });
553
- var deleteLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
589
+ var deleteLinkedTodos = (sessionId, agentIds) => Effect3.gen(function* () {
554
590
  const todosDir = getTodosDir();
555
- const exists = yield* Effect2.tryPromise(
556
- () => fs3.access(todosDir).then(() => true).catch(() => false)
591
+ const exists = yield* Effect3.tryPromise(
592
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
557
593
  );
558
594
  if (!exists) return { deletedCount: 0 };
559
595
  const backupDir = path3.join(todosDir, ".bak");
560
- yield* Effect2.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
596
+ yield* Effect3.tryPromise(() => fs4.mkdir(backupDir, { recursive: true }));
561
597
  let deletedCount = 0;
562
598
  const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
563
- const sessionTodoExists = yield* Effect2.tryPromise(
564
- () => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
599
+ const sessionTodoExists = yield* Effect3.tryPromise(
600
+ () => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
565
601
  );
566
602
  if (sessionTodoExists) {
567
603
  const backupPath = path3.join(backupDir, `${sessionId}.json`);
568
- yield* Effect2.tryPromise(() => fs3.rename(sessionTodoPath, backupPath));
604
+ yield* Effect3.tryPromise(() => fs4.rename(sessionTodoPath, backupPath));
569
605
  deletedCount++;
570
606
  }
571
607
  for (const agentId of agentIds) {
572
608
  const shortAgentId = agentId.replace("agent-", "");
573
609
  const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
574
- const agentTodoExists = yield* Effect2.tryPromise(
575
- () => fs3.access(agentTodoPath).then(() => true).catch(() => false)
610
+ const agentTodoExists = yield* Effect3.tryPromise(
611
+ () => fs4.access(agentTodoPath).then(() => true).catch(() => false)
576
612
  );
577
613
  if (agentTodoExists) {
578
614
  const backupPath = path3.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`);
579
- yield* Effect2.tryPromise(() => fs3.rename(agentTodoPath, backupPath));
615
+ yield* Effect3.tryPromise(() => fs4.rename(agentTodoPath, backupPath));
580
616
  deletedCount++;
581
617
  }
582
618
  }
583
619
  return { deletedCount };
584
620
  });
585
- var findOrphanTodos = () => Effect2.gen(function* () {
621
+ var findOrphanTodos = () => Effect3.gen(function* () {
586
622
  const todosDir = getTodosDir();
587
623
  const sessionsDir = getSessionsDir();
588
- const [todosExists, sessionsExists] = yield* Effect2.all([
589
- Effect2.tryPromise(
590
- () => fs3.access(todosDir).then(() => true).catch(() => false)
624
+ const [todosExists, sessionsExists] = yield* Effect3.all([
625
+ Effect3.tryPromise(
626
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
591
627
  ),
592
- Effect2.tryPromise(
593
- () => fs3.access(sessionsDir).then(() => true).catch(() => false)
628
+ Effect3.tryPromise(
629
+ () => fs4.access(sessionsDir).then(() => true).catch(() => false)
594
630
  )
595
631
  ]);
596
632
  if (!todosExists || !sessionsExists) return [];
597
- const todoFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
633
+ const todoFiles = yield* Effect3.tryPromise(() => fs4.readdir(todosDir));
598
634
  const jsonFiles = todoFiles.filter((f) => f.endsWith(".json"));
599
635
  const validSessionIds = /* @__PURE__ */ new Set();
600
- const projectEntries = yield* Effect2.tryPromise(
601
- () => fs3.readdir(sessionsDir, { withFileTypes: true })
636
+ const projectEntries = yield* Effect3.tryPromise(
637
+ () => fs4.readdir(sessionsDir, { withFileTypes: true })
602
638
  );
603
639
  for (const entry of projectEntries) {
604
640
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
605
641
  const projectPath = path3.join(sessionsDir, entry.name);
606
- const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
642
+ const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
607
643
  for (const f of files) {
608
644
  if (f.endsWith(".jsonl") && !f.startsWith("agent-")) {
609
645
  validSessionIds.add(f.replace(".jsonl", ""));
@@ -622,40 +658,40 @@ var findOrphanTodos = () => Effect2.gen(function* () {
622
658
  }
623
659
  return orphans;
624
660
  });
625
- var deleteOrphanTodos = () => Effect2.gen(function* () {
661
+ var deleteOrphanTodos = () => Effect3.gen(function* () {
626
662
  const todosDir = getTodosDir();
627
663
  const orphans = yield* findOrphanTodos();
628
664
  if (orphans.length === 0) return { success: true, deletedCount: 0 };
629
665
  const backupDir = path3.join(todosDir, ".bak");
630
- yield* Effect2.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
666
+ yield* Effect3.tryPromise(() => fs4.mkdir(backupDir, { recursive: true }));
631
667
  let deletedCount = 0;
632
668
  for (const orphan of orphans) {
633
669
  const filePath = path3.join(todosDir, orphan);
634
670
  const backupPath = path3.join(backupDir, orphan);
635
- yield* Effect2.tryPromise(() => fs3.rename(filePath, backupPath));
671
+ yield* Effect3.tryPromise(() => fs4.rename(filePath, backupPath));
636
672
  deletedCount++;
637
673
  }
638
674
  return { success: true, deletedCount };
639
675
  });
640
676
 
641
677
  // src/session/projects.ts
642
- import { Effect as Effect3 } from "effect";
643
- import * as fs4 from "fs/promises";
678
+ import { Effect as Effect4 } from "effect";
679
+ import * as fs5 from "fs/promises";
644
680
  import * as path4 from "path";
645
- var listProjects = Effect3.gen(function* () {
681
+ var listProjects = Effect4.gen(function* () {
646
682
  const sessionsDir = getSessionsDir();
647
- const exists = yield* Effect3.tryPromise(
648
- () => fs4.access(sessionsDir).then(() => true).catch(() => false)
683
+ const exists = yield* Effect4.tryPromise(
684
+ () => fs5.access(sessionsDir).then(() => true).catch(() => false)
649
685
  );
650
686
  if (!exists) {
651
687
  return [];
652
688
  }
653
- const entries = yield* Effect3.tryPromise(() => fs4.readdir(sessionsDir, { withFileTypes: true }));
654
- const projects = yield* Effect3.all(
689
+ const entries = yield* Effect4.tryPromise(() => fs5.readdir(sessionsDir, { withFileTypes: true }));
690
+ const projects = yield* Effect4.all(
655
691
  entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map(
656
- (entry) => Effect3.gen(function* () {
692
+ (entry) => Effect4.gen(function* () {
657
693
  const projectPath = path4.join(sessionsDir, entry.name);
658
- const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
694
+ const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
659
695
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
660
696
  return {
661
697
  name: entry.name,
@@ -671,15 +707,184 @@ var listProjects = Effect3.gen(function* () {
671
707
  });
672
708
 
673
709
  // src/session/crud.ts
674
- import { Effect as Effect4, pipe, Array as A, Option as O } from "effect";
675
- import * as fs5 from "fs/promises";
710
+ import { Effect as Effect5, pipe, Array as A, Option as O } from "effect";
711
+ import * as fs6 from "fs/promises";
676
712
  import * as path5 from "path";
677
713
  import * as crypto from "crypto";
678
- var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(function* () {
714
+
715
+ // src/session/validation.ts
716
+ function validateChain(messages) {
717
+ const errors = [];
718
+ const uuids = /* @__PURE__ */ new Set();
719
+ for (const msg of messages) {
720
+ if (msg.uuid) {
721
+ uuids.add(msg.uuid);
722
+ }
723
+ }
724
+ let foundFirstMessage = false;
725
+ for (let i = 0; i < messages.length; i++) {
726
+ const msg = messages[i];
727
+ if (msg.type === "file-history-snapshot") {
728
+ continue;
729
+ }
730
+ if (!msg.uuid) {
731
+ continue;
732
+ }
733
+ if (!foundFirstMessage) {
734
+ foundFirstMessage = true;
735
+ if (msg.parentUuid === null) {
736
+ continue;
737
+ }
738
+ if (msg.parentUuid === void 0) {
739
+ errors.push({
740
+ type: "broken_chain",
741
+ uuid: msg.uuid,
742
+ line: i + 1,
743
+ parentUuid: null
744
+ });
745
+ continue;
746
+ }
747
+ }
748
+ if (msg.parentUuid === null || msg.parentUuid === void 0) {
749
+ errors.push({
750
+ type: "broken_chain",
751
+ uuid: msg.uuid,
752
+ line: i + 1,
753
+ parentUuid: null
754
+ });
755
+ continue;
756
+ }
757
+ if (!uuids.has(msg.parentUuid)) {
758
+ errors.push({
759
+ type: "orphan_parent",
760
+ uuid: msg.uuid,
761
+ line: i + 1,
762
+ parentUuid: msg.parentUuid
763
+ });
764
+ }
765
+ }
766
+ return {
767
+ valid: errors.length === 0,
768
+ errors
769
+ };
770
+ }
771
+ function validateToolUseResult(messages) {
772
+ const errors = [];
773
+ const toolUseIds = /* @__PURE__ */ new Set();
774
+ for (const msg of messages) {
775
+ const content = msg.message?.content;
776
+ if (Array.isArray(content)) {
777
+ for (const item of content) {
778
+ if (item.type === "tool_use" && item.id) {
779
+ toolUseIds.add(item.id);
780
+ }
781
+ }
782
+ }
783
+ }
784
+ for (let i = 0; i < messages.length; i++) {
785
+ const msg = messages[i];
786
+ const content = msg.message?.content;
787
+ if (!Array.isArray(content)) {
788
+ continue;
789
+ }
790
+ for (const item of content) {
791
+ if (item.type === "tool_result" && item.tool_use_id) {
792
+ if (!toolUseIds.has(item.tool_use_id)) {
793
+ errors.push({
794
+ type: "orphan_tool_result",
795
+ uuid: msg.uuid || "",
796
+ line: i + 1,
797
+ toolUseId: item.tool_use_id
798
+ });
799
+ }
800
+ }
801
+ }
802
+ }
803
+ return {
804
+ valid: errors.length === 0,
805
+ errors
806
+ };
807
+ }
808
+ function deleteMessageWithChainRepair(messages, targetId, targetType) {
809
+ let targetIndex = -1;
810
+ if (targetType === "file-history-snapshot") {
811
+ targetIndex = messages.findIndex(
812
+ (m) => m.type === "file-history-snapshot" && m.messageId === targetId
813
+ );
814
+ } else if (targetType === "summary") {
815
+ targetIndex = messages.findIndex(
816
+ (m) => m.leafUuid === targetId
817
+ );
818
+ } else {
819
+ targetIndex = messages.findIndex((m) => m.uuid === targetId);
820
+ if (targetIndex === -1) {
821
+ targetIndex = messages.findIndex(
822
+ (m) => m.leafUuid === targetId
823
+ );
824
+ }
825
+ if (targetIndex === -1) {
826
+ targetIndex = messages.findIndex(
827
+ (m) => m.type === "file-history-snapshot" && m.messageId === targetId
828
+ );
829
+ }
830
+ }
831
+ if (targetIndex === -1) {
832
+ return { deleted: null, alsoDeleted: [] };
833
+ }
834
+ const deletedMsg = messages[targetIndex];
835
+ const toolUseIds = [];
836
+ if (deletedMsg.type === "assistant") {
837
+ const content = deletedMsg.message?.content;
838
+ if (Array.isArray(content)) {
839
+ for (const item of content) {
840
+ if (item.type === "tool_use" && item.id) {
841
+ toolUseIds.push(item.id);
842
+ }
843
+ }
844
+ }
845
+ }
846
+ const toolResultIndices = [];
847
+ if (toolUseIds.length > 0) {
848
+ for (let i = 0; i < messages.length; i++) {
849
+ const msg = messages[i];
850
+ if (msg.type === "user") {
851
+ const content = msg.message?.content;
852
+ if (Array.isArray(content)) {
853
+ for (const item of content) {
854
+ if (item.type === "tool_result" && item.tool_use_id && toolUseIds.includes(item.tool_use_id)) {
855
+ toolResultIndices.push(i);
856
+ break;
857
+ }
858
+ }
859
+ }
860
+ }
861
+ }
862
+ }
863
+ const indicesToDelete = [targetIndex, ...toolResultIndices].sort((a, b) => b - a);
864
+ for (const idx of indicesToDelete) {
865
+ const msg = messages[idx];
866
+ const isInParentChain = msg.type !== "file-history-snapshot" && msg.uuid;
867
+ if (isInParentChain) {
868
+ const deletedUuid = msg.uuid;
869
+ const parentUuid = msg.parentUuid;
870
+ for (const m of messages) {
871
+ if (m.parentUuid === deletedUuid) {
872
+ m.parentUuid = parentUuid;
873
+ }
874
+ }
875
+ }
876
+ }
877
+ const alsoDeleted = toolResultIndices.map((i) => messages[i]);
878
+ for (const idx of indicesToDelete) {
879
+ messages.splice(idx, 1);
880
+ }
881
+ return { deleted: deletedMsg, alsoDeleted };
882
+ }
883
+
884
+ // src/session/crud.ts
885
+ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect5.gen(function* () {
679
886
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
680
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
681
- const lines = content.trim().split("\n").filter(Boolean);
682
- const messages = lines.map((line) => JSON.parse(line));
887
+ const messages = yield* readJsonlFile(filePath);
683
888
  const summaryIdx = messages.findIndex((m) => m.type === "summary");
684
889
  if (summaryIdx >= 0) {
685
890
  messages[summaryIdx] = { ...messages[summaryIdx], summary: newSummary };
@@ -693,20 +898,18 @@ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(f
693
898
  messages.unshift(summaryMsg);
694
899
  }
695
900
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
696
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
901
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
697
902
  return { success: true };
698
903
  });
699
- var listSessions = (projectName) => Effect4.gen(function* () {
904
+ var listSessions = (projectName) => Effect5.gen(function* () {
700
905
  const projectPath = path5.join(getSessionsDir(), projectName);
701
- const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
906
+ const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
702
907
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
703
- const sessions = yield* Effect4.all(
908
+ const sessions = yield* Effect5.all(
704
909
  sessionFiles.map(
705
- (file) => Effect4.gen(function* () {
910
+ (file) => Effect5.gen(function* () {
706
911
  const filePath = path5.join(projectPath, file);
707
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
708
- const lines = content.trim().split("\n").filter(Boolean);
709
- const messages = lines.map((line) => JSON.parse(line));
912
+ const messages = yield* readJsonlFile(filePath);
710
913
  const sessionId = file.replace(".jsonl", "");
711
914
  const userAssistantMessages = messages.filter(
712
915
  (m) => m.type === "user" || m.type === "assistant"
@@ -757,41 +960,24 @@ var listSessions = (projectName) => Effect4.gen(function* () {
757
960
  return dateB - dateA;
758
961
  });
759
962
  });
760
- var readSession = (projectName, sessionId) => Effect4.gen(function* () {
963
+ var readSession = (projectName, sessionId) => Effect5.gen(function* () {
761
964
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
762
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
763
- const lines = content.trim().split("\n").filter(Boolean);
764
- return lines.map((line) => JSON.parse(line));
965
+ return yield* readJsonlFile(filePath);
765
966
  });
766
- var deleteMessage = (projectName, sessionId, messageUuid) => Effect4.gen(function* () {
967
+ var deleteMessage = (projectName, sessionId, messageUuid, targetType) => Effect5.gen(function* () {
767
968
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
768
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
769
- const lines = content.trim().split("\n").filter(Boolean);
770
- const messages = lines.map((line) => JSON.parse(line));
771
- const targetIndex = messages.findIndex(
772
- (m) => m.uuid === messageUuid || m.messageId === messageUuid || m.leafUuid === messageUuid
773
- );
774
- if (targetIndex === -1) {
969
+ const messages = yield* readJsonlFile(filePath);
970
+ const result = deleteMessageWithChainRepair(messages, messageUuid, targetType);
971
+ if (!result.deleted) {
775
972
  return { success: false, error: "Message not found" };
776
973
  }
777
- const deletedMsg = messages[targetIndex];
778
- const deletedUuid = deletedMsg?.uuid ?? deletedMsg?.messageId;
779
- const parentUuid = deletedMsg?.parentUuid;
780
- for (const msg of messages) {
781
- if (msg.parentUuid === deletedUuid) {
782
- msg.parentUuid = parentUuid;
783
- }
784
- }
785
- messages.splice(targetIndex, 1);
786
974
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
787
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
788
- return { success: true, deletedMessage: deletedMsg };
975
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
976
+ return { success: true, deletedMessage: result.deleted };
789
977
  });
790
- var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(function* () {
978
+ var restoreMessage = (projectName, sessionId, message, index) => Effect5.gen(function* () {
791
979
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
792
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
793
- const lines = content.trim().split("\n").filter(Boolean);
794
- const messages = lines.map((line) => JSON.parse(line));
980
+ const messages = yield* readJsonlFile(filePath);
795
981
  const msgUuid = message.uuid ?? message.messageId;
796
982
  if (!msgUuid) {
797
983
  return { success: false, error: "Message has no uuid or messageId" };
@@ -806,41 +992,41 @@ var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(fun
806
992
  const insertIndex = Math.min(index, messages.length);
807
993
  messages.splice(insertIndex, 0, message);
808
994
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
809
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
995
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
810
996
  return { success: true };
811
997
  });
812
- var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
998
+ var deleteSession = (projectName, sessionId) => Effect5.gen(function* () {
813
999
  const sessionsDir = getSessionsDir();
814
1000
  const projectPath = path5.join(sessionsDir, projectName);
815
1001
  const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
816
1002
  const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
817
- const stat4 = yield* Effect4.tryPromise(() => fs5.stat(filePath));
1003
+ const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
818
1004
  if (stat4.size === 0) {
819
- yield* Effect4.tryPromise(() => fs5.unlink(filePath));
1005
+ yield* Effect5.tryPromise(() => fs6.unlink(filePath));
820
1006
  const agentBackupDir2 = path5.join(projectPath, ".bak");
821
- yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir2, { recursive: true }));
1007
+ yield* Effect5.tryPromise(() => fs6.mkdir(agentBackupDir2, { recursive: true }));
822
1008
  for (const agentId of linkedAgents) {
823
1009
  const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
824
1010
  const agentBackupPath = path5.join(agentBackupDir2, `${agentId}.jsonl`);
825
- yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
1011
+ yield* Effect5.tryPromise(() => fs6.rename(agentPath, agentBackupPath).catch(() => {
826
1012
  }));
827
1013
  }
828
1014
  yield* deleteLinkedTodos(sessionId, linkedAgents);
829
1015
  return { success: true, deletedAgents: linkedAgents.length };
830
1016
  }
831
1017
  const backupDir = path5.join(sessionsDir, ".bak");
832
- yield* Effect4.tryPromise(() => fs5.mkdir(backupDir, { recursive: true }));
1018
+ yield* Effect5.tryPromise(() => fs6.mkdir(backupDir, { recursive: true }));
833
1019
  const agentBackupDir = path5.join(projectPath, ".bak");
834
- yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir, { recursive: true }));
1020
+ yield* Effect5.tryPromise(() => fs6.mkdir(agentBackupDir, { recursive: true }));
835
1021
  for (const agentId of linkedAgents) {
836
1022
  const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
837
1023
  const agentBackupPath = path5.join(agentBackupDir, `${agentId}.jsonl`);
838
- yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
1024
+ yield* Effect5.tryPromise(() => fs6.rename(agentPath, agentBackupPath).catch(() => {
839
1025
  }));
840
1026
  }
841
1027
  const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
842
1028
  const backupPath = path5.join(backupDir, `${projectName}_${sessionId}.jsonl`);
843
- yield* Effect4.tryPromise(() => fs5.rename(filePath, backupPath));
1029
+ yield* Effect5.tryPromise(() => fs6.rename(filePath, backupPath));
844
1030
  return {
845
1031
  success: true,
846
1032
  backupPath,
@@ -848,15 +1034,15 @@ var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
848
1034
  deletedTodos: todosResult.deletedCount
849
1035
  };
850
1036
  });
851
- var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function* () {
1037
+ var renameSession = (projectName, sessionId, newTitle) => Effect5.gen(function* () {
852
1038
  const projectPath = path5.join(getSessionsDir(), projectName);
853
1039
  const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
854
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
1040
+ const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
855
1041
  const lines = content.trim().split("\n").filter(Boolean);
856
1042
  if (lines.length === 0) {
857
1043
  return { success: false, error: "Empty session" };
858
1044
  }
859
- const messages = lines.map((line) => JSON.parse(line));
1045
+ const messages = parseJsonlLines(lines, filePath);
860
1046
  const sessionUuids = /* @__PURE__ */ new Set();
861
1047
  for (const msg of messages) {
862
1048
  if (msg.uuid && typeof msg.uuid === "string") {
@@ -875,16 +1061,14 @@ var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function*
875
1061
  messages.unshift(customTitleRecord);
876
1062
  }
877
1063
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
878
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
879
- const projectFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
1064
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
1065
+ const projectFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
880
1066
  const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
881
1067
  const summariesTargetingThis = [];
882
1068
  for (const file of allJsonlFiles) {
883
1069
  const otherFilePath = path5.join(projectPath, file);
884
1070
  try {
885
- const otherContent = yield* Effect4.tryPromise(() => fs5.readFile(otherFilePath, "utf-8"));
886
- const otherLines = otherContent.trim().split("\n").filter(Boolean);
887
- const otherMessages = otherLines.map((l) => JSON.parse(l));
1071
+ const otherMessages = yield* readJsonlFile(otherFilePath);
888
1072
  for (let i = 0; i < otherMessages.length; i++) {
889
1073
  const msg = otherMessages[i];
890
1074
  if (msg.type === "summary" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
@@ -903,19 +1087,15 @@ var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function*
903
1087
  summariesTargetingThis.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
904
1088
  const firstSummary = summariesTargetingThis[0];
905
1089
  const summaryFilePath = path5.join(projectPath, firstSummary.file);
906
- const summaryContent = yield* Effect4.tryPromise(() => fs5.readFile(summaryFilePath, "utf-8"));
907
- const summaryLines = summaryContent.trim().split("\n").filter(Boolean);
908
- const summaryMessages = summaryLines.map((l) => JSON.parse(l));
1090
+ const summaryMessages = yield* readJsonlFile(summaryFilePath);
909
1091
  summaryMessages[firstSummary.idx] = {
910
1092
  ...summaryMessages[firstSummary.idx],
911
1093
  summary: newTitle
912
1094
  };
913
1095
  const newSummaryContent = summaryMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
914
- yield* Effect4.tryPromise(() => fs5.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
1096
+ yield* Effect5.tryPromise(() => fs6.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
915
1097
  } else {
916
- const currentContent = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
917
- const currentLines = currentContent.trim().split("\n").filter(Boolean);
918
- const currentMessages = currentLines.map((l) => JSON.parse(l));
1098
+ const currentMessages = yield* readJsonlFile(filePath);
919
1099
  const firstUserIdx = currentMessages.findIndex((m) => m.type === "user");
920
1100
  if (firstUserIdx >= 0) {
921
1101
  const firstMsg = currentMessages[firstUserIdx];
@@ -932,52 +1112,50 @@ var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function*
932
1112
 
933
1113
  ${cleanedText}`;
934
1114
  const updatedContent = currentMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
935
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, updatedContent, "utf-8"));
1115
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, updatedContent, "utf-8"));
936
1116
  }
937
1117
  }
938
1118
  }
939
1119
  }
940
1120
  return { success: true };
941
1121
  });
942
- var moveSession = (sourceProject, sessionId, targetProject) => Effect4.gen(function* () {
1122
+ var moveSession = (sourceProject, sessionId, targetProject) => Effect5.gen(function* () {
943
1123
  const sessionsDir = getSessionsDir();
944
1124
  const sourcePath = path5.join(sessionsDir, sourceProject);
945
1125
  const targetPath = path5.join(sessionsDir, targetProject);
946
1126
  const sourceFile = path5.join(sourcePath, `${sessionId}.jsonl`);
947
1127
  const targetFile = path5.join(targetPath, `${sessionId}.jsonl`);
948
- const sourceExists = yield* Effect4.tryPromise(
949
- () => fs5.access(sourceFile).then(() => true).catch(() => false)
1128
+ const sourceExists = yield* Effect5.tryPromise(
1129
+ () => fs6.access(sourceFile).then(() => true).catch(() => false)
950
1130
  );
951
1131
  if (!sourceExists) {
952
1132
  return { success: false, error: "Source session not found" };
953
1133
  }
954
- const targetExists = yield* Effect4.tryPromise(
955
- () => fs5.access(targetFile).then(() => true).catch(() => false)
1134
+ const targetExists = yield* Effect5.tryPromise(
1135
+ () => fs6.access(targetFile).then(() => true).catch(() => false)
956
1136
  );
957
1137
  if (targetExists) {
958
1138
  return { success: false, error: "Session already exists in target project" };
959
1139
  }
960
- yield* Effect4.tryPromise(() => fs5.mkdir(targetPath, { recursive: true }));
1140
+ yield* Effect5.tryPromise(() => fs6.mkdir(targetPath, { recursive: true }));
961
1141
  const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
962
- yield* Effect4.tryPromise(() => fs5.rename(sourceFile, targetFile));
1142
+ yield* Effect5.tryPromise(() => fs6.rename(sourceFile, targetFile));
963
1143
  for (const agentId of linkedAgents) {
964
1144
  const sourceAgentFile = path5.join(sourcePath, `${agentId}.jsonl`);
965
1145
  const targetAgentFile = path5.join(targetPath, `${agentId}.jsonl`);
966
- const agentExists = yield* Effect4.tryPromise(
967
- () => fs5.access(sourceAgentFile).then(() => true).catch(() => false)
1146
+ const agentExists = yield* Effect5.tryPromise(
1147
+ () => fs6.access(sourceAgentFile).then(() => true).catch(() => false)
968
1148
  );
969
1149
  if (agentExists) {
970
- yield* Effect4.tryPromise(() => fs5.rename(sourceAgentFile, targetAgentFile));
1150
+ yield* Effect5.tryPromise(() => fs6.rename(sourceAgentFile, targetAgentFile));
971
1151
  }
972
1152
  }
973
1153
  return { success: true };
974
1154
  });
975
- var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(function* () {
1155
+ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect5.gen(function* () {
976
1156
  const projectPath = path5.join(getSessionsDir(), projectName);
977
1157
  const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
978
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
979
- const lines = content.trim().split("\n").filter(Boolean);
980
- const allMessages = lines.map((line) => JSON.parse(line));
1158
+ const allMessages = yield* readJsonlFile(filePath);
981
1159
  const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
982
1160
  if (splitIndex === -1) {
983
1161
  return { success: false, error: "Message not found" };
@@ -1023,30 +1201,30 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(f
1023
1201
  updatedMovedMessages.unshift(clonedSummary);
1024
1202
  }
1025
1203
  const keptContent = keptMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1026
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, keptContent, "utf-8"));
1204
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, keptContent, "utf-8"));
1027
1205
  const newFilePath = path5.join(projectPath, `${newSessionId}.jsonl`);
1028
1206
  const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1029
- yield* Effect4.tryPromise(() => fs5.writeFile(newFilePath, newContent, "utf-8"));
1030
- const agentFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
1207
+ yield* Effect5.tryPromise(() => fs6.writeFile(newFilePath, newContent, "utf-8"));
1208
+ const agentFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
1031
1209
  const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
1032
1210
  for (const agentFile of agentJsonlFiles) {
1033
1211
  const agentPath = path5.join(projectPath, agentFile);
1034
- const agentContent = yield* Effect4.tryPromise(() => fs5.readFile(agentPath, "utf-8"));
1212
+ const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
1035
1213
  const agentLines = agentContent.trim().split("\n").filter(Boolean);
1036
1214
  if (agentLines.length === 0) continue;
1037
- const firstAgentMsg = JSON.parse(agentLines[0]);
1215
+ const agentMessages = parseJsonlLines(agentLines, agentPath);
1216
+ const firstAgentMsg = agentMessages[0];
1038
1217
  if (firstAgentMsg.sessionId === sessionId) {
1039
1218
  const agentId = agentFile.replace("agent-", "").replace(".jsonl", "");
1040
1219
  const isRelatedToMoved = movedMessages.some(
1041
1220
  (msg) => msg.agentId === agentId
1042
1221
  );
1043
1222
  if (isRelatedToMoved) {
1044
- const updatedAgentMessages = agentLines.map((line) => {
1045
- const msg = JSON.parse(line);
1046
- return JSON.stringify({ ...msg, sessionId: newSessionId });
1047
- });
1223
+ const updatedAgentMessages = agentMessages.map(
1224
+ (msg) => JSON.stringify({ ...msg, sessionId: newSessionId })
1225
+ );
1048
1226
  const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
1049
- yield* Effect4.tryPromise(() => fs5.writeFile(agentPath, updatedAgentContent, "utf-8"));
1227
+ yield* Effect5.tryPromise(() => fs6.writeFile(agentPath, updatedAgentContent, "utf-8"));
1050
1228
  }
1051
1229
  }
1052
1230
  }
@@ -1060,15 +1238,53 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(f
1060
1238
  });
1061
1239
 
1062
1240
  // src/session/tree.ts
1063
- import { Effect as Effect5 } from "effect";
1064
- import * as fs6 from "fs/promises";
1241
+ import { Effect as Effect6 } from "effect";
1242
+ import * as fs7 from "fs/promises";
1065
1243
  import * as path6 from "path";
1066
- var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect5.gen(function* () {
1244
+ var sortSessions = (sessions, sort) => {
1245
+ return sessions.sort((a, b) => {
1246
+ let comparison = 0;
1247
+ switch (sort.field) {
1248
+ case "summary": {
1249
+ comparison = a.sortTimestamp - b.sortTimestamp;
1250
+ break;
1251
+ }
1252
+ case "modified": {
1253
+ comparison = (a.fileMtime ?? 0) - (b.fileMtime ?? 0);
1254
+ break;
1255
+ }
1256
+ case "created": {
1257
+ const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
1258
+ const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
1259
+ comparison = createdA - createdB;
1260
+ break;
1261
+ }
1262
+ case "updated": {
1263
+ const updatedA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
1264
+ const updatedB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
1265
+ comparison = updatedA - updatedB;
1266
+ break;
1267
+ }
1268
+ case "messageCount": {
1269
+ comparison = a.messageCount - b.messageCount;
1270
+ break;
1271
+ }
1272
+ case "title": {
1273
+ const titleA = a.customTitle ?? a.currentSummary ?? a.title;
1274
+ const titleB = b.customTitle ?? b.currentSummary ?? b.title;
1275
+ comparison = titleA.localeCompare(titleB);
1276
+ break;
1277
+ }
1278
+ }
1279
+ return sort.order === "desc" ? -comparison : comparison;
1280
+ });
1281
+ };
1282
+ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect6.gen(function* () {
1067
1283
  const projectPath = path6.join(getSessionsDir(), projectName);
1068
1284
  const filePath = path6.join(projectPath, `${sessionId}.jsonl`);
1069
- const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
1285
+ const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
1070
1286
  const lines = content.trim().split("\n").filter(Boolean);
1071
- const messages = lines.map((line) => JSON.parse(line));
1287
+ const messages = parseJsonlLines(lines, filePath);
1072
1288
  let summaries;
1073
1289
  if (summariesByTargetSession) {
1074
1290
  summaries = [...summariesByTargetSession.get(sessionId) ?? []].sort((a, b) => {
@@ -1084,26 +1300,24 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1084
1300
  sessionUuids.add(msg.uuid);
1085
1301
  }
1086
1302
  }
1087
- const projectFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
1303
+ const projectFiles = yield* Effect6.tryPromise(() => fs7.readdir(projectPath));
1088
1304
  const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
1089
1305
  for (const file of allJsonlFiles) {
1090
1306
  try {
1091
1307
  const otherFilePath = path6.join(projectPath, file);
1092
- const otherContent = yield* Effect5.tryPromise(() => fs6.readFile(otherFilePath, "utf-8"));
1308
+ const otherContent = yield* Effect6.tryPromise(() => fs7.readFile(otherFilePath, "utf-8"));
1093
1309
  const otherLines = otherContent.trim().split("\n").filter(Boolean);
1094
- for (const line of otherLines) {
1095
- try {
1096
- const msg = JSON.parse(line);
1097
- if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
1098
- const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
1099
- summaries.push({
1100
- summary: msg.summary,
1101
- leafUuid: msg.leafUuid,
1102
- timestamp: targetMsg?.timestamp ?? msg.timestamp,
1103
- sourceFile: file
1104
- });
1105
- }
1106
- } catch {
1310
+ for (let i = 0; i < otherLines.length; i++) {
1311
+ const msg = tryParseJsonLine(otherLines[i], i + 1, otherFilePath);
1312
+ if (!msg) continue;
1313
+ if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
1314
+ const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
1315
+ summaries.push({
1316
+ summary: msg.summary,
1317
+ leafUuid: msg.leafUuid,
1318
+ timestamp: targetMsg?.timestamp ?? msg.timestamp,
1319
+ sourceFile: file
1320
+ });
1107
1321
  }
1108
1322
  }
1109
1323
  } catch {
@@ -1137,7 +1351,7 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1137
1351
  for (const agentId of linkedAgentIds) {
1138
1352
  const agentPath = path6.join(projectPath, `${agentId}.jsonl`);
1139
1353
  try {
1140
- const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
1354
+ const agentContent = yield* Effect6.tryPromise(() => fs7.readFile(agentPath, "utf-8"));
1141
1355
  const agentLines = agentContent.trim().split("\n").filter(Boolean);
1142
1356
  const agentMsgs = agentLines.map((l) => JSON.parse(l));
1143
1357
  const agentUserAssistant = agentMsgs.filter(
@@ -1164,6 +1378,8 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1164
1378
  }
1165
1379
  }
1166
1380
  const todos = yield* findLinkedTodos(sessionId, linkedAgentIds);
1381
+ const createdAt = firstMessage?.timestamp ?? void 0;
1382
+ const sortTimestamp = getSessionSortTimestamp({ summaries, createdAt });
1167
1383
  return {
1168
1384
  id: sessionId,
1169
1385
  projectName,
@@ -1171,9 +1387,10 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1171
1387
  customTitle,
1172
1388
  currentSummary: summaries[0]?.summary,
1173
1389
  messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
1174
- createdAt: firstMessage?.timestamp ?? void 0,
1390
+ createdAt,
1175
1391
  updatedAt: lastMessage?.timestamp ?? void 0,
1176
1392
  fileMtime,
1393
+ sortTimestamp,
1177
1394
  summaries,
1178
1395
  agents,
1179
1396
  todos,
@@ -1182,22 +1399,22 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1182
1399
  });
1183
1400
  var loadSessionTreeData = (projectName, sessionId) => loadSessionTreeDataInternal(projectName, sessionId, void 0);
1184
1401
  var DEFAULT_SORT = { field: "summary", order: "desc" };
1185
- var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* () {
1402
+ var loadProjectTreeData = (projectName, sortOptions) => Effect6.gen(function* () {
1186
1403
  const project = (yield* listProjects).find((p) => p.name === projectName);
1187
1404
  if (!project) {
1188
1405
  return null;
1189
1406
  }
1190
1407
  const sort = sortOptions ?? DEFAULT_SORT;
1191
1408
  const projectPath = path6.join(getSessionsDir(), projectName);
1192
- const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
1409
+ const files = yield* Effect6.tryPromise(() => fs7.readdir(projectPath));
1193
1410
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
1194
1411
  const fileMtimes = /* @__PURE__ */ new Map();
1195
- yield* Effect5.all(
1412
+ yield* Effect6.all(
1196
1413
  sessionFiles.map(
1197
- (file) => Effect5.gen(function* () {
1414
+ (file) => Effect6.gen(function* () {
1198
1415
  const filePath = path6.join(projectPath, file);
1199
1416
  try {
1200
- const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
1417
+ const stat4 = yield* Effect6.tryPromise(() => fs7.stat(filePath));
1201
1418
  fileMtimes.set(file.replace(".jsonl", ""), stat4.mtimeMs);
1202
1419
  } catch {
1203
1420
  }
@@ -1208,38 +1425,36 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1208
1425
  const globalUuidMap = /* @__PURE__ */ new Map();
1209
1426
  const allSummaries = [];
1210
1427
  const allJsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1211
- yield* Effect5.all(
1428
+ yield* Effect6.all(
1212
1429
  allJsonlFiles.map(
1213
- (file) => Effect5.gen(function* () {
1430
+ (file) => Effect6.gen(function* () {
1214
1431
  const filePath = path6.join(projectPath, file);
1215
1432
  const fileSessionId = file.replace(".jsonl", "");
1216
1433
  try {
1217
- const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
1434
+ const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
1218
1435
  const lines = content.trim().split("\n").filter(Boolean);
1219
- for (const line of lines) {
1220
- try {
1221
- const msg = JSON.parse(line);
1222
- if (msg.uuid && typeof msg.uuid === "string") {
1223
- globalUuidMap.set(msg.uuid, {
1224
- sessionId: fileSessionId,
1225
- timestamp: msg.timestamp
1226
- });
1227
- }
1228
- if (msg.messageId && typeof msg.messageId === "string") {
1229
- globalUuidMap.set(msg.messageId, {
1230
- sessionId: fileSessionId,
1231
- timestamp: msg.snapshot?.timestamp
1232
- });
1233
- }
1234
- if (msg.type === "summary" && typeof msg.summary === "string") {
1235
- allSummaries.push({
1236
- summary: msg.summary,
1237
- leafUuid: msg.leafUuid,
1238
- timestamp: msg.timestamp,
1239
- sourceFile: file
1240
- });
1241
- }
1242
- } catch {
1436
+ for (let i = 0; i < lines.length; i++) {
1437
+ const msg = tryParseJsonLine(lines[i], i + 1, filePath);
1438
+ if (!msg) continue;
1439
+ if (msg.uuid && typeof msg.uuid === "string") {
1440
+ globalUuidMap.set(msg.uuid, {
1441
+ sessionId: fileSessionId,
1442
+ timestamp: msg.timestamp
1443
+ });
1444
+ }
1445
+ if (msg.messageId && typeof msg.messageId === "string") {
1446
+ globalUuidMap.set(msg.messageId, {
1447
+ sessionId: fileSessionId,
1448
+ timestamp: msg.snapshot?.timestamp
1449
+ });
1450
+ }
1451
+ if (msg.type === "summary" && typeof msg.summary === "string") {
1452
+ allSummaries.push({
1453
+ summary: msg.summary,
1454
+ leafUuid: msg.leafUuid,
1455
+ timestamp: msg.timestamp,
1456
+ sourceFile: file
1457
+ });
1243
1458
  }
1244
1459
  }
1245
1460
  } catch {
@@ -1267,7 +1482,7 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1267
1482
  }
1268
1483
  }
1269
1484
  }
1270
- const sessions = yield* Effect5.all(
1485
+ const sessions = yield* Effect6.all(
1271
1486
  sessionFiles.map((file) => {
1272
1487
  const sessionId = file.replace(".jsonl", "");
1273
1488
  const mtime = fileMtimes.get(sessionId);
@@ -1275,46 +1490,7 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1275
1490
  }),
1276
1491
  { concurrency: 10 }
1277
1492
  );
1278
- const sortedSessions = sessions.sort((a, b) => {
1279
- let comparison = 0;
1280
- switch (sort.field) {
1281
- case "summary": {
1282
- const timeA = getSessionSortTimestamp(a);
1283
- const timeB = getSessionSortTimestamp(b);
1284
- const dateA = timeA ? new Date(timeA).getTime() : a.fileMtime ?? 0;
1285
- const dateB = timeB ? new Date(timeB).getTime() : b.fileMtime ?? 0;
1286
- comparison = dateA - dateB;
1287
- break;
1288
- }
1289
- case "modified": {
1290
- comparison = (a.fileMtime ?? 0) - (b.fileMtime ?? 0);
1291
- break;
1292
- }
1293
- case "created": {
1294
- const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
1295
- const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
1296
- comparison = createdA - createdB;
1297
- break;
1298
- }
1299
- case "updated": {
1300
- const updatedA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
1301
- const updatedB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
1302
- comparison = updatedA - updatedB;
1303
- break;
1304
- }
1305
- case "messageCount": {
1306
- comparison = a.messageCount - b.messageCount;
1307
- break;
1308
- }
1309
- case "title": {
1310
- const titleA = a.customTitle ?? a.currentSummary ?? a.title;
1311
- const titleB = b.customTitle ?? b.currentSummary ?? b.title;
1312
- comparison = titleA.localeCompare(titleB);
1313
- break;
1314
- }
1315
- }
1316
- return sort.order === "desc" ? -comparison : comparison;
1317
- });
1493
+ const sortedSessions = sortSessions(sessions, sort);
1318
1494
  const filteredSessions = sortedSessions.filter((s) => {
1319
1495
  if (isErrorSessionTitle(s.title)) return false;
1320
1496
  if (isErrorSessionTitle(s.customTitle)) return false;
@@ -1331,10 +1507,10 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1331
1507
  });
1332
1508
 
1333
1509
  // src/session/analysis.ts
1334
- import { Effect as Effect6 } from "effect";
1335
- import * as fs7 from "fs/promises";
1510
+ import { Effect as Effect7 } from "effect";
1511
+ import * as fs8 from "fs/promises";
1336
1512
  import * as path7 from "path";
1337
- var analyzeSession = (projectName, sessionId) => Effect6.gen(function* () {
1513
+ var analyzeSession = (projectName, sessionId) => Effect7.gen(function* () {
1338
1514
  const messages = yield* readSession(projectName, sessionId);
1339
1515
  let userMessages = 0;
1340
1516
  let assistantMessages = 0;
@@ -1465,13 +1641,13 @@ var analyzeSession = (projectName, sessionId) => Effect6.gen(function* () {
1465
1641
  milestones
1466
1642
  };
1467
1643
  });
1468
- var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
1644
+ var compressSession = (projectName, sessionId, options = {}) => Effect7.gen(function* () {
1469
1645
  const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
1470
1646
  const filePath = path7.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
1471
- const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
1647
+ const content = yield* Effect7.tryPromise(() => fs8.readFile(filePath, "utf-8"));
1472
1648
  const originalSize = Buffer.byteLength(content, "utf-8");
1473
1649
  const lines = content.trim().split("\n").filter(Boolean);
1474
- const messages = lines.map((line) => JSON.parse(line));
1650
+ const messages = parseJsonlLines(lines, filePath);
1475
1651
  let removedSnapshots = 0;
1476
1652
  let truncatedOutputs = 0;
1477
1653
  const snapshotIndices = [];
@@ -1511,7 +1687,7 @@ var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(func
1511
1687
  }
1512
1688
  const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1513
1689
  const compressedSize = Buffer.byteLength(newContent, "utf-8");
1514
- yield* Effect6.tryPromise(() => fs7.writeFile(filePath, newContent, "utf-8"));
1690
+ yield* Effect7.tryPromise(() => fs8.writeFile(filePath, newContent, "utf-8"));
1515
1691
  return {
1516
1692
  success: true,
1517
1693
  originalSize,
@@ -1520,12 +1696,12 @@ var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(func
1520
1696
  truncatedOutputs
1521
1697
  };
1522
1698
  });
1523
- var extractProjectKnowledge = (projectName, sessionIds) => Effect6.gen(function* () {
1699
+ var extractProjectKnowledge = (projectName, sessionIds) => Effect7.gen(function* () {
1524
1700
  const sessionsDir = getSessionsDir();
1525
1701
  const projectDir = path7.join(sessionsDir, projectName);
1526
1702
  let targetSessionIds = sessionIds;
1527
1703
  if (!targetSessionIds) {
1528
- const files = yield* Effect6.tryPromise(() => fs7.readdir(projectDir));
1704
+ const files = yield* Effect7.tryPromise(() => fs8.readdir(projectDir));
1529
1705
  targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
1530
1706
  }
1531
1707
  const fileModifyCount = /* @__PURE__ */ new Map();
@@ -1599,7 +1775,7 @@ function truncateText(text, maxLen) {
1599
1775
  }
1600
1776
  return cleaned;
1601
1777
  }
1602
- var summarizeSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
1778
+ var summarizeSession = (projectName, sessionId, options = {}) => Effect7.gen(function* () {
1603
1779
  const { limit = 50, maxLength = 100 } = options;
1604
1780
  const messages = yield* readSession(projectName, sessionId);
1605
1781
  const lines = [];
@@ -1651,15 +1827,15 @@ var summarizeSession = (projectName, sessionId, options = {}) => Effect6.gen(fun
1651
1827
  });
1652
1828
 
1653
1829
  // src/session/cleanup.ts
1654
- import { Effect as Effect7 } from "effect";
1655
- import * as fs8 from "fs/promises";
1830
+ import { Effect as Effect8 } from "effect";
1831
+ import * as fs9 from "fs/promises";
1656
1832
  import * as path8 from "path";
1657
- var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* () {
1833
+ var cleanInvalidMessages = (projectName, sessionId) => Effect8.gen(function* () {
1658
1834
  const filePath = path8.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
1659
- const content = yield* Effect7.tryPromise(() => fs8.readFile(filePath, "utf-8"));
1835
+ const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
1660
1836
  const lines = content.trim().split("\n").filter(Boolean);
1661
1837
  if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
1662
- const messages = lines.map((line) => JSON.parse(line));
1838
+ const messages = parseJsonlLines(lines, filePath);
1663
1839
  const invalidIndices = [];
1664
1840
  messages.forEach((msg, idx) => {
1665
1841
  if (isInvalidApiKeyMessage(msg)) {
@@ -1688,7 +1864,7 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* ()
1688
1864
  lastValidUuid = msg.uuid;
1689
1865
  }
1690
1866
  const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
1691
- yield* Effect7.tryPromise(() => fs8.writeFile(filePath, newContent, "utf-8"));
1867
+ yield* Effect8.tryPromise(() => fs9.writeFile(filePath, newContent, "utf-8"));
1692
1868
  const remainingUserAssistant = filtered.filter(
1693
1869
  (m) => m.type === "user" || m.type === "assistant"
1694
1870
  ).length;
@@ -1696,14 +1872,14 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* ()
1696
1872
  const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
1697
1873
  return { removedCount: invalidIndices.length, remainingCount };
1698
1874
  });
1699
- var previewCleanup = (projectName) => Effect7.gen(function* () {
1875
+ var previewCleanup = (projectName) => Effect8.gen(function* () {
1700
1876
  const projects = yield* listProjects;
1701
1877
  const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
1702
1878
  const orphanTodos = yield* findOrphanTodos();
1703
1879
  const orphanTodoCount = orphanTodos.length;
1704
- const results = yield* Effect7.all(
1880
+ const results = yield* Effect8.all(
1705
1881
  targetProjects.map(
1706
- (project) => Effect7.gen(function* () {
1882
+ (project) => Effect8.gen(function* () {
1707
1883
  const sessions = yield* listSessions(project.name);
1708
1884
  const emptySessions = sessions.filter((s) => s.messageCount === 0);
1709
1885
  const invalidSessions = sessions.filter(
@@ -1736,7 +1912,7 @@ var previewCleanup = (projectName) => Effect7.gen(function* () {
1736
1912
  }
1737
1913
  return results;
1738
1914
  });
1739
- var clearSessions = (options) => Effect7.gen(function* () {
1915
+ var clearSessions = (options) => Effect8.gen(function* () {
1740
1916
  const {
1741
1917
  projectName,
1742
1918
  clearEmpty = true,
@@ -1755,7 +1931,7 @@ var clearSessions = (options) => Effect7.gen(function* () {
1755
1931
  if (clearInvalid) {
1756
1932
  for (const project of targetProjects) {
1757
1933
  const projectPath = path8.join(getSessionsDir(), project.name);
1758
- const files = yield* Effect7.tryPromise(() => fs8.readdir(projectPath));
1934
+ const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
1759
1935
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
1760
1936
  for (const file of sessionFiles) {
1761
1937
  const sessionId = file.replace(".jsonl", "");
@@ -1811,72 +1987,96 @@ var clearSessions = (options) => Effect7.gen(function* () {
1811
1987
  });
1812
1988
 
1813
1989
  // src/session/search.ts
1814
- import { Effect as Effect8 } from "effect";
1815
- import * as fs9 from "fs/promises";
1990
+ import { Effect as Effect9, pipe as pipe2 } from "effect";
1991
+ import * as fs10 from "fs/promises";
1816
1992
  import * as path9 from "path";
1817
- var searchSessions = (query, options = {}) => Effect8.gen(function* () {
1993
+ var extractSnippet = (text, matchIndex, queryLength) => {
1994
+ const start = Math.max(0, matchIndex - 50);
1995
+ const end = Math.min(text.length, matchIndex + queryLength + 50);
1996
+ return (start > 0 ? "..." : "") + text.slice(start, end).trim() + (end < text.length ? "..." : "");
1997
+ };
1998
+ var findContentMatch = (lines, queryLower, filePath) => {
1999
+ for (let i = 0; i < lines.length; i++) {
2000
+ const msg = tryParseJsonLine(lines[i], i + 1, filePath);
2001
+ if (!msg) continue;
2002
+ if (msg.type !== "user" && msg.type !== "assistant") continue;
2003
+ const text = extractTextContent(msg.message);
2004
+ const textLower = text.toLowerCase();
2005
+ const matchIndex = textLower.indexOf(queryLower);
2006
+ if (matchIndex !== -1) {
2007
+ return {
2008
+ msg,
2009
+ snippet: extractSnippet(text, matchIndex, queryLower.length)
2010
+ };
2011
+ }
2012
+ }
2013
+ return null;
2014
+ };
2015
+ var searchSessionContent = (projectName, sessionId, filePath, queryLower) => pipe2(
2016
+ Effect9.tryPromise(() => fs10.readFile(filePath, "utf-8")),
2017
+ Effect9.map((content) => {
2018
+ const lines = content.trim().split("\n").filter(Boolean);
2019
+ const match = findContentMatch(lines, queryLower, filePath);
2020
+ if (!match) return null;
2021
+ return {
2022
+ sessionId,
2023
+ projectName,
2024
+ title: extractTitle(extractTextContent(match.msg.message)) || `Session ${sessionId.slice(0, 8)}`,
2025
+ matchType: "content",
2026
+ snippet: match.snippet,
2027
+ messageUuid: match.msg.uuid,
2028
+ timestamp: match.msg.timestamp
2029
+ };
2030
+ }),
2031
+ Effect9.catchAll(() => Effect9.succeed(null))
2032
+ );
2033
+ var searchProjectContent = (project, queryLower, alreadyFoundIds) => Effect9.gen(function* () {
2034
+ const projectPath = path9.join(getSessionsDir(), project.name);
2035
+ const files = yield* Effect9.tryPromise(() => fs10.readdir(projectPath));
2036
+ const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
2037
+ const searchEffects = sessionFiles.map((file) => ({
2038
+ sessionId: file.replace(".jsonl", ""),
2039
+ filePath: path9.join(projectPath, file)
2040
+ })).filter(({ sessionId }) => !alreadyFoundIds.has(`${project.name}:${sessionId}`)).map(
2041
+ ({ sessionId, filePath }) => searchSessionContent(project.name, sessionId, filePath, queryLower)
2042
+ );
2043
+ const results = yield* Effect9.all(searchEffects, { concurrency: 10 });
2044
+ return results.filter((r) => r !== null);
2045
+ });
2046
+ var searchSessions = (query, options = {}) => Effect9.gen(function* () {
1818
2047
  const { projectName, searchContent = false } = options;
1819
- const results = [];
1820
2048
  const queryLower = query.toLowerCase();
1821
2049
  const projects = yield* listProjects;
1822
2050
  const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
1823
- for (const project of targetProjects) {
1824
- const sessions = yield* listSessions(project.name);
1825
- for (const session of sessions) {
1826
- const titleLower = (session.title ?? "").toLowerCase();
1827
- if (titleLower.includes(queryLower)) {
1828
- results.push({
1829
- sessionId: session.id,
1830
- projectName: project.name,
1831
- title: session.title ?? "Untitled",
1832
- matchType: "title",
1833
- timestamp: session.updatedAt
1834
- });
1835
- }
1836
- }
1837
- }
2051
+ const titleSearchEffects = targetProjects.map(
2052
+ (project) => pipe2(
2053
+ listSessions(project.name),
2054
+ Effect9.map(
2055
+ (sessions) => sessions.filter((session) => (session.title ?? "").toLowerCase().includes(queryLower)).map(
2056
+ (session) => ({
2057
+ sessionId: session.id,
2058
+ projectName: project.name,
2059
+ title: session.title ?? "Untitled",
2060
+ matchType: "title",
2061
+ timestamp: session.updatedAt
2062
+ })
2063
+ )
2064
+ )
2065
+ )
2066
+ );
2067
+ const titleResultsNested = yield* Effect9.all(titleSearchEffects, { concurrency: 10 });
2068
+ const titleResults = titleResultsNested.flat();
2069
+ let contentResults = [];
1838
2070
  if (searchContent) {
1839
- for (const project of targetProjects) {
1840
- const projectPath = path9.join(getSessionsDir(), project.name);
1841
- const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
1842
- const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
1843
- for (const file of sessionFiles) {
1844
- const sessionId = file.replace(".jsonl", "");
1845
- if (results.some((r) => r.sessionId === sessionId && r.projectName === project.name)) {
1846
- continue;
1847
- }
1848
- const filePath = path9.join(projectPath, file);
1849
- const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
1850
- const lines = content.trim().split("\n").filter(Boolean);
1851
- for (const line of lines) {
1852
- try {
1853
- const msg = JSON.parse(line);
1854
- if (msg.type !== "user" && msg.type !== "assistant") continue;
1855
- const text = extractTextContent(msg.message);
1856
- const textLower = text.toLowerCase();
1857
- if (textLower.includes(queryLower)) {
1858
- const matchIndex = textLower.indexOf(queryLower);
1859
- const start = Math.max(0, matchIndex - 50);
1860
- const end = Math.min(text.length, matchIndex + query.length + 50);
1861
- const snippet = (start > 0 ? "..." : "") + text.slice(start, end).trim() + (end < text.length ? "..." : "");
1862
- results.push({
1863
- sessionId,
1864
- projectName: project.name,
1865
- title: extractTitle(extractTextContent(msg.message)) || `Session ${sessionId.slice(0, 8)}`,
1866
- matchType: "content",
1867
- snippet,
1868
- messageUuid: msg.uuid,
1869
- timestamp: msg.timestamp
1870
- });
1871
- break;
1872
- }
1873
- } catch {
1874
- }
1875
- }
1876
- }
1877
- }
2071
+ const alreadyFoundIds = new Set(titleResults.map((r) => `${r.projectName}:${r.sessionId}`));
2072
+ const contentSearchEffects = targetProjects.map(
2073
+ (project) => searchProjectContent(project, queryLower, alreadyFoundIds)
2074
+ );
2075
+ const contentResultsNested = yield* Effect9.all(contentSearchEffects, { concurrency: 5 });
2076
+ contentResults = contentResultsNested.flat();
1878
2077
  }
1879
- return results.sort((a, b) => {
2078
+ const allResults = [...titleResults, ...contentResults];
2079
+ return allResults.sort((a, b) => {
1880
2080
  const dateA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
1881
2081
  const dateB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
1882
2082
  return dateB - dateA;
@@ -1884,8 +2084,8 @@ var searchSessions = (query, options = {}) => Effect8.gen(function* () {
1884
2084
  });
1885
2085
 
1886
2086
  // src/session/files.ts
1887
- import { Effect as Effect9 } from "effect";
1888
- var getSessionFiles = (projectName, sessionId) => Effect9.gen(function* () {
2087
+ import { Effect as Effect10 } from "effect";
2088
+ var getSessionFiles = (projectName, sessionId) => Effect10.gen(function* () {
1889
2089
  const messages = yield* readSession(projectName, sessionId);
1890
2090
  const fileChanges = [];
1891
2091
  const seenFiles = /* @__PURE__ */ new Set();
@@ -1939,13 +2139,13 @@ var getSessionFiles = (projectName, sessionId) => Effect9.gen(function* () {
1939
2139
  });
1940
2140
 
1941
2141
  // src/session/index-file.ts
1942
- import { Effect as Effect10 } from "effect";
1943
- import * as fs10 from "fs/promises";
2142
+ import { Effect as Effect11 } from "effect";
2143
+ import * as fs11 from "fs/promises";
1944
2144
  import * as path10 from "path";
1945
- var loadSessionsIndex = (projectName) => Effect10.gen(function* () {
2145
+ var loadSessionsIndex = (projectName) => Effect11.gen(function* () {
1946
2146
  const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
1947
2147
  try {
1948
- const content = yield* Effect10.tryPromise(() => fs10.readFile(indexPath, "utf-8"));
2148
+ const content = yield* Effect11.tryPromise(() => fs11.readFile(indexPath, "utf-8"));
1949
2149
  const index = JSON.parse(content);
1950
2150
  return index;
1951
2151
  } catch {
@@ -1972,10 +2172,10 @@ var sortIndexEntriesByModified = (entries) => {
1972
2172
  return modB - modA;
1973
2173
  });
1974
2174
  };
1975
- var hasSessionsIndex = (projectName) => Effect10.gen(function* () {
2175
+ var hasSessionsIndex = (projectName) => Effect11.gen(function* () {
1976
2176
  const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
1977
2177
  try {
1978
- yield* Effect10.tryPromise(() => fs10.access(indexPath));
2178
+ yield* Effect11.tryPromise(() => fs11.access(indexPath));
1979
2179
  return true;
1980
2180
  } catch {
1981
2181
  return false;
@@ -1988,6 +2188,7 @@ export {
1988
2188
  createLogger,
1989
2189
  deleteLinkedTodos,
1990
2190
  deleteMessage,
2191
+ deleteMessageWithChainRepair,
1991
2192
  deleteOrphanAgents,
1992
2193
  deleteOrphanTodos,
1993
2194
  deleteSession,
@@ -2021,8 +2222,11 @@ export {
2021
2222
  loadSessionsIndex,
2022
2223
  maskHomePath,
2023
2224
  moveSession,
2225
+ parseCommandMessage,
2226
+ parseJsonlLines,
2024
2227
  pathToFolderName,
2025
2228
  previewCleanup,
2229
+ readJsonlFile,
2026
2230
  readSession,
2027
2231
  renameSession,
2028
2232
  restoreMessage,
@@ -2033,6 +2237,9 @@ export {
2033
2237
  sortProjects,
2034
2238
  splitSession,
2035
2239
  summarizeSession,
2036
- updateSessionSummary
2240
+ tryParseJsonLine,
2241
+ updateSessionSummary,
2242
+ validateChain,
2243
+ validateToolUseResult
2037
2244
  };
2038
2245
  //# sourceMappingURL=index.js.map