onbuzz 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +425 -0
  3. package/bin/cli.js +556 -0
  4. package/bin/loxia-terminal-v2.js +162 -0
  5. package/bin/loxia-terminal.js +90 -0
  6. package/bin/start-with-terminal.js +200 -0
  7. package/node_modules/@isaacs/balanced-match/LICENSE.md +23 -0
  8. package/node_modules/@isaacs/balanced-match/README.md +60 -0
  9. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts +9 -0
  10. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts.map +1 -0
  11. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js +59 -0
  12. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js.map +1 -0
  13. package/node_modules/@isaacs/balanced-match/dist/commonjs/package.json +3 -0
  14. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts +9 -0
  15. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts.map +1 -0
  16. package/node_modules/@isaacs/balanced-match/dist/esm/index.js +54 -0
  17. package/node_modules/@isaacs/balanced-match/dist/esm/index.js.map +1 -0
  18. package/node_modules/@isaacs/balanced-match/dist/esm/package.json +3 -0
  19. package/node_modules/@isaacs/balanced-match/package.json +79 -0
  20. package/node_modules/@isaacs/brace-expansion/LICENSE +23 -0
  21. package/node_modules/@isaacs/brace-expansion/README.md +97 -0
  22. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts +6 -0
  23. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts.map +1 -0
  24. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js +199 -0
  25. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +1 -0
  26. package/node_modules/@isaacs/brace-expansion/dist/commonjs/package.json +3 -0
  27. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts +6 -0
  28. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts.map +1 -0
  29. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js +195 -0
  30. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +1 -0
  31. package/node_modules/@isaacs/brace-expansion/dist/esm/package.json +3 -0
  32. package/node_modules/@isaacs/brace-expansion/package.json +60 -0
  33. package/node_modules/glob/LICENSE.md +63 -0
  34. package/node_modules/glob/README.md +1177 -0
  35. package/node_modules/glob/dist/commonjs/glob.d.ts +388 -0
  36. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -0
  37. package/node_modules/glob/dist/commonjs/glob.js +247 -0
  38. package/node_modules/glob/dist/commonjs/glob.js.map +1 -0
  39. package/node_modules/glob/dist/commonjs/has-magic.d.ts +14 -0
  40. package/node_modules/glob/dist/commonjs/has-magic.d.ts.map +1 -0
  41. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  42. package/node_modules/glob/dist/commonjs/has-magic.js.map +1 -0
  43. package/node_modules/glob/dist/commonjs/ignore.d.ts +24 -0
  44. package/node_modules/glob/dist/commonjs/ignore.d.ts.map +1 -0
  45. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  46. package/node_modules/glob/dist/commonjs/ignore.js.map +1 -0
  47. package/node_modules/glob/dist/commonjs/index.d.ts +97 -0
  48. package/node_modules/glob/dist/commonjs/index.d.ts.map +1 -0
  49. package/node_modules/glob/dist/commonjs/index.js +68 -0
  50. package/node_modules/glob/dist/commonjs/index.js.map +1 -0
  51. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  52. package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
  53. package/node_modules/glob/dist/commonjs/package.json +3 -0
  54. package/node_modules/glob/dist/commonjs/pattern.d.ts +76 -0
  55. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -0
  56. package/node_modules/glob/dist/commonjs/pattern.js +219 -0
  57. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -0
  58. package/node_modules/glob/dist/commonjs/processor.d.ts +59 -0
  59. package/node_modules/glob/dist/commonjs/processor.d.ts.map +1 -0
  60. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  61. package/node_modules/glob/dist/commonjs/processor.js.map +1 -0
  62. package/node_modules/glob/dist/commonjs/walker.d.ts +97 -0
  63. package/node_modules/glob/dist/commonjs/walker.d.ts.map +1 -0
  64. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  65. package/node_modules/glob/dist/commonjs/walker.js.map +1 -0
  66. package/node_modules/glob/dist/esm/glob.d.ts +388 -0
  67. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -0
  68. package/node_modules/glob/dist/esm/glob.js +243 -0
  69. package/node_modules/glob/dist/esm/glob.js.map +1 -0
  70. package/node_modules/glob/dist/esm/has-magic.d.ts +14 -0
  71. package/node_modules/glob/dist/esm/has-magic.d.ts.map +1 -0
  72. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  73. package/node_modules/glob/dist/esm/has-magic.js.map +1 -0
  74. package/node_modules/glob/dist/esm/ignore.d.ts +24 -0
  75. package/node_modules/glob/dist/esm/ignore.d.ts.map +1 -0
  76. package/node_modules/glob/dist/esm/ignore.js +115 -0
  77. package/node_modules/glob/dist/esm/ignore.js.map +1 -0
  78. package/node_modules/glob/dist/esm/index.d.ts +97 -0
  79. package/node_modules/glob/dist/esm/index.d.ts.map +1 -0
  80. package/node_modules/glob/dist/esm/index.js +55 -0
  81. package/node_modules/glob/dist/esm/index.js.map +1 -0
  82. package/node_modules/glob/dist/esm/index.min.js +4 -0
  83. package/node_modules/glob/dist/esm/index.min.js.map +7 -0
  84. package/node_modules/glob/dist/esm/package.json +3 -0
  85. package/node_modules/glob/dist/esm/pattern.d.ts +76 -0
  86. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -0
  87. package/node_modules/glob/dist/esm/pattern.js +215 -0
  88. package/node_modules/glob/dist/esm/pattern.js.map +1 -0
  89. package/node_modules/glob/dist/esm/processor.d.ts +59 -0
  90. package/node_modules/glob/dist/esm/processor.d.ts.map +1 -0
  91. package/node_modules/glob/dist/esm/processor.js +294 -0
  92. package/node_modules/glob/dist/esm/processor.js.map +1 -0
  93. package/node_modules/glob/dist/esm/walker.d.ts +97 -0
  94. package/node_modules/glob/dist/esm/walker.d.ts.map +1 -0
  95. package/node_modules/glob/dist/esm/walker.js +381 -0
  96. package/node_modules/glob/dist/esm/walker.js.map +1 -0
  97. package/node_modules/glob/node_modules/minimatch/LICENSE.md +55 -0
  98. package/node_modules/glob/node_modules/minimatch/README.md +453 -0
  99. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +2 -0
  100. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -0
  101. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  102. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -0
  103. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +20 -0
  104. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -0
  105. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +591 -0
  106. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -0
  107. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts +8 -0
  108. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -0
  109. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +152 -0
  110. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -0
  111. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +15 -0
  112. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -0
  113. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  114. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -0
  115. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +94 -0
  116. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -0
  117. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +1029 -0
  118. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -0
  119. package/node_modules/glob/node_modules/minimatch/dist/commonjs/package.json +3 -0
  120. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +22 -0
  121. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -0
  122. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  123. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -0
  124. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +2 -0
  125. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -0
  126. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  127. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
  128. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +20 -0
  129. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -0
  130. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +587 -0
  131. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -0
  132. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts +8 -0
  133. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -0
  134. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +148 -0
  135. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
  136. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +15 -0
  137. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -0
  138. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +26 -0
  139. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -0
  140. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +94 -0
  141. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -0
  142. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +1016 -0
  143. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -0
  144. package/node_modules/glob/node_modules/minimatch/dist/esm/package.json +3 -0
  145. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +22 -0
  146. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -0
  147. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +34 -0
  148. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
  149. package/node_modules/glob/node_modules/minimatch/package.json +67 -0
  150. package/node_modules/glob/package.json +101 -0
  151. package/node_modules/minipass/LICENSE +15 -0
  152. package/node_modules/minipass/README.md +825 -0
  153. package/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
  154. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -0
  155. package/node_modules/minipass/dist/commonjs/index.js +1028 -0
  156. package/node_modules/minipass/dist/commonjs/index.js.map +1 -0
  157. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  158. package/node_modules/minipass/dist/esm/index.d.ts +549 -0
  159. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -0
  160. package/node_modules/minipass/dist/esm/index.js +1018 -0
  161. package/node_modules/minipass/dist/esm/index.js.map +1 -0
  162. package/node_modules/minipass/dist/esm/package.json +3 -0
  163. package/node_modules/minipass/package.json +82 -0
  164. package/node_modules/package-json-from-dist/LICENSE.md +63 -0
  165. package/node_modules/package-json-from-dist/README.md +110 -0
  166. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +89 -0
  167. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +1 -0
  168. package/node_modules/package-json-from-dist/dist/commonjs/index.js +134 -0
  169. package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +1 -0
  170. package/node_modules/package-json-from-dist/dist/commonjs/package.json +3 -0
  171. package/node_modules/package-json-from-dist/dist/esm/index.d.ts +89 -0
  172. package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +1 -0
  173. package/node_modules/package-json-from-dist/dist/esm/index.js +129 -0
  174. package/node_modules/package-json-from-dist/dist/esm/index.js.map +1 -0
  175. package/node_modules/package-json-from-dist/dist/esm/package.json +3 -0
  176. package/node_modules/package-json-from-dist/package.json +68 -0
  177. package/node_modules/path-scurry/LICENSE.md +55 -0
  178. package/node_modules/path-scurry/README.md +636 -0
  179. package/node_modules/path-scurry/dist/commonjs/index.d.ts +1115 -0
  180. package/node_modules/path-scurry/dist/commonjs/index.d.ts.map +1 -0
  181. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  182. package/node_modules/path-scurry/dist/commonjs/index.js.map +1 -0
  183. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  184. package/node_modules/path-scurry/dist/esm/index.d.ts +1115 -0
  185. package/node_modules/path-scurry/dist/esm/index.d.ts.map +1 -0
  186. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  187. package/node_modules/path-scurry/dist/esm/index.js.map +1 -0
  188. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  190. package/node_modules/path-scurry/node_modules/lru-cache/README.md +383 -0
  191. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +1323 -0
  192. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -0
  193. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1589 -0
  194. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -0
  195. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  196. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +7 -0
  197. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  198. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +1323 -0
  199. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -0
  200. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1585 -0
  201. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -0
  202. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  203. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +7 -0
  204. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  205. package/node_modules/path-scurry/node_modules/lru-cache/package.json +101 -0
  206. package/node_modules/path-scurry/package.json +88 -0
  207. package/node_modules/rimraf/LICENSE.md +55 -0
  208. package/node_modules/rimraf/README.md +226 -0
  209. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts +3 -0
  210. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts.map +1 -0
  211. package/node_modules/rimraf/dist/commonjs/default-tmp.js +58 -0
  212. package/node_modules/rimraf/dist/commonjs/default-tmp.js.map +1 -0
  213. package/node_modules/rimraf/dist/commonjs/error.d.ts +6 -0
  214. package/node_modules/rimraf/dist/commonjs/error.d.ts.map +1 -0
  215. package/node_modules/rimraf/dist/commonjs/error.js +10 -0
  216. package/node_modules/rimraf/dist/commonjs/error.js.map +1 -0
  217. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts +3 -0
  218. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts.map +1 -0
  219. package/node_modules/rimraf/dist/commonjs/fix-eperm.js +38 -0
  220. package/node_modules/rimraf/dist/commonjs/fix-eperm.js.map +1 -0
  221. package/node_modules/rimraf/dist/commonjs/fs.d.ts +15 -0
  222. package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -0
  223. package/node_modules/rimraf/dist/commonjs/fs.js +33 -0
  224. package/node_modules/rimraf/dist/commonjs/fs.js.map +1 -0
  225. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts +3 -0
  226. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts.map +1 -0
  227. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js +24 -0
  228. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js.map +1 -0
  229. package/node_modules/rimraf/dist/commonjs/index.d.ts +50 -0
  230. package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -0
  231. package/node_modules/rimraf/dist/commonjs/index.js +78 -0
  232. package/node_modules/rimraf/dist/commonjs/index.js.map +1 -0
  233. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts +34 -0
  234. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -0
  235. package/node_modules/rimraf/dist/commonjs/opt-arg.js +53 -0
  236. package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -0
  237. package/node_modules/rimraf/dist/commonjs/package.json +3 -0
  238. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts +4 -0
  239. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -0
  240. package/node_modules/rimraf/dist/commonjs/path-arg.js +48 -0
  241. package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -0
  242. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +3 -0
  243. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -0
  244. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js +19 -0
  245. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js.map +1 -0
  246. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts +8 -0
  247. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts.map +1 -0
  248. package/node_modules/rimraf/dist/commonjs/retry-busy.js +65 -0
  249. package/node_modules/rimraf/dist/commonjs/retry-busy.js.map +1 -0
  250. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts +3 -0
  251. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts.map +1 -0
  252. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js +8 -0
  253. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js.map +1 -0
  254. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts +4 -0
  255. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -0
  256. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js +138 -0
  257. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -0
  258. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts +4 -0
  259. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts.map +1 -0
  260. package/node_modules/rimraf/dist/commonjs/rimraf-native.js +24 -0
  261. package/node_modules/rimraf/dist/commonjs/rimraf-native.js.map +1 -0
  262. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts +4 -0
  263. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -0
  264. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +103 -0
  265. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -0
  266. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts +4 -0
  267. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -0
  268. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js +159 -0
  269. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -0
  270. package/node_modules/rimraf/dist/commonjs/use-native.d.ts +4 -0
  271. package/node_modules/rimraf/dist/commonjs/use-native.d.ts.map +1 -0
  272. package/node_modules/rimraf/dist/commonjs/use-native.js +18 -0
  273. package/node_modules/rimraf/dist/commonjs/use-native.js.map +1 -0
  274. package/node_modules/rimraf/dist/esm/bin.d.mts +3 -0
  275. package/node_modules/rimraf/dist/esm/bin.d.mts.map +1 -0
  276. package/node_modules/rimraf/dist/esm/bin.mjs +250 -0
  277. package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -0
  278. package/node_modules/rimraf/dist/esm/default-tmp.d.ts +3 -0
  279. package/node_modules/rimraf/dist/esm/default-tmp.d.ts.map +1 -0
  280. package/node_modules/rimraf/dist/esm/default-tmp.js +55 -0
  281. package/node_modules/rimraf/dist/esm/default-tmp.js.map +1 -0
  282. package/node_modules/rimraf/dist/esm/error.d.ts +6 -0
  283. package/node_modules/rimraf/dist/esm/error.d.ts.map +1 -0
  284. package/node_modules/rimraf/dist/esm/error.js +5 -0
  285. package/node_modules/rimraf/dist/esm/error.js.map +1 -0
  286. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts +3 -0
  287. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts.map +1 -0
  288. package/node_modules/rimraf/dist/esm/fix-eperm.js +33 -0
  289. package/node_modules/rimraf/dist/esm/fix-eperm.js.map +1 -0
  290. package/node_modules/rimraf/dist/esm/fs.d.ts +15 -0
  291. package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -0
  292. package/node_modules/rimraf/dist/esm/fs.js +18 -0
  293. package/node_modules/rimraf/dist/esm/fs.js.map +1 -0
  294. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts +3 -0
  295. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts.map +1 -0
  296. package/node_modules/rimraf/dist/esm/ignore-enoent.js +19 -0
  297. package/node_modules/rimraf/dist/esm/ignore-enoent.js.map +1 -0
  298. package/node_modules/rimraf/dist/esm/index.d.ts +50 -0
  299. package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -0
  300. package/node_modules/rimraf/dist/esm/index.js +70 -0
  301. package/node_modules/rimraf/dist/esm/index.js.map +1 -0
  302. package/node_modules/rimraf/dist/esm/opt-arg.d.ts +34 -0
  303. package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -0
  304. package/node_modules/rimraf/dist/esm/opt-arg.js +46 -0
  305. package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -0
  306. package/node_modules/rimraf/dist/esm/package.json +3 -0
  307. package/node_modules/rimraf/dist/esm/path-arg.d.ts +4 -0
  308. package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -0
  309. package/node_modules/rimraf/dist/esm/path-arg.js +46 -0
  310. package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -0
  311. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +3 -0
  312. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -0
  313. package/node_modules/rimraf/dist/esm/readdir-or-error.js +14 -0
  314. package/node_modules/rimraf/dist/esm/readdir-or-error.js.map +1 -0
  315. package/node_modules/rimraf/dist/esm/retry-busy.d.ts +8 -0
  316. package/node_modules/rimraf/dist/esm/retry-busy.d.ts.map +1 -0
  317. package/node_modules/rimraf/dist/esm/retry-busy.js +60 -0
  318. package/node_modules/rimraf/dist/esm/retry-busy.js.map +1 -0
  319. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts +3 -0
  320. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts.map +1 -0
  321. package/node_modules/rimraf/dist/esm/rimraf-manual.js +5 -0
  322. package/node_modules/rimraf/dist/esm/rimraf-manual.js.map +1 -0
  323. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts +4 -0
  324. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -0
  325. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +133 -0
  326. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -0
  327. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts +4 -0
  328. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts.map +1 -0
  329. package/node_modules/rimraf/dist/esm/rimraf-native.js +19 -0
  330. package/node_modules/rimraf/dist/esm/rimraf-native.js.map +1 -0
  331. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts +4 -0
  332. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -0
  333. package/node_modules/rimraf/dist/esm/rimraf-posix.js +98 -0
  334. package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -0
  335. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts +4 -0
  336. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -0
  337. package/node_modules/rimraf/dist/esm/rimraf-windows.js +154 -0
  338. package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -0
  339. package/node_modules/rimraf/dist/esm/use-native.d.ts +4 -0
  340. package/node_modules/rimraf/dist/esm/use-native.d.ts.map +1 -0
  341. package/node_modules/rimraf/dist/esm/use-native.js +15 -0
  342. package/node_modules/rimraf/dist/esm/use-native.js.map +1 -0
  343. package/node_modules/rimraf/package.json +92 -0
  344. package/package.json +152 -0
  345. package/scripts/install-scanners.js +258 -0
  346. package/scripts/watchdog.js +147 -0
  347. package/src/analyzers/CSSAnalyzer.js +297 -0
  348. package/src/analyzers/ConfigValidator.js +690 -0
  349. package/src/analyzers/ESLintAnalyzer.js +320 -0
  350. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  351. package/src/analyzers/PrettierFormatter.js +247 -0
  352. package/src/analyzers/PythonAnalyzer.js +283 -0
  353. package/src/analyzers/SecurityAnalyzer.js +729 -0
  354. package/src/analyzers/SparrowAnalyzer.js +341 -0
  355. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  356. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  357. package/src/analyzers/codeCloneDetector/detector.js +250 -0
  358. package/src/analyzers/codeCloneDetector/index.js +192 -0
  359. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  360. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  361. package/src/analyzers/codeCloneDetector/scanner.js +88 -0
  362. package/src/core/agentPool.js +1957 -0
  363. package/src/core/agentScheduler.js +3212 -0
  364. package/src/core/contextManager.js +709 -0
  365. package/src/core/flowExecutor.js +928 -0
  366. package/src/core/messageProcessor.js +808 -0
  367. package/src/core/orchestrator.js +584 -0
  368. package/src/core/stateManager.js +1500 -0
  369. package/src/index.js +972 -0
  370. package/src/interfaces/cli.js +553 -0
  371. package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +208 -0
  372. package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +236 -0
  373. package/src/interfaces/terminal/__tests__/smoke/agents.test.js +138 -0
  374. package/src/interfaces/terminal/__tests__/smoke/components.test.js +137 -0
  375. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +350 -0
  376. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -0
  377. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +332 -0
  378. package/src/interfaces/terminal/__tests__/smoke/messages.test.js +256 -0
  379. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +388 -0
  380. package/src/interfaces/terminal/api/apiClient.js +299 -0
  381. package/src/interfaces/terminal/api/messageRouter.js +262 -0
  382. package/src/interfaces/terminal/api/session.js +266 -0
  383. package/src/interfaces/terminal/api/websocket.js +497 -0
  384. package/src/interfaces/terminal/components/AgentCreator.js +705 -0
  385. package/src/interfaces/terminal/components/AgentEditor.js +678 -0
  386. package/src/interfaces/terminal/components/AgentSwitcher.js +330 -0
  387. package/src/interfaces/terminal/components/ErrorBoundary.js +92 -0
  388. package/src/interfaces/terminal/components/ErrorPanel.js +264 -0
  389. package/src/interfaces/terminal/components/Header.js +28 -0
  390. package/src/interfaces/terminal/components/HelpPanel.js +231 -0
  391. package/src/interfaces/terminal/components/InputBox.js +118 -0
  392. package/src/interfaces/terminal/components/Layout.js +603 -0
  393. package/src/interfaces/terminal/components/LoadingSpinner.js +71 -0
  394. package/src/interfaces/terminal/components/MessageList.js +281 -0
  395. package/src/interfaces/terminal/components/MultilineTextInput.js +251 -0
  396. package/src/interfaces/terminal/components/SearchPanel.js +265 -0
  397. package/src/interfaces/terminal/components/SettingsPanel.js +415 -0
  398. package/src/interfaces/terminal/components/StatusBar.js +65 -0
  399. package/src/interfaces/terminal/components/TextInput.js +127 -0
  400. package/src/interfaces/terminal/config/agentEditorConstants.js +227 -0
  401. package/src/interfaces/terminal/config/constants.js +393 -0
  402. package/src/interfaces/terminal/index.js +168 -0
  403. package/src/interfaces/terminal/state/useAgentControl.js +496 -0
  404. package/src/interfaces/terminal/state/useAgents.js +537 -0
  405. package/src/interfaces/terminal/state/useConnection.js +444 -0
  406. package/src/interfaces/terminal/state/useMessages.js +630 -0
  407. package/src/interfaces/terminal/state/useTools.js +554 -0
  408. package/src/interfaces/terminal/utils/debugLogger.js +44 -0
  409. package/src/interfaces/terminal/utils/settingsStorage.js +232 -0
  410. package/src/interfaces/terminal/utils/theme.js +85 -0
  411. package/src/interfaces/webServer.js +5457 -0
  412. package/src/modules/fileExplorer/controller.js +413 -0
  413. package/src/modules/fileExplorer/index.js +37 -0
  414. package/src/modules/fileExplorer/middleware.js +92 -0
  415. package/src/modules/fileExplorer/routes.js +158 -0
  416. package/src/modules/fileExplorer/types.js +44 -0
  417. package/src/services/agentActivityService.js +399 -0
  418. package/src/services/aiService.js +2618 -0
  419. package/src/services/apiKeyManager.js +334 -0
  420. package/src/services/benchmarkService.js +196 -0
  421. package/src/services/budgetService.js +565 -0
  422. package/src/services/contextInjectionService.js +268 -0
  423. package/src/services/conversationCompactionService.js +1103 -0
  424. package/src/services/credentialVault.js +685 -0
  425. package/src/services/errorHandler.js +810 -0
  426. package/src/services/fileAttachmentService.js +547 -0
  427. package/src/services/flowContextService.js +189 -0
  428. package/src/services/memoryService.js +521 -0
  429. package/src/services/modelRouterService.js +365 -0
  430. package/src/services/modelsService.js +323 -0
  431. package/src/services/ollamaService.js +452 -0
  432. package/src/services/portRegistry.js +336 -0
  433. package/src/services/portTracker.js +223 -0
  434. package/src/services/projectDetector.js +404 -0
  435. package/src/services/promptService.js +372 -0
  436. package/src/services/qualityInspector.js +796 -0
  437. package/src/services/scheduleService.js +725 -0
  438. package/src/services/serviceRegistry.js +386 -0
  439. package/src/services/skillsService.js +486 -0
  440. package/src/services/telegramService.js +920 -0
  441. package/src/services/tokenCountingService.js +316 -0
  442. package/src/services/visualEditorBridge.js +1033 -0
  443. package/src/services/visualEditorServer.js +1727 -0
  444. package/src/services/whatsappService.js +663 -0
  445. package/src/tools/__tests__/webTool.e2e.test.js +569 -0
  446. package/src/tools/__tests__/webTool.unit.test.js +195 -0
  447. package/src/tools/agentCommunicationTool.js +1343 -0
  448. package/src/tools/agentDelayTool.js +498 -0
  449. package/src/tools/asyncToolManager.js +604 -0
  450. package/src/tools/baseTool.js +887 -0
  451. package/src/tools/browserTool.js +897 -0
  452. package/src/tools/cloneDetectionTool.js +581 -0
  453. package/src/tools/codeMapTool.js +857 -0
  454. package/src/tools/dependencyResolverTool.js +1212 -0
  455. package/src/tools/docxTool.js +623 -0
  456. package/src/tools/excelTool.js +636 -0
  457. package/src/tools/fileContentReplaceTool.js +840 -0
  458. package/src/tools/fileTreeTool.js +833 -0
  459. package/src/tools/filesystemTool.js +1217 -0
  460. package/src/tools/helpTool.js +198 -0
  461. package/src/tools/imageTool.js +1034 -0
  462. package/src/tools/importAnalyzerTool.js +1056 -0
  463. package/src/tools/jobDoneTool.js +388 -0
  464. package/src/tools/memoryTool.js +554 -0
  465. package/src/tools/pdfTool.js +627 -0
  466. package/src/tools/seekTool.js +883 -0
  467. package/src/tools/skillsTool.js +276 -0
  468. package/src/tools/staticAnalysisTool.js +2146 -0
  469. package/src/tools/taskManagerTool.js +2836 -0
  470. package/src/tools/terminalTool.js +2486 -0
  471. package/src/tools/userPromptTool.js +474 -0
  472. package/src/tools/videoTool.js +1139 -0
  473. package/src/tools/visionTool.js +507 -0
  474. package/src/tools/visualEditorTool.js +1175 -0
  475. package/src/tools/webTool.js +3114 -0
  476. package/src/tools/whatsappTool.js +457 -0
  477. package/src/types/agent.js +519 -0
  478. package/src/types/contextReference.js +972 -0
  479. package/src/types/conversation.js +730 -0
  480. package/src/types/toolCommand.js +747 -0
  481. package/src/utilities/attachmentValidator.js +288 -0
  482. package/src/utilities/browserStealth.js +630 -0
  483. package/src/utilities/configManager.js +618 -0
  484. package/src/utilities/constants.js +870 -0
  485. package/src/utilities/directoryAccessManager.js +566 -0
  486. package/src/utilities/fileProcessor.js +307 -0
  487. package/src/utilities/humanBehavior.js +453 -0
  488. package/src/utilities/jsonRepair.js +242 -0
  489. package/src/utilities/logger.js +436 -0
  490. package/src/utilities/platformUtils.js +255 -0
  491. package/src/utilities/platformUtils.test.js +98 -0
  492. package/src/utilities/stealthConstants.js +377 -0
  493. package/src/utilities/structuredFileValidator.js +699 -0
  494. package/src/utilities/tagParser.js +878 -0
  495. package/src/utilities/toolConstants.js +415 -0
  496. package/src/utilities/userDataDir.js +300 -0
  497. package/web-ui/build/brands/autopilot/favicon.svg +1 -0
  498. package/web-ui/build/brands/autopilot/logo.webp +0 -0
  499. package/web-ui/build/brands/onbuzz/favicon.svg +1 -0
  500. package/web-ui/build/brands/onbuzz/logo-text.webp +0 -0
  501. package/web-ui/build/brands/onbuzz/logo.webp +0 -0
  502. package/web-ui/build/index.html +15 -0
  503. package/web-ui/build/logo.png +0 -0
  504. package/web-ui/build/logo2.png +0 -0
  505. package/web-ui/build/static/index-SmQFfvBs.js +746 -0
  506. package/web-ui/build/static/index-V2ySwjHp.css +1 -0
