wave-agent-sdk 0.0.8 → 0.0.11
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 +92 -23
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +351 -137
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/managers/aiManager.d.ts +14 -36
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +74 -77
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +4 -3
- package/dist/managers/hookManager.d.ts +3 -8
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +39 -29
- package/dist/managers/liveConfigManager.d.ts +55 -18
- package/dist/managers/liveConfigManager.d.ts.map +1 -1
- package/dist/managers/liveConfigManager.js +372 -90
- package/dist/managers/lspManager.d.ts +43 -0
- package/dist/managers/lspManager.d.ts.map +1 -0
- package/dist/managers/lspManager.js +326 -0
- package/dist/managers/messageManager.d.ts +8 -16
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +52 -74
- package/dist/managers/permissionManager.d.ts +75 -0
- package/dist/managers/permissionManager.d.ts.map +1 -0
- package/dist/managers/permissionManager.js +368 -0
- package/dist/managers/skillManager.d.ts +1 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +2 -1
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +0 -1
- package/dist/managers/subagentManager.d.ts +8 -23
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +97 -117
- package/dist/managers/toolManager.d.ts +38 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +66 -2
- package/dist/services/aiService.d.ts +3 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +123 -30
- package/dist/services/configurationService.d.ts +116 -0
- package/dist/services/configurationService.d.ts.map +1 -0
- package/dist/services/configurationService.js +585 -0
- package/dist/services/fileWatcher.d.ts.map +1 -1
- package/dist/services/fileWatcher.js +5 -6
- package/dist/services/hook.d.ts +7 -124
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +46 -458
- package/dist/services/jsonlHandler.d.ts +24 -15
- package/dist/services/jsonlHandler.d.ts.map +1 -1
- package/dist/services/jsonlHandler.js +67 -88
- package/dist/services/memory.d.ts +0 -9
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +2 -49
- package/dist/services/session.d.ts +82 -33
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +275 -181
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +109 -11
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +25 -0
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +30 -6
- package/dist/tools/lspTool.d.ts +6 -0
- package/dist/tools/lspTool.d.ts.map +1 -0
- package/dist/tools/lspTool.js +589 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +26 -7
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +111 -2
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +23 -0
- package/dist/tools/types.d.ts +11 -8
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +25 -9
- package/dist/types/commands.d.ts +0 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +69 -0
- package/dist/types/configuration.d.ts.map +1 -0
- package/dist/types/configuration.js +8 -0
- package/dist/types/core.d.ts +10 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +41 -0
- package/dist/types/environment.d.ts.map +1 -1
- package/dist/types/fileSearch.d.ts +5 -0
- package/dist/types/fileSearch.d.ts.map +1 -0
- package/dist/types/fileSearch.js +1 -0
- package/dist/types/hooks.d.ts +11 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -7
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -0
- package/dist/types/lsp.d.ts +90 -0
- package/dist/types/lsp.d.ts.map +1 -0
- package/dist/types/lsp.js +4 -0
- package/dist/types/messaging.d.ts +6 -11
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/permissions.d.ts +39 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +12 -0
- package/dist/types/session.d.ts +1 -6
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/types/tools.d.ts +35 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +4 -0
- package/dist/utils/abortUtils.d.ts +34 -0
- package/dist/utils/abortUtils.d.ts.map +1 -0
- package/dist/utils/abortUtils.js +92 -0
- package/dist/utils/bashHistory.d.ts +4 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +21 -4
- package/dist/utils/bashParser.d.ts +24 -0
- package/dist/utils/bashParser.d.ts.map +1 -0
- package/dist/utils/bashParser.js +413 -0
- package/dist/utils/builtinSubagents.d.ts +7 -0
- package/dist/utils/builtinSubagents.d.ts.map +1 -0
- package/dist/utils/builtinSubagents.js +65 -0
- package/dist/utils/cacheControlUtils.d.ts +8 -33
- package/dist/utils/cacheControlUtils.d.ts.map +1 -1
- package/dist/utils/cacheControlUtils.js +83 -126
- package/dist/utils/constants.d.ts +0 -12
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +1 -13
- package/dist/utils/convertMessagesForAPI.d.ts +2 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +33 -14
- package/dist/utils/fileSearch.d.ts +14 -0
- package/dist/utils/fileSearch.d.ts.map +1 -0
- package/dist/utils/fileSearch.js +88 -0
- package/dist/utils/fileUtils.d.ts +14 -2
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +101 -17
- package/dist/utils/globalLogger.d.ts +0 -14
- package/dist/utils/globalLogger.d.ts.map +1 -1
- package/dist/utils/globalLogger.js +0 -16
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +1 -17
- package/dist/utils/messageOperations.d.ts +1 -11
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +7 -24
- package/dist/utils/pathEncoder.d.ts +4 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -1
- package/dist/utils/pathEncoder.js +16 -9
- package/dist/utils/pathSafety.d.ts +10 -0
- package/dist/utils/pathSafety.d.ts.map +1 -0
- package/dist/utils/pathSafety.js +23 -0
- package/dist/utils/subagentParser.d.ts +2 -2
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +10 -7
- package/package.json +9 -9
- package/src/agent.ts +475 -216
- package/src/index.ts +3 -0
- package/src/managers/aiManager.ts +107 -111
- package/src/managers/backgroundBashManager.ts +4 -3
- package/src/managers/hookManager.ts +44 -39
- package/src/managers/liveConfigManager.ts +524 -138
- package/src/managers/lspManager.ts +434 -0
- package/src/managers/messageManager.ts +73 -103
- package/src/managers/permissionManager.ts +480 -0
- package/src/managers/skillManager.ts +3 -1
- package/src/managers/slashCommandManager.ts +1 -2
- package/src/managers/subagentManager.ts +116 -159
- package/src/managers/toolManager.ts +95 -3
- package/src/services/aiService.ts +207 -26
- package/src/services/configurationService.ts +762 -0
- package/src/services/fileWatcher.ts +5 -6
- package/src/services/hook.ts +50 -631
- package/src/services/jsonlHandler.ts +84 -100
- package/src/services/memory.ts +2 -59
- package/src/services/session.ts +338 -213
- package/src/tools/bashTool.ts +126 -13
- package/src/tools/deleteFileTool.ts +36 -0
- package/src/tools/editTool.ts +41 -7
- package/src/tools/lspTool.ts +760 -0
- package/src/tools/multiEditTool.ts +37 -8
- package/src/tools/readTool.ts +125 -2
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/todoWriteTool.ts +33 -1
- package/src/tools/types.ts +15 -9
- package/src/tools/writeTool.ts +36 -10
- package/src/types/commands.ts +0 -1
- package/src/types/config.ts +5 -0
- package/src/types/configuration.ts +73 -0
- package/src/types/core.ts +11 -0
- package/src/types/environment.ts +44 -0
- package/src/types/fileSearch.ts +4 -0
- package/src/types/hooks.ts +14 -11
- package/src/types/index.ts +5 -0
- package/src/types/lsp.ts +96 -0
- package/src/types/messaging.ts +8 -13
- package/src/types/permissions.ts +52 -0
- package/src/types/session.ts +3 -8
- package/src/types/skills.ts +1 -0
- package/src/types/tools.ts +38 -0
- package/src/utils/abortUtils.ts +118 -0
- package/src/utils/bashHistory.ts +28 -4
- package/src/utils/bashParser.ts +444 -0
- package/src/utils/builtinSubagents.ts +71 -0
- package/src/utils/cacheControlUtils.ts +106 -171
- package/src/utils/constants.ts +1 -16
- package/src/utils/convertMessagesForAPI.ts +38 -14
- package/src/utils/fileSearch.ts +107 -0
- package/src/utils/fileUtils.ts +114 -19
- package/src/utils/globalLogger.ts +0 -17
- package/src/utils/markdownParser.ts +1 -19
- package/src/utils/messageOperations.ts +7 -35
- package/src/utils/pathEncoder.ts +24 -9
- package/src/utils/pathSafety.ts +26 -0
- package/src/utils/subagentParser.ts +11 -8
- package/dist/constants/events.d.ts +0 -28
- package/dist/constants/events.d.ts.map +0 -1
- package/dist/constants/events.js +0 -27
- package/dist/services/configurationWatcher.d.ts +0 -120
- package/dist/services/configurationWatcher.d.ts.map +0 -1
- package/dist/services/configurationWatcher.js +0 -439
- package/dist/services/memoryStore.d.ts +0 -81
- package/dist/services/memoryStore.d.ts.map +0 -1
- package/dist/services/memoryStore.js +0 -200
- package/dist/types/memoryStore.d.ts +0 -82
- package/dist/types/memoryStore.d.ts.map +0 -1
- package/dist/types/memoryStore.js +0 -7
- package/dist/utils/configResolver.d.ts +0 -65
- package/dist/utils/configResolver.d.ts.map +0 -1
- package/dist/utils/configResolver.js +0 -210
- package/src/constants/events.ts +0 -38
- package/src/services/configurationWatcher.ts +0 -622
- package/src/services/memoryStore.ts +0 -279
- package/src/types/memoryStore.ts +0 -94
- package/src/utils/configResolver.ts +0 -302
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage,
|
|
1
|
+
import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addUserMessageToMessages, extractUserInputHistory, addMemoryBlockToMessage, addCommandOutputMessage, updateCommandOutputInMessage, completeCommandInMessage, addSubagentBlockToMessage, updateSubagentBlockInMessage, removeLastUserMessage, } from "../utils/messageOperations.js";
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { appendMessages, createSession, generateSessionId, SESSION_DIR, } from "../services/session.js";
|
|
4
4
|
import { pathEncoder } from "../utils/pathEncoder.js";
|
|
5
5
|
export class MessageManager {
|
|
6
6
|
constructor(options) {
|
|
@@ -8,14 +8,12 @@ export class MessageManager {
|
|
|
8
8
|
this.messages = [];
|
|
9
9
|
this.latestTotalTokens = 0;
|
|
10
10
|
this.userInputHistory = [];
|
|
11
|
-
this.sessionStartTime = new Date().toISOString();
|
|
12
11
|
this.workdir = options.workdir;
|
|
13
12
|
this.encodedWorkdir = pathEncoder.encodeSync(this.workdir); // Cache encoded workdir
|
|
14
13
|
this.callbacks = options.callbacks;
|
|
15
14
|
this.logger = options.logger;
|
|
16
15
|
this.savedMessageCount = 0; // Initialize saved message count tracker
|
|
17
16
|
this.sessionType = options.sessionType || "main";
|
|
18
|
-
this.parentSessionId = options.parentSessionId;
|
|
19
17
|
this.subagentType = options.subagentType;
|
|
20
18
|
// Compute and cache the transcript path
|
|
21
19
|
this.transcriptPath = this.computeTranscriptPath();
|
|
@@ -33,9 +31,6 @@ export class MessageManager {
|
|
|
33
31
|
getUserInputHistory() {
|
|
34
32
|
return [...this.userInputHistory];
|
|
35
33
|
}
|
|
36
|
-
getSessionStartTime() {
|
|
37
|
-
return this.sessionStartTime;
|
|
38
|
-
}
|
|
39
34
|
getWorkdir() {
|
|
40
35
|
return this.workdir;
|
|
41
36
|
}
|
|
@@ -71,7 +66,7 @@ export class MessageManager {
|
|
|
71
66
|
*/
|
|
72
67
|
async createSessionIfNeeded() {
|
|
73
68
|
try {
|
|
74
|
-
await createSession(this.sessionId, this.workdir, this.sessionType
|
|
69
|
+
await createSession(this.sessionId, this.workdir, this.sessionType);
|
|
75
70
|
}
|
|
76
71
|
catch (error) {
|
|
77
72
|
this.logger?.error("Failed to create session:", error);
|
|
@@ -99,7 +94,7 @@ export class MessageManager {
|
|
|
99
94
|
}
|
|
100
95
|
// Use JSONL format for new sessions
|
|
101
96
|
await appendMessages(this.sessionId, unsavedMessages, // Only append new messages
|
|
102
|
-
this.workdir);
|
|
97
|
+
this.workdir, this.sessionType);
|
|
103
98
|
// Update the saved message count
|
|
104
99
|
this.savedMessageCount = this.messages.length;
|
|
105
100
|
}
|
|
@@ -107,46 +102,6 @@ export class MessageManager {
|
|
|
107
102
|
this.logger?.error("Failed to save session:", error);
|
|
108
103
|
}
|
|
109
104
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Handle session restoration logic
|
|
112
|
-
*/
|
|
113
|
-
async handleSessionRestoration(restoreSessionId, continueLastSession) {
|
|
114
|
-
// Clean up expired sessions first
|
|
115
|
-
cleanupExpiredSessionsFromJsonl(this.workdir).catch((error) => {
|
|
116
|
-
this.logger?.warn("Failed to cleanup expired sessions:", error);
|
|
117
|
-
});
|
|
118
|
-
if (!restoreSessionId && !continueLastSession) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
let sessionToRestore = null;
|
|
123
|
-
if (restoreSessionId) {
|
|
124
|
-
// Use only JSONL format - no legacy support
|
|
125
|
-
sessionToRestore = await loadSessionFromJsonl(restoreSessionId, this.workdir);
|
|
126
|
-
if (!sessionToRestore) {
|
|
127
|
-
console.error(`Session not found: ${restoreSessionId}`);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else if (continueLastSession) {
|
|
132
|
-
// Use only JSONL format - no legacy support
|
|
133
|
-
sessionToRestore = await getLatestSessionFromJsonl(this.workdir);
|
|
134
|
-
if (!sessionToRestore) {
|
|
135
|
-
console.error(`No previous session found for workdir: ${this.workdir}`);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (sessionToRestore) {
|
|
140
|
-
console.log(`Restoring session: ${sessionToRestore.id}`);
|
|
141
|
-
// Initialize from session data
|
|
142
|
-
this.initializeFromSession(sessionToRestore);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
console.error("Failed to restore session:", error);
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
105
|
setlatestTotalTokens(latestTotalTokens) {
|
|
151
106
|
if (this.latestTotalTokens !== latestTotalTokens) {
|
|
152
107
|
this.latestTotalTokens = latestTotalTokens;
|
|
@@ -165,7 +120,6 @@ export class MessageManager {
|
|
|
165
120
|
this.setUserInputHistory([]);
|
|
166
121
|
this.setSessionId(generateSessionId());
|
|
167
122
|
this.setlatestTotalTokens(0);
|
|
168
|
-
this.sessionStartTime = new Date().toISOString();
|
|
169
123
|
this.savedMessageCount = 0; // Reset saved message count
|
|
170
124
|
}
|
|
171
125
|
// Initialize state from session data
|
|
@@ -173,8 +127,6 @@ export class MessageManager {
|
|
|
173
127
|
this.setSessionId(sessionData.id);
|
|
174
128
|
this.setMessages([...sessionData.messages]);
|
|
175
129
|
this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
|
|
176
|
-
// Restore the original session start time
|
|
177
|
-
this.sessionStartTime = sessionData.metadata.startedAt;
|
|
178
130
|
// Extract user input history from session messages
|
|
179
131
|
this.setUserInputHistory(extractUserInputHistory(sessionData.messages));
|
|
180
132
|
// Set saved message count to the number of loaded messages since they're already saved
|
|
@@ -205,36 +157,36 @@ export class MessageManager {
|
|
|
205
157
|
this.callbacks.onUserMessageAdded?.(params);
|
|
206
158
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
207
159
|
}
|
|
208
|
-
addAssistantMessage(content, toolCalls, usage,
|
|
209
|
-
const
|
|
210
|
-
? Object.fromEntries(Object.entries(
|
|
160
|
+
addAssistantMessage(content, toolCalls, usage, additionalFields) {
|
|
161
|
+
const additionalFieldsRecord = additionalFields
|
|
162
|
+
? Object.fromEntries(Object.entries(additionalFields).filter(([, value]) => value !== undefined))
|
|
211
163
|
: undefined;
|
|
212
|
-
const newMessages = addAssistantMessageToMessages(this.messages, content, toolCalls, usage,
|
|
164
|
+
const newMessages = addAssistantMessageToMessages(this.messages, content, toolCalls, usage, additionalFieldsRecord);
|
|
213
165
|
this.setMessages(newMessages);
|
|
214
166
|
this.callbacks.onAssistantMessageAdded?.();
|
|
215
167
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
216
168
|
}
|
|
217
|
-
|
|
218
|
-
if (!
|
|
169
|
+
mergeAssistantAdditionalFields(additionalFields) {
|
|
170
|
+
if (!additionalFields || Object.keys(additionalFields).length === 0) {
|
|
219
171
|
return;
|
|
220
172
|
}
|
|
221
173
|
const newMessages = [...this.messages];
|
|
222
174
|
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
223
175
|
const message = newMessages[i];
|
|
224
176
|
if (message.role === "assistant") {
|
|
225
|
-
const
|
|
226
|
-
...(message.
|
|
177
|
+
const mergedAdditionalFields = {
|
|
178
|
+
...(message.additionalFields || {}),
|
|
227
179
|
};
|
|
228
|
-
for (const [key, value] of Object.entries(
|
|
180
|
+
for (const [key, value] of Object.entries(additionalFields)) {
|
|
229
181
|
if (value === undefined) {
|
|
230
182
|
continue;
|
|
231
183
|
}
|
|
232
|
-
|
|
184
|
+
mergedAdditionalFields[key] = value;
|
|
233
185
|
}
|
|
234
|
-
if (Object.keys(
|
|
186
|
+
if (Object.keys(mergedAdditionalFields).length === 0) {
|
|
235
187
|
return;
|
|
236
188
|
}
|
|
237
|
-
message.
|
|
189
|
+
message.additionalFields = mergedAdditionalFields;
|
|
238
190
|
this.setMessages(newMessages);
|
|
239
191
|
return;
|
|
240
192
|
}
|
|
@@ -259,15 +211,6 @@ export class MessageManager {
|
|
|
259
211
|
this.callbacks.onToolBlockUpdated?.(params);
|
|
260
212
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
261
213
|
}
|
|
262
|
-
addDiffBlock(filePath, diffResult) {
|
|
263
|
-
const newMessages = addDiffBlockToMessage({
|
|
264
|
-
messages: this.messages,
|
|
265
|
-
path: filePath,
|
|
266
|
-
diffResult,
|
|
267
|
-
});
|
|
268
|
-
this.setMessages(newMessages);
|
|
269
|
-
this.callbacks.onDiffBlockAdded?.(filePath, JSON.stringify(diffResult));
|
|
270
|
-
}
|
|
271
214
|
addErrorBlock(error) {
|
|
272
215
|
const newMessages = addErrorBlockToMessage({
|
|
273
216
|
messages: this.messages,
|
|
@@ -408,7 +351,7 @@ export class MessageManager {
|
|
|
408
351
|
}
|
|
409
352
|
else {
|
|
410
353
|
// Add new text block if none exists
|
|
411
|
-
lastMessage.blocks.
|
|
354
|
+
lastMessage.blocks.push({
|
|
412
355
|
type: "text",
|
|
413
356
|
content: newAccumulatedContent,
|
|
414
357
|
});
|
|
@@ -418,6 +361,41 @@ export class MessageManager {
|
|
|
418
361
|
// Note: Subagent-specific callbacks are now handled by SubagentManager
|
|
419
362
|
this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
|
|
420
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Update the current assistant message reasoning during streaming
|
|
366
|
+
* This method updates the last assistant message's reasoning content without creating a new message
|
|
367
|
+
*/
|
|
368
|
+
updateCurrentMessageReasoning(newAccumulatedReasoning) {
|
|
369
|
+
if (this.messages.length === 0)
|
|
370
|
+
return;
|
|
371
|
+
const lastMessage = this.messages[this.messages.length - 1];
|
|
372
|
+
if (lastMessage.role !== "assistant")
|
|
373
|
+
return;
|
|
374
|
+
// Get the current reasoning content to calculate the chunk
|
|
375
|
+
const reasoningBlockIndex = lastMessage.blocks.findIndex((block) => block.type === "reasoning");
|
|
376
|
+
const currentReasoning = reasoningBlockIndex >= 0
|
|
377
|
+
? lastMessage.blocks[reasoningBlockIndex].content || ""
|
|
378
|
+
: "";
|
|
379
|
+
// Calculate the chunk (new content since last update)
|
|
380
|
+
const chunk = newAccumulatedReasoning.slice(currentReasoning.length);
|
|
381
|
+
if (reasoningBlockIndex >= 0) {
|
|
382
|
+
// Update existing reasoning block
|
|
383
|
+
lastMessage.blocks[reasoningBlockIndex] = {
|
|
384
|
+
type: "reasoning",
|
|
385
|
+
content: newAccumulatedReasoning,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
// Add new reasoning block if none exists
|
|
390
|
+
lastMessage.blocks.push({
|
|
391
|
+
type: "reasoning",
|
|
392
|
+
content: newAccumulatedReasoning,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// Trigger callbacks with chunk and accumulated reasoning content
|
|
396
|
+
this.callbacks.onAssistantReasoningUpdated?.(chunk, newAccumulatedReasoning);
|
|
397
|
+
this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
|
|
398
|
+
}
|
|
421
399
|
/**
|
|
422
400
|
* Remove the last user message from the conversation
|
|
423
401
|
* Used for hook error handling when the user prompt needs to be erased
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Manager for handling tool permission checks
|
|
3
|
+
*
|
|
4
|
+
* This manager provides utilities for checking permissions before tool execution.
|
|
5
|
+
* It implements the permission logic for different modes (default vs bypass) and
|
|
6
|
+
* handles custom callback integration.
|
|
7
|
+
*/
|
|
8
|
+
import type { PermissionDecision, ToolPermissionContext, PermissionCallback, PermissionMode } from "../types/permissions.js";
|
|
9
|
+
import type { Logger } from "../types/index.js";
|
|
10
|
+
export interface PermissionManagerOptions {
|
|
11
|
+
/** Logger for debugging permission decisions */
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
/** Configured default permission mode from settings */
|
|
14
|
+
configuredDefaultMode?: PermissionMode;
|
|
15
|
+
/** Allowed rules from settings */
|
|
16
|
+
allowedRules?: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare class PermissionManager {
|
|
19
|
+
private logger?;
|
|
20
|
+
private configuredDefaultMode?;
|
|
21
|
+
private allowedRules;
|
|
22
|
+
private onConfiguredDefaultModeChange?;
|
|
23
|
+
constructor(options?: PermissionManagerOptions);
|
|
24
|
+
/**
|
|
25
|
+
* Set a callback to be notified when the effective permission mode changes due to configuration updates
|
|
26
|
+
*/
|
|
27
|
+
setOnConfiguredDefaultModeChange(callback: (mode: PermissionMode) => void): void;
|
|
28
|
+
/**
|
|
29
|
+
* Update the configured default mode (e.g., when configuration reloads)
|
|
30
|
+
*/
|
|
31
|
+
updateConfiguredDefaultMode(defaultMode?: PermissionMode): void;
|
|
32
|
+
/**
|
|
33
|
+
* Get all currently allowed rules
|
|
34
|
+
*/
|
|
35
|
+
getAllowedRules(): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Update the allowed rules (e.g., when configuration reloads)
|
|
38
|
+
*/
|
|
39
|
+
updateAllowedRules(rules: string[]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the current effective permission mode for tool execution context
|
|
42
|
+
*/
|
|
43
|
+
getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the effective permission mode based on CLI override and configured default
|
|
46
|
+
*/
|
|
47
|
+
resolveEffectivePermissionMode(cliPermissionMode?: PermissionMode): PermissionMode;
|
|
48
|
+
/**
|
|
49
|
+
* Check if a tool execution requires permission and handle the authorization flow
|
|
50
|
+
* Called by individual tools after validation/diff, before real operation
|
|
51
|
+
*/
|
|
52
|
+
checkPermission(context: ToolPermissionContext): Promise<PermissionDecision>;
|
|
53
|
+
/**
|
|
54
|
+
* Determine if a tool requires permission checks based on its name
|
|
55
|
+
*/
|
|
56
|
+
isRestrictedTool(toolName: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Helper method to create a permission context for CLI integration
|
|
59
|
+
*/
|
|
60
|
+
createContext(toolName: string, permissionMode: PermissionMode, callback?: PermissionCallback, toolInput?: Record<string, unknown>): ToolPermissionContext;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a tool call is allowed by persistent rules
|
|
63
|
+
*/
|
|
64
|
+
private isAllowedByRule;
|
|
65
|
+
/**
|
|
66
|
+
* Expand a bash command into individual permission rules, filtering out safe commands.
|
|
67
|
+
* Used when saving permissions to the allow list.
|
|
68
|
+
*
|
|
69
|
+
* @param command The full bash command string
|
|
70
|
+
* @param workdir The working directory for path safety checks
|
|
71
|
+
* @returns Array of permission rules in "Bash(cmd)" format
|
|
72
|
+
*/
|
|
73
|
+
expandBashRule(command: string, workdir: string): string[];
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=permissionManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissionManager.d.ts","sourceRoot":"","sources":["../../src/managers/permissionManager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EACV,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAYhD,MAAM,WAAW,wBAAwB;IACvC,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,cAAc,CAAC;IACvC,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,qBAAqB,CAAC,CAAiB;IAC/C,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,6BAA6B,CAAC,CAAiC;gBAE3D,OAAO,GAAE,wBAA6B;IAMlD;;OAEG;IACI,gCAAgC,CACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GACvC,IAAI;IAIP;;OAEG;IACH,2BAA2B,CAAC,WAAW,CAAC,EAAE,cAAc,GAAG,IAAI;IAyB/D;;OAEG;IACI,eAAe,IAAI,MAAM,EAAE;IAIlC;;OAEG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAOzC;;OAEG;IACH,uBAAuB,CAAC,iBAAiB,CAAC,EAAE,cAAc,GAAG,cAAc;IAI3E;;OAEG;IACH,8BAA8B,CAC5B,iBAAiB,CAAC,EAAE,cAAc,GACjC,cAAc;IAuBjB;;;OAGG;IACG,eAAe,CACnB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;IAqF9B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAW3C;;OAEG;IACH,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,cAAc,EAC9B,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,qBAAqB;IAuExB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiEvB;;;;;;;OAOG;IACI,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;CAiFlE"}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Manager for handling tool permission checks
|
|
3
|
+
*
|
|
4
|
+
* This manager provides utilities for checking permissions before tool execution.
|
|
5
|
+
* It implements the permission logic for different modes (default vs bypass) and
|
|
6
|
+
* handles custom callback integration.
|
|
7
|
+
*/
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { RESTRICTED_TOOLS } from "../types/permissions.js";
|
|
10
|
+
import { splitBashCommand, stripEnvVars, stripRedirections, getSmartPrefix, DANGEROUS_COMMANDS, } from "../utils/bashParser.js";
|
|
11
|
+
import { isPathInside } from "../utils/pathSafety.js";
|
|
12
|
+
const SAFE_COMMANDS = ["cd", "ls", "pwd"];
|
|
13
|
+
export class PermissionManager {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.allowedRules = [];
|
|
16
|
+
this.logger = options.logger;
|
|
17
|
+
this.configuredDefaultMode = options.configuredDefaultMode;
|
|
18
|
+
this.allowedRules = options.allowedRules || [];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Set a callback to be notified when the effective permission mode changes due to configuration updates
|
|
22
|
+
*/
|
|
23
|
+
setOnConfiguredDefaultModeChange(callback) {
|
|
24
|
+
this.onConfiguredDefaultModeChange = callback;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Update the configured default mode (e.g., when configuration reloads)
|
|
28
|
+
*/
|
|
29
|
+
updateConfiguredDefaultMode(defaultMode) {
|
|
30
|
+
const oldEffectiveMode = this.getCurrentEffectiveMode();
|
|
31
|
+
this.logger?.debug("Updating configured default permission mode", {
|
|
32
|
+
previous: this.configuredDefaultMode,
|
|
33
|
+
new: defaultMode,
|
|
34
|
+
});
|
|
35
|
+
this.configuredDefaultMode = defaultMode;
|
|
36
|
+
const newEffectiveMode = this.getCurrentEffectiveMode();
|
|
37
|
+
if (oldEffectiveMode !== newEffectiveMode &&
|
|
38
|
+
this.onConfiguredDefaultModeChange) {
|
|
39
|
+
this.logger?.debug("Effective permission mode changed due to configuration update", {
|
|
40
|
+
oldMode: oldEffectiveMode,
|
|
41
|
+
newMode: newEffectiveMode,
|
|
42
|
+
});
|
|
43
|
+
this.onConfiguredDefaultModeChange(newEffectiveMode);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get all currently allowed rules
|
|
48
|
+
*/
|
|
49
|
+
getAllowedRules() {
|
|
50
|
+
return [...this.allowedRules];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Update the allowed rules (e.g., when configuration reloads)
|
|
54
|
+
*/
|
|
55
|
+
updateAllowedRules(rules) {
|
|
56
|
+
this.logger?.debug("Updating allowed permission rules", {
|
|
57
|
+
count: rules.length,
|
|
58
|
+
});
|
|
59
|
+
this.allowedRules = rules;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the current effective permission mode for tool execution context
|
|
63
|
+
*/
|
|
64
|
+
getCurrentEffectiveMode(cliPermissionMode) {
|
|
65
|
+
return this.resolveEffectivePermissionMode(cliPermissionMode);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the effective permission mode based on CLI override and configured default
|
|
69
|
+
*/
|
|
70
|
+
resolveEffectivePermissionMode(cliPermissionMode) {
|
|
71
|
+
// CLI override takes highest precedence
|
|
72
|
+
if (cliPermissionMode !== undefined) {
|
|
73
|
+
this.logger?.debug("Using CLI permission mode override", {
|
|
74
|
+
cliMode: cliPermissionMode,
|
|
75
|
+
configuredDefault: this.configuredDefaultMode,
|
|
76
|
+
});
|
|
77
|
+
return cliPermissionMode;
|
|
78
|
+
}
|
|
79
|
+
// Use configured default mode if available
|
|
80
|
+
if (this.configuredDefaultMode !== undefined) {
|
|
81
|
+
this.logger?.debug("Using configured default permission mode", {
|
|
82
|
+
configuredDefault: this.configuredDefaultMode,
|
|
83
|
+
});
|
|
84
|
+
return this.configuredDefaultMode;
|
|
85
|
+
}
|
|
86
|
+
// Fall back to system default
|
|
87
|
+
this.logger?.debug("Using system default permission mode");
|
|
88
|
+
return "default";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if a tool execution requires permission and handle the authorization flow
|
|
92
|
+
* Called by individual tools after validation/diff, before real operation
|
|
93
|
+
*/
|
|
94
|
+
async checkPermission(context) {
|
|
95
|
+
this.logger?.debug("Checking permission for tool", {
|
|
96
|
+
toolName: context.toolName,
|
|
97
|
+
permissionMode: context.permissionMode,
|
|
98
|
+
hasCallback: !!context.canUseToolCallback,
|
|
99
|
+
});
|
|
100
|
+
// 1. If bypassPermissions mode, always allow
|
|
101
|
+
if (context.permissionMode === "bypassPermissions") {
|
|
102
|
+
this.logger?.debug("Permission bypassed for tool", {
|
|
103
|
+
toolName: context.toolName,
|
|
104
|
+
});
|
|
105
|
+
return { behavior: "allow" };
|
|
106
|
+
}
|
|
107
|
+
// 1.1 If acceptEdits mode, allow Edit, MultiEdit, Delete, Write
|
|
108
|
+
if (context.permissionMode === "acceptEdits") {
|
|
109
|
+
const autoAcceptedTools = ["Edit", "MultiEdit", "Delete", "Write"];
|
|
110
|
+
if (autoAcceptedTools.includes(context.toolName)) {
|
|
111
|
+
this.logger?.debug("Permission automatically accepted for tool in acceptEdits mode", {
|
|
112
|
+
toolName: context.toolName,
|
|
113
|
+
});
|
|
114
|
+
return { behavior: "allow" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// 1.2 Check if tool call matches any allowed rule
|
|
118
|
+
if (this.isAllowedByRule(context)) {
|
|
119
|
+
this.logger?.debug("Permission allowed by persistent rule", {
|
|
120
|
+
toolName: context.toolName,
|
|
121
|
+
});
|
|
122
|
+
return { behavior: "allow" };
|
|
123
|
+
}
|
|
124
|
+
// 2. If not a restricted tool, always allow
|
|
125
|
+
if (!this.isRestrictedTool(context.toolName)) {
|
|
126
|
+
this.logger?.debug("Tool is not restricted, allowing", {
|
|
127
|
+
toolName: context.toolName,
|
|
128
|
+
});
|
|
129
|
+
return { behavior: "allow" };
|
|
130
|
+
}
|
|
131
|
+
// 3. If custom callback provided, call it and return result
|
|
132
|
+
if (context.canUseToolCallback) {
|
|
133
|
+
try {
|
|
134
|
+
this.logger?.debug("Calling custom permission callback for tool", {
|
|
135
|
+
toolName: context.toolName,
|
|
136
|
+
});
|
|
137
|
+
const decision = await context.canUseToolCallback(context);
|
|
138
|
+
this.logger?.debug("Custom callback returned decision", {
|
|
139
|
+
toolName: context.toolName,
|
|
140
|
+
decision,
|
|
141
|
+
});
|
|
142
|
+
return decision;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
146
|
+
this.logger?.error("Error in permission callback", {
|
|
147
|
+
toolName: context.toolName,
|
|
148
|
+
error: errorMessage,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
behavior: "deny",
|
|
152
|
+
message: "Error in permission callback",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// 4. For default mode on restricted tools without callback, integrate with CLI confirmation
|
|
157
|
+
// Note: CLI confirmation integration will be implemented in Phase 2
|
|
158
|
+
this.logger?.warn("No permission callback provided for restricted tool in default mode", {
|
|
159
|
+
toolName: context.toolName,
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
behavior: "deny",
|
|
163
|
+
message: `Tool '${context.toolName}' requires permission approval. No permission callback configured.`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Determine if a tool requires permission checks based on its name
|
|
168
|
+
*/
|
|
169
|
+
isRestrictedTool(toolName) {
|
|
170
|
+
const isRestricted = RESTRICTED_TOOLS.includes(toolName);
|
|
171
|
+
this.logger?.debug("Checking if tool is restricted", {
|
|
172
|
+
toolName,
|
|
173
|
+
isRestricted,
|
|
174
|
+
});
|
|
175
|
+
return isRestricted;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Helper method to create a permission context for CLI integration
|
|
179
|
+
*/
|
|
180
|
+
createContext(toolName, permissionMode, callback, toolInput) {
|
|
181
|
+
let suggestedPrefix;
|
|
182
|
+
if (toolName === "Bash" && toolInput?.command) {
|
|
183
|
+
const command = String(toolInput.command);
|
|
184
|
+
const parts = splitBashCommand(command);
|
|
185
|
+
// Only suggest prefix for single commands to avoid confusion with complex chains
|
|
186
|
+
if (parts.length === 1) {
|
|
187
|
+
const processedPart = stripRedirections(stripEnvVars(parts[0]));
|
|
188
|
+
suggestedPrefix = getSmartPrefix(processedPart) ?? undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const context = {
|
|
192
|
+
toolName,
|
|
193
|
+
permissionMode,
|
|
194
|
+
canUseToolCallback: callback,
|
|
195
|
+
toolInput,
|
|
196
|
+
suggestedPrefix,
|
|
197
|
+
};
|
|
198
|
+
// Set hidePersistentOption for dangerous or out-of-bounds bash commands
|
|
199
|
+
if (toolName === "Bash" && toolInput?.command) {
|
|
200
|
+
const command = String(toolInput.command);
|
|
201
|
+
const workdir = toolInput.workdir;
|
|
202
|
+
const parts = splitBashCommand(command);
|
|
203
|
+
const isDangerous = parts.some((part) => {
|
|
204
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
205
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
206
|
+
if (commandMatch) {
|
|
207
|
+
const cmd = commandMatch[1];
|
|
208
|
+
const args = commandMatch[2]?.trim() || "";
|
|
209
|
+
// Check blacklist
|
|
210
|
+
if (DANGEROUS_COMMANDS.includes(cmd)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
// Check out-of-bounds for cd and ls
|
|
214
|
+
if (workdir && (cmd === "cd" || cmd === "ls")) {
|
|
215
|
+
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
216
|
+
return pathArgs.some((pathArg) => {
|
|
217
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
218
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
219
|
+
return !isPathInside(absolutePath, workdir);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
});
|
|
225
|
+
if (isDangerous) {
|
|
226
|
+
context.hidePersistentOption = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
this.logger?.debug("Created permission context", {
|
|
230
|
+
toolName,
|
|
231
|
+
permissionMode,
|
|
232
|
+
hasCallback: !!callback,
|
|
233
|
+
hasToolInput: !!toolInput,
|
|
234
|
+
suggestedPrefix,
|
|
235
|
+
});
|
|
236
|
+
return context;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check if a tool call is allowed by persistent rules
|
|
240
|
+
*/
|
|
241
|
+
isAllowedByRule(context) {
|
|
242
|
+
if (context.toolName === "Bash" && context.toolInput?.command) {
|
|
243
|
+
const command = String(context.toolInput.command);
|
|
244
|
+
const parts = splitBashCommand(command);
|
|
245
|
+
if (parts.length === 0)
|
|
246
|
+
return false;
|
|
247
|
+
const workdir = context.toolInput?.workdir;
|
|
248
|
+
return parts.every((part) => {
|
|
249
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
250
|
+
// Check for safe commands
|
|
251
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
252
|
+
if (commandMatch) {
|
|
253
|
+
const cmd = commandMatch[1];
|
|
254
|
+
const args = commandMatch[2]?.trim() || "";
|
|
255
|
+
if (SAFE_COMMANDS.includes(cmd)) {
|
|
256
|
+
if (workdir) {
|
|
257
|
+
if (cmd === "pwd") {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
// For cd and ls, check paths
|
|
261
|
+
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
262
|
+
if (pathArgs.length === 0) {
|
|
263
|
+
// cd or ls without arguments operates on current dir (workdir)
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
267
|
+
// Remove quotes if present
|
|
268
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
269
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
270
|
+
return isPathInside(absolutePath, workdir);
|
|
271
|
+
});
|
|
272
|
+
if (allPathsSafe) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const action = `${context.toolName}(${processedPart})`;
|
|
279
|
+
const allowedByRule = this.allowedRules.some((rule) => {
|
|
280
|
+
if (rule.endsWith(":*)")) {
|
|
281
|
+
const prefix = rule.slice(0, -3);
|
|
282
|
+
return action.startsWith(prefix);
|
|
283
|
+
}
|
|
284
|
+
return action === rule;
|
|
285
|
+
});
|
|
286
|
+
if (allowedByRule)
|
|
287
|
+
return true;
|
|
288
|
+
return !this.isRestrictedTool(context.toolName);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Add other tools if needed in the future
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Expand a bash command into individual permission rules, filtering out safe commands.
|
|
296
|
+
* Used when saving permissions to the allow list.
|
|
297
|
+
*
|
|
298
|
+
* @param command The full bash command string
|
|
299
|
+
* @param workdir The working directory for path safety checks
|
|
300
|
+
* @returns Array of permission rules in "Bash(cmd)" format
|
|
301
|
+
*/
|
|
302
|
+
expandBashRule(command, workdir) {
|
|
303
|
+
const parts = splitBashCommand(command);
|
|
304
|
+
const rules = [];
|
|
305
|
+
for (const part of parts) {
|
|
306
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
307
|
+
// Check for safe commands
|
|
308
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
309
|
+
let isSafe = false;
|
|
310
|
+
if (commandMatch) {
|
|
311
|
+
const cmd = commandMatch[1];
|
|
312
|
+
const args = commandMatch[2]?.trim() || "";
|
|
313
|
+
if (SAFE_COMMANDS.includes(cmd)) {
|
|
314
|
+
if (cmd === "pwd") {
|
|
315
|
+
isSafe = true;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// For cd and ls, check paths
|
|
319
|
+
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
320
|
+
if (pathArgs.length === 0) {
|
|
321
|
+
isSafe = true;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
325
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
326
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
327
|
+
return isPathInside(absolutePath, workdir);
|
|
328
|
+
});
|
|
329
|
+
if (allPathsSafe) {
|
|
330
|
+
isSafe = true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (!isSafe) {
|
|
337
|
+
// Check if command is dangerous or out-of-bounds
|
|
338
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
339
|
+
if (commandMatch) {
|
|
340
|
+
const cmd = commandMatch[1];
|
|
341
|
+
const args = commandMatch[2]?.trim() || "";
|
|
342
|
+
if (DANGEROUS_COMMANDS.includes(cmd)) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (cmd === "cd" || cmd === "ls") {
|
|
346
|
+
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
347
|
+
const isOutOfBounds = pathArgs.some((pathArg) => {
|
|
348
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
349
|
+
const absolutePath = path.resolve(workdir, cleanPath);
|
|
350
|
+
return !isPathInside(absolutePath, workdir);
|
|
351
|
+
});
|
|
352
|
+
if (isOutOfBounds) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const smartPrefix = getSmartPrefix(processedPart);
|
|
358
|
+
if (smartPrefix) {
|
|
359
|
+
rules.push(`Bash(${smartPrefix}:*)`);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
rules.push(`Bash(${processedPart})`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return rules;
|
|
367
|
+
}
|
|
368
|
+
}
|