wave-agent-sdk 0.0.6 → 0.0.8

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.
Files changed (180) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +209 -24
  4. package/dist/constants/events.d.ts +28 -0
  5. package/dist/constants/events.d.ts.map +1 -0
  6. package/dist/constants/events.js +27 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/managers/aiManager.d.ts +34 -1
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +248 -132
  13. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundBashManager.js +7 -6
  15. package/dist/managers/hookManager.d.ts +13 -16
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +81 -44
  18. package/dist/managers/liveConfigManager.d.ts +58 -0
  19. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  20. package/dist/managers/liveConfigManager.js +160 -0
  21. package/dist/managers/messageManager.d.ts +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +168 -49
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +9 -3
  26. package/dist/managers/subagentManager.d.ts +51 -0
  27. package/dist/managers/subagentManager.d.ts.map +1 -1
  28. package/dist/managers/subagentManager.js +190 -19
  29. package/dist/services/aiService.d.ts +13 -5
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +350 -74
  32. package/dist/services/configurationWatcher.d.ts +120 -0
  33. package/dist/services/configurationWatcher.d.ts.map +1 -0
  34. package/dist/services/configurationWatcher.js +439 -0
  35. package/dist/services/fileWatcher.d.ts +69 -0
  36. package/dist/services/fileWatcher.d.ts.map +1 -0
  37. package/dist/services/fileWatcher.js +213 -0
  38. package/dist/services/hook.d.ts +91 -9
  39. package/dist/services/hook.d.ts.map +1 -1
  40. package/dist/services/hook.js +393 -43
  41. package/dist/services/jsonlHandler.d.ts +62 -0
  42. package/dist/services/jsonlHandler.d.ts.map +1 -0
  43. package/dist/services/jsonlHandler.js +257 -0
  44. package/dist/services/memory.d.ts +9 -0
  45. package/dist/services/memory.d.ts.map +1 -1
  46. package/dist/services/memory.js +81 -12
  47. package/dist/services/memoryStore.d.ts +81 -0
  48. package/dist/services/memoryStore.d.ts.map +1 -0
  49. package/dist/services/memoryStore.js +200 -0
  50. package/dist/services/session.d.ts +64 -49
  51. package/dist/services/session.d.ts.map +1 -1
  52. package/dist/services/session.js +310 -132
  53. package/dist/tools/bashTool.d.ts.map +1 -1
  54. package/dist/tools/bashTool.js +5 -4
  55. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  56. package/dist/tools/deleteFileTool.js +2 -1
  57. package/dist/tools/editTool.d.ts.map +1 -1
  58. package/dist/tools/editTool.js +3 -2
  59. package/dist/tools/multiEditTool.d.ts.map +1 -1
  60. package/dist/tools/multiEditTool.js +4 -3
  61. package/dist/tools/readTool.d.ts.map +1 -1
  62. package/dist/tools/readTool.js +2 -1
  63. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  64. package/dist/tools/todoWriteTool.js +3 -10
  65. package/dist/tools/writeTool.d.ts.map +1 -1
  66. package/dist/tools/writeTool.js +5 -6
  67. package/dist/types/commands.d.ts +4 -0
  68. package/dist/types/commands.d.ts.map +1 -1
  69. package/dist/types/core.d.ts +35 -0
  70. package/dist/types/core.d.ts.map +1 -1
  71. package/dist/types/environment.d.ts +42 -0
  72. package/dist/types/environment.d.ts.map +1 -0
  73. package/dist/types/environment.js +21 -0
  74. package/dist/types/hooks.d.ts +8 -2
  75. package/dist/types/hooks.d.ts.map +1 -1
  76. package/dist/types/hooks.js +8 -2
  77. package/dist/types/index.d.ts +2 -0
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js +2 -0
  80. package/dist/types/memoryStore.d.ts +82 -0
  81. package/dist/types/memoryStore.d.ts.map +1 -0
  82. package/dist/types/memoryStore.js +7 -0
  83. package/dist/types/messaging.d.ts +21 -9
  84. package/dist/types/messaging.d.ts.map +1 -1
  85. package/dist/types/messaging.js +5 -1
  86. package/dist/types/session.d.ts +20 -0
  87. package/dist/types/session.d.ts.map +1 -0
  88. package/dist/types/session.js +7 -0
  89. package/dist/utils/bashHistory.d.ts.map +1 -1
  90. package/dist/utils/bashHistory.js +27 -26
  91. package/dist/utils/cacheControlUtils.d.ts +121 -0
  92. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  93. package/dist/utils/cacheControlUtils.js +367 -0
  94. package/dist/utils/commandPathResolver.d.ts +52 -0
  95. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  96. package/dist/utils/commandPathResolver.js +145 -0
  97. package/dist/utils/configPaths.d.ts +85 -0
  98. package/dist/utils/configPaths.d.ts.map +1 -0
  99. package/dist/utils/configPaths.js +121 -0
  100. package/dist/utils/configResolver.d.ts +37 -10
  101. package/dist/utils/configResolver.d.ts.map +1 -1
  102. package/dist/utils/configResolver.js +127 -23
  103. package/dist/utils/constants.d.ts +1 -1
  104. package/dist/utils/constants.js +1 -1
  105. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  106. package/dist/utils/convertMessagesForAPI.js +8 -13
  107. package/dist/utils/customCommands.d.ts.map +1 -1
  108. package/dist/utils/customCommands.js +66 -21
  109. package/dist/utils/fileUtils.d.ts +15 -0
  110. package/dist/utils/fileUtils.d.ts.map +1 -0
  111. package/dist/utils/fileUtils.js +61 -0
  112. package/dist/utils/globalLogger.d.ts +102 -0
  113. package/dist/utils/globalLogger.d.ts.map +1 -0
  114. package/dist/utils/globalLogger.js +136 -0
  115. package/dist/utils/hookMatcher.d.ts +1 -6
  116. package/dist/utils/hookMatcher.d.ts.map +1 -1
  117. package/dist/utils/mcpUtils.d.ts.map +1 -1
  118. package/dist/utils/mcpUtils.js +25 -3
  119. package/dist/utils/messageOperations.d.ts +27 -27
  120. package/dist/utils/messageOperations.d.ts.map +1 -1
  121. package/dist/utils/messageOperations.js +46 -36
  122. package/dist/utils/pathEncoder.d.ts +104 -0
  123. package/dist/utils/pathEncoder.d.ts.map +1 -0
  124. package/dist/utils/pathEncoder.js +272 -0
  125. package/dist/utils/subagentParser.d.ts.map +1 -1
  126. package/dist/utils/subagentParser.js +2 -1
  127. package/dist/utils/tokenCalculation.d.ts +26 -0
  128. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  129. package/dist/utils/tokenCalculation.js +36 -0
  130. package/package.json +6 -3
  131. package/src/agent.ts +301 -37
  132. package/src/constants/events.ts +38 -0
  133. package/src/index.ts +2 -0
  134. package/src/managers/aiManager.ts +325 -173
  135. package/src/managers/backgroundBashManager.ts +7 -6
  136. package/src/managers/hookManager.ts +106 -84
  137. package/src/managers/liveConfigManager.ts +248 -0
  138. package/src/managers/messageManager.ts +237 -100
  139. package/src/managers/slashCommandManager.ts +9 -7
  140. package/src/managers/subagentManager.ts +284 -22
  141. package/src/services/aiService.ts +474 -83
  142. package/src/services/configurationWatcher.ts +622 -0
  143. package/src/services/fileWatcher.ts +301 -0
  144. package/src/services/hook.ts +538 -47
  145. package/src/services/jsonlHandler.ts +319 -0
  146. package/src/services/memory.ts +92 -12
  147. package/src/services/memoryStore.ts +279 -0
  148. package/src/services/session.ts +381 -157
  149. package/src/tools/bashTool.ts +5 -4
  150. package/src/tools/deleteFileTool.ts +2 -1
  151. package/src/tools/editTool.ts +3 -2
  152. package/src/tools/multiEditTool.ts +4 -3
  153. package/src/tools/readTool.ts +2 -1
  154. package/src/tools/todoWriteTool.ts +3 -11
  155. package/src/tools/writeTool.ts +7 -6
  156. package/src/types/commands.ts +6 -0
  157. package/src/types/core.ts +44 -0
  158. package/src/types/environment.ts +60 -0
  159. package/src/types/hooks.ts +21 -8
  160. package/src/types/index.ts +2 -0
  161. package/src/types/memoryStore.ts +94 -0
  162. package/src/types/messaging.ts +21 -10
  163. package/src/types/session.ts +25 -0
  164. package/src/utils/bashHistory.ts +27 -27
  165. package/src/utils/cacheControlUtils.ts +540 -0
  166. package/src/utils/commandPathResolver.ts +189 -0
  167. package/src/utils/configPaths.ts +163 -0
  168. package/src/utils/configResolver.ts +182 -22
  169. package/src/utils/constants.ts +1 -1
  170. package/src/utils/convertMessagesForAPI.ts +8 -14
  171. package/src/utils/customCommands.ts +90 -22
  172. package/src/utils/fileUtils.ts +65 -0
  173. package/src/utils/globalLogger.ts +145 -0
  174. package/src/utils/hookMatcher.ts +1 -12
  175. package/src/utils/mcpUtils.ts +34 -3
  176. package/src/utils/messageOperations.ts +77 -60
  177. package/src/utils/pathEncoder.ts +379 -0
  178. package/src/utils/subagentParser.ts +2 -1
  179. package/src/utils/tokenCalculation.ts +43 -0
  180. package/src/types/index.ts.backup +0 -357
