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.
- package/dist/agent.d.ts +32 -20
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +209 -24
- package/dist/constants/events.d.ts +28 -0
- package/dist/constants/events.d.ts.map +1 -0
- package/dist/constants/events.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/managers/aiManager.d.ts +34 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +248 -132
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +7 -6
- package/dist/managers/hookManager.d.ts +13 -16
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +81 -44
- package/dist/managers/liveConfigManager.d.ts +58 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +160 -0
- package/dist/managers/messageManager.d.ts +41 -24
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +168 -49
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +9 -3
- package/dist/managers/subagentManager.d.ts +51 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +190 -19
- package/dist/services/aiService.d.ts +13 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +350 -74
- package/dist/services/configurationWatcher.d.ts +120 -0
- package/dist/services/configurationWatcher.d.ts.map +1 -0
- package/dist/services/configurationWatcher.js +439 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +213 -0
- package/dist/services/hook.d.ts +91 -9
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +393 -43
- package/dist/services/jsonlHandler.d.ts +62 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +257 -0
- package/dist/services/memory.d.ts +9 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +81 -12
- package/dist/services/memoryStore.d.ts +81 -0
- package/dist/services/memoryStore.d.ts.map +1 -0
- package/dist/services/memoryStore.js +200 -0
- package/dist/services/session.d.ts +64 -49
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +310 -132
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +5 -4
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +2 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +3 -2
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +4 -3
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +2 -1
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +3 -10
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +5 -6
- package/dist/types/commands.d.ts +4 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/core.d.ts +35 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +42 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/hooks.d.ts +8 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/memoryStore.d.ts +82 -0
- package/dist/types/memoryStore.d.ts.map +1 -0
- package/dist/types/memoryStore.js +7 -0
- package/dist/types/messaging.d.ts +21 -9
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/messaging.js +5 -1
- package/dist/types/session.d.ts +20 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +27 -26
- package/dist/utils/cacheControlUtils.d.ts +121 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +367 -0
- package/dist/utils/commandPathResolver.d.ts +52 -0
- package/dist/utils/commandPathResolver.d.ts.map +1 -0
- package/dist/utils/commandPathResolver.js +145 -0
- package/dist/utils/configPaths.d.ts +85 -0
- package/dist/utils/configPaths.d.ts.map +1 -0
- package/dist/utils/configPaths.js +121 -0
- package/dist/utils/configResolver.d.ts +37 -10
- package/dist/utils/configResolver.d.ts.map +1 -1
- package/dist/utils/configResolver.js +127 -23
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +8 -13
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileUtils.d.ts +15 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +61 -0
- package/dist/utils/globalLogger.d.ts +102 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +136 -0
- package/dist/utils/hookMatcher.d.ts +1 -6
- package/dist/utils/hookMatcher.d.ts.map +1 -1
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +27 -27
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +46 -36
- package/dist/utils/pathEncoder.d.ts +104 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +272 -0
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +2 -1
- package/dist/utils/tokenCalculation.d.ts +26 -0
- package/dist/utils/tokenCalculation.d.ts.map +1 -0
- package/dist/utils/tokenCalculation.js +36 -0
- package/package.json +6 -3
- package/src/agent.ts +301 -37
- package/src/constants/events.ts +38 -0
- package/src/index.ts +2 -0
- package/src/managers/aiManager.ts +325 -173
- package/src/managers/backgroundBashManager.ts +7 -6
- package/src/managers/hookManager.ts +106 -84
- package/src/managers/liveConfigManager.ts +248 -0
- package/src/managers/messageManager.ts +237 -100
- package/src/managers/slashCommandManager.ts +9 -7
- package/src/managers/subagentManager.ts +284 -22
- package/src/services/aiService.ts +474 -83
- package/src/services/configurationWatcher.ts +622 -0
- package/src/services/fileWatcher.ts +301 -0
- package/src/services/hook.ts +538 -47
- package/src/services/jsonlHandler.ts +319 -0
- package/src/services/memory.ts +92 -12
- package/src/services/memoryStore.ts +279 -0
- package/src/services/session.ts +381 -157
- package/src/tools/bashTool.ts +5 -4
- package/src/tools/deleteFileTool.ts +2 -1
- package/src/tools/editTool.ts +3 -2
- package/src/tools/multiEditTool.ts +4 -3
- package/src/tools/readTool.ts +2 -1
- package/src/tools/todoWriteTool.ts +3 -11
- package/src/tools/writeTool.ts +7 -6
- package/src/types/commands.ts +6 -0
- package/src/types/core.ts +44 -0
- package/src/types/environment.ts +60 -0
- package/src/types/hooks.ts +21 -8
- package/src/types/index.ts +2 -0
- package/src/types/memoryStore.ts +94 -0
- package/src/types/messaging.ts +21 -10
- package/src/types/session.ts +25 -0
- package/src/utils/bashHistory.ts +27 -27
- package/src/utils/cacheControlUtils.ts +540 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/configResolver.ts +182 -22
- package/src/utils/constants.ts +1 -1
- package/src/utils/convertMessagesForAPI.ts +8 -14
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileUtils.ts +65 -0
- package/src/utils/globalLogger.ts +145 -0
- package/src/utils/hookMatcher.ts +1 -12
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +77 -60
- package/src/utils/pathEncoder.ts +379 -0
- package/src/utils/subagentParser.ts +2 -1
- package/src/utils/tokenCalculation.ts +43 -0
- package/src/types/index.ts.backup +0 -357
package/dist/services/session.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
*
|
|
10
|
-
* @
|
|
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
|
|
14
|
-
return
|
|
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(
|
|
21
|
-
const resolvedDir = resolveSessionDir(sessionDir);
|
|
21
|
+
export async function ensureSessionDir() {
|
|
22
22
|
try {
|
|
23
|
-
await fs.mkdir(
|
|
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,
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
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
|
-
*
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
*
|
|
57
|
+
* Append messages to session using JSONL format (new approach)
|
|
50
58
|
*
|
|
51
|
-
* @param sessionId -
|
|
52
|
-
* @param
|
|
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
|
|
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 (
|
|
69
|
+
if (newMessages.length === 0) {
|
|
66
70
|
return;
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
76
|
+
await fs.access(filePath);
|
|
87
77
|
}
|
|
88
78
|
catch (error) {
|
|
89
|
-
|
|
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
|
|
95
|
+
* Load session data from JSONL file (new approach)
|
|
94
96
|
*
|
|
95
|
-
* @param sessionId -
|
|
96
|
-
* @param
|
|
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
|
|
101
|
-
const filePath = getSessionFilePath(sessionId, sessionDir);
|
|
101
|
+
export async function loadSessionFromJsonl(sessionId, workdir) {
|
|
102
102
|
try {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
122
|
-
* @
|
|
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
|
|
127
|
-
const sessions = await
|
|
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
|
|
132
|
-
const latestSession = sessions.sort((a, b) =>
|
|
133
|
-
return
|
|
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
|
|
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
|
|
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
|
|
181
|
+
export async function listSessionsFromJsonl(workdir, includeAllWorkdirs = false, includeSubagentSessions = false) {
|
|
145
182
|
try {
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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 -
|
|
186
|
-
* @param
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
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
|
|
225
|
-
|
|
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
|
|
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
|
-
*
|
|
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 -
|
|
239
|
-
* @param
|
|
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
|
|
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":"
|
|
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"}
|
package/dist/tools/bashTool.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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":"
|
|
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
|
-
|
|
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":"
|
|
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"}
|
package/dist/tools/editTool.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|