@claude-sessions/core 0.4.0 → 0.4.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 +95 -67
- package/dist/index.js +242 -69
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-BdPaAnP8.d.ts → types-DZsLFIFK.d.ts} +4 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MessagePayload, a as Message, P as Project, S as SummaryInfo,
|
|
2
|
-
export {
|
|
1
|
+
import { M as MessagePayload$1, a as Message, P as Project, S as SummaryInfo, b as SessionTodos, c as MoveSessionResult, A as AgentInfo, d as SessionSortOptions, e as ProjectTreeData, C as CompressSessionOptions, f as SummarizeSessionOptions, g as ConversationLine, h as SearchResult, F as FileChange, i as SessionsIndex, j as SessionIndexEntry } from './types-DZsLFIFK.js';
|
|
2
|
+
export { p as CleanupPreview, o as ClearSessionsResult, v as CompressSessionResult, k as ContentItem, D as DeleteSessionResult, w as ProjectKnowledge, R as RenameSessionResult, r as ResumeSessionOptions, s as ResumeSessionResult, u as SessionAnalysis, m as SessionFilesSummary, l as SessionMeta, y as SessionSortField, z as SessionSortOrder, q as SessionTreeData, n as SplitSessionResult, x as SummarizeSessionResult, T as TodoItem, t as ToolUsageStats } from './types-DZsLFIFK.js';
|
|
3
3
|
import * as effect_Cause from 'effect/Cause';
|
|
4
4
|
import { Effect } from 'effect';
|
|
5
5
|
|
|
@@ -62,13 +62,22 @@ declare const findProjectByWorkspacePath: (workspacePath: string, projectNames:
|
|
|
62
62
|
* Utility functions for message processing
|
|
63
63
|
*/
|
|
64
64
|
|
|
65
|
-
declare const extractTextContent: (message: MessagePayload | undefined) => string;
|
|
65
|
+
declare const extractTextContent: (message: MessagePayload$1 | undefined) => string;
|
|
66
|
+
/**
|
|
67
|
+
* Parse command message content (e.g., slash commands like /commit)
|
|
68
|
+
* Returns the command name and message extracted from XML tags
|
|
69
|
+
*/
|
|
70
|
+
declare const parseCommandMessage: (content?: string) => {
|
|
71
|
+
name: string;
|
|
72
|
+
message: string;
|
|
73
|
+
};
|
|
66
74
|
declare const extractTitle: (text: string) => string;
|
|
67
75
|
declare const isInvalidApiKeyMessage: (msg: Message) => boolean;
|
|
68
76
|
declare const isContinuationSummary: (msg: Message) => boolean;
|
|
69
77
|
/**
|
|
70
78
|
* Get display title with fallback logic
|
|
71
79
|
* Priority: customTitle > currentSummary (truncated) > title > fallback
|
|
80
|
+
* Also handles slash command format in title
|
|
72
81
|
*/
|
|
73
82
|
declare const getDisplayTitle: (customTitle: string | undefined, currentSummary: string | undefined, title: string | undefined, maxLength?: number, fallback?: string) => string;
|
|
74
83
|
/**
|
|
@@ -76,21 +85,6 @@ declare const getDisplayTitle: (customTitle: string | undefined, currentSummary:
|
|
|
76
85
|
* Only masks the specified homeDir, not other users' paths
|
|
77
86
|
*/
|
|
78
87
|
declare const maskHomePath: (text: string, homeDir: string) => string;
|
|
79
|
-
/**
|
|
80
|
-
* Get session sort timestamp based on official Claude Code extension behavior
|
|
81
|
-
*
|
|
82
|
-
* CRITICAL ARCHITECTURE:
|
|
83
|
-
* - Summary records have `leafUuid` but NO timestamp in the summary record itself
|
|
84
|
-
* - `leafUuid` points to a message in ANOTHER session (cross-session reference)
|
|
85
|
-
* - The timestamp for sorting must come from the TARGET message's timestamp
|
|
86
|
-
* - Official extension uses leafUuid's target message timestamp for sorting/display
|
|
87
|
-
*
|
|
88
|
-
* Priority: summaries[0].timestamp (leafUuid-based) > createdAt
|
|
89
|
-
*/
|
|
90
|
-
declare const getSessionSortTimestamp: (session: {
|
|
91
|
-
summaries?: SummaryInfo[];
|
|
92
|
-
createdAt?: string;
|
|
93
|
-
}) => string | undefined;
|
|
94
88
|
/**
|
|
95
89
|
* Sort projects with priority:
|
|
96
90
|
* 1. Current project (if specified)
|
|
@@ -102,6 +96,14 @@ declare const sortProjects: (projects: Project[], options?: {
|
|
|
102
96
|
homeDir?: string;
|
|
103
97
|
filterEmpty?: boolean;
|
|
104
98
|
}) => Project[];
|
|
99
|
+
/**
|
|
100
|
+
* Get sort timestamp for session (Unix timestamp ms)
|
|
101
|
+
* Priority: summaries[0].timestamp > createdAt > 0
|
|
102
|
+
*/
|
|
103
|
+
declare const getSessionSortTimestamp: (session: {
|
|
104
|
+
summaries?: SummaryInfo[];
|
|
105
|
+
createdAt?: string;
|
|
106
|
+
}) => number;
|
|
105
107
|
|
|
106
108
|
declare const findLinkedAgents: (projectName: string, sessionId: string) => Effect.Effect<string[], effect_Cause.UnknownException, never>;
|
|
107
109
|
declare const findOrphanAgents: (projectName: string) => Effect.Effect<{
|
|
@@ -121,15 +123,7 @@ declare const deleteOrphanAgents: (projectName: string) => Effect.Effect<{
|
|
|
121
123
|
declare const loadAgentMessages: (projectName: string, _sessionId: string, // Reserved for future validation
|
|
122
124
|
agentId: string) => Effect.Effect<Message[], effect_Cause.UnknownException, never>;
|
|
123
125
|
|
|
124
|
-
declare const findLinkedTodos: (sessionId: string, agentIds?: string[]) => Effect.Effect<
|
|
125
|
-
sessionId: string;
|
|
126
|
-
sessionTodos: TodoItem[];
|
|
127
|
-
agentTodos: {
|
|
128
|
-
agentId: string;
|
|
129
|
-
todos: TodoItem[];
|
|
130
|
-
}[];
|
|
131
|
-
hasTodos: boolean;
|
|
132
|
-
}, effect_Cause.UnknownException, never>;
|
|
126
|
+
declare const findLinkedTodos: (sessionId: string, agentIds?: string[]) => Effect.Effect<SessionTodos | undefined, effect_Cause.UnknownException, never>;
|
|
133
127
|
declare const sessionHasTodos: (sessionId: string, agentIds?: string[]) => Effect.Effect<boolean, effect_Cause.UnknownException, never>;
|
|
134
128
|
declare const deleteLinkedTodos: (sessionId: string, agentIds: string[]) => Effect.Effect<{
|
|
135
129
|
deletedCount: number;
|
|
@@ -156,7 +150,7 @@ declare const listSessions: (projectName: string) => Effect.Effect<{
|
|
|
156
150
|
updatedAt: string | undefined;
|
|
157
151
|
}[], effect_Cause.UnknownException, never>;
|
|
158
152
|
declare const readSession: (projectName: string, sessionId: string) => Effect.Effect<Message[], effect_Cause.UnknownException, never>;
|
|
159
|
-
declare const deleteMessage: (projectName: string, sessionId: string, messageUuid: string) => Effect.Effect<{
|
|
153
|
+
declare const deleteMessage: (projectName: string, sessionId: string, messageUuid: string, targetType?: "file-history-snapshot" | "summary") => Effect.Effect<{
|
|
160
154
|
success: boolean;
|
|
161
155
|
error: string;
|
|
162
156
|
deletedMessage?: undefined;
|
|
@@ -217,48 +211,13 @@ declare const loadSessionTreeData: (projectName: string, sessionId: string) => E
|
|
|
217
211
|
createdAt: string;
|
|
218
212
|
updatedAt: string;
|
|
219
213
|
fileMtime: number | undefined;
|
|
214
|
+
sortTimestamp: number;
|
|
220
215
|
summaries: SummaryInfo[];
|
|
221
216
|
agents: AgentInfo[];
|
|
222
|
-
todos:
|
|
223
|
-
sessionId: string;
|
|
224
|
-
sessionTodos: TodoItem[];
|
|
225
|
-
agentTodos: {
|
|
226
|
-
agentId: string;
|
|
227
|
-
todos: TodoItem[];
|
|
228
|
-
}[];
|
|
229
|
-
hasTodos: boolean;
|
|
230
|
-
};
|
|
217
|
+
todos: SessionTodos | undefined;
|
|
231
218
|
lastCompactBoundaryUuid: string | undefined;
|
|
232
219
|
}, effect_Cause.UnknownException, never>;
|
|
233
|
-
declare const loadProjectTreeData: (projectName: string, sortOptions?: SessionSortOptions) => Effect.Effect<
|
|
234
|
-
name: string;
|
|
235
|
-
displayName: string;
|
|
236
|
-
path: string;
|
|
237
|
-
sessionCount: number;
|
|
238
|
-
sessions: {
|
|
239
|
-
id: string;
|
|
240
|
-
projectName: string;
|
|
241
|
-
title: string;
|
|
242
|
-
customTitle: string | undefined;
|
|
243
|
-
currentSummary: string;
|
|
244
|
-
messageCount: number;
|
|
245
|
-
createdAt: string;
|
|
246
|
-
updatedAt: string;
|
|
247
|
-
fileMtime: number | undefined;
|
|
248
|
-
summaries: SummaryInfo[];
|
|
249
|
-
agents: AgentInfo[];
|
|
250
|
-
todos: {
|
|
251
|
-
sessionId: string;
|
|
252
|
-
sessionTodos: TodoItem[];
|
|
253
|
-
agentTodos: {
|
|
254
|
-
agentId: string;
|
|
255
|
-
todos: TodoItem[];
|
|
256
|
-
}[];
|
|
257
|
-
hasTodos: boolean;
|
|
258
|
-
};
|
|
259
|
-
lastCompactBoundaryUuid: string | undefined;
|
|
260
|
-
}[];
|
|
261
|
-
} | null, effect_Cause.UnknownException, never>;
|
|
220
|
+
declare const loadProjectTreeData: (projectName: string, sortOptions?: SessionSortOptions) => Effect.Effect<ProjectTreeData | null, effect_Cause.UnknownException, never>;
|
|
262
221
|
|
|
263
222
|
declare const analyzeSession: (projectName: string, sessionId: string) => Effect.Effect<{
|
|
264
223
|
sessionId: string;
|
|
@@ -373,6 +332,75 @@ declare const getSessionFiles: (projectName: string, sessionId: string) => Effec
|
|
|
373
332
|
totalChanges: number;
|
|
374
333
|
}, effect_Cause.UnknownException, never>;
|
|
375
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Session message chain validation utilities
|
|
337
|
+
*
|
|
338
|
+
* Validates:
|
|
339
|
+
* 1. parentUuid chain integrity (skip file-history-snapshot)
|
|
340
|
+
* 2. tool_use_id / tool_result matching
|
|
341
|
+
*/
|
|
342
|
+
interface ChainError {
|
|
343
|
+
type: 'broken_chain' | 'orphan_parent';
|
|
344
|
+
uuid: string;
|
|
345
|
+
line: number;
|
|
346
|
+
parentUuid: string | null;
|
|
347
|
+
expectedParent?: string;
|
|
348
|
+
}
|
|
349
|
+
interface ToolUseResultError {
|
|
350
|
+
type: 'orphan_tool_result';
|
|
351
|
+
uuid: string;
|
|
352
|
+
line: number;
|
|
353
|
+
toolUseId: string;
|
|
354
|
+
}
|
|
355
|
+
interface ValidationResult {
|
|
356
|
+
valid: boolean;
|
|
357
|
+
errors: (ChainError | ToolUseResultError)[];
|
|
358
|
+
}
|
|
359
|
+
interface MessagePayload {
|
|
360
|
+
role?: string;
|
|
361
|
+
content?: unknown;
|
|
362
|
+
}
|
|
363
|
+
interface GenericMessage {
|
|
364
|
+
type?: string;
|
|
365
|
+
uuid?: string;
|
|
366
|
+
parentUuid?: string | null;
|
|
367
|
+
message?: MessagePayload;
|
|
368
|
+
messageId?: string;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Validate parentUuid chain for messages
|
|
372
|
+
*
|
|
373
|
+
* Rules:
|
|
374
|
+
* - Skip file-history-snapshot type (has no uuid, uses messageId instead)
|
|
375
|
+
* - Skip messages without uuid
|
|
376
|
+
* - First message can have null parentUuid
|
|
377
|
+
* - Subsequent messages must have valid parentUuid pointing to existing uuid
|
|
378
|
+
*/
|
|
379
|
+
declare function validateChain(messages: readonly GenericMessage[]): ValidationResult;
|
|
380
|
+
/**
|
|
381
|
+
* Validate tool_use_id / tool_result matching
|
|
382
|
+
*
|
|
383
|
+
* Rules:
|
|
384
|
+
* - All tool_result blocks must have a corresponding tool_use block in the session
|
|
385
|
+
* - tool_use blocks are collected from all messages (not just previous)
|
|
386
|
+
*/
|
|
387
|
+
declare function validateToolUseResult(messages: readonly GenericMessage[]): ValidationResult;
|
|
388
|
+
/**
|
|
389
|
+
* Delete a message and repair the parentUuid chain
|
|
390
|
+
*
|
|
391
|
+
* This is a pure function for client-side use (without file I/O)
|
|
392
|
+
* Server-side deleteMessage in crud.ts uses similar logic with file operations
|
|
393
|
+
*
|
|
394
|
+
* @param messages - Array of messages (will be mutated)
|
|
395
|
+
* @param targetId - uuid, messageId, or leafUuid of message to delete
|
|
396
|
+
* @param targetType - Optional: 'file-history-snapshot' or 'summary' to disambiguate collisions
|
|
397
|
+
* @returns Object with deleted message and messages to also delete (orphan tool_results)
|
|
398
|
+
*/
|
|
399
|
+
declare function deleteMessageWithChainRepair<T extends GenericMessage>(messages: T[], targetId: string, targetType?: 'file-history-snapshot' | 'summary'): {
|
|
400
|
+
deleted: T | null;
|
|
401
|
+
alsoDeleted: T[];
|
|
402
|
+
};
|
|
403
|
+
|
|
376
404
|
/**
|
|
377
405
|
* Load sessions-index.json for a project
|
|
378
406
|
* Returns null if the file doesn't exist
|
|
@@ -426,4 +454,4 @@ declare const getLogger: () => Logger;
|
|
|
426
454
|
*/
|
|
427
455
|
declare const createLogger: (namespace: string) => Logger;
|
|
428
456
|
|
|
429
|
-
export { AgentInfo, CompressSessionOptions, ConversationLine, FileChange, type Logger, Message, MessagePayload, MoveSessionResult, Project, SearchResult, SessionIndexEntry, SessionSortOptions, SessionsIndex, SummarizeSessionOptions, SummaryInfo,
|
|
457
|
+
export { AgentInfo, type ChainError, CompressSessionOptions, ConversationLine, FileChange, type Logger, Message, MessagePayload$1 as MessagePayload, MoveSessionResult, Project, ProjectTreeData, SearchResult, SessionIndexEntry, SessionSortOptions, SessionTodos, SessionsIndex, SummarizeSessionOptions, SummaryInfo, type ToolUseResultError, type ValidationResult, analyzeSession, clearSessions, compressSession, createLogger, deleteLinkedTodos, deleteMessage, deleteMessageWithChainRepair, deleteOrphanAgents, deleteOrphanTodos, deleteSession, displayPathToFolderName, extractProjectKnowledge, extractTextContent, extractTitle, findLinkedAgents, findLinkedTodos, findOrphanAgents, findOrphanTodos, findProjectByWorkspacePath, folderNameToDisplayPath, folderNameToPath, getDisplayTitle, getIndexEntryDisplayTitle, getLogger, getRealPathFromSession, getSessionFiles, getSessionSortTimestamp, getSessionsDir, getTodosDir, hasSessionsIndex, isContinuationSummary, isInvalidApiKeyMessage, listProjects, listSessions, loadAgentMessages, loadProjectTreeData, loadSessionTreeData, loadSessionsIndex, maskHomePath, moveSession, parseCommandMessage, pathToFolderName, previewCleanup, readSession, renameSession, restoreMessage, searchSessions, sessionHasTodos, setLogger, sortIndexEntriesByModified, sortProjects, splitSession, summarizeSession, updateSessionSummary, validateChain, validateToolUseResult };
|
package/dist/index.js
CHANGED
|
@@ -178,14 +178,19 @@ var extractTextContent = (message) => {
|
|
|
178
178
|
}
|
|
179
179
|
return "";
|
|
180
180
|
};
|
|
181
|
+
var parseCommandMessage = (content) => {
|
|
182
|
+
const name = content?.match(/<command-name>([^<]+)<\/command-name>/)?.[1] ?? "";
|
|
183
|
+
const message = content?.match(/<command-message>([^<]+)<\/command-message>/)?.[1] ?? "";
|
|
184
|
+
return { name, message };
|
|
185
|
+
};
|
|
181
186
|
var extractTitle = (text) => {
|
|
182
187
|
if (!text) return "Untitled";
|
|
188
|
+
const { name } = parseCommandMessage(text);
|
|
189
|
+
if (name) return name;
|
|
183
190
|
let cleaned = text.replace(/<ide_[^>]*>[\s\S]*?<\/ide_[^>]*>/g, "").trim();
|
|
184
191
|
if (!cleaned) return "Untitled";
|
|
185
192
|
if (cleaned.includes("\n\n")) {
|
|
186
193
|
cleaned = cleaned.split("\n\n")[0];
|
|
187
|
-
} else if (cleaned.includes("\n")) {
|
|
188
|
-
cleaned = cleaned.split("\n")[0];
|
|
189
194
|
}
|
|
190
195
|
if (cleaned.length > 100) {
|
|
191
196
|
return cleaned.slice(0, 100) + "...";
|
|
@@ -218,7 +223,13 @@ var getDisplayTitle = (customTitle, currentSummary, title, maxLength = 60, fallb
|
|
|
218
223
|
if (currentSummary) {
|
|
219
224
|
return currentSummary.length > maxLength ? currentSummary.slice(0, maxLength - 3) + "..." : currentSummary;
|
|
220
225
|
}
|
|
221
|
-
if (title && title !== "Untitled")
|
|
226
|
+
if (title && title !== "Untitled") {
|
|
227
|
+
if (title.includes("<command-name>")) {
|
|
228
|
+
const { name } = parseCommandMessage(title);
|
|
229
|
+
if (name) return name;
|
|
230
|
+
}
|
|
231
|
+
return title;
|
|
232
|
+
}
|
|
222
233
|
return fallback;
|
|
223
234
|
};
|
|
224
235
|
var replaceMessageContent = (msg, text) => ({
|
|
@@ -254,9 +265,6 @@ var maskHomePath = (text, homeDir) => {
|
|
|
254
265
|
const regex = new RegExp(`${escapedHome}(?=[/\\\\]|$)`, "g");
|
|
255
266
|
return text.replace(regex, "~");
|
|
256
267
|
};
|
|
257
|
-
var getSessionSortTimestamp = (session) => {
|
|
258
|
-
return session.summaries?.[0]?.timestamp ?? session.createdAt;
|
|
259
|
-
};
|
|
260
268
|
var sortProjects = (projects, options = {}) => {
|
|
261
269
|
const { currentProjectName, homeDir, filterEmpty = true } = options;
|
|
262
270
|
const filtered = filterEmpty ? projects.filter((p) => p.sessionCount > 0) : projects;
|
|
@@ -275,6 +283,10 @@ var sortProjects = (projects, options = {}) => {
|
|
|
275
283
|
return a.displayName.localeCompare(b.displayName);
|
|
276
284
|
});
|
|
277
285
|
};
|
|
286
|
+
var getSessionSortTimestamp = (session) => {
|
|
287
|
+
const timestampStr = session.summaries?.[0]?.timestamp ?? session.createdAt;
|
|
288
|
+
return timestampStr ? new Date(timestampStr).getTime() : 0;
|
|
289
|
+
};
|
|
278
290
|
|
|
279
291
|
// src/agents.ts
|
|
280
292
|
import { Effect } from "effect";
|
|
@@ -452,12 +464,7 @@ var findLinkedTodos = (sessionId, agentIds = []) => Effect2.gen(function* () {
|
|
|
452
464
|
() => fs3.access(todosDir).then(() => true).catch(() => false)
|
|
453
465
|
);
|
|
454
466
|
if (!exists) {
|
|
455
|
-
return
|
|
456
|
-
sessionId,
|
|
457
|
-
sessionTodos: [],
|
|
458
|
-
agentTodos: [],
|
|
459
|
-
hasTodos: false
|
|
460
|
-
};
|
|
467
|
+
return void 0;
|
|
461
468
|
}
|
|
462
469
|
const sessionTodoPath = path3.join(todosDir, `${sessionId}.json`);
|
|
463
470
|
let sessionTodos = [];
|
|
@@ -675,6 +682,177 @@ import { Effect as Effect4, pipe, Array as A, Option as O } from "effect";
|
|
|
675
682
|
import * as fs5 from "fs/promises";
|
|
676
683
|
import * as path5 from "path";
|
|
677
684
|
import * as crypto from "crypto";
|
|
685
|
+
|
|
686
|
+
// src/session/validation.ts
|
|
687
|
+
function validateChain(messages) {
|
|
688
|
+
const errors = [];
|
|
689
|
+
const uuids = /* @__PURE__ */ new Set();
|
|
690
|
+
for (const msg of messages) {
|
|
691
|
+
if (msg.uuid) {
|
|
692
|
+
uuids.add(msg.uuid);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
let foundFirstMessage = false;
|
|
696
|
+
for (let i = 0; i < messages.length; i++) {
|
|
697
|
+
const msg = messages[i];
|
|
698
|
+
if (msg.type === "file-history-snapshot") {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (!msg.uuid) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (!foundFirstMessage) {
|
|
705
|
+
foundFirstMessage = true;
|
|
706
|
+
if (msg.parentUuid === null) {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (msg.parentUuid === void 0) {
|
|
710
|
+
errors.push({
|
|
711
|
+
type: "broken_chain",
|
|
712
|
+
uuid: msg.uuid,
|
|
713
|
+
line: i + 1,
|
|
714
|
+
parentUuid: null
|
|
715
|
+
});
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (msg.parentUuid === null || msg.parentUuid === void 0) {
|
|
720
|
+
errors.push({
|
|
721
|
+
type: "broken_chain",
|
|
722
|
+
uuid: msg.uuid,
|
|
723
|
+
line: i + 1,
|
|
724
|
+
parentUuid: null
|
|
725
|
+
});
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (!uuids.has(msg.parentUuid)) {
|
|
729
|
+
errors.push({
|
|
730
|
+
type: "orphan_parent",
|
|
731
|
+
uuid: msg.uuid,
|
|
732
|
+
line: i + 1,
|
|
733
|
+
parentUuid: msg.parentUuid
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
valid: errors.length === 0,
|
|
739
|
+
errors
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function validateToolUseResult(messages) {
|
|
743
|
+
const errors = [];
|
|
744
|
+
const toolUseIds = /* @__PURE__ */ new Set();
|
|
745
|
+
for (const msg of messages) {
|
|
746
|
+
const content = msg.message?.content;
|
|
747
|
+
if (Array.isArray(content)) {
|
|
748
|
+
for (const item of content) {
|
|
749
|
+
if (item.type === "tool_use" && item.id) {
|
|
750
|
+
toolUseIds.add(item.id);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
for (let i = 0; i < messages.length; i++) {
|
|
756
|
+
const msg = messages[i];
|
|
757
|
+
const content = msg.message?.content;
|
|
758
|
+
if (!Array.isArray(content)) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
for (const item of content) {
|
|
762
|
+
if (item.type === "tool_result" && item.tool_use_id) {
|
|
763
|
+
if (!toolUseIds.has(item.tool_use_id)) {
|
|
764
|
+
errors.push({
|
|
765
|
+
type: "orphan_tool_result",
|
|
766
|
+
uuid: msg.uuid || "",
|
|
767
|
+
line: i + 1,
|
|
768
|
+
toolUseId: item.tool_use_id
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
valid: errors.length === 0,
|
|
776
|
+
errors
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function deleteMessageWithChainRepair(messages, targetId, targetType) {
|
|
780
|
+
let targetIndex = -1;
|
|
781
|
+
if (targetType === "file-history-snapshot") {
|
|
782
|
+
targetIndex = messages.findIndex(
|
|
783
|
+
(m) => m.type === "file-history-snapshot" && m.messageId === targetId
|
|
784
|
+
);
|
|
785
|
+
} else if (targetType === "summary") {
|
|
786
|
+
targetIndex = messages.findIndex(
|
|
787
|
+
(m) => m.leafUuid === targetId
|
|
788
|
+
);
|
|
789
|
+
} else {
|
|
790
|
+
targetIndex = messages.findIndex((m) => m.uuid === targetId);
|
|
791
|
+
if (targetIndex === -1) {
|
|
792
|
+
targetIndex = messages.findIndex(
|
|
793
|
+
(m) => m.leafUuid === targetId
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
if (targetIndex === -1) {
|
|
797
|
+
targetIndex = messages.findIndex(
|
|
798
|
+
(m) => m.type === "file-history-snapshot" && m.messageId === targetId
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (targetIndex === -1) {
|
|
803
|
+
return { deleted: null, alsoDeleted: [] };
|
|
804
|
+
}
|
|
805
|
+
const deletedMsg = messages[targetIndex];
|
|
806
|
+
const toolUseIds = [];
|
|
807
|
+
if (deletedMsg.type === "assistant") {
|
|
808
|
+
const content = deletedMsg.message?.content;
|
|
809
|
+
if (Array.isArray(content)) {
|
|
810
|
+
for (const item of content) {
|
|
811
|
+
if (item.type === "tool_use" && item.id) {
|
|
812
|
+
toolUseIds.push(item.id);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const toolResultIndices = [];
|
|
818
|
+
if (toolUseIds.length > 0) {
|
|
819
|
+
for (let i = 0; i < messages.length; i++) {
|
|
820
|
+
const msg = messages[i];
|
|
821
|
+
if (msg.type === "user") {
|
|
822
|
+
const content = msg.message?.content;
|
|
823
|
+
if (Array.isArray(content)) {
|
|
824
|
+
for (const item of content) {
|
|
825
|
+
if (item.type === "tool_result" && item.tool_use_id && toolUseIds.includes(item.tool_use_id)) {
|
|
826
|
+
toolResultIndices.push(i);
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const indicesToDelete = [targetIndex, ...toolResultIndices].sort((a, b) => b - a);
|
|
835
|
+
for (const idx of indicesToDelete) {
|
|
836
|
+
const msg = messages[idx];
|
|
837
|
+
const isInParentChain = msg.type !== "file-history-snapshot" && msg.uuid;
|
|
838
|
+
if (isInParentChain) {
|
|
839
|
+
const deletedUuid = msg.uuid;
|
|
840
|
+
const parentUuid = msg.parentUuid;
|
|
841
|
+
for (const m of messages) {
|
|
842
|
+
if (m.parentUuid === deletedUuid) {
|
|
843
|
+
m.parentUuid = parentUuid;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const alsoDeleted = toolResultIndices.map((i) => messages[i]);
|
|
849
|
+
for (const idx of indicesToDelete) {
|
|
850
|
+
messages.splice(idx, 1);
|
|
851
|
+
}
|
|
852
|
+
return { deleted: deletedMsg, alsoDeleted };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/session/crud.ts
|
|
678
856
|
var updateSessionSummary = (projectName, sessionId, newSummary) => Effect4.gen(function* () {
|
|
679
857
|
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
680
858
|
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
@@ -763,29 +941,18 @@ var readSession = (projectName, sessionId) => Effect4.gen(function* () {
|
|
|
763
941
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
764
942
|
return lines.map((line) => JSON.parse(line));
|
|
765
943
|
});
|
|
766
|
-
var deleteMessage = (projectName, sessionId, messageUuid) => Effect4.gen(function* () {
|
|
944
|
+
var deleteMessage = (projectName, sessionId, messageUuid, targetType) => Effect4.gen(function* () {
|
|
767
945
|
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
768
946
|
const content = yield* Effect4.tryPromise(() => fs5.readFile(filePath, "utf-8"));
|
|
769
947
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
770
948
|
const messages = lines.map((line) => JSON.parse(line));
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
);
|
|
774
|
-
if (targetIndex === -1) {
|
|
949
|
+
const result = deleteMessageWithChainRepair(messages, messageUuid, targetType);
|
|
950
|
+
if (!result.deleted) {
|
|
775
951
|
return { success: false, error: "Message not found" };
|
|
776
952
|
}
|
|
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
953
|
const newContent = messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
|
|
787
954
|
yield* Effect4.tryPromise(() => fs5.writeFile(filePath, newContent, "utf-8"));
|
|
788
|
-
return { success: true, deletedMessage:
|
|
955
|
+
return { success: true, deletedMessage: result.deleted };
|
|
789
956
|
});
|
|
790
957
|
var restoreMessage = (projectName, sessionId, message, index) => Effect4.gen(function* () {
|
|
791
958
|
const filePath = path5.join(getSessionsDir(), projectName, `${sessionId}.jsonl`);
|
|
@@ -1063,6 +1230,44 @@ var splitSession = (projectName, sessionId, splitAtMessageUuid) => Effect4.gen(f
|
|
|
1063
1230
|
import { Effect as Effect5 } from "effect";
|
|
1064
1231
|
import * as fs6 from "fs/promises";
|
|
1065
1232
|
import * as path6 from "path";
|
|
1233
|
+
var sortSessions = (sessions, sort) => {
|
|
1234
|
+
return sessions.sort((a, b) => {
|
|
1235
|
+
let comparison = 0;
|
|
1236
|
+
switch (sort.field) {
|
|
1237
|
+
case "summary": {
|
|
1238
|
+
comparison = a.sortTimestamp - b.sortTimestamp;
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
case "modified": {
|
|
1242
|
+
comparison = (a.fileMtime ?? 0) - (b.fileMtime ?? 0);
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
case "created": {
|
|
1246
|
+
const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
|
1247
|
+
const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
|
1248
|
+
comparison = createdA - createdB;
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
case "updated": {
|
|
1252
|
+
const updatedA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
1253
|
+
const updatedB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
1254
|
+
comparison = updatedA - updatedB;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
case "messageCount": {
|
|
1258
|
+
comparison = a.messageCount - b.messageCount;
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
case "title": {
|
|
1262
|
+
const titleA = a.customTitle ?? a.currentSummary ?? a.title;
|
|
1263
|
+
const titleB = b.customTitle ?? b.currentSummary ?? b.title;
|
|
1264
|
+
comparison = titleA.localeCompare(titleB);
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return sort.order === "desc" ? -comparison : comparison;
|
|
1269
|
+
});
|
|
1270
|
+
};
|
|
1066
1271
|
var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSession, fileMtime) => Effect5.gen(function* () {
|
|
1067
1272
|
const projectPath = path6.join(getSessionsDir(), projectName);
|
|
1068
1273
|
const filePath = path6.join(projectPath, `${sessionId}.jsonl`);
|
|
@@ -1164,6 +1369,8 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
|
|
|
1164
1369
|
}
|
|
1165
1370
|
}
|
|
1166
1371
|
const todos = yield* findLinkedTodos(sessionId, linkedAgentIds);
|
|
1372
|
+
const createdAt = firstMessage?.timestamp ?? void 0;
|
|
1373
|
+
const sortTimestamp = getSessionSortTimestamp({ summaries, createdAt });
|
|
1167
1374
|
return {
|
|
1168
1375
|
id: sessionId,
|
|
1169
1376
|
projectName,
|
|
@@ -1171,9 +1378,10 @@ var loadSessionTreeDataInternal = (projectName, sessionId, summariesByTargetSess
|
|
|
1171
1378
|
customTitle,
|
|
1172
1379
|
currentSummary: summaries[0]?.summary,
|
|
1173
1380
|
messageCount: userAssistantMessages.length > 0 ? userAssistantMessages.length : summaries.length > 0 ? 1 : 0,
|
|
1174
|
-
createdAt
|
|
1381
|
+
createdAt,
|
|
1175
1382
|
updatedAt: lastMessage?.timestamp ?? void 0,
|
|
1176
1383
|
fileMtime,
|
|
1384
|
+
sortTimestamp,
|
|
1177
1385
|
summaries,
|
|
1178
1386
|
agents,
|
|
1179
1387
|
todos,
|
|
@@ -1275,46 +1483,7 @@ var loadProjectTreeData = (projectName, sortOptions) => Effect5.gen(function* ()
|
|
|
1275
1483
|
}),
|
|
1276
1484
|
{ concurrency: 10 }
|
|
1277
1485
|
);
|
|
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
|
-
});
|
|
1486
|
+
const sortedSessions = sortSessions(sessions, sort);
|
|
1318
1487
|
const filteredSessions = sortedSessions.filter((s) => {
|
|
1319
1488
|
if (isErrorSessionTitle(s.title)) return false;
|
|
1320
1489
|
if (isErrorSessionTitle(s.customTitle)) return false;
|
|
@@ -1988,6 +2157,7 @@ export {
|
|
|
1988
2157
|
createLogger,
|
|
1989
2158
|
deleteLinkedTodos,
|
|
1990
2159
|
deleteMessage,
|
|
2160
|
+
deleteMessageWithChainRepair,
|
|
1991
2161
|
deleteOrphanAgents,
|
|
1992
2162
|
deleteOrphanTodos,
|
|
1993
2163
|
deleteSession,
|
|
@@ -2021,6 +2191,7 @@ export {
|
|
|
2021
2191
|
loadSessionsIndex,
|
|
2022
2192
|
maskHomePath,
|
|
2023
2193
|
moveSession,
|
|
2194
|
+
parseCommandMessage,
|
|
2024
2195
|
pathToFolderName,
|
|
2025
2196
|
previewCleanup,
|
|
2026
2197
|
readSession,
|
|
@@ -2033,6 +2204,8 @@ export {
|
|
|
2033
2204
|
sortProjects,
|
|
2034
2205
|
splitSession,
|
|
2035
2206
|
summarizeSession,
|
|
2036
|
-
updateSessionSummary
|
|
2207
|
+
updateSessionSummary,
|
|
2208
|
+
validateChain,
|
|
2209
|
+
validateToolUseResult
|
|
2037
2210
|
};
|
|
2038
2211
|
//# sourceMappingURL=index.js.map
|