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