@claude-sessions/core 0.4.1 → 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,111 +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 parseCommandMessage = (content) => {
182
- const name = content?.match(/<command-name>([^<]+)<\/command-name>/)?.[1] ?? "";
183
- const message = content?.match(/<command-message>([^<]+)<\/command-message>/)?.[1] ?? "";
184
- return { name, message };
185
- };
186
- var extractTitle = (text) => {
187
- if (!text) return "Untitled";
188
- const { name } = parseCommandMessage(text);
189
- if (name) return name;
190
- let cleaned = text.replace(/<ide_[^>]*>[\s\S]*?<\/ide_[^>]*>/g, "").trim();
191
- if (!cleaned) return "Untitled";
192
- if (cleaned.includes("\n\n")) {
193
- cleaned = cleaned.split("\n\n")[0];
194
- }
195
- if (cleaned.length > 100) {
196
- return cleaned.slice(0, 100) + "...";
197
- }
198
- return cleaned || "Untitled";
199
- };
200
- var isInvalidApiKeyMessage = (msg) => {
201
- const text = extractTextContent(msg.message);
202
- return text.includes("Invalid API key");
203
- };
204
- var ERROR_SESSION_PATTERNS = [
205
- "API Error",
206
- "authentication_error",
207
- "Invalid API key",
208
- "OAuth token has expired",
209
- "Please run /login"
210
- ];
211
- var isErrorSessionTitle = (title) => {
212
- if (!title) return false;
213
- return ERROR_SESSION_PATTERNS.some((pattern) => title.includes(pattern));
214
- };
215
- var isContinuationSummary = (msg) => {
216
- if (msg.isCompactSummary === true) return true;
217
- if (msg.type !== "user") return false;
218
- const text = extractTextContent(msg.message);
219
- return text.startsWith("This session is being continued from");
220
- };
221
- var getDisplayTitle = (customTitle, currentSummary, title, maxLength = 60, fallback = "Untitled") => {
222
- if (customTitle) return customTitle;
223
- if (currentSummary) {
224
- return currentSummary.length > maxLength ? currentSummary.slice(0, maxLength - 3) + "..." : currentSummary;
225
- }
226
- if (title && title !== "Untitled") {
227
- if (title.includes("<command-name>")) {
228
- const { name } = parseCommandMessage(title);
229
- if (name) return name;
230
- }
231
- return title;
232
- }
233
- return fallback;
234
- };
235
- var replaceMessageContent = (msg, text) => ({
236
- ...msg,
237
- message: {
238
- ...msg.message,
239
- content: [{ type: "text", text }]
240
- },
241
- toolUseResult: void 0
242
- });
243
- var cleanupSplitFirstMessage = (msg) => {
244
- const toolUseResult = msg.toolUseResult;
245
- if (!toolUseResult) return msg;
246
- if (typeof toolUseResult === "object" && "answers" in toolUseResult) {
247
- const answers = toolUseResult.answers;
248
- const qaText = Object.entries(answers).map(([q, a]) => `Q: ${q}
249
- A: ${a}`).join("\n\n");
250
- return replaceMessageContent(msg, qaText);
251
- }
252
- if (typeof toolUseResult === "string") {
253
- const rejectionMarker = "The user provided the following reason for the rejection:";
254
- const rejectionIndex = toolUseResult.indexOf(rejectionMarker);
255
- if (rejectionIndex === -1) return msg;
256
- const text = toolUseResult.slice(rejectionIndex + rejectionMarker.length).trim();
257
- if (!text) return msg;
258
- return replaceMessageContent(msg, text);
259
- }
260
- return msg;
261
- };
262
- var maskHomePath = (text, homeDir) => {
263
- if (!homeDir) return text;
264
- const escapedHome = homeDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
265
- const regex = new RegExp(`${escapedHome}(?=[/\\\\]|$)`, "g");
266
- return text.replace(regex, "~");
267
- };
297
+ // src/projects.ts
268
298
  var sortProjects = (projects, options = {}) => {
269
299
  const { currentProjectName, homeDir, filterEmpty = true } = options;
270
300
  const filtered = filterEmpty ? projects.filter((p) => p.sessionCount > 0) : projects;
@@ -283,23 +313,19 @@ var sortProjects = (projects, options = {}) => {
283
313
  return a.displayName.localeCompare(b.displayName);
284
314
  });
285
315
  };
286
- var getSessionSortTimestamp = (session) => {
287
- const timestampStr = session.summaries?.[0]?.timestamp ?? session.createdAt;
288
- return timestampStr ? new Date(timestampStr).getTime() : 0;
289
- };
290
316
 
291
317
  // src/agents.ts
292
- import { Effect } from "effect";
293
- import * as fs2 from "fs/promises";
318
+ import { Effect as Effect2 } from "effect";
319
+ import * as fs3 from "fs/promises";
294
320
  import * as path2 from "path";
