wave-agent-sdk 1.0.4 → 1.0.6

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 (194) hide show
  1. package/dist/agent.d.ts +23 -1
  2. package/dist/agent.js +34 -4
  3. package/dist/managers/aiManager.d.ts +36 -0
  4. package/dist/managers/aiManager.js +240 -8
  5. package/dist/managers/messageManager.d.ts +1 -3
  6. package/dist/managers/messageManager.js +19 -9
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/services/session.js +2 -9
  10. package/dist/telemetry/instrumentation.d.ts +1 -1
  11. package/dist/telemetry/instrumentation.js +57 -28
  12. package/dist/tools/bashTool.js +16 -6
  13. package/dist/tools/grepTool.js +11 -3
  14. package/dist/types/index.d.ts +3 -0
  15. package/dist/types/messaging.d.ts +2 -0
  16. package/dist/utils/containerSetup.js +0 -3
  17. package/dist/utils/messageOperations.d.ts +10 -1
  18. package/dist/utils/messageOperations.js +19 -1
  19. package/dist/utils/notificationXml.js +5 -0
  20. package/dist/utils/ripgrep.d.ts +4 -4
  21. package/dist/utils/ripgrep.js +4 -27
  22. package/package.json +20 -8
  23. package/bin/rg +0 -79
  24. package/dist/managers/forkedAgentManager.d.ts +0 -50
  25. package/dist/managers/forkedAgentManager.js +0 -130
  26. package/scripts/install_ripgrep.js +0 -111
  27. package/scripts/postinstall.js +0 -38
  28. package/src/agent.ts +0 -1271
  29. package/src/constants/subagents.ts +0 -4
  30. package/src/constants/toolLimits.ts +0 -15
  31. package/src/constants/tools.ts +0 -24
  32. package/src/core/plugin.ts +0 -237
  33. package/src/core/session.ts +0 -9
  34. package/src/index.ts +0 -38
  35. package/src/managers/MemoryRuleManager.ts +0 -198
  36. package/src/managers/aiManager.ts +0 -2429
  37. package/src/managers/backgroundTaskManager.ts +0 -480
  38. package/src/managers/bangManager.ts +0 -119
  39. package/src/managers/cronManager.ts +0 -401
  40. package/src/managers/foregroundTaskManager.ts +0 -30
  41. package/src/managers/forkedAgentManager.ts +0 -216
  42. package/src/managers/hookManager.ts +0 -1089
  43. package/src/managers/liveConfigManager.ts +0 -445
  44. package/src/managers/lspManager.ts +0 -470
  45. package/src/managers/mcpManager.ts +0 -1010
  46. package/src/managers/messageManager.ts +0 -1180
  47. package/src/managers/messageQueue.ts +0 -157
  48. package/src/managers/permissionManager.ts +0 -1140
  49. package/src/managers/planManager.ts +0 -112
  50. package/src/managers/pluginManager.ts +0 -330
  51. package/src/managers/pluginScopeManager.ts +0 -124
  52. package/src/managers/reversionManager.ts +0 -178
  53. package/src/managers/skillManager.ts +0 -578
  54. package/src/managers/slashCommandManager.ts +0 -564
  55. package/src/managers/subagentManager.ts +0 -915
  56. package/src/managers/toolManager.ts +0 -489
  57. package/src/managers/workflowManager.ts +0 -493
  58. package/src/prompts/autoMemory.ts +0 -33
  59. package/src/prompts/autoMemoryExtraction.ts +0 -146
  60. package/src/prompts/index.ts +0 -506
  61. package/src/prompts/planModeReminders.ts +0 -126
  62. package/src/services/GitService.ts +0 -131
  63. package/src/services/MarketplaceService.ts +0 -1052
  64. package/src/services/MemoryRuleService.ts +0 -71
  65. package/src/services/aiService.ts +0 -919
  66. package/src/services/authService.ts +0 -565
  67. package/src/services/autoMemoryService.ts +0 -280
  68. package/src/services/configurationService.ts +0 -1520
  69. package/src/services/fileWatcher.ts +0 -319
  70. package/src/services/hook.ts +0 -348
  71. package/src/services/initializationService.ts +0 -357
  72. package/src/services/interactionService.ts +0 -229
  73. package/src/services/jsonlHandler.ts +0 -295
  74. package/src/services/memory.ts +0 -234
  75. package/src/services/pluginLoader.ts +0 -252
  76. package/src/services/remoteSettingsService.ts +0 -366
  77. package/src/services/reversionService.ts +0 -122
  78. package/src/services/session.ts +0 -922
  79. package/src/services/taskManager.ts +0 -321
  80. package/src/telemetry/events.ts +0 -65
  81. package/src/telemetry/instrumentation.ts +0 -499
  82. package/src/telemetry/sessionTracing.ts +0 -348
  83. package/src/tools/agentTool.ts +0 -322
  84. package/src/tools/askUserQuestion.ts +0 -160
  85. package/src/tools/bashTool.ts +0 -621
  86. package/src/tools/buildTool.ts +0 -61
  87. package/src/tools/cronCreateTool.ts +0 -161
  88. package/src/tools/cronDeleteTool.ts +0 -51
  89. package/src/tools/cronListTool.ts +0 -47
  90. package/src/tools/editTool.ts +0 -313
  91. package/src/tools/enterPlanMode.ts +0 -124
  92. package/src/tools/enterWorktreeTool.ts +0 -190
  93. package/src/tools/exitPlanMode.ts +0 -139
  94. package/src/tools/exitWorktreeTool.ts +0 -236
  95. package/src/tools/globTool.ts +0 -167
  96. package/src/tools/grepTool.ts +0 -399
  97. package/src/tools/lspTool.ts +0 -883
  98. package/src/tools/readTool.ts +0 -426
  99. package/src/tools/skillTool.ts +0 -248
  100. package/src/tools/taskManagementTools.ts +0 -574
  101. package/src/tools/taskStopTool.ts +0 -78
  102. package/src/tools/types.ts +0 -137
  103. package/src/tools/webFetchTool.ts +0 -373
  104. package/src/tools/workflowTool.ts +0 -205
  105. package/src/tools/writeTool.ts +0 -267
  106. package/src/types/agent.ts +0 -128
  107. package/src/types/auth.ts +0 -19
  108. package/src/types/commands.ts +0 -30
  109. package/src/types/config.ts +0 -43
  110. package/src/types/configuration.ts +0 -168
  111. package/src/types/core.ts +0 -105
  112. package/src/types/cron.ts +0 -13
  113. package/src/types/environment.ts +0 -106
  114. package/src/types/fileSearch.ts +0 -4
  115. package/src/types/history.ts +0 -7
  116. package/src/types/hooks.ts +0 -256
  117. package/src/types/index.ts +0 -41
  118. package/src/types/lsp.ts +0 -98
  119. package/src/types/marketplace.ts +0 -71
  120. package/src/types/mcp.ts +0 -43
  121. package/src/types/memoryRule.ts +0 -31
  122. package/src/types/messaging.ts +0 -114
  123. package/src/types/permissions.ts +0 -80
  124. package/src/types/plugins.ts +0 -39
  125. package/src/types/processes.ts +0 -99
  126. package/src/types/reversion.ts +0 -29
  127. package/src/types/session.ts +0 -12
  128. package/src/types/skills.ts +0 -108
  129. package/src/types/tasks.ts +0 -13
  130. package/src/types/telemetry.ts +0 -98
  131. package/src/types/tools.ts +0 -44
  132. package/src/types/workflow.ts +0 -6
  133. package/src/utils/abortUtils.ts +0 -118
  134. package/src/utils/atomicWrite.ts +0 -61
  135. package/src/utils/bashParser.ts +0 -899
  136. package/src/utils/cacheControlUtils.ts +0 -426
  137. package/src/utils/commandArgumentParser.ts +0 -161
  138. package/src/utils/commandPathResolver.ts +0 -88
  139. package/src/utils/configPaths.ts +0 -220
  140. package/src/utils/configValidator.ts +0 -100
  141. package/src/utils/constants.ts +0 -39
  142. package/src/utils/container.ts +0 -92
  143. package/src/utils/containerSetup.ts +0 -371
  144. package/src/utils/convertMessagesForAPI.ts +0 -326
  145. package/src/utils/cronTasks.ts +0 -128
  146. package/src/utils/cronTasksLock.ts +0 -182
  147. package/src/utils/cronToHuman.ts +0 -99
  148. package/src/utils/customCommands.ts +0 -95
  149. package/src/utils/editUtils.ts +0 -20
  150. package/src/utils/fileFormat.ts +0 -40
  151. package/src/utils/fileSearch.ts +0 -151
  152. package/src/utils/fileUtils.ts +0 -223
  153. package/src/utils/gitUtils.ts +0 -315
  154. package/src/utils/globalLogger.ts +0 -128
  155. package/src/utils/groupMessagesByApiRound.ts +0 -121
  156. package/src/utils/hookMatcher.ts +0 -176
  157. package/src/utils/markdownParser.ts +0 -317
  158. package/src/utils/mcpUtils.ts +0 -138
  159. package/src/utils/messageOperations.ts +0 -641
  160. package/src/utils/modelCapabilities.ts +0 -30
  161. package/src/utils/nameGenerator.ts +0 -99
  162. package/src/utils/notificationXml.ts +0 -57
  163. package/src/utils/openaiClient.ts +0 -251
  164. package/src/utils/parseCronExpression.ts +0 -78
  165. package/src/utils/path.ts +0 -72
  166. package/src/utils/pathEncoder.ts +0 -400
  167. package/src/utils/pathSafety.ts +0 -40
  168. package/src/utils/promptHistory.ts +0 -168
  169. package/src/utils/ripgrep.ts +0 -35
  170. package/src/utils/shellResolver.ts +0 -190
  171. package/src/utils/skillParser.ts +0 -246
  172. package/src/utils/stringUtils.ts +0 -156
  173. package/src/utils/subagentParser.ts +0 -279
  174. package/src/utils/taskReminder.ts +0 -96
  175. package/src/utils/tokenCalculation.ts +0 -43
  176. package/src/utils/tokenEstimate.ts +0 -34
  177. package/src/utils/toolResultStorage.ts +0 -117
  178. package/src/utils/worktreeSession.ts +0 -26
  179. package/src/utils/worktreeUtils.ts +0 -786
  180. package/src/workflow/budgetTracker.ts +0 -34
  181. package/src/workflow/concurrencyLimiter.ts +0 -47
  182. package/src/workflow/journal.ts +0 -95
  183. package/src/workflow/progressReporter.ts +0 -141
  184. package/src/workflow/runState.ts +0 -65
  185. package/src/workflow/scriptRuntime.ts +0 -274
  186. package/src/workflow/structuredOutput.ts +0 -123
  187. package/src/workflow/types.ts +0 -101
  188. package/src/workflow/workflowApis.ts +0 -410
  189. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  190. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  191. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  192. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  193. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  194. package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
