@claude-sessions/core 0.3.6 → 0.3.7
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 +70 -4
- package/dist/index.js +430 -30
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-C2fzbmg9.d.ts → types-Cz8chaYQ.d.ts} +110 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MessagePayload, a as Message, P as Project, T as TodoItem, F as FileChange, b as MoveSessionResult,
|
|
2
|
-
export {
|
|
1
|
+
import { M as MessagePayload, a as Message, P as Project, T as TodoItem, F as FileChange, C as CompressSessionOptions, S as SummarizeSessionOptions, b as ConversationLine, c as MoveSessionResult, d as SearchResult, e as SummaryInfo, A as AgentInfo } from './types-Cz8chaYQ.js';
|
|
2
|
+
export { l as CleanupPreview, k as ClearSessionsResult, s as CompressSessionResult, f as ContentItem, D as DeleteSessionResult, t as ProjectKnowledge, n as ProjectTreeData, R as RenameSessionResult, o as ResumeSessionOptions, p as ResumeSessionResult, r as SessionAnalysis, i as SessionFilesSummary, g as SessionMeta, h as SessionTodos, m as SessionTreeData, j as SplitSessionResult, u as SummarizeSessionResult, q as ToolUsageStats } from './types-Cz8chaYQ.js';
|
|
3
3
|
import * as effect_Cause from 'effect/Cause';
|
|
4
4
|
import { Effect } from 'effect';
|
|
5
5
|
|
|
@@ -11,7 +11,9 @@ interface FileSystem {
|
|
|
11
11
|
readFileSync: (path: string, encoding: 'utf-8') => string;
|
|
12
12
|
readdirSync: (path: string) => string[];
|
|
13
13
|
}
|
|
14
|
-
/** Get Claude sessions directory (~/.claude/projects)
|
|
14
|
+
/** Get Claude sessions directory (~/.claude/projects)
|
|
15
|
+
* Can be overridden with CLAUDE_SESSIONS_DIR environment variable for testing
|
|
16
|
+
*/
|
|
15
17
|
declare const getSessionsDir: () => string;
|
|
16
18
|
/** Get Claude todos directory (~/.claude/todos) */
|
|
17
19
|
declare const getTodosDir: () => string;
|
|
@@ -94,6 +96,11 @@ declare const findOrphanAgents: (projectName: string) => Effect.Effect<{
|
|
|
94
96
|
declare const deleteOrphanAgents: (projectName: string) => Effect.Effect<{
|
|
95
97
|
success: boolean;
|
|
96
98
|
deletedAgents: string[];
|
|
99
|
+
backedUpAgents: string[];
|
|
100
|
+
cleanedFolders: string[];
|
|
101
|
+
deletedCount: number;
|
|
102
|
+
backedUpCount: number;
|
|
103
|
+
cleanedFolderCount: number;
|
|
97
104
|
count: number;
|
|
98
105
|
}, effect_Cause.UnknownException, never>;
|
|
99
106
|
declare const loadAgentMessages: (projectName: string, _sessionId: string, // Reserved for future validation
|
|
@@ -168,6 +175,34 @@ declare const getSessionFiles: (projectName: string, sessionId: string) => Effec
|
|
|
168
175
|
files: FileChange[];
|
|
169
176
|
totalChanges: number;
|
|
170
177
|
}, effect_Cause.UnknownException, never>;
|
|
178
|
+
declare const analyzeSession: (projectName: string, sessionId: string) => Effect.Effect<{
|
|
179
|
+
sessionId: string;
|
|
180
|
+
projectName: string;
|
|
181
|
+
durationMinutes: number;
|
|
182
|
+
stats: {
|
|
183
|
+
totalMessages: number;
|
|
184
|
+
userMessages: number;
|
|
185
|
+
assistantMessages: number;
|
|
186
|
+
summaryCount: number;
|
|
187
|
+
snapshotCount: number;
|
|
188
|
+
};
|
|
189
|
+
toolUsage: {
|
|
190
|
+
name: string;
|
|
191
|
+
count: number;
|
|
192
|
+
errorCount: number;
|
|
193
|
+
}[];
|
|
194
|
+
filesChanged: string[];
|
|
195
|
+
patterns: {
|
|
196
|
+
type: string;
|
|
197
|
+
description: string;
|
|
198
|
+
count: number;
|
|
199
|
+
}[];
|
|
200
|
+
milestones: {
|
|
201
|
+
timestamp?: string;
|
|
202
|
+
description: string;
|
|
203
|
+
messageUuid?: string;
|
|
204
|
+
}[];
|
|
205
|
+
}, effect_Cause.UnknownException, never>;
|
|
171
206
|
declare const moveSession: (sourceProject: string, sessionId: string, targetProject: string) => Effect.Effect<MoveSessionResult, Error>;
|
|
172
207
|
declare const splitSession: (projectName: string, sessionId: string, splitAtMessageUuid: string) => Effect.Effect<{
|
|
173
208
|
success: false;
|
|
@@ -277,6 +312,37 @@ declare const loadProjectTreeData: (projectName: string) => Effect.Effect<{
|
|
|
277
312
|
declare const updateSessionSummary: (projectName: string, sessionId: string, newSummary: string) => Effect.Effect<{
|
|
278
313
|
success: boolean;
|
|
279
314
|
}, effect_Cause.UnknownException, never>;
|
|
315
|
+
declare const compressSession: (projectName: string, sessionId: string, options?: CompressSessionOptions) => Effect.Effect<{
|
|
316
|
+
success: true;
|
|
317
|
+
originalSize: number;
|
|
318
|
+
compressedSize: number;
|
|
319
|
+
removedSnapshots: number;
|
|
320
|
+
truncatedOutputs: number;
|
|
321
|
+
}, effect_Cause.UnknownException, never>;
|
|
322
|
+
declare const extractProjectKnowledge: (projectName: string, sessionIds?: string[]) => Effect.Effect<{
|
|
323
|
+
projectName: string;
|
|
324
|
+
patterns: never[];
|
|
325
|
+
hotFiles: {
|
|
326
|
+
path: string;
|
|
327
|
+
modifyCount: number;
|
|
328
|
+
lastModified: string | undefined;
|
|
329
|
+
}[];
|
|
330
|
+
workflows: {
|
|
331
|
+
sequence: string[];
|
|
332
|
+
count: number;
|
|
333
|
+
}[];
|
|
334
|
+
decisions: {
|
|
335
|
+
context: string;
|
|
336
|
+
decision: string;
|
|
337
|
+
sessionId: string;
|
|
338
|
+
}[];
|
|
339
|
+
}, effect_Cause.UnknownException, never>;
|
|
340
|
+
declare const summarizeSession: (projectName: string, sessionId: string, options?: SummarizeSessionOptions) => Effect.Effect<{
|
|
341
|
+
sessionId: string;
|
|
342
|
+
projectName: string;
|
|
343
|
+
lines: ConversationLine[];
|
|
344
|
+
formatted: string;
|
|
345
|
+
}, effect_Cause.UnknownException, never>;
|
|
280
346
|
|
|
281
347
|
/**
|
|
282
348
|
* Simple logger abstraction for Claude Sessions
|
|
@@ -312,4 +378,4 @@ declare const getLogger: () => Logger;
|
|
|
312
378
|
*/
|
|
313
379
|
declare const createLogger: (namespace: string) => Logger;
|
|
314
380
|
|
|
315
|
-
export { AgentInfo, FileChange, type Logger, Message, MessagePayload, MoveSessionResult, Project, SearchResult, SummaryInfo, TodoItem, clearSessions, createLogger, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, getDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, maskHomePath, moveSession, pathToFolderName, previewCleanup, readSession, renameSession, restoreMessage, searchSessions, sessionHasTodos, setLogger, sortProjects, splitSession, updateSessionSummary };
|
|
381
|
+
export { AgentInfo, CompressSessionOptions, ConversationLine, FileChange, type Logger, Message, MessagePayload, MoveSessionResult, Project, SearchResult, SummarizeSessionOptions, SummaryInfo, TodoItem, analyzeSession, clearSessions, compressSession, createLogger, deleteLinkedTodos, deleteMessage, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractProjectKnowledge, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, getDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionsDir, getTodosDir, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, maskHomePath, moveSession, pathToFolderName, previewCleanup, readSession, renameSession, restoreMessage, searchSessions, sessionHasTodos, setLogger, sortProjects, splitSession, summarizeSession, updateSessionSummary };
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var createLogger = (namespace) => ({
|
|
|
24
24
|
|
|
25
25
|
// src/paths.ts
|
|
26
26
|
var log = createLogger("paths");
|
|
27
|
-
var getSessionsDir = () => path.join(os.homedir(), ".claude", "projects");
|
|
27
|
+
var getSessionsDir = () => process.env.CLAUDE_SESSIONS_DIR || path.join(os.homedir(), ".claude", "projects");
|
|
28
28
|
var getTodosDir = () => path.join(os.homedir(), ".claude", "todos");
|
|
29
29
|
var extractCwdFromContent = (content) => {
|
|
30
30
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
@@ -80,22 +80,22 @@ var pathToFolderName = (absolutePath) => {
|
|
|
80
80
|
return convertNonAscii(absolutePath).replace(/^\//g, "-").replace(/\/\./g, "--").replace(/\//g, "-").replace(/\./g, "-");
|
|
81
81
|
};
|
|
82
82
|
var tryGetCwdFromFile = (filePath, fileSystem = fs, logger2 = log) => {
|
|
83
|
-
const
|
|
83
|
+
const basename3 = path.basename(filePath);
|
|
84
84
|
try {
|
|
85
85
|
const content = fileSystem.readFileSync(filePath, "utf-8");
|
|
86
86
|
const cwd = extractCwdFromContent(content);
|
|
87
87
|
if (cwd === null) {
|
|
88
88
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
89
89
|
if (lines.length === 0) {
|
|
90
|
-
logger2.debug(`tryGetCwdFromFile: ${
|
|
90
|
+
logger2.debug(`tryGetCwdFromFile: ${basename3} -> empty file`);
|
|
91
91
|
} else {
|
|
92
|
-
logger2.debug(`tryGetCwdFromFile: ${
|
|
92
|
+
logger2.debug(`tryGetCwdFromFile: ${basename3} -> no cwd found in ${lines.length} lines`);
|
|
93
93
|
}
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
96
|
return cwd;
|
|
97
97
|
} catch (e) {
|
|
98
|
-
logger2.warn(`tryGetCwdFromFile: ${
|
|
98
|
+
logger2.warn(`tryGetCwdFromFile: ${basename3} -> read error: ${e}`);
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
};
|
|
@@ -298,46 +298,127 @@ var findLinkedAgents = (projectName, sessionId) => Effect.gen(function* () {
|
|
|
298
298
|
}
|
|
299
299
|
return linkedAgents;
|
|
300
300
|
});
|
|
301
|
-
var
|
|
301
|
+
var findOrphanAgentsWithPaths = (projectName) => Effect.gen(function* () {
|
|
302
302
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
303
303
|
const files = yield* Effect.tryPromise(() => fs2.readdir(projectPath));
|
|
304
304
|
const sessionIds = new Set(
|
|
305
305
|
files.filter((f) => !f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => f.replace(".jsonl", ""))
|
|
306
306
|
);
|
|
307
|
-
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
308
307
|
const orphanAgents = [];
|
|
309
|
-
|
|
308
|
+
const checkAgentFile = async (filePath) => {
|
|
309
|
+
try {
|
|
310
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
311
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
312
|
+
const firstLine = lines[0];
|
|
313
|
+
if (!firstLine) return null;
|
|
314
|
+
const parsed = JSON.parse(firstLine);
|
|
315
|
+
if (parsed.sessionId && !sessionIds.has(parsed.sessionId)) {
|
|
316
|
+
const fileName = path2.basename(filePath);
|
|
317
|
+
return {
|
|
318
|
+
agentId: fileName.replace(".jsonl", ""),
|
|
319
|
+
sessionId: parsed.sessionId,
|
|
320
|
+
lineCount: lines.length
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
};
|
|
327
|
+
const rootAgentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl"));
|
|
328
|
+
for (const agentFile of rootAgentFiles) {
|
|
310
329
|
const filePath = path2.join(projectPath, agentFile);
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
330
|
+
const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
|
|
331
|
+
if (orphan) {
|
|
332
|
+
orphanAgents.push({ ...orphan, filePath });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const entry of files) {
|
|
336
|
+
const entryPath = path2.join(projectPath, entry);
|
|
337
|
+
const stat3 = yield* Effect.tryPromise(() => fs2.stat(entryPath).catch(() => null));
|
|
338
|
+
if (stat3?.isDirectory() && !entry.startsWith(".")) {
|
|
339
|
+
const subagentsPath = path2.join(entryPath, "subagents");
|
|
340
|
+
const subagentsExists = yield* Effect.tryPromise(
|
|
341
|
+
() => fs2.stat(subagentsPath).then(() => true).catch(() => false)
|
|
342
|
+
);
|
|
343
|
+
if (subagentsExists) {
|
|
344
|
+
const subagentFiles = yield* Effect.tryPromise(
|
|
345
|
+
() => fs2.readdir(subagentsPath).catch(() => [])
|
|
346
|
+
);
|
|
347
|
+
for (const subagentFile of subagentFiles) {
|
|
348
|
+
if (subagentFile.startsWith("agent-") && subagentFile.endsWith(".jsonl")) {
|
|
349
|
+
const filePath = path2.join(subagentsPath, subagentFile);
|
|
350
|
+
const orphan = yield* Effect.tryPromise(() => checkAgentFile(filePath));
|
|
351
|
+
if (orphan) {
|
|
352
|
+
orphanAgents.push({ ...orphan, filePath });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
321
355
|
}
|
|
322
|
-
} catch {
|
|
323
356
|
}
|
|
324
357
|
}
|
|
325
358
|
}
|
|
326
359
|
return orphanAgents;
|
|
327
360
|
});
|
|
361
|
+
var findOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
362
|
+
const orphans = yield* findOrphanAgentsWithPaths(projectName);
|
|
363
|
+
return orphans.map(({ agentId, sessionId }) => ({ agentId, sessionId }));
|
|
364
|
+
});
|
|
328
365
|
var deleteOrphanAgents = (projectName) => Effect.gen(function* () {
|
|
329
366
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
330
|
-
const orphans = yield*
|
|
331
|
-
const backupDir = path2.join(projectPath, ".bak");
|
|
332
|
-
yield* Effect.tryPromise(() => fs2.mkdir(backupDir, { recursive: true }));
|
|
367
|
+
const orphans = yield* findOrphanAgentsWithPaths(projectName);
|
|
333
368
|
const deletedAgents = [];
|
|
369
|
+
const backedUpAgents = [];
|
|
370
|
+
const cleanedFolders = [];
|
|
371
|
+
let backupDirCreated = false;
|
|
372
|
+
const foldersToCheck = /* @__PURE__ */ new Set();
|
|
334
373
|
for (const orphan of orphans) {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
374
|
+
const parentDir = path2.dirname(orphan.filePath);
|
|
375
|
+
if (parentDir.endsWith("/subagents") || parentDir.endsWith("\\subagents")) {
|
|
376
|
+
foldersToCheck.add(parentDir);
|
|
377
|
+
}
|
|
378
|
+
if (orphan.lineCount <= 2) {
|
|
379
|
+
yield* Effect.tryPromise(() => fs2.unlink(orphan.filePath));
|
|
380
|
+
deletedAgents.push(orphan.agentId);
|
|
381
|
+
} else {
|
|
382
|
+
if (!backupDirCreated) {
|
|
383
|
+
const backupDir2 = path2.join(projectPath, ".bak");
|
|
384
|
+
yield* Effect.tryPromise(() => fs2.mkdir(backupDir2, { recursive: true }));
|
|
385
|
+
backupDirCreated = true;
|
|
386
|
+
}
|
|
387
|
+
const backupDir = path2.join(projectPath, ".bak");
|
|
388
|
+
const agentBackupPath = path2.join(backupDir, `${orphan.agentId}.jsonl`);
|
|
389
|
+
yield* Effect.tryPromise(() => fs2.rename(orphan.filePath, agentBackupPath));
|
|
390
|
+
backedUpAgents.push(orphan.agentId);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const subagentsDir of foldersToCheck) {
|
|
394
|
+
const isEmpty = yield* Effect.tryPromise(async () => {
|
|
395
|
+
const files = await fs2.readdir(subagentsDir);
|
|
396
|
+
return files.length === 0;
|
|
397
|
+
});
|
|
398
|
+
if (isEmpty) {
|
|
399
|
+
yield* Effect.tryPromise(() => fs2.rmdir(subagentsDir));
|
|
400
|
+
cleanedFolders.push(subagentsDir);
|
|
401
|
+
const sessionDir = path2.dirname(subagentsDir);
|
|
402
|
+
const sessionDirEmpty = yield* Effect.tryPromise(async () => {
|
|
403
|
+
const files = await fs2.readdir(sessionDir);
|
|
404
|
+
return files.length === 0;
|
|
405
|
+
});
|
|
406
|
+
if (sessionDirEmpty) {
|
|
407
|
+
yield* Effect.tryPromise(() => fs2.rmdir(sessionDir));
|
|
408
|
+
cleanedFolders.push(sessionDir);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
339
411
|
}
|
|
340
|
-
return {
|
|
412
|
+
return {
|
|
413
|
+
success: true,
|
|
414
|
+
deletedAgents,
|
|
415
|
+
backedUpAgents,
|
|
416
|
+
cleanedFolders,
|
|
417
|
+
deletedCount: deletedAgents.length,
|
|
418
|
+
backedUpCount: backedUpAgents.length,
|
|
419
|
+
cleanedFolderCount: cleanedFolders.length,
|
|
420
|
+
count: deletedAgents.length + backedUpAgents.length
|
|
421
|
+
};
|
|
341
422
|
});
|
|
342
423
|
var loadAgentMessages = (projectName, _sessionId, agentId) => Effect.gen(function* () {
|
|
343
424
|
const projectPath = path2.join(getSessionsDir(), projectName);
|
|
@@ -688,8 +769,8 @@ var deleteSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
688
769
|
const projectPath = path4.join(sessionsDir, projectName);
|
|
689
770
|
const filePath = path4.join(projectPath, `${sessionId}.jsonl`);
|
|
690
771
|
const linkedAgents = yield* findLinkedAgents(projectName, sessionId);
|
|
691
|
-
const
|
|
692
|
-
if (
|
|
772
|
+
const stat3 = yield* Effect3.tryPromise(() => fs4.stat(filePath));
|
|
773
|
+
if (stat3.size === 0) {
|
|
693
774
|
yield* Effect3.tryPromise(() => fs4.unlink(filePath));
|
|
694
775
|
const agentBackupDir2 = path4.join(projectPath, ".bak");
|
|
695
776
|
yield* Effect3.tryPromise(() => fs4.mkdir(agentBackupDir2, { recursive: true }));
|
|
@@ -865,6 +946,137 @@ var getSessionFiles = (projectName, sessionId) => Effect3.gen(function* () {
|
|
|
865
946
|
totalChanges: fileChanges.length
|
|
866
947
|
};
|
|
867
948
|
});
|
|
949
|
+
var analyzeSession = (projectName, sessionId) => Effect3.gen(function* () {
|
|
950
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
951
|
+
let userMessages = 0;
|
|
952
|
+
let assistantMessages = 0;
|
|
953
|
+
let summaryCount = 0;
|
|
954
|
+
let snapshotCount = 0;
|
|
955
|
+
const toolUsageMap = /* @__PURE__ */ new Map();
|
|
956
|
+
const filesChanged = /* @__PURE__ */ new Set();
|
|
957
|
+
const patterns = [];
|
|
958
|
+
const milestones = [];
|
|
959
|
+
let firstTimestamp;
|
|
960
|
+
let lastTimestamp;
|
|
961
|
+
for (const msg of messages) {
|
|
962
|
+
if (msg.timestamp) {
|
|
963
|
+
if (!firstTimestamp) firstTimestamp = msg.timestamp;
|
|
964
|
+
lastTimestamp = msg.timestamp;
|
|
965
|
+
}
|
|
966
|
+
if (msg.type === "user") {
|
|
967
|
+
userMessages++;
|
|
968
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
969
|
+
if (content.toLowerCase().includes("commit") || content.toLowerCase().includes("\uC644\uB8CC")) {
|
|
970
|
+
milestones.push({
|
|
971
|
+
timestamp: msg.timestamp,
|
|
972
|
+
description: `User checkpoint: ${content.slice(0, 50)}...`,
|
|
973
|
+
messageUuid: msg.uuid
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
} else if (msg.type === "assistant") {
|
|
977
|
+
assistantMessages++;
|
|
978
|
+
if (msg.message?.content && Array.isArray(msg.message.content)) {
|
|
979
|
+
for (const item of msg.message.content) {
|
|
980
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
981
|
+
const toolUse = item;
|
|
982
|
+
const toolName = toolUse.name ?? "unknown";
|
|
983
|
+
const existing = toolUsageMap.get(toolName) ?? { count: 0, errorCount: 0 };
|
|
984
|
+
existing.count++;
|
|
985
|
+
toolUsageMap.set(toolName, existing);
|
|
986
|
+
if ((toolName === "Write" || toolName === "Edit") && toolUse.input?.file_path) {
|
|
987
|
+
filesChanged.add(toolUse.input.file_path);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} else if (msg.type === "summary") {
|
|
993
|
+
summaryCount++;
|
|
994
|
+
if (msg.summary) {
|
|
995
|
+
milestones.push({
|
|
996
|
+
timestamp: msg.timestamp,
|
|
997
|
+
description: `Summary: ${msg.summary.slice(0, 100)}...`,
|
|
998
|
+
messageUuid: msg.uuid
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
} else if (msg.type === "file-history-snapshot") {
|
|
1002
|
+
snapshotCount++;
|
|
1003
|
+
const snapshot = msg;
|
|
1004
|
+
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1005
|
+
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1006
|
+
filesChanged.add(filePath);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
for (const msg of messages) {
|
|
1012
|
+
if (msg.type === "user" && msg.content && Array.isArray(msg.content)) {
|
|
1013
|
+
for (const item of msg.content) {
|
|
1014
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_result" && "is_error" in item && item.is_error) {
|
|
1015
|
+
const toolResultItem = item;
|
|
1016
|
+
const toolUseId = toolResultItem.tool_use_id;
|
|
1017
|
+
if (toolUseId) {
|
|
1018
|
+
for (const prevMsg of messages) {
|
|
1019
|
+
if (prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
|
1020
|
+
for (const prevItem of prevMsg.message.content) {
|
|
1021
|
+
if (prevItem && typeof prevItem === "object" && "type" in prevItem && prevItem.type === "tool_use" && "id" in prevItem && prevItem.id === toolUseId) {
|
|
1022
|
+
const toolName = prevItem.name ?? "unknown";
|
|
1023
|
+
const existing = toolUsageMap.get(toolName);
|
|
1024
|
+
if (existing) {
|
|
1025
|
+
existing.errorCount++;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
let durationMinutes = 0;
|
|
1037
|
+
if (firstTimestamp && lastTimestamp) {
|
|
1038
|
+
const first = new Date(firstTimestamp).getTime();
|
|
1039
|
+
const last = new Date(lastTimestamp).getTime();
|
|
1040
|
+
durationMinutes = Math.round((last - first) / 1e3 / 60);
|
|
1041
|
+
}
|
|
1042
|
+
const toolUsageArray = Array.from(toolUsageMap.entries()).map(([name, stats]) => ({
|
|
1043
|
+
name,
|
|
1044
|
+
count: stats.count,
|
|
1045
|
+
errorCount: stats.errorCount
|
|
1046
|
+
}));
|
|
1047
|
+
for (const tool of toolUsageArray) {
|
|
1048
|
+
if (tool.count >= 3 && tool.errorCount / tool.count > 0.3) {
|
|
1049
|
+
patterns.push({
|
|
1050
|
+
type: "high_error_rate",
|
|
1051
|
+
description: `${tool.name} had ${tool.errorCount}/${tool.count} errors (${Math.round(tool.errorCount / tool.count * 100)}%)`,
|
|
1052
|
+
count: tool.errorCount
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (snapshotCount > 10) {
|
|
1057
|
+
patterns.push({
|
|
1058
|
+
type: "many_snapshots",
|
|
1059
|
+
description: `${snapshotCount} file-history-snapshots could be compressed`,
|
|
1060
|
+
count: snapshotCount
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
sessionId,
|
|
1065
|
+
projectName,
|
|
1066
|
+
durationMinutes,
|
|
1067
|
+
stats: {
|
|
1068
|
+
totalMessages: messages.length,
|
|
1069
|
+
userMessages,
|
|
1070
|
+
assistantMessages,
|
|
1071
|
+
summaryCount,
|
|
1072
|
+
snapshotCount
|
|
1073
|
+
},
|
|
1074
|
+
toolUsage: toolUsageArray.sort((a, b) => b.count - a.count),
|
|
1075
|
+
filesChanged: Array.from(filesChanged),
|
|
1076
|
+
patterns,
|
|
1077
|
+
milestones
|
|
1078
|
+
};
|
|
1079
|
+
});
|
|
868
1080
|
var moveSession = (sourceProject, sessionId, targetProject) => Effect3.gen(function* () {
|
|
869
1081
|
const sessionsDir = getSessionsDir();
|
|
870
1082
|
const sourcePath = path4.join(sessionsDir, sourceProject);
|
|
@@ -1072,7 +1284,7 @@ var clearSessions = (options) => Effect3.gen(function* () {
|
|
|
1072
1284
|
clearEmpty = true,
|
|
1073
1285
|
clearInvalid = true,
|
|
1074
1286
|
skipWithTodos = true,
|
|
1075
|
-
clearOrphanAgents =
|
|
1287
|
+
clearOrphanAgents = true,
|
|
1076
1288
|
clearOrphanTodos = false
|
|
1077
1289
|
} = options;
|
|
1078
1290
|
const projects = yield* listProjects;
|
|
@@ -1431,8 +1643,194 @@ var updateSessionSummary = (projectName, sessionId, newSummary) => Effect3.gen(f
|
|
|
1431
1643
|
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
1432
1644
|
return { success: true };
|
|
1433
1645
|
});
|
|
1646
|
+
var compressSession = (projectName, sessionId, options = {}) => Effect3.gen(function* () {
|
|
1647
|
+
const { keepSnapshots = "first_last", maxToolOutputLength = 5e3 } = options;
|
|
1648
|
+
const filePath = path4.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
1649
|
+
const content = yield* Effect3.tryPromise(() => fs4.readFile(filePath, "utf-8"));
|
|
1650
|
+
const originalSize = Buffer.byteLength(content, "utf-8");
|
|
1651
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1652
|
+
const messages = lines.map((line) => JSON.parse(line));
|
|
1653
|
+
let removedSnapshots = 0;
|
|
1654
|
+
let truncatedOutputs = 0;
|
|
1655
|
+
const snapshotIndices = [];
|
|
1656
|
+
messages.forEach((msg, idx) => {
|
|
1657
|
+
if (msg.type === "file-history-snapshot") {
|
|
1658
|
+
snapshotIndices.push(idx);
|
|
1659
|
+
}
|
|
1660
|
+
});
|
|
1661
|
+
const filteredMessages = messages.filter((msg, idx) => {
|
|
1662
|
+
if (msg.type === "file-history-snapshot") {
|
|
1663
|
+
if (keepSnapshots === "none") {
|
|
1664
|
+
removedSnapshots++;
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
if (keepSnapshots === "first_last") {
|
|
1668
|
+
const isFirst = idx === snapshotIndices[0];
|
|
1669
|
+
const isLast = idx === snapshotIndices[snapshotIndices.length - 1];
|
|
1670
|
+
if (!isFirst && !isLast) {
|
|
1671
|
+
removedSnapshots++;
|
|
1672
|
+
return false;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return true;
|
|
1677
|
+
});
|
|
1678
|
+
for (const msg of filteredMessages) {
|
|
1679
|
+
if (msg.type === "user" && Array.isArray(msg.content)) {
|
|
1680
|
+
for (const item of msg.content) {
|
|
1681
|
+
if (item.type === "tool_result" && typeof item.content === "string") {
|
|
1682
|
+
if (maxToolOutputLength > 0 && item.content.length > maxToolOutputLength) {
|
|
1683
|
+
item.content = item.content.slice(0, maxToolOutputLength) + "\n... [truncated]";
|
|
1684
|
+
truncatedOutputs++;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const newContent = filteredMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
1691
|
+
const compressedSize = Buffer.byteLength(newContent, "utf-8");
|
|
1692
|
+
yield* Effect3.tryPromise(() => fs4.writeFile(filePath, newContent, "utf-8"));
|
|
1693
|
+
return {
|
|
1694
|
+
success: true,
|
|
1695
|
+
originalSize,
|
|
1696
|
+
compressedSize,
|
|
1697
|
+
removedSnapshots,
|
|
1698
|
+
truncatedOutputs
|
|
1699
|
+
};
|
|
1700
|
+
});
|
|
1701
|
+
var extractProjectKnowledge = (projectName, sessionIds) => Effect3.gen(function* () {
|
|
1702
|
+
const sessionsDir = getSessionsDir();
|
|
1703
|
+
const projectDir = path4.join(sessionsDir, projectName);
|
|
1704
|
+
let targetSessionIds = sessionIds;
|
|
1705
|
+
if (!targetSessionIds) {
|
|
1706
|
+
const files = yield* Effect3.tryPromise(() => fs4.readdir(projectDir));
|
|
1707
|
+
targetSessionIds = files.filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-")).map((f) => f.replace(".jsonl", ""));
|
|
1708
|
+
}
|
|
1709
|
+
const fileModifyCount = /* @__PURE__ */ new Map();
|
|
1710
|
+
const toolSequences = [];
|
|
1711
|
+
const decisions = [];
|
|
1712
|
+
for (const sessionId of targetSessionIds) {
|
|
1713
|
+
try {
|
|
1714
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1715
|
+
for (const msg of messages) {
|
|
1716
|
+
if (msg.type === "file-history-snapshot") {
|
|
1717
|
+
const snapshot = msg;
|
|
1718
|
+
if (snapshot.snapshot?.trackedFileBackups) {
|
|
1719
|
+
for (const filePath of Object.keys(snapshot.snapshot.trackedFileBackups)) {
|
|
1720
|
+
const existing = fileModifyCount.get(filePath) ?? { count: 0 };
|
|
1721
|
+
existing.count++;
|
|
1722
|
+
existing.lastModified = snapshot.snapshot.timestamp;
|
|
1723
|
+
fileModifyCount.set(filePath, existing);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (msg.type === "assistant" && msg.message?.content && Array.isArray(msg.message.content)) {
|
|
1728
|
+
const tools = [];
|
|
1729
|
+
for (const item of msg.message.content) {
|
|
1730
|
+
if (item && typeof item === "object" && "type" in item && item.type === "tool_use") {
|
|
1731
|
+
const toolUse = item;
|
|
1732
|
+
if (toolUse.name) tools.push(toolUse.name);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (tools.length > 1) {
|
|
1736
|
+
toolSequences.push(tools);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (msg.type === "summary" && msg.summary) {
|
|
1740
|
+
decisions.push({
|
|
1741
|
+
context: "Session summary",
|
|
1742
|
+
decision: msg.summary.slice(0, 200),
|
|
1743
|
+
sessionId
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
} catch {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const hotFiles = Array.from(fileModifyCount.entries()).map(([filePath, data]) => ({
|
|
1752
|
+
path: filePath,
|
|
1753
|
+
modifyCount: data.count,
|
|
1754
|
+
lastModified: data.lastModified
|
|
1755
|
+
})).sort((a, b) => b.modifyCount - a.modifyCount).slice(0, 20);
|
|
1756
|
+
const workflowMap = /* @__PURE__ */ new Map();
|
|
1757
|
+
for (const seq of toolSequences) {
|
|
1758
|
+
const key = seq.join(" -> ");
|
|
1759
|
+
workflowMap.set(key, (workflowMap.get(key) ?? 0) + 1);
|
|
1760
|
+
}
|
|
1761
|
+
const workflows = Array.from(workflowMap.entries()).filter(([, count]) => count >= 2).map(([sequence, count]) => ({
|
|
1762
|
+
sequence: sequence.split(" -> "),
|
|
1763
|
+
count
|
|
1764
|
+
})).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
1765
|
+
return {
|
|
1766
|
+
projectName,
|
|
1767
|
+
patterns: [],
|
|
1768
|
+
hotFiles,
|
|
1769
|
+
workflows,
|
|
1770
|
+
decisions: decisions.slice(0, 20)
|
|
1771
|
+
};
|
|
1772
|
+
});
|
|
1773
|
+
var summarizeSession = (projectName, sessionId, options = {}) => Effect3.gen(function* () {
|
|
1774
|
+
const { limit = 50, maxLength = 100 } = options;
|
|
1775
|
+
const messages = yield* readSession(projectName, sessionId);
|
|
1776
|
+
const lines = [];
|
|
1777
|
+
let count = 0;
|
|
1778
|
+
for (const msg of messages) {
|
|
1779
|
+
if (count >= limit) break;
|
|
1780
|
+
if (msg.type === "user" || msg.type === "human") {
|
|
1781
|
+
let timeStr;
|
|
1782
|
+
if (msg.timestamp) {
|
|
1783
|
+
try {
|
|
1784
|
+
const dt = new Date(msg.timestamp);
|
|
1785
|
+
timeStr = dt.toLocaleString("ko-KR", {
|
|
1786
|
+
month: "2-digit",
|
|
1787
|
+
day: "2-digit",
|
|
1788
|
+
hour: "2-digit",
|
|
1789
|
+
minute: "2-digit",
|
|
1790
|
+
hour12: false
|
|
1791
|
+
});
|
|
1792
|
+
} catch {
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
const text = extractTextContent(msg.message);
|
|
1796
|
+
if (text) {
|
|
1797
|
+
const truncated = truncateText(text, maxLength);
|
|
1798
|
+
lines.push({ role: "user", content: truncated, timestamp: timeStr });
|
|
1799
|
+
count++;
|
|
1800
|
+
}
|
|
1801
|
+
} else if (msg.type === "assistant") {
|
|
1802
|
+
const text = extractTextContent(msg.message);
|
|
1803
|
+
if (text) {
|
|
1804
|
+
const truncated = truncateText(text, maxLength);
|
|
1805
|
+
lines.push({ role: "assistant", content: truncated });
|
|
1806
|
+
count++;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const formatted = lines.map((line) => {
|
|
1811
|
+
if (line.role === "user") {
|
|
1812
|
+
return line.timestamp ? `user [${line.timestamp}]: ${line.content}` : `user: ${line.content}`;
|
|
1813
|
+
}
|
|
1814
|
+
return `assistant: ${line.content}`;
|
|
1815
|
+
}).join("\n");
|
|
1816
|
+
return {
|
|
1817
|
+
sessionId,
|
|
1818
|
+
projectName,
|
|
1819
|
+
lines,
|
|
1820
|
+
formatted
|
|
1821
|
+
};
|
|
1822
|
+
});
|
|
1823
|
+
function truncateText(text, maxLen) {
|
|
1824
|
+
const cleaned = text.replace(/\n/g, " ");
|
|
1825
|
+
if (cleaned.length > maxLen) {
|
|
1826
|
+
return cleaned.slice(0, maxLen) + "...";
|
|
1827
|
+
}
|
|
1828
|
+
return cleaned;
|
|
1829
|
+
}
|
|
1434
1830
|
export {
|
|
1831
|
+
analyzeSession,
|
|
1435
1832
|
clearSessions,
|
|
1833
|
+
compressSession,
|
|
1436
1834
|
createLogger,
|
|
1437
1835
|
deleteLinkedTodos,
|
|
1438
1836
|
deleteMessage,
|
|
@@ -1440,6 +1838,7 @@ export {
|
|
|
1440
1838
|
deleteOrphanTodos,
|
|
1441
1839
|
deleteSession,
|
|
1442
1840
|
displayPathToFolderName,
|
|
1841
|
+
extractProjectKnowledge,
|
|
1443
1842
|
extractTextContent,
|
|
1444
1843
|
extractTitle,
|
|
1445
1844
|
findLinkedAgents,
|
|
@@ -1474,6 +1873,7 @@ export {
|
|
|
1474
1873
|
setLogger,
|
|
1475
1874
|
sortProjects,
|
|
1476
1875
|
splitSession,
|
|
1876
|
+
summarizeSession,
|
|
1477
1877
|
updateSessionSummary
|
|
1478
1878
|
};
|
|
1479
1879
|
//# sourceMappingURL=index.js.map
|