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.
Files changed (236) hide show
  1. package/dist/agent.d.ts +92 -23
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +351 -137
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -0
  7. package/dist/managers/aiManager.d.ts +14 -36
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +74 -77
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +4 -3
  12. package/dist/managers/hookManager.d.ts +3 -8
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +39 -29
  15. package/dist/managers/liveConfigManager.d.ts +55 -18
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  17. package/dist/managers/liveConfigManager.js +372 -90
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +8 -16
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +52 -74
  24. package/dist/managers/permissionManager.d.ts +75 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +368 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +0 -1
  32. package/dist/managers/subagentManager.d.ts +8 -23
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +97 -117
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +3 -1
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +123 -30
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts.map +1 -1
  45. package/dist/services/fileWatcher.js +5 -6
  46. package/dist/services/hook.d.ts +7 -124
  47. package/dist/services/hook.d.ts.map +1 -1
  48. package/dist/services/hook.js +46 -458
  49. package/dist/services/jsonlHandler.d.ts +24 -15
  50. package/dist/services/jsonlHandler.d.ts.map +1 -1
  51. package/dist/services/jsonlHandler.js +67 -88
  52. package/dist/services/memory.d.ts +0 -9
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +2 -49
  55. package/dist/services/session.d.ts +82 -33
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +275 -181
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +109 -11
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +25 -0
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +30 -6
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +26 -7
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +111 -2
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +25 -9
  78. package/dist/types/commands.d.ts +0 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +10 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +41 -0
  88. package/dist/types/environment.d.ts.map +1 -1
  89. package/dist/types/fileSearch.d.ts +5 -0
  90. package/dist/types/fileSearch.d.ts.map +1 -0
  91. package/dist/types/fileSearch.js +1 -0
  92. package/dist/types/hooks.d.ts +11 -2
  93. package/dist/types/hooks.d.ts.map +1 -1
  94. package/dist/types/hooks.js +1 -7
  95. package/dist/types/index.d.ts +5 -0
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/index.js +5 -0
  98. package/dist/types/lsp.d.ts +90 -0
  99. package/dist/types/lsp.d.ts.map +1 -0
  100. package/dist/types/lsp.js +4 -0
  101. package/dist/types/messaging.d.ts +6 -11
  102. package/dist/types/messaging.d.ts.map +1 -1
  103. package/dist/types/permissions.d.ts +39 -0
  104. package/dist/types/permissions.d.ts.map +1 -0
  105. package/dist/types/permissions.js +12 -0
  106. package/dist/types/session.d.ts +1 -6
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/skills.d.ts +1 -0
  109. package/dist/types/skills.d.ts.map +1 -1
  110. package/dist/types/tools.d.ts +35 -0
  111. package/dist/types/tools.d.ts.map +1 -0
  112. package/dist/types/tools.js +4 -0
  113. package/dist/utils/abortUtils.d.ts +34 -0
  114. package/dist/utils/abortUtils.d.ts.map +1 -0
  115. package/dist/utils/abortUtils.js +92 -0
  116. package/dist/utils/bashHistory.d.ts +4 -0
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +21 -4
  119. package/dist/utils/bashParser.d.ts +24 -0
  120. package/dist/utils/bashParser.d.ts.map +1 -0
  121. package/dist/utils/bashParser.js +413 -0
  122. package/dist/utils/builtinSubagents.d.ts +7 -0
  123. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  124. package/dist/utils/builtinSubagents.js +65 -0
  125. package/dist/utils/cacheControlUtils.d.ts +8 -33
  126. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  127. package/dist/utils/cacheControlUtils.js +83 -126
  128. package/dist/utils/constants.d.ts +0 -12
  129. package/dist/utils/constants.d.ts.map +1 -1
  130. package/dist/utils/constants.js +1 -13
  131. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  132. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  133. package/dist/utils/convertMessagesForAPI.js +33 -14
  134. package/dist/utils/fileSearch.d.ts +14 -0
  135. package/dist/utils/fileSearch.d.ts.map +1 -0
  136. package/dist/utils/fileSearch.js +88 -0
  137. package/dist/utils/fileUtils.d.ts +14 -2
  138. package/dist/utils/fileUtils.d.ts.map +1 -1
  139. package/dist/utils/fileUtils.js +101 -17
  140. package/dist/utils/globalLogger.d.ts +0 -14
  141. package/dist/utils/globalLogger.d.ts.map +1 -1
  142. package/dist/utils/globalLogger.js +0 -16
  143. package/dist/utils/markdownParser.d.ts.map +1 -1
  144. package/dist/utils/markdownParser.js +1 -17
  145. package/dist/utils/messageOperations.d.ts +1 -11
  146. package/dist/utils/messageOperations.d.ts.map +1 -1
  147. package/dist/utils/messageOperations.js +7 -24
  148. package/dist/utils/pathEncoder.d.ts +4 -0
  149. package/dist/utils/pathEncoder.d.ts.map +1 -1
  150. package/dist/utils/pathEncoder.js +16 -9
  151. package/dist/utils/pathSafety.d.ts +10 -0
  152. package/dist/utils/pathSafety.d.ts.map +1 -0
  153. package/dist/utils/pathSafety.js +23 -0
  154. package/dist/utils/subagentParser.d.ts +2 -2
  155. package/dist/utils/subagentParser.d.ts.map +1 -1
  156. package/dist/utils/subagentParser.js +10 -7
  157. package/package.json +9 -9
  158. package/src/agent.ts +475 -216
  159. package/src/index.ts +3 -0
  160. package/src/managers/aiManager.ts +107 -111
  161. package/src/managers/backgroundBashManager.ts +4 -3
  162. package/src/managers/hookManager.ts +44 -39
  163. package/src/managers/liveConfigManager.ts +524 -138
  164. package/src/managers/lspManager.ts +434 -0
  165. package/src/managers/messageManager.ts +73 -103
  166. package/src/managers/permissionManager.ts +480 -0
  167. package/src/managers/skillManager.ts +3 -1
  168. package/src/managers/slashCommandManager.ts +1 -2
  169. package/src/managers/subagentManager.ts +116 -159
  170. package/src/managers/toolManager.ts +95 -3
  171. package/src/services/aiService.ts +207 -26
  172. package/src/services/configurationService.ts +762 -0
  173. package/src/services/fileWatcher.ts +5 -6
  174. package/src/services/hook.ts +50 -631
  175. package/src/services/jsonlHandler.ts +84 -100
  176. package/src/services/memory.ts +2 -59
  177. package/src/services/session.ts +338 -213
  178. package/src/tools/bashTool.ts +126 -13
  179. package/src/tools/deleteFileTool.ts +36 -0
  180. package/src/tools/editTool.ts +41 -7
  181. package/src/tools/lspTool.ts +760 -0
  182. package/src/tools/multiEditTool.ts +37 -8
  183. package/src/tools/readTool.ts +125 -2
  184. package/src/tools/skillTool.ts +2 -2
  185. package/src/tools/todoWriteTool.ts +33 -1
  186. package/src/tools/types.ts +15 -9
  187. package/src/tools/writeTool.ts +36 -10
  188. package/src/types/commands.ts +0 -1
  189. package/src/types/config.ts +5 -0
  190. package/src/types/configuration.ts +73 -0
  191. package/src/types/core.ts +11 -0
  192. package/src/types/environment.ts +44 -0
  193. package/src/types/fileSearch.ts +4 -0
  194. package/src/types/hooks.ts +14 -11
  195. package/src/types/index.ts +5 -0
  196. package/src/types/lsp.ts +96 -0
  197. package/src/types/messaging.ts +8 -13
  198. package/src/types/permissions.ts +52 -0
  199. package/src/types/session.ts +3 -8
  200. package/src/types/skills.ts +1 -0
  201. package/src/types/tools.ts +38 -0
  202. package/src/utils/abortUtils.ts +118 -0
  203. package/src/utils/bashHistory.ts +28 -4
  204. package/src/utils/bashParser.ts +444 -0
  205. package/src/utils/builtinSubagents.ts +71 -0
  206. package/src/utils/cacheControlUtils.ts +106 -171
  207. package/src/utils/constants.ts +1 -16
  208. package/src/utils/convertMessagesForAPI.ts +38 -14
  209. package/src/utils/fileSearch.ts +107 -0
  210. package/src/utils/fileUtils.ts +114 -19
  211. package/src/utils/globalLogger.ts +0 -17
  212. package/src/utils/markdownParser.ts +1 -19
  213. package/src/utils/messageOperations.ts +7 -35
  214. package/src/utils/pathEncoder.ts +24 -9
  215. package/src/utils/pathSafety.ts +26 -0
  216. package/src/utils/subagentParser.ts +11 -8
  217. package/dist/constants/events.d.ts +0 -28
  218. package/dist/constants/events.d.ts.map +0 -1
  219. package/dist/constants/events.js +0 -27
  220. package/dist/services/configurationWatcher.d.ts +0 -120
  221. package/dist/services/configurationWatcher.d.ts.map +0 -1
  222. package/dist/services/configurationWatcher.js +0 -439
  223. package/dist/services/memoryStore.d.ts +0 -81
  224. package/dist/services/memoryStore.d.ts.map +0 -1
  225. package/dist/services/memoryStore.js +0 -200
  226. package/dist/types/memoryStore.d.ts +0 -82
  227. package/dist/types/memoryStore.d.ts.map +0 -1
  228. package/dist/types/memoryStore.js +0 -7
  229. package/dist/utils/configResolver.d.ts +0 -65
  230. package/dist/utils/configResolver.d.ts.map +0 -1
  231. package/dist/utils/configResolver.js +0 -210
  232. package/src/constants/events.ts +0 -38
  233. package/src/services/configurationWatcher.ts +0 -622
  234. package/src/services/memoryStore.ts +0 -279
  235. package/src/types/memoryStore.ts +0 -94
  236. package/src/utils/configResolver.ts +0 -302