@@ -1,899 +0,0 @@
1
- /**
2
- * Splits a complex bash command into individual simple commands by shell operators (&&, ||, ;, |, &).
3
- * Correctly handles quotes, escaped characters, and subshells.
4
- */
5
- export function splitBashCommand(command: string): string[] {
6
- let inSingleQuote = false;
7
- let inDoubleQuote = false;
8
- let escaped = false;
9
- let parenLevel = 0;
10
- const splitPositions: number[] = [];
11
-
12
- for (let i = 0; i < command.length; i++) {
13
- const char = command[i];
14
- const nextChar = command[i + 1];
15
-
16
- if (escaped) {
17
- escaped = false;
18
- continue;
19
- }
20
-
21
- if (char === "\\") {
22
- escaped = true;
23
- continue;
24
- }
25
-
26
- if (char === "'" && !inDoubleQuote) {
27
- inSingleQuote = !inSingleQuote;
28
- continue;
29
- }
30
-
31
- if (char === '"' && !inSingleQuote) {
32
- inDoubleQuote = !inDoubleQuote;
33
- continue;
34
- }
35
-
36
- if (inSingleQuote || inDoubleQuote) {
37
- continue;
38
- }
39
-
40
- if (char === "(") {
41
- parenLevel++;
42
- continue;
43
- }
44
-
45
- if (char === ")") {
46
- parenLevel--;
47
- continue;
48
- }
49
-
50
- if (parenLevel > 0) {
51
- continue;
52
- }
53
-
54
- // Check for operators
55
- let opLen = 0;
56
- if (char === "&" && nextChar === "&") opLen = 2;
57
- else if (char === "|" && nextChar === "|") opLen = 2;
58
- else if (char === "|" && nextChar === "&") opLen = 2;
59
- else if (char === ";") opLen = 1;
60
- else if (char === "|") opLen = 1;
61
- else if (char === "&" && nextChar !== ">" && command[i - 1] !== ">")
62
- opLen = 1;
63
-
64
- if (opLen > 0) {
65
- // Check if preceded by an odd number of backslashes
66
- let backslashCount = 0;
67
- for (let j = i - 1; j >= 0; j--) {
68
- if (command[j] === "\\") backslashCount++;
69
- else break;
70
- }
71
-
72
- // ALSO check if preceded by an escaped operator character (e.g., \&&)
73
- let precededByEscapedOp = false;
74
- if (i > 0 && /[&|;]/.test(command[i - 1])) {
75
- let bsCount = 0;
76
- for (let j = i - 2; j >= 0; j--) {
77
- if (command[j] === "\\") bsCount++;
78
- else break;
79
- }
80
- if (bsCount % 2 !== 0) precededByEscapedOp = true;
81
- }
82
-
83
- if (backslashCount % 2 === 0 && !precededByEscapedOp) {
84
- splitPositions.push(i, i + opLen);
85
- i += opLen - 1;
86
- }
87
- }
88
- }
89
-
90
- let lastPos = 0;
91
- const parts: string[] = [];
92
- for (let i = 0; i < splitPositions.length; i += 2) {
93
- const start = splitPositions[i];
94
- const end = splitPositions[i + 1];
95
- const part = command.substring(lastPos, start).trim();
96
- if (part) parts.push(part);
97
- lastPos = end;
98
- }
99
- const lastPart = command.substring(lastPos).trim();
100
- if (lastPart) parts.push(lastPart);
101
-
102
- const finalResult: string[] = [];
103
- for (const part of parts) {
104
- const envStripped = stripEnvVars(part);
105
- const stripped = stripRedirections(envStripped);
106
- if (
107
- stripped.startsWith("(") &&
108
- stripped.endsWith(")") &&
109
- stripped === envStripped
110
- ) {
111
- const inner = stripped.substring(1, stripped.length - 1).trim();
112
- if (inner) {
113
- finalResult.push(...splitBashCommand(inner));
114
- }
115
- } else {
116
- finalResult.push(part);
117
- }
118
- }
119
-
120
- return finalResult;
121
- }
122
-
123
- /**
124
- * Removes inline environment variable assignments (e.g., VAR=val cmd -> cmd).
125
- */
126
- export function stripEnvVars(command: string): string {
127
- let result = command.trim();
128
- while (true) {
129
- const match = result.match(/^([a-zA-Z_][a-zA-Z0-9_]*)=/);
130
- if (!match) break;
131
-
132
- const varNameEnd = match[0].length;
133
- let valueEnd = varNameEnd;
134
-
135
- if (result[varNameEnd] === "'") {
136
- valueEnd = result.indexOf("'", varNameEnd + 1);
137
- if (valueEnd === -1) break;
138
- valueEnd++;
139
- } else if (result[varNameEnd] === '"') {
140
- let escaped = false;
141
- let found = false;
142
- for (let i = varNameEnd + 1; i < result.length; i++) {
143
- if (escaped) {
144
- escaped = false;
145
- continue;
146
- }
147
- if (result[i] === "\\") {
148
- escaped = true;
149
- continue;
150
- }
151
- if (result[i] === '"') {
152
- valueEnd = i + 1;
153
- found = true;
154
- break;
155
- }
156
- }
157
- if (!found) break;
158
- } else {
159
- const spaceIndex = result.search(/\s/);
160
- if (spaceIndex === -1) {
161
- return "";
162
- }
163
- valueEnd = spaceIndex;
164
- }
165
-
166
- result = result.substring(valueEnd).trim();
167
- }
168
- return result;
169
- }
170
-
171
- /**
172
- * Removes redirections (e.g., echo "data" > output.txt -> echo "data").
173
- */
174
- export function stripRedirections(command: string): string {
175
- let result = "";
176
- let inSingleQuote = false;
177
- let inDoubleQuote = false;
178
- let escaped = false;
179
-
180
- for (let i = 0; i < command.length; i++) {
181
- const char = command[i];
182
-
183
- if (escaped) {
184
- result += char;
185
- escaped = false;
186
- continue;
187
- }
188
-
189
- if (char === "\\") {
190
- result += char;
191
- escaped = true;
192
- continue;
193
- }
194
-
195
- if (char === "'" && !inDoubleQuote) {
196
- inSingleQuote = !inSingleQuote;
197
- result += char;
198
- continue;
199
- }
200
-
201
- if (char === '"' && !inSingleQuote) {
202
- inDoubleQuote = !inDoubleQuote;
203
- result += char;
204
- continue;
205
- }
206
-
207
- if (inSingleQuote || inDoubleQuote) {
208
- result += char;
209
- continue;
210
- }
211
-
212
- // Handle whitespace outside quotes: collapse multiple spaces into one
213
- if (/\s/.test(char)) {
214
- if (result.length > 0 && !/\s/.test(result[result.length - 1])) {
215
- result += " ";
216
- }
217
- continue;
218
- }
219
-
220
- // Check for redirection
221
- if (char === ">" || char === "<") {
222
- // Check if preceded by a digit or & (for 2> or &>)
223
- if (result.length > 0 && /[0-9&]/.test(result[result.length - 1])) {
224
- // Ensure it's at the start of a word or preceded by whitespace
225
- if (result.length === 1 || /\s/.test(result[result.length - 2])) {
226
- // Remove the digit/& from result
227
- result = result.substring(0, result.length - 1);
228
- }
229
- }
230
-
231
- let end = i + 1;
232
- if (command[end] === char) {
233
- end++;
234
- if (char === "<" && command[end] === "-") {
235
- end++;
236
- }
237
- } else if (
238
- command[end] === "&" ||
239
- (char === ">" && command[end] === "|")
240
- ) {
241
- end++;
242
- }
243
-
244
- // Skip whitespace after operator
245
- while (end < command.length && /\s/.test(command[end])) {
246
- end++;
247
- }
248
-
249
- // Skip the following word (the target of redirection)
250
- let wordEscaped = false;
251
- let wordInSingleQuote = false;
252
- let wordInDoubleQuote = false;
253
- while (end < command.length) {
254
- const c = command[end];
255
- if (wordEscaped) {
256
- wordEscaped = false;
257
- end++;
258
- continue;
259
- }
260
- if (c === "\\") {
261
- wordEscaped = true;
262
- end++;
263
- continue;
264
- }
265
- if (c === "'" && !wordInDoubleQuote) {
266
- wordInSingleQuote = !wordInSingleQuote;
267
- end++;
268
- continue;
269
- }
270
- if (c === '"' && !wordInSingleQuote) {
271
- wordInDoubleQuote = !wordInDoubleQuote;
272
- end++;
273
- continue;
274
- }
275
- if (!wordInSingleQuote && !wordInDoubleQuote && /\s/.test(c)) {
276
- break;
277
- }
278
- end++;
279
- }
280
-
281
- i = end - 1;
282
- // After stripping a redirection, ensure there's a space if we're not at the end
283
- if (result.length > 0 && !/\s/.test(result[result.length - 1])) {
284
- result += " ";
285
- }
286
- continue;
287
- }
288
-
289
- result += char;
290
- }
291
-
292
- return result.trim();
293
- }
294
-
295
- /**
296
- * Checks if a bash command contains any write redirections (>, >>, &>, 2>, >|).
297
- */
298
- export function hasWriteRedirections(command: string): boolean {
299
- let inSingleQuote = false;
300
- let inDoubleQuote = false;
301
- let escaped = false;
302
-
303
- for (let i = 0; i < command.length; i++) {
304
- const char = command[i];
305
-
306
- if (escaped) {
307
- escaped = false;
308
- continue;
309
- }
310
-
311
- if (char === "\\") {
312
- escaped = true;
313
- continue;
314
- }
315
-
316
- if (char === "'" && !inDoubleQuote) {
317
- inSingleQuote = !inSingleQuote;
318
- continue;
319
- }
320
-
321
- if (char === '"' && !inSingleQuote) {
322
- inDoubleQuote = !inDoubleQuote;
323
- continue;
324
- }
325
-
326
- if (inSingleQuote || inDoubleQuote) {
327
- continue;
328
- }
329
-
330
- if (char === ">") {
331
- // Check if this is a redirection to /dev/null
332
- let j = i + 1;
333
- // Handle >> or >|
334
- if (j < command.length && (command[j] === ">" || command[j] === "|")) {
335
- j++;
336
- }
337
-
338
- // Skip whitespace after operator
339
- while (j < command.length && /\s/.test(command[j])) {
340
- j++;
341
- }
342
-
343
- // Extract the target word, handling quotes and escapes
344
- let target = "";
345
- let targetEscaped = false;
346
- let targetInSingleQuote = false;
347
- let targetInDoubleQuote = false;
348
- let k = j;
349
- while (k < command.length) {
350
- const c = command[k];
351
- if (targetEscaped) {
352
- targetEscaped = false;
353
- target += c;
354
- k++;
355
- continue;
356
- }
357
- if (c === "\\") {
358
- targetEscaped = true;
359
- k++;
360
- continue;
361
- }
362
- if (c === "'" && !targetInDoubleQuote) {
363
- targetInSingleQuote = !targetInSingleQuote;
364
- k++;
365
- continue;
366
- }
367
- if (c === '"' && !targetInSingleQuote) {
368
- targetInDoubleQuote = !targetInDoubleQuote;
369
- k++;
370
- continue;
371
- }
372
- if (!targetInSingleQuote && !targetInDoubleQuote && /\s/.test(c)) {
373
- break;
374
- }
375
- target += c;
376
- k++;
377
- }
378
-
379
- // If the target is exactly /dev/null, we ignore this redirection
380
- if (target === "/dev/null") {
381
- i = k - 1; // Move the main loop index to the end of the target
382
- continue;
383
- }
384
-
385
- // Ignore file descriptor redirections like 2>&1, >&2, etc.
386
- if (target.startsWith("&") && /^\d+$/.test(target.substring(1))) {
387
- i = k - 1;
388
- continue;
389
- }
390
-
391
- return true;
392
- }
393
- }
394
-
395
- return false;
396
- }
397
-
398
- /**
399
- * Checks if a bash command contains any heredocs (<<, <<-).
400
- */
401
- export function hasHeredoc(command: string): boolean {
402
- let inSingleQuote = false;
403
- let inDoubleQuote = false;
404
- let escaped = false;
405
-
406
- for (let i = 0; i < command.length; i++) {
407
- const char = command[i];
408
-
409
- if (escaped) {
410
- escaped = false;
411
- continue;
412
- }
413
-
414
- if (char === "\\") {
415
- escaped = true;
416
- continue;
417
- }
418
-
419
- if (char === "'" && !inDoubleQuote) {
420
- inSingleQuote = !inSingleQuote;
421
- continue;
422
- }
423
-
424
- if (char === '"' && !inSingleQuote) {
425
- inDoubleQuote = !inDoubleQuote;
426
- continue;
427
- }
428
-
429
- if (inSingleQuote || inDoubleQuote) {
430
- continue;
431
- }
432
-
433
- if (char === "<" && command[i + 1] === "<") {
434
- return true;
435
- }
436
- }
437
-
438
- return false;
439
- }
440
-
441
- /**
442
- * Checks if a bash command is a heredoc write operation (e.g., cat <<EOF > file).
443
- */
444
- export function isBashHeredocWrite(command: string): boolean {
445
- return hasHeredoc(command) && hasWriteRedirections(command);
446
- }
447
-
448
- /**
449
- * Blacklist of dangerous commands that should not be safely prefix-matched
450
- * and should not have persistent permissions.
451
- */
452
- export const DANGEROUS_COMMANDS = [
453
- "rm",
454
- "mv",
455
- "chmod",
456
- "chown",
457
- "sh",
458
- "bash",
459
- "zsh",
460
- "fish",
461
- "pwsh",
462
- "cmd.exe",
463
- "powershell.exe",
464
- "sudo",
465
- "dd",
466
- "apt",
467
- "apt-get",
468
- "yum",
469
- "dnf",
470
- "ssh",
471
- "scp",
472
- "sftp",
473
- "ftp",
474
- "telnet",
475
- "nc",
476
- "netcat",
477
- ];
478
-
479
- /**
480
- * Read-only command set: commands that only read/transform data and write to stdout.
481
- * When a command in this set is used without write redirections, command substitution,
482
- * or dangerous flags (e.g. sed -i), it is auto-allowed without a confirmation dialog.
483
- * Aligned with Claude Code's SEMANTIC_READ_ONLY_COMMANDS, excluding interactive pagers
484
- * (less, more, man, info), command executors (xargs), and infinite-output generators (yes).
485
- * FR-019.2 through FR-019.7 in tool-permission-system.md.
486
- */
487
- export const READ_ONLY_COMMANDS = [
488
- "ls",
489
- "cat",
490
- "head",
491
- "tail",
492
- "wc",
493
- "sort",
494
- "uniq",
495
- "grep",
496
- "egrep",
497
- "fgrep",
498
- "rg",
499
- "find",
500
- "which",
501
- "whereis",
502
- "file",
503
- "stat",
504
- "du",
505
- "df",
506
- "free",
507
- "uptime",
508
- "uname",
509
- "hostname",
510
- "whoami",
511
- "id",
512
- "groups",
513
- "env",
514
- "printenv",
515
- "echo",
516
- "printf",
517
- "date",
518
- "true",
519
- "false",
520
- "pwd",
521
- "tree",
522
- "diff",
523
- "cmp",
524
- "md5sum",
525
- "sha256sum",
526
- "sha1sum",
527
- "xxd",
528
- "od",
529
- "hexdump",
530
- "strings",
531
- "readlink",
532
- "realpath",
533
- "basename",
534
- "dirname",
535
- "seq",
536
- "column",
537
- "jq",
538
- "yq",
539
- "cut",
540
- "paste",
541
- "tr",
542
- "awk",
543
- "sed",
544
- "test",
545
- "expr",
546
- "bc",
547
- "sleep",
548
- ];
549
-
550
- /**
551
- * Registry of commands and their expected subcommand depth for smart prefix extraction.
552
- * For example, 'git: 2' means 'git commit' is a valid prefix, but 'git' alone is not.
553
- * Multi-word keys can be used for more specific rules.
554
- */
555
- export interface ToolRule {
556
- depth: number;
557
- scopeFlags?: string[];
558
- }
559
-
560
- export const TOOL_RULES: Record<string, ToolRule> = {
561
- // Node/JS
562
- npm: { depth: 2, scopeFlags: ["--prefix", "-C", "--registry"] },
563
- "npm run": { depth: 3, scopeFlags: ["--prefix", "-C", "--registry"] },
564
- pnpm: { depth: 2, scopeFlags: ["-C", "--dir", "-F", "--filter"] },
565
- "pnpm run": { depth: 3, scopeFlags: ["-C", "--dir", "-F", "--filter"] },
566
- yarn: { depth: 2, scopeFlags: ["workspace", "--cwd"] },
567
- "yarn run": { depth: 3, scopeFlags: ["workspace", "--cwd"] },
568
- "yarn workspace": { depth: 4, scopeFlags: ["--cwd"] },
569
- bun: { depth: 2 },
570
- "bun run": { depth: 3 },
571
- deno: { depth: 2 },
572
- "deno run": { depth: 3 },
573
- "deno task": { depth: 3 },
574
-
575
- // Git
576
- git: {
577
- depth: 2,
578
- scopeFlags: ["-C", "-c", "--directory", "--work-tree", "--git-dir"],
579
- },
580
-
581
- // Python
582
- python: { depth: 2 },
583
- python3: { depth: 2 },
584
- "python -m": { depth: 2 },
585
- "python3 -m": { depth: 2 },
586
- "python -m pip install": { depth: 3 },
587
- "python3 -m pip install": { depth: 3 },
588
- pip: { depth: 2 },
589
- pip3: { depth: 2 },
590
- poetry: { depth: 2 },
591
- conda: { depth: 2 },
592
-
593
- // Java
594
- mvn: { depth: 2 },
595
- gradle: { depth: 2 },
596
- java: { depth: 1 },
597
- "java -jar": { depth: 1 },
598
-
599
- // Rust & Go
600
- cargo: { depth: 2 },
601
- go: { depth: 2 },
602
-
603
- // Containers & Infrastructure
604
- docker: { depth: 2 },
605
- "docker-compose": { depth: 2 },
606
- kubectl: { depth: 2 },
607
- terraform: { depth: 2 },
608
- gcloud: { depth: 2 },
609
- "gcloud compute": { depth: 4 },
610
- "gcloud container": { depth: 4 },
611
- aws: { depth: 2 },
612
- };
613
-
614
- /**
615
- * Registry of dangerous subcommands for specific tools.
616
- */
617
- export const DANGEROUS_SUBCOMMANDS: Record<string, string[]> = {
618
- docker: ["rm", "rmi", "system", "volume", "network", "image", "container"],
619
- git: ["reset", "clean"],
620
- npm: ["uninstall", "un", "remove", "rm"],
621
- pnpm: ["uninstall", "un", "remove", "rm"],
622
- yarn: ["remove"],
623
- deno: ["uninstall"],
624
- bun: ["remove", "rm"],
625
- };
626
-
627
- /**
628
- * Heuristic to determine if a flag takes an argument.
629
- * If nextArg doesn't start with '-' and isn't a known subcommand, assume it's a flag value.
630
- */
631
- function flagTakesArg(flag: string, nextArg: string | undefined): boolean {
632
- if (!nextArg) return false;
633
- if (nextArg.startsWith("-")) return false;
634
- // If it's a common subcommand, it's probably not a flag argument
635
- const commonSubcommands = [
636
- "install",
637
- "add",
638
- "remove",
639
- "run",
640
- "test",
641
- "build",
642
- "status",
643
- "diff",
644
- "commit",
645
- "push",
646
- "pull",
647
- "checkout",
648
- "log",
649
- "fetch",
650
- "merge",
651
- "rebase",
652
- ];
653
- if (commonSubcommands.includes(nextArg)) return false;
654
- return true;
655
- }
656
-
657
- /**
658
- * Detects if an argument is a file path or URL.
659
- */
660
- function shouldStopAtArg(arg: string): boolean {
661
- if (!arg) return false;
662
- // URLs
663
- if (/^(https?|ftp|ssh|git):\/\//.test(arg)) return true;
664
- // File paths (starts with /, ./, ../, or ~/)
665
- if (
666
- arg.startsWith("/") ||
667
- arg.startsWith("./") ||
668
- arg.startsWith("../") ||
669
- arg.startsWith("~/")
670
- )
671
- return true;
672
- // Common file extensions (but not scoped packages or common subcommands)
673
- if (
674
- /\.(ts|js|py|sh|md|txt|json|yml|yaml|html|css|go|rs|java|cpp|c|h|php|rb|pl|sql)$/.test(
675
- arg,
676
- ) &&
677
- !arg.includes("@") &&
678
- !arg.includes("/")
679
- )
680
- return true;
681
- return false;
682
- }
683
-
684
- /**
685
- * Checks if a find command is dangerous (e.g., contains -exec, -delete, etc.).
686
- */
687
- export function isDangerousFind(command: string): boolean {
688
- const stripped = stripRedirections(stripEnvVars(command));
689
- const tokens = stripped.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
690
- if (tokens.length === 0 || tokens[0] !== "find") return false;
691
-
692
- const dangerousFlags = [
693
- "-exec",
694
- "-execdir",
695
- "-ok",
696
- "-okdir",
697
- "-delete",
698
- "-fprint",
699
- "-fprint0",
700
- "-fprintf",
701
- ];
702
- return tokens.some((token) => {
703
- const unquoted = token.replace(/^(['"])(.*)\1$/, "$2");
704
- return dangerousFlags.includes(unquoted);
705
- });
706
- }
707
-
708
- /**
709
- * Detects command substitution $(...) or backticks `...` in a command string.
710
- * Commands with substitution are never auto-allowed because the substituted
711
- * command may be dangerous (e.g. cat $(rm x)). FR-019.6.
712
- */
713
- export function hasCommandSubstitution(command: string): boolean {
714
- // Remove quoted strings first so $() or backticks inside quotes don't trigger
715
- const stripped = command
716
- .replace(/"(?:[^"\\]|\\.)*"/g, '""')
717
- .replace(/'(?:[^'\\]|\\.)*'/g, "''");
718
- return /\$\([^)]*\)/.test(stripped) || /`[^`]*`/.test(stripped);
719
- }
720
-
721
- /**
722
- * Detects process substitution <(...) or >(...) in a command string.
723
- * These can execute side effects and are never auto-allowed. FR-019.6.
724
- */
725
- export function hasProcessSubstitution(command: string): boolean {
726
- const stripped = command
727
- .replace(/"(?:[^"\\]|\\.)*"/g, '""')
728
- .replace(/'(?:[^'\\]|\\.)*'/g, "''");
729
- return /[<>]\([^)]*\)/.test(stripped);
730
- }
731
-
732
- /**
733
- * Detects sed in-place edit flag (-i, with optional backup suffix like -i.bak).
734
- * sed -i modifies files in place and must NOT be auto-allowed. FR-019.5.
735
- */
736
- export function hasSedInPlace(command: string): boolean {
737
- const stripped = stripRedirections(stripEnvVars(command));
738
- const tokens = stripped.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
739
- if (tokens.length === 0 || tokens[0] !== "sed") return false;
740
- return tokens.some((token) => /^-i(\..*)?$/.test(token));
741
- }
742
-
743
- /**
744
- * Extracts a "smart prefix" from a bash command based on common developer tools.
745
- * Returns null if the command is blacklisted or cannot be safely prefix-matched.
746
- */
747
- export function getSmartPrefix(command: string): string | null {
748
- const parts = splitBashCommand(command);
749
- if (parts.length === 0) return null;
750
-
751
- // For now, we only support prefix matching for single commands or the first command in a chain
752
- // to keep it simple and safe.
753
- const firstCommand = parts[0];
754
-
755
- const stripped = stripRedirections(stripEnvVars(firstCommand));
756
- const tokens = stripped.split(/\s+/);
757
- if (tokens.length === 0) return null;
758
-
759
- const prefixParts: string[] = [];
760
- let i = 0;
761
-
762
- // Handle prefix tools like sudo
763
- const prefixTools = ["sudo", "time", "stdbuf", "timeout"];
764
- while (i < tokens.length && prefixTools.includes(tokens[i])) {
765
- prefixParts.push(tokens[i]);
766
- i++;
767
- }
768
-
769
- if (i >= tokens.length) return null;
770
-
771
- const exe = tokens[i];
772
- // Blacklist - Hard blacklist for dangerous commands
773
- if (DANGEROUS_COMMANDS.includes(exe)) return null;
774
-
775
- // Find the longest matching rule in TOOL_RULES
776
- let bestRuleKey = "";
777
- let rule: ToolRule | undefined;
778
-
779
- for (const [key, r] of Object.entries(TOOL_RULES)) {
780
- const keyTokens = key.split(/\s+/);
781
- let match = true;
782
- for (let j = 0; j < keyTokens.length; j++) {
783
- if (tokens[i + j] !== keyTokens[j]) {
784
- match = false;
785
- break;
786
- }
787
- }
788
- if (match && key.length > bestRuleKey.length) {
789
- bestRuleKey = key;
790
- rule = r;
791
- }
792
- }
793
-
794
- // If no rule found, we don't suggest a prefix
795
- if (!rule) return null;
796
-
797
- const depth = rule.depth;
798
- const scopeFlags = rule.scopeFlags || [];
799
- let currentDepth = 0;
800
-
801
- // Safety check: only allow safe subcommands for git
802
- const safeGitSubcommands = [
803
- "commit",
804
- "push",
805
- "pull",
806
- "checkout",
807
- "add",
808
- "status",
809
- "diff",
810
- "branch",
811
- "merge",
812
- "rebase",
813
- "log",
814
- "fetch",
815
- "remote",
816
- "stash",
817
- ];
818
-
819
- const destructiveGitFlags = [
820
- "-d",
821
- "-D",
822
- "--delete",
823
- "--hard",
824
- "--force",
825
- "-f",
826
- ];
827
-
828
- // Global safety check: scan ALL tokens for dangerous flags/subcommands
829
- for (let j = i; j < tokens.length; j++) {
830
- const token = tokens[j];
831
- if (token.startsWith("-")) {
832
- if (exe === "git" && destructiveGitFlags.includes(token)) return null;
833
- } else {
834
- if (DANGEROUS_SUBCOMMANDS[exe]?.includes(token)) return null;
835
- }
836
- }
837
-
838
- // Include all tokens from the best matching rule
839
- const ruleTokens = bestRuleKey.split(/\s+/);
840
- for (let j = 0; j < ruleTokens.length; j++) {
841
- const token = tokens[i];
842
- if (!token) break;
843
-
844
- if (token.startsWith("-")) {
845
- if (exe === "git" && destructiveGitFlags.includes(token)) return null;
846
- } else {
847
- if (DANGEROUS_SUBCOMMANDS[exe]?.includes(token)) return null;
848
- if (
849
- exe === "git" &&
850
- currentDepth > 0 &&
851
- !safeGitSubcommands.includes(token)
852
- ) {
853
- return null;
854
- }
855
- currentDepth++;
856
- }
857
-
858
- prefixParts.push(token);
859
- i++;
860
- }
861
-
862
- // Continue until we reach the required depth
863
- while (i < tokens.length && currentDepth < depth) {
864
- const token = tokens[i];
865
-
866
- if (token.startsWith("-")) {
867
- // Safety checks for flags
868
- if (exe === "git" && destructiveGitFlags.includes(token)) return null;
869
-
870
- prefixParts.push(token);
871
- if (scopeFlags.includes(token) || flagTakesArg(token, tokens[i + 1])) {
872
- if (i + 1 < tokens.length) {
873
- prefixParts.push(tokens[++i]);
874
- }
875
- }
876
- } else {
877
- // Safety checks for subcommands
878
- if (DANGEROUS_SUBCOMMANDS[exe]?.includes(token)) return null;
879
- if (
880
- exe === "git" &&
881
- currentDepth > 0 &&
882
- !safeGitSubcommands.includes(token)
883
- ) {
884
- return null;
885
- }
886
-
887
- // Stop at data/paths
888
- if (shouldStopAtArg(token) && currentDepth > 0) break;
889
-
890
- prefixParts.push(token);
891
- currentDepth++;
892
- }
893
- i++;
894
- }
895
-
896
- if (currentDepth < depth) return null;
897
-
898
- return prefixParts.join(" ");
899
- }