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,1139 @@
1
+ /**
2
+ * @file tools/videoTool.js
3
+ * @description Tool for generating videos using AI models (resolved dynamically from backend)
4
+ */
5
+
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { promises as fs } from 'fs';
9
+ import { BaseTool } from './baseTool.js';
10
+
11
+ /**
12
+ * Configuration constants for video generation
13
+ * Based on Sora 1 API specifications
14
+ */
15
+ const VIDEO_CONFIG = {
16
+ DEFAULT_MODEL: null, // Resolved dynamically from modelsService via aiService
17
+ DEFAULT_WIDTH: 1080,
18
+ DEFAULT_HEIGHT: 1080,
19
+ DEFAULT_DURATION: 5, // seconds
20
+ DEFAULT_VARIANTS: 1,
21
+ // Sora 1 supported resolutions
22
+ VALID_RESOLUTIONS: [
23
+ '480x480', '480x854', '854x480', '720x720',
24
+ '720x1280', '1280x720', '1080x1080', '1080x1920', '1920x1080'
25
+ ],
26
+ MIN_DURATION: 1,
27
+ MAX_DURATION: 20, // seconds (Sora 1 limit)
28
+ MAX_VARIANTS: 4,
29
+ MAX_CONCURRENT: 2, // Sora limits concurrent jobs to 2
30
+ QUEUE_LIMIT: 5,
31
+ POLL_INTERVAL: 5000, // 5 seconds between status checks
32
+ MAX_POLL_TIME: 600000, // 10 minutes max wait
33
+ TEMP_CLEANUP_MS: 86400000, // 24 hours (videos expire after 24h anyway)
34
+ MAX_PROMPT_LENGTH: 4000,
35
+ DOWNLOAD_TIMEOUT: 300000 // 5 minutes for video download
36
+ };
37
+
38
+ /**
39
+ * VideoTool - Generate videos using Sora AI model
40
+ * Supports async job-based processing with polling
41
+ */
42
+ export class VideoTool extends BaseTool {
43
+ constructor(config = {}, logger = null) {
44
+ super(config, logger);
45
+
46
+ // Override tool ID
47
+ this.id = 'video-gen';
48
+
49
+ // Job queue and tracking
50
+ this.queue = [];
51
+ this.activeJobs = new Map(); // Currently processing jobs (max 2)
52
+ this.completedJobs = new Map();
53
+ this.isProcessing = false;
54
+
55
+ // AIService will be injected later
56
+ this.aiService = null;
57
+
58
+ // AgentPool will be injected later (for saving to conversation history)
59
+ this.agentPool = null;
60
+
61
+ // Temp directory for videos
62
+ this.tempDir = path.join(os.tmpdir(), 'loxia-videos');
63
+
64
+ // Cleanup timers
65
+ this.cleanupTimers = new Map();
66
+
67
+ // Polling timers
68
+ this.pollTimers = new Map();
69
+ }
70
+
71
+ /**
72
+ * Set AI service for video generation
73
+ * @param {AIService} aiService - AI service instance
74
+ */
75
+ setAIService(aiService) {
76
+ this.aiService = aiService;
77
+ this.logger?.info('AI Service set for VideoTool');
78
+ }
79
+
80
+ /**
81
+ * Set Agent Pool for saving results to conversation history
82
+ * @param {AgentPool} agentPool - AgentPool instance
83
+ */
84
+ setAgentPool(agentPool) {
85
+ this.agentPool = agentPool;
86
+ this.logger?.info('AgentPool set for VideoTool');
87
+ }
88
+
89
+ /**
90
+ * Get tool description for agent system prompt
91
+ * @returns {string} Formatted tool description
92
+ */
93
+ getDescription() {
94
+ return `Tool: Video Generator - Generate videos using Sora AI
95
+
96
+ **Purpose:** Generate videos from text descriptions using Sora. Videos are saved to files and displayed in chat.
97
+
98
+ **CRITICAL: Automatic Execution**
99
+ - ANY \`\`\`json block with "toolId": "video-gen" will be EXECUTED IMMEDIATELY
100
+ - Just output the command when you want to generate a video
101
+ - Video generation takes several minutes - a job ID is returned immediately
102
+
103
+ **USAGE:**
104
+ \`\`\`json
105
+ {
106
+ "toolId": "video-gen",
107
+ "parameters": {
108
+ "prompt": "Detailed description of the video",
109
+ "outputPath": "videos/filename.mp4",
110
+ "width": 1080,
111
+ "height": 1080,
112
+ "duration": 5
113
+ }
114
+ }
115
+ \`\`\`
116
+
117
+ **Parameters:**
118
+ - **prompt** (required): Detailed description of video to generate
119
+ - **outputPath** (optional): Path to save video (permanent). Omit for temp file.
120
+ - **width** (optional): Video width - 480, 720, 854, 1080, 1280, 1792, 1920 (default: 1080)
121
+ - **height** (optional): Video height - 480, 720, 854, 1080, 1280, 1792, 1920 (default: 1080)
122
+ - **duration** (optional): Duration in seconds, 1-20 (default: 5)
123
+ - **variants** (optional): Number of video variants to generate, 1-4 (default: 1)
124
+
125
+ **Valid Resolutions:** 480x480, 480x854, 854x480, 720x720, 720x1280, 1280x720, 1080x1080, 1080x1920, 1920x1080
126
+
127
+ **EXAMPLE:**
128
+ User: "create a video of a cat playing"
129
+ You output:
130
+ \`\`\`json
131
+ {
132
+ "toolId": "video-gen",
133
+ "parameters": {
134
+ "prompt": "A fluffy orange tabby cat with white paws crouches low on a sunlit hardwood floor, eyes locked on a red laser dot. The cat pounces forward, slides slightly on the polished wood, then quickly pivots to chase the dot as it darts away. Warm afternoon sunlight streams through sheer curtains, casting soft golden highlights on the cat's fur. Cozy living room with a beige sofa in the background.",
135
+ "outputPath": "videos/cat-playing.mp4",
136
+ "width": 1080,
137
+ "height": 1080,
138
+ "duration": 5
139
+ }
140
+ }
141
+ \`\`\`
142
+
143
+ **Prompt Guidelines (IMPORTANT):**
144
+ Sora simulates a physical world, so write prompts as narratives, not camera commands. Use the CAST method: describe the **Character** (appearance, clothing, posture), **Action** (break into beats like "takes three steps, pauses, looks back"), **Setting** (time of day, weather, specific objects), and **Tone/Atmosphere** (lighting quality, color palette, mood). Be specific and sensory—replace "beautiful street" with "rain-slick Tokyo asphalt reflecting neon signs." Keep prompts under 120 words, focus on ONE action per clip, and use simple camera cues only if needed ("wide shot," "close-up"). Anchor lighting explicitly ("warm golden hour sunlight with soft shadows") and name colors (teal, amber, magenta) for palette consistency. For character consistency across clips, repeat the same distinctive details (clothing colors, accessories, features) in each prompt. Avoid real people, copyrighted characters, and sensitive content.
145
+
146
+ **Notes:**
147
+ - Videos take 3-10 minutes to generate
148
+ - Max ${VIDEO_CONFIG.QUEUE_LIMIT} videos in queue, max 2 concurrent jobs
149
+ - Videos expire after 24 hours`;
150
+ }
151
+
152
+ /**
153
+ * Parse video generation parameters
154
+ * @param {string|Object} content - Raw content or parsed object
155
+ * @returns {Object} Parsed parameters
156
+ */
157
+ parseParameters(content) {
158
+ // Handle JSON format
159
+ if (typeof content === 'object' && content !== null) {
160
+ return this._parseJSONParams(content);
161
+ }
162
+
163
+ // Handle string format
164
+ if (typeof content === 'string') {
165
+ const trimmed = content.trim();
166
+
167
+ // Try to parse as JSON first
168
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
169
+ try {
170
+ const parsed = JSON.parse(trimmed);
171
+ return this._parseJSONParams(parsed);
172
+ } catch (err) {
173
+ // Not valid JSON, fall through to XML parsing
174
+ }
175
+ }
176
+
177
+ // Parse as XML
178
+ return this._parseXMLParams(content);
179
+ }
180
+
181
+ throw new Error('Invalid parameter format');
182
+ }
183
+
184
+ /**
185
+ * Parse JSON parameters
186
+ * @private
187
+ */
188
+ _parseJSONParams(obj) {
189
+ // Handle parameters wrapper (when called via toolId/parameters structure)
190
+ if (obj.parameters) {
191
+ obj = obj.parameters;
192
+ }
193
+
194
+ // Check for batch mode
195
+ if (obj.batch && Array.isArray(obj.batch)) {
196
+ return {
197
+ batch: true,
198
+ videos: obj.batch.map(vid => this._parseVideoParams(vid))
199
+ };
200
+ }
201
+
202
+ return {
203
+ batch: false,
204
+ videos: [this._parseVideoParams(obj)]
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Parse XML parameters
210
+ * @private
211
+ */
212
+ _parseXMLParams(content) {
213
+ const params = { batch: false, videos: [] };
214
+
215
+ // Check for batch mode
216
+ const batchMatch = /<batch>([\s\S]*?)<\/batch>/i.exec(content);
217
+
218
+ if (batchMatch) {
219
+ params.batch = true;
220
+ const batchContent = batchMatch[1];
221
+
222
+ // Extract individual <video> blocks
223
+ const videoRegex = /<video>([\s\S]*?)<\/video>/gi;
224
+ let match;
225
+
226
+ while ((match = videoRegex.exec(batchContent)) !== null) {
227
+ params.videos.push(this._parseXMLVideo(match[1]));
228
+ }
229
+ } else {
230
+ // Single video mode
231
+ params.videos.push(this._parseXMLVideo(content));
232
+ }
233
+
234
+ if (params.videos.length === 0) {
235
+ throw new Error('No valid video parameters found');
236
+ }
237
+
238
+ return params;
239
+ }
240
+
241
+ /**
242
+ * Parse single video parameters from object
243
+ * @private
244
+ */
245
+ _parseVideoParams(obj) {
246
+ const outputPath = obj.outputPath || obj['output-path'] || null;
247
+
248
+ return {
249
+ prompt: obj.prompt || '',
250
+ outputPath: outputPath,
251
+ saveToProject: outputPath !== null,
252
+ model: obj.model || VIDEO_CONFIG.DEFAULT_MODEL,
253
+ width: parseInt(obj.width) || VIDEO_CONFIG.DEFAULT_WIDTH,
254
+ height: parseInt(obj.height) || VIDEO_CONFIG.DEFAULT_HEIGHT,
255
+ duration: parseInt(obj.duration) || VIDEO_CONFIG.DEFAULT_DURATION,
256
+ variants: parseInt(obj.variants) || VIDEO_CONFIG.DEFAULT_VARIANTS
257
+ };
258
+ }
259
+
260
+ /**
261
+ * Parse single video parameters from XML string
262
+ * @private
263
+ */
264
+ _parseXMLVideo(xmlContent) {
265
+ const extractTag = (tag) => {
266
+ const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'i');
267
+ const match = regex.exec(xmlContent);
268
+ return match ? match[1].trim() : null;
269
+ };
270
+
271
+ const outputPath = extractTag('output-path') || null;
272
+
273
+ return {
274
+ prompt: extractTag('prompt') || '',
275
+ outputPath: outputPath,
276
+ saveToProject: outputPath !== null,
277
+ model: extractTag('model') || VIDEO_CONFIG.DEFAULT_MODEL,
278
+ width: parseInt(extractTag('width')) || VIDEO_CONFIG.DEFAULT_WIDTH,
279
+ height: parseInt(extractTag('height')) || VIDEO_CONFIG.DEFAULT_HEIGHT,
280
+ duration: parseInt(extractTag('duration')) || VIDEO_CONFIG.DEFAULT_DURATION,
281
+ variants: parseInt(extractTag('variants')) || VIDEO_CONFIG.DEFAULT_VARIANTS
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Execute video generation
287
+ * @param {Object|string} params - Parsed parameters object OR raw XML/JSON string
288
+ * @param {Object} context - Execution context
289
+ * @returns {Promise<Object>} Execution result
290
+ */
291
+ async execute(params, context = {}) {
292
+ try {
293
+ const { agentId, projectDir, directoryAccess, sessionId } = context;
294
+
295
+ // Auto-detect and parse inputs
296
+ if (typeof params === 'string') {
297
+ this.logger?.info('VideoTool: Auto-parsing string parameters');
298
+ params = this.parseParameters(params);
299
+ } else if (typeof params === 'object' && params !== null && !params.videos) {
300
+ this.logger?.info('VideoTool: Normalizing object parameters');
301
+ params = this.parseParameters(params);
302
+ }
303
+
304
+ // Validate parameters
305
+ this._validateParameters(params);
306
+
307
+ // Queue videos
308
+ const jobIds = [];
309
+
310
+ for (const videoParams of params.videos) {
311
+ // Create job
312
+ const jobId = this._generateJobId();
313
+
314
+ const job = {
315
+ jobId,
316
+ agentId,
317
+ sessionId,
318
+ prompt: videoParams.prompt,
319
+ outputPath: videoParams.outputPath,
320
+ saveToProject: videoParams.saveToProject,
321
+ model: videoParams.model,
322
+ width: videoParams.width,
323
+ height: videoParams.height,
324
+ duration: videoParams.duration,
325
+ variants: videoParams.variants,
326
+ projectDir: projectDir || process.cwd(),
327
+ directoryAccess,
328
+ status: 'queued',
329
+ soraJobId: null, // Will be set when submitted to Sora
330
+ createdAt: new Date().toISOString()
331
+ };
332
+
333
+ // Check queue limit
334
+ if (this.queue.length >= VIDEO_CONFIG.QUEUE_LIMIT) {
335
+ return {
336
+ success: false,
337
+ error: `Queue limit reached (${VIDEO_CONFIG.QUEUE_LIMIT} videos). Please wait for current jobs to complete.`,
338
+ queueLength: this.queue.length
339
+ };
340
+ }
341
+
342
+ this.queue.push(job);
343
+ jobIds.push(jobId);
344
+
345
+ this.logger?.info(`Video generation job queued: ${jobId}`, {
346
+ prompt: videoParams.prompt.substring(0, 50) + '...',
347
+ queuePosition: this.queue.length
348
+ });
349
+ }
350
+
351
+ // Start processing if not already running
352
+ if (!this.isProcessing) {
353
+ this._processQueue().catch(err => {
354
+ this.logger?.error('Queue processing error:', err);
355
+ });
356
+ }
357
+
358
+ // Return immediate response
359
+ return {
360
+ success: true,
361
+ jobIds,
362
+ queueLength: this.queue.length,
363
+ activeJobs: this.activeJobs.size,
364
+ message: params.batch
365
+ ? `${jobIds.length} videos queued for generation`
366
+ : 'Video queued for generation',
367
+ estimatedWaitTime: this._estimateWaitTime()
368
+ };
369
+
370
+ } catch (error) {
371
+ this.logger?.error('Video generation error:', error);
372
+ return {
373
+ success: false,
374
+ error: error.message
375
+ };
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Validate parameters
381
+ * @private
382
+ */
383
+ _validateParameters(params) {
384
+ if (!params.videos || params.videos.length === 0) {
385
+ throw new Error('No videos specified');
386
+ }
387
+
388
+ for (const vid of params.videos) {
389
+ if (!vid.prompt || vid.prompt.trim().length === 0) {
390
+ throw new Error('Video prompt is required');
391
+ }
392
+
393
+ if (vid.prompt.length > VIDEO_CONFIG.MAX_PROMPT_LENGTH) {
394
+ throw new Error(`Prompt too long (max ${VIDEO_CONFIG.MAX_PROMPT_LENGTH} characters)`);
395
+ }
396
+
397
+ // Validate resolution
398
+ const resolution = `${vid.width}x${vid.height}`;
399
+ if (!VIDEO_CONFIG.VALID_RESOLUTIONS.includes(resolution)) {
400
+ throw new Error(`Invalid resolution: ${resolution}. Valid resolutions: ${VIDEO_CONFIG.VALID_RESOLUTIONS.join(', ')}`);
401
+ }
402
+
403
+ // Validate duration
404
+ if (vid.duration < VIDEO_CONFIG.MIN_DURATION || vid.duration > VIDEO_CONFIG.MAX_DURATION) {
405
+ throw new Error(`Invalid duration: ${vid.duration}. Must be between ${VIDEO_CONFIG.MIN_DURATION} and ${VIDEO_CONFIG.MAX_DURATION} seconds`);
406
+ }
407
+
408
+ // Validate variants
409
+ if (vid.variants < 1 || vid.variants > VIDEO_CONFIG.MAX_VARIANTS) {
410
+ throw new Error(`Invalid variants: ${vid.variants}. Must be between 1 and ${VIDEO_CONFIG.MAX_VARIANTS}`);
411
+ }
412
+
413
+ if (vid.outputPath) {
414
+ const ext = path.extname(vid.outputPath).toLowerCase();
415
+ if (ext && ext !== '.mp4') {
416
+ throw new Error(`Invalid format: ${ext}. Only .mp4 is supported`);
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Process the video generation queue
424
+ * @private
425
+ */
426
+ async _processQueue() {
427
+ if (this.isProcessing) {
428
+ return;
429
+ }
430
+
431
+ this.isProcessing = true;
432
+
433
+ while (this.queue.length > 0 || this.activeJobs.size > 0) {
434
+ // Start new jobs if under concurrent limit
435
+ while (this.queue.length > 0 && this.activeJobs.size < VIDEO_CONFIG.MAX_CONCURRENT) {
436
+ const job = this.queue.shift();
437
+ await this._startVideoJob(job);
438
+ }
439
+
440
+ // Wait a bit before checking again
441
+ await new Promise(resolve => setTimeout(resolve, 1000));
442
+ }
443
+
444
+ this.isProcessing = false;
445
+ }
446
+
447
+ /**
448
+ * Start a video generation job
449
+ * @private
450
+ */
451
+ async _startVideoJob(job) {
452
+ this.logger?.info(`Starting video generation job: ${job.jobId}`);
453
+
454
+ try {
455
+ job.status = 'submitting';
456
+
457
+ // Check if AI service is available
458
+ if (!this.aiService) {
459
+ throw new Error('AI service not available. Video generation requires AI service.');
460
+ }
461
+
462
+ // Submit to Sora API
463
+ const options = {
464
+ width: job.width,
465
+ height: job.height,
466
+ duration: job.duration,
467
+ variants: job.variants,
468
+ sessionId: job.sessionId
469
+ };
470
+
471
+ const result = await this.aiService.generateVideo(job.prompt, options);
472
+
473
+ // Store Sora job ID
474
+ job.soraJobId = result.jobId;
475
+ job.status = 'processing';
476
+ job.submittedAt = new Date().toISOString();
477
+
478
+ // Add to active jobs
479
+ this.activeJobs.set(job.jobId, job);
480
+
481
+ this.logger?.info(`Video job submitted to Sora: ${job.soraJobId}`, {
482
+ localJobId: job.jobId
483
+ });
484
+
485
+ // Broadcast status update
486
+ this._broadcastJobStatus(job, 'processing', 'Video generation started');
487
+
488
+ // Start polling for completion
489
+ this._pollJobStatus(job);
490
+
491
+ } catch (error) {
492
+ this.logger?.error(`Failed to start video job: ${job.jobId}`, error);
493
+
494
+ job.status = 'failed';
495
+ job.error = error.message;
496
+ job.completedAt = new Date().toISOString();
497
+
498
+ this.completedJobs.set(job.jobId, job);
499
+
500
+ // Broadcast error
501
+ this._broadcastJobStatus(job, 'failed', error.message);
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Poll for job completion
507
+ * @private
508
+ */
509
+ async _pollJobStatus(job) {
510
+ const startTime = Date.now();
511
+
512
+ const poll = async () => {
513
+ try {
514
+ // Check if we've exceeded max poll time
515
+ if (Date.now() - startTime > VIDEO_CONFIG.MAX_POLL_TIME) {
516
+ throw new Error('Video generation timeout - exceeded maximum wait time');
517
+ }
518
+
519
+ // Get status from Sora - pass sessionId for API key retrieval
520
+ const status = await this.aiService.getVideoJobStatus(job.soraJobId, {
521
+ sessionId: job.sessionId,
522
+ model: job.model
523
+ });
524
+
525
+ this.logger?.debug(`Video job status: ${status.status}`, {
526
+ jobId: job.jobId,
527
+ soraJobId: job.soraJobId,
528
+ sessionId: job.sessionId
529
+ });
530
+
531
+ if (status.status === 'succeeded' || status.status === 'completed') {
532
+ // Job completed successfully
533
+ await this._handleJobComplete(job, status);
534
+ } else if (status.status === 'failed' || status.status === 'cancelled') {
535
+ // Job failed
536
+ throw new Error(status.error || 'Video generation failed');
537
+ } else {
538
+ // Still processing - poll again
539
+ const timer = setTimeout(() => poll(), VIDEO_CONFIG.POLL_INTERVAL);
540
+ this.pollTimers.set(job.jobId, timer);
541
+ }
542
+ } catch (error) {
543
+ this.logger?.error(`Video job failed: ${job.jobId}`, error);
544
+ await this._handleJobFailed(job, error);
545
+ }
546
+ };
547
+
548
+ // Start polling
549
+ poll();
550
+ }
551
+
552
+ /**
553
+ * Handle successful job completion
554
+ * @private
555
+ */
556
+ async _handleJobComplete(job, status) {
557
+ this.logger?.info(`Video generation completed: ${job.jobId}`);
558
+
559
+ try {
560
+ // Clear poll timer
561
+ const timer = this.pollTimers.get(job.jobId);
562
+ if (timer) {
563
+ clearTimeout(timer);
564
+ this.pollTimers.delete(job.jobId);
565
+ }
566
+
567
+ // Remove from active jobs
568
+ this.activeJobs.delete(job.jobId);
569
+
570
+ // Get generation ID from status - Sora returns generation ID, not direct URL
571
+ const generationId = status.generationId || status.generations?.[0]?.id;
572
+
573
+ if (!generationId) {
574
+ throw new Error('No generation ID received from Sora');
575
+ }
576
+
577
+ // Construct video download URL through our backend proxy
578
+ // The backend will authenticate with Sora and stream the video
579
+ const backendUrl = this.aiService?.baseUrl || process.env.LOXIA_BACKEND_URL || 'http://localhost:3001';
580
+ const videoUrl = `${backendUrl}/llm/video-content/${generationId}?model=${job.model || ''}`;
581
+
582
+ this.logger?.info(`Video content URL: ${videoUrl}`, { generationId });
583
+
584
+ // Resolve output path and download
585
+ const resolvedOutputPath = await this._resolveOutputPath(job);
586
+
587
+ let savedToDisk = false;
588
+ let downloadError = null;
589
+
590
+ try {
591
+ await fs.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
592
+ await this._downloadVideo(videoUrl, resolvedOutputPath, job.sessionId);
593
+ savedToDisk = true;
594
+
595
+ // Schedule cleanup if temp file
596
+ if (!job.saveToProject) {
597
+ this._scheduleCleanup(resolvedOutputPath, job.jobId);
598
+ }
599
+ } catch (err) {
600
+ downloadError = err.message;
601
+ this.logger?.warn(`Failed to save video to disk: ${err.message}`);
602
+ }
603
+
604
+ job.status = 'completed';
605
+ job.result = {
606
+ jobId: job.jobId,
607
+ soraJobId: job.soraJobId,
608
+ prompt: job.prompt,
609
+ outputPath: job.outputPath,
610
+ resolvedOutputPath: savedToDisk ? resolvedOutputPath : null,
611
+ temporaryUrl: videoUrl,
612
+ savedToDisk,
613
+ downloadError,
614
+ success: true,
615
+ model: job.model,
616
+ width: job.width,
617
+ height: job.height,
618
+ duration: job.duration,
619
+ generations: status.generations || []
620
+ };
621
+ job.completedAt = new Date().toISOString();
622
+
623
+ this.completedJobs.set(job.jobId, job);
624
+
625
+ // Broadcast success
626
+ this._broadcastJobResult(job, savedToDisk ? resolvedOutputPath : null, videoUrl, savedToDisk);
627
+
628
+ // Save to conversation history
629
+ await this._saveToConversationHistory(job, false);
630
+
631
+ } catch (error) {
632
+ this.logger?.error(`Error handling job completion: ${job.jobId}`, error);
633
+ await this._handleJobFailed(job, error);
634
+ }
635
+ }
636
+
637
+ /**
638
+ * Handle job failure
639
+ * @private
640
+ */
641
+ async _handleJobFailed(job, error) {
642
+ // Clear poll timer
643
+ const timer = this.pollTimers.get(job.jobId);
644
+ if (timer) {
645
+ clearTimeout(timer);
646
+ this.pollTimers.delete(job.jobId);
647
+ }
648
+
649
+ // Remove from active jobs
650
+ this.activeJobs.delete(job.jobId);
651
+
652
+ job.status = 'failed';
653
+ job.error = error.message;
654
+ job.completedAt = new Date().toISOString();
655
+
656
+ this.completedJobs.set(job.jobId, job);
657
+
658
+ // Broadcast error
659
+ this._broadcastJobStatus(job, 'failed', error.message);
660
+
661
+ // Save error to conversation history
662
+ await this._saveToConversationHistory(job, true);
663
+ }
664
+
665
+ /**
666
+ * Broadcast job status update
667
+ * @private
668
+ */
669
+ _broadcastJobStatus(job, status, message) {
670
+ if (global.loxiaWebServer && job.sessionId) {
671
+ global.loxiaWebServer.broadcastToSession(job.sessionId, {
672
+ type: 'videoJobStatus',
673
+ agentId: job.agentId,
674
+ jobId: job.jobId,
675
+ soraJobId: job.soraJobId,
676
+ status,
677
+ message,
678
+ prompt: job.prompt,
679
+ timestamp: new Date().toISOString()
680
+ });
681
+ }
682
+ }
683
+
684
+ /**
685
+ * Broadcast job result
686
+ * @private
687
+ */
688
+ _broadcastJobResult(job, localPath, videoUrl, savedToDisk) {
689
+ this.logger?.info('📢 Broadcasting video result', {
690
+ jobId: job.jobId,
691
+ savedToDisk,
692
+ localPath,
693
+ originalVideoUrl: videoUrl,
694
+ hasWebServer: !!global.loxiaWebServer,
695
+ sessionId: job.sessionId
696
+ });
697
+
698
+ if (global.loxiaWebServer && job.sessionId) {
699
+ // Convert to web URL if saved locally
700
+ let webUrl = videoUrl;
701
+ if (savedToDisk && localPath) {
702
+ webUrl = this._convertToWebUrl(localPath, job.sessionId);
703
+ }
704
+
705
+ global.loxiaWebServer.broadcastToSession(job.sessionId, {
706
+ type: 'videoGenerated',
707
+ agentId: job.agentId,
708
+ jobId: job.jobId,
709
+ soraJobId: job.soraJobId,
710
+ videoUrl: webUrl,
711
+ localPath,
712
+ prompt: job.prompt,
713
+ success: true,
714
+ savedToDisk,
715
+ isTemporary: !savedToDisk,
716
+ width: job.width,
717
+ height: job.height,
718
+ duration: job.duration,
719
+ timestamp: job.completedAt
720
+ });
721
+
722
+ this.logger?.info('Video generation broadcast sent', {
723
+ jobId: job.jobId,
724
+ savedToDisk
725
+ });
726
+ }
727
+ }
728
+
729
+ /**
730
+ * Save result to conversation history
731
+ * @private
732
+ */
733
+ async _saveToConversationHistory(job, isError) {
734
+ if (!this.agentPool || !job.agentId) {
735
+ return;
736
+ }
737
+
738
+ try {
739
+ const agent = await this.agentPool.getAgent(job.agentId);
740
+ if (!agent) {
741
+ return;
742
+ }
743
+
744
+ let message;
745
+
746
+ if (isError) {
747
+ message = {
748
+ id: `vid-error-${job.jobId}`,
749
+ role: 'system',
750
+ content: `Video generation failed: ${job.error}\n\n**Prompt:** ${job.prompt}`,
751
+ timestamp: job.completedAt,
752
+ type: 'error',
753
+ toolId: 'video-gen',
754
+ status: 'failed',
755
+ jobId: job.jobId
756
+ };
757
+ } else {
758
+ let content = `Video generated: ${job.prompt}`;
759
+
760
+ if (!job.result.savedToDisk) {
761
+ content += '\n\nWarning: Video is using a temporary URL (expires in ~24 hours).';
762
+ if (job.result.downloadError) {
763
+ content += `\n**Error:** ${job.result.downloadError}`;
764
+ }
765
+ }
766
+
767
+ message = {
768
+ id: `vid-result-${job.jobId}`,
769
+ role: 'assistant',
770
+ content,
771
+ timestamp: job.completedAt,
772
+ // Use persistent URL (session-independent) for conversation history
773
+ // This allows videos to work after restart
774
+ videoUrl: job.result.savedToDisk
775
+ ? this._convertToPersistentUrl(job.persistentFilename || path.basename(job.result.resolvedOutputPath))
776
+ : job.result.temporaryUrl,
777
+ // Also store filename for fallback lookups
778
+ videoFilename: job.persistentFilename || (job.result.resolvedOutputPath ? path.basename(job.result.resolvedOutputPath) : null),
779
+ type: 'video-result',
780
+ toolId: 'video-gen',
781
+ status: 'completed',
782
+ isTemporary: !job.result.savedToDisk,
783
+ savedToDisk: job.result.savedToDisk,
784
+ width: job.width,
785
+ height: job.height,
786
+ duration: job.duration
787
+ };
788
+ }
789
+
790
+ // Add to full conversation
791
+ agent.conversations.full.messages.push(message);
792
+ agent.conversations.full.lastUpdated = job.completedAt;
793
+
794
+ // Add to current model conversation if exists
795
+ if (agent.currentModel && agent.conversations[agent.currentModel]) {
796
+ agent.conversations[agent.currentModel].messages.push(message);
797
+ agent.conversations[agent.currentModel].lastUpdated = job.completedAt;
798
+ }
799
+
800
+ agent.lastActivity = job.completedAt;
801
+
802
+ await this.agentPool.persistAgentState(job.agentId);
803
+
804
+ this.logger?.info('Video result saved to conversation history', {
805
+ agentId: job.agentId,
806
+ jobId: job.jobId,
807
+ isError
808
+ });
809
+
810
+ // Queue tool result so agent "sees" the completion/failure and can continue
811
+ if (isError) {
812
+ await this.agentPool.addToolResult(job.agentId, {
813
+ toolId: 'video-gen',
814
+ status: 'failed',
815
+ error: job.error,
816
+ result: {
817
+ jobId: job.jobId,
818
+ prompt: job.prompt
819
+ },
820
+ timestamp: job.completedAt
821
+ });
822
+ } else {
823
+ await this.agentPool.addToolResult(job.agentId, {
824
+ toolId: 'video-gen',
825
+ status: 'completed',
826
+ result: {
827
+ jobId: job.jobId,
828
+ prompt: job.prompt,
829
+ videoUrl: message.videoUrl,
830
+ localPath: job.result.resolvedOutputPath,
831
+ savedToDisk: job.result.savedToDisk,
832
+ isTemporary: !job.result.savedToDisk,
833
+ width: job.width,
834
+ height: job.height,
835
+ duration: job.duration
836
+ },
837
+ timestamp: job.completedAt
838
+ });
839
+ }
840
+
841
+ this.logger?.info('Video result queued for agent processing', {
842
+ agentId: job.agentId,
843
+ jobId: job.jobId,
844
+ isError
845
+ });
846
+
847
+ } catch (error) {
848
+ this.logger?.error('Failed to save video result to conversation history', {
849
+ error: error.message,
850
+ agentId: job.agentId,
851
+ jobId: job.jobId
852
+ });
853
+ }
854
+ }
855
+
856
+ /**
857
+ * Resolve output path - save to agent's working directory for persistence
858
+ * @private
859
+ */
860
+ async _resolveOutputPath(job) {
861
+ // Use agent's working directory from directoryAccess settings
862
+ // Fall back to projectDir, then cwd
863
+ const workingDir = job.directoryAccess?.workingDirectory || job.projectDir || process.cwd();
864
+ const videosDir = path.join(workingDir, 'generated-videos');
865
+
866
+ // Create videos directory
867
+ await fs.mkdir(videosDir, { recursive: true });
868
+
869
+ let filename;
870
+ if (job.outputPath) {
871
+ // User specified a path - use it if within working directory
872
+ if (path.isAbsolute(job.outputPath)) {
873
+ // Absolute path - validate it's within working directory
874
+ const normalizedPath = path.normalize(job.outputPath);
875
+ if (!normalizedPath.startsWith(path.normalize(workingDir))) {
876
+ throw new Error('Output path must be within agent working directory');
877
+ }
878
+ // Use the full path, create parent dirs
879
+ await fs.mkdir(path.dirname(normalizedPath), { recursive: true });
880
+ job.saveToProject = true;
881
+ job.persistentFilename = path.basename(normalizedPath);
882
+ return normalizedPath;
883
+ } else {
884
+ // Relative path - resolve relative to working directory
885
+ const resolvedPath = path.normalize(path.join(workingDir, job.outputPath));
886
+ if (!resolvedPath.startsWith(path.normalize(workingDir))) {
887
+ throw new Error('Path traversal detected');
888
+ }
889
+ await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
890
+ job.saveToProject = true;
891
+ job.persistentFilename = path.basename(resolvedPath);
892
+ return resolvedPath;
893
+ }
894
+ } else {
895
+ // Generate filename from job ID in generated-videos folder
896
+ filename = `video-${job.jobId}.mp4`;
897
+ }
898
+
899
+ const resolvedPath = path.join(videosDir, filename);
900
+
901
+ this.logger?.info('📁 Video will be saved to agent directory', {
902
+ workingDir,
903
+ filename,
904
+ resolvedPath
905
+ });
906
+
907
+ // Mark job as saved to project for correct URL generation
908
+ job.saveToProject = true;
909
+ job.persistentFilename = filename;
910
+
911
+ return resolvedPath;
912
+ }
913
+
914
+ /**
915
+ * Download video from URL
916
+ * @private
917
+ */
918
+ async _downloadVideo(videoUrl, outputPath, sessionId) {
919
+ try {
920
+ // Get API key for authentication with backend
921
+ let apiKey = null;
922
+ if (this.aiService?.apiKeyManager) {
923
+ const keys = this.aiService.apiKeyManager.getKeysForRequest(null);
924
+ apiKey = keys.loxiaApiKey;
925
+ }
926
+ if (!apiKey) {
927
+ apiKey = process.env.LOXIA_API_KEY;
928
+ }
929
+
930
+ const headers = {};
931
+ if (apiKey) {
932
+ headers['Authorization'] = `Bearer ${apiKey}`;
933
+ }
934
+
935
+ this.logger?.info('📥 Downloading video', {
936
+ url: videoUrl,
937
+ hasApiKey: !!apiKey,
938
+ apiKeySource: apiKey ? (this.aiService?.apiKeyManager ? 'apiKeyManager' : 'env') : 'none',
939
+ outputPath,
940
+ sessionId
941
+ });
942
+
943
+ const response = await fetch(videoUrl, {
944
+ headers,
945
+ signal: AbortSignal.timeout(VIDEO_CONFIG.DOWNLOAD_TIMEOUT)
946
+ });
947
+
948
+ if (!response.ok) {
949
+ const errorText = await response.text().catch(() => '');
950
+ throw new Error(`Failed to download video: HTTP ${response.status} - ${errorText}`);
951
+ }
952
+
953
+ const buffer = Buffer.from(await response.arrayBuffer());
954
+ await fs.writeFile(outputPath, buffer);
955
+
956
+ this.logger?.info(`Video saved to: ${outputPath}`);
957
+
958
+ } catch (error) {
959
+ this.logger?.error('❌ Video download failed', {
960
+ errorName: error.name,
961
+ errorMessage: error.message,
962
+ url: videoUrl,
963
+ outputPath
964
+ });
965
+
966
+ if (error.name === 'TimeoutError') {
967
+ throw new Error('Video download timeout');
968
+ } else if (error.name === 'TypeError') {
969
+ throw new Error(`Network error: ${error.message}`);
970
+ } else {
971
+ throw new Error(`Download failed: ${error.message}`);
972
+ }
973
+ }
974
+ }
975
+
976
+ /**
977
+ * Schedule cleanup of temp file
978
+ * @private
979
+ */
980
+ _scheduleCleanup(filePath, jobId) {
981
+ const timer = setTimeout(async () => {
982
+ try {
983
+ await fs.unlink(filePath);
984
+ this.logger?.debug(`Cleaned up temp video: ${filePath}`);
985
+ this.cleanupTimers.delete(jobId);
986
+ } catch (error) {
987
+ // File might already be deleted, ignore
988
+ }
989
+ }, VIDEO_CONFIG.TEMP_CLEANUP_MS);
990
+
991
+ this.cleanupTimers.set(jobId, timer);
992
+ }
993
+
994
+ /**
995
+ * Convert local file path to web-accessible URL
996
+ * @private
997
+ */
998
+ _convertToWebUrl(localPath, sessionId) {
999
+ const filename = path.basename(localPath);
1000
+ const port = global.loxiaWebServer?.port || 8080;
1001
+ let host = global.loxiaWebServer?.host || 'localhost';
1002
+
1003
+ if (host === '0.0.0.0') {
1004
+ host = 'localhost';
1005
+ }
1006
+
1007
+ const webUrl = `http://${host}:${port}/api/videos/${sessionId}/${filename}`;
1008
+
1009
+ this.logger?.info('🔗 Converting local path to web URL', {
1010
+ localPath,
1011
+ sessionId,
1012
+ webUrl
1013
+ });
1014
+
1015
+ return webUrl;
1016
+ }
1017
+
1018
+ /**
1019
+ * Convert filename to persistent (session-independent) URL
1020
+ * These URLs work after browser refresh and system restart
1021
+ * @private
1022
+ */
1023
+ _convertToPersistentUrl(filename) {
1024
+ const port = global.loxiaWebServer?.port || 8080;
1025
+ let host = global.loxiaWebServer?.host || 'localhost';
1026
+
1027
+ if (host === '0.0.0.0') {
1028
+ host = 'localhost';
1029
+ }
1030
+
1031
+ // Use the session-independent endpoint that searches all agent directories
1032
+ const persistentUrl = `http://${host}:${port}/api/generated-videos/${filename}`;
1033
+
1034
+ this.logger?.info('🔗 Generated persistent URL for video', {
1035
+ filename,
1036
+ persistentUrl
1037
+ });
1038
+
1039
+ return persistentUrl;
1040
+ }
1041
+
1042
+ /**
1043
+ * Estimate wait time based on queue
1044
+ * @private
1045
+ */
1046
+ _estimateWaitTime() {
1047
+ const avgGenerationTime = 300; // 5 minutes in seconds
1048
+ const queuePosition = this.queue.length;
1049
+ const activeJobs = this.activeJobs.size;
1050
+
1051
+ if (queuePosition === 0 && activeJobs === 0) {
1052
+ return '~5 minutes';
1053
+ }
1054
+
1055
+ // Calculate based on queue position and concurrent limit
1056
+ const waitingJobs = queuePosition + activeJobs;
1057
+ const batches = Math.ceil(waitingJobs / VIDEO_CONFIG.MAX_CONCURRENT);
1058
+ const estimatedSeconds = batches * avgGenerationTime;
1059
+
1060
+ const minutes = Math.floor(estimatedSeconds / 60);
1061
+
1062
+ if (minutes >= 60) {
1063
+ const hours = Math.floor(minutes / 60);
1064
+ const remainingMinutes = minutes % 60;
1065
+ return `~${hours}h ${remainingMinutes}m`;
1066
+ }
1067
+
1068
+ return `~${minutes} minutes`;
1069
+ }
1070
+
1071
+ /**
1072
+ * Generate unique job ID
1073
+ * @private
1074
+ */
1075
+ _generateJobId() {
1076
+ return `vid-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1077
+ }
1078
+
1079
+ /**
1080
+ * Get job status
1081
+ * @param {string} jobId - Job ID
1082
+ * @returns {Object} Job status
1083
+ */
1084
+ getJobStatus(jobId) {
1085
+ // Check completed jobs
1086
+ if (this.completedJobs.has(jobId)) {
1087
+ return this.completedJobs.get(jobId);
1088
+ }
1089
+
1090
+ // Check active jobs
1091
+ if (this.activeJobs.has(jobId)) {
1092
+ return this.activeJobs.get(jobId);
1093
+ }
1094
+
1095
+ // Check queue
1096
+ const queuedJob = this.queue.find(job => job.jobId === jobId);
1097
+ if (queuedJob) {
1098
+ return queuedJob;
1099
+ }
1100
+
1101
+ return {
1102
+ jobId,
1103
+ status: 'not_found'
1104
+ };
1105
+ }
1106
+
1107
+ /**
1108
+ * Cleanup on shutdown
1109
+ */
1110
+ async cleanup() {
1111
+ this.logger?.info('Shutting down VideoTool');
1112
+
1113
+ // Clear all cleanup timers
1114
+ for (const timer of this.cleanupTimers.values()) {
1115
+ clearTimeout(timer);
1116
+ }
1117
+ this.cleanupTimers.clear();
1118
+
1119
+ // Clear all poll timers
1120
+ for (const timer of this.pollTimers.values()) {
1121
+ clearTimeout(timer);
1122
+ }
1123
+ this.pollTimers.clear();
1124
+
1125
+ // Mark queued jobs as cancelled
1126
+ for (const job of this.queue) {
1127
+ job.status = 'cancelled';
1128
+ }
1129
+ this.queue = [];
1130
+
1131
+ // Mark active jobs as cancelled
1132
+ for (const job of this.activeJobs.values()) {
1133
+ job.status = 'cancelled';
1134
+ }
1135
+ this.activeJobs.clear();
1136
+ }
1137
+ }
1138
+
1139
+ export default VideoTool;