wave-agent-sdk 0.0.15 → 0.0.17-alpha.0
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 +11 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +71 -11
- package/dist/constants/prompts.d.ts +14 -0
- package/dist/constants/prompts.d.ts.map +1 -0
- package/dist/constants/prompts.js +61 -0
- package/dist/constants/tools.d.ts +18 -0
- package/dist/constants/tools.d.ts.map +1 -0
- package/dist/constants/tools.js +17 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/managers/aiManager.d.ts +8 -2
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +52 -16
- package/dist/managers/hookManager.d.ts +7 -2
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +10 -0
- package/dist/managers/liveConfigManager.d.ts +1 -1
- package/dist/managers/liveConfigManager.d.ts.map +1 -1
- package/dist/managers/liveConfigManager.js +43 -22
- package/dist/managers/permissionManager.d.ts +50 -1
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +289 -58
- package/dist/managers/planManager.d.ts +21 -0
- package/dist/managers/planManager.d.ts.map +1 -0
- package/dist/managers/planManager.js +35 -0
- package/dist/managers/pluginManager.d.ts +57 -0
- package/dist/managers/pluginManager.d.ts.map +1 -0
- package/dist/managers/pluginManager.js +124 -0
- package/dist/managers/pluginScopeManager.d.ts +35 -0
- package/dist/managers/pluginScopeManager.d.ts.map +1 -0
- package/dist/managers/pluginScopeManager.js +39 -0
- package/dist/managers/skillManager.d.ts +4 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +15 -0
- package/dist/managers/slashCommandManager.d.ts +4 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +45 -3
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +3 -1
- package/dist/managers/toolManager.d.ts +4 -0
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +24 -1
- package/dist/services/GitService.d.ts +16 -0
- package/dist/services/GitService.d.ts.map +1 -0
- package/dist/services/GitService.js +75 -0
- package/dist/services/MarketplaceService.d.ts +62 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -0
- package/dist/services/MarketplaceService.js +320 -0
- package/dist/services/aiService.d.ts +1 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +11 -11
- package/dist/services/configurationService.d.ts +11 -4
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +211 -67
- package/dist/services/fileWatcher.js +3 -3
- package/dist/services/hook.js +1 -1
- package/dist/services/pluginLoader.d.ts +35 -0
- package/dist/services/pluginLoader.d.ts.map +1 -0
- package/dist/services/pluginLoader.js +149 -0
- package/dist/tools/askUserQuestion.d.ts +3 -0
- package/dist/tools/askUserQuestion.d.ts.map +1 -0
- package/dist/tools/askUserQuestion.js +109 -0
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +37 -40
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +17 -19
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +29 -36
- package/dist/tools/exitPlanMode.d.ts +6 -0
- package/dist/tools/exitPlanMode.d.ts.map +1 -0
- package/dist/tools/exitPlanMode.js +76 -0
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +4 -3
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +5 -4
- package/dist/tools/lsTool.d.ts.map +1 -1
- package/dist/tools/lsTool.js +16 -3
- package/dist/tools/lspTool.d.ts.map +1 -1
- package/dist/tools/lspTool.js +3 -2
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +24 -31
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +16 -3
- package/dist/tools/skillTool.d.ts.map +1 -1
- package/dist/tools/skillTool.js +30 -27
- package/dist/tools/taskTool.d.ts.map +1 -1
- package/dist/tools/taskTool.js +36 -31
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +3 -2
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +13 -17
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +36 -1
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +2 -15
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/marketplace.d.ts +43 -0
- package/dist/types/marketplace.d.ts.map +1 -0
- package/dist/types/marketplace.js +1 -0
- package/dist/types/permissions.d.ts +4 -2
- package/dist/types/permissions.d.ts.map +1 -1
- package/dist/types/permissions.js +8 -5
- package/dist/types/plugins.d.ts +35 -0
- package/dist/types/plugins.d.ts.map +1 -0
- package/dist/types/plugins.js +1 -0
- package/dist/types/tools.d.ts +17 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/bashHistory.d.ts +3 -3
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +10 -8
- package/dist/utils/bashParser.d.ts.map +1 -1
- package/dist/utils/bashParser.js +63 -20
- package/dist/utils/configPaths.d.ts +4 -0
- package/dist/utils/configPaths.d.ts.map +1 -1
- package/dist/utils/configPaths.js +6 -0
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +4 -0
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +4 -2
- package/dist/utils/customCommands.d.ts +6 -0
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +1 -1
- package/dist/utils/editUtils.d.ts +17 -0
- package/dist/utils/editUtils.d.ts.map +1 -0
- package/dist/utils/editUtils.js +69 -0
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +25 -1
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +2 -1
- package/dist/utils/nameGenerator.d.ts +8 -0
- package/dist/utils/nameGenerator.d.ts.map +1 -0
- package/dist/utils/nameGenerator.js +75 -0
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +14 -2
- package/dist/utils/pathSafety.d.ts.map +1 -1
- package/dist/utils/pathSafety.js +17 -2
- package/package.json +6 -1
- package/src/agent.ts +95 -10
- package/src/constants/prompts.ts +93 -0
- package/src/constants/tools.ts +17 -0
- package/src/index.ts +8 -0
- package/src/managers/aiManager.ts +86 -18
- package/src/managers/hookManager.ts +23 -6
- package/src/managers/liveConfigManager.ts +56 -29
- package/src/managers/permissionManager.ts +361 -61
- package/src/managers/planManager.ts +45 -0
- package/src/managers/pluginManager.ts +182 -0
- package/src/managers/pluginScopeManager.ts +75 -0
- package/src/managers/skillManager.ts +18 -0
- package/src/managers/slashCommandManager.ts +67 -5
- package/src/managers/subagentManager.ts +3 -1
- package/src/managers/toolManager.ts +25 -3
- package/src/services/GitService.ts +97 -0
- package/src/services/MarketplaceService.ts +428 -0
- package/src/services/aiService.ts +33 -16
- package/src/services/configurationService.ts +244 -86
- package/src/services/fileWatcher.ts +3 -3
- package/src/services/hook.ts +1 -1
- package/src/services/pluginLoader.ts +181 -0
- package/src/tools/askUserQuestion.ts +128 -0
- package/src/tools/bashTool.ts +52 -49
- package/src/tools/deleteFileTool.ts +22 -28
- package/src/tools/editTool.ts +42 -48
- package/src/tools/exitPlanMode.ts +93 -0
- package/src/tools/globTool.ts +4 -4
- package/src/tools/grepTool.ts +9 -5
- package/src/tools/lsTool.ts +27 -4
- package/src/tools/lspTool.ts +3 -2
- package/src/tools/multiEditTool.ts +40 -42
- package/src/tools/readTool.ts +23 -4
- package/src/tools/skillTool.ts +35 -30
- package/src/tools/taskTool.ts +39 -33
- package/src/tools/todoWriteTool.ts +3 -2
- package/src/tools/writeTool.ts +19 -27
- package/src/types/commands.ts +1 -0
- package/src/types/config.ts +2 -0
- package/src/types/configuration.ts +42 -1
- package/src/types/hooks.ts +8 -25
- package/src/types/index.ts +3 -0
- package/src/types/marketplace.ts +52 -0
- package/src/types/permissions.ts +29 -6
- package/src/types/plugins.ts +37 -0
- package/src/types/tools.ts +20 -0
- package/src/utils/bashHistory.ts +9 -16
- package/src/utils/bashParser.ts +97 -46
- package/src/utils/configPaths.ts +7 -0
- package/src/utils/constants.ts +5 -0
- package/src/utils/convertMessagesForAPI.ts +4 -2
- package/src/utils/customCommands.ts +1 -1
- package/src/utils/editUtils.ts +82 -0
- package/src/utils/markdownParser.ts +28 -1
- package/src/utils/messageOperations.ts +2 -1
- package/src/utils/nameGenerator.ts +78 -0
- package/src/utils/openaiClient.ts +14 -2
- package/src/utils/pathSafety.ts +16 -2
|
@@ -6,16 +6,25 @@
|
|
|
6
6
|
* handles custom callback integration.
|
|
7
7
|
*/
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import { minimatch } from "minimatch";
|
|
9
10
|
import { RESTRICTED_TOOLS } from "../types/permissions.js";
|
|
10
11
|
import { splitBashCommand, stripEnvVars, stripRedirections, getSmartPrefix, DANGEROUS_COMMANDS, } from "../utils/bashParser.js";
|
|
11
12
|
import { isPathInside } from "../utils/pathSafety.js";
|
|
12
|
-
|
|
13
|
+
import { BASH_TOOL_NAME, EDIT_TOOL_NAME, MULTI_EDIT_TOOL_NAME, DELETE_FILE_TOOL_NAME, WRITE_TOOL_NAME, READ_TOOL_NAME, LS_TOOL_NAME, } from "../constants/tools.js";
|
|
14
|
+
const SAFE_COMMANDS = ["cd", "ls", "pwd", "true", "false"];
|
|
13
15
|
export class PermissionManager {
|
|
14
16
|
constructor(options = {}) {
|
|
15
17
|
this.allowedRules = [];
|
|
18
|
+
this.deniedRules = [];
|
|
19
|
+
this.temporaryRules = [];
|
|
20
|
+
this.additionalDirectories = [];
|
|
16
21
|
this.logger = options.logger;
|
|
17
22
|
this.configuredDefaultMode = options.configuredDefaultMode;
|
|
18
23
|
this.allowedRules = options.allowedRules || [];
|
|
24
|
+
this.deniedRules = options.deniedRules || [];
|
|
25
|
+
this.workdir = options.workdir;
|
|
26
|
+
this.planFilePath = options.planFilePath;
|
|
27
|
+
this.updateAdditionalDirectories(options.additionalDirectories || []);
|
|
19
28
|
}
|
|
20
29
|
/**
|
|
21
30
|
* Set a callback to be notified when the effective permission mode changes due to configuration updates
|
|
@@ -58,11 +67,105 @@ export class PermissionManager {
|
|
|
58
67
|
});
|
|
59
68
|
this.allowedRules = rules;
|
|
60
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Update the denied rules (e.g., when configuration reloads)
|
|
72
|
+
*/
|
|
73
|
+
updateDeniedRules(rules) {
|
|
74
|
+
this.logger?.debug("Updating denied permission rules", {
|
|
75
|
+
count: rules.length,
|
|
76
|
+
});
|
|
77
|
+
this.deniedRules = rules;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Add temporary rules for the current session
|
|
81
|
+
*/
|
|
82
|
+
addTemporaryRules(rules) {
|
|
83
|
+
this.logger?.debug("Adding temporary permission rules", {
|
|
84
|
+
count: rules.length,
|
|
85
|
+
rules,
|
|
86
|
+
});
|
|
87
|
+
this.temporaryRules.push(...rules);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clear all temporary rules
|
|
91
|
+
*/
|
|
92
|
+
clearTemporaryRules() {
|
|
93
|
+
this.logger?.debug("Clearing temporary permission rules");
|
|
94
|
+
this.temporaryRules = [];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update the additional directories (e.g., when configuration reloads)
|
|
98
|
+
*/
|
|
99
|
+
updateAdditionalDirectories(directories) {
|
|
100
|
+
this.logger?.debug("Updating additional directories", {
|
|
101
|
+
count: directories.length,
|
|
102
|
+
});
|
|
103
|
+
this.additionalDirectories = directories.map((dir) => {
|
|
104
|
+
if (this.workdir && !path.isAbsolute(dir)) {
|
|
105
|
+
return path.resolve(this.workdir, dir);
|
|
106
|
+
}
|
|
107
|
+
return path.resolve(dir);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Update the working directory
|
|
112
|
+
*/
|
|
113
|
+
updateWorkdir(workdir) {
|
|
114
|
+
this.logger?.debug("Updating working directory", {
|
|
115
|
+
workdir,
|
|
116
|
+
});
|
|
117
|
+
this.workdir = workdir;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Set the current plan file path
|
|
121
|
+
*/
|
|
122
|
+
setPlanFilePath(path) {
|
|
123
|
+
this.logger?.debug("Setting plan file path", { path });
|
|
124
|
+
this.planFilePath = path;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the current plan file path
|
|
128
|
+
*/
|
|
129
|
+
getPlanFilePath() {
|
|
130
|
+
return this.planFilePath;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if a path is inside the Safe Zone (workdir + additionalDirectories)
|
|
134
|
+
*/
|
|
135
|
+
isInsideSafeZone(targetPath, workdir) {
|
|
136
|
+
const effectiveWorkdir = workdir || this.workdir;
|
|
137
|
+
// Resolve the target path relative to effectiveWorkdir if it's not absolute
|
|
138
|
+
const absolutePath = effectiveWorkdir && !path.isAbsolute(targetPath)
|
|
139
|
+
? path.resolve(effectiveWorkdir, targetPath)
|
|
140
|
+
: path.resolve(targetPath);
|
|
141
|
+
// Check workdir
|
|
142
|
+
if (effectiveWorkdir && isPathInside(absolutePath, effectiveWorkdir)) {
|
|
143
|
+
return { isInside: true, resolvedPath: absolutePath };
|
|
144
|
+
}
|
|
145
|
+
// Check additional directories
|
|
146
|
+
for (const dir of this.additionalDirectories) {
|
|
147
|
+
if (isPathInside(absolutePath, dir)) {
|
|
148
|
+
return { isInside: true, resolvedPath: absolutePath };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
this.logger?.debug("Path is outside Safe Zone", {
|
|
152
|
+
absolutePath,
|
|
153
|
+
workdir: effectiveWorkdir,
|
|
154
|
+
additionalDirectories: this.additionalDirectories,
|
|
155
|
+
});
|
|
156
|
+
return { isInside: false, resolvedPath: absolutePath };
|
|
157
|
+
}
|
|
61
158
|
/**
|
|
62
159
|
* Get the current effective permission mode for tool execution context
|
|
63
160
|
*/
|
|
64
161
|
getCurrentEffectiveMode(cliPermissionMode) {
|
|
65
|
-
|
|
162
|
+
const mode = this.resolveEffectivePermissionMode(cliPermissionMode);
|
|
163
|
+
this.logger?.debug("getCurrentEffectiveMode", {
|
|
164
|
+
cliPermissionMode,
|
|
165
|
+
configuredDefaultMode: this.configuredDefaultMode,
|
|
166
|
+
resolvedMode: mode,
|
|
167
|
+
});
|
|
168
|
+
return mode;
|
|
66
169
|
}
|
|
67
170
|
/**
|
|
68
171
|
* Resolve the effective permission mode based on CLI override and configured default
|
|
@@ -97,6 +200,19 @@ export class PermissionManager {
|
|
|
97
200
|
permissionMode: context.permissionMode,
|
|
98
201
|
hasCallback: !!context.canUseToolCallback,
|
|
99
202
|
});
|
|
203
|
+
// 0. Check denied rules first - Deny always takes precedence
|
|
204
|
+
for (const rule of this.deniedRules) {
|
|
205
|
+
if (this.matchesRule(context, rule)) {
|
|
206
|
+
this.logger?.warn("Permission denied by rule", {
|
|
207
|
+
toolName: context.toolName,
|
|
208
|
+
rule,
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
behavior: "deny",
|
|
212
|
+
message: `Access to tool '${context.toolName}' is explicitly denied by rule: ${rule}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
100
216
|
// 1. If bypassPermissions mode, always allow
|
|
101
217
|
if (context.permissionMode === "bypassPermissions") {
|
|
102
218
|
this.logger?.debug("Permission bypassed for tool", {
|
|
@@ -106,14 +222,77 @@ export class PermissionManager {
|
|
|
106
222
|
}
|
|
107
223
|
// 1.1 If acceptEdits mode, allow Edit, MultiEdit, Delete, Write
|
|
108
224
|
if (context.permissionMode === "acceptEdits") {
|
|
109
|
-
const autoAcceptedTools = [
|
|
225
|
+
const autoAcceptedTools = [
|
|
226
|
+
EDIT_TOOL_NAME,
|
|
227
|
+
MULTI_EDIT_TOOL_NAME,
|
|
228
|
+
DELETE_FILE_TOOL_NAME,
|
|
229
|
+
WRITE_TOOL_NAME,
|
|
230
|
+
];
|
|
110
231
|
if (autoAcceptedTools.includes(context.toolName)) {
|
|
232
|
+
// Enforce Safe Zone for file operations
|
|
233
|
+
const targetPath = (context.toolInput?.file_path ||
|
|
234
|
+
context.toolInput?.target_file);
|
|
235
|
+
const workdir = context.toolInput?.workdir;
|
|
236
|
+
if (targetPath) {
|
|
237
|
+
const { isInside, resolvedPath } = this.isInsideSafeZone(targetPath, workdir);
|
|
238
|
+
if (!isInside) {
|
|
239
|
+
this.logger?.warn("File operation outside the Safe Zone in acceptEdits mode", {
|
|
240
|
+
toolName: context.toolName,
|
|
241
|
+
targetPath,
|
|
242
|
+
resolvedPath,
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
behavior: "deny",
|
|
246
|
+
message: `Tool '${context.toolName}' attempted to modify a file outside the Safe Zone: ${targetPath}. Operations outside the Safe Zone always require manual confirmation.`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
111
250
|
this.logger?.debug("Permission automatically accepted for tool in acceptEdits mode", {
|
|
112
251
|
toolName: context.toolName,
|
|
113
252
|
});
|
|
114
253
|
return { behavior: "allow" };
|
|
115
254
|
}
|
|
116
255
|
}
|
|
256
|
+
// 1.3 If plan mode, allow Read-only tools and Edit/Write for plan file
|
|
257
|
+
if (context.permissionMode === "plan") {
|
|
258
|
+
if (context.toolName === BASH_TOOL_NAME) {
|
|
259
|
+
return {
|
|
260
|
+
behavior: "deny",
|
|
261
|
+
message: "Bash commands are not allowed in plan mode.",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const writeTools = [
|
|
265
|
+
EDIT_TOOL_NAME,
|
|
266
|
+
MULTI_EDIT_TOOL_NAME,
|
|
267
|
+
WRITE_TOOL_NAME,
|
|
268
|
+
DELETE_FILE_TOOL_NAME,
|
|
269
|
+
];
|
|
270
|
+
if (context.toolName === DELETE_FILE_TOOL_NAME) {
|
|
271
|
+
return {
|
|
272
|
+
behavior: "deny",
|
|
273
|
+
message: "Delete operations are not allowed in plan mode.",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (writeTools.includes(context.toolName)) {
|
|
277
|
+
const targetPath = (context.toolInput?.file_path ||
|
|
278
|
+
context.toolInput?.target_file);
|
|
279
|
+
if (this.planFilePath && targetPath) {
|
|
280
|
+
const absoluteTargetPath = path.resolve(targetPath);
|
|
281
|
+
const absolutePlanPath = path.resolve(this.planFilePath);
|
|
282
|
+
if (absoluteTargetPath === absolutePlanPath) {
|
|
283
|
+
this.logger?.debug("Allowing write to plan file in plan mode", {
|
|
284
|
+
toolName: context.toolName,
|
|
285
|
+
targetPath,
|
|
286
|
+
});
|
|
287
|
+
return { behavior: "allow" };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
behavior: "deny",
|
|
292
|
+
message: `In plan mode, you are only allowed to edit the designated plan file: ${this.planFilePath || "not set"}.`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
117
296
|
// 1.2 Check if tool call matches any allowed rule
|
|
118
297
|
if (this.isAllowedByRule(context)) {
|
|
119
298
|
this.logger?.debug("Permission allowed by persistent rule", {
|
|
@@ -179,7 +358,7 @@ export class PermissionManager {
|
|
|
179
358
|
*/
|
|
180
359
|
createContext(toolName, permissionMode, callback, toolInput) {
|
|
181
360
|
let suggestedPrefix;
|
|
182
|
-
if (toolName ===
|
|
361
|
+
if (toolName === BASH_TOOL_NAME && toolInput?.command) {
|
|
183
362
|
const command = String(toolInput.command);
|
|
184
363
|
const parts = splitBashCommand(command);
|
|
185
364
|
// Only suggest prefix for single commands to avoid confusion with complex chains
|
|
@@ -196,7 +375,7 @@ export class PermissionManager {
|
|
|
196
375
|
suggestedPrefix,
|
|
197
376
|
};
|
|
198
377
|
// Set hidePersistentOption for dangerous or out-of-bounds bash commands
|
|
199
|
-
if (toolName ===
|
|
378
|
+
if (toolName === BASH_TOOL_NAME && toolInput?.command) {
|
|
200
379
|
const command = String(toolInput.command);
|
|
201
380
|
const workdir = toolInput.workdir;
|
|
202
381
|
const parts = splitBashCommand(command);
|
|
@@ -211,12 +390,12 @@ export class PermissionManager {
|
|
|
211
390
|
return true;
|
|
212
391
|
}
|
|
213
392
|
// Check out-of-bounds for cd and ls
|
|
214
|
-
if (
|
|
393
|
+
if (cmd === "cd" || cmd === "ls") {
|
|
215
394
|
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
216
395
|
return pathArgs.some((pathArg) => {
|
|
217
396
|
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
218
|
-
const
|
|
219
|
-
return !
|
|
397
|
+
const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
|
|
398
|
+
return !isInside;
|
|
220
399
|
});
|
|
221
400
|
}
|
|
222
401
|
}
|
|
@@ -236,60 +415,112 @@ export class PermissionManager {
|
|
|
236
415
|
return context;
|
|
237
416
|
}
|
|
238
417
|
/**
|
|
239
|
-
* Check if a tool call
|
|
418
|
+
* Check if a tool call matches a specific permission rule
|
|
419
|
+
*/
|
|
420
|
+
matchesRule(context, rule) {
|
|
421
|
+
// 1. Simple tool name match (e.g., "Bash", "Write")
|
|
422
|
+
if (rule === context.toolName) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
// 2. Tool with pattern match (e.g., "Bash(rm:*)", "Read(**/*.env)")
|
|
426
|
+
const match = rule.match(/^(\w+)\((.*)\)$/);
|
|
427
|
+
if (!match) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
const [, toolName, pattern] = match;
|
|
431
|
+
if (toolName !== context.toolName) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
// Handle Bash command rules
|
|
435
|
+
if (toolName === BASH_TOOL_NAME) {
|
|
436
|
+
const command = String(context.toolInput?.command || "");
|
|
437
|
+
const processedPart = stripRedirections(stripEnvVars(command));
|
|
438
|
+
if (pattern.endsWith(":*")) {
|
|
439
|
+
return processedPart.startsWith(pattern.slice(0, -2));
|
|
440
|
+
}
|
|
441
|
+
return processedPart === pattern;
|
|
442
|
+
}
|
|
443
|
+
// Handle path-based rules (e.g., "Read(**/*.env)")
|
|
444
|
+
const pathTools = [
|
|
445
|
+
READ_TOOL_NAME,
|
|
446
|
+
WRITE_TOOL_NAME,
|
|
447
|
+
EDIT_TOOL_NAME,
|
|
448
|
+
MULTI_EDIT_TOOL_NAME,
|
|
449
|
+
DELETE_FILE_TOOL_NAME,
|
|
450
|
+
LS_TOOL_NAME,
|
|
451
|
+
];
|
|
452
|
+
if (pathTools.includes(toolName)) {
|
|
453
|
+
const targetPath = (context.toolInput?.file_path ||
|
|
454
|
+
context.toolInput?.target_file ||
|
|
455
|
+
context.toolInput?.path);
|
|
456
|
+
if (targetPath) {
|
|
457
|
+
return minimatch(targetPath, pattern, { dot: true });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Check if a tool call is allowed by persistent or temporary rules
|
|
240
464
|
*/
|
|
241
465
|
isAllowedByRule(context) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
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)
|
|
466
|
+
const isAllowedByRuleList = (ctx, rules) => {
|
|
467
|
+
if (ctx.toolName === BASH_TOOL_NAME && ctx.toolInput?.command) {
|
|
468
|
+
const command = String(ctx.toolInput.command);
|
|
469
|
+
const parts = splitBashCommand(command);
|
|
470
|
+
if (parts.length === 0)
|
|
471
|
+
return false;
|
|
472
|
+
const workdir = ctx.toolInput?.workdir;
|
|
473
|
+
return parts.every((part) => {
|
|
474
|
+
const processedPart = stripRedirections(stripEnvVars(part));
|
|
475
|
+
// Check for safe commands
|
|
476
|
+
const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
|
|
477
|
+
if (commandMatch) {
|
|
478
|
+
const cmd = commandMatch[1];
|
|
479
|
+
const args = commandMatch[2]?.trim() || "";
|
|
480
|
+
if (SAFE_COMMANDS.includes(cmd)) {
|
|
481
|
+
if (cmd === "pwd" || cmd === "true" || cmd === "false") {
|
|
264
482
|
return true;
|
|
265
483
|
}
|
|
266
|
-
|
|
267
|
-
//
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
484
|
+
if (workdir) {
|
|
485
|
+
// For cd and ls, check paths
|
|
486
|
+
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
487
|
+
if (pathArgs.length === 0) {
|
|
488
|
+
// cd or ls without arguments operates on current dir (workdir)
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
492
|
+
// Remove quotes if present
|
|
493
|
+
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
494
|
+
const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
|
|
495
|
+
return isInside;
|
|
496
|
+
});
|
|
497
|
+
if (allPathsSafe) {
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
274
500
|
}
|
|
275
501
|
}
|
|
276
502
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
503
|
+
// Check if this specific part is allowed by any rule
|
|
504
|
+
// We create a temporary context with just this part of the command
|
|
505
|
+
const partContext = {
|
|
506
|
+
...ctx,
|
|
507
|
+
toolInput: { ...ctx.toolInput, command: processedPart },
|
|
508
|
+
};
|
|
509
|
+
const allowedByRule = rules.some((rule) => this.matchesRule(partContext, rule));
|
|
510
|
+
if (allowedByRule)
|
|
511
|
+
return true;
|
|
512
|
+
return !this.isRestrictedTool(ctx.toolName);
|
|
285
513
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
514
|
+
}
|
|
515
|
+
// For other tools, check if any rule matches
|
|
516
|
+
return rules.some((rule) => this.matchesRule(ctx, rule));
|
|
517
|
+
};
|
|
518
|
+
// Check temporary rules first
|
|
519
|
+
if (isAllowedByRuleList(context, this.temporaryRules)) {
|
|
520
|
+
return true;
|
|
290
521
|
}
|
|
291
|
-
//
|
|
292
|
-
return
|
|
522
|
+
// Check persistent allowed rules
|
|
523
|
+
return isAllowedByRuleList(context, this.allowedRules);
|
|
293
524
|
}
|
|
294
525
|
/**
|
|
295
526
|
* Expand a bash command into individual permission rules, filtering out safe commands.
|
|
@@ -311,7 +542,7 @@ export class PermissionManager {
|
|
|
311
542
|
const cmd = commandMatch[1];
|
|
312
543
|
const args = commandMatch[2]?.trim() || "";
|
|
313
544
|
if (SAFE_COMMANDS.includes(cmd)) {
|
|
314
|
-
if (cmd === "pwd") {
|
|
545
|
+
if (cmd === "pwd" || cmd === "true" || cmd === "false") {
|
|
315
546
|
isSafe = true;
|
|
316
547
|
}
|
|
317
548
|
else {
|
|
@@ -323,8 +554,8 @@ export class PermissionManager {
|
|
|
323
554
|
else {
|
|
324
555
|
const allPathsSafe = pathArgs.every((pathArg) => {
|
|
325
556
|
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
326
|
-
const
|
|
327
|
-
return
|
|
557
|
+
const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
|
|
558
|
+
return isInside;
|
|
328
559
|
});
|
|
329
560
|
if (allPathsSafe) {
|
|
330
561
|
isSafe = true;
|
|
@@ -346,8 +577,8 @@ export class PermissionManager {
|
|
|
346
577
|
const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
|
|
347
578
|
const isOutOfBounds = pathArgs.some((pathArg) => {
|
|
348
579
|
const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
|
|
349
|
-
const
|
|
350
|
-
return !
|
|
580
|
+
const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
|
|
581
|
+
return !isInside;
|
|
351
582
|
});
|
|
352
583
|
if (isOutOfBounds) {
|
|
353
584
|
continue;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Logger } from "../types/core.js";
|
|
2
|
+
/**
|
|
3
|
+
* Manages plan files for plan mode
|
|
4
|
+
*/
|
|
5
|
+
export declare class PlanManager {
|
|
6
|
+
private logger?;
|
|
7
|
+
private planDir;
|
|
8
|
+
constructor(logger?: Logger | undefined);
|
|
9
|
+
/**
|
|
10
|
+
* Ensures the plan directory exists and generates a new plan file path with a random name
|
|
11
|
+
*/
|
|
12
|
+
getOrGeneratePlanFilePath(): Promise<{
|
|
13
|
+
path: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the directory where plan files are stored
|
|
18
|
+
*/
|
|
19
|
+
getPlanDir(): string;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=planManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planManager.d.ts","sourceRoot":"","sources":["../../src/managers/planManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;GAEG;AACH,qBAAa,WAAW;IAGV,OAAO,CAAC,MAAM,CAAC;IAF3B,OAAO,CAAC,OAAO,CAAS;gBAEJ,MAAM,CAAC,EAAE,MAAM,YAAA;IAInC;;OAEG;IACU,yBAAyB,IAAI,OAAO,CAAC;QAChD,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IAgBF;;OAEG;IACI,UAAU,IAAI,MAAM;CAG5B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { generateRandomName } from "../utils/nameGenerator.js";
|
|
5
|
+
/**
|
|
6
|
+
* Manages plan files for plan mode
|
|
7
|
+
*/
|
|
8
|
+
export class PlanManager {
|
|
9
|
+
constructor(logger) {
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.planDir = path.join(os.homedir(), ".wave", "plans");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Ensures the plan directory exists and generates a new plan file path with a random name
|
|
15
|
+
*/
|
|
16
|
+
async getOrGeneratePlanFilePath() {
|
|
17
|
+
try {
|
|
18
|
+
await fs.mkdir(this.planDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
this.logger?.error(`Failed to create plan directory: ${this.planDir}`, error);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
const name = generateRandomName();
|
|
25
|
+
const filePath = path.join(this.planDir, `${name}.md`);
|
|
26
|
+
this.logger?.info(`Generated plan file path: ${filePath}`);
|
|
27
|
+
return { path: filePath, name };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns the directory where plan files are stored
|
|
31
|
+
*/
|
|
32
|
+
getPlanDir() {
|
|
33
|
+
return this.planDir;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Plugin, PluginConfig, Logger } from "../types/index.js";
|
|
2
|
+
import { SkillManager } from "./skillManager.js";
|
|
3
|
+
import { HookManager } from "./hookManager.js";
|
|
4
|
+
import { LspManager } from "./lspManager.js";
|
|
5
|
+
import { McpManager } from "./mcpManager.js";
|
|
6
|
+
import { SlashCommandManager } from "./slashCommandManager.js";
|
|
7
|
+
import { ConfigurationService } from "../services/configurationService.js";
|
|
8
|
+
export interface PluginManagerOptions {
|
|
9
|
+
workdir: string;
|
|
10
|
+
logger?: Logger;
|
|
11
|
+
skillManager?: SkillManager;
|
|
12
|
+
hookManager?: HookManager;
|
|
13
|
+
lspManager?: LspManager;
|
|
14
|
+
mcpManager?: McpManager;
|
|
15
|
+
slashCommandManager?: SlashCommandManager;
|
|
16
|
+
enabledPlugins?: Record<string, boolean>;
|
|
17
|
+
configurationService?: ConfigurationService;
|
|
18
|
+
}
|
|
19
|
+
export declare class PluginManager {
|
|
20
|
+
private plugins;
|
|
21
|
+
private workdir;
|
|
22
|
+
private logger?;
|
|
23
|
+
private skillManager?;
|
|
24
|
+
private hookManager?;
|
|
25
|
+
private lspManager?;
|
|
26
|
+
private mcpManager?;
|
|
27
|
+
private slashCommandManager?;
|
|
28
|
+
private enabledPlugins;
|
|
29
|
+
private configurationService?;
|
|
30
|
+
constructor(options: PluginManagerOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Update enabled plugins configuration
|
|
33
|
+
*/
|
|
34
|
+
updateEnabledPlugins(enabledPlugins: Record<string, boolean>): void;
|
|
35
|
+
/**
|
|
36
|
+
* Load plugins installed via marketplace
|
|
37
|
+
*/
|
|
38
|
+
private loadInstalledPlugins;
|
|
39
|
+
/**
|
|
40
|
+
* Load a single plugin from an absolute path
|
|
41
|
+
*/
|
|
42
|
+
private loadSinglePlugin;
|
|
43
|
+
/**
|
|
44
|
+
* Load plugins from configuration
|
|
45
|
+
* @param configs Array of plugin configurations
|
|
46
|
+
*/
|
|
47
|
+
loadPlugins(configs: PluginConfig[]): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Get all loaded plugins
|
|
50
|
+
*/
|
|
51
|
+
getPlugins(): Plugin[];
|
|
52
|
+
/**
|
|
53
|
+
* Get a plugin by name
|
|
54
|
+
*/
|
|
55
|
+
getPlugin(name: string): Plugin | undefined;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=pluginManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pluginManager.d.ts","sourceRoot":"","sources":["../../src/managers/pluginManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAE3E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,mBAAmB,CAAC,CAAsB;IAClD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,oBAAoB,CAAC,CAAuB;gBAExC,OAAO,EAAE,oBAAoB;IAYzC;;OAEG;IACH,oBAAoB,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAInE;;OAEG;YACW,oBAAoB;IA2BlC;;OAEG;YACW,gBAAgB;IA2D9B;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzD;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAItB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAG5C"}
|