295
- var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
321
+ var findLinkedAgents = (projectName, sessionId) => Effect2.gen(function* () {
296
322
  const projectPath = path2.join(getSessionsDir(), projectName);
297
- const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
323
+ const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
298
324
  const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
299
325
  const linkedAgents = [];
300
326
  for (const agentFile of agentFiles) {
301
327
  const filePath = path2.join(projectPath, agentFile);
302
- const content = yield* Effect.tryPromise(() => fs2.readFile(filePath, "utf-8"));
328
+ const content = yield* Effect2.tryPromise(() => fs3.readFile(filePath, "utf-8"));
303
329
  const firstLine = content.split("\n")[0];
304
330
  if (firstLine) {
305
331
  try {
@@ -313,16 +339,16 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
313
339
  }
314
340
  return linkedAgents;
315
341
  });
316
- var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
342
+ var findOrphanAgentsWithPaths = (projectName) => Effect2.gen(function* () {
317
343
  const projectPath = path2.join(getSessionsDir(), projectName);
318
- const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
344
+ const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
319
345
  const sessionIds = new Set(
320
346
  files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
321
347
  );
322
348
  const orphanAgents = [];
323
349
  const checkAgentFile = async (filePath) => {
324
350
  try {
325
- const content = await fs2.readFile(filePath, "utf-8");
351
+ const content = await fs3.readFile(filePath, "utf-8");
326
352
  const lines = content.split("\n").filter((l) => l.trim());
327
353
  const firstLine = lines[0];
328
354
  if (!firstLine) return null;
@@ -342,27 +368,27 @@ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
342
368
  const rootAgentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
343
369
  for (const agentFile of rootAgentFiles) {
344
370
  const filePath = path2.join(projectPath, agentFile);
345
- const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
371
+ const orphan = yield* Effect2.tryPromise(() => checkAgentFile(filePath));
346
372
  if (orphan) {
347
373
  orphanAgents.push({ ...orphan, filePath });
348
374
  }
349
375
  }
350
376
  for (const entry of files) {
351
377
  const entryPath = path2.join(projectPath, entry);
352
- const stat4 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
378
+ const stat4 = yield* Effect2.tryPromise(() => fs3.stat(entryPath).catch(() => null));
353
379
  if (stat4?.isDirectory() && !entry.startsWith(".")) {
354
380
  const subagentsPath = path2.join(entryPath, "subagents");
355
- const subagentsExists = yield* Effect.tryPromise(
356
- () => fs2.stat(subagentsPath).then(() => true).catch(() => false)
381
+ const subagentsExists = yield* Effect2.tryPromise(
382
+ () => fs3.stat(subagentsPath).then(() => true).catch(() => false)
357
383
  );
358
384
  if (subagentsExists) {
359
- const subagentFiles = yield* Effect.tryPromise(
360
- () => fs2.readdir(subagentsPath).catch(() => [])
385
+ const subagentFiles = yield* Effect2.tryPromise(
386
+ () => fs3.readdir(subagentsPath).catch(() => [])
361
387
  );
362
388
  for (const subagentFile of subagentFiles) {
363
389
  if (subagentFile.startsWith("agent-") && subagentFile.endsWith(".jsonl")) {
364
390
  const filePath = path2.join(subagentsPath, subagentFile);
365
- const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
391
+ const orphan = yield* Effect2.tryPromise(() => checkAgentFile(filePath));
366
392
  if (orphan) {
367
393
  orphanAgents.push({ ...orphan, filePath });
368
394
  }
@@ -373,11 +399,11 @@ var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
373
399
  }
374
400
  return orphanAgents;
375
401
  });
376
- var findOrphanAgents = (projectName) => Effect.gen(function* () {
402
+ var findOrphanAgents = (projectName) => Effect2.gen(function* () {
377
403
  const orphans = yield* findOrphanAgentsWithPaths(projectName);
378
404
  return orphans.map(({ agentId, sessionId }) => ({ agentId, sessionId }));
379
405
  });
380
- var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
406
+ var deleteOrphanAgents = (projectName) => Effect2.gen(function* () {
381
407
  const projectPath = path2.join(getSessionsDir(), projectName);
382
408
  const orphans = yield* findOrphanAgentsWithPaths(projectName);
383
409
  const deletedAgents = [];
@@ -391,35 +417,35 @@ var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
391
417
  foldersToCheck.add(parentDir);
392
418
  }
393
419
  if (orphan.lineCount <= 2) {
394
- yield* Effect.tryPromise(() => fs2.unlink(orphan.filePath));
420
+ yield* Effect2.tryPromise(() => fs3.unlink(orphan.filePath));
395
421
  deletedAgents.push(orphan.agentId);
396
422
  } else {
397
423
  if (!backupDirCreated) {
398
424
  const backupDir2 = path2.join(projectPath, ".bak");
399
- yield* Effect.tryPromise(() => fs2.mkdir(backupDir2, { recursive: true }));
425
+ yield* Effect2.tryPromise(() => fs3.mkdir(backupDir2, { recursive: true }));
400
426
  backupDirCreated = true;
401
427
  }
402
428
  const backupDir = path2.join(projectPath, ".bak");
403
429
  const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
404
- yield* Effect.tryPromise(() => fs2.rename(orphan.filePath, agentBackupPath));
430
+ yield* Effect2.tryPromise(() => fs3.rename(orphan.filePath, agentBackupPath));
405
431
  backedUpAgents.push(orphan.agentId);
406
432
  }
407
433
  }
408
434
  for (const subagentsDir of foldersToCheck) {
409
- const isEmpty = yield* Effect.tryPromise(async () => {
410
- const files = await fs2.readdir(subagentsDir);
435
+ const isEmpty = yield* Effect2.tryPromise(async () => {
436
+ const files = await fs3.readdir(subagentsDir);
411
437
  return files.length === 0;
412
438
  });
413
439
  if (isEmpty) {
414
- yield* Effect.tryPromise(() => fs2.rmdir(subagentsDir));
440
+ yield* Effect2.tryPromise(() => fs3.rmdir(subagentsDir));
415
441
  cleanedFolders.push(subagentsDir);
416
442
  const sessionDir = path2.dirname(subagentsDir);
417
- const sessionDirEmpty = yield* Effect.tryPromise(async () => {
418
- const files = await fs2.readdir(sessionDir);
443
+ const sessionDirEmpty = yield* Effect2.tryPromise(async () => {
444
+ const files = await fs3.readdir(sessionDir);
419
445
  return files.length === 0;
420
446
  });
421
447
  if (sessionDirEmpty) {
422
- yield* Effect.tryPromise(() => fs2.rmdir(sessionDir));
448
+ yield* Effect2.tryPromise(() => fs3.rmdir(sessionDir));
423
449
  cleanedFolders.push(sessionDir);
424
450
  }
425
451
  }
@@ -435,50 +461,53 @@ var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
435
461
  count: deletedAgents.length + backedUpAgents.length
436
462
  };
437
463
  });
438
- var loadAgentMessages = (projectName, _sessionId, agentId) => Effect.gen(function* () {
464
+ var loadAgentMessages = (projectName, _sessionId, agentId) => Effect2.gen(function* () {
439
465
  const projectPath = path2.join(getSessionsDir(), projectName);
440
466
  const agentFilePath = path2.join(projectPath, `${agentId}.jsonl`);
441
- const content = yield* Effect.tryPromise(() => fs2.readFile(agentFilePath, "utf-8"));
467
+ const content = yield* Effect2.tryPromise(() => fs3.readFile(agentFilePath, "utf-8"));
442
468
  const lines = content.split("\n").filter((line) => line.trim());
443
469
  const messages = [];
444
- for (const line of lines) {
445
- try {
446
- const parsed = JSON.parse(line);
447
- if ("sessionId" in parsed && !("type" in parsed)) {
448
- continue;
449
- }
450
- messages.push(parsed);
451
- } 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;
452
475
  }
476
+ messages.push(parsed);
453
477
  }
454
478
  return messages;
455
479
  });
456
480
 
457
481
  // src/todos.ts
458
- import { Effect as Effect2 } from "effect";
459
- import * as fs3 from "fs/promises";
482
+ import { Effect as Effect3 } from "effect";
483
+ import * as fs4 from "fs/promises";
460
484
  import * as path3 from "path";
461
- var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
485
+ var findLinkedTodos = (sessionId, agentIds = []) => Effect3.gen(function* () {
462
486
  const todosDir = getTodosDir();
463
- const exists = yield* Effect2.tryPromise(
464
- () => fs3.access(todosDir).then(() => true).catch(() => false)
487
+ const exists = yield* Effect3.tryPromise(
488
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
465
489
  );
466
490
  if (!exists) {
467
- return void 0;
491
+ return {
492
+ sessionId,
493
+ sessionTodos: [],
494
+ agentTodos: [],
495
+ hasTodos: false
496
+ };
468
497
  }
469
498
  const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
470
499
  let sessionTodos = [];
471
- const sessionTodoExists = yield* Effect2.tryPromise(
472
- () => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
500
+ const sessionTodoExists = yield* Effect3.tryPromise(
501
+ () => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
473
502
  );
474
503
  if (sessionTodoExists) {
475
- const content = yield* Effect2.tryPromise(() => fs3.readFile(sessionTodoPath, "utf-8"));
504
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(sessionTodoPath, "utf-8"));
476
505
  try {
477
506
  sessionTodos = JSON.parse(content);
478
507
  } catch {
479
508
  }
480
509
  }
481
- const allFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
510
+ const allFiles = yield* Effect3.tryPromise(() => fs4.readdir(todosDir));
482
511
  const agentTodoPattern = new RegExp(`^${sessionId}-agent-([a-f0-9-]+)\\.json$`);
483
512
  const discoveredAgentIds = new Set(agentIds);
484
513
  for (const file of allFiles) {
@@ -491,11 +520,11 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
491
520
  for (const agentId of discoveredAgentIds) {
492
521
  const shortAgentId = agentId.replace("agent-", "");
493
522
  const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
494
- const agentTodoExists = yield* Effect2.tryPromise(
495
- () => fs3.access(agentTodoPath).then(() => true).catch(() => false)
523
+ const agentTodoExists = yield* Effect3.tryPromise(
524
+ () => fs4.access(agentTodoPath).then(() => true).catch(() => false)
496
525
  );
497
526
  if (agentTodoExists) {
498
- const content = yield* Effect2.tryPromise(() => fs3.readFile(agentTodoPath, "utf-8"));
527
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(agentTodoPath, "utf-8"));
499
528
  try {
500
529
  const todos = JSON.parse(content);
501
530
  if (todos.length > 0) {
@@ -513,25 +542,25 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
513
542
  hasTodos
514
543
  };
515
544
  });
516
- var sessionHasTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
545
+ var sessionHasTodos = (sessionId, agentIds = []) => Effect3.gen(function* () {
517
546
  const todosDir = getTodosDir();
518
- const exists = yield* Effect2.tryPromise(
519
- () => fs3.access(todosDir).then(() => true).catch(() => false)
547
+ const exists = yield* Effect3.tryPromise(
548
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
520
549
  );
521
550
  if (!exists) return false;
522
551
  const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
523
- const sessionTodoExists = yield* Effect2.tryPromise(
524
- () => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
552
+ const sessionTodoExists = yield* Effect3.tryPromise(
553
+ () => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
525
554
  );
526
555
  if (sessionTodoExists) {
527
- const content = yield* Effect2.tryPromise(() => fs3.readFile(sessionTodoPath, "utf-8"));
556
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(sessionTodoPath, "utf-8"));
528
557
  try {
529
558
  const todos = JSON.parse(content);
530
559
  if (todos.length > 0) return true;
531
560
  } catch {
532
561
  }
533
562
  }
534
- const allFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
563
+ const allFiles = yield* Effect3.tryPromise(() => fs4.readdir(todosDir));
535
564
  const agentTodoPattern = new RegExp(`^${sessionId}-agent-([a-f0-9-]+)\\.json$`);
536
565
  const discoveredAgentIds = new Set(agentIds);
537
566
  for (const file of allFiles) {
@@ -543,11 +572,11 @@ var sessionHasTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
543
572
  for (const agentId of discoveredAgentIds) {
544
573
  const shortAgentId = agentId.replace("agent-", "");
545
574
  const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
546
- const agentTodoExists = yield* Effect2.tryPromise(
547
- () => fs3.access(agentTodoPath).then(() => true).catch(() => false)
575
+ const agentTodoExists = yield* Effect3.tryPromise(
576
+ () => fs4.access(agentTodoPath).then(() => true).catch(() => false)
548
577
  );
549
578
  if (agentTodoExists) {
550
- const content = yield* Effect2.tryPromise(() => fs3.readFile(agentTodoPath, "utf-8"));
579
+ const content = yield* Effect3.tryPromise(() => fs4.readFile(agentTodoPath, "utf-8"));
551
580
  try {
552
581
  const todos = JSON.parse(content);
553
582
  if (todos.length > 0) return true;
@@ -557,60 +586,60 @@ var sessionHasTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
557
586
  }
558
587
  return false;
559
588
  });
560
- var deleteLinkedTodos = (sessionId, agentIds) => Effect2.gen(function* () {
589
+ var deleteLinkedTodos = (sessionId, agentIds) => Effect3.gen(function* () {
561
590
  const todosDir = getTodosDir();
562
- const exists = yield* Effect2.tryPromise(
563
- () => fs3.access(todosDir).then(() => true).catch(() => false)
591
+ const exists = yield* Effect3.tryPromise(
592
+ () => fs4.access(todosDir).then(() => true).catch(() => false)
564
593
  );
565
594
  if (!exists) return { deletedCount: 0 };
566
595
  const backupDir = path3.join(todosDir, ".bak");
567
- yield* Effect2.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
596
+ yield* Effect3.tryPromise(() => fs4.mkdir(backupDir, { recursive: true }));
568
597
  let deletedCount = 0;
569
598
  const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
570
- const sessionTodoExists = yield* Effect2.tryPromise(
571
- () => fs3.access(sessionTodoPath).then(() => true).catch(() => false)
599
+ const sessionTodoExists = yield* Effect3.tryPromise(
600
+ () => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
572
601
  );
573
602
  if (sessionTodoExists) {
574
603
  const backupPath = path3.join(backupDir, `${sessionId}.json`);
575
- yield* Effect2.tryPromise(() => fs3.rename(sessionTodoPath, backupPath));
604
+ yield* Effect3.tryPromise(() => fs4.rename(sessionTodoPath, backupPath));
576
605
  deletedCount++;
577
606
  }
578
607
  for (const agentId of agentIds) {
579
608
  const shortAgentId = agentId.replace("agent-", "");
580
609
  const agentTodoPath = path3.join(todosDir, `${sessionId}-agent-${shortAgentId}.json`);
581
- const agentTodoExists = yield* Effect2.tryPromise(
582
- () => fs3.access(agentTodoPath).then(() => true).catch(() => false)
610
+ const agentTodoExists = yield* Effect3.tryPromise(
611
+ () => fs4.access(agentTodoPath).then(() => true).catch(() => false)
583
612
  );
584
613
  if (agentTodoExists) {
585
614
  const backupPath = path3.join(backupDir, `${sessionId}-agent-${shortAgentId}.json`);
586
- yield* Effect2.tryPromise(() => fs3.rename(agentTodoPath, backupPath));
615
+ yield* Effect3.tryPromise(() => fs4.rename(agentTodoPath, backupPath));
587
616
  deletedCount++;
588
617
  }
589
618
  }
590
619
  return { deletedCount };
591
620
  });
592
- var findOrphanTodos = () => Effect2.gen(function* () {
621
+ var findOrphanTodos = () => Effect3.gen(function* () {
593
622
  const todosDir = getTodosDir();
594
623
  const sessionsDir = getSessionsDir();
595
- const [todosExists, sessionsExists] = yield* Effect2.all([
596
- Effect2.tryPromise(
597
- () => 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)
598
627
  ),
599
- Effect2.tryPromise(
600
- () => fs3.access(sessionsDir).then(() => true).catch(() => false)
628
+ Effect3.tryPromise(
629
+ () => fs4.access(sessionsDir).then(() => true).catch(() => false)
601
630
  )
602
631
  ]);
603
632
  if (!todosExists || !sessionsExists) return [];
604
- const todoFiles = yield* Effect2.tryPromise(() => fs3.readdir(todosDir));
633
+ const todoFiles = yield* Effect3.tryPromise(() => fs4.readdir(todosDir));
605
634
  const jsonFiles = todoFiles.filter((f) => f.endsWith(".json"));
606
635
  const validSessionIds = /* @__PURE__ */ new Set();
607
- const projectEntries = yield* Effect2.tryPromise(
608
- () => fs3.readdir(sessionsDir, { withFileTypes: true })
636
+ const projectEntries = yield* Effect3.tryPromise(
637
+ () => fs4.readdir(sessionsDir, { withFileTypes: true })
609
638
  );
610
639
  for (const entry of projectEntries) {
611
640
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
612
641
  const projectPath = path3.join(sessionsDir, entry.name);
613
- const files = yield* Effect2.tryPromise(() => fs3.readdir(projectPath));
642
+ const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
614
643
  for (const f of files) {
615
644
  if (f.endsWith(".jsonl") && !f.startsWith("agent-")) {
616
645
  validSessionIds.add(f.replace(".jsonl", ""));
@@ -629,40 +658,40 @@ var findOrphanTodos = () => Effect2.gen(function* () {
629
658
  }
630
659
  return orphans;
631
660
  });
632
- var deleteOrphanTodos = () => Effect2.gen(function* () {
661
+ var deleteOrphanTodos = () => Effect3.gen(function* () {
633
662
  const todosDir = getTodosDir();
634
663
  const orphans = yield* findOrphanTodos();
635
664
  if (orphans.length === 0) return { success: true, deletedCount: 0 };
636
665
  const backupDir = path3.join(todosDir, ".bak");
637
- yield* Effect2.tryPromise(() => fs3.mkdir(backupDir, { recursive: true }));
666
+ yield* Effect3.tryPromise(() => fs4.mkdir(backupDir, { recursive: true }));
638
667
  let deletedCount = 0;
639
668
  for (const orphan of orphans) {
640
669
  const filePath = path3.join(todosDir, orphan);
641
670
  const backupPath = path3.join(backupDir, orphan);
642
- yield* Effect2.tryPromise(() => fs3.rename(filePath, backupPath));
671
+ yield* Effect3.tryPromise(() => fs4.rename(filePath, backupPath));
643
672
  deletedCount++;
644
673
  }
645
674
  return { success: true, deletedCount };
646
675
  });
647
676
 
648
677
  // src/session/projects.ts
649
- import { Effect as Effect3 } from "effect";
650
- import * as fs4 from "fs/promises";
678
+ import { Effect as Effect4 } from "effect";
679
+ import * as fs5 from "fs/promises";
651
680
  import * as path4 from "path";
652
- var listProjects = Effect3.gen(function* () {
681
+ var listProjects = Effect4.gen(function* () {
653
682
  const sessionsDir = getSessionsDir();
654
- const exists = yield* Effect3.tryPromise(
655
- () => fs4.access(sessionsDir).then(() => true).catch(() => false)
683
+ const exists = yield* Effect4.tryPromise(
684
+ () => fs5.access(sessionsDir).then(() => true).catch(() => false)
656
685
  );
657
686
  if (!exists) {
658
687
  return [];
659
688
  }
660
- const entries = yield* Effect3.tryPromise(() => fs4.readdir(sessionsDir, { withFileTypes: true }));
661
- const projects = yield* Effect3.all(
689
+ const entries = yield* Effect4.tryPromise(() => fs5.readdir(sessionsDir, { withFileTypes: true }));
690
+ const projects = yield* Effect4.all(
662
691
  entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map(
663
- (entry) => Effect3.gen(function* () {
692
+ (entry) => Effect4.gen(function* () {
664
693
  const projectPath = path4.join(sessionsDir, entry.name);
665
- const files = yield* Effect3.tryPromise(() => fs4.readdir(projectPath));
694
+ const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
666
695
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
667
696
  return {
668
697
  name: entry.name,
@@ -678,8 +707,8 @@ var listProjects = Effect3.gen(function* () {
678
707
  });
679
708
 
680
709
  // src/session/crud.ts
681
- import { Effect as Effect4, pipe, Array as A, Option as O } from "effect";
682
- import * as fs5 from "fs/promises";
710
+ import { Effect as Effect5, pipe, Array as A, Option as O } from "effect";
711
+ import * as fs6 from "fs/promises";
683
712
  import * as path5 from "path";
684
713
  import * as crypto from "crypto";
685
714
 
@@ -853,11 +882,9 @@ function deleteMessageWithChainRepair(messages, targetId, targetType) {
853
882
  }
854
883
 
855
884
  // src/session/crud.ts
856
- var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(function* () {
885
+ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect5.gen(function* () {
857
886
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
858
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
859
- const lines = content.trim().split("\n").filter(Boolean);
860
- const messages = lines.map((line) => JSON.parse(line));
887
+ const messages = yield* readJsonlFile(filePath);
861
888
  const summaryIdx = messages.findIndex((m) => m.type === "summary");
862
889
  if (summaryIdx >= 0) {
863
890
  messages[summaryIdx] = { ...messages[summaryIdx], summary: newSummary };
@@ -871,20 +898,18 @@ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(f
871
898
  messages.unshift(summaryMsg);
872
899
  }
873
900
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
874
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
901
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
875
902
  return { success: true };
876
903
  });
877
- var listSessions = (projectName) => Effect4.gen(function* () {
904
+ var listSessions = (projectName) => Effect5.gen(function* () {
878
905
  const projectPath = path5.join(getSessionsDir(), projectName);
879
- const files = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
906
+ const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
880
907
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
881
- const sessions = yield* Effect4.all(
908
+ const sessions = yield* Effect5.all(
882
909
  sessionFiles.map(
883
- (file) => Effect4.gen(function* () {
910
+ (file) => Effect5.gen(function* () {
884
911
  const filePath = path5.join(projectPath, file);
885
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
886
- const lines = content.trim().split("\n").filter(Boolean);
887
- const messages = lines.map((line) => JSON.parse(line));
912
+ const messages = yield* readJsonlFile(filePath);
888
913
  const sessionId = file.replace(".jsonl", "");
889
914
  const userAssistantMessages = messages.filter(
890
915
  (m) => m.type === "user" || m.type === "assistant"
@@ -935,30 +960,24 @@ var listSessions = (projectName) => Effect4.gen(function* () {
935
960
  return dateB - dateA;
936
961
  });
937
962
  });
938
- var readSession = (projectName, sessionId) => Effect4.gen(function* () {
963
+ var readSession = (projectName, sessionId) => Effect5.gen(function* () {
939
964
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
940
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
941
- const lines = content.trim().split("\n").filter(Boolean);
942
- return lines.map((line) => JSON.parse(line));
965
+ return yield* readJsonlFile(filePath);
943
966
  });
944
- var deleteMessage = (projectName, sessionId, messageUuid, targetType) => Effect4.gen(function* () {
967
+ var deleteMessage = (projectName, sessionId, messageUuid, targetType) => Effect5.gen(function* () {
945
968
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
946
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
947
- const lines = content.trim().split("\n").filter(Boolean);
948
- const messages = lines.map((line) => JSON.parse(line));
969
+ const messages = yield* readJsonlFile(filePath);
949
970
  const result = deleteMessageWithChainRepair(messages, messageUuid, targetType);
950
971
  if (!result.deleted) {
951
972
  return { success: false, error: "Message not found" };
952
973
  }
953
974
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
954
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
975
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
955
976
  return { success: true, deletedMessage: result.deleted };
956
977
  });
957
- var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(function* () {
978
+ var restoreMessage = (projectName, sessionId, message, index) => Effect5.gen(function* () {
958
979
  const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
959
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
960
- const lines = content.trim().split("\n").filter(Boolean);
961
- const messages = lines.map((line) => JSON.parse(line));
980
+ const messages = yield* readJsonlFile(filePath);
962
981
  const msgUuid = message.uuid ?? message.messageId;
963
982
  if (!msgUuid) {
964
983
  return { success: false, error: "Message has no uuid or messageId" };
@@ -973,41 +992,41 @@ var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(fun
973
992
  const insertIndex = Math.min(index, messages.length);
974
993
  messages.splice(insertIndex, 0, message);
975
994
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
976
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
995
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
977
996
  return { success: true };
978
997
  });
979
- var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
998
+ var deleteSession = (projectName, sessionId) => Effect5.gen(function* () {
980
999
  const sessionsDir = getSessionsDir();
981
1000
  const projectPath = path5.join(sessionsDir, projectName);
982
1001
  const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
983
1002
  const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
984
- const stat4 = yield* Effect4.tryPromise(() => fs5.stat(filePath));
1003
+ const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
985
1004
  if (stat4.size === 0) {
986
- yield* Effect4.tryPromise(() => fs5.unlink(filePath));
1005
+ yield* Effect5.tryPromise(() => fs6.unlink(filePath));
987
1006
  const agentBackupDir2 = path5.join(projectPath, ".bak");
988
- yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir2, { recursive: true }));
1007
+ yield* Effect5.tryPromise(() => fs6.mkdir(agentBackupDir2, { recursive: true }));
989
1008
  for (const agentId of linkedAgents) {
990
1009
  const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
991
1010
  const agentBackupPath = path5.join(agentBackupDir2, `${agentId}.jsonl`);
992
- yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
1011
+ yield* Effect5.tryPromise(() => fs6.rename(agentPath, agentBackupPath).catch(() => {
993
1012
  }));
994
1013
  }
995
1014
  yield* deleteLinkedTodos(sessionId, linkedAgents);
996
1015
  return { success: true, deletedAgents: linkedAgents.length };
997
1016
  }
998
1017
  const backupDir = path5.join(sessionsDir, ".bak");
999
- yield* Effect4.tryPromise(() => fs5.mkdir(backupDir, { recursive: true }));
1018
+ yield* Effect5.tryPromise(() => fs6.mkdir(backupDir, { recursive: true }));
1000
1019
  const agentBackupDir = path5.join(projectPath, ".bak");
1001
- yield* Effect4.tryPromise(() => fs5.mkdir(agentBackupDir, { recursive: true }));
1020
+ yield* Effect5.tryPromise(() => fs6.mkdir(agentBackupDir, { recursive: true }));
1002
1021
  for (const agentId of linkedAgents) {
1003
1022
  const agentPath = path5.join(projectPath, `${agentId}.jsonl`);
1004
1023
  const agentBackupPath = path5.join(agentBackupDir, `${agentId}.jsonl`);
1005
- yield* Effect4.tryPromise(() => fs5.rename(agentPath, agentBackupPath).catch(() => {
1024
+ yield* Effect5.tryPromise(() => fs6.rename(agentPath, agentBackupPath).catch(() => {
1006
1025
  }));
1007
1026
  }
1008
1027
  const todosResult = yield* deleteLinkedTodos(sessionId, linkedAgents);
1009
1028
  const backupPath = path5.join(backupDir, `${projectName}_${sessionId}.jsonl`);
1010
- yield* Effect4.tryPromise(() => fs5.rename(filePath, backupPath));
1029
+ yield* Effect5.tryPromise(() => fs6.rename(filePath, backupPath));
1011
1030
  return {
1012
1031
  success: true,
1013
1032
  backupPath,
@@ -1015,15 +1034,15 @@ var deleteSession = (projectName, sessionId) => Effect4.gen(function* () {
1015
1034
  deletedTodos: todosResult.deletedCount
1016
1035
  };
1017
1036
  });
1018
- var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function* () {
1037
+ var renameSession = (projectName, sessionId, newTitle) => Effect5.gen(function* () {
1019
1038
  const projectPath = path5.join(getSessionsDir(), projectName);
1020
1039
  const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
1021
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
1040
+ const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
1022
1041
  const lines = content.trim().split("\n").filter(Boolean);
1023
1042
  if (lines.length === 0) {
1024
1043
  return { success: false, error: "Empty session" };
1025
1044
  }
1026
- const messages = lines.map((line) => JSON.parse(line));
1045
+ const messages = parseJsonlLines(lines, filePath);
1027
1046
  const sessionUuids = /* @__PURE__ */ new Set();
1028
1047
  for (const msg of messages) {
1029
1048
  if (msg.uuid && typeof msg.uuid === "string") {
@@ -1042,16 +1061,14 @@ var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function*
1042
1061
  messages.unshift(customTitleRecord);
1043
1062
  }
1044
1063
  const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1045
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
1046
- 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));
1047
1066
  const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
1048
1067
  const summariesTargetingThis = [];
1049
1068
  for (const file of allJsonlFiles) {
1050
1069
  const otherFilePath = path5.join(projectPath, file);
1051
1070
  try {
1052
- const otherContent = yield* Effect4.tryPromise(() => fs5.readFile(otherFilePath, "utf-8"));
1053
- const otherLines = otherContent.trim().split("\n").filter(Boolean);
1054
- const otherMessages = otherLines.map((l) => JSON.parse(l));
1071
+ const otherMessages = yield* readJsonlFile(otherFilePath);
1055
1072
  for (let i = 0; i < otherMessages.length; i++) {
1056
1073
  const msg = otherMessages[i];
1057
1074
  if (msg.type === "summary" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
@@ -1070,19 +1087,15 @@ var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function*
1070
1087
  summariesTargetingThis.sort((a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? ""));
1071
1088
  const firstSummary = summariesTargetingThis[0];
1072
1089
  const summaryFilePath = path5.join(projectPath, firstSummary.file);
1073
- const summaryContent = yield* Effect4.tryPromise(() => fs5.readFile(summaryFilePath, "utf-8"));
1074
- const summaryLines = summaryContent.trim().split("\n").filter(Boolean);
1075
- const summaryMessages = summaryLines.map((l) => JSON.parse(l));
1090
+ const summaryMessages = yield* readJsonlFile(summaryFilePath);
1076
1091
  summaryMessages[firstSummary.idx] = {
1077
1092
  ...summaryMessages[firstSummary.idx],
1078
1093
  summary: newTitle
1079
1094
  };
1080
1095
  const newSummaryContent = summaryMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1081
- yield* Effect4.tryPromise(() => fs5.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
1096
+ yield* Effect5.tryPromise(() => fs6.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
1082
1097
  } else {
1083
- const currentContent = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
1084
- const currentLines = currentContent.trim().split("\n").filter(Boolean);
1085
- const currentMessages = currentLines.map((l) => JSON.parse(l));
1098
+ const currentMessages = yield* readJsonlFile(filePath);
1086
1099
  const firstUserIdx = currentMessages.findIndex((m) => m.type === "user");
1087
1100
  if (firstUserIdx >= 0) {
1088
1101
  const firstMsg = currentMessages[firstUserIdx];
@@ -1099,52 +1112,50 @@ var renameSession = (projectName, sessionId, newTitle) => Effect4.gen(function*
1099
1112
 
1100
1113
  ${cleanedText}`;
1101
1114
  const updatedContent = currentMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1102
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, updatedContent, "utf-8"));
1115
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, updatedContent, "utf-8"));
1103
1116
  }
1104
1117
  }
1105
1118
  }
1106
1119
  }
1107
1120
  return { success: true };
1108
1121
  });
1109
- var moveSession = (sourceProject, sessionId, targetProject) => Effect4.gen(function* () {
1122
+ var moveSession = (sourceProject, sessionId, targetProject) => Effect5.gen(function* () {
1110
1123
  const sessionsDir = getSessionsDir();
1111
1124
  const sourcePath = path5.join(sessionsDir, sourceProject);
1112
1125
  const targetPath = path5.join(sessionsDir, targetProject);
1113
1126
  const sourceFile = path5.join(sourcePath, `${sessionId}.jsonl`);
1114
1127
  const targetFile = path5.join(targetPath, `${sessionId}.jsonl`);
1115
- const sourceExists = yield* Effect4.tryPromise(
1116
- () => fs5.access(sourceFile).then(() => true).catch(() => false)
1128
+ const sourceExists = yield* Effect5.tryPromise(
1129
+ () => fs6.access(sourceFile).then(() => true).catch(() => false)
1117
1130
  );
1118
1131
  if (!sourceExists) {
1119
1132
  return { success: false, error: "Source session not found" };
1120
1133
  }
1121
- const targetExists = yield* Effect4.tryPromise(
1122
- () => fs5.access(targetFile).then(() => true).catch(() => false)
1134
+ const targetExists = yield* Effect5.tryPromise(
1135
+ () => fs6.access(targetFile).then(() => true).catch(() => false)
1123
1136
  );
1124
1137
  if (targetExists) {
1125
1138
  return { success: false, error: "Session already exists in target project" };
1126
1139
  }
1127
- yield* Effect4.tryPromise(() => fs5.mkdir(targetPath, { recursive: true }));
1140
+ yield* Effect5.tryPromise(() => fs6.mkdir(targetPath, { recursive: true }));
1128
1141
  const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
1129
- yield* Effect4.tryPromise(() => fs5.rename(sourceFile, targetFile));
1142
+ yield* Effect5.tryPromise(() => fs6.rename(sourceFile, targetFile));
1130
1143
  for (const agentId of linkedAgents) {
1131
1144
  const sourceAgentFile = path5.join(sourcePath, `${agentId}.jsonl`);
1132
1145
  const targetAgentFile = path5.join(targetPath, `${agentId}.jsonl`);
1133
- const agentExists = yield* Effect4.tryPromise(
1134
- () => fs5.access(sourceAgentFile).then(() => true).catch(() => false)
1146
+ const agentExists = yield* Effect5.tryPromise(
1147
+ () => fs6.access(sourceAgentFile).then(() => true).catch(() => false)
1135
1148
  );
1136
1149
  if (agentExists) {
1137
- yield* Effect4.tryPromise(() => fs5.rename(sourceAgentFile, targetAgentFile));
1150
+ yield* Effect5.tryPromise(() => fs6.rename(sourceAgentFile, targetAgentFile));
1138
1151
  }
1139
1152
  }
1140
1153
  return { success: true };
1141
1154
  });
1142
- var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(function* () {
1155
+ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect5.gen(function* () {
1143
1156
  const projectPath = path5.join(getSessionsDir(), projectName);
1144
1157
  const filePath = path5.join(projectPath, `${sessionId}.jsonl`);
1145
- const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
1146
- const lines = content.trim().split("\n").filter(Boolean);
1147
- const allMessages = lines.map((line) => JSON.parse(line));
1158
+ const allMessages = yield* readJsonlFile(filePath);
1148
1159
  const splitIndex = allMessages.findIndex((m) => m.uuid === splitAtMessageUuid);
1149
1160
  if (splitIndex === -1) {
1150
1161
  return { success: false, error: "Message not found" };
@@ -1190,30 +1201,30 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(f
1190
1201
  updatedMovedMessages.unshift(clonedSummary);
1191
1202
  }
1192
1203
  const keptContent = keptMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1193
- yield* Effect4.tryPromise(() => fs5.writeFile(filePath, keptContent, "utf-8"));
1204
+ yield* Effect5.tryPromise(() => fs6.writeFile(filePath, keptContent, "utf-8"));
1194
1205
  const newFilePath = path5.join(projectPath, `${newSessionId}.jsonl`);
1195
1206
  const newContent = updatedMovedMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1196
- yield* Effect4.tryPromise(() => fs5.writeFile(newFilePath, newContent, "utf-8"));
1197
- const agentFiles = yield* Effect4.tryPromise(() => fs5.readdir(projectPath));
1207
+ yield* Effect5.tryPromise(() => fs6.writeFile(newFilePath, newContent, "utf-8"));
1208
+ const agentFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
1198
1209
  const agentJsonlFiles = agentFiles.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
1199
1210
  for (const agentFile of agentJsonlFiles) {
1200
1211
  const agentPath = path5.join(projectPath, agentFile);
1201
- const agentContent = yield* Effect4.tryPromise(() => fs5.readFile(agentPath, "utf-8"));
1212
+ const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
1202
1213
  const agentLines = agentContent.trim().split("\n").filter(Boolean);
1203
1214
  if (agentLines.length === 0) continue;
1204
- const firstAgentMsg = JSON.parse(agentLines[0]);
1215
+ const agentMessages = parseJsonlLines(agentLines, agentPath);
1216
+ const firstAgentMsg = agentMessages[0];
1205
1217
  if (firstAgentMsg.sessionId === sessionId) {
1206
1218
  const agentId = agentFile.replace("agent-", "").replace(".jsonl", "");
1207
1219
  const isRelatedToMoved = movedMessages.some(
1208
1220
  (msg) => msg.agentId === agentId
1209
1221
  );
1210
1222
  if (isRelatedToMoved) {
1211
- const updatedAgentMessages = agentLines.map((line) => {
1212
- const msg = JSON.parse(line);
1213
- return JSON.stringify({ ...msg, sessionId: newSessionId });
1214
- });
1223
+ const updatedAgentMessages = agentMessages.map(
1224
+ (msg) => JSON.stringify({ ...msg, sessionId: newSessionId })
1225
+ );
1215
1226
  const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
1216
- yield* Effect4.tryPromise(() => fs5.writeFile(agentPath, updatedAgentContent, "utf-8"));
1227
+ yield* Effect5.tryPromise(() => fs6.writeFile(agentPath, updatedAgentContent, "utf-8"));
1217
1228
  }
1218
1229
  }
1219
1230
  }
@@ -1227,8 +1238,8 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(f
1227
1238
  });
1228
1239
 
1229
1240
  // src/session/tree.ts
1230
- import { Effect as Effect5 } from "effect";
1231
- import * as fs6 from "fs/promises";
1241
+ import { Effect as Effect6 } from "effect";
1242
+ import * as fs7 from "fs/promises";
1232
1243
  import * as path6 from "path";
1233
1244
  var sortSessions = (sessions, sort) => {
1234
1245
  return sessions.sort((a, b) => {
@@ -1268,12 +1279,12 @@ var sortSessions = (sessions, sort) => {
1268
1279
  return sort.order === "desc" ? -comparison : comparison;
1269
1280
  });
1270
1281
  };
1271
- var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect5.gen(function* () {
1282
+ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect6.gen(function* () {
1272
1283
  const projectPath = path6.join(getSessionsDir(), projectName);
1273
1284
  const filePath = path6.join(projectPath, `${sessionId}.jsonl`);
1274
- const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
1285
+ const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
1275
1286
  const lines = content.trim().split("\n").filter(Boolean);
1276
- const messages = lines.map((line) => JSON.parse(line));
1287
+ const messages = parseJsonlLines(lines, filePath);
1277
1288
  let summaries;
1278
1289
  if (summariesByTargetSession) {
1279
1290
  summaries = [...summariesByTargetSession.get(sessionId) ?? []].sort((a, b) => {
@@ -1289,26 +1300,24 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1289
1300
  sessionUuids.add(msg.uuid);
1290
1301
  }
1291
1302
  }
1292
- const projectFiles = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
1303
+ const projectFiles = yield* Effect6.tryPromise(() => fs7.readdir(projectPath));
1293
1304
  const allJsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
1294
1305
  for (const file of allJsonlFiles) {
1295
1306
  try {
1296
1307
  const otherFilePath = path6.join(projectPath, file);
1297
- const otherContent = yield* Effect5.tryPromise(() => fs6.readFile(otherFilePath, "utf-8"));
1308
+ const otherContent = yield* Effect6.tryPromise(() => fs7.readFile(otherFilePath, "utf-8"));
1298
1309
  const otherLines = otherContent.trim().split("\n").filter(Boolean);
1299
- for (const line of otherLines) {
1300
- try {
1301
- const msg = JSON.parse(line);
1302
- if (msg.type === "summary" && typeof msg.summary === "string" && typeof msg.leafUuid === "string" && sessionUuids.has(msg.leafUuid)) {
1303
- const targetMsg = messages.find((m) => m.uuid === msg.leafUuid);
1304
- summaries.push({
1305
- summary: msg.summary,
1306
- leafUuid: msg.leafUuid,
1307
- timestamp: targetMsg?.timestamp ?? msg.timestamp,
1308
- sourceFile: file
1309
- });
1310
- }
1311
- } catch {
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
+ });
1312
1321
  }
1313
1322
  }
1314
1323
  } catch {
@@ -1342,7 +1351,7 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1342
1351
  for (const agentId of linkedAgentIds) {
1343
1352
  const agentPath = path6.join(projectPath, `${agentId}.jsonl`);
1344
1353
  try {
1345
- const agentContent = yield* Effect5.tryPromise(() => fs6.readFile(agentPath, "utf-8"));
1354
+ const agentContent = yield* Effect6.tryPromise(() => fs7.readFile(agentPath, "utf-8"));
1346
1355
  const agentLines = agentContent.trim().split("\n").filter(Boolean);
1347
1356
  const agentMsgs = agentLines.map((l) => JSON.parse(l));
1348
1357
  const agentUserAssistant = agentMsgs.filter(
@@ -1390,22 +1399,22 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
1390
1399
  });
1391
1400
  var loadSessionTreeData = (projectName, sessionId) => loadSessionTreeDataInternal(projectName, sessionId, void 0);
1392
1401
  var DEFAULT_SORT = { field: "summary", order: "desc" };
1393
- var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* () {
1402
+ var loadProjectTreeData = (projectName, sortOptions) => Effect6.gen(function* () {
1394
1403
  const project = (yield* listProjects).find((p) => p.name === projectName);
1395
1404
  if (!project) {
1396
1405
  return null;
1397
1406
  }
1398
1407
  const sort = sortOptions ?? DEFAULT_SORT;
1399
1408
  const projectPath = path6.join(getSessionsDir(), projectName);
1400
- const files = yield* Effect5.tryPromise(() => fs6.readdir(projectPath));
1409
+ const files = yield* Effect6.tryPromise(() => fs7.readdir(projectPath));
1401
1410
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
1402
1411
  const fileMtimes = /* @__PURE__ */ new Map();
1403
- yield* Effect5.all(
1412
+ yield* Effect6.all(
1404
1413
  sessionFiles.map(
1405
- (file) => Effect5.gen(function* () {
1414
+ (file) => Effect6.gen(function* () {
1406
1415
  const filePath = path6.join(projectPath, file);
1407
1416
  try {
1408
- const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
1417
+ const stat4 = yield* Effect6.tryPromise(() => fs7.stat(filePath));
1409
1418
  fileMtimes.set(file.replace(".jsonl", ""), stat4.mtimeMs);
1410
1419
  } catch {
1411
1420
  }
@@ -1416,38 +1425,36 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1416
1425
  const globalUuidMap = /* @__PURE__ */ new Map();
1417
1426
  const allSummaries = [];
1418
1427
  const allJsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1419
- yield* Effect5.all(
1428
+ yield* Effect6.all(
1420
1429
  allJsonlFiles.map(
1421
- (file) => Effect5.gen(function* () {
1430
+ (file) => Effect6.gen(function* () {
1422
1431
  const filePath = path6.join(projectPath, file);
1423
1432
  const fileSessionId = file.replace(".jsonl", "");
1424
1433
  try {
1425
- const content = yield* Effect5.tryPromise(() => fs6.readFile(filePath, "utf-8"));
1434
+ const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
1426
1435
  const lines = content.trim().split("\n").filter(Boolean);
1427
- for (const line of lines) {
1428
- try {
1429
- const msg = JSON.parse(line);
1430
- if (msg.uuid && typeof msg.uuid === "string") {
1431
- globalUuidMap.set(msg.uuid, {
1432
- sessionId: fileSessionId,
1433
- timestamp: msg.timestamp
1434
- });
1435
- }
1436
- if (msg.messageId && typeof msg.messageId === "string") {
1437
- globalUuidMap.set(msg.messageId, {
1438
- sessionId: fileSessionId,
1439
- timestamp: msg.snapshot?.timestamp
1440
- });
1441
- }
1442
- if (msg.type === "summary" && typeof msg.summary === "string") {
1443
- allSummaries.push({
1444
- summary: msg.summary,
1445
- leafUuid: msg.leafUuid,
1446
- timestamp: msg.timestamp,
1447
- sourceFile: file
1448
- });
1449
- }
1450
- } catch {
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
+ });
1451
1458
  }
1452
1459
  }
1453
1460
  } catch {
@@ -1475,7 +1482,7 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1475
1482
  }
1476
1483
  }
1477
1484
  }
1478
- const sessions = yield* Effect5.all(
1485
+ const sessions = yield* Effect6.all(
1479
1486
  sessionFiles.map((file) => {
1480
1487
  const sessionId = file.replace(".jsonl", "");
1481
1488
  const mtime = fileMtimes.get(sessionId);
@@ -1500,10 +1507,10 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
1500
1507
  });
1501
1508
 
1502
1509
  // src/session/analysis.ts
1503
- import { Effect as Effect6 } from "effect";
1504
- import * as fs7 from "fs/promises";
1510
+ import { Effect as Effect7 } from "effect";
1511
+ import * as fs8 from "fs/promises";
1505
1512
  import * as path7 from "path";
1506
- var analyzeSession = (projectName, sessionId) => Effect6.gen(function* () {
1513
+ var analyzeSession = (projectName, sessionId) => Effect7.gen(function* () {
1507
1514
  const messages = yield* readSession(projectName, sessionId);
1508
1515
  let userMessages = 0;
1509
1516
  let assistantMessages = 0;
@@ -1634,13 +1641,13 @@ var analyzeSession = (projectName, sessionId) => Effect6.gen(function* () {
1634
1641
  milestones
1635
1642
  };
1636
1643
  });
1637
- var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
1644
+ var compressSession = (projectName, sessionId, options = {}) => Effect7.gen(function* () {
1638
1645
  const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
1639
1646
  const filePath = path7.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
1640
- const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
1647
+ const content = yield* Effect7.tryPromise(() => fs8.readFile(filePath, "utf-8"));
1641
1648
  const originalSize = Buffer.byteLength(content, "utf-8");
1642
1649
  const lines = content.trim().split("\n").filter(Boolean);
1643
- const messages = lines.map((line) => JSON.parse(line));
1650
+ const messages = parseJsonlLines(lines, filePath);
1644
1651
  let removedSnapshots = 0;
1645
1652
  let truncatedOutputs = 0;
1646
1653
  const snapshotIndices = [];
@@ -1680,7 +1687,7 @@ var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(func
1680
1687
  }
1681
1688
  const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
1682
1689
  const compressedSize = Buffer.byteLength(newContent, "utf-8");
1683
- yield* Effect6.tryPromise(() => fs7.writeFile(filePath, newContent, "utf-8"));
1690
+ yield* Effect7.tryPromise(() => fs8.writeFile(filePath, newContent, "utf-8"));
1684
1691
  return {
1685
1692
  success: true,
1686
1693
  originalSize,
@@ -1689,12 +1696,12 @@ var compressSession = (projectName, sessionId, options = {}) => Effect6.gen(func
1689
1696
  truncatedOutputs
1690
1697
  };
1691
1698
  });
1692
- var extractProjectKnowledge = (projectName, sessionIds) => Effect6.gen(function* () {
1699
+ var extractProjectKnowledge = (projectName, sessionIds) => Effect7.gen(function* () {
1693
1700
  const sessionsDir = getSessionsDir();
1694
1701
  const projectDir = path7.join(sessionsDir, projectName);
1695
1702
  let targetSessionIds = sessionIds;
1696
1703
  if (!targetSessionIds) {
1697
- const files = yield* Effect6.tryPromise(() => fs7.readdir(projectDir));
1704
+ const files = yield* Effect7.tryPromise(() => fs8.readdir(projectDir));
1698
1705
  targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
1699
1706
  }
1700
1707
  const fileModifyCount = /* @__PURE__ */ new Map();
@@ -1768,7 +1775,7 @@ function truncateText(text, maxLen) {
1768
1775
  }
1769
1776
  return cleaned;
1770
1777
  }
1771
- var summarizeSession = (projectName, sessionId, options = {}) => Effect6.gen(function* () {
1778
+ var summarizeSession = (projectName, sessionId, options = {}) => Effect7.gen(function* () {
1772
1779
  const { limit = 50, maxLength = 100 } = options;
1773
1780
  const messages = yield* readSession(projectName, sessionId);
1774
1781
  const lines = [];
@@ -1820,15 +1827,15 @@ var summarizeSession = (projectName, sessionId, options = {}) => Effect6.gen(fun
1820
1827
  });
1821
1828
 
1822
1829
  // src/session/cleanup.ts
1823
- import { Effect as Effect7 } from "effect";
1824
- import * as fs8 from "fs/promises";
1830
+ import { Effect as Effect8 } from "effect";
1831
+ import * as fs9 from "fs/promises";
1825
1832
  import * as path8 from "path";
1826
- var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* () {
1833
+ var cleanInvalidMessages = (projectName, sessionId) => Effect8.gen(function* () {
1827
1834
  const filePath = path8.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
1828
- const content = yield* Effect7.tryPromise(() => fs8.readFile(filePath, "utf-8"));
1835
+ const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
1829
1836
  const lines = content.trim().split("\n").filter(Boolean);
1830
1837
  if (lines.length === 0) return { removedCount: 0, remainingCount: 0 };
1831
- const messages = lines.map((line) => JSON.parse(line));
1838
+ const messages = parseJsonlLines(lines, filePath);
1832
1839
  const invalidIndices = [];
1833
1840
  messages.forEach((msg, idx) => {
1834
1841
  if (isInvalidApiKeyMessage(msg)) {
@@ -1857,7 +1864,7 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* ()
1857
1864
  lastValidUuid = msg.uuid;
1858
1865
  }
1859
1866
  const newContent = filtered.length > 0 ? filtered.map((m) => JSON.stringify(m)).join("\n") + "\n" : "";
1860
- yield* Effect7.tryPromise(() => fs8.writeFile(filePath, newContent, "utf-8"));
1867
+ yield* Effect8.tryPromise(() => fs9.writeFile(filePath, newContent, "utf-8"));
1861
1868
  const remainingUserAssistant = filtered.filter(
1862
1869
  (m) => m.type === "user" || m.type === "assistant"
1863
1870
  ).length;
@@ -1865,14 +1872,14 @@ var cleanInvalidMessages = (projectName, sessionId) => Effect7.gen(function* ()
1865
1872
  const remainingCount = remainingUserAssistant > 0 ? remainingUserAssistant : hasSummary ? 1 : 0;
1866
1873
  return { removedCount: invalidIndices.length, remainingCount };
1867
1874
  });
1868
- var previewCleanup = (projectName) => Effect7.gen(function* () {
1875
+ var previewCleanup = (projectName) => Effect8.gen(function* () {
1869
1876
  const projects = yield* listProjects;
1870
1877
  const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
1871
1878
  const orphanTodos = yield* findOrphanTodos();
1872
1879
  const orphanTodoCount = orphanTodos.length;
1873
- const results = yield* Effect7.all(
1880
+ const results = yield* Effect8.all(
1874
1881
  targetProjects.map(
1875
- (project) => Effect7.gen(function* () {
1882
+ (project) => Effect8.gen(function* () {
1876
1883
  const sessions = yield* listSessions(project.name);
1877
1884
  const emptySessions = sessions.filter((s) => s.messageCount === 0);
1878
1885
  const invalidSessions = sessions.filter(
@@ -1905,7 +1912,7 @@ var previewCleanup = (projectName) => Effect7.gen(function* () {
1905
1912
  }
1906
1913
  return results;
1907
1914
  });
1908
- var clearSessions = (options) => Effect7.gen(function* () {
1915
+ var clearSessions = (options) => Effect8.gen(function* () {
1909
1916
  const {
1910
1917
  projectName,
1911
1918
  clearEmpty = true,
@@ -1924,7 +1931,7 @@ var clearSessions = (options) => Effect7.gen(function* () {
1924
1931
  if (clearInvalid) {
1925
1932
  for (const project of targetProjects) {
1926
1933
  const projectPath = path8.join(getSessionsDir(), project.name);
1927
- const files = yield* Effect7.tryPromise(() => fs8.readdir(projectPath));
1934
+ const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
1928
1935
  const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
1929
1936
  for (const file of sessionFiles) {
1930
1937
  const sessionId = file.replace(".jsonl", "");
@@ -1980,72 +1987,96 @@ var clearSessions = (options) => Effect7.gen(function* () {
1980
1987
  });
1981
1988
 
1982
1989
  // src/session/search.ts
1983
- import { Effect as Effect8 } from "effect";
1984
- import * as fs9 from "fs/promises";
1990
+ import { Effect as Effect9, pipe as pipe2 } from "effect";
1991
+ import * as fs10 from "fs/promises";
1985
1992
  import * as path9 from "path";
1986
- 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* () {
1987
2047
  const { projectName, searchContent = false } = options;
1988
- const results = [];
1989
2048
  const queryLower = query.toLowerCase();
1990
2049
  const projects = yield* listProjects;
1991
2050
  const targetProjects = projectName ? projects.filter((p) => p.name === projectName) : projects;
1992
- for (const project of targetProjects) {
1993
- const sessions = yield* listSessions(project.name);
1994
- for (const session of sessions) {
1995
- const titleLower = (session.title ?? "").toLowerCase();
1996
- if (titleLower.includes(queryLower)) {
1997
- results.push({
1998
- sessionId: session.id,
1999
- projectName: project.name,
2000
- title: session.title ?? "Untitled",
2001
- matchType: "title",
2002
- timestamp: session.updatedAt
2003
- });
2004
- }
2005
- }
2006
- }
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 = [];
2007
2070
  if (searchContent) {
2008
- for (const project of targetProjects) {
2009
- const projectPath = path9.join(getSessionsDir(), project.name);
2010
- const files = yield* Effect8.tryPromise(() => fs9.readdir(projectPath));
2011
- const sessionFiles = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
2012
- for (const file of sessionFiles) {
2013
- const sessionId = file.replace(".jsonl", "");
2014
- if (results.some((r) => r.sessionId === sessionId && r.projectName === project.name)) {
2015
- continue;
2016
- }
2017
- const filePath = path9.join(projectPath, file);
2018
- const content = yield* Effect8.tryPromise(() => fs9.readFile(filePath, "utf-8"));
2019
- const lines = content.trim().split("\n").filter(Boolean);
2020
- for (const line of lines) {
2021
- try {
2022
- const msg = JSON.parse(line);
2023
- if (msg.type !== "user" && msg.type !== "assistant") continue;
2024
- const text = extractTextContent(msg.message);
2025
- const textLower = text.toLowerCase();
2026
- if (textLower.includes(queryLower)) {
2027
- const matchIndex = textLower.indexOf(queryLower);
2028
- const start = Math.max(0, matchIndex - 50);
2029
- const end = Math.min(text.length, matchIndex + query.length + 50);
2030
- const snippet = (start > 0 ? "..." : "") + text.slice(start, end).trim() + (end < text.length ? "..." : "");
2031
- results.push({
2032
- sessionId,
2033
- projectName: project.name,
2034
- title: extractTitle(extractTextContent(msg.message)) || `Session ${sessionId.slice(0, 8)}`,
2035
- matchType: "content",
2036
- snippet,
2037
- messageUuid: msg.uuid,
2038
- timestamp: msg.timestamp
2039
- });
2040
- break;
2041
- }
2042
- } catch {
2043
- }
2044
- }
2045
- }
2046
- }
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();
2047
2077
  }
2048
- return results.sort((a, b) => {
2078
+ const allResults = [...titleResults, ...contentResults];
2079
+ return allResults.sort((a, b) => {
2049
2080
  const dateA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
2050
2081
  const dateB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
2051
2082
  return dateB - dateA;
@@ -2053,8 +2084,8 @@ var searchSessions = (query, options = {}) => Effect8.gen(function* () {
2053
2084
  });
2054
2085
 
2055
2086
  // src/session/files.ts
2056
- import { Effect as Effect9 } from "effect";
2057
- var getSessionFiles = (projectName, sessionId) => Effect9.gen(function* () {
2087
+ import { Effect as Effect10 } from "effect";
2088
+ var getSessionFiles = (projectName, sessionId) => Effect10.gen(function* () {
2058
2089
  const messages = yield* readSession(projectName, sessionId);
2059
2090
  const fileChanges = [];
2060
2091
  const seenFiles = /* @__PURE__ */ new Set();
@@ -2108,13 +2139,13 @@ var getSessionFiles = (projectName, sessionId) => Effect9.gen(function* () {
2108
2139
  });
2109
2140
 
2110
2141
  // src/session/index-file.ts
2111
- import { Effect as Effect10 } from "effect";
2112
- import * as fs10 from "fs/promises";
2142
+ import { Effect as Effect11 } from "effect";
2143
+ import * as fs11 from "fs/promises";
2113
2144
  import * as path10 from "path";
2114
- var loadSessionsIndex = (projectName) => Effect10.gen(function* () {
2145
+ var loadSessionsIndex = (projectName) => Effect11.gen(function* () {
2115
2146
  const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
2116
2147
  try {
2117
- const content = yield* Effect10.tryPromise(() => fs10.readFile(indexPath, "utf-8"));
2148
+ const content = yield* Effect11.tryPromise(() => fs11.readFile(indexPath, "utf-8"));
2118
2149
  const index = JSON.parse(content);
2119
2150
  return index;
2120
2151
  } catch {
@@ -2141,10 +2172,10 @@ var sortIndexEntriesByModified = (entries) => {
2141
2172
  return modB - modA;
2142
2173
  });
2143
2174
  };
2144
- var hasSessionsIndex = (projectName) => Effect10.gen(function* () {
2175
+ var hasSessionsIndex = (projectName) => Effect11.gen(function* () {
2145
2176
  const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
2146
2177
  try {
2147
- yield* Effect10.tryPromise(() => fs10.access(indexPath));
2178
+ yield* Effect11.tryPromise(() => fs11.access(indexPath));
2148
2179
  return true;
2149
2180
  } catch {
2150
2181
  return false;
@@ -2192,8 +2223,10 @@ export {
2192
2223
  maskHomePath,
2193
2224
  moveSession,
2194
2225
  parseCommandMessage,
2226
+ parseJsonlLines,
2195
2227
  pathToFolderName,
2196
2228
  previewCleanup,
2229
+ readJsonlFile,
2197
2230
  readSession,
2198
2231
  renameSession,
2199
2232
  restoreMessage,
@@ -2204,6 +2237,7 @@ export {
2204
2237
  sortProjects,
2205
2238
  splitSession,
2206
2239
  summarizeSession,
2240
+ tryParseJsonLine,
2207
2241
  updateSessionSummary,
2208
2242
  validateChain,
2209
2243
  validateToolUseResult