@@ -1,196 +1,320 @@
1
1
  import { promises as fs } from "fs";
2
2
  import { join } from "path";
3
3
  import { homedir } from "os";
4
- // Constants
5
- const SESSION_DIR = join(homedir(), ".wave", "sessions");
6
- const VERSION = "1.0.0";
7
- const MAX_SESSION_AGE_DAYS = 30;
4
+ import { v6 as uuidv6 } from "uuid";
5
+ import { PathEncoder } from "../utils/pathEncoder.js";
6
+ import { JsonlHandler } from "../services/jsonlHandler.js";
7
+ import { extractLatestTotalTokens } from "../utils/tokenCalculation.js";
8
8
  /**
9
- * Resolve session directory path with fallback to default
10
- * @param sessionDir Optional custom session directory
11
- * @returns Resolved session directory path
9
+ * Generate a new UUIDv6-based session ID
10
+ * @returns UUIDv6 string for time-ordered sessions
12
11
  */
13
- export function resolveSessionDir(sessionDir) {
14
- return sessionDir || SESSION_DIR;
12
+ export function generateSessionId() {
13
+ return uuidv6();
15
14
  }
15
+ // Constants
16
+ export const SESSION_DIR = join(homedir(), ".wave", "projects");
17
+ const MAX_SESSION_AGE_DAYS = 14;
16
18
  /**
17
19
  * Ensure session directory exists
18
- * @param sessionDir Optional custom session directory
19
20
  */
20
- export async function ensureSessionDir(sessionDir) {
21
- const resolvedDir = resolveSessionDir(sessionDir);
21
+ export async function ensureSessionDir() {
22
22
  try {
23
- await fs.mkdir(resolvedDir, { recursive: true });
23
+ await fs.mkdir(SESSION_DIR, { recursive: true });
24
24
  }
25
25
  catch (error) {
26
26
  throw new Error(`Failed to create session directory: ${error}`);
27
27
  }
28
28
  }
29
29
  /**
30
- * Generate session file path
30
+ * Generate session file path using project-based directory structure
31
+ * Note: With metadata-based approach, we no longer need separate subagent directories
32
+ * @param sessionId - UUIDv6 session identifier
33
+ * @param workdir - Working directory for the session
34
+ * @returns Promise resolving to full file path for the session JSONL file
31
35
  */
32
- export function getSessionFilePath(sessionId, sessionDir) {
33
- const shortId = sessionId.split("_")[2] || sessionId.slice(-8);
34
- const resolvedDir = resolveSessionDir(sessionDir);
35
- return join(resolvedDir, `session_${shortId}.json`);
36
+ export async function getSessionFilePath(sessionId, workdir) {
37
+ const encoder = new PathEncoder();
38
+ const projectDir = await encoder.createProjectDirectory(workdir, SESSION_DIR);
39
+ // All sessions (main and subagent) now go in the same directory
40
+ // Session type is determined by metadata, not file path
41
+ return join(projectDir.encodedPath, `${sessionId}.jsonl`);
36
42
  }
37
43
  /**
38
- * Filter out diff blocks from messages to avoid saving unimportant data
44
+ * Create a new session with metadata
45
+ * @param sessionId - UUIDv6 session identifier
46
+ * @param workdir - Working directory for the session
47
+ * @param sessionType - Type of session ('main' or 'subagent')
48
+ * @param parentSessionId - Parent session ID for subagent sessions
49
+ * @param subagentType - Type of subagent for subagent sessions
39
50
  */
40
- function filterDiffBlocks(messages) {
41
- return messages
42
- .map((message) => ({
43
- ...message,
44
- blocks: message.blocks.filter((block) => block.type !== "diff"),
45
- }))
46
- .filter((message) => message.blocks.length > 0);
51
+ export async function createSession(sessionId, workdir, sessionType = "main", parentSessionId, subagentType) {
52
+ const jsonlHandler = new JsonlHandler();
53
+ const filePath = await getSessionFilePath(sessionId, workdir);
54
+ await jsonlHandler.createSession(filePath, sessionId, workdir, sessionType, parentSessionId, subagentType);
47
55
  }
48
56
  /**
49
- * Save session data to storage
57
+ * Append messages to session using JSONL format (new approach)
50
58
  *
51
- * @param sessionId - Unique identifier for the session
52
- * @param messages - Array of messages to save
59
+ * @param sessionId - UUIDv6 session identifier
60
+ * @param newMessages - Array of messages to append
53
61
  * @param workdir - Working directory for the session
54
- * @param latestTotalTokens - Total tokens used in the session
55
- * @param startedAt - ISO timestamp when session started (defaults to current time)
56
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
57
- * @throws {Error} When session cannot be saved due to permission or disk space issues
58
62
  */
59
- export async function saveSession(sessionId, messages, workdir, latestTotalTokens = 0, startedAt, sessionDir) {
63
+ export async function appendMessages(sessionId, newMessages, workdir) {
60
64
  // Do not save session files in test environment
61
65
  if (process.env.NODE_ENV === "test") {
62
66
  return;
63
67
  }
64
68
  // Do not save if there are no messages
65
- if (messages.length === 0) {
69
+ if (newMessages.length === 0) {
66
70
  return;
67
71
  }
68
- await ensureSessionDir(sessionDir);
69
- // Filter out diff blocks before saving
70
- const filteredMessages = filterDiffBlocks(messages);
71
- const now = new Date().toISOString();
72
- const sessionData = {
73
- id: sessionId,
74
- timestamp: now,
75
- version: VERSION,
76
- messages: filteredMessages,
77
- metadata: {
78
- workdir: workdir,
79
- startedAt: startedAt || now,
80
- lastActiveAt: now,
81
- latestTotalTokens,
82
- },
83
- };
84
- const filePath = getSessionFilePath(sessionId, sessionDir);
72
+ const jsonlHandler = new JsonlHandler();
73
+ const filePath = await getSessionFilePath(sessionId, workdir);
74
+ // Check if session file exists, throw error if it doesn't
85
75
  try {
86
- await fs.writeFile(filePath, JSON.stringify(sessionData, null, 2), "utf-8");
76
+ await fs.access(filePath);
87
77
  }
88
78
  catch (error) {
89
- throw new Error(`Failed to save session ${sessionId}: ${error}`);
79
+ if (error.code === "ENOENT") {
80
+ // File doesn't exist, throw error - sessions must be created explicitly
81
+ throw new Error(`Session file not found: ${sessionId}. Use createSession() to create a new session first.`);
82
+ }
83
+ else {
84
+ // Some other error accessing the file, re-throw it
85
+ throw error;
86
+ }
90
87
  }
88
+ const messagesWithTimestamp = newMessages.map((msg) => ({
89
+ timestamp: new Date().toISOString(),
90
+ ...msg,
91
+ }));
92
+ await jsonlHandler.append(filePath, messagesWithTimestamp, { atomic: false });
91
93
  }
92
94
  /**
93
- * Load session data from storage
95
+ * Load session data from JSONL file (new approach)
94
96
  *
95
- * @param sessionId - Unique identifier for the session to load
96
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
97
+ * @param sessionId - UUIDv6 session identifier
98
+ * @param workdir - Working directory for the session
97
99
  * @returns Promise that resolves to session data or null if session doesn't exist
98
- * @throws {Error} When session exists but cannot be read or contains invalid data
99
100
  */
100
- export async function loadSession(sessionId, sessionDir) {
101
- const filePath = getSessionFilePath(sessionId, sessionDir);
101
+ export async function loadSessionFromJsonl(sessionId, workdir) {
102
102
  try {
103
- const content = await fs.readFile(filePath, "utf-8");
104
- const sessionData = JSON.parse(content);
105
- // Validate session data format
106
- if (!sessionData.id || !sessionData.messages || !sessionData.metadata) {
107
- throw new Error("Invalid session data format");
103
+ const jsonlHandler = new JsonlHandler();
104
+ const filePath = await getSessionFilePath(sessionId, workdir);
105
+ const messages = await jsonlHandler.read(filePath);
106
+ if (messages.length === 0) {
107
+ return null;
108
108
  }
109
+ // Extract metadata from messages
110
+ const firstMessage = messages[0];
111
+ const lastMessage = messages[messages.length - 1];
112
+ const sessionData = {
113
+ id: sessionId,
114
+ messages: messages.map((msg) => {
115
+ // Remove timestamp property for backward compatibility
116
+ const { timestamp: _ignored, ...messageWithoutTimestamp } = msg;
117
+ void _ignored; // Use the variable to avoid eslint error
118
+ return messageWithoutTimestamp;
119
+ }),
120
+ metadata: {
121
+ workdir,
122
+ startedAt: firstMessage.timestamp,
123
+ lastActiveAt: lastMessage.timestamp,
124
+ latestTotalTokens: lastMessage.usage
125
+ ? extractLatestTotalTokens([lastMessage])
126
+ : 0,
127
+ },
128
+ };
109
129
  return sessionData;
110
130
  }
111
131
  catch (error) {
112
- if (error.code === "ENOENT") {
132
+ const errorMessage = error instanceof Error ? error.message : String(error);
133
+ // Check if the underlying error is ENOENT (file doesn't exist)
134
+ if (errorMessage.includes("ENOENT") ||
135
+ error.code === "ENOENT") {
113
136
  return null; // Session file does not exist
114
137
  }
138
+ // Check for JSON parsing errors (corrupted files)
139
+ if (errorMessage.includes("Invalid JSON") ||
140
+ errorMessage.includes("Unexpected token")) {
141
+ return null; // Treat corrupted files as non-existent
142
+ }
115
143
  throw new Error(`Failed to load session ${sessionId}: ${error}`);
116
144
  }
117
145
  }
118
146
  /**
119
- * Get the most recent session for a specific working directory
147
+ * Get the most recently active session for a specific working directory (new JSONL approach)
148
+ * Only returns main sessions, skips subagent sessions
149
+ * Sessions are sorted by last active time (most recently active first)
120
150
  *
121
- * @param workdir - Working directory to find the most recent session for
122
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
123
- * @returns Promise that resolves to the most recent session data or null if no sessions exist
124
- * @throws {Error} When session directory cannot be accessed or session data is corrupted
151
+ * @param workdir - Working directory to find the most recently active session for
152
+ * @returns Promise that resolves to the most recently active session data or null if no sessions exist
125
153
  */
126
- export async function getLatestSession(workdir, sessionDir) {
127
- const sessions = await listSessions(workdir, false, sessionDir);
154
+ export async function getLatestSessionFromJsonl(workdir) {
155
+ const sessions = await listSessionsFromJsonl(workdir, false); // Uses default includeSubagentSessions = false
128
156
  if (sessions.length === 0) {
129
157
  return null;
130
158
  }
131
- // Sort by last active time, return the latest
132
- const latestSession = sessions.sort((a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime())[0];
133
- return loadSession(latestSession.id, sessionDir);
159
+ // Sort by last active time (most recently active first)
160
+ const latestSession = sessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime())[0];
161
+ return loadSessionFromJsonl(latestSession.id, workdir);
162
+ }
163
+ /**
164
+ * List all sessions for a specific working directory (convenience wrapper)
165
+ * Only returns main sessions, skips subagent sessions
166
+ *
167
+ * @param workdir - Working directory to filter sessions by
168
+ * @returns Promise that resolves to array of session metadata objects
169
+ */
170
+ export async function listSessions(workdir) {
171
+ return listSessionsFromJsonl(workdir, false); // Uses default includeSubagentSessions = false
134
172
  }
135
173
  /**
136
- * List all sessions for a specific working directory or across all working directories
174
+ * List all sessions for a specific working directory using JSONL format (new approach)
137
175
  *
138
176
  * @param workdir - Working directory to filter sessions by
139
177
  * @param includeAllWorkdirs - If true, returns sessions from all working directories
140
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
178
+ * @param includeSubagentSessions - If true, includes subagent sessions (default: false for user-facing operations)
141
179
  * @returns Promise that resolves to array of session metadata objects
142
- * @throws {Error} When session directory cannot be accessed or read
143
180
  */
144
- export async function listSessions(workdir, includeAllWorkdirs = false, sessionDir) {
181
+ export async function listSessionsFromJsonl(workdir, includeAllWorkdirs = false, includeSubagentSessions = false) {
145
182
  try {
146
- await ensureSessionDir(sessionDir);
147
- const resolvedDir = resolveSessionDir(sessionDir);
148
- const files = await fs.readdir(resolvedDir);
149
- const sessions = [];
150
- for (const file of files) {
151
- if (!file.startsWith("session_") || !file.endsWith(".json")) {
152
- continue;
153
- }
154
- try {
155
- const filePath = join(resolvedDir, file);
156
- const content = await fs.readFile(filePath, "utf-8");
157
- const sessionData = JSON.parse(content);
158
- // Only return sessions for the current working directory, unless includeAllWorkdirs is true
159
- if (!includeAllWorkdirs && sessionData.metadata.workdir !== workdir) {
183
+ const encoder = new PathEncoder();
184
+ const baseDir = SESSION_DIR;
185
+ // If not including all workdirs, just scan the specific project directory
186
+ if (!includeAllWorkdirs) {
187
+ const projectDir = await encoder.createProjectDirectory(workdir, baseDir);
188
+ const files = await fs.readdir(projectDir.encodedPath);
189
+ const sessions = [];
190
+ for (const file of files) {
191
+ if (!file.endsWith(".jsonl")) {
192
+ continue;
193
+ }
194
+ const sessionId = file.replace(".jsonl", "");
195
+ try {
196
+ const jsonlHandler = new JsonlHandler();
197
+ const filePath = join(projectDir.encodedPath, file);
198
+ // Read metadata (efficient O(1) operation)
199
+ const metadata = await jsonlHandler.readMetadata(filePath);
200
+ if (metadata) {
201
+ // For lastActiveAt and latestTotalTokens, we need the last message
202
+ const lastMessage = await jsonlHandler.getLastMessage(filePath);
203
+ sessions.push({
204
+ id: sessionId,
205
+ sessionType: metadata.sessionType,
206
+ parentSessionId: metadata.parentSessionId,
207
+ subagentType: metadata.subagentType,
208
+ workdir: metadata.workdir,
209
+ startedAt: new Date(metadata.startedAt),
210
+ lastActiveAt: lastMessage
211
+ ? new Date(lastMessage.timestamp)
212
+ : new Date(metadata.startedAt),
213
+ latestTotalTokens: lastMessage?.usage
214
+ ? extractLatestTotalTokens([lastMessage])
215
+ : 0,
216
+ });
217
+ }
218
+ }
219
+ catch {
220
+ // Skip corrupted session files
160
221
  continue;
161
222
  }
162
- sessions.push({
163
- id: sessionData.id,
164
- timestamp: sessionData.timestamp,
165
- workdir: sessionData.metadata.workdir,
166
- startedAt: sessionData.metadata.startedAt,
167
- lastActiveAt: sessionData.metadata.lastActiveAt,
168
- latestTotalTokens: sessionData.metadata.latestTotalTokens,
169
- });
170
223
  }
171
- catch {
172
- // Skip corrupted session files and continue processing others
173
- continue;
224
+ // Sort by last active time (most recently active first)
225
+ const sortedSessions = sessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
226
+ // Filter out subagent sessions if requested
227
+ if (!includeSubagentSessions) {
228
+ return sortedSessions.filter((session) => session.sessionType === "main");
229
+ }
230
+ return sortedSessions;
231
+ }
232
+ // For all workdirs, scan all project directories
233
+ const sessions = [];
234
+ try {
235
+ const projectDirs = await fs.readdir(baseDir);
236
+ for (const projectDirName of projectDirs) {
237
+ const projectPath = join(baseDir, projectDirName);
238
+ const stat = await fs.stat(projectPath);
239
+ if (!stat.isDirectory()) {
240
+ continue;
241
+ }
242
+ const files = await fs.readdir(projectPath);
243
+ for (const file of files) {
244
+ if (!file.endsWith(".jsonl")) {
245
+ continue;
246
+ }
247
+ const sessionId = file.replace(".jsonl", "");
248
+ try {
249
+ const jsonlHandler = new JsonlHandler();
250
+ const filePath = join(projectPath, file);
251
+ // Read metadata (efficient O(1) operation)
252
+ const metadata = await jsonlHandler.readMetadata(filePath);
253
+ if (metadata) {
254
+ // For lastActiveAt and latestTotalTokens, we need the last message
255
+ const lastMessage = await jsonlHandler.getLastMessage(filePath);
256
+ sessions.push({
257
+ id: sessionId,
258
+ sessionType: metadata.sessionType,
259
+ parentSessionId: metadata.parentSessionId,
260
+ subagentType: metadata.subagentType,
261
+ workdir: metadata.workdir,
262
+ startedAt: new Date(metadata.startedAt),
263
+ lastActiveAt: lastMessage
264
+ ? new Date(lastMessage.timestamp)
265
+ : new Date(metadata.startedAt),
266
+ latestTotalTokens: lastMessage?.usage
267
+ ? extractLatestTotalTokens([lastMessage])
268
+ : 0,
269
+ });
270
+ }
271
+ }
272
+ catch {
273
+ // Skip corrupted session files
274
+ continue;
275
+ }
276
+ }
174
277
  }
175
278
  }
176
- return sessions.sort((a, b) => new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime());
279
+ catch {
280
+ // If base directory doesn't exist, return empty array
281
+ return [];
282
+ }
283
+ // Sort by last active time (most recently active first)
284
+ const sortedSessions = sessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
285
+ // Filter out subagent sessions if requested
286
+ if (!includeSubagentSessions) {
287
+ return sortedSessions.filter((session) => session.sessionType === "main");
288
+ }
289
+ return sortedSessions;
177
290
  }
178
291
  catch (error) {
179
292
  throw new Error(`Failed to list sessions: ${error}`);
180
293
  }
181
294
  }
182
295
  /**
183
- * Delete a session from storage
296
+ * Delete a session from JSONL storage (new approach)
184
297
  *
185
- * @param sessionId - Unique identifier for the session to delete
186
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
298
+ * @param sessionId - UUIDv6 session identifier
299
+ * @param workdir - Working directory for the session
187
300
  * @returns Promise that resolves to true if session was deleted, false if it didn't exist
188
- * @throws {Error} When session exists but cannot be deleted due to permission issues
189
301
  */
190
- export async function deleteSession(sessionId, sessionDir) {
191
- const filePath = getSessionFilePath(sessionId, sessionDir);
302
+ export async function deleteSessionFromJsonl(sessionId, workdir) {
192
303
  try {
304
+ const filePath = await getSessionFilePath(sessionId, workdir);
193
305
  await fs.unlink(filePath);
306
+ // Try to clean up empty project directory
307
+ const encoder = new PathEncoder();
308
+ const projectDir = await encoder.createProjectDirectory(workdir, SESSION_DIR);
309
+ try {
310
+ const files = await fs.readdir(projectDir.encodedPath);
311
+ if (files.length === 0) {
312
+ await fs.rmdir(projectDir.encodedPath);
313
+ }
314
+ }
315
+ catch {
316
+ // Ignore errors if directory is not empty or can't be removed
317
+ }
194
318
  return true;
195
319
  }
196
320
  catch (error) {
@@ -201,47 +325,101 @@ export async function deleteSession(sessionId, sessionDir) {
201
325
  }
202
326
  }
203
327
  /**
204
- * Clean up expired sessions older than the configured maximum age
328
+ * Clean up expired sessions older than 14 days based on file modification time
205
329
  *
206
330
  * @param workdir - Working directory to clean up sessions for
207
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
208
331
  * @returns Promise that resolves to the number of sessions that were deleted
209
- * @throws {Error} When session directory cannot be accessed or sessions cannot be deleted
210
332
  */
211
- export async function cleanupExpiredSessions(workdir, sessionDir) {
333
+ export async function cleanupExpiredSessionsFromJsonl(workdir) {
212
334
  // Do not perform cleanup operations in test environment
213
335
  if (process.env.NODE_ENV === "test") {
214
336
  return 0;
215
337
  }
216
- const sessions = await listSessions(workdir, true, sessionDir);
217
- const now = new Date();
218
- const maxAge = MAX_SESSION_AGE_DAYS * 24 * 60 * 60 * 1000; // Convert to milliseconds
219
- let deletedCount = 0;
220
- for (const session of sessions) {
221
- const sessionAge = now.getTime() - new Date(session.lastActiveAt).getTime();
222
- if (sessionAge > maxAge) {
338
+ try {
339
+ const encoder = new PathEncoder();
340
+ const projectDir = await encoder.createProjectDirectory(workdir, SESSION_DIR);
341
+ const files = await fs.readdir(projectDir.encodedPath);
342
+ const now = new Date();
343
+ const maxAge = MAX_SESSION_AGE_DAYS * 24 * 60 * 60 * 1000; // Convert to milliseconds
344
+ let deletedCount = 0;
345
+ for (const file of files) {
346
+ if (!file.endsWith(".jsonl")) {
347
+ continue;
348
+ }
349
+ const filePath = join(projectDir.encodedPath, file);
223
350
  try {
224
- await deleteSession(session.id, sessionDir);
225
- deletedCount++;
351
+ const stat = await fs.stat(filePath);
352
+ const fileAge = now.getTime() - stat.mtime.getTime();
353
+ if (fileAge > maxAge) {
354
+ await fs.unlink(filePath);
355
+ deletedCount++;
356
+ }
226
357
  }
227
358
  catch {
228
- // Skip failed deletions and continue processing other sessions
359
+ // Skip failed operations and continue processing other files
229
360
  continue;
230
361
  }
231
362
  }
363
+ // Clean up empty project directory if no files remain
364
+ try {
365
+ const remainingFiles = await fs.readdir(projectDir.encodedPath);
366
+ if (remainingFiles.length === 0) {
367
+ await fs.rmdir(projectDir.encodedPath);
368
+ }
369
+ }
370
+ catch {
371
+ // Ignore errors if directory is not empty or can't be removed
372
+ }
373
+ return deletedCount;
374
+ }
375
+ catch {
376
+ // Return 0 if project directory doesn't exist or can't be accessed
377
+ return 0;
232
378
  }
233
- return deletedCount;
234
379
  }
235
380
  /**
236
- * Check if a session exists in storage
381
+ * Clean up empty project directories in the session directory
382
+ */
383
+ export async function cleanupEmptyProjectDirectories() {
384
+ // Do not perform cleanup operations in test environment
385
+ if (process.env.NODE_ENV === "test") {
386
+ return;
387
+ }
388
+ try {
389
+ const baseDir = SESSION_DIR;
390
+ const projectDirs = await fs.readdir(baseDir);
391
+ for (const projectDirName of projectDirs) {
392
+ const projectPath = join(baseDir, projectDirName);
393
+ const stat = await fs.stat(projectPath);
394
+ if (stat.isDirectory()) {
395
+ try {
396
+ const files = await fs.readdir(projectPath);
397
+ // If directory is empty, remove it
398
+ if (files.length === 0) {
399
+ await fs.rmdir(projectPath);
400
+ }
401
+ }
402
+ catch {
403
+ // Skip errors for directories we can't read or remove
404
+ continue;
405
+ }
406
+ }
407
+ }
408
+ }
409
+ catch {
410
+ // Ignore errors if base directory doesn't exist or can't be accessed
411
+ }
412
+ }
413
+ /**
414
+ * Check if a session exists in JSONL storage (new approach)
237
415
  *
238
- * @param sessionId - Unique identifier for the session to check
239
- * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
416
+ * @param sessionId - UUIDv6 session identifier
417
+ * @param workdir - Working directory for the session
240
418
  * @returns Promise that resolves to true if session exists, false otherwise
241
419
  */
242
- export async function sessionExists(sessionId, sessionDir) {
243
- const filePath = getSessionFilePath(sessionId, sessionDir);
420
+ export async function sessionExistsInJsonl(sessionId, workdir) {
244
421
  try {
422
+ const filePath = await getSessionFilePath(sessionId, workdir);
245
423
  await fs.access(filePath);
246
424
  return true;
247
425
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bashTool.d.ts","sourceRoot":"","sources":["../../src/tools/bashTool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UA2NtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,UAwF5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,UA8E1B,CAAC"}
1
+ {"version":3,"file":"bashTool.d.ts","sourceRoot":"","sources":["../../src/tools/bashTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UA2NtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,UAwF5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,UA8E1B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "child_process";
2
+ import { logger } from "../utils/globalLogger.js";
2
3
  /**
3
4
  * Bash command execution tool - supports both foreground and background execution
4
5
  */
@@ -112,8 +113,8 @@ export const bashTool = {
112
113
  try {
113
114
  process.kill(-child.pid, "SIGKILL");
114
115
  }
115
- catch {
116
- // logger.error("Failed to force kill process:", killError);
116
+ catch (killError) {
117
+ logger.error("Failed to force kill process:", killError);
117
118
  }
118
119
  }
119
120
  }, 1000);
@@ -128,8 +129,8 @@ export const bashTool = {
128
129
  }
129
130
  }, 1000);
130
131
  }
131
- catch {
132
- // logger.error("Failed to kill child process:", directKillError);
132
+ catch (directKillError) {
133
+ logger.error("Failed to kill child process:", directKillError);
133
134
  }
134
135
  }
135
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deleteFileTool.d.ts","sourceRoot":"","sources":["../../src/tools/deleteFileTool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAGtE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,UAyE5B,CAAC"}
1
+ {"version":3,"file":"deleteFileTool.d.ts","sourceRoot":"","sources":["../../src/tools/deleteFileTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAGtE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,UAyE5B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { unlink } from "fs/promises";
2
+ import { logger } from "../utils/globalLogger.js";
2
3
  import { resolvePath, getDisplayPath } from "../utils/path.js";
3
4
  /**
4
5
  * Delete file tool plugin
@@ -38,7 +39,7 @@ export const deleteFileTool = {
38
39
  const filePath = resolvePath(targetFile, context.workdir);
39
40
  // Delete file
40
41
  await unlink(filePath);
41
- // logger.debug(`Successfully deleted file: ${filePath}`);
42
+ logger.debug(`Successfully deleted file: ${filePath}`);
42
43
  return {
43
44
  success: true,
44
45
  content: `Successfully deleted file: ${targetFile}`,
@@ -1 +1 @@
1
- {"version":3,"file":"editTool.d.ts","sourceRoot":"","sources":["../../src/tools/editTool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAetE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UAqKtB,CAAC"}
1
+ {"version":3,"file":"editTool.d.ts","sourceRoot":"","sources":["../../src/tools/editTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAetE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,UAqKtB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { readFile, writeFile } from "fs/promises";
2
+ import { logger } from "../utils/globalLogger.js";
2
3
  import { resolvePath, getDisplayPath } from "../utils/path.js";
3
4
  import { diffLines } from "diff";
4
5
  /**
@@ -138,7 +139,7 @@ export const editTool = {
138
139
  const shortResult = replaceAll
139
140
  ? `Replaced ${replacementCount} instances`
140
141
  : "Text replaced successfully";
141
- // logger.debug(`Edit tool: ${shortResult}`);
142
+ logger.debug(`Edit tool: ${shortResult}`);
142
143
  return {
143
144
  success: true,
144
145
  content: shortResult,
@@ -151,7 +152,7 @@ export const editTool = {
151
152
  }
152
153
  catch (error) {
153
154
  const errorMessage = error instanceof Error ? error.message : String(error);
154
- // logger.error(`Edit tool error: ${errorMessage}`);
155
+ logger.error(`Edit tool error: ${errorMessage}`);
155
156
  return {
156
157
  success: false,
157
158
  content: "",
@@ -1 +1 @@
1
- {"version":3,"file":"multiEditTool.d.ts","sourceRoot":"","sources":["../../src/tools/multiEditTool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAwBtE;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAwO3B,CAAC"}
1
+ {"version":3,"file":"multiEditTool.d.ts","sourceRoot":"","sources":["../../src/tools/multiEditTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAwBtE;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAwO3B,CAAC"}