@@ -0,0 +1,480 @@
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
+
9
+ import path from "node:path";
10
+ import type {
11
+ PermissionDecision,
12
+ ToolPermissionContext,
13
+ PermissionCallback,
14
+ PermissionMode,
15
+ } from "../types/permissions.js";
16
+ import { RESTRICTED_TOOLS } from "../types/permissions.js";
17
+ import type { Logger } from "../types/index.js";
18
+ import {
19
+ splitBashCommand,
20
+ stripEnvVars,
21
+ stripRedirections,
22
+ getSmartPrefix,
23
+ DANGEROUS_COMMANDS,
24
+ } from "../utils/bashParser.js";
25
+ import { isPathInside } from "../utils/pathSafety.js";
26
+
27
+ const SAFE_COMMANDS = ["cd", "ls", "pwd"];
28
+
29
+ export interface PermissionManagerOptions {
30
+ /** Logger for debugging permission decisions */
31
+ logger?: Logger;
32
+ /** Configured default permission mode from settings */
33
+ configuredDefaultMode?: PermissionMode;
34
+ /** Allowed rules from settings */
35
+ allowedRules?: string[];
36
+ }
37
+
38
+ export class PermissionManager {
39
+ private logger?: Logger;
40
+ private configuredDefaultMode?: PermissionMode;
41
+ private allowedRules: string[] = [];
42
+ private onConfiguredDefaultModeChange?: (mode: PermissionMode) => void;
43
+
44
+ constructor(options: PermissionManagerOptions = {}) {
45
+ this.logger = options.logger;
46
+ this.configuredDefaultMode = options.configuredDefaultMode;
47
+ this.allowedRules = options.allowedRules || [];
48
+ }
49
+
50
+ /**
51
+ * Set a callback to be notified when the effective permission mode changes due to configuration updates
52
+ */
53
+ public setOnConfiguredDefaultModeChange(
54
+ callback: (mode: PermissionMode) => void,
55
+ ): void {
56
+ this.onConfiguredDefaultModeChange = callback;
57
+ }
58
+
59
+ /**
60
+ * Update the configured default mode (e.g., when configuration reloads)
61
+ */
62
+ updateConfiguredDefaultMode(defaultMode?: PermissionMode): void {
63
+ const oldEffectiveMode = this.getCurrentEffectiveMode();
64
+
65
+ this.logger?.debug("Updating configured default permission mode", {
66
+ previous: this.configuredDefaultMode,
67
+ new: defaultMode,
68
+ });
69
+ this.configuredDefaultMode = defaultMode;
70
+
71
+ const newEffectiveMode = this.getCurrentEffectiveMode();
72
+ if (
73
+ oldEffectiveMode !== newEffectiveMode &&
74
+ this.onConfiguredDefaultModeChange
75
+ ) {
76
+ this.logger?.debug(
77
+ "Effective permission mode changed due to configuration update",
78
+ {
79
+ oldMode: oldEffectiveMode,
80
+ newMode: newEffectiveMode,
81
+ },
82
+ );
83
+ this.onConfiguredDefaultModeChange(newEffectiveMode);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get all currently allowed rules
89
+ */
90
+ public getAllowedRules(): string[] {
91
+ return [...this.allowedRules];
92
+ }
93
+
94
+ /**
95
+ * Update the allowed rules (e.g., when configuration reloads)
96
+ */
97
+ updateAllowedRules(rules: string[]): void {
98
+ this.logger?.debug("Updating allowed permission rules", {
99
+ count: rules.length,
100
+ });
101
+ this.allowedRules = rules;
102
+ }
103
+
104
+ /**
105
+ * Get the current effective permission mode for tool execution context
106
+ */
107
+ getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode {
108
+ return this.resolveEffectivePermissionMode(cliPermissionMode);
109
+ }
110
+
111
+ /**
112
+ * Resolve the effective permission mode based on CLI override and configured default
113
+ */
114
+ resolveEffectivePermissionMode(
115
+ cliPermissionMode?: PermissionMode,
116
+ ): PermissionMode {
117
+ // CLI override takes highest precedence
118
+ if (cliPermissionMode !== undefined) {
119
+ this.logger?.debug("Using CLI permission mode override", {
120
+ cliMode: cliPermissionMode,
121
+ configuredDefault: this.configuredDefaultMode,
122
+ });
123
+ return cliPermissionMode;
124
+ }
125
+
126
+ // Use configured default mode if available
127
+ if (this.configuredDefaultMode !== undefined) {
128
+ this.logger?.debug("Using configured default permission mode", {
129
+ configuredDefault: this.configuredDefaultMode,
130
+ });
131
+ return this.configuredDefaultMode;
132
+ }
133
+
134
+ // Fall back to system default
135
+ this.logger?.debug("Using system default permission mode");
136
+ return "default";
137
+ }
138
+
139
+ /**
140
+ * Check if a tool execution requires permission and handle the authorization flow
141
+ * Called by individual tools after validation/diff, before real operation
142
+ */
143
+ async checkPermission(
144
+ context: ToolPermissionContext,
145
+ ): Promise<PermissionDecision> {
146
+ this.logger?.debug("Checking permission for tool", {
147
+ toolName: context.toolName,
148
+ permissionMode: context.permissionMode,
149
+ hasCallback: !!context.canUseToolCallback,
150
+ });
151
+
152
+ // 1. If bypassPermissions mode, always allow
153
+ if (context.permissionMode === "bypassPermissions") {
154
+ this.logger?.debug("Permission bypassed for tool", {
155
+ toolName: context.toolName,
156
+ });
157
+ return { behavior: "allow" };
158
+ }
159
+
160
+ // 1.1 If acceptEdits mode, allow Edit, MultiEdit, Delete, Write
161
+ if (context.permissionMode === "acceptEdits") {
162
+ const autoAcceptedTools = ["Edit", "MultiEdit", "Delete", "Write"];
163
+ if (autoAcceptedTools.includes(context.toolName)) {
164
+ this.logger?.debug(
165
+ "Permission automatically accepted for tool in acceptEdits mode",
166
+ {
167
+ toolName: context.toolName,
168
+ },
169
+ );
170
+ return { behavior: "allow" };
171
+ }
172
+ }
173
+
174
+ // 1.2 Check if tool call matches any allowed rule
175
+ if (this.isAllowedByRule(context)) {
176
+ this.logger?.debug("Permission allowed by persistent rule", {
177
+ toolName: context.toolName,
178
+ });
179
+ return { behavior: "allow" };
180
+ }
181
+
182
+ // 2. If not a restricted tool, always allow
183
+ if (!this.isRestrictedTool(context.toolName)) {
184
+ this.logger?.debug("Tool is not restricted, allowing", {
185
+ toolName: context.toolName,
186
+ });
187
+ return { behavior: "allow" };
188
+ }
189
+
190
+ // 3. If custom callback provided, call it and return result
191
+ if (context.canUseToolCallback) {
192
+ try {
193
+ this.logger?.debug("Calling custom permission callback for tool", {
194
+ toolName: context.toolName,
195
+ });
196
+ const decision = await context.canUseToolCallback(context);
197
+ this.logger?.debug("Custom callback returned decision", {
198
+ toolName: context.toolName,
199
+ decision,
200
+ });
201
+ return decision;
202
+ } catch (error) {
203
+ const errorMessage =
204
+ error instanceof Error ? error.message : String(error);
205
+ this.logger?.error("Error in permission callback", {
206
+ toolName: context.toolName,
207
+ error: errorMessage,
208
+ });
209
+ return {
210
+ behavior: "deny",
211
+ message: "Error in permission callback",
212
+ };
213
+ }
214
+ }
215
+
216
+ // 4. For default mode on restricted tools without callback, integrate with CLI confirmation
217
+ // Note: CLI confirmation integration will be implemented in Phase 2
218
+ this.logger?.warn(
219
+ "No permission callback provided for restricted tool in default mode",
220
+ {
221
+ toolName: context.toolName,
222
+ },
223
+ );
224
+ return {
225
+ behavior: "deny",
226
+ message: `Tool '${context.toolName}' requires permission approval. No permission callback configured.`,
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Determine if a tool requires permission checks based on its name
232
+ */
233
+ isRestrictedTool(toolName: string): boolean {
234
+ const isRestricted = (RESTRICTED_TOOLS as readonly string[]).includes(
235
+ toolName,
236
+ );
237
+ this.logger?.debug("Checking if tool is restricted", {
238
+ toolName,
239
+ isRestricted,
240
+ });
241
+ return isRestricted;
242
+ }
243
+
244
+ /**
245
+ * Helper method to create a permission context for CLI integration
246
+ */
247
+ createContext(
248
+ toolName: string,
249
+ permissionMode: PermissionMode,
250
+ callback?: PermissionCallback,
251
+ toolInput?: Record<string, unknown>,
252
+ ): ToolPermissionContext {
253
+ let suggestedPrefix: string | undefined;
254
+ if (toolName === "Bash" && toolInput?.command) {
255
+ const command = String(toolInput.command);
256
+ const parts = splitBashCommand(command);
257
+ // Only suggest prefix for single commands to avoid confusion with complex chains
258
+ if (parts.length === 1) {
259
+ const processedPart = stripRedirections(stripEnvVars(parts[0]));
260
+ suggestedPrefix = getSmartPrefix(processedPart) ?? undefined;
261
+ }
262
+ }
263
+
264
+ const context: ToolPermissionContext = {
265
+ toolName,
266
+ permissionMode,
267
+ canUseToolCallback: callback,
268
+ toolInput,
269
+ suggestedPrefix,
270
+ };
271
+
272
+ // Set hidePersistentOption for dangerous or out-of-bounds bash commands
273
+ if (toolName === "Bash" && toolInput?.command) {
274
+ const command = String(toolInput.command);
275
+ const workdir = toolInput.workdir as string | undefined;
276
+ const parts = splitBashCommand(command);
277
+
278
+ const isDangerous = parts.some((part) => {
279
+ const processedPart = stripRedirections(stripEnvVars(part));
280
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
281
+ if (commandMatch) {
282
+ const cmd = commandMatch[1];
283
+ const args = commandMatch[2]?.trim() || "";
284
+
285
+ // Check blacklist
286
+ if (DANGEROUS_COMMANDS.includes(cmd)) {
287
+ return true;
288
+ }
289
+
290
+ // Check out-of-bounds for cd and ls
291
+ if (workdir && (cmd === "cd" || cmd === "ls")) {
292
+ const pathArgs =
293
+ (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
294
+ (arg) => !arg.startsWith("-"),
295
+ ) || [];
296
+
297
+ return pathArgs.some((pathArg) => {
298
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
299
+ const absolutePath = path.resolve(workdir, cleanPath);
300
+ return !isPathInside(absolutePath, workdir);
301
+ });
302
+ }
303
+ }
304
+ return false;
305
+ });
306
+
307
+ if (isDangerous) {
308
+ context.hidePersistentOption = true;
309
+ }
310
+ }
311
+
312
+ this.logger?.debug("Created permission context", {
313
+ toolName,
314
+ permissionMode,
315
+ hasCallback: !!callback,
316
+ hasToolInput: !!toolInput,
317
+ suggestedPrefix,
318
+ });
319
+
320
+ return context;
321
+ }
322
+
323
+ /**
324
+ * Check if a tool call is allowed by persistent rules
325
+ */
326
+ private isAllowedByRule(context: ToolPermissionContext): boolean {
327
+ if (context.toolName === "Bash" && context.toolInput?.command) {
328
+ const command = String(context.toolInput.command);
329
+ const parts = splitBashCommand(command);
330
+ if (parts.length === 0) return false;
331
+
332
+ const workdir = context.toolInput?.workdir as string | undefined;
333
+
334
+ return parts.every((part) => {
335
+ const processedPart = stripRedirections(stripEnvVars(part));
336
+
337
+ // Check for safe commands
338
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
339
+ if (commandMatch) {
340
+ const cmd = commandMatch[1];
341
+ const args = commandMatch[2]?.trim() || "";
342
+
343
+ if (SAFE_COMMANDS.includes(cmd)) {
344
+ if (workdir) {
345
+ if (cmd === "pwd") {
346
+ return true;
347
+ }
348
+
349
+ // For cd and ls, check paths
350
+ const pathArgs =
351
+ (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
352
+ (arg) => !arg.startsWith("-"),
353
+ ) || [];
354
+
355
+ if (pathArgs.length === 0) {
356
+ // cd or ls without arguments operates on current dir (workdir)
357
+ return true;
358
+ }
359
+
360
+ const allPathsSafe = pathArgs.every((pathArg) => {
361
+ // Remove quotes if present
362
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
363
+ const absolutePath = path.resolve(workdir, cleanPath);
364
+ return isPathInside(absolutePath, workdir);
365
+ });
366
+
367
+ if (allPathsSafe) {
368
+ return true;
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ const action = `${context.toolName}(${processedPart})`;
375
+ const allowedByRule = this.allowedRules.some((rule) => {
376
+ if (rule.endsWith(":*)")) {
377
+ const prefix = rule.slice(0, -3);
378
+ return action.startsWith(prefix);
379
+ }
380
+ return action === rule;
381
+ });
382
+
383
+ if (allowedByRule) return true;
384
+ return !this.isRestrictedTool(context.toolName);
385
+ });
386
+ }
387
+ // Add other tools if needed in the future
388
+ return false;
389
+ }
390
+
391
+ /**
392
+ * Expand a bash command into individual permission rules, filtering out safe commands.
393
+ * Used when saving permissions to the allow list.
394
+ *
395
+ * @param command The full bash command string
396
+ * @param workdir The working directory for path safety checks
397
+ * @returns Array of permission rules in "Bash(cmd)" format
398
+ */
399
+ public expandBashRule(command: string, workdir: string): string[] {
400
+ const parts = splitBashCommand(command);
401
+ const rules: string[] = [];
402
+
403
+ for (const part of parts) {
404
+ const processedPart = stripRedirections(stripEnvVars(part));
405
+
406
+ // Check for safe commands
407
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
408
+ let isSafe = false;
409
+
410
+ if (commandMatch) {
411
+ const cmd = commandMatch[1];
412
+ const args = commandMatch[2]?.trim() || "";
413
+
414
+ if (SAFE_COMMANDS.includes(cmd)) {
415
+ if (cmd === "pwd") {
416
+ isSafe = true;
417
+ } else {
418
+ // For cd and ls, check paths
419
+ const pathArgs =
420
+ (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
421
+ (arg) => !arg.startsWith("-"),
422
+ ) || [];
423
+
424
+ if (pathArgs.length === 0) {
425
+ isSafe = true;
426
+ } else {
427
+ const allPathsSafe = pathArgs.every((pathArg) => {
428
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
429
+ const absolutePath = path.resolve(workdir, cleanPath);
430
+ return isPathInside(absolutePath, workdir);
431
+ });
432
+ if (allPathsSafe) {
433
+ isSafe = true;
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
439
+
440
+ if (!isSafe) {
441
+ // Check if command is dangerous or out-of-bounds
442
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
443
+ if (commandMatch) {
444
+ const cmd = commandMatch[1];
445
+ const args = commandMatch[2]?.trim() || "";
446
+
447
+ if (DANGEROUS_COMMANDS.includes(cmd)) {
448
+ continue;
449
+ }
450
+
451
+ if (cmd === "cd" || cmd === "ls") {
452
+ const pathArgs =
453
+ (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
454
+ (arg) => !arg.startsWith("-"),
455
+ ) || [];
456
+
457
+ const isOutOfBounds = pathArgs.some((pathArg) => {
458
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
459
+ const absolutePath = path.resolve(workdir, cleanPath);
460
+ return !isPathInside(absolutePath, workdir);
461
+ });
462
+
463
+ if (isOutOfBounds) {
464
+ continue;
465
+ }
466
+ }
467
+ }
468
+
469
+ const smartPrefix = getSmartPrefix(processedPart);
470
+ if (smartPrefix) {
471
+ rules.push(`Bash(${smartPrefix}:*)`);
472
+ } else {
473
+ rules.push(`Bash(${processedPart})`);
474
+ }
475
+ }
476
+ }
477
+
478
+ return rules;
479
+ }
480
+ }
@@ -20,6 +20,7 @@ export class SkillManager {
20
20
  private personalSkillsPath: string;
21
21
  private scanTimeout: number;
22
22
  private logger?: Logger;
23
+ private workdir: string;
23
24
 
24
25
  private skillMetadata = new Map<string, SkillMetadata>();
25
26
  private skillContent = new Map<string, Skill>();
@@ -30,6 +31,7 @@ export class SkillManager {
30
31
  options.personalSkillsPath || join(homedir(), ".wave", "skills");
31
32
  this.scanTimeout = options.scanTimeout || 5000;
32
33
  this.logger = options.logger;
34
+ this.workdir = options.workdir || process.cwd();
33
35
  }
34
36
 
35
37
  /**
@@ -123,7 +125,7 @@ export class SkillManager {
123
125
  );
124
126
 
125
127
  const projectCollection = await this.discoverSkillCollection(
126
- process.cwd(),
128
+ this.workdir,
127
129
  "project",
128
130
  );
129
131
 
@@ -218,7 +218,7 @@ export class SlashCommandManager {
218
218
  private async executeCustomCommandInMainAgent(
219
219
  commandName: string,
220
220
  content: string,
221
- config?: { model?: string; allowedTools?: string[] },
221
+ config?: { model?: string },
222
222
  args?: string,
223
223
  ): Promise<void> {
224
224
  try {
@@ -272,7 +272,6 @@ export class SlashCommandManager {
272
272
  // Execute the AI conversation with custom configuration
273
273
  await this.aiManager.sendAIMessage({
274
274
  model: config?.model,
275
- allowedTools: config?.allowedTools,
276
275
  });
277
276
  } catch (error) {
278
277
  this.logger?.error(