wave-agent-sdk 1.0.5 → 1.0.7

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 (202) hide show
  1. package/dist/agent.d.ts +34 -1
  2. package/dist/agent.js +49 -4
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +1 -1
  5. package/dist/managers/aiManager.d.ts +36 -0
  6. package/dist/managers/aiManager.js +240 -11
  7. package/dist/managers/messageManager.d.ts +2 -4
  8. package/dist/managers/messageManager.js +14 -7
  9. package/dist/managers/slashCommandManager.js +28 -0
  10. package/dist/managers/subagentManager.d.ts +0 -2
  11. package/dist/services/aiService.d.ts +2 -3
  12. package/dist/services/aiService.js +4 -18
  13. package/dist/services/configurationService.d.ts +7 -0
  14. package/dist/services/configurationService.js +10 -0
  15. package/dist/telemetry/instrumentation.d.ts +1 -1
  16. package/dist/telemetry/instrumentation.js +57 -28
  17. package/dist/tools/bashTool.js +16 -6
  18. package/dist/tools/grepTool.js +11 -3
  19. package/dist/types/core.d.ts +1 -34
  20. package/dist/types/index.d.ts +4 -0
  21. package/dist/types/messaging.d.ts +2 -0
  22. package/dist/utils/cacheControlUtils.d.ts +5 -27
  23. package/dist/utils/cacheControlUtils.js +12 -73
  24. package/dist/utils/containerSetup.js +0 -3
  25. package/dist/utils/messageOperations.d.ts +2 -1
  26. package/dist/utils/messageOperations.js +2 -1
  27. package/dist/utils/notificationXml.js +5 -0
  28. package/dist/utils/ripgrep.d.ts +4 -4
  29. package/dist/utils/ripgrep.js +4 -27
  30. package/package.json +20 -8
  31. package/bin/rg +0 -79
  32. package/dist/managers/forkedAgentManager.d.ts +0 -50
  33. package/dist/managers/forkedAgentManager.js +0 -130
  34. package/scripts/install_ripgrep.js +0 -111
  35. package/scripts/postinstall.js +0 -38
  36. package/src/agent.ts +0 -1271
  37. package/src/constants/subagents.ts +0 -4
  38. package/src/constants/toolLimits.ts +0 -15
  39. package/src/constants/tools.ts +0 -24
  40. package/src/core/plugin.ts +0 -237
  41. package/src/core/session.ts +0 -9
  42. package/src/index.ts +0 -38
  43. package/src/managers/MemoryRuleManager.ts +0 -198
  44. package/src/managers/aiManager.ts +0 -2429
  45. package/src/managers/backgroundTaskManager.ts +0 -480
  46. package/src/managers/bangManager.ts +0 -119
  47. package/src/managers/cronManager.ts +0 -401
  48. package/src/managers/foregroundTaskManager.ts +0 -30
  49. package/src/managers/forkedAgentManager.ts +0 -216
  50. package/src/managers/hookManager.ts +0 -1089
  51. package/src/managers/liveConfigManager.ts +0 -445
  52. package/src/managers/lspManager.ts +0 -470
  53. package/src/managers/mcpManager.ts +0 -1010
  54. package/src/managers/messageManager.ts +0 -1184
  55. package/src/managers/messageQueue.ts +0 -157
  56. package/src/managers/permissionManager.ts +0 -1140
  57. package/src/managers/planManager.ts +0 -112
  58. package/src/managers/pluginManager.ts +0 -330
  59. package/src/managers/pluginScopeManager.ts +0 -124
  60. package/src/managers/reversionManager.ts +0 -178
  61. package/src/managers/skillManager.ts +0 -578
  62. package/src/managers/slashCommandManager.ts +0 -564
  63. package/src/managers/subagentManager.ts +0 -915
  64. package/src/managers/toolManager.ts +0 -489
  65. package/src/managers/workflowManager.ts +0 -493
  66. package/src/prompts/autoMemory.ts +0 -33
  67. package/src/prompts/autoMemoryExtraction.ts +0 -146
  68. package/src/prompts/index.ts +0 -506
  69. package/src/prompts/planModeReminders.ts +0 -126
  70. package/src/services/GitService.ts +0 -131
  71. package/src/services/MarketplaceService.ts +0 -1052
  72. package/src/services/MemoryRuleService.ts +0 -71
  73. package/src/services/aiService.ts +0 -919
  74. package/src/services/authService.ts +0 -565
  75. package/src/services/autoMemoryService.ts +0 -280
  76. package/src/services/configurationService.ts +0 -1520
  77. package/src/services/fileWatcher.ts +0 -319
  78. package/src/services/hook.ts +0 -348
  79. package/src/services/initializationService.ts +0 -357
  80. package/src/services/interactionService.ts +0 -229
  81. package/src/services/jsonlHandler.ts +0 -295
  82. package/src/services/memory.ts +0 -234
  83. package/src/services/pluginLoader.ts +0 -252
  84. package/src/services/remoteSettingsService.ts +0 -366
  85. package/src/services/reversionService.ts +0 -122
  86. package/src/services/session.ts +0 -917
  87. package/src/services/taskManager.ts +0 -321
  88. package/src/telemetry/events.ts +0 -65
  89. package/src/telemetry/instrumentation.ts +0 -499
  90. package/src/telemetry/sessionTracing.ts +0 -348
  91. package/src/tools/agentTool.ts +0 -322
  92. package/src/tools/askUserQuestion.ts +0 -160
  93. package/src/tools/bashTool.ts +0 -621
  94. package/src/tools/buildTool.ts +0 -61
  95. package/src/tools/cronCreateTool.ts +0 -161
  96. package/src/tools/cronDeleteTool.ts +0 -51
  97. package/src/tools/cronListTool.ts +0 -47
  98. package/src/tools/editTool.ts +0 -313
  99. package/src/tools/enterPlanMode.ts +0 -124
  100. package/src/tools/enterWorktreeTool.ts +0 -190
  101. package/src/tools/exitPlanMode.ts +0 -139
  102. package/src/tools/exitWorktreeTool.ts +0 -236
  103. package/src/tools/globTool.ts +0 -167
  104. package/src/tools/grepTool.ts +0 -399
  105. package/src/tools/lspTool.ts +0 -883
  106. package/src/tools/readTool.ts +0 -426
  107. package/src/tools/skillTool.ts +0 -248
  108. package/src/tools/taskManagementTools.ts +0 -574
  109. package/src/tools/taskStopTool.ts +0 -78
  110. package/src/tools/types.ts +0 -137
  111. package/src/tools/webFetchTool.ts +0 -373
  112. package/src/tools/workflowTool.ts +0 -205
  113. package/src/tools/writeTool.ts +0 -267
  114. package/src/types/agent.ts +0 -128
  115. package/src/types/auth.ts +0 -19
  116. package/src/types/commands.ts +0 -30
  117. package/src/types/config.ts +0 -43
  118. package/src/types/configuration.ts +0 -168
  119. package/src/types/core.ts +0 -105
  120. package/src/types/cron.ts +0 -13
  121. package/src/types/environment.ts +0 -106
  122. package/src/types/fileSearch.ts +0 -4
  123. package/src/types/history.ts +0 -7
  124. package/src/types/hooks.ts +0 -256
  125. package/src/types/index.ts +0 -41
  126. package/src/types/lsp.ts +0 -98
  127. package/src/types/marketplace.ts +0 -71
  128. package/src/types/mcp.ts +0 -43
  129. package/src/types/memoryRule.ts +0 -31
  130. package/src/types/messaging.ts +0 -114
  131. package/src/types/permissions.ts +0 -80
  132. package/src/types/plugins.ts +0 -39
  133. package/src/types/processes.ts +0 -99
  134. package/src/types/reversion.ts +0 -29
  135. package/src/types/session.ts +0 -12
  136. package/src/types/skills.ts +0 -108
  137. package/src/types/tasks.ts +0 -13
  138. package/src/types/telemetry.ts +0 -98
  139. package/src/types/tools.ts +0 -44
  140. package/src/types/workflow.ts +0 -6
  141. package/src/utils/abortUtils.ts +0 -118
  142. package/src/utils/atomicWrite.ts +0 -61
  143. package/src/utils/bashParser.ts +0 -899
  144. package/src/utils/cacheControlUtils.ts +0 -426
  145. package/src/utils/commandArgumentParser.ts +0 -161
  146. package/src/utils/commandPathResolver.ts +0 -88
  147. package/src/utils/configPaths.ts +0 -220
  148. package/src/utils/configValidator.ts +0 -100
  149. package/src/utils/constants.ts +0 -39
  150. package/src/utils/container.ts +0 -92
  151. package/src/utils/containerSetup.ts +0 -371
  152. package/src/utils/convertMessagesForAPI.ts +0 -326
  153. package/src/utils/cronTasks.ts +0 -128
  154. package/src/utils/cronTasksLock.ts +0 -182
  155. package/src/utils/cronToHuman.ts +0 -99
  156. package/src/utils/customCommands.ts +0 -95
  157. package/src/utils/editUtils.ts +0 -20
  158. package/src/utils/fileFormat.ts +0 -40
  159. package/src/utils/fileSearch.ts +0 -151
  160. package/src/utils/fileUtils.ts +0 -223
  161. package/src/utils/gitUtils.ts +0 -315
  162. package/src/utils/globalLogger.ts +0 -128
  163. package/src/utils/groupMessagesByApiRound.ts +0 -121
  164. package/src/utils/hookMatcher.ts +0 -176
  165. package/src/utils/markdownParser.ts +0 -317
  166. package/src/utils/mcpUtils.ts +0 -138
  167. package/src/utils/messageOperations.ts +0 -659
  168. package/src/utils/modelCapabilities.ts +0 -30
  169. package/src/utils/nameGenerator.ts +0 -99
  170. package/src/utils/notificationXml.ts +0 -57
  171. package/src/utils/openaiClient.ts +0 -251
  172. package/src/utils/parseCronExpression.ts +0 -78
  173. package/src/utils/path.ts +0 -72
  174. package/src/utils/pathEncoder.ts +0 -400
  175. package/src/utils/pathSafety.ts +0 -40
  176. package/src/utils/promptHistory.ts +0 -168
  177. package/src/utils/ripgrep.ts +0 -35
  178. package/src/utils/shellResolver.ts +0 -190
  179. package/src/utils/skillParser.ts +0 -246
  180. package/src/utils/stringUtils.ts +0 -156
  181. package/src/utils/subagentParser.ts +0 -279
  182. package/src/utils/taskReminder.ts +0 -96
  183. package/src/utils/tokenCalculation.ts +0 -43
  184. package/src/utils/tokenEstimate.ts +0 -34
  185. package/src/utils/toolResultStorage.ts +0 -117
  186. package/src/utils/worktreeSession.ts +0 -26
  187. package/src/utils/worktreeUtils.ts +0 -786
  188. package/src/workflow/budgetTracker.ts +0 -34
  189. package/src/workflow/concurrencyLimiter.ts +0 -47
  190. package/src/workflow/journal.ts +0 -95
  191. package/src/workflow/progressReporter.ts +0 -141
  192. package/src/workflow/runState.ts +0 -65
  193. package/src/workflow/scriptRuntime.ts +0 -274
  194. package/src/workflow/structuredOutput.ts +0 -123
  195. package/src/workflow/types.ts +0 -101
  196. package/src/workflow/workflowApis.ts +0 -410
  197. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  198. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  199. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  200. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  201. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  202. 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
- }