@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.d.ts +123 -73
- package/dist/index.js +681 -474
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-BdPaAnP8.d.ts → types-mWa378iC.d.ts} +3 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/paths.ts
|
|
2
|
-
import * as
|
|
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 (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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/
|
|
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
|
|
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) =>
|
|
321
|
+
var findLinkedAgents = (projectName, sessionId) => Effect2.gen(function* () {
|
|
284
322
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
285
|
-
const files = yield*
|
|
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*
|
|
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) =>
|
|
342
|
+
var findOrphanAgentsWithPaths = (projectName) => Effect2.gen(function* () {
|
|
305
343
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
306
|
-
const files = yield*
|
|
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
|
|
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*
|
|
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*
|
|
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*
|
|
344
|
-
() =>
|
|
381
|
+
const subagentsExists = yield* Effect2.tryPromise(
|
|
382
|
+
() => fs3.stat(subagentsPath).then(() => true).catch(() => false)
|
|
345
383
|
);
|
|
346
384
|
if (subagentsExists) {
|
|
347
|
-
const subagentFiles = yield*
|
|
348
|
-
() =>
|
|
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*
|
|
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) =>
|
|
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) =>
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
398
|
-
const files = await
|
|
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*
|
|
440
|
+
yield* Effect2.tryPromise(() => fs3.rmdir(subagentsDir));
|
|
403
441
|
cleanedFolders.push(subagentsDir);
|
|
404
442
|
const sessionDir = path2.dirname(subagentsDir);
|
|
405
|
-
const sessionDirEmpty = yield*
|
|
406
|
-
const files = await
|
|
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*
|
|
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) =>
|
|
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*
|
|
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 (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
|
447
|
-
import * as
|
|
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 = []) =>
|
|
485
|
+
var findLinkedTodos = (sessionId, agentIds = []) => Effect3.gen(function* () {
|
|
450
486
|
const todosDir = getTodosDir();
|
|
451
|
-
const exists = yield*
|
|
452
|
-
() =>
|
|
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*
|
|
465
|
-
() =>
|
|
500
|
+
const sessionTodoExists = yield* Effect3.tryPromise(
|
|
501
|
+
() => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
466
502
|
);
|
|
467
503
|
if (sessionTodoExists) {
|
|
468
|
-
const content = yield*
|
|
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*
|
|
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*
|
|
488
|
-
() =>
|
|
523
|
+
const agentTodoExists = yield* Effect3.tryPromise(
|
|
524
|
+
() => fs4.access(agentTodoPath).then(() => true).catch(() => false)
|
|
489
525
|
);
|
|
490
526
|
if (agentTodoExists) {
|
|
491
|
-
const content = yield*
|
|
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 = []) =>
|
|
545
|
+
var sessionHasTodos = (sessionId, agentIds = []) => Effect3.gen(function* () {
|
|
510
546
|
const todosDir = getTodosDir();
|
|
511
|
-
const exists = yield*
|
|
512
|
-
() =>
|
|
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*
|
|
517
|
-
() =>
|
|
552
|
+
const sessionTodoExists = yield* Effect3.tryPromise(
|
|
553
|
+
() => fs4.access(sessionTodoPath).then(() => true).catch(() => false)
|
|
518
554
|
);
|
|
519
555
|
if (sessionTodoExists) {
|
|
520
|
-
const content = yield*
|
|
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*
|
|
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*
|
|
540
|
-
() =>
|
|
575
|
+
const agentTodoExists = yield* Effect3.tryPromise(
|
|
576
|
+
() => fs4.access(agentTodoPath).then(() => true).catch(() => false)
|
|
541
577
|
);
|
|
542
578
|
if (agentTodoExists) {
|
|
543
|
-
const content = yield*
|
|
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) =>
|
|
589
|
+
var deleteLinkedTodos = (sessionId, agentIds) => Effect3.gen(function* () {
|
|
554
590
|
const todosDir = getTodosDir();
|
|
555
|
-
const exists = yield*
|
|
556
|
-
() =>
|
|
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*
|
|
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*
|
|
564
|
-
() =>
|
|
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*
|
|
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*
|
|
575
|
-
() =>
|
|
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*
|
|
615
|
+
yield* Effect3.tryPromise(() => fs4.rename(agentTodoPath, backupPath));
|
|
580
616
|
deletedCount++;
|
|
581
617
|
}
|
|
582
618
|
}
|
|
583
619
|
return { deletedCount };
|
|
584
620
|
});
|
|
585
|
-
var findOrphanTodos = () =>
|
|
621
|
+
var findOrphanTodos = () => Effect3.gen(function* () {
|
|
586
622
|
const todosDir = getTodosDir();
|
|
587
623
|
const sessionsDir = getSessionsDir();
|
|
588
|
-
const [todosExists, sessionsExists] = yield*
|
|
589
|
-
|
|
590
|
-
() =>
|
|
624
|
+
const [todosExists, sessionsExists] = yield* Effect3.all([
|
|
625
|
+
Effect3.tryPromise(
|
|
626
|
+
() => fs4.access(todosDir).then(() => true).catch(() => false)
|
|
591
627
|
),
|
|
592
|
-
|
|
593
|
-
() =>
|
|
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*
|
|
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*
|
|
601
|
-
() =>
|
|
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*
|
|
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 = () =>
|
|
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*
|
|
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*
|
|
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
|
|
643
|
-
import * as
|
|
678
|
+
import { Effect as Effect4 } from "effect";
|
|
679
|
+
import * as fs5 from "fs/promises";
|
|
644
680
|
import * as path4 from "path";
|
|
645
|
-
var listProjects =
|
|
681
|
+
var listProjects = Effect4.gen(function* () {
|
|
646
682
|
const sessionsDir = getSessionsDir();
|
|
647
|
-
const exists = yield*
|
|
648
|
-
() =>
|
|
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*
|
|
654
|
-
const projects = yield*
|
|
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) =>
|
|
692
|
+
(entry) => Effect4.gen(function* () {
|
|
657
693
|
const projectPath = path4.join(sessionsDir, entry.name);
|
|
658
|
-
const files = yield*
|
|
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
|
|
675
|
-
import * as
|
|
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
|
-
|
|
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
|
|
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*
|
|
901
|
+
yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
|
|
697
902
|
return { success: true };
|
|
698
903
|
});
|
|
699
|
-
var listSessions = (projectName) =>
|
|
904
|
+
var listSessions = (projectName) => Effect5.gen(function* () {
|
|
700
905
|
const projectPath = path5.join(getSessionsDir(), projectName);
|
|
701
|
-
const files = yield*
|
|
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*
|
|
908
|
+
const sessions = yield* Effect5.all(
|
|
704
909
|
sessionFiles.map(
|
|
705
|
-
(file) =>
|
|
910
|
+
(file) => Effect5.gen(function* () {
|
|
706
911
|
const filePath = path5.join(projectPath, file);
|
|
707
|
-
const
|
|
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) =>
|
|
963
|
+
var readSession = (projectName, sessionId) => Effect5.gen(function* () {
|
|
761
964
|
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
762
|
-
|
|
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) =>
|
|
967
|
+
var deleteMessage = (projectName, sessionId, messageUuid, targetType) => Effect5.gen(function* () {
|
|
767
968
|
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
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*
|
|
788
|
-
return { success: true, deletedMessage:
|
|
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) =>
|
|
978
|
+
var restoreMessage = (projectName, sessionId, message, index) => Effect5.gen(function* () {
|
|
791
979
|
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
792
|
-
const
|
|
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*
|
|
995
|
+
yield* Effect5.tryPromise(() => fs6.writeFile(filePath, newContent, "utf-8"));
|
|
810
996
|
return { success: true };
|
|
811
997
|
});
|
|
812
|
-
var deleteSession = (projectName, sessionId) =>
|
|
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*
|
|
1003
|
+
const stat4 = yield* Effect5.tryPromise(() => fs6.stat(filePath));
|
|
818
1004
|
if (stat4.size === 0) {
|
|
819
|
-
yield*
|
|
1005
|
+
yield* Effect5.tryPromise(() => fs6.unlink(filePath));
|
|
820
1006
|
const agentBackupDir2 = path5.join(projectPath, ".bak");
|
|
821
|
-
yield*
|
|
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*
|
|
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*
|
|
1018
|
+
yield* Effect5.tryPromise(() => fs6.mkdir(backupDir, { recursive: true }));
|
|
833
1019
|
const agentBackupDir = path5.join(projectPath, ".bak");
|
|
834
|
-
yield*
|
|
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*
|
|
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*
|
|
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) =>
|
|
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*
|
|
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
|
|
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*
|
|
879
|
-
const projectFiles = yield*
|
|
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
|
|
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
|
|
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*
|
|
1096
|
+
yield* Effect5.tryPromise(() => fs6.writeFile(summaryFilePath, newSummaryContent, "utf-8"));
|
|
915
1097
|
} else {
|
|
916
|
-
const
|
|
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*
|
|
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) =>
|
|
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*
|
|
949
|
-
() =>
|
|
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*
|
|
955
|
-
() =>
|
|
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*
|
|
1140
|
+
yield* Effect5.tryPromise(() => fs6.mkdir(targetPath, { recursive: true }));
|
|
961
1141
|
const linkedAgents = yield* findLinkedAgents(sourceProject, sessionId);
|
|
962
|
-
yield*
|
|
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*
|
|
967
|
-
() =>
|
|
1146
|
+
const agentExists = yield* Effect5.tryPromise(
|
|
1147
|
+
() => fs6.access(sourceAgentFile).then(() => true).catch(() => false)
|
|
968
1148
|
);
|
|
969
1149
|
if (agentExists) {
|
|
970
|
-
yield*
|
|
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) =>
|
|
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
|
|
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*
|
|
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*
|
|
1030
|
-
const agentFiles = yield*
|
|
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*
|
|
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
|
|
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 =
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
});
|
|
1223
|
+
const updatedAgentMessages = agentMessages.map(
|
|
1224
|
+
(msg) => JSON.stringify({ ...msg, sessionId: newSessionId })
|
|
1225
|
+
);
|
|
1048
1226
|
const updatedAgentContent = updatedAgentMessages.join("\n") + "\n";
|
|
1049
|
-
yield*
|
|
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
|
|
1064
|
-
import * as
|
|
1241
|
+
import { Effect as Effect6 } from "effect";
|
|
1242
|
+
import * as fs7 from "fs/promises";
|
|
1065
1243
|
import * as path6 from "path";
|
|
1066
|
-
var
|
|
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*
|
|
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
|
|
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*
|
|
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*
|
|
1308
|
+
const otherContent = yield* Effect6.tryPromise(() => fs7.readFile(otherFilePath, "utf-8"));
|
|
1093
1309
|
const otherLines = otherContent.trim().split("\n").filter(Boolean);
|
|
1094
|
-
for (
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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*
|
|
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
|
|
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) =>
|
|
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*
|
|
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*
|
|
1412
|
+
yield* Effect6.all(
|
|
1196
1413
|
sessionFiles.map(
|
|
1197
|
-
(file) =>
|
|
1414
|
+
(file) => Effect6.gen(function* () {
|
|
1198
1415
|
const filePath = path6.join(projectPath, file);
|
|
1199
1416
|
try {
|
|
1200
|
-
const stat4 = yield*
|
|
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*
|
|
1428
|
+
yield* Effect6.all(
|
|
1212
1429
|
allJsonlFiles.map(
|
|
1213
|
-
(file) =>
|
|
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*
|
|
1434
|
+
const content = yield* Effect6.tryPromise(() => fs7.readFile(filePath, "utf-8"));
|
|
1218
1435
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
1219
|
-
for (
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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*
|
|
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
|
|
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
|
|
1335
|
-
import * as
|
|
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) =>
|
|
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 = {}) =>
|
|
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*
|
|
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
|
|
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*
|
|
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) =>
|
|
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*
|
|
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 = {}) =>
|
|
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
|
|
1655
|
-
import * as
|
|
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) =>
|
|
1833
|
+
var cleanInvalidMessages = (projectName, sessionId) => Effect8.gen(function* () {
|
|
1658
1834
|
const filePath = path8.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1659
|
-
const content = yield*
|
|
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
|
|
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*
|
|
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) =>
|
|
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*
|
|
1880
|
+
const results = yield* Effect8.all(
|
|
1705
1881
|
targetProjects.map(
|
|
1706
|
-
(project) =>
|
|
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) =>
|
|
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*
|
|
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
|
|
1815
|
-
import * as
|
|
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
|
|
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
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
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
|
-
|
|
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
|
|
1888
|
-
var getSessionFiles = (projectName, sessionId) =>
|
|
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
|
|
1943
|
-
import * as
|
|
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) =>
|
|
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*
|
|
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) =>
|
|
2175
|
+
var hasSessionsIndex = (projectName) => Effect11.gen(function* () {
|
|
1976
2176
|
const indexPath = path10.join(getSessionsDir(), projectName, "sessions-index.json");
|
|
1977
2177
|
try {
|
|
1978
|
-
yield*
|
|
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
|
-
|
|
2240
|
+
tryParseJsonLine,
|
|
2241
|
+
updateSessionSummary,
|
|
2242
|
+
validateChain,
|
|
2243
|
+
validateToolUseResult
|
|
2037
2244
|
};
|
|
2038
2245
|
//# sourceMappingURL=index.js.map
|