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.
Files changed (204) hide show
  1. package/dist/agent.d.ts +11 -1
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +71 -11
  4. package/dist/constants/prompts.d.ts +14 -0
  5. package/dist/constants/prompts.d.ts.map +1 -0
  6. package/dist/constants/prompts.js +61 -0
  7. package/dist/constants/tools.d.ts +18 -0
  8. package/dist/constants/tools.d.ts.map +1 -0
  9. package/dist/constants/tools.js +17 -0
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +6 -0
  13. package/dist/managers/aiManager.d.ts +8 -2
  14. package/dist/managers/aiManager.d.ts.map +1 -1
  15. package/dist/managers/aiManager.js +52 -16
  16. package/dist/managers/hookManager.d.ts +7 -2
  17. package/dist/managers/hookManager.d.ts.map +1 -1
  18. package/dist/managers/hookManager.js +10 -0
  19. package/dist/managers/liveConfigManager.d.ts +1 -1
  20. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  21. package/dist/managers/liveConfigManager.js +43 -22
  22. package/dist/managers/permissionManager.d.ts +50 -1
  23. package/dist/managers/permissionManager.d.ts.map +1 -1
  24. package/dist/managers/permissionManager.js +289 -58
  25. package/dist/managers/planManager.d.ts +21 -0
  26. package/dist/managers/planManager.d.ts.map +1 -0
  27. package/dist/managers/planManager.js +35 -0
  28. package/dist/managers/pluginManager.d.ts +57 -0
  29. package/dist/managers/pluginManager.d.ts.map +1 -0
  30. package/dist/managers/pluginManager.js +124 -0
  31. package/dist/managers/pluginScopeManager.d.ts +35 -0
  32. package/dist/managers/pluginScopeManager.d.ts.map +1 -0
  33. package/dist/managers/pluginScopeManager.js +39 -0
  34. package/dist/managers/skillManager.d.ts +4 -0
  35. package/dist/managers/skillManager.d.ts.map +1 -1
  36. package/dist/managers/skillManager.js +15 -0
  37. package/dist/managers/slashCommandManager.d.ts +4 -0
  38. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  39. package/dist/managers/slashCommandManager.js +45 -3
  40. package/dist/managers/subagentManager.d.ts.map +1 -1
  41. package/dist/managers/subagentManager.js +3 -1
  42. package/dist/managers/toolManager.d.ts +4 -0
  43. package/dist/managers/toolManager.d.ts.map +1 -1
  44. package/dist/managers/toolManager.js +24 -1
  45. package/dist/services/GitService.d.ts +16 -0
  46. package/dist/services/GitService.d.ts.map +1 -0
  47. package/dist/services/GitService.js +75 -0
  48. package/dist/services/MarketplaceService.d.ts +62 -0
  49. package/dist/services/MarketplaceService.d.ts.map +1 -0
  50. package/dist/services/MarketplaceService.js +320 -0
  51. package/dist/services/aiService.d.ts +1 -0
  52. package/dist/services/aiService.d.ts.map +1 -1
  53. package/dist/services/aiService.js +11 -11
  54. package/dist/services/configurationService.d.ts +11 -4
  55. package/dist/services/configurationService.d.ts.map +1 -1
  56. package/dist/services/configurationService.js +211 -67
  57. package/dist/services/fileWatcher.js +3 -3
  58. package/dist/services/hook.js +1 -1
  59. package/dist/services/pluginLoader.d.ts +35 -0
  60. package/dist/services/pluginLoader.d.ts.map +1 -0
  61. package/dist/services/pluginLoader.js +149 -0
  62. package/dist/tools/askUserQuestion.d.ts +3 -0
  63. package/dist/tools/askUserQuestion.d.ts.map +1 -0
  64. package/dist/tools/askUserQuestion.js +109 -0
  65. package/dist/tools/bashTool.d.ts.map +1 -1
  66. package/dist/tools/bashTool.js +37 -40
  67. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  68. package/dist/tools/deleteFileTool.js +17 -19
  69. package/dist/tools/editTool.d.ts.map +1 -1
  70. package/dist/tools/editTool.js +29 -36
  71. package/dist/tools/exitPlanMode.d.ts +6 -0
  72. package/dist/tools/exitPlanMode.d.ts.map +1 -0
  73. package/dist/tools/exitPlanMode.js +76 -0
  74. package/dist/tools/globTool.d.ts.map +1 -1
  75. package/dist/tools/globTool.js +4 -3
  76. package/dist/tools/grepTool.d.ts.map +1 -1
  77. package/dist/tools/grepTool.js +5 -4
  78. package/dist/tools/lsTool.d.ts.map +1 -1
  79. package/dist/tools/lsTool.js +16 -3
  80. package/dist/tools/lspTool.d.ts.map +1 -1
  81. package/dist/tools/lspTool.js +3 -2
  82. package/dist/tools/multiEditTool.d.ts.map +1 -1
  83. package/dist/tools/multiEditTool.js +24 -31
  84. package/dist/tools/readTool.d.ts.map +1 -1
  85. package/dist/tools/readTool.js +16 -3
  86. package/dist/tools/skillTool.d.ts.map +1 -1
  87. package/dist/tools/skillTool.js +30 -27
  88. package/dist/tools/taskTool.d.ts.map +1 -1
  89. package/dist/tools/taskTool.js +36 -31
  90. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  91. package/dist/tools/todoWriteTool.js +3 -2
  92. package/dist/tools/writeTool.d.ts.map +1 -1
  93. package/dist/tools/writeTool.js +13 -17
  94. package/dist/types/commands.d.ts +1 -0
  95. package/dist/types/commands.d.ts.map +1 -1
  96. package/dist/types/config.d.ts +2 -0
  97. package/dist/types/config.d.ts.map +1 -1
  98. package/dist/types/configuration.d.ts +36 -1
  99. package/dist/types/configuration.d.ts.map +1 -1
  100. package/dist/types/hooks.d.ts +2 -15
  101. package/dist/types/hooks.d.ts.map +1 -1
  102. package/dist/types/index.d.ts +3 -0
  103. package/dist/types/index.d.ts.map +1 -1
  104. package/dist/types/index.js +3 -0
  105. package/dist/types/marketplace.d.ts +43 -0
  106. package/dist/types/marketplace.d.ts.map +1 -0
  107. package/dist/types/marketplace.js +1 -0
  108. package/dist/types/permissions.d.ts +4 -2
  109. package/dist/types/permissions.d.ts.map +1 -1
  110. package/dist/types/permissions.js +8 -5
  111. package/dist/types/plugins.d.ts +35 -0
  112. package/dist/types/plugins.d.ts.map +1 -0
  113. package/dist/types/plugins.js +1 -0
  114. package/dist/types/tools.d.ts +17 -0
  115. package/dist/types/tools.d.ts.map +1 -1
  116. package/dist/utils/bashHistory.d.ts +3 -3
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +10 -8
  119. package/dist/utils/bashParser.d.ts.map +1 -1
  120. package/dist/utils/bashParser.js +63 -20
  121. package/dist/utils/configPaths.d.ts +4 -0
  122. package/dist/utils/configPaths.d.ts.map +1 -1
  123. package/dist/utils/configPaths.js +6 -0
  124. package/dist/utils/constants.d.ts +4 -0
  125. package/dist/utils/constants.d.ts.map +1 -1
  126. package/dist/utils/constants.js +4 -0
  127. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  128. package/dist/utils/convertMessagesForAPI.js +4 -2
  129. package/dist/utils/customCommands.d.ts +6 -0
  130. package/dist/utils/customCommands.d.ts.map +1 -1
  131. package/dist/utils/customCommands.js +1 -1
  132. package/dist/utils/editUtils.d.ts +17 -0
  133. package/dist/utils/editUtils.d.ts.map +1 -0
  134. package/dist/utils/editUtils.js +69 -0
  135. package/dist/utils/markdownParser.d.ts.map +1 -1
  136. package/dist/utils/markdownParser.js +25 -1
  137. package/dist/utils/messageOperations.d.ts.map +1 -1
  138. package/dist/utils/messageOperations.js +2 -1
  139. package/dist/utils/nameGenerator.d.ts +8 -0
  140. package/dist/utils/nameGenerator.d.ts.map +1 -0
  141. package/dist/utils/nameGenerator.js +75 -0
  142. package/dist/utils/openaiClient.d.ts.map +1 -1
  143. package/dist/utils/openaiClient.js +14 -2
  144. package/dist/utils/pathSafety.d.ts.map +1 -1
  145. package/dist/utils/pathSafety.js +17 -2
  146. package/package.json +6 -1
  147. package/src/agent.ts +95 -10
  148. package/src/constants/prompts.ts +93 -0
  149. package/src/constants/tools.ts +17 -0
  150. package/src/index.ts +8 -0
  151. package/src/managers/aiManager.ts +86 -18
  152. package/src/managers/hookManager.ts +23 -6
  153. package/src/managers/liveConfigManager.ts +56 -29
  154. package/src/managers/permissionManager.ts +361 -61
  155. package/src/managers/planManager.ts +45 -0
  156. package/src/managers/pluginManager.ts +182 -0
  157. package/src/managers/pluginScopeManager.ts +75 -0
  158. package/src/managers/skillManager.ts +18 -0
  159. package/src/managers/slashCommandManager.ts +67 -5
  160. package/src/managers/subagentManager.ts +3 -1
  161. package/src/managers/toolManager.ts +25 -3
  162. package/src/services/GitService.ts +97 -0
  163. package/src/services/MarketplaceService.ts +428 -0
  164. package/src/services/aiService.ts +33 -16
  165. package/src/services/configurationService.ts +244 -86
  166. package/src/services/fileWatcher.ts +3 -3
  167. package/src/services/hook.ts +1 -1
  168. package/src/services/pluginLoader.ts +181 -0
  169. package/src/tools/askUserQuestion.ts +128 -0
  170. package/src/tools/bashTool.ts +52 -49
  171. package/src/tools/deleteFileTool.ts +22 -28
  172. package/src/tools/editTool.ts +42 -48
  173. package/src/tools/exitPlanMode.ts +93 -0
  174. package/src/tools/globTool.ts +4 -4
  175. package/src/tools/grepTool.ts +9 -5
  176. package/src/tools/lsTool.ts +27 -4
  177. package/src/tools/lspTool.ts +3 -2
  178. package/src/tools/multiEditTool.ts +40 -42
  179. package/src/tools/readTool.ts +23 -4
  180. package/src/tools/skillTool.ts +35 -30
  181. package/src/tools/taskTool.ts +39 -33
  182. package/src/tools/todoWriteTool.ts +3 -2
  183. package/src/tools/writeTool.ts +19 -27
  184. package/src/types/commands.ts +1 -0
  185. package/src/types/config.ts +2 -0
  186. package/src/types/configuration.ts +42 -1
  187. package/src/types/hooks.ts +8 -25
  188. package/src/types/index.ts +3 -0
  189. package/src/types/marketplace.ts +52 -0
  190. package/src/types/permissions.ts +29 -6
  191. package/src/types/plugins.ts +37 -0
  192. package/src/types/tools.ts +20 -0
  193. package/src/utils/bashHistory.ts +9 -16
  194. package/src/utils/bashParser.ts +97 -46
  195. package/src/utils/configPaths.ts +7 -0
  196. package/src/utils/constants.ts +5 -0
  197. package/src/utils/convertMessagesForAPI.ts +4 -2
  198. package/src/utils/customCommands.ts +1 -1
  199. package/src/utils/editUtils.ts +82 -0
  200. package/src/utils/markdownParser.ts +28 -1
  201. package/src/utils/messageOperations.ts +2 -1
  202. package/src/utils/nameGenerator.ts +78 -0
  203. package/src/utils/openaiClient.ts +14 -2
  204. 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
- const SAFE_COMMANDS = ["cd", "ls", "pwd"];
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
- return this.resolveEffectivePermissionMode(cliPermissionMode);
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 = ["Edit", "MultiEdit", "Delete", "Write"];
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 === "Bash" && toolInput?.command) {
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 === "Bash" && toolInput?.command) {
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 (workdir && (cmd === "cd" || cmd === "ls")) {
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 absolutePath = path.resolve(workdir, cleanPath);
219
- return !isPathInside(absolutePath, workdir);
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 is allowed by persistent rules
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
- 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)
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
- 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;
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
- 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;
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
- if (allowedByRule)
287
- return true;
288
- return !this.isRestrictedTool(context.toolName);
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
- // Add other tools if needed in the future
292
- return false;
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 absolutePath = path.resolve(workdir, cleanPath);
327
- return isPathInside(absolutePath, workdir);
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 absolutePath = path.resolve(workdir, cleanPath);
350
- return !isPathInside(absolutePath, workdir);
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"}