@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 CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MessagePayload, a as Message, P as Project, S as SummaryInfo, T as TodoItem, b as MoveSessionResult, A as AgentInfo, c as SessionSortOptions, C as CompressSessionOptions, d as SummarizeSessionOptions, e as ConversationLine, f as SearchResult, F as FileChange, g as SessionsIndex, h as SessionIndexEntry } from './types-BdPaAnP8.js';
2
- export { o as CleanupPreview, n as ClearSessionsResult, v as CompressSessionResult, i as ContentItem, D as DeleteSessionResult, w as ProjectKnowledge, q as ProjectTreeData, R as RenameSessionResult, r as ResumeSessionOptions, s as ResumeSessionResult, u as SessionAnalysis, l as SessionFilesSummary, j as SessionMeta, y as SessionSortField, z as SessionSortOrder, k as SessionTodos, p as SessionTreeData, m as SplitSessionResult, x as SummarizeSessionResult, t as ToolUsageStats } from './types-BdPaAnP8.js';
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, TodoItem, analyzeSession, clearSessions, compressSession, createLogger, deleteLinkedTodos, deleteMessage, 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, pathToFolderName, previewCleanup, readSession, renameSession, restoreMessage, searchSessions, sessionHasTodos, setLogger, sortIndexEntriesByModified, sortProjects, splitSession, summarizeSession, updateSessionSummary };
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") return title;
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 targetIndex = messages.findIndex(
772
- (m) => m.uuid === messageUuid || m.messageId === messageUuid || m.leafUuid === messageUuid
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: deletedMsg };
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: firstMessage?.timestamp ?? void 0,
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.sort((a, b) => {
1279
- let comparison = 0;
1280
- switch (sort.field) {
1281
- case "summary": {
1282
- const timeA = getSessionSortTimestamp(a);
1283
- const timeB = getSessionSortTimestamp(b);
1284
- const dateA = timeA ? new Date(timeA).getTime() : a.fileMtime ?? 0;
1285
- const dateB = timeB ? new Date(timeB).getTime() : b.fileMtime ?? 0;
1286
- comparison = dateA - dateB;
1287
- break;
1288
- }
1289
- case "modified": {
1290
- comparison = (a.fileMtime ?? 0) - (b.fileMtime ?? 0);
1291
- break;
1292
- }
1293
- case "created": {
1294
- const createdA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
1295
- const createdB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
1296
- comparison = createdA - createdB;
1297
- break;
1298
- }
1299
- case "updated": {
1300
- const updatedA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
1301
- const updatedB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
1302
- comparison = updatedA - updatedB;
1303
- break;
1304
- }
1305
- case "messageCount": {
1306
- comparison = a.messageCount - b.messageCount;
1307
- break;
1308
- }
1309
- case "title": {
1310
- const titleA = a.customTitle ?? a.currentSummary ?? a.title;
1311
- const titleB = b.customTitle ?? b.currentSummary ?? b.title;
1312
- comparison = titleA.localeCompare(titleB);
1313
- break;
1314
- }
1315
- }
1316
- return sort.order === "desc" ? -comparison : comparison;
1317
- });
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