@@ -0,0 +1,2486 @@
1
+ /**
2
+ * TerminalTool - Execute terminal/command line operations
3
+ *
4
+ * Purpose:
5
+ * - Execute system commands safely
6
+ * - Handle directory navigation
7
+ * - Manage command output and errors
8
+ * - Support both synchronous and asynchronous execution
9
+ */
10
+
11
+ import { BaseTool } from './baseTool.js';
12
+ import TagParser from '../utilities/tagParser.js';
13
+ import DirectoryAccessManager from '../utilities/directoryAccessManager.js';
14
+ import { spawn, exec } from 'child_process';
15
+ import path from 'path';
16
+ import fs from 'fs/promises';
17
+
18
+ import {
19
+ TOOL_STATUS,
20
+ SYSTEM_DEFAULTS,
21
+ TERMINAL_CONFIG
22
+ } from '../utilities/constants.js';
23
+
24
+ /**
25
+ * PromptDetector - Detects interactive prompts in command output
26
+ * Phase 2: Prompt Detection System
27
+ */
28
+ class PromptDetector {
29
+ constructor() {
30
+ // Common prompt patterns (case-insensitive)
31
+ this.promptPatterns = [
32
+ // Yes/No prompts
33
+ { pattern: /\(y\/n\)/i, type: 'yes-no', description: 'Yes/No question' },
34
+ { pattern: /\(Y\/n\)/i, type: 'yes-no-default-yes', description: 'Yes/No (default Yes)' },
35
+ { pattern: /\(y\/N\)/i, type: 'yes-no-default-no', description: 'Yes/No (default No)' },
36
+ { pattern: /\[y\/n\]/i, type: 'yes-no', description: 'Yes/No question' },
37
+ { pattern: /\[Y\/n\]/i, type: 'yes-no-default-yes', description: 'Yes/No (default Yes)' },
38
+ { pattern: /\[y\/N\]/i, type: 'yes-no-default-no', description: 'Yes/No (default No)' },
39
+
40
+ // Continue prompts
41
+ { pattern: /continue\?/i, type: 'continue', description: 'Continue prompt' },
42
+ { pattern: /proceed\?/i, type: 'continue', description: 'Proceed prompt' },
43
+ { pattern: /press any key to continue/i, type: 'keypress', description: 'Press any key' },
44
+ { pattern: /press enter to continue/i, type: 'keypress', description: 'Press enter' },
45
+ { pattern: /hit enter to continue/i, type: 'keypress', description: 'Hit enter' },
46
+
47
+ // Password/Authentication prompts
48
+ { pattern: /password:/i, type: 'password', description: 'Password prompt' },
49
+ { pattern: /enter password/i, type: 'password', description: 'Password prompt' },
50
+ { pattern: /passphrase:/i, type: 'password', description: 'Passphrase prompt' },
51
+ { pattern: /username:/i, type: 'username', description: 'Username prompt' },
52
+ { pattern: /enter username/i, type: 'username', description: 'Username prompt' },
53
+
54
+ // Input prompts
55
+ { pattern: /enter\s+\w+:/i, type: 'input', description: 'Generic input prompt' },
56
+ { pattern: /please enter/i, type: 'input', description: 'Generic input prompt' },
57
+ { pattern: /input:/i, type: 'input', description: 'Generic input prompt' },
58
+
59
+ // Confirmation prompts
60
+ { pattern: /are you sure\?/i, type: 'confirmation', description: 'Confirmation prompt' },
61
+ { pattern: /do you want to/i, type: 'confirmation', description: 'Confirmation prompt' },
62
+ { pattern: /would you like to/i, type: 'confirmation', description: 'Confirmation prompt' },
63
+
64
+ // Selection prompts
65
+ { pattern: /select an option/i, type: 'selection', description: 'Selection prompt' },
66
+ { pattern: /choose/i, type: 'selection', description: 'Selection prompt' },
67
+ { pattern: /\d+\)\s+\w+/g, type: 'menu', description: 'Menu selection' } // Matches: 1) Option
68
+ ];
69
+ }
70
+
71
+ /**
72
+ * Analyze output for prompt patterns
73
+ * @param {string} output - Output text to analyze (stdout or stderr)
74
+ * @param {string} source - Source of output ('stdout' or 'stderr')
75
+ * @returns {Object|null} Prompt detection result or null
76
+ */
77
+ detectPrompt(output, source = 'stdout') {
78
+ if (!output || output.trim().length === 0) {
79
+ return null;
80
+ }
81
+
82
+ // Get the last few lines (prompts are usually at the end)
83
+ const lines = output.split('\n');
84
+ const lastLines = lines.slice(-5).join('\n'); // Check last 5 lines
85
+
86
+ // Check each pattern
87
+ for (const promptDef of this.promptPatterns) {
88
+ const match = lastLines.match(promptDef.pattern);
89
+ if (match) {
90
+ return {
91
+ detected: true,
92
+ type: promptDef.type,
93
+ description: promptDef.description,
94
+ matchedText: match[0],
95
+ matchIndex: match.index,
96
+ source: source,
97
+ fullContext: lastLines,
98
+ timestamp: Date.now()
99
+ };
100
+ }
101
+ }
102
+
103
+ // Check for generic prompt indicators
104
+ // Look for lines ending with : or ? without newline after
105
+ const lastLine = lines[lines.length - 1] || '';
106
+ if (lastLine.trim().length > 0) {
107
+ const endsWithColon = /:\s*$/.test(lastLine);
108
+ const endsWithQuestion = /\?\s*$/.test(lastLine);
109
+
110
+ if (endsWithColon || endsWithQuestion) {
111
+ // Might be a prompt - check if it's asking for input
112
+ const looksLikePrompt = /\b(enter|type|provide|specify|input)\b/i.test(lastLine);
113
+ if (looksLikePrompt) {
114
+ return {
115
+ detected: true,
116
+ type: 'generic',
117
+ description: 'Generic input prompt detected',
118
+ matchedText: lastLine.trim(),
119
+ source: source,
120
+ fullContext: lastLines,
121
+ timestamp: Date.now(),
122
+ confidence: 0.7 // Lower confidence for generic detection
123
+ };
124
+ }
125
+ }
126
+ }
127
+
128
+ return null;
129
+ }
130
+
131
+ /**
132
+ * Check if output indicates command is waiting (no prompt but no output)
133
+ * @param {number} lastOutputTime - Timestamp of last output
134
+ * @param {number} hangThresholdMs - Milliseconds to consider as hanging
135
+ * @returns {Object} Hang detection result
136
+ */
137
+ detectHang(lastOutputTime, hangThresholdMs = 30000) {
138
+ const now = Date.now();
139
+ const timeSinceLastOutput = now - lastOutputTime;
140
+
141
+ return {
142
+ isHanging: timeSinceLastOutput >= hangThresholdMs,
143
+ timeSinceLastOutput: timeSinceLastOutput,
144
+ threshold: hangThresholdMs,
145
+ likelyWaiting: timeSinceLastOutput >= hangThresholdMs / 2 // 50% threshold
146
+ };
147
+ }
148
+ }
149
+
150
+ class TerminalTool extends BaseTool {
151
+ constructor(config = {}, logger = null) {
152
+ super(config, logger);
153
+
154
+ // Tool metadata
155
+ this.requiresProject = false;
156
+ this.isAsync = false; // Most commands are quick, use sync execution
157
+ this.timeout = config.timeout || 120000; // 2 minutes default
158
+ this.maxConcurrentOperations = config.maxConcurrentOperations || 3;
159
+
160
+ // Current working directories per context
161
+ this.workingDirectories = new Map();
162
+
163
+ // Command history
164
+ this.commandHistory = [];
165
+
166
+ // Security settings
167
+ this.allowedCommands = config.allowedCommands || null; // null = all allowed
168
+ this.blockedCommands = config.blockedCommands || [
169
+ 'rm -rf /',
170
+ 'format',
171
+ 'del /f /q',
172
+ 'shutdown',
173
+ 'reboot',
174
+ 'halt'
175
+ ];
176
+
177
+ // Directory access manager
178
+ this.directoryAccessManager = new DirectoryAccessManager(config, logger);
179
+
180
+ // Prompt detector (Phase 2)
181
+ this.promptDetector = new PromptDetector();
182
+
183
+ // Phase 3 & 4: Background command tracking
184
+ this.commandTracker = new Map(); // commandId -> { agentId, pid, process, state, buffers, timestamps }
185
+ this.commandIdCounter = 0;
186
+
187
+ // Resource limits
188
+ this.MAX_BACKGROUND_COMMANDS_PER_AGENT = config.maxBackgroundCommandsPerAgent || 5;
189
+ this.MAX_BACKGROUND_COMMANDS_GLOBAL = config.maxBackgroundCommandsGlobal || 20;
190
+ this.MAX_COMMAND_AGE_MINUTES = config.maxCommandAgeMinutes || 60;
191
+
192
+ // Terminal detection
193
+ this.detectedTerminal = null;
194
+ this.platformType = null;
195
+ this.initializeTerminalDetection();
196
+ }
197
+
198
+ /**
199
+ * Get tool description for LLM consumption
200
+ * @returns {string} Tool description
201
+ */
202
+ getDescription() {
203
+ return `
204
+ Terminal Tool: Execute system commands and manage terminal operations safely.
205
+
206
+ IMPORTANT: For file and directory creation, prefer using the FileSystem tool.
207
+ Reserve the Terminal tool for command-line operations like npm, git, curl, etc.
208
+
209
+ USAGE:
210
+ \`\`\`json
211
+ {
212
+ "toolId": "terminal",
213
+ "actions": [
214
+ {"type": "run-command", "command": "npm install express"},
215
+ {"type": "change-directory", "directory": "project/backend"}
216
+ ]
217
+ }
218
+ \`\`\`
219
+
220
+ SUPPORTED ACTIONS:
221
+ - run-command: Execute a command (command)
222
+ - change-directory: Change working directory (directory)
223
+ - list-directory: List directory contents (directory)
224
+ - create-directory: Create directory (directory) - prefer FileSystem tool
225
+ - get-working-directory: Get current directory
226
+
227
+ PARAMETERS:
228
+ - command: The command to execute
229
+ - directory: Directory path for navigation/operations
230
+ - timeout: Optional timeout in milliseconds (max ${this.timeout}ms)
231
+ - async: Whether to run command asynchronously (true/false)
232
+
233
+ EXAMPLES:
234
+
235
+ Run npm install:
236
+ \`\`\`json
237
+ {"toolId": "terminal", "actions": [{"type": "run-command", "command": "npm install"}]}
238
+ \`\`\`
239
+
240
+ Git operations:
241
+ \`\`\`json
242
+ {
243
+ "toolId": "terminal",
244
+ "actions": [
245
+ {"type": "run-command", "command": "git status"},
246
+ {"type": "run-command", "command": "git add ."},
247
+ {"type": "run-command", "command": "git commit -m \\"Update files\\""}
248
+ ]
249
+ }
250
+ \`\`\`
251
+
252
+ Change directory and build:
253
+ \`\`\`json
254
+ {
255
+ "toolId": "terminal",
256
+ "actions": [
257
+ {"type": "change-directory", "directory": "../frontend"},
258
+ {"type": "run-command", "command": "npm run build"}
259
+ ]
260
+ }
261
+ \`\`\`
262
+
263
+ SECURITY:
264
+ - Dangerous commands are blocked
265
+ - Commands execute in isolated environment
266
+ - Output is captured and returned safely
267
+
268
+ BEST PRACTICES:
269
+ - Use FileSystem tool for file/directory operations
270
+ - Use Terminal for CLI utilities (npm, git, curl, etc.)
271
+ - Check command output to verify success
272
+ `;
273
+ }
274
+
275
+ /**
276
+ * Parse parameters from tool command content
277
+ * @param {string} content - Raw tool command content
278
+ * @returns {Object} Parsed parameters
279
+ */
280
+ parseParameters(content) {
281
+ try {
282
+ const params = {};
283
+
284
+ // Extract individual action parameters
285
+ const runCommandMatches = TagParser.extractContent(content, 'run-command');
286
+ const changeDirMatches = TagParser.extractContent(content, 'change-directory');
287
+ const listDirMatches = TagParser.extractContent(content, 'list-directory');
288
+ const createDirMatches = TagParser.extractContent(content, 'create-directory');
289
+ const getWdMatches = TagParser.extractContent(content, 'get-working-directory');
290
+ const timeoutMatches = TagParser.extractContent(content, 'timeout');
291
+ const asyncMatches = TagParser.extractContent(content, 'async');
292
+
293
+ // Build actions array
294
+ const actions = [];
295
+
296
+ if (runCommandMatches.length > 0) {
297
+ actions.push({
298
+ type: 'run-command',
299
+ command: runCommandMatches[0].trim()
300
+ });
301
+ }
302
+
303
+ if (changeDirMatches.length > 0) {
304
+ actions.push({
305
+ type: 'change-directory',
306
+ directory: changeDirMatches[0].trim()
307
+ });
308
+ }
309
+
310
+ if (listDirMatches.length > 0) {
311
+ actions.push({
312
+ type: 'list-directory',
313
+ directory: listDirMatches[0].trim() || '.'
314
+ });
315
+ }
316
+
317
+ if (createDirMatches.length > 0) {
318
+ actions.push({
319
+ type: 'create-directory',
320
+ directory: createDirMatches[0].trim()
321
+ });
322
+ }
323
+
324
+ if (getWdMatches.length > 0) {
325
+ actions.push({
326
+ type: 'get-working-directory'
327
+ });
328
+ }
329
+
330
+ params.actions = actions;
331
+
332
+ // Parse additional options
333
+ if (timeoutMatches.length > 0) {
334
+ params.timeout = parseInt(timeoutMatches[0], 10);
335
+ }
336
+
337
+ if (asyncMatches.length > 0) {
338
+ params.async = asyncMatches[0].toLowerCase() === 'true';
339
+ }
340
+
341
+ params.rawContent = content.trim();
342
+
343
+ return params;
344
+
345
+ } catch (error) {
346
+ throw new Error(`Failed to parse terminal parameters: ${error.message}`);
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Get required parameters
352
+ * @returns {Array<string>} Array of required parameter names
353
+ */
354
+ getRequiredParameters() {
355
+ return ['actions'];
356
+ }
357
+
358
+ /**
359
+ * Custom parameter validation
360
+ * @param {Object} params - Parameters to validate
361
+ * @returns {Object} Validation result
362
+ */
363
+ customValidateParameters(params) {
364
+ const errors = [];
365
+
366
+ if (!params.actions || !Array.isArray(params.actions) || params.actions.length === 0) {
367
+ errors.push('At least one action is required');
368
+ } else {
369
+ // Validate each action
370
+ for (const [index, action] of params.actions.entries()) {
371
+ if (!action.type) {
372
+ errors.push(`Action ${index + 1}: type is required`);
373
+ continue;
374
+ }
375
+
376
+ switch (action.type) {
377
+ case 'run-command':
378
+ if (!action.command || !action.command.trim()) {
379
+ errors.push(`Action ${index + 1}: command is required for run-command`);
380
+ } else if (this.isBlockedCommand(action.command)) {
381
+ errors.push(`Action ${index + 1}: command is blocked for security: ${action.command}`);
382
+ } else if (this.allowedCommands && !this.isAllowedCommand(action.command)) {
383
+ errors.push(`Action ${index + 1}: command is not in allowed list: ${action.command}`);
384
+ }
385
+ break;
386
+
387
+ case 'change-directory':
388
+ case 'list-directory':
389
+ case 'create-directory':
390
+ if (!action.directory || !action.directory.trim()) {
391
+ errors.push(`Action ${index + 1}: directory is required for ${action.type}`);
392
+ }
393
+ break;
394
+
395
+ case 'get-working-directory':
396
+ // No additional validation needed
397
+ break;
398
+
399
+ default:
400
+ errors.push(`Action ${index + 1}: unknown action type: ${action.type}`);
401
+ }
402
+ }
403
+ }
404
+
405
+ if (params.timeout && (params.timeout < 1000 || params.timeout > this.timeout)) {
406
+ errors.push(`Timeout must be between 1000 and ${this.timeout} milliseconds`);
407
+ }
408
+
409
+ return {
410
+ valid: errors.length === 0,
411
+ errors
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Execute tool with parsed parameters
417
+ * @param {Object} params - Parsed parameters
418
+ * @param {Object} context - Execution context
419
+ * @returns {Promise<Object>} Execution result
420
+ */
421
+ async execute(params, context) {
422
+ const { actions, timeout: customTimeout, async: forceAsync } = params;
423
+ const { agentId, projectDir, directoryAccess } = context;
424
+
425
+ // Get directory access configuration from agent or create default
426
+ const accessConfig = directoryAccess ||
427
+ this.directoryAccessManager.createDirectoryAccess({
428
+ workingDirectory: projectDir || process.cwd(),
429
+ writeEnabledDirectories: [projectDir || process.cwd()],
430
+ restrictToProject: true
431
+ });
432
+
433
+ // IMPORTANT: If the agent has directoryAccess configured, use its workingDirectory
434
+ // This ensures UI-configured project directories are respected
435
+
436
+ // Get or set current working directory for this agent
437
+ const contextKey = `${agentId}-${projectDir || 'default'}`;
438
+ let currentWorkingDir = this.workingDirectories.get(contextKey) ||
439
+ this.directoryAccessManager.getWorkingDirectory(accessConfig);
440
+
441
+ const results = [];
442
+
443
+ for (const action of actions) {
444
+ try {
445
+ let result;
446
+
447
+ switch (action.type) {
448
+ case 'run-command':
449
+ result = await this.executeCommand(action.command, currentWorkingDir, {
450
+ timeout: customTimeout || this.timeout,
451
+ async: forceAsync || false,
452
+ agentId,
453
+ context: {
454
+ toolsRegistry: context.toolsRegistry,
455
+ aiService: context.aiService,
456
+ apiKey: context.apiKey,
457
+ customApiKeys: context.customApiKeys,
458
+ platformProvided: context.platformProvided
459
+ }
460
+ });
461
+ break;
462
+
463
+ case 'change-directory':
464
+ result = await this.changeDirectory(action.directory, currentWorkingDir, accessConfig);
465
+ currentWorkingDir = result.newDirectory;
466
+ this.workingDirectories.set(contextKey, currentWorkingDir);
467
+ // Propagate to the agent's directoryAccess so all other tools
468
+ // (filesystem, seek, etc.) resolve paths from the same base
469
+ if (directoryAccess) {
470
+ directoryAccess.workingDirectory = currentWorkingDir;
471
+ }
472
+ break;
473
+
474
+ case 'list-directory':
475
+ result = await this.listDirectory(action.directory === '.' ? currentWorkingDir : action.directory);
476
+ break;
477
+
478
+ case 'create-directory':
479
+ result = await this.createDirectory(action.directory, currentWorkingDir);
480
+ break;
481
+
482
+ case 'get-working-directory':
483
+ result = {
484
+ success: true,
485
+ action: 'get-working-directory',
486
+ workingDirectory: currentWorkingDir,
487
+ message: `Current working directory: ${currentWorkingDir}`
488
+ };
489
+ break;
490
+
491
+ default:
492
+ throw new Error(`Unknown action type: ${action.type}`);
493
+ }
494
+
495
+ results.push(result);
496
+
497
+ // Add to command history
498
+ this.addToHistory(action, result, agentId);
499
+
500
+ } catch (error) {
501
+ const errorResult = {
502
+ success: false,
503
+ action: action.type,
504
+ error: error.message,
505
+ command: action.command || action.directory,
506
+ workingDirectory: currentWorkingDir
507
+ };
508
+
509
+ results.push(errorResult);
510
+ this.addToHistory(action, errorResult, agentId);
511
+ }
512
+ }
513
+
514
+ // Determine overall success based on individual action results
515
+ const overallSuccess = results.every(result => result.success);
516
+ const failedActions = results.filter(result => !result.success);
517
+
518
+ return {
519
+ success: overallSuccess,
520
+ actions: results,
521
+ workingDirectory: currentWorkingDir,
522
+ executedActions: actions.length,
523
+ failedActions: failedActions.length,
524
+ toolUsed: 'terminal',
525
+ message: overallSuccess
526
+ ? `All ${actions.length} actions completed successfully`
527
+ : `${failedActions.length} of ${actions.length} actions failed`
528
+ };
529
+ }
530
+
531
+ /**
532
+ * Execute a command in the specified directory
533
+ * @private
534
+ */
535
+ async executeCommand(command, workingDir, options = {}) {
536
+ const { timeout = this.timeout, async: isAsync = false, agentId, context } = options;
537
+
538
+ // Translate command for current terminal (now async with AI support)
539
+ const originalCommand = command;
540
+ let translatedCommand;
541
+
542
+ try {
543
+ translatedCommand = await this.translateCommand(command, {
544
+ agentId,
545
+ toolsRegistry: context?.toolsRegistry,
546
+ messageProcessor: context?.messageProcessor,
547
+ aiService: context?.aiService,
548
+ apiKey: context?.apiKey,
549
+ customApiKeys: context?.customApiKeys,
550
+ platformProvided: context?.platformProvided
551
+ });
552
+ } catch (error) {
553
+ this.logger?.warn('Command translation failed, using original command', {
554
+ originalCommand,
555
+ error: error.message
556
+ });
557
+ translatedCommand = command;
558
+ }
559
+
560
+ // Generate command ID for tracking
561
+ const commandId = `${agentId || 'unknown'}-cmd-${Date.now()}-${++this.commandIdCounter}`;
562
+
563
+ return new Promise((resolve, reject) => {
564
+ const startTime = Date.now();
565
+
566
+ this.logger?.info(`Executing command: ${translatedCommand}`, {
567
+ originalCommand,
568
+ translatedCommand,
569
+ terminal: this.detectedTerminal,
570
+ workingDirectory: workingDir,
571
+ timeout,
572
+ agentId,
573
+ commandId
574
+ });
575
+
576
+ // Track this command in commandTracker for UI visibility
577
+ const commandInfo = {
578
+ commandId,
579
+ agentId: agentId || 'unknown',
580
+ pid: null,
581
+ command: originalCommand,
582
+ translatedCommand,
583
+ workingDirectory: workingDir,
584
+ startTime: new Date().toISOString(),
585
+ state: TERMINAL_CONFIG.STATES.RUNNING,
586
+ exitCode: null,
587
+ stdoutBuffer: '',
588
+ stderrBuffer: '',
589
+ lastOutputTime: Date.now(),
590
+ promptDetected: null,
591
+ process: null,
592
+ isBackground: false
593
+ };
594
+ this.commandTracker.set(commandId, commandInfo);
595
+
596
+ const childProcess = exec(translatedCommand, {
597
+ cwd: workingDir,
598
+ timeout,
599
+ maxBuffer: 1024 * 1024, // 1MB buffer
600
+ env: { ...process.env }
601
+ }, (error, stdout, stderr) => {
602
+ const executionTime = Date.now() - startTime;
603
+
604
+ // Update tracked command info
605
+ commandInfo.stdoutBuffer = stdout;
606
+ commandInfo.stderrBuffer = stderr;
607
+ commandInfo.endTime = new Date().toISOString();
608
+ commandInfo.lastOutputTime = Date.now();
609
+
610
+ if (error) {
611
+ commandInfo.state = TERMINAL_CONFIG.STATES.FAILED;
612
+ commandInfo.exitCode = error.code || -1;
613
+ commandInfo.error = error.message;
614
+
615
+ this.logger?.error(`Command failed: ${translatedCommand}`, {
616
+ originalCommand,
617
+ translatedCommand,
618
+ error: error.message,
619
+ workingDirectory: workingDir,
620
+ executionTime,
621
+ commandId
622
+ });
623
+
624
+ resolve({
625
+ success: false,
626
+ action: 'run-command',
627
+ command: originalCommand,
628
+ commandId,
629
+ translatedCommand: translatedCommand !== originalCommand ? translatedCommand : undefined,
630
+ error: error.message,
631
+ stderr: stderr.trim(),
632
+ stdout: stdout.trim(),
633
+ exitCode: error.code,
634
+ executionTime,
635
+ workingDirectory: workingDir
636
+ });
637
+ return;
638
+ }
639
+
640
+ commandInfo.state = TERMINAL_CONFIG.STATES.COMPLETED;
641
+ commandInfo.exitCode = 0;
642
+
643
+ this.logger?.info(`Command completed: ${translatedCommand}`, {
644
+ originalCommand,
645
+ translatedCommand,
646
+ executionTime,
647
+ stdoutLength: stdout.length,
648
+ stderrLength: stderr.length,
649
+ commandId
650
+ });
651
+
652
+ resolve({
653
+ success: true,
654
+ action: 'run-command',
655
+ command: originalCommand,
656
+ commandId,
657
+ translatedCommand: translatedCommand !== originalCommand ? translatedCommand : undefined,
658
+ stdout: stdout.trim(),
659
+ stderr: stderr.trim(),
660
+ exitCode: 0,
661
+ executionTime,
662
+ workingDirectory: workingDir,
663
+ message: `Command executed successfully in ${executionTime}ms`
664
+ });
665
+ });
666
+
667
+ // Store PID
668
+ commandInfo.pid = childProcess.pid;
669
+ commandInfo.process = childProcess;
670
+
671
+ // Handle timeout - only if command is still running
672
+ setTimeout(() => {
673
+ if (commandInfo.state === TERMINAL_CONFIG.STATES.RUNNING && !childProcess.killed) {
674
+ childProcess.kill('SIGTERM');
675
+ commandInfo.state = TERMINAL_CONFIG.STATES.FAILED;
676
+ commandInfo.error = `Command timed out after ${timeout}ms`;
677
+ commandInfo.endTime = new Date().toISOString();
678
+ reject(new Error(`Command timed out after ${timeout}ms: ${translatedCommand} (original: ${originalCommand})`));
679
+ }
680
+ }, timeout);
681
+ });
682
+ }
683
+
684
+ /**
685
+ * Execute a command using spawn() for streaming output
686
+ * @param {string} command - Command to execute
687
+ * @param {string} workingDir - Working directory
688
+ * @param {Object} options - Execution options
689
+ * @returns {Promise<Object>} Execution result
690
+ * @private
691
+ */
692
+ async executeCommandWithSpawn(command, workingDir, options = {}) {
693
+ const { timeout = this.timeout, agentId, context } = options;
694
+
695
+ // Translate command for current terminal
696
+ const originalCommand = command;
697
+ let translatedCommand;
698
+
699
+ try {
700
+ translatedCommand = await this.translateCommand(command, {
701
+ agentId,
702
+ toolsRegistry: context?.toolsRegistry,
703
+ messageProcessor: context?.messageProcessor,
704
+ aiService: context?.aiService,
705
+ apiKey: context?.apiKey,
706
+ customApiKeys: context?.customApiKeys,
707
+ platformProvided: context?.platformProvided
708
+ });
709
+ } catch (error) {
710
+ this.logger?.warn('Command translation failed, using original command', {
711
+ originalCommand,
712
+ error: error.message
713
+ });
714
+ translatedCommand = command;
715
+ }
716
+
717
+ return new Promise((resolve, reject) => {
718
+ const startTime = Date.now();
719
+
720
+ this.logger?.info(`Executing command with spawn: ${translatedCommand}`, {
721
+ originalCommand,
722
+ translatedCommand,
723
+ terminal: this.detectedTerminal,
724
+ workingDirectory: workingDir,
725
+ timeout,
726
+ agentId
727
+ });
728
+
729
+ // Parse command into program and args
730
+ // For shell commands, we need to run them through a shell
731
+ let childProcess;
732
+
733
+ if (this.detectedTerminal === 'cmd' || this.detectedTerminal === 'powershell') {
734
+ // Windows: Use cmd /c or powershell -Command
735
+ const shell = this.detectedTerminal === 'powershell' ? 'powershell' : 'cmd';
736
+ const shellArgs = this.detectedTerminal === 'powershell'
737
+ ? ['-Command', translatedCommand]
738
+ : ['/c', translatedCommand];
739
+
740
+ childProcess = spawn(shell, shellArgs, {
741
+ cwd: workingDir,
742
+ env: { ...process.env },
743
+ windowsHide: true
744
+ });
745
+ } else {
746
+ // Unix/macOS: Use user's shell (respects zsh on macOS, bash on Linux, etc.)
747
+ const userShell = process.env.SHELL || '/bin/sh';
748
+ childProcess = spawn(userShell, ['-c', translatedCommand], {
749
+ cwd: workingDir,
750
+ env: { ...process.env }
751
+ });
752
+ }
753
+
754
+ // Buffers for stdout and stderr
755
+ let stdoutData = '';
756
+ let stderrData = '';
757
+ let isTimedOut = false;
758
+ let exitCode = null;
759
+
760
+ // Phase 2: Prompt detection tracking
761
+ let lastOutputTime = Date.now();
762
+ let promptDetectionResult = null;
763
+
764
+ // Set up timeout
765
+ const timeoutId = setTimeout(() => {
766
+ if (!childProcess.killed && exitCode === null) {
767
+ isTimedOut = true;
768
+ this.logger?.warn(`Command timed out, killing process: ${translatedCommand}`, {
769
+ timeout,
770
+ agentId
771
+ });
772
+ childProcess.kill('SIGTERM');
773
+
774
+ // If SIGTERM doesn't work, try SIGKILL after 5s
775
+ setTimeout(() => {
776
+ if (!childProcess.killed) {
777
+ childProcess.kill('SIGKILL');
778
+ }
779
+ }, 5000);
780
+ }
781
+ }, timeout);
782
+
783
+ // Stream stdout
784
+ childProcess.stdout.on('data', (chunk) => {
785
+ const data = chunk.toString();
786
+ stdoutData += data;
787
+ lastOutputTime = Date.now(); // Update last output time
788
+
789
+ this.logger?.debug(`Command output chunk: ${data.substring(0, 100)}`, {
790
+ agentId,
791
+ command: originalCommand.substring(0, 50)
792
+ });
793
+
794
+ // Phase 2: Check for prompts in stdout
795
+ if (!promptDetectionResult) {
796
+ const detection = this.promptDetector.detectPrompt(stdoutData, 'stdout');
797
+ if (detection) {
798
+ promptDetectionResult = detection;
799
+ this.logger?.info('Prompt detected in stdout', {
800
+ type: detection.type,
801
+ description: detection.description,
802
+ matchedText: detection.matchedText,
803
+ agentId,
804
+ command: originalCommand.substring(0, 50)
805
+ });
806
+ }
807
+ }
808
+ });
809
+
810
+ // Stream stderr
811
+ childProcess.stderr.on('data', (chunk) => {
812
+ const data = chunk.toString();
813
+ stderrData += data;
814
+ lastOutputTime = Date.now(); // Update last output time
815
+
816
+ this.logger?.debug(`Command error chunk: ${data.substring(0, 100)}`, {
817
+ agentId,
818
+ command: originalCommand.substring(0, 50)
819
+ });
820
+
821
+ // Phase 2: Check for prompts in stderr
822
+ if (!promptDetectionResult) {
823
+ const detection = this.promptDetector.detectPrompt(stderrData, 'stderr');
824
+ if (detection) {
825
+ promptDetectionResult = detection;
826
+ this.logger?.info('Prompt detected in stderr', {
827
+ type: detection.type,
828
+ description: detection.description,
829
+ matchedText: detection.matchedText,
830
+ agentId,
831
+ command: originalCommand.substring(0, 50)
832
+ });
833
+ }
834
+ }
835
+ });
836
+
837
+ // Handle process exit
838
+ childProcess.on('exit', (code, signal) => {
839
+ clearTimeout(timeoutId);
840
+ exitCode = code;
841
+ const executionTime = Date.now() - startTime;
842
+
843
+ // Phase 2: Calculate time since last output
844
+ const timeSinceLastOutput = Date.now() - lastOutputTime;
845
+
846
+ this.logger?.info(`Command process exited: ${translatedCommand}`, {
847
+ exitCode: code,
848
+ signal,
849
+ executionTime,
850
+ timedOut: isTimedOut,
851
+ stdoutLength: stdoutData.length,
852
+ stderrLength: stderrData.length,
853
+ promptDetected: !!promptDetectionResult,
854
+ timeSinceLastOutput
855
+ });
856
+
857
+ // Build common result object with Phase 2 additions
858
+ const baseResult = {
859
+ action: 'run-command',
860
+ command: originalCommand,
861
+ translatedCommand: translatedCommand !== originalCommand ? translatedCommand : undefined,
862
+ stdout: stdoutData.trim(),
863
+ stderr: stderrData.trim(),
864
+ exitCode: code,
865
+ executionTime,
866
+ workingDirectory: workingDir,
867
+ // Phase 2: Prompt detection fields
868
+ promptDetected: !!promptDetectionResult,
869
+ promptInfo: promptDetectionResult || undefined,
870
+ lastOutputTime: lastOutputTime,
871
+ timeSinceLastOutput: timeSinceLastOutput
872
+ };
873
+
874
+ // If timed out, reject
875
+ if (isTimedOut) {
876
+ resolve({
877
+ ...baseResult,
878
+ success: false,
879
+ error: `Command timed out after ${timeout}ms`,
880
+ exitCode: code || -1,
881
+ timedOut: true
882
+ });
883
+ return;
884
+ }
885
+
886
+ // If exit code is not 0, consider it a failure
887
+ if (code !== 0) {
888
+ this.logger?.error(`Command failed with exit code ${code}: ${translatedCommand}`, {
889
+ originalCommand,
890
+ translatedCommand,
891
+ exitCode: code,
892
+ stderr: stderrData.substring(0, 200),
893
+ executionTime,
894
+ promptDetected: !!promptDetectionResult
895
+ });
896
+
897
+ resolve({
898
+ ...baseResult,
899
+ success: false,
900
+ error: `Command exited with code ${code}`
901
+ });
902
+ return;
903
+ }
904
+
905
+ // Success
906
+ this.logger?.info(`Command completed successfully: ${translatedCommand}`, {
907
+ originalCommand,
908
+ executionTime,
909
+ stdoutLength: stdoutData.length,
910
+ stderrLength: stderrData.length,
911
+ promptDetected: !!promptDetectionResult
912
+ });
913
+
914
+ resolve({
915
+ ...baseResult,
916
+ success: true,
917
+ exitCode: 0,
918
+ message: `Command executed successfully in ${executionTime}ms`
919
+ });
920
+ });
921
+
922
+ // Handle spawn errors
923
+ childProcess.on('error', (error) => {
924
+ clearTimeout(timeoutId);
925
+ const executionTime = Date.now() - startTime;
926
+ const timeSinceLastOutput = Date.now() - lastOutputTime;
927
+
928
+ this.logger?.error(`Command spawn error: ${translatedCommand}`, {
929
+ originalCommand,
930
+ error: error.message,
931
+ executionTime,
932
+ promptDetected: !!promptDetectionResult
933
+ });
934
+
935
+ resolve({
936
+ success: false,
937
+ action: 'run-command',
938
+ command: originalCommand,
939
+ translatedCommand: translatedCommand !== originalCommand ? translatedCommand : undefined,
940
+ error: error.message,
941
+ stderr: stderrData.trim(),
942
+ stdout: stdoutData.trim(),
943
+ exitCode: -1,
944
+ executionTime,
945
+ workingDirectory: workingDir,
946
+ // Phase 2: Prompt detection fields
947
+ promptDetected: !!promptDetectionResult,
948
+ promptInfo: promptDetectionResult || undefined,
949
+ lastOutputTime: lastOutputTime,
950
+ timeSinceLastOutput: timeSinceLastOutput
951
+ });
952
+ });
953
+ });
954
+ }
955
+
956
+ /**
957
+ * Change current working directory
958
+ * @private
959
+ */
960
+ async changeDirectory(targetDir, currentDir, accessConfig) {
961
+ try {
962
+ let newDirectory;
963
+
964
+ if (path.isAbsolute(targetDir)) {
965
+ newDirectory = targetDir;
966
+ } else {
967
+ newDirectory = path.resolve(currentDir, targetDir);
968
+ }
969
+
970
+ // Verify directory exists
971
+ const stats = await fs.stat(newDirectory);
972
+ if (!stats.isDirectory()) {
973
+ throw new Error(`Not a directory: ${newDirectory}`);
974
+ }
975
+
976
+ // Security check: validate directory access using DirectoryAccessManager
977
+ const accessResult = this.directoryAccessManager.validateReadAccess(newDirectory, accessConfig);
978
+ if (!accessResult.allowed) {
979
+ this.logger?.warn(`Directory change blocked: ${accessResult.reason}`, {
980
+ targetDirectory: newDirectory,
981
+ reason: accessResult.reason,
982
+ category: accessResult.category
983
+ });
984
+ throw new Error(`Access denied: ${accessResult.reason}`);
985
+ }
986
+
987
+ return {
988
+ success: true,
989
+ action: 'change-directory',
990
+ previousDirectory: currentDir,
991
+ newDirectory,
992
+ message: `Changed directory to ${newDirectory}`
993
+ };
994
+
995
+ } catch (error) {
996
+ throw new Error(`Failed to change directory to ${targetDir}: ${error.message}`);
997
+ }
998
+ }
999
+
1000
+ /**
1001
+ * List directory contents
1002
+ * @private
1003
+ */
1004
+ async listDirectory(dirPath) {
1005
+ try {
1006
+ const files = await fs.readdir(dirPath, { withFileTypes: true });
1007
+
1008
+ const contents = files.map(file => ({
1009
+ name: file.name,
1010
+ type: file.isDirectory() ? 'directory' : 'file',
1011
+ isSymlink: file.isSymbolicLink()
1012
+ }));
1013
+
1014
+ return {
1015
+ success: true,
1016
+ action: 'list-directory',
1017
+ directory: dirPath,
1018
+ contents,
1019
+ totalItems: contents.length,
1020
+ directories: contents.filter(item => item.type === 'directory').length,
1021
+ files: contents.filter(item => item.type === 'file').length,
1022
+ message: `Listed ${contents.length} items in ${dirPath}`
1023
+ };
1024
+
1025
+ } catch (error) {
1026
+ throw new Error(`Failed to list directory ${dirPath}: ${error.message}`);
1027
+ }
1028
+ }
1029
+
1030
+ /**
1031
+ * Create directory
1032
+ * @private
1033
+ */
1034
+ async createDirectory(dirPath, currentDir) {
1035
+ try {
1036
+ let fullPath;
1037
+
1038
+ if (path.isAbsolute(dirPath)) {
1039
+ fullPath = dirPath;
1040
+ } else {
1041
+ fullPath = path.resolve(currentDir, dirPath);
1042
+ }
1043
+
1044
+ await fs.mkdir(fullPath, { recursive: true });
1045
+
1046
+ return {
1047
+ success: true,
1048
+ action: 'create-directory',
1049
+ directory: fullPath,
1050
+ relativePath: path.relative(currentDir, fullPath),
1051
+ message: `Created directory: ${fullPath}`
1052
+ };
1053
+
1054
+ } catch (error) {
1055
+ throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
1056
+ }
1057
+ }
1058
+
1059
+ /**
1060
+ * Check if command is blocked for security
1061
+ * @private
1062
+ */
1063
+ isBlockedCommand(command) {
1064
+ const cmdLower = command.toLowerCase().trim();
1065
+
1066
+ return this.blockedCommands.some(blocked => {
1067
+ const blockedLower = blocked.toLowerCase();
1068
+ return cmdLower === blockedLower || cmdLower.startsWith(blockedLower + ' ');
1069
+ });
1070
+ }
1071
+
1072
+ /**
1073
+ * Check if command is in allowed list
1074
+ * @private
1075
+ */
1076
+ isAllowedCommand(command) {
1077
+ if (!this.allowedCommands) return true;
1078
+
1079
+ const cmdLower = command.toLowerCase().trim();
1080
+ const cmdBase = cmdLower.split(' ')[0];
1081
+
1082
+ return this.allowedCommands.some(allowed =>
1083
+ allowed.toLowerCase() === cmdBase ||
1084
+ cmdLower.startsWith(allowed.toLowerCase() + ' ')
1085
+ );
1086
+ }
1087
+
1088
+ /**
1089
+ * Add command to history
1090
+ * @private
1091
+ */
1092
+ addToHistory(action, result, agentId) {
1093
+ const historyEntry = {
1094
+ timestamp: new Date().toISOString(),
1095
+ agentId,
1096
+ action: action.type,
1097
+ command: action.command || action.directory,
1098
+ success: result.success,
1099
+ executionTime: result.executionTime || 0,
1100
+ workingDirectory: result.workingDirectory
1101
+ };
1102
+
1103
+ this.commandHistory.push(historyEntry);
1104
+
1105
+ // Keep only last 100 entries
1106
+ if (this.commandHistory.length > 100) {
1107
+ this.commandHistory = this.commandHistory.slice(-100);
1108
+ }
1109
+ }
1110
+
1111
+ /**
1112
+ * Get supported actions for this tool
1113
+ * @returns {Array<string>} Array of supported action names
1114
+ */
1115
+ getSupportedActions() {
1116
+ return ['run-command', 'change-directory', 'list-directory', 'create-directory', 'get-working-directory'];
1117
+ }
1118
+
1119
+ /**
1120
+ * Get parameter schema for validation
1121
+ * @returns {Object} Parameter schema
1122
+ */
1123
+ getParameterSchema() {
1124
+ return {
1125
+ type: 'object',
1126
+ properties: {
1127
+ actions: {
1128
+ type: 'array',
1129
+ minItems: 1,
1130
+ items: {
1131
+ type: 'object',
1132
+ properties: {
1133
+ type: {
1134
+ type: 'string',
1135
+ enum: this.getSupportedActions()
1136
+ },
1137
+ command: { type: 'string' },
1138
+ directory: { type: 'string' }
1139
+ },
1140
+ required: ['type']
1141
+ }
1142
+ },
1143
+ timeout: {
1144
+ type: 'integer',
1145
+ minimum: 1000,
1146
+ maximum: this.timeout
1147
+ },
1148
+ async: {
1149
+ type: 'boolean'
1150
+ }
1151
+ },
1152
+ required: ['actions']
1153
+ };
1154
+ }
1155
+
1156
+ /**
1157
+ * Get command history for debugging
1158
+ * @returns {Array} Command history
1159
+ */
1160
+ getCommandHistory(agentId = null) {
1161
+ if (agentId) {
1162
+ return this.commandHistory.filter(entry => entry.agentId === agentId);
1163
+ }
1164
+ return [...this.commandHistory];
1165
+ }
1166
+
1167
+ /**
1168
+ * Clear working directory context for agent
1169
+ * @param {string} agentId - Agent identifier
1170
+ */
1171
+ clearWorkingDirectory(agentId) {
1172
+ for (const [key] of this.workingDirectories) {
1173
+ if (key.startsWith(`${agentId}-`)) {
1174
+ this.workingDirectories.delete(key);
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ /**
1180
+ * Get current working directory for agent
1181
+ * @param {string} agentId - Agent identifier
1182
+ * @param {string} projectDir - Project directory
1183
+ * @returns {string} Current working directory
1184
+ */
1185
+ getCurrentWorkingDirectory(agentId, projectDir = null) {
1186
+ const contextKey = `${agentId}-${projectDir || 'default'}`;
1187
+ return this.workingDirectories.get(contextKey) || projectDir || process.cwd();
1188
+ }
1189
+
1190
+ /**
1191
+ * Initialize terminal detection
1192
+ * @private
1193
+ */
1194
+ initializeTerminalDetection() {
1195
+ // Detect platform
1196
+ this.platformType = process.platform;
1197
+
1198
+ // Detect terminal type based on environment
1199
+ if (process.platform === 'win32') {
1200
+ // Windows detection
1201
+ if (process.env.PSModulePath) {
1202
+ this.detectedTerminal = 'powershell';
1203
+ } else if (process.env.SHELL && process.env.SHELL.includes('bash')) {
1204
+ this.detectedTerminal = 'bash'; // Git Bash or WSL
1205
+ } else {
1206
+ this.detectedTerminal = 'cmd'; // Windows Command Prompt
1207
+ }
1208
+ } else if (process.platform === 'darwin') {
1209
+ // macOS: Detect actual shell (zsh is default since Catalina 10.15)
1210
+ const shell = process.env.SHELL || '/bin/zsh';
1211
+ if (shell.includes('zsh')) {
1212
+ this.detectedTerminal = 'zsh';
1213
+ } else if (shell.includes('fish')) {
1214
+ this.detectedTerminal = 'fish';
1215
+ } else {
1216
+ this.detectedTerminal = 'bash';
1217
+ }
1218
+ } else {
1219
+ // Linux/Unix: Detect actual shell from $SHELL
1220
+ const shell = process.env.SHELL || '/bin/bash';
1221
+ if (shell.includes('zsh')) {
1222
+ this.detectedTerminal = 'zsh';
1223
+ } else if (shell.includes('fish')) {
1224
+ this.detectedTerminal = 'fish';
1225
+ } else {
1226
+ this.detectedTerminal = 'bash';
1227
+ }
1228
+ }
1229
+
1230
+ this.logger?.info('Terminal detected', {
1231
+ platform: this.platformType,
1232
+ terminal: this.detectedTerminal,
1233
+ shell: process.env.SHELL
1234
+ });
1235
+ }
1236
+
1237
+ /**
1238
+ * Translate command for current terminal using AI if needed
1239
+ * @param {string} command - Original command
1240
+ * @param {Object} context - Execution context with aiService access
1241
+ * @returns {Promise<string>} Translated command
1242
+ * @private
1243
+ */
1244
+ async translateCommand(command, context = {}) {
1245
+ const trimmedCommand = command.trim();
1246
+
1247
+ // Perform comprehensive command analysis
1248
+ const analysis = this.analyzeCommandCompatibility(trimmedCommand);
1249
+
1250
+ this.logger?.info('Command compatibility analysis', {
1251
+ command: trimmedCommand,
1252
+ detectedTerminal: analysis.detectedTerminal,
1253
+ commandType: analysis.commandType,
1254
+ commandCategory: analysis.commandCategory,
1255
+ compatible: analysis.compatible,
1256
+ issues: analysis.specificIssues,
1257
+ suggestedAction: analysis.suggestedAction,
1258
+ confidence: analysis.confidence
1259
+ });
1260
+
1261
+ if (analysis.compatible) {
1262
+ return command;
1263
+ }
1264
+
1265
+ // Try simple translations first (fast path)
1266
+ let simpleTranslation = null;
1267
+ if (this.detectedTerminal === 'cmd') {
1268
+ simpleTranslation = this.translateToWindowsCmd(trimmedCommand);
1269
+ } else if (this.detectedTerminal === 'powershell') {
1270
+ simpleTranslation = this.translateToPowerShell(trimmedCommand);
1271
+ } else if (this.detectedTerminal === 'bash') {
1272
+ simpleTranslation = this.translateToBash(trimmedCommand);
1273
+ }
1274
+
1275
+ // If simple translation looks sufficient and command is simple, use it
1276
+ if (simpleTranslation && this.isSimpleCommand(trimmedCommand)) {
1277
+ this.logger?.info('Using simple translation', {
1278
+ original: command,
1279
+ translated: simpleTranslation,
1280
+ method: 'simple'
1281
+ });
1282
+ return simpleTranslation;
1283
+ }
1284
+
1285
+ // For complex commands, use AI translation
1286
+ if (context.aiService || (context.toolsRegistry && context.agentId)) {
1287
+ this.logger?.info('Attempting AI translation for complex command', {
1288
+ command: command.substring(0, 100) + '...',
1289
+ hasAiService: !!context.aiService,
1290
+ hasToolsRegistry: !!context.toolsRegistry,
1291
+ agentId: context.agentId
1292
+ });
1293
+
1294
+ try {
1295
+ const aiTranslation = await this.translateCommandWithAI(command, context);
1296
+ if (aiTranslation && aiTranslation.trim() !== command.trim()) {
1297
+ this.logger?.info('Using AI translation', {
1298
+ original: command,
1299
+ translated: aiTranslation,
1300
+ method: 'ai'
1301
+ });
1302
+ return aiTranslation;
1303
+ } else {
1304
+ this.logger?.warn('AI translation returned same command', {
1305
+ original: command,
1306
+ translated: aiTranslation
1307
+ });
1308
+ }
1309
+ } catch (error) {
1310
+ this.logger?.warn('AI translation failed, falling back to simple translation', {
1311
+ original: command,
1312
+ error: error.message,
1313
+ stack: error.stack
1314
+ });
1315
+ }
1316
+ } else {
1317
+ this.logger?.warn('AI translation not available - missing context', {
1318
+ hasAiService: !!context.aiService,
1319
+ hasToolsRegistry: !!context.toolsRegistry,
1320
+ hasAgentId: !!context.agentId,
1321
+ contextKeys: Object.keys(context)
1322
+ });
1323
+ }
1324
+
1325
+ // Fallback to simple translation or original command
1326
+ return simpleTranslation || command;
1327
+ }
1328
+
1329
+ /**
1330
+ * Check if command is simple enough for regex translation
1331
+ * @param {string} command - Command to check
1332
+ * @returns {boolean} Whether command is simple
1333
+ * @private
1334
+ */
1335
+ isSimpleCommand(command) {
1336
+ // Check for complex command patterns using simple string methods
1337
+ const cmd = command.toLowerCase();
1338
+
1339
+ // Multi-line or escape sequences
1340
+ if (command.includes('\\n') || command.includes('\\r') || command.includes('\\t')) return false;
1341
+ if (command.includes('\n') || command.includes('\r')) return false;
1342
+
1343
+ // Code content
1344
+ if (command.includes('#include') || command.includes('printf') || command.includes('main()')) return false;
1345
+ if (command.includes('def ') || command.includes('function ') || command.includes('class ')) return false;
1346
+
1347
+ // Single quotes (problematic on Windows CMD)
1348
+ if (command.includes("echo '") && !command.includes('echo "')) return false;
1349
+
1350
+ // Long complex strings (likely contain complex content)
1351
+ const singleQuoteMatch = command.match(/'([^']*)'/);
1352
+ if (singleQuoteMatch && singleQuoteMatch[1].length > 50) return false;
1353
+
1354
+ // Unix-style paths in redirections on Windows
1355
+ if (this.detectedTerminal === 'cmd' && command.includes('>') && command.includes('/') && !command.includes('\\')) {
1356
+ return false;
1357
+ }
1358
+
1359
+ // Multiple commands
1360
+ if (command.includes(' && ') || command.includes(' || ') || command.includes('; ')) return false;
1361
+
1362
+ // Command substitution or complex shell features
1363
+ if (command.includes('$(') || command.includes('`') || command.includes('{') || command.includes('[')) return false;
1364
+
1365
+ // Loops and conditionals
1366
+ if (cmd.includes('for ') || cmd.includes('while ') || cmd.includes('if ')) return false;
1367
+ if (cmd.includes('export ') || cmd.includes('source ')) return false;
1368
+
1369
+ // Pipes and redirections
1370
+ if (command.includes(' | ') || command.includes(' > &')) return false;
1371
+
1372
+ return true; // Command is simple
1373
+ }
1374
+
1375
+ /**
1376
+ * Translate command using AI service
1377
+ * @param {string} command - Original command
1378
+ * @param {Object} context - Execution context
1379
+ * @returns {Promise<string>} AI-translated command
1380
+ * @private
1381
+ */
1382
+ async translateCommandWithAI(command, context) {
1383
+ // Get AI service - either directly provided or through tools registry
1384
+ let aiService = context.aiService;
1385
+ if (!aiService && context.toolsRegistry && context.agentId) {
1386
+ // Try to get AI service through the system (this would need to be passed through context)
1387
+ const messageProcessor = context.messageProcessor; // Would need to be passed in context
1388
+ if (messageProcessor && messageProcessor.aiService) {
1389
+ aiService = messageProcessor.aiService;
1390
+ }
1391
+ }
1392
+
1393
+ if (!aiService) {
1394
+ throw new Error('AI service not available for command translation');
1395
+ }
1396
+
1397
+ const translationPrompt = this.buildTranslationPrompt(command);
1398
+
1399
+ // Use a lightweight model for translation
1400
+ const model = 'gpt-4-mini'; // Fast and cost-effective for simple translations
1401
+
1402
+ try {
1403
+ const response = await aiService.sendMessage(model, translationPrompt, {
1404
+ agentId: context.agentId,
1405
+ temperature: 0.1, // Low temperature for consistent translations
1406
+ maxTokens: 200, // Short response expected
1407
+ apiKey: context.apiKey,
1408
+ customApiKeys: context.customApiKeys,
1409
+ platformProvided: context.platformProvided
1410
+ });
1411
+
1412
+ // Extract the translated command from the response
1413
+ const translatedCommand = this.extractTranslatedCommand(response.content);
1414
+
1415
+ this.logger?.debug('AI command translation completed', {
1416
+ original: command,
1417
+ translated: translatedCommand,
1418
+ model: model,
1419
+ terminal: this.detectedTerminal
1420
+ });
1421
+
1422
+ return translatedCommand;
1423
+
1424
+ } catch (error) {
1425
+ this.logger?.error('AI command translation failed', {
1426
+ command,
1427
+ terminal: this.detectedTerminal,
1428
+ error: error.message
1429
+ });
1430
+ throw error;
1431
+ }
1432
+ }
1433
+
1434
+ /**
1435
+ * Build translation prompt for AI service
1436
+ * @param {string} command - Command to translate
1437
+ * @returns {string} Translation prompt
1438
+ * @private
1439
+ */
1440
+ buildTranslationPrompt(command) {
1441
+ const terminalInfo = {
1442
+ 'cmd': 'Windows Command Prompt (cmd.exe)',
1443
+ 'powershell': 'Windows PowerShell',
1444
+ 'bash': 'Bash shell (Linux/Unix)',
1445
+ 'zsh': 'Z shell (macOS default since Catalina)',
1446
+ 'fish': 'Fish shell (friendly interactive shell)'
1447
+ };
1448
+
1449
+ const currentTerminal = terminalInfo[this.detectedTerminal] || this.detectedTerminal;
1450
+
1451
+ // Get detailed analysis for better translation context
1452
+ const analysis = this.analyzeCommandCompatibility(command);
1453
+
1454
+ let prompt = `Translate this command to work correctly in ${currentTerminal}:
1455
+
1456
+ ORIGINAL COMMAND:
1457
+ \`\`\`
1458
+ ${command}
1459
+ \`\`\`
1460
+
1461
+ COMMAND ANALYSIS:
1462
+ - Command Type: ${analysis.commandType}
1463
+ - Category: ${analysis.commandCategory}
1464
+ - Target Terminal: ${currentTerminal}
1465
+ - Compatibility Issues: ${analysis.specificIssues.join(', ') || 'None detected'}`;
1466
+
1467
+ // Add alternative suggestions if available
1468
+ if (analysis.commandType === 'unix' && analysis.alternatives && analysis.alternatives[this.detectedTerminal]) {
1469
+ prompt += `\n- Suggested Alternative: ${analysis.alternatives[this.detectedTerminal]}`;
1470
+ }
1471
+
1472
+ prompt += `
1473
+
1474
+ REQUIREMENTS:
1475
+ 1. Make the command work correctly in ${currentTerminal}
1476
+ 2. Preserve the original intent and functionality
1477
+ 3. Handle file paths, quoting, and syntax correctly
1478
+ 4. If creating files, ensure proper encoding and line endings
1479
+ 5. Address the specific compatibility issues identified above
1480
+ 6. Return ONLY the translated command, nothing else
1481
+
1482
+ TRANSLATED COMMAND:`;
1483
+
1484
+ return prompt;
1485
+ }
1486
+
1487
+ /**
1488
+ * Extract translated command from AI response
1489
+ * @param {string} response - AI response content
1490
+ * @returns {string} Extracted command
1491
+ * @private
1492
+ */
1493
+ extractTranslatedCommand(response) {
1494
+ // Remove markdown code blocks if present
1495
+ let extracted = response.replace(/```[a-z]*\n?(.*?)\n?```/s, '$1');
1496
+
1497
+ // Remove common prefixes
1498
+ extracted = extracted.replace(/^(TRANSLATED COMMAND:|Command:|Result:)\s*/i, '');
1499
+
1500
+ // Take first line if multiple lines (unless it's intentionally multiline)
1501
+ const lines = extracted.trim().split('\n');
1502
+ if (lines.length > 1 && !extracted.includes('&&') && !extracted.includes('||')) {
1503
+ extracted = lines[0].trim();
1504
+ }
1505
+
1506
+ return extracted.trim();
1507
+ }
1508
+
1509
+ /**
1510
+ * Analyze command compatibility with current terminal
1511
+ * @param {string} command - Command to check
1512
+ * @returns {Object} Compatibility analysis result
1513
+ * @private
1514
+ */
1515
+ analyzeCommandCompatibility(command) {
1516
+ const analysis = this.classifyCommand(command);
1517
+ const isCompatible = this.isCommandCompatibleWithTerminal(analysis, this.detectedTerminal);
1518
+
1519
+ return {
1520
+ compatible: isCompatible,
1521
+ detectedTerminal: this.detectedTerminal,
1522
+ commandType: analysis.type,
1523
+ commandCategory: analysis.category,
1524
+ specificIssues: analysis.issues,
1525
+ suggestedAction: isCompatible ? 'execute' : 'translate',
1526
+ confidence: analysis.confidence
1527
+ };
1528
+ }
1529
+
1530
+ /**
1531
+ * Legacy wrapper for backward compatibility
1532
+ * @param {string} command - Command to check
1533
+ * @returns {boolean} Whether command is compatible
1534
+ * @private
1535
+ */
1536
+ isCommandCompatible(command) {
1537
+ return this.analyzeCommandCompatibility(command).compatible;
1538
+ }
1539
+
1540
+ /**
1541
+ * Classify command type and detect potential issues
1542
+ * @param {string} command - Command to analyze
1543
+ * @returns {Object} Command classification
1544
+ * @private
1545
+ */
1546
+ classifyCommand(command) {
1547
+ const cmd = command.toLowerCase().trim();
1548
+ const firstWord = cmd.split(' ')[0];
1549
+ const issues = [];
1550
+ let confidence = 0.9;
1551
+
1552
+ // Unix/Linux commands
1553
+ const unixCommands = {
1554
+ // File operations
1555
+ 'ls': { category: 'file-listing', alternatives: { cmd: 'dir', powershell: 'Get-ChildItem' }},
1556
+ 'cat': { category: 'file-viewing', alternatives: { cmd: 'type', powershell: 'Get-Content' }},
1557
+ 'grep': { category: 'text-search', alternatives: { cmd: 'findstr', powershell: 'Select-String' }},
1558
+ 'find': { category: 'file-search', alternatives: { cmd: 'dir /s', powershell: 'Get-ChildItem -Recurse' }},
1559
+ 'head': { category: 'file-viewing', alternatives: { cmd: 'more', powershell: 'Get-Content -Head' }},
1560
+ 'tail': { category: 'file-viewing', alternatives: { cmd: 'more +n', powershell: 'Get-Content -Tail' }},
1561
+ 'wc': { category: 'text-analysis', alternatives: { cmd: 'find /c', powershell: 'Measure-Object' }},
1562
+
1563
+ // File manipulation
1564
+ 'cp': { category: 'file-copy', alternatives: { cmd: 'copy', powershell: 'Copy-Item' }},
1565
+ 'mv': { category: 'file-move', alternatives: { cmd: 'move', powershell: 'Move-Item' }},
1566
+ 'rm': { category: 'file-delete', alternatives: { cmd: 'del', powershell: 'Remove-Item' }},
1567
+ 'mkdir': { category: 'directory-create', alternatives: { cmd: 'mkdir', powershell: 'New-Item -Type Directory' }},
1568
+ 'rmdir': { category: 'directory-delete', alternatives: { cmd: 'rmdir', powershell: 'Remove-Item' }},
1569
+ 'touch': { category: 'file-create', alternatives: { cmd: 'type nul >', powershell: 'New-Item -Type File' }},
1570
+
1571
+ // Permissions and ownership
1572
+ 'chmod': { category: 'permissions', alternatives: { cmd: 'icacls', powershell: 'Set-Acl' }},
1573
+ 'chown': { category: 'ownership', alternatives: { cmd: 'takeown', powershell: 'Set-Acl' }},
1574
+
1575
+ // Process management
1576
+ 'ps': { category: 'process-list', alternatives: { cmd: 'tasklist', powershell: 'Get-Process' }},
1577
+ 'kill': { category: 'process-kill', alternatives: { cmd: 'taskkill', powershell: 'Stop-Process' }},
1578
+ 'killall': { category: 'process-kill', alternatives: { cmd: 'taskkill /f /im', powershell: 'Get-Process | Stop-Process' }},
1579
+ 'top': { category: 'process-monitor', alternatives: { cmd: 'tasklist', powershell: 'Get-Process | Sort-Object CPU' }},
1580
+
1581
+ // Network
1582
+ 'wget': { category: 'download', alternatives: { cmd: 'curl', powershell: 'Invoke-WebRequest' }},
1583
+ 'curl': { category: 'http-client', alternatives: { cmd: 'curl', powershell: 'Invoke-RestMethod' }},
1584
+ 'ping': { category: 'network-test', alternatives: { cmd: 'ping', powershell: 'Test-NetConnection' }},
1585
+
1586
+ // Text processing
1587
+ 'awk': { category: 'text-processing', alternatives: { cmd: 'for /f', powershell: 'ForEach-Object' }},
1588
+ 'sed': { category: 'text-edit', alternatives: { cmd: 'powershell -c', powershell: 'native' }},
1589
+ 'sort': { category: 'text-sort', alternatives: { cmd: 'sort', powershell: 'Sort-Object' }},
1590
+ 'uniq': { category: 'text-dedupe', alternatives: { cmd: 'sort /unique', powershell: 'Sort-Object -Unique' }},
1591
+
1592
+ // Environment
1593
+ 'pwd': { category: 'directory-current', alternatives: { cmd: 'cd', powershell: 'Get-Location' }},
1594
+ 'whoami': { category: 'user-info', alternatives: { cmd: 'echo %USERNAME%', powershell: 'whoami' }},
1595
+ 'env': { category: 'environment', alternatives: { cmd: 'set', powershell: 'Get-ChildItem Env:' }},
1596
+ 'export': { category: 'environment', alternatives: { cmd: 'set', powershell: '$env:' }},
1597
+ 'source': { category: 'script-execute', alternatives: { cmd: 'call', powershell: '. ' }},
1598
+
1599
+ // Archives
1600
+ 'tar': { category: 'archive', alternatives: { cmd: '7z', powershell: 'Compress-Archive' }},
1601
+ 'zip': { category: 'archive', alternatives: { cmd: 'powershell Compress-Archive', powershell: 'Compress-Archive' }},
1602
+ 'unzip': { category: 'archive', alternatives: { cmd: 'powershell Expand-Archive', powershell: 'Expand-Archive' }},
1603
+
1604
+ // System info
1605
+ 'df': { category: 'disk-info', alternatives: { cmd: 'fsutil volume diskfree', powershell: 'Get-WmiObject -Class Win32_LogicalDisk' }},
1606
+ 'du': { category: 'disk-usage', alternatives: { cmd: 'dir /s', powershell: 'Get-ChildItem -Recurse | Measure-Object' }},
1607
+ 'free': { category: 'memory-info', alternatives: { cmd: 'systeminfo', powershell: 'Get-WmiObject -Class Win32_PhysicalMemory' }},
1608
+ 'uname': { category: 'system-info', alternatives: { cmd: 'systeminfo', powershell: 'Get-ComputerInfo' }}
1609
+ };
1610
+
1611
+ // Windows CMD commands
1612
+ const cmdCommands = {
1613
+ 'dir': { category: 'file-listing', alternatives: { unix: 'ls', powershell: 'Get-ChildItem' }},
1614
+ 'type': { category: 'file-viewing', alternatives: { unix: 'cat', powershell: 'Get-Content' }},
1615
+ 'copy': { category: 'file-copy', alternatives: { unix: 'cp', powershell: 'Copy-Item' }},
1616
+ 'move': { category: 'file-move', alternatives: { unix: 'mv', powershell: 'Move-Item' }},
1617
+ 'del': { category: 'file-delete', alternatives: { unix: 'rm', powershell: 'Remove-Item' }},
1618
+ 'tasklist': { category: 'process-list', alternatives: { unix: 'ps', powershell: 'Get-Process' }},
1619
+ 'taskkill': { category: 'process-kill', alternatives: { unix: 'kill', powershell: 'Stop-Process' }},
1620
+ 'findstr': { category: 'text-search', alternatives: { unix: 'grep', powershell: 'Select-String' }}
1621
+ };
1622
+
1623
+ // PowerShell commands
1624
+ const powershellCommands = {
1625
+ 'get-childitem': { category: 'file-listing', alternatives: { unix: 'ls', cmd: 'dir' }},
1626
+ 'get-content': { category: 'file-viewing', alternatives: { unix: 'cat', cmd: 'type' }},
1627
+ 'copy-item': { category: 'file-copy', alternatives: { unix: 'cp', cmd: 'copy' }},
1628
+ 'move-item': { category: 'file-move', alternatives: { unix: 'mv', cmd: 'move' }},
1629
+ 'remove-item': { category: 'file-delete', alternatives: { unix: 'rm', cmd: 'del' }},
1630
+ 'get-process': { category: 'process-list', alternatives: { unix: 'ps', cmd: 'tasklist' }},
1631
+ 'stop-process': { category: 'process-kill', alternatives: { unix: 'kill', cmd: 'taskkill' }}
1632
+ };
1633
+
1634
+ // Determine command type
1635
+ let commandType = 'unknown';
1636
+ let category = 'unknown';
1637
+ let alternatives = {};
1638
+
1639
+ if (unixCommands[firstWord]) {
1640
+ commandType = 'unix';
1641
+ category = unixCommands[firstWord].category;
1642
+ alternatives = unixCommands[firstWord].alternatives;
1643
+ } else if (cmdCommands[firstWord]) {
1644
+ commandType = 'windows-cmd';
1645
+ category = cmdCommands[firstWord].category;
1646
+ alternatives = cmdCommands[firstWord].alternatives;
1647
+ } else if (powershellCommands[firstWord]) {
1648
+ commandType = 'powershell';
1649
+ category = powershellCommands[firstWord].category;
1650
+ alternatives = powershellCommands[firstWord].alternatives;
1651
+ } else {
1652
+ // Check for built-in commands that work everywhere
1653
+ const universalCommands = ['echo', 'cd', 'exit', 'help'];
1654
+ if (universalCommands.includes(firstWord)) {
1655
+ commandType = 'universal';
1656
+ category = 'builtin';
1657
+ } else {
1658
+ commandType = 'unknown';
1659
+ confidence = 0.3;
1660
+ }
1661
+ }
1662
+
1663
+ // Check for syntax issues
1664
+ if (command.includes("'") && !command.includes('"')) {
1665
+ issues.push('single-quotes-problematic');
1666
+ }
1667
+ if (command.includes('/') && !command.includes('\\') && !command.includes('http') && command.includes('>')) {
1668
+ issues.push('unix-style-paths');
1669
+ }
1670
+ if (command.includes('\\n') || command.includes('\\t')) {
1671
+ issues.push('escape-sequences');
1672
+ }
1673
+ if (command.includes(' && ') || command.includes(' || ') || command.includes(';')) {
1674
+ issues.push('command-chaining');
1675
+ }
1676
+
1677
+ return {
1678
+ type: commandType,
1679
+ category: category,
1680
+ alternatives: alternatives,
1681
+ issues: issues,
1682
+ confidence: confidence,
1683
+ firstWord: firstWord,
1684
+ fullCommand: command
1685
+ };
1686
+ }
1687
+
1688
+ /**
1689
+ * Check if classified command is compatible with terminal
1690
+ * @param {Object} analysis - Command analysis
1691
+ * @param {string} terminal - Target terminal type
1692
+ * @returns {boolean} Whether compatible
1693
+ * @private
1694
+ */
1695
+ isCommandCompatibleWithTerminal(analysis, terminal) {
1696
+ switch (terminal) {
1697
+ case 'cmd':
1698
+ if (analysis.type === 'unix') return false;
1699
+ if (analysis.issues.includes('single-quotes-problematic')) return false;
1700
+ if (analysis.issues.includes('unix-style-paths')) return false;
1701
+ return analysis.type === 'windows-cmd' || analysis.type === 'universal';
1702
+
1703
+ case 'powershell':
1704
+ if (analysis.type === 'unix' && !analysis.alternatives.powershell) return false;
1705
+ return true; // PowerShell is quite compatible
1706
+
1707
+ case 'zsh':
1708
+ case 'fish':
1709
+ case 'bash':
1710
+ // All Unix-like shells handle Unix commands similarly
1711
+ if (analysis.type === 'windows-cmd') return false;
1712
+ return analysis.type === 'unix' || analysis.type === 'universal';
1713
+
1714
+ default:
1715
+ return false;
1716
+ }
1717
+ }
1718
+
1719
+ /**
1720
+ * Translate command to Windows CMD syntax
1721
+ * @param {string} command - Original command
1722
+ * @returns {string} Windows CMD equivalent
1723
+ * @private
1724
+ */
1725
+ translateToWindowsCmd(command) {
1726
+ let translated = command;
1727
+
1728
+ // Common Unix to Windows translations
1729
+ const translations = new Map([
1730
+ // Directory listing
1731
+ [/^ls\s*$/, 'dir'],
1732
+ [/^ls\s+-la?$/, 'dir'],
1733
+ [/^ls\s+-l$/, 'dir'],
1734
+ [/^ls\s+-a$/, 'dir /a'],
1735
+
1736
+ // File operations
1737
+ [/^cat\s+(.+)$/, 'type $1'],
1738
+ [/^cp\s+(.+)\s+(.+)$/, 'copy $1 $2'],
1739
+ [/^mv\s+(.+)\s+(.+)$/, 'move $1 $2'],
1740
+ [/^rm\s+(.+)$/, 'del $1'],
1741
+ [/^mkdir\s+(.+)$/, 'mkdir $1'],
1742
+ [/^rmdir\s+(.+)$/, 'rmdir $1'],
1743
+
1744
+ // Process management
1745
+ [/^ps\s*$/, 'tasklist'],
1746
+ [/^kill\s+(.+)$/, 'taskkill /PID $1'],
1747
+
1748
+ // Environment
1749
+ [/^pwd\s*$/, 'cd'],
1750
+ [/^whoami\s*$/, 'echo %USERNAME%'],
1751
+
1752
+ // Network
1753
+ [/^ping\s+(.+)$/, 'ping $1'],
1754
+ [/^wget\s+(.+)$/, 'curl -O $1'],
1755
+ [/^curl\s+(.+)$/, 'curl $1']
1756
+ ]);
1757
+
1758
+ // Apply translations
1759
+ for (const [regex, replacement] of translations) {
1760
+ if (regex.test(translated)) {
1761
+ translated = translated.replace(regex, replacement);
1762
+ break;
1763
+ }
1764
+ }
1765
+
1766
+ // Fix echo command with single quotes
1767
+ translated = translated.replace(/echo\s+'([^']+)'\s*>/g, 'echo "$1" >');
1768
+ translated = translated.replace(/echo\s+'([^']+)'$/g, 'echo "$1"');
1769
+
1770
+ // Fix multi-line echo commands
1771
+ if (translated.includes("echo '") && translated.includes("\\n")) {
1772
+ // Convert multi-line echo to multiple echo commands or use different approach
1773
+ const match = translated.match(/echo\s+'(.+?)'\s*>\s*(.+)$/);
1774
+ if (match) {
1775
+ const content = match[1].replace(/\\n/g, '\n');
1776
+ const filename = match[2];
1777
+ const lines = content.split('\n');
1778
+
1779
+ // Create a batch file approach for multi-line content
1780
+ translated = `(${lines.map(line => `echo ${line}`).join(' & ')}) > ${filename}`;
1781
+ }
1782
+ }
1783
+
1784
+ this.logger?.info('Command translated for Windows CMD', {
1785
+ original: command,
1786
+ translated: translated
1787
+ });
1788
+
1789
+ return translated;
1790
+ }
1791
+
1792
+ /**
1793
+ * Translate command to PowerShell syntax
1794
+ * @param {string} command - Original command
1795
+ * @returns {string} PowerShell equivalent
1796
+ * @private
1797
+ */
1798
+ translateToPowerShell(command) {
1799
+ let translated = command;
1800
+
1801
+ const translations = new Map([
1802
+ [/^ls\s*$/, 'Get-ChildItem'],
1803
+ [/^ls\s+-la?$/, 'Get-ChildItem -Force'],
1804
+ [/^cat\s+(.+)$/, 'Get-Content $1'],
1805
+ [/^cp\s+(.+)\s+(.+)$/, 'Copy-Item $1 $2'],
1806
+ [/^mv\s+(.+)\s+(.+)$/, 'Move-Item $1 $2'],
1807
+ [/^rm\s+(.+)$/, 'Remove-Item $1'],
1808
+ [/^pwd\s*$/, 'Get-Location'],
1809
+ [/^ps\s*$/, 'Get-Process']
1810
+ ]);
1811
+
1812
+ for (const [regex, replacement] of translations) {
1813
+ if (regex.test(translated)) {
1814
+ translated = translated.replace(regex, replacement);
1815
+ break;
1816
+ }
1817
+ }
1818
+
1819
+ this.logger?.info('Command translated for PowerShell', {
1820
+ original: command,
1821
+ translated: translated
1822
+ });
1823
+
1824
+ return translated;
1825
+ }
1826
+
1827
+ /**
1828
+ * Translate command to Bash syntax
1829
+ * @param {string} command - Original command
1830
+ * @returns {string} Bash equivalent
1831
+ * @private
1832
+ */
1833
+ translateToBash(command) {
1834
+ // For bash, mainly fix Windows-specific commands
1835
+ let translated = command;
1836
+
1837
+ const translations = new Map([
1838
+ [/^dir\s*$/, 'ls'],
1839
+ [/^dir\s+\/a$/, 'ls -a'],
1840
+ [/^type\s+(.+)$/, 'cat $1'],
1841
+ [/^copy\s+(.+)\s+(.+)$/, 'cp $1 $2'],
1842
+ [/^move\s+(.+)\s+(.+)$/, 'mv $1 $2'],
1843
+ [/^del\s+(.+)$/, 'rm $1'],
1844
+ [/^tasklist\s*$/, 'ps'],
1845
+ [/^cd\s*$/, 'pwd']
1846
+ ]);
1847
+
1848
+ for (const [regex, replacement] of translations) {
1849
+ if (regex.test(translated)) {
1850
+ translated = translated.replace(regex, replacement);
1851
+ break;
1852
+ }
1853
+ }
1854
+
1855
+ return translated;
1856
+ }
1857
+
1858
+ /**
1859
+ * Resource cleanup
1860
+ * @param {string} operationId - Operation identifier
1861
+ */
1862
+ async cleanup(operationId) {
1863
+ // Clean up any hanging processes or temporary resources
1864
+ // This would be expanded based on specific needs
1865
+ }
1866
+
1867
+ /**
1868
+ * Phase 3 & 4: Start a background command
1869
+ * @param {string} command - Command to execute
1870
+ * @param {string} workingDir - Working directory
1871
+ * @param {Object} options - Execution options
1872
+ * @returns {Promise<Object>} Command info with commandId
1873
+ */
1874
+ async startBackgroundCommand(command, workingDir, options = {}) {
1875
+ const { agentId, context } = options;
1876
+
1877
+ if (!agentId) {
1878
+ throw new Error('agentId is required for background commands');
1879
+ }
1880
+
1881
+ // Check agent resource limits (only count active commands: running or waiting_for_input)
1882
+ const agentCommands = this.getAgentCommands(agentId);
1883
+ const activeAgentCommands = agentCommands.filter(cmd =>
1884
+ cmd.state === 'running' || cmd.state === 'waiting_for_input'
1885
+ );
1886
+ if (activeAgentCommands.length >= this.MAX_BACKGROUND_COMMANDS_PER_AGENT) {
1887
+ throw new Error(`Maximum background commands per agent exceeded (${this.MAX_BACKGROUND_COMMANDS_PER_AGENT})`);
1888
+ }
1889
+
1890
+ // Check global resource limits (only count active commands)
1891
+ const allCommands = Array.from(this.commandTracker.values());
1892
+ const activeGlobalCommands = allCommands.filter(cmd =>
1893
+ cmd.state === 'running' || cmd.state === 'waiting_for_input'
1894
+ );
1895
+ if (activeGlobalCommands.length >= this.MAX_BACKGROUND_COMMANDS_GLOBAL) {
1896
+ throw new Error(`Maximum global background commands exceeded (${this.MAX_BACKGROUND_COMMANDS_GLOBAL})`);
1897
+ }
1898
+
1899
+ // Generate unique command ID
1900
+ const commandId = `${agentId}-cmd-${Date.now()}-${++this.commandIdCounter}`;
1901
+
1902
+ // Translate command
1903
+ const originalCommand = command;
1904
+ let translatedCommand;
1905
+
1906
+ try {
1907
+ translatedCommand = await this.translateCommand(command, {
1908
+ agentId,
1909
+ toolsRegistry: context?.toolsRegistry,
1910
+ messageProcessor: context?.messageProcessor,
1911
+ aiService: context?.aiService,
1912
+ apiKey: context?.apiKey,
1913
+ customApiKeys: context?.customApiKeys,
1914
+ platformProvided: context?.platformProvided
1915
+ });
1916
+ } catch (error) {
1917
+ this.logger?.warn('Command translation failed, using original command', {
1918
+ originalCommand,
1919
+ error: error.message
1920
+ });
1921
+ translatedCommand = command;
1922
+ }
1923
+
1924
+ this.logger?.info(`Starting background command: ${translatedCommand}`, {
1925
+ commandId,
1926
+ agentId,
1927
+ originalCommand,
1928
+ workingDirectory: workingDir
1929
+ });
1930
+
1931
+ // Spawn process with stdin kept open
1932
+ let childProcess;
1933
+
1934
+ if (this.detectedTerminal === 'cmd' || this.detectedTerminal === 'powershell') {
1935
+ const shell = this.detectedTerminal === 'powershell' ? 'powershell' : 'cmd';
1936
+ const shellArgs = this.detectedTerminal === 'powershell'
1937
+ ? ['-Command', translatedCommand]
1938
+ : ['/c', translatedCommand];
1939
+
1940
+ childProcess = spawn(shell, shellArgs, {
1941
+ cwd: workingDir,
1942
+ env: { ...process.env },
1943
+ windowsHide: true,
1944
+ stdio: ['pipe', 'pipe', 'pipe'] // Keep stdin open
1945
+ });
1946
+ } else {
1947
+ childProcess = spawn('sh', ['-c', translatedCommand], {
1948
+ cwd: workingDir,
1949
+ env: { ...process.env },
1950
+ stdio: ['pipe', 'pipe', 'pipe'] // Keep stdin open
1951
+ });
1952
+ }
1953
+
1954
+ // Initialize command tracking
1955
+ const commandInfo = {
1956
+ commandId,
1957
+ agentId,
1958
+ pid: childProcess.pid,
1959
+ command: originalCommand,
1960
+ translatedCommand,
1961
+ workingDirectory: workingDir,
1962
+ startTime: new Date().toISOString(),
1963
+ state: 'running',
1964
+ exitCode: null,
1965
+ stdoutBuffer: '',
1966
+ stderrBuffer: '',
1967
+ lastOutputTime: Date.now(),
1968
+ promptDetected: null,
1969
+ process: childProcess
1970
+ };
1971
+
1972
+ this.commandTracker.set(commandId, commandInfo);
1973
+
1974
+ // Set up stream handlers
1975
+ childProcess.stdout.on('data', (chunk) => {
1976
+ const data = chunk.toString();
1977
+ commandInfo.stdoutBuffer += data;
1978
+ commandInfo.lastOutputTime = Date.now();
1979
+
1980
+ // Check for prompts
1981
+ if (!commandInfo.promptDetected) {
1982
+ const detection = this.promptDetector.detectPrompt(commandInfo.stdoutBuffer, 'stdout');
1983
+ if (detection) {
1984
+ commandInfo.promptDetected = detection;
1985
+ commandInfo.state = 'waiting_for_input';
1986
+ this.logger?.info('Prompt detected in background command', {
1987
+ commandId,
1988
+ agentId,
1989
+ type: detection.type,
1990
+ matchedText: detection.matchedText
1991
+ });
1992
+ }
1993
+ }
1994
+ });
1995
+
1996
+ childProcess.stderr.on('data', (chunk) => {
1997
+ const data = chunk.toString();
1998
+ commandInfo.stderrBuffer += data;
1999
+ commandInfo.lastOutputTime = Date.now();
2000
+
2001
+ // Check for prompts in stderr too
2002
+ if (!commandInfo.promptDetected) {
2003
+ const detection = this.promptDetector.detectPrompt(commandInfo.stderrBuffer, 'stderr');
2004
+ if (detection) {
2005
+ commandInfo.promptDetected = detection;
2006
+ commandInfo.state = 'waiting_for_input';
2007
+ this.logger?.info('Prompt detected in background command stderr', {
2008
+ commandId,
2009
+ agentId,
2010
+ type: detection.type,
2011
+ matchedText: detection.matchedText
2012
+ });
2013
+ }
2014
+ }
2015
+ });
2016
+
2017
+ childProcess.on('exit', (code, signal) => {
2018
+ commandInfo.exitCode = code;
2019
+ commandInfo.state = code === 0 ? 'completed' : 'failed';
2020
+ commandInfo.endTime = new Date().toISOString();
2021
+
2022
+ this.logger?.info('Background command exited', {
2023
+ commandId,
2024
+ agentId,
2025
+ exitCode: code,
2026
+ signal,
2027
+ state: commandInfo.state
2028
+ });
2029
+ });
2030
+
2031
+ childProcess.on('error', (error) => {
2032
+ commandInfo.state = 'failed';
2033
+ commandInfo.error = error.message;
2034
+ commandInfo.endTime = new Date().toISOString();
2035
+
2036
+ this.logger?.error('Background command error', {
2037
+ commandId,
2038
+ agentId,
2039
+ error: error.message
2040
+ });
2041
+ });
2042
+
2043
+ // Return command info
2044
+ return {
2045
+ success: true,
2046
+ commandId,
2047
+ pid: childProcess.pid,
2048
+ command: originalCommand,
2049
+ translatedCommand,
2050
+ workingDirectory: workingDir,
2051
+ message: `Background command started with ID: ${commandId}`
2052
+ };
2053
+ }
2054
+
2055
+ /**
2056
+ * Phase 3: Send input to a background command (stdin)
2057
+ * @param {string} commandId - Command identifier
2058
+ * @param {string} input - Input to send (will add newline automatically)
2059
+ * @param {string} agentId - Agent identifier for ownership validation
2060
+ * @returns {Object} Result
2061
+ */
2062
+ sendInput(commandId, input, agentId) {
2063
+ // Validate ownership
2064
+ const commandInfo = this.validateCommandOwnership(commandId, agentId);
2065
+
2066
+ if (commandInfo.state === 'completed' || commandInfo.state === 'failed') {
2067
+ throw new Error(`Cannot send input to ${commandInfo.state} command`);
2068
+ }
2069
+
2070
+ if (!commandInfo.process || commandInfo.process.killed) {
2071
+ throw new Error('Command process is not running');
2072
+ }
2073
+
2074
+ // Send input with newline
2075
+ const inputWithNewline = input.endsWith('\n') ? input : input + '\n';
2076
+ commandInfo.process.stdin.write(inputWithNewline);
2077
+
2078
+ this.logger?.info('Input sent to background command', {
2079
+ commandId,
2080
+ agentId,
2081
+ inputLength: input.length
2082
+ });
2083
+
2084
+ // Update state if it was waiting
2085
+ if (commandInfo.state === 'waiting_for_input') {
2086
+ commandInfo.state = 'running';
2087
+ commandInfo.promptDetected = null; // Clear prompt after answering
2088
+ }
2089
+
2090
+ return {
2091
+ success: true,
2092
+ commandId,
2093
+ message: 'Input sent successfully'
2094
+ };
2095
+ }
2096
+
2097
+ /**
2098
+ * Phase 4: Get status of a background command
2099
+ * @param {string} commandId - Command identifier
2100
+ * @param {string} agentId - Agent identifier for ownership validation
2101
+ * @returns {Object} Command status
2102
+ */
2103
+ getCommandStatus(commandId, agentId) {
2104
+ const commandInfo = this.validateCommandOwnership(commandId, agentId);
2105
+
2106
+ const timeSinceLastOutput = Date.now() - commandInfo.lastOutputTime;
2107
+
2108
+ return {
2109
+ success: true,
2110
+ commandId,
2111
+ pid: commandInfo.pid,
2112
+ command: commandInfo.command,
2113
+ state: commandInfo.state,
2114
+ exitCode: commandInfo.exitCode,
2115
+ startTime: commandInfo.startTime,
2116
+ endTime: commandInfo.endTime,
2117
+ workingDirectory: commandInfo.workingDirectory,
2118
+ stdout: commandInfo.stdoutBuffer,
2119
+ stderr: commandInfo.stderrBuffer,
2120
+ stdoutLength: commandInfo.stdoutBuffer.length,
2121
+ stderrLength: commandInfo.stderrBuffer.length,
2122
+ lastOutputTime: commandInfo.lastOutputTime,
2123
+ timeSinceLastOutput,
2124
+ promptDetected: !!commandInfo.promptDetected,
2125
+ promptInfo: commandInfo.promptDetected || undefined
2126
+ };
2127
+ }
2128
+
2129
+ /**
2130
+ * Phase 4: Kill a background command
2131
+ * @param {string} commandId - Command identifier
2132
+ * @param {string} agentId - Agent identifier for ownership validation
2133
+ * @returns {Object} Result
2134
+ */
2135
+ killCommand(commandId, agentId) {
2136
+ const commandInfo = this.validateCommandOwnership(commandId, agentId);
2137
+
2138
+ if (commandInfo.state === 'completed' || commandInfo.state === 'failed') {
2139
+ return {
2140
+ success: true,
2141
+ commandId,
2142
+ message: 'Command already terminated',
2143
+ state: commandInfo.state
2144
+ };
2145
+ }
2146
+
2147
+ if (commandInfo.process && !commandInfo.process.killed) {
2148
+ commandInfo.process.kill('SIGTERM');
2149
+
2150
+ // If SIGTERM doesn't work, try SIGKILL after 5s
2151
+ setTimeout(() => {
2152
+ if (commandInfo.process && !commandInfo.process.killed) {
2153
+ commandInfo.process.kill('SIGKILL');
2154
+ }
2155
+ }, 5000);
2156
+
2157
+ this.logger?.info('Background command killed', {
2158
+ commandId,
2159
+ agentId
2160
+ });
2161
+
2162
+ return {
2163
+ success: true,
2164
+ commandId,
2165
+ message: 'Command killed successfully'
2166
+ };
2167
+ }
2168
+
2169
+ return {
2170
+ success: false,
2171
+ commandId,
2172
+ error: 'Command process not found or already killed'
2173
+ };
2174
+ }
2175
+
2176
+ /**
2177
+ * Phase 4: List all commands for an agent
2178
+ * @param {string} agentId - Agent identifier
2179
+ * @returns {Array} List of command info objects
2180
+ */
2181
+ listAgentCommands(agentId) {
2182
+ const commands = this.getAgentCommands(agentId);
2183
+
2184
+ return commands.map(cmd => ({
2185
+ commandId: cmd.commandId,
2186
+ command: cmd.command,
2187
+ state: cmd.state,
2188
+ pid: cmd.pid,
2189
+ exitCode: cmd.exitCode,
2190
+ startTime: cmd.startTime,
2191
+ endTime: cmd.endTime,
2192
+ promptDetected: !!cmd.promptDetected,
2193
+ timeSinceLastOutput: Date.now() - cmd.lastOutputTime
2194
+ }));
2195
+ }
2196
+
2197
+ /**
2198
+ * Phase 4: Get agent's commands (helper method)
2199
+ * @param {string} agentId - Agent identifier
2200
+ * @returns {Array} Array of command info objects
2201
+ * @private
2202
+ */
2203
+ getAgentCommands(agentId) {
2204
+ return Array.from(this.commandTracker.values())
2205
+ .filter(cmd => cmd.agentId === agentId);
2206
+ }
2207
+
2208
+ /**
2209
+ * Phase 4: Validate command ownership
2210
+ * @param {string} commandId - Command identifier
2211
+ * @param {string} agentId - Agent identifier
2212
+ * @returns {Object} Command info if valid
2213
+ * @throws {Error} If command not found or access denied
2214
+ * @private
2215
+ */
2216
+ validateCommandOwnership(commandId, agentId) {
2217
+ const commandInfo = this.commandTracker.get(commandId);
2218
+
2219
+ if (!commandInfo) {
2220
+ throw new Error(`Command not found: ${commandId}`);
2221
+ }
2222
+
2223
+ if (commandInfo.agentId !== agentId) {
2224
+ throw new Error(`Access denied: Command belongs to agent ${commandInfo.agentId}`);
2225
+ }
2226
+
2227
+ return commandInfo;
2228
+ }
2229
+
2230
+ /**
2231
+ * Phase 4: Cleanup all commands for an agent (called when agent is deleted)
2232
+ * @param {string} agentId - Agent identifier
2233
+ * @returns {Promise<Object>} Cleanup result
2234
+ */
2235
+ async cleanupAgent(agentId) {
2236
+ const commands = this.getAgentCommands(agentId);
2237
+
2238
+ let killedCount = 0;
2239
+ let removedCount = 0;
2240
+
2241
+ for (const commandInfo of commands) {
2242
+ // Kill process if still running
2243
+ if (commandInfo.process && !commandInfo.process.killed) {
2244
+ commandInfo.process.kill('SIGTERM');
2245
+ killedCount++;
2246
+
2247
+ // Force kill after delay
2248
+ setTimeout(() => {
2249
+ if (commandInfo.process && !commandInfo.process.killed) {
2250
+ commandInfo.process.kill('SIGKILL');
2251
+ }
2252
+ }, 3000);
2253
+ }
2254
+
2255
+ // Remove from tracker
2256
+ this.commandTracker.delete(commandInfo.commandId);
2257
+ removedCount++;
2258
+ }
2259
+
2260
+ this.logger?.info('Agent commands cleaned up', {
2261
+ agentId,
2262
+ killedCount,
2263
+ removedCount
2264
+ });
2265
+
2266
+ return {
2267
+ success: true,
2268
+ agentId,
2269
+ killedCount,
2270
+ removedCount,
2271
+ message: `Cleaned up ${removedCount} commands for agent ${agentId}`
2272
+ };
2273
+ }
2274
+
2275
+ /**
2276
+ * Phase 4: Auto-cleanup stale completed commands
2277
+ * @returns {Object} Cleanup result
2278
+ */
2279
+ cleanupStaleCommands() {
2280
+ const now = Date.now();
2281
+ let removedCount = 0;
2282
+
2283
+ for (const [commandId, commandInfo] of this.commandTracker.entries()) {
2284
+ // Only clean up completed/failed commands
2285
+ if (commandInfo.state !== 'completed' && commandInfo.state !== 'failed') {
2286
+ continue;
2287
+ }
2288
+
2289
+ // Check age
2290
+ const startTime = new Date(commandInfo.startTime).getTime();
2291
+ const ageMinutes = (now - startTime) / 1000 / 60;
2292
+
2293
+ if (ageMinutes > this.MAX_COMMAND_AGE_MINUTES) {
2294
+ this.commandTracker.delete(commandId);
2295
+ removedCount++;
2296
+ }
2297
+ }
2298
+
2299
+ if (removedCount > 0) {
2300
+ this.logger?.info('Stale commands cleaned up', {
2301
+ removedCount,
2302
+ ageThresholdMinutes: this.MAX_COMMAND_AGE_MINUTES
2303
+ });
2304
+ }
2305
+
2306
+ return {
2307
+ success: true,
2308
+ removedCount,
2309
+ message: `Cleaned up ${removedCount} stale commands`
2310
+ };
2311
+ }
2312
+
2313
+ // ============================================
2314
+ // UI/API Methods for Terminal Task Viewing
2315
+ // ============================================
2316
+
2317
+ /**
2318
+ * Get running tasks for UI display (sanitized, no process objects)
2319
+ * @param {string} agentId - Agent identifier (optional, if null returns all)
2320
+ * @returns {Array} List of running task info for UI
2321
+ */
2322
+ getRunningTasksForUI(agentId = null) {
2323
+ const commands = agentId
2324
+ ? this.getAgentCommands(agentId)
2325
+ : Array.from(this.commandTracker.values());
2326
+
2327
+ // Filter to only running/waiting tasks and map to UI-safe format
2328
+ return commands
2329
+ .filter(cmd => cmd.state === 'running' || cmd.state === 'waiting_for_input')
2330
+ .map(cmd => ({
2331
+ commandId: cmd.commandId,
2332
+ agentId: cmd.agentId,
2333
+ command: cmd.command,
2334
+ workingDirectory: cmd.workingDirectory,
2335
+ state: cmd.state,
2336
+ pid: cmd.pid,
2337
+ startTime: cmd.startTime,
2338
+ elapsedMs: Date.now() - new Date(cmd.startTime).getTime(),
2339
+ outputSize: (cmd.stdoutBuffer?.length || 0) + (cmd.stderrBuffer?.length || 0),
2340
+ hasStderr: (cmd.stderrBuffer?.length || 0) > 0,
2341
+ promptDetected: cmd.promptDetected ? {
2342
+ type: cmd.promptDetected.type,
2343
+ description: cmd.promptDetected.description,
2344
+ matchedText: cmd.promptDetected.matchedText
2345
+ } : null,
2346
+ lastOutputTime: cmd.lastOutputTime,
2347
+ timeSinceLastOutput: Date.now() - cmd.lastOutputTime
2348
+ }))
2349
+ .sort((a, b) => new Date(b.startTime) - new Date(a.startTime)); // Newest first
2350
+ }
2351
+
2352
+ /**
2353
+ * Get task output for UI display
2354
+ * @param {string} commandId - Command identifier
2355
+ * @param {string} agentId - Agent identifier (for ownership validation, optional)
2356
+ * @param {Object} options - Output options
2357
+ * @param {number} options.tailLines - Number of lines from end (default: 100)
2358
+ * @param {boolean} options.includeStderr - Include stderr output (default: true)
2359
+ * @param {number} options.maxLength - Maximum total output length (default: 50000)
2360
+ * @returns {Object} Task output info
2361
+ */
2362
+ getTaskOutput(commandId, agentId = null, options = {}) {
2363
+ const {
2364
+ tailLines = TERMINAL_CONFIG.DEFAULT_TAIL_LINES,
2365
+ includeStderr = true,
2366
+ maxLength = TERMINAL_CONFIG.MAX_OUTPUT_LENGTH
2367
+ } = options;
2368
+
2369
+ const commandInfo = this.commandTracker.get(commandId);
2370
+
2371
+ if (!commandInfo) {
2372
+ return { success: false, error: 'Command not found' };
2373
+ }
2374
+
2375
+ // Validate ownership if agentId provided
2376
+ if (agentId && commandInfo.agentId !== agentId) {
2377
+ return { success: false, error: 'Access denied' };
2378
+ }
2379
+
2380
+ // Get stdout
2381
+ let stdout = commandInfo.stdoutBuffer || '';
2382
+ if (tailLines > 0) {
2383
+ const lines = stdout.split('\n');
2384
+ stdout = lines.slice(-tailLines).join('\n');
2385
+ }
2386
+ if (stdout.length > maxLength) {
2387
+ stdout = '... (truncated) ...\n' + stdout.slice(-maxLength);
2388
+ }
2389
+
2390
+ // Get stderr if requested
2391
+ let stderr = '';
2392
+ if (includeStderr) {
2393
+ stderr = commandInfo.stderrBuffer || '';
2394
+ if (tailLines > 0) {
2395
+ const lines = stderr.split('\n');
2396
+ stderr = lines.slice(-tailLines).join('\n');
2397
+ }
2398
+ if (stderr.length > maxLength / 2) {
2399
+ stderr = '... (truncated) ...\n' + stderr.slice(-(maxLength / 2));
2400
+ }
2401
+ }
2402
+
2403
+ return {
2404
+ success: true,
2405
+ commandId,
2406
+ agentId: commandInfo.agentId,
2407
+ command: commandInfo.command,
2408
+ workingDirectory: commandInfo.workingDirectory,
2409
+ state: commandInfo.state,
2410
+ exitCode: commandInfo.exitCode,
2411
+ startTime: commandInfo.startTime,
2412
+ endTime: commandInfo.endTime,
2413
+ stdout,
2414
+ stderr,
2415
+ totalStdoutSize: commandInfo.stdoutBuffer?.length || 0,
2416
+ totalStderrSize: commandInfo.stderrBuffer?.length || 0,
2417
+ promptDetected: commandInfo.promptDetected ? {
2418
+ type: commandInfo.promptDetected.type,
2419
+ description: commandInfo.promptDetected.description,
2420
+ matchedText: commandInfo.promptDetected.matchedText
2421
+ } : null
2422
+ };
2423
+ }
2424
+
2425
+ /**
2426
+ * Get recent tasks (including completed) for UI display
2427
+ * @param {string} agentId - Agent identifier (optional)
2428
+ * @param {number} limit - Maximum number of tasks to return (default: 20)
2429
+ * @returns {Array} List of recent tasks
2430
+ */
2431
+ getRecentTasksForUI(agentId = null, limit = TERMINAL_CONFIG.RECENT_TASKS_LIMIT) {
2432
+ const commands = agentId
2433
+ ? this.getAgentCommands(agentId)
2434
+ : Array.from(this.commandTracker.values());
2435
+
2436
+ return commands
2437
+ .map(cmd => ({
2438
+ commandId: cmd.commandId,
2439
+ agentId: cmd.agentId,
2440
+ command: cmd.command,
2441
+ workingDirectory: cmd.workingDirectory,
2442
+ state: cmd.state,
2443
+ pid: cmd.pid,
2444
+ exitCode: cmd.exitCode,
2445
+ startTime: cmd.startTime,
2446
+ endTime: cmd.endTime,
2447
+ elapsedMs: cmd.endTime
2448
+ ? new Date(cmd.endTime).getTime() - new Date(cmd.startTime).getTime()
2449
+ : Date.now() - new Date(cmd.startTime).getTime(),
2450
+ outputSize: (cmd.stdoutBuffer?.length || 0) + (cmd.stderrBuffer?.length || 0),
2451
+ hasStderr: (cmd.stderrBuffer?.length || 0) > 0,
2452
+ promptDetected: !!cmd.promptDetected
2453
+ }))
2454
+ .sort((a, b) => new Date(b.startTime) - new Date(a.startTime))
2455
+ .slice(0, limit);
2456
+ }
2457
+
2458
+ /**
2459
+ * Get terminal tasks summary (counts by state)
2460
+ * @param {string} agentId - Agent identifier (optional)
2461
+ * @returns {Object} Summary counts
2462
+ */
2463
+ getTasksSummary(agentId = null) {
2464
+ const commands = agentId
2465
+ ? this.getAgentCommands(agentId)
2466
+ : Array.from(this.commandTracker.values());
2467
+
2468
+ const summary = {
2469
+ total: commands.length,
2470
+ running: 0,
2471
+ waiting_for_input: 0,
2472
+ completed: 0,
2473
+ failed: 0
2474
+ };
2475
+
2476
+ for (const cmd of commands) {
2477
+ if (summary[cmd.state] !== undefined) {
2478
+ summary[cmd.state]++;
2479
+ }
2480
+ }
2481
+
2482
+ return summary;
2483
+ }
2484
+ }
2485
+
2486
+ export default TerminalTool;