@myrialabs/clopen 0.0.1

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 (456) hide show
  1. package/.env.example +6 -0
  2. package/.github/workflows/release.yml +60 -0
  3. package/.github/workflows/test.yml +40 -0
  4. package/CONTRIBUTING.md +499 -0
  5. package/LICENSE +21 -0
  6. package/README.md +209 -0
  7. package/backend/index.ts +156 -0
  8. package/backend/lib/chat/helpers.ts +42 -0
  9. package/backend/lib/chat/index.ts +2 -0
  10. package/backend/lib/chat/stream-manager.ts +1126 -0
  11. package/backend/lib/database/README.md +77 -0
  12. package/backend/lib/database/index.ts +119 -0
  13. package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
  14. package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
  15. package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
  16. package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
  17. package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
  18. package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
  19. package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
  20. package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
  21. package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
  22. package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
  23. package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
  24. package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
  25. package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
  26. package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
  27. package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
  28. package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
  29. package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
  30. package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
  31. package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
  32. package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
  33. package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
  34. package/backend/lib/database/migrations/index.ts +154 -0
  35. package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
  36. package/backend/lib/database/queries/engine-queries.ts +75 -0
  37. package/backend/lib/database/queries/index.ts +9 -0
  38. package/backend/lib/database/queries/message-queries.ts +472 -0
  39. package/backend/lib/database/queries/project-queries.ts +117 -0
  40. package/backend/lib/database/queries/session-queries.ts +271 -0
  41. package/backend/lib/database/queries/settings-queries.ts +34 -0
  42. package/backend/lib/database/queries/snapshot-queries.ts +326 -0
  43. package/backend/lib/database/queries/utils-queries.ts +59 -0
  44. package/backend/lib/database/seeders/index.ts +13 -0
  45. package/backend/lib/database/seeders/settings_seeder.ts +84 -0
  46. package/backend/lib/database/utils/connection.ts +174 -0
  47. package/backend/lib/database/utils/index.ts +4 -0
  48. package/backend/lib/database/utils/migration-runner.ts +118 -0
  49. package/backend/lib/database/utils/seeder-runner.ts +121 -0
  50. package/backend/lib/engine/adapters/claude/environment.ts +164 -0
  51. package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
  52. package/backend/lib/engine/adapters/claude/index.ts +1 -0
  53. package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
  54. package/backend/lib/engine/adapters/claude/stream.ts +177 -0
  55. package/backend/lib/engine/adapters/opencode/index.ts +2 -0
  56. package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
  57. package/backend/lib/engine/adapters/opencode/server.ts +104 -0
  58. package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
  59. package/backend/lib/engine/index.ts +196 -0
  60. package/backend/lib/engine/types.ts +58 -0
  61. package/backend/lib/files/file-operations.ts +478 -0
  62. package/backend/lib/files/file-reading.ts +308 -0
  63. package/backend/lib/files/file-watcher.ts +383 -0
  64. package/backend/lib/files/path-browsing.ts +382 -0
  65. package/backend/lib/git/git-executor.ts +88 -0
  66. package/backend/lib/git/git-parser.ts +411 -0
  67. package/backend/lib/git/git-service.ts +505 -0
  68. package/backend/lib/mcp/README.md +1144 -0
  69. package/backend/lib/mcp/config.ts +316 -0
  70. package/backend/lib/mcp/index.ts +35 -0
  71. package/backend/lib/mcp/project-context.ts +236 -0
  72. package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
  73. package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
  74. package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
  75. package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
  76. package/backend/lib/mcp/servers/helper.ts +143 -0
  77. package/backend/lib/mcp/servers/index.ts +45 -0
  78. package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
  79. package/backend/lib/mcp/servers/weather/index.ts +31 -0
  80. package/backend/lib/mcp/stdio-server.ts +103 -0
  81. package/backend/lib/mcp/types.ts +65 -0
  82. package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
  83. package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
  84. package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
  85. package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
  86. package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
  87. package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
  88. package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
  89. package/backend/lib/preview/browser/browser-pool.ts +357 -0
  90. package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
  91. package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
  92. package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
  93. package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
  94. package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
  95. package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
  96. package/backend/lib/preview/browser/types.ts +359 -0
  97. package/backend/lib/preview/index.ts +24 -0
  98. package/backend/lib/project/index.ts +2 -0
  99. package/backend/lib/project/status-manager.ts +182 -0
  100. package/backend/lib/shared/index.ts +2 -0
  101. package/backend/lib/shared/port-utils.ts +25 -0
  102. package/backend/lib/shared/process-manager.ts +281 -0
  103. package/backend/lib/snapshot/blob-store.ts +227 -0
  104. package/backend/lib/snapshot/gitignore.ts +307 -0
  105. package/backend/lib/snapshot/helpers.ts +397 -0
  106. package/backend/lib/snapshot/snapshot-service.ts +483 -0
  107. package/backend/lib/terminal/helpers.ts +14 -0
  108. package/backend/lib/terminal/index.ts +8 -0
  109. package/backend/lib/terminal/pty-manager.ts +4 -0
  110. package/backend/lib/terminal/pty-session-manager.ts +387 -0
  111. package/backend/lib/terminal/shell-utils.ts +313 -0
  112. package/backend/lib/terminal/stream-manager.ts +293 -0
  113. package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
  114. package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
  115. package/backend/lib/user/helpers.ts +87 -0
  116. package/backend/lib/utils/ws.ts +944 -0
  117. package/backend/lib/vite-dev.ts +353 -0
  118. package/backend/middleware/cors.ts +15 -0
  119. package/backend/middleware/error-handler.ts +49 -0
  120. package/backend/middleware/logger.ts +9 -0
  121. package/backend/types/api.ts +24 -0
  122. package/backend/ws/README.md +1505 -0
  123. package/backend/ws/chat/background.ts +198 -0
  124. package/backend/ws/chat/index.ts +21 -0
  125. package/backend/ws/chat/stream.ts +707 -0
  126. package/backend/ws/engine/claude/accounts.ts +401 -0
  127. package/backend/ws/engine/claude/index.ts +13 -0
  128. package/backend/ws/engine/claude/status.ts +43 -0
  129. package/backend/ws/engine/index.ts +14 -0
  130. package/backend/ws/engine/opencode/index.ts +11 -0
  131. package/backend/ws/engine/opencode/status.ts +30 -0
  132. package/backend/ws/engine/utils.ts +36 -0
  133. package/backend/ws/files/index.ts +30 -0
  134. package/backend/ws/files/read.ts +189 -0
  135. package/backend/ws/files/search.ts +453 -0
  136. package/backend/ws/files/watch.ts +124 -0
  137. package/backend/ws/files/write.ts +143 -0
  138. package/backend/ws/git/branch.ts +106 -0
  139. package/backend/ws/git/commit.ts +39 -0
  140. package/backend/ws/git/conflict.ts +68 -0
  141. package/backend/ws/git/diff.ts +69 -0
  142. package/backend/ws/git/index.ts +24 -0
  143. package/backend/ws/git/log.ts +41 -0
  144. package/backend/ws/git/remote.ts +214 -0
  145. package/backend/ws/git/staging.ts +84 -0
  146. package/backend/ws/git/status.ts +90 -0
  147. package/backend/ws/index.ts +69 -0
  148. package/backend/ws/mcp/index.ts +61 -0
  149. package/backend/ws/messages/crud.ts +74 -0
  150. package/backend/ws/messages/index.ts +14 -0
  151. package/backend/ws/preview/browser/cleanup.ts +129 -0
  152. package/backend/ws/preview/browser/console.ts +114 -0
  153. package/backend/ws/preview/browser/interact.ts +513 -0
  154. package/backend/ws/preview/browser/mcp.ts +129 -0
  155. package/backend/ws/preview/browser/native-ui.ts +235 -0
  156. package/backend/ws/preview/browser/stats.ts +55 -0
  157. package/backend/ws/preview/browser/tab-info.ts +126 -0
  158. package/backend/ws/preview/browser/tab.ts +166 -0
  159. package/backend/ws/preview/browser/webcodecs.ts +293 -0
  160. package/backend/ws/preview/index.ts +146 -0
  161. package/backend/ws/projects/crud.ts +113 -0
  162. package/backend/ws/projects/index.ts +25 -0
  163. package/backend/ws/projects/presence.ts +46 -0
  164. package/backend/ws/projects/status.ts +116 -0
  165. package/backend/ws/sessions/crud.ts +327 -0
  166. package/backend/ws/sessions/index.ts +33 -0
  167. package/backend/ws/settings/crud.ts +112 -0
  168. package/backend/ws/settings/index.ts +14 -0
  169. package/backend/ws/snapshot/index.ts +17 -0
  170. package/backend/ws/snapshot/restore.ts +173 -0
  171. package/backend/ws/snapshot/timeline.ts +141 -0
  172. package/backend/ws/system/index.ts +14 -0
  173. package/backend/ws/system/operations.ts +49 -0
  174. package/backend/ws/terminal/index.ts +40 -0
  175. package/backend/ws/terminal/persistence.ts +153 -0
  176. package/backend/ws/terminal/session.ts +382 -0
  177. package/backend/ws/terminal/stream.ts +79 -0
  178. package/backend/ws/tunnel/index.ts +14 -0
  179. package/backend/ws/tunnel/operations.ts +91 -0
  180. package/backend/ws/types.ts +20 -0
  181. package/backend/ws/user/crud.ts +156 -0
  182. package/backend/ws/user/index.ts +14 -0
  183. package/bin/clopen.ts +307 -0
  184. package/bun.lock +1352 -0
  185. package/frontend/App.svelte +34 -0
  186. package/frontend/app.css +313 -0
  187. package/frontend/lib/app-environment.ts +10 -0
  188. package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
  189. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
  190. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
  191. package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
  192. package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
  193. package/frontend/lib/components/chat/formatters/index.ts +3 -0
  194. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
  195. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
  196. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
  197. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
  198. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
  199. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
  200. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
  201. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
  202. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
  203. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
  204. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
  205. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
  206. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
  207. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
  208. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
  209. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
  210. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
  211. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
  212. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
  213. package/frontend/lib/components/chat/shared/index.ts +2 -0
  214. package/frontend/lib/components/chat/shared/utils.ts +116 -0
  215. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
  216. package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
  217. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
  218. package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
  219. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
  220. package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
  221. package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
  222. package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
  223. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
  224. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
  225. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
  226. package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
  227. package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
  228. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
  229. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
  230. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
  231. package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
  232. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
  233. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
  234. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
  235. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
  236. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
  237. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
  238. package/frontend/lib/components/chat/tools/components/index.ts +7 -0
  239. package/frontend/lib/components/chat/tools/index.ts +26 -0
  240. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
  241. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
  242. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
  243. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
  244. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
  245. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
  246. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
  247. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
  248. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
  249. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
  250. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
  251. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
  252. package/frontend/lib/components/common/Alert.svelte +139 -0
  253. package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
  254. package/frontend/lib/components/common/Button.svelte +71 -0
  255. package/frontend/lib/components/common/Card.svelte +102 -0
  256. package/frontend/lib/components/common/Checkbox.svelte +48 -0
  257. package/frontend/lib/components/common/Dialog.svelte +249 -0
  258. package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
  259. package/frontend/lib/components/common/Icon.svelte +58 -0
  260. package/frontend/lib/components/common/Input.svelte +72 -0
  261. package/frontend/lib/components/common/Lightbox.svelte +233 -0
  262. package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
  263. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
  264. package/frontend/lib/components/common/Modal.svelte +177 -0
  265. package/frontend/lib/components/common/ModalProvider.svelte +28 -0
  266. package/frontend/lib/components/common/ModelSelector.svelte +110 -0
  267. package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
  268. package/frontend/lib/components/common/NotificationToast.svelte +113 -0
  269. package/frontend/lib/components/common/PageTemplate.svelte +76 -0
  270. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
  271. package/frontend/lib/components/common/Select.svelte +98 -0
  272. package/frontend/lib/components/common/Textarea.svelte +80 -0
  273. package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
  274. package/frontend/lib/components/common/lucide-icons.ts +1642 -0
  275. package/frontend/lib/components/common/material-icons.ts +1082 -0
  276. package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
  277. package/frontend/lib/components/common/xterm/index.ts +16 -0
  278. package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
  279. package/frontend/lib/components/common/xterm/types.ts +31 -0
  280. package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
  281. package/frontend/lib/components/files/FileNode.svelte +384 -0
  282. package/frontend/lib/components/files/FileTree.svelte +681 -0
  283. package/frontend/lib/components/files/FileViewer.svelte +728 -0
  284. package/frontend/lib/components/files/SearchResults.svelte +303 -0
  285. package/frontend/lib/components/git/BranchManager.svelte +458 -0
  286. package/frontend/lib/components/git/ChangesSection.svelte +107 -0
  287. package/frontend/lib/components/git/CommitForm.svelte +76 -0
  288. package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
  289. package/frontend/lib/components/git/DiffViewer.svelte +364 -0
  290. package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
  291. package/frontend/lib/components/git/GitButton.svelte +33 -0
  292. package/frontend/lib/components/git/GitLog.svelte +361 -0
  293. package/frontend/lib/components/git/GitModal.svelte +80 -0
  294. package/frontend/lib/components/history/HistoryModal.svelte +563 -0
  295. package/frontend/lib/components/history/HistoryView.svelte +615 -0
  296. package/frontend/lib/components/index.ts +34 -0
  297. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
  298. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
  299. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
  300. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
  301. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
  302. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
  303. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
  304. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
  305. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
  306. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
  307. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
  308. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
  309. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
  310. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
  311. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
  312. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
  313. package/frontend/lib/components/preview/index.ts +2 -0
  314. package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
  315. package/frontend/lib/components/settings/SettingsView.svelte +36 -0
  316. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
  317. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
  318. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
  319. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
  320. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
  321. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
  322. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
  323. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
  324. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
  325. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
  326. package/frontend/lib/components/terminal/Terminal.svelte +368 -0
  327. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
  328. package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
  329. package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
  330. package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
  331. package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
  332. package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
  333. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
  334. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
  335. package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
  336. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
  337. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
  338. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
  339. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
  340. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
  341. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
  342. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
  343. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
  344. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
  345. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
  346. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
  347. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
  348. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
  349. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
  350. package/frontend/lib/constants/preview.ts +45 -0
  351. package/frontend/lib/services/chat/chat.service.ts +704 -0
  352. package/frontend/lib/services/chat/index.ts +7 -0
  353. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
  354. package/frontend/lib/services/notification/index.ts +8 -0
  355. package/frontend/lib/services/notification/push.service.ts +144 -0
  356. package/frontend/lib/services/notification/sound.service.ts +127 -0
  357. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
  358. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
  359. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
  360. package/frontend/lib/services/preview/index.ts +23 -0
  361. package/frontend/lib/services/project/index.ts +8 -0
  362. package/frontend/lib/services/project/status.service.ts +159 -0
  363. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
  364. package/frontend/lib/services/terminal/background/index.ts +130 -0
  365. package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
  366. package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
  367. package/frontend/lib/services/terminal/index.ts +14 -0
  368. package/frontend/lib/services/terminal/persistence.service.ts +260 -0
  369. package/frontend/lib/services/terminal/project.service.ts +953 -0
  370. package/frontend/lib/services/terminal/session.service.ts +364 -0
  371. package/frontend/lib/services/terminal/terminal.service.ts +369 -0
  372. package/frontend/lib/stores/core/app.svelte.ts +117 -0
  373. package/frontend/lib/stores/core/files.svelte.ts +73 -0
  374. package/frontend/lib/stores/core/presence.svelte.ts +48 -0
  375. package/frontend/lib/stores/core/projects.svelte.ts +317 -0
  376. package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
  377. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
  378. package/frontend/lib/stores/features/models.svelte.ts +89 -0
  379. package/frontend/lib/stores/features/settings.svelte.ts +87 -0
  380. package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
  381. package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
  382. package/frontend/lib/stores/features/user.svelte.ts +96 -0
  383. package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
  384. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
  385. package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
  386. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
  387. package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
  388. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
  389. package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
  390. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
  391. package/frontend/lib/types/native-ui.ts +73 -0
  392. package/frontend/lib/utils/chat/date-separator.ts +39 -0
  393. package/frontend/lib/utils/chat/message-grouper.ts +219 -0
  394. package/frontend/lib/utils/chat/message-processor.ts +135 -0
  395. package/frontend/lib/utils/chat/tool-handler.ts +161 -0
  396. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
  397. package/frontend/lib/utils/click-outside.ts +20 -0
  398. package/frontend/lib/utils/context-manager.ts +257 -0
  399. package/frontend/lib/utils/file-icon-mappings.ts +769 -0
  400. package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
  401. package/frontend/lib/utils/git-status.ts +68 -0
  402. package/frontend/lib/utils/platform.ts +113 -0
  403. package/frontend/lib/utils/port-check.ts +65 -0
  404. package/frontend/lib/utils/terminalFormatter.ts +207 -0
  405. package/frontend/lib/utils/theme.ts +6 -0
  406. package/frontend/lib/utils/tree-visualizer.ts +320 -0
  407. package/frontend/lib/utils/ws.ts +44 -0
  408. package/frontend/main.ts +13 -0
  409. package/index.html +70 -0
  410. package/package.json +111 -0
  411. package/scripts/generate-icons.ts +87 -0
  412. package/scripts/pre-publish-check.sh +142 -0
  413. package/scripts/setup-hooks.sh +134 -0
  414. package/scripts/validate-branch-name.sh +47 -0
  415. package/scripts/validate-commit-msg.sh +42 -0
  416. package/shared/constants/engines.ts +134 -0
  417. package/shared/types/database/connection.ts +16 -0
  418. package/shared/types/database/index.ts +6 -0
  419. package/shared/types/database/schema.ts +141 -0
  420. package/shared/types/engine/index.ts +45 -0
  421. package/shared/types/filesystem/index.ts +22 -0
  422. package/shared/types/git.ts +171 -0
  423. package/shared/types/messaging/index.ts +239 -0
  424. package/shared/types/messaging/tool.ts +526 -0
  425. package/shared/types/network/api.ts +18 -0
  426. package/shared/types/network/index.ts +5 -0
  427. package/shared/types/stores/app.ts +23 -0
  428. package/shared/types/stores/dialog.ts +21 -0
  429. package/shared/types/stores/index.ts +3 -0
  430. package/shared/types/stores/settings.ts +15 -0
  431. package/shared/types/terminal/index.ts +44 -0
  432. package/shared/types/ui/components.ts +61 -0
  433. package/shared/types/ui/icons.ts +23 -0
  434. package/shared/types/ui/index.ts +22 -0
  435. package/shared/types/ui/notifications.ts +14 -0
  436. package/shared/types/ui/theme.ts +12 -0
  437. package/shared/types/websocket/index.ts +43 -0
  438. package/shared/types/window.d.ts +13 -0
  439. package/shared/utils/anonymous-user.ts +168 -0
  440. package/shared/utils/async.ts +10 -0
  441. package/shared/utils/diff-calculator.ts +184 -0
  442. package/shared/utils/file-type-detection.ts +166 -0
  443. package/shared/utils/logger.ts +158 -0
  444. package/shared/utils/message-formatter.ts +79 -0
  445. package/shared/utils/path.ts +47 -0
  446. package/shared/utils/ws-client.ts +768 -0
  447. package/shared/utils/ws-server.ts +660 -0
  448. package/static/audio/notification.ogg +0 -0
  449. package/static/favicon.svg +8 -0
  450. package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
  451. package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
  452. package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
  453. package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
  454. package/static/fonts/dm-sans.css +96 -0
  455. package/svelte.config.js +20 -0
  456. package/vite.config.ts +33 -0
@@ -0,0 +1,882 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { Page } from 'puppeteer-core';
3
+ import { BrowserTabManager } from './browser-tab-manager.js';
4
+ import { BrowserConsoleManager } from './browser-console-manager.js';
5
+ import { BrowserInteractionHandler } from './browser-interaction-handler.js';
6
+ import { BrowserNavigationTracker } from './browser-navigation-tracker.js';
7
+ import { BrowserVideoCapture } from './browser-video-capture.js';
8
+ import { BrowserDialogHandler } from './browser-dialog-handler.js';
9
+ import { BrowserNativeUIHandler } from './browser-native-ui-handler.js';
10
+ import { browserMcpControl } from './browser-mcp-control.js';
11
+ import { ws } from '$backend/lib/utils/ws';
12
+ import { debug } from '$shared/utils/logger';
13
+ import type {
14
+ BrowserTab,
15
+ BrowserTabInfo,
16
+ BrowserConsoleMessage,
17
+ BrowserAutonomousAction,
18
+ DeviceSize,
19
+ Rotation,
20
+ BrowserDialogResponse,
21
+ BrowserSelectResponse,
22
+ BrowserContextMenuResponse,
23
+ BrowserContextMenuInfo
24
+ } from './types';
25
+
26
+ /**
27
+ * Browser Preview Service
28
+ *
29
+ * Main orchestrator for browser preview functionality.
30
+ * Tab-centric architecture - all operations work with tabs.
31
+ *
32
+ * Architecture:
33
+ * - Tabs are the primary unit (no separate session concept)
34
+ * - Each tab = isolated browser context + page
35
+ * - Event-driven communication with frontend
36
+ * - Manages all browser operations: streaming, interaction, console, etc.
37
+ * - **PROJECT ISOLATION**: Each instance is isolated per project
38
+ */
39
+ export class BrowserPreviewService extends EventEmitter {
40
+ private tabManager: BrowserTabManager;
41
+ private consoleManager: BrowserConsoleManager;
42
+ private interactionHandler: BrowserInteractionHandler;
43
+ private navigationTracker: BrowserNavigationTracker;
44
+ private videoCapture: BrowserVideoCapture;
45
+ private dialogHandler: BrowserDialogHandler;
46
+ private nativeUIHandler: BrowserNativeUIHandler;
47
+
48
+ // Store context menu info for later action execution
49
+ private contextMenus = new Map<string, BrowserContextMenuInfo>();
50
+
51
+ // Project ID for isolation (REQUIRED)
52
+ private projectId: string;
53
+
54
+ constructor(projectId: string) {
55
+ super();
56
+
57
+ if (!projectId) {
58
+ throw new Error('projectId is required for BrowserPreviewService');
59
+ }
60
+
61
+ this.projectId = projectId;
62
+
63
+ // Initialize managers with projectId for isolation
64
+ this.tabManager = new BrowserTabManager(projectId);
65
+ this.consoleManager = new BrowserConsoleManager();
66
+ this.interactionHandler = new BrowserInteractionHandler();
67
+ this.navigationTracker = new BrowserNavigationTracker();
68
+ this.videoCapture = new BrowserVideoCapture();
69
+ this.dialogHandler = new BrowserDialogHandler();
70
+ this.nativeUIHandler = new BrowserNativeUIHandler();
71
+
72
+ // Forward events from handlers to main service
73
+ this.setupEventForwarding();
74
+ }
75
+
76
+ private setupEventForwarding() {
77
+ // Forward console events
78
+ this.consoleManager.on('console-message', (data) => {
79
+ this.emit('preview:browser-console-message', data);
80
+ });
81
+ this.consoleManager.on('console-clear', (data) => {
82
+ this.emit('preview:browser-console-clear', data);
83
+ });
84
+
85
+ // Forward interaction events (MCP cursor)
86
+ this.interactionHandler.on('cursor-position', (data) => {
87
+ this.emit('preview:browser-mcp-cursor-position', data);
88
+ });
89
+ this.interactionHandler.on('cursor-click', (data) => {
90
+ this.emit('preview:browser-mcp-cursor-click', data);
91
+ });
92
+ this.interactionHandler.on('test-completed', (data) => {
93
+ this.emit('preview:browser-mcp-test-completed', data);
94
+ });
95
+
96
+ // Forward navigation events and handle video streaming restart
97
+ this.navigationTracker.on('navigation', async (data) => {
98
+ this.emit('preview:browser-navigation', data);
99
+
100
+ // After navigation completes, restart video streaming for the tab
101
+ // This re-injects the peer script and restarts CDP screencast
102
+ const { sessionId } = data;
103
+ if (this.videoCapture.isStreaming(sessionId)) {
104
+ const tab = this.getTab(sessionId);
105
+ if (tab) {
106
+ // Small delay to ensure page is fully loaded
107
+ setTimeout(async () => {
108
+ try {
109
+ const success = await this.videoCapture.handleNavigation(sessionId, tab);
110
+ if (success) {
111
+ this.emit('preview:browser-navigation-streaming-ready', { sessionId });
112
+ }
113
+ } catch (error) {
114
+ // Silently fail - frontend will request refresh if needed
115
+ }
116
+ }, 100);
117
+ }
118
+ }
119
+ });
120
+
121
+ // Forward navigation loading events
122
+ this.navigationTracker.on('navigation-loading', (data) => {
123
+ this.emit('preview:browser-navigation-loading', data);
124
+ });
125
+
126
+ // Forward new window events
127
+ this.tabManager.on('new-window', (data) => {
128
+ this.emit('preview:browser-new-window', data);
129
+ });
130
+
131
+ // Forward tab events (already have correct event names from tab manager)
132
+ this.tabManager.on('preview:browser-tab-opened', (data) => {
133
+ this.emit('preview:browser-tab-opened', data);
134
+ });
135
+ this.tabManager.on('preview:browser-tab-closed', (data) => {
136
+ this.emit('preview:browser-tab-closed', data);
137
+ });
138
+ this.tabManager.on('preview:browser-tab-switched', (data) => {
139
+ this.emit('preview:browser-tab-switched', data);
140
+ });
141
+ this.tabManager.on('preview:browser-tab-navigated', (data) => {
142
+ this.emit('preview:browser-tab-navigated', data);
143
+ });
144
+
145
+ // Forward video capture events
146
+ this.videoCapture.on('ice-candidate', (data) => {
147
+ this.emit('preview:browser-webcodecs-ice-candidate', data);
148
+ });
149
+ this.videoCapture.on('connection-state', (data) => {
150
+ this.emit('preview:browser-webcodecs-connection-state', data);
151
+ });
152
+ this.videoCapture.on('cursor-change', (data) => {
153
+ this.emit('preview:browser-cursor-change', data);
154
+ });
155
+ this.videoCapture.on('navigation-streaming-ready', (data) => {
156
+ this.emit('preview:browser-navigation-streaming-ready', data);
157
+ });
158
+
159
+ // Forward dialog events
160
+ this.dialogHandler.on('dialog', (data) => {
161
+ this.emit('preview:browser-dialog', data);
162
+ });
163
+ this.dialogHandler.on('print', (data) => {
164
+ this.emit('preview:browser-print', data);
165
+ });
166
+
167
+ // Forward native UI events
168
+ this.nativeUIHandler.on('copy-to-clipboard', (data) => {
169
+ this.emit('preview:browser-copy-to-clipboard', data);
170
+ });
171
+ this.nativeUIHandler.on('open-url-new-tab', (data) => {
172
+ this.emit('preview:browser-open-url-new-tab', data);
173
+ });
174
+ this.nativeUIHandler.on('download-image', (data) => {
175
+ this.emit('preview:browser-download-image', data);
176
+ });
177
+ this.nativeUIHandler.on('copy-image-to-clipboard', (data) => {
178
+ this.emit('preview:browser-copy-image-to-clipboard', data);
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Get project ID for this service instance
184
+ */
185
+ getProjectId(): string {
186
+ return this.projectId;
187
+ }
188
+
189
+ // ============================================================================
190
+ // Tab Management Methods
191
+ // ============================================================================
192
+
193
+ /**
194
+ * Create a new tab with optional URL
195
+ *
196
+ * If URL is provided, navigate to it immediately.
197
+ * If URL is not provided, create blank tab (about:blank).
198
+ *
199
+ * Default rotation depends on device size:
200
+ * - Desktop/laptop: landscape
201
+ * - Tablet/mobile: portrait
202
+ */
203
+ async createTab(url?: string, deviceSize: DeviceSize = 'laptop', rotation?: Rotation): Promise<BrowserTab> {
204
+ // Use device-appropriate default rotation if not specified
205
+ const actualRotation = rotation || ((deviceSize === 'desktop' || deviceSize === 'laptop') ? 'landscape' : 'portrait');
206
+ // Pre-navigation setup callback for dialog bindings
207
+ const preNavigationSetup = async (page: Page) => {
208
+ // Note: We'll setup dialog bindings using tabId after tab is created
209
+ };
210
+
211
+ // Create tab
212
+ const tab = await this.tabManager.createTab(url, deviceSize, actualRotation, {
213
+ setActive: true,
214
+ preNavigationSetup
215
+ });
216
+
217
+ // Setup console and navigation tracking
218
+ await this.consoleManager.setupConsoleLogging(tab.id, tab.page, tab);
219
+ await this.navigationTracker.setupNavigationTracking(tab.id, tab.page, tab);
220
+
221
+ // Setup dialog bindings and handling
222
+ await this.dialogHandler.setupDialogBindings(tab.id, tab.page);
223
+ await this.dialogHandler.setupDialogHandling(tab.id, tab.page, tab);
224
+
225
+ return tab;
226
+ }
227
+
228
+ /**
229
+ * Navigate tab to a new URL
230
+ */
231
+ async navigateTab(tabId: string, url: string): Promise<string> {
232
+ const actualUrl = await this.tabManager.navigateTab(tabId, url);
233
+
234
+ // Mark navigation for frame deduplication
235
+ this.markNavigation(tabId, url);
236
+
237
+ return actualUrl;
238
+ }
239
+
240
+ /**
241
+ * Close a tab and cleanup its resources
242
+ */
243
+ async closeTab(tabId: string): Promise<{ success: boolean; newActiveTabId: string | null }> {
244
+ const tab = this.tabManager.getTab(tabId);
245
+ if (!tab) {
246
+ return { success: false, newActiveTabId: null };
247
+ }
248
+
249
+ // Stop WebCodecs streaming first
250
+ await this.stopWebCodecsStreaming(tabId);
251
+
252
+ // Clear cursor tracking for this tab
253
+ this.interactionHandler.clearSessionCursor(tabId);
254
+
255
+ // Clear dialogs for this tab
256
+ this.dialogHandler.clearSessionDialogs(tabId);
257
+
258
+ // Close the tab (this will cleanup context, page, etc.)
259
+ const result = await this.tabManager.closeTab(tabId);
260
+
261
+ // Emit tab closed event (for MCP control manager and other listeners)
262
+ this.emit('preview:browser-tab-destroyed', { tabId });
263
+
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * Switch to a specific tab
269
+ */
270
+ switchTab(tabId: string): boolean {
271
+ return this.tabManager.setActiveTab(tabId);
272
+ }
273
+
274
+ /**
275
+ * Get a tab by ID
276
+ */
277
+ getTab(tabId: string): BrowserTab | null {
278
+ return this.tabManager.getTab(tabId);
279
+ }
280
+
281
+ /**
282
+ * Get the active tab
283
+ */
284
+ getActiveTab(): BrowserTab | null {
285
+ return this.tabManager.getActiveTab();
286
+ }
287
+
288
+ /**
289
+ * Get all tabs
290
+ */
291
+ getAllTabs(): BrowserTab[] {
292
+ return this.tabManager.getAllTabs();
293
+ }
294
+
295
+ /**
296
+ * Change viewport settings (device size and rotation) for an existing tab
297
+ */
298
+ async setViewport(tabId: string, deviceSize: DeviceSize, rotation: Rotation): Promise<boolean> {
299
+ return await this.tabManager.setViewport(tabId, deviceSize, rotation);
300
+ }
301
+
302
+ /**
303
+ * Get tab count
304
+ */
305
+ getTabCount(): number {
306
+ return this.tabManager.getTabCount();
307
+ }
308
+
309
+ /**
310
+ * Get tab info
311
+ */
312
+ getTabInfo(tabId: string): BrowserTabInfo | null {
313
+ return this.tabManager.getTabInfo(tabId);
314
+ }
315
+
316
+ /**
317
+ * Get all tabs info
318
+ */
319
+ getAllTabsInfo(): BrowserTabInfo[] {
320
+ return this.tabManager.getAllTabsInfo();
321
+ }
322
+
323
+ /**
324
+ * Get available tab IDs
325
+ */
326
+ getAvailableTabIds(): string[] {
327
+ return this.tabManager.getAvailableTabIds();
328
+ }
329
+
330
+ /**
331
+ * Get tabs status (for admin/debugging)
332
+ */
333
+ getTabsStatus() {
334
+ return this.tabManager.getTabsStatus();
335
+ }
336
+
337
+ /**
338
+ * Update tab title from URL
339
+ */
340
+ updateTabTitleFromUrl(tabId: string, url: string): void {
341
+ this.tabManager.updateTabTitleFromUrl(tabId, url);
342
+ }
343
+
344
+ /**
345
+ * Check if tab is valid
346
+ */
347
+ isValidTab(tabId: string): boolean {
348
+ const tab = this.getTab(tabId);
349
+ return tab !== null && !tab.isDestroyed;
350
+ }
351
+
352
+ // ============================================================================
353
+ // WebCodecs Streaming Methods (optimized, ~20-40ms, lower bandwidth)
354
+ // ============================================================================
355
+ async startWebCodecsStreaming(tabId: string): Promise<boolean> {
356
+ const tab = this.getTab(tabId);
357
+ if (!tab) {
358
+ return false;
359
+ }
360
+ return await this.videoCapture.startStreaming(
361
+ tabId,
362
+ tab,
363
+ () => this.isValidTab(tabId)
364
+ );
365
+ }
366
+
367
+ async stopWebCodecsStreaming(tabId: string): Promise<void> {
368
+ const tab = this.getTab(tabId);
369
+ await this.videoCapture.stopStreaming(tabId, tab ?? undefined);
370
+ }
371
+
372
+ async updateWebCodecsScale(tabId: string, newScale: number): Promise<boolean> {
373
+ const tab = this.getTab(tabId);
374
+ if (!tab) {
375
+ return false;
376
+ }
377
+ return await this.videoCapture.updateScale(tabId, tab, newScale);
378
+ }
379
+
380
+ async updateWebCodecsViewport(tabId: string, width: number, height: number, newScale: number): Promise<boolean> {
381
+ const tab = this.getTab(tabId);
382
+ if (!tab) {
383
+ return false;
384
+ }
385
+ return await this.videoCapture.updateViewport(tabId, tab, width, height, newScale);
386
+ }
387
+
388
+ async getWebCodecsOffer(tabId: string): Promise<RTCSessionDescriptionInit | null> {
389
+ const tab = this.getTab(tabId);
390
+ if (!tab) {
391
+ return null;
392
+ }
393
+ return await this.videoCapture.createOffer(tabId, tab);
394
+ }
395
+
396
+ async handleWebCodecsAnswer(tabId: string, answer: RTCSessionDescriptionInit): Promise<boolean> {
397
+ const tab = this.getTab(tabId);
398
+ if (!tab) {
399
+ return false;
400
+ }
401
+ return await this.videoCapture.handleAnswer(tabId, tab, answer);
402
+ }
403
+
404
+ async addWebCodecsIceCandidate(tabId: string, candidate: RTCIceCandidateInit): Promise<boolean> {
405
+ const tab = this.getTab(tabId);
406
+ if (!tab) {
407
+ return false;
408
+ }
409
+ return await this.videoCapture.addIceCandidate(tabId, tab, candidate);
410
+ }
411
+
412
+ isWebCodecsActive(tabId: string): boolean {
413
+ return this.videoCapture.isStreaming(tabId);
414
+ }
415
+
416
+ async getWebCodecsStats(tabId: string) {
417
+ const tab = this.getTab(tabId);
418
+ if (!tab) {
419
+ return null;
420
+ }
421
+ return await this.videoCapture.getStats(tabId, tab);
422
+ }
423
+
424
+ markUserInteraction(tabId: string): void {
425
+ this.tabManager.markTabActivity(tabId);
426
+ }
427
+
428
+ // Public method to mark tab activity (called from WS handlers)
429
+ markActiveTabActivity(): void {
430
+ const tab = this.getActiveTab();
431
+ if (tab) {
432
+ this.tabManager.markTabActivity(tab.id);
433
+ }
434
+ }
435
+
436
+ markNavigation(tabId: string, _newUrl?: string): void {
437
+ // Navigation tracking is now handled by WebCodecs automatically
438
+ // This method is kept for API compatibility
439
+ }
440
+
441
+ // ============================================================================
442
+ // Console Management Methods
443
+ // ============================================================================
444
+ getConsoleLogs(tabId: string): BrowserConsoleMessage[] {
445
+ const tab = this.getTab(tabId);
446
+ return tab ? this.consoleManager.getConsoleLogs(tab) : [];
447
+ }
448
+
449
+ clearConsoleLogs(tabId: string): boolean {
450
+ const tab = this.getTab(tabId);
451
+ return tab ? this.consoleManager.clearConsoleLogs(tab) : false;
452
+ }
453
+
454
+ toggleConsoleLogging(tabId: string, enabled: boolean): boolean {
455
+ const tab = this.getTab(tabId);
456
+ return tab ? this.consoleManager.toggleConsoleLogging(tab, enabled) : false;
457
+ }
458
+
459
+ async executeConsoleCommand(tabId: string, command: string): Promise<any> {
460
+ const tab = this.getTab(tabId);
461
+ if (!tab) throw new Error('Tab not found or invalid');
462
+ return this.consoleManager.executeConsoleCommand(tab, command);
463
+ }
464
+
465
+ // ============================================================================
466
+ // Interaction & Autonomous Actions Methods
467
+ // ============================================================================
468
+ async performAutonomousActions(tabId: string, actions: BrowserAutonomousAction[]) {
469
+ const tab = this.getTab(tabId);
470
+ if (!tab) throw new Error('Tab not found or invalid');
471
+
472
+ const results = await this.interactionHandler.performAutonomousActions(
473
+ tabId,
474
+ tab,
475
+ actions,
476
+ () => this.isValidTab(tabId)
477
+ );
478
+
479
+ return results;
480
+ }
481
+
482
+ /**
483
+ * Perform autonomous actions using tab object directly
484
+ * More efficient when tab is already available
485
+ */
486
+ async performAutonomousActionsWithTab(tab: BrowserTab, actions: BrowserAutonomousAction[]) {
487
+ const results = await this.interactionHandler.performAutonomousActions(
488
+ tab.id,
489
+ tab,
490
+ actions,
491
+ () => this.isValidTab(tab.id)
492
+ );
493
+
494
+ return results;
495
+ }
496
+
497
+ // ============================================================================
498
+ // Dialog Management Methods
499
+ // ============================================================================
500
+ async respondToDialog(response: BrowserDialogResponse): Promise<boolean> {
501
+ return await this.dialogHandler.respondToDialog(response);
502
+ }
503
+
504
+ // ============================================================================
505
+ // Native UI Methods (Select & Context Menu)
506
+ // ============================================================================
507
+ async checkForSelectElement(tabId: string, x: number, y: number) {
508
+ const tab = this.getTab(tabId);
509
+ if (!tab) return null;
510
+
511
+ const selectInfo = await this.nativeUIHandler.checkForSelect(tabId, tab.page, x, y);
512
+ if (selectInfo) {
513
+ this.emit('preview:browser-select', selectInfo);
514
+ }
515
+ return selectInfo;
516
+ }
517
+
518
+ async handleSelectResponse(tabId: string, response: BrowserSelectResponse): Promise<boolean> {
519
+ const tab = this.getTab(tabId);
520
+ if (!tab) return false;
521
+
522
+ return await this.nativeUIHandler.handleSelectResponse(tab.page, response);
523
+ }
524
+
525
+ async checkForContextMenu(tabId: string, x: number, y: number) {
526
+ const tab = this.getTab(tabId);
527
+ if (!tab) return null;
528
+
529
+ const menuInfo = await this.nativeUIHandler.checkForContextMenu(tabId, tab.page, x, y);
530
+ if (menuInfo) {
531
+ // Store menu info for later action execution
532
+ this.contextMenus.set(menuInfo.menuId, menuInfo);
533
+ this.emit('preview:browser-context-menu', menuInfo);
534
+ }
535
+ return menuInfo;
536
+ }
537
+
538
+ async handleContextMenuResponse(tabId: string, response: BrowserContextMenuResponse, clipboardText?: string): Promise<boolean> {
539
+ const tab = this.getTab(tabId);
540
+ if (!tab) return false;
541
+
542
+ // Get stored menu info
543
+ const menuInfo = this.contextMenus.get(response.menuId);
544
+ if (!menuInfo) return false;
545
+
546
+ const result = await this.nativeUIHandler.handleContextMenuResponse(tab.page, response, menuInfo, clipboardText);
547
+
548
+ // Clean up stored menu info
549
+ this.contextMenus.delete(response.menuId);
550
+
551
+ return result;
552
+ }
553
+
554
+ // ============================================================================
555
+ // Cleanup Methods
556
+ // ============================================================================
557
+ async cleanup() {
558
+ // Clear all cursor tracking
559
+ this.interactionHandler.clearAllSessionCursors();
560
+ // Cleanup tabs (this will also cleanup all contexts/pages/browser pool)
561
+ await this.tabManager.cleanup();
562
+ }
563
+
564
+ async cleanupInactiveTabs() {
565
+ return this.tabManager.cleanupInactiveTabs();
566
+ }
567
+
568
+ async forceCleanupAll() {
569
+ // First try normal cleanup
570
+ await this.cleanup();
571
+
572
+ // Cleanup video capture sessions
573
+ await this.videoCapture.cleanup();
574
+
575
+ // Clear all dialogs and context menus
576
+ this.dialogHandler.clearAllDialogs();
577
+ this.contextMenus.clear();
578
+
579
+ // Remove all listeners to prevent memory leaks
580
+ this.removeAllListeners();
581
+ this.consoleManager.removeAllListeners();
582
+ this.interactionHandler.removeAllListeners();
583
+ this.navigationTracker.removeAllListeners();
584
+ this.videoCapture.removeAllListeners();
585
+ this.dialogHandler.removeAllListeners();
586
+ this.nativeUIHandler.removeAllListeners();
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Browser Preview Service Manager
592
+ *
593
+ * Manages BrowserPreviewService instances per project.
594
+ * Provides project isolation - each project has its own browser tabs and state.
595
+ */
596
+ class BrowserPreviewServiceManager {
597
+ private services = new Map<string, BrowserPreviewService>();
598
+
599
+ /**
600
+ * Get or create a BrowserPreviewService for a project
601
+ */
602
+ getService(projectId: string): BrowserPreviewService {
603
+ if (!projectId) {
604
+ throw new Error('projectId is required and cannot be empty');
605
+ }
606
+
607
+ if (!this.services.has(projectId)) {
608
+ debug.log('preview', `🆕 Creating new BrowserPreviewService for project: ${projectId}`);
609
+ const service = new BrowserPreviewService(projectId);
610
+ this.services.set(projectId, service);
611
+
612
+ // Setup WebSocket event forwarding for this service
613
+ this.setupWebSocketForwarding(service, projectId);
614
+ debug.log('preview', `✅ BrowserPreviewService fully initialized for project: ${projectId}`);
615
+ }
616
+
617
+ return this.services.get(projectId)!;
618
+ }
619
+
620
+ /**
621
+ * Setup WebSocket event forwarding for a service instance
622
+ * Events are emitted to the specific project only
623
+ */
624
+ private setupWebSocketForwarding(service: BrowserPreviewService, projectId: string): void {
625
+ debug.log('preview', `🔌 Setting up WebSocket forwarding for project: ${projectId}...`);
626
+
627
+ // Forward WebCodecs events
628
+ service.on('preview:browser-webcodecs-ice-candidate', (data) => {
629
+ ws.emit.project(projectId, 'preview:browser-stream-ice', {
630
+ sessionId: data.sessionId,
631
+ candidate: data.candidate,
632
+ from: data.from
633
+ });
634
+ });
635
+
636
+ service.on('preview:browser-webcodecs-connection-state', (data) => {
637
+ ws.emit.project(projectId, 'preview:browser-stream-state', data);
638
+ });
639
+
640
+ service.on('preview:browser-cursor-change', (data) => {
641
+ ws.emit.project(projectId, 'preview:browser-cursor-change', data);
642
+ });
643
+
644
+ // Forward navigation events
645
+ service.on('preview:browser-navigation-loading', (data) => {
646
+ ws.emit.project(projectId, 'preview:browser-navigation-loading', data);
647
+ });
648
+
649
+ service.on('preview:browser-navigation', (data) => {
650
+ ws.emit.project(projectId, 'preview:browser-navigation', data);
651
+ });
652
+
653
+ // Forward tab events
654
+ service.on('preview:browser-tab-opened', (data) => {
655
+ debug.log('preview', `🚀 Forwarding preview:browser-tab-opened to project ${projectId}:`, data);
656
+ ws.emit.project(projectId, 'preview:browser-tab-opened', data);
657
+ });
658
+
659
+ service.on('preview:browser-tab-closed', (data) => {
660
+ ws.emit.project(projectId, 'preview:browser-tab-closed', data);
661
+ });
662
+
663
+ service.on('preview:browser-tab-switched', (data) => {
664
+ ws.emit.project(projectId, 'preview:browser-tab-switched', data);
665
+ });
666
+
667
+ service.on('preview:browser-tab-navigated', (data) => {
668
+ ws.emit.project(projectId, 'preview:browser-tab-navigated', data);
669
+ });
670
+
671
+ // Forward console events
672
+ service.on('preview:browser-console-message', (data) => {
673
+ ws.emit.project(projectId, 'preview:browser-console-message', data);
674
+ });
675
+
676
+ service.on('preview:browser-console-clear', (data) => {
677
+ ws.emit.project(projectId, 'preview:browser-console-clear', data);
678
+ });
679
+
680
+ // Forward MCP events
681
+ service.on('preview:browser-mcp-cursor-position', (data) => {
682
+ ws.emit.project(projectId, 'preview:browser-mcp-cursor-position', data);
683
+ });
684
+
685
+ service.on('preview:browser-mcp-cursor-click', (data) => {
686
+ ws.emit.project(projectId, 'preview:browser-mcp-cursor-click', data);
687
+ });
688
+
689
+ service.on('preview:browser-mcp-test-completed', (data) => {
690
+ ws.emit.project(projectId, 'preview:browser-mcp-test-completed', data);
691
+ });
692
+
693
+ // Forward dialog events
694
+ service.on('preview:browser-dialog', (data) => {
695
+ ws.emit.project(projectId, 'preview:browser-dialog', data);
696
+ });
697
+
698
+ service.on('preview:browser-print', (data) => {
699
+ ws.emit.project(projectId, 'preview:browser-print', data);
700
+ });
701
+
702
+ // Forward native UI events
703
+ service.on('preview:browser-select', (data) => {
704
+ ws.emit.project(projectId, 'preview:browser-select', data);
705
+ });
706
+
707
+ service.on('preview:browser-context-menu', (data) => {
708
+ ws.emit.project(projectId, 'preview:browser-context-menu', data);
709
+ });
710
+
711
+ service.on('preview:browser-copy-to-clipboard', (data) => {
712
+ ws.emit.project(projectId, 'preview:browser-copy-to-clipboard', data);
713
+ });
714
+
715
+ service.on('preview:browser-open-url-new-tab', (data) => {
716
+ ws.emit.project(projectId, 'preview:browser-open-url-new-tab', data);
717
+ });
718
+
719
+ service.on('preview:browser-download-image', (data) => {
720
+ ws.emit.project(projectId, 'preview:browser-download-image', data);
721
+ });
722
+
723
+ service.on('preview:browser-copy-image-to-clipboard', (data) => {
724
+ ws.emit.project(projectId, 'preview:browser-copy-image-to-clipboard', data);
725
+ });
726
+
727
+ // Forward new window events
728
+ service.on('preview:browser-new-window', (data) => {
729
+ ws.emit.project(projectId, 'preview:browser-new-window', data);
730
+ });
731
+
732
+ // Forward MCP control events (from singleton browserMcpControl)
733
+ browserMcpControl.on('control-start', (data) => {
734
+ debug.log('preview', `🚀 Forwarding mcp-control-start to project ${projectId}:`, data);
735
+ ws.emit.project(projectId, 'preview:browser-mcp-control-start', {
736
+ browserSessionId: data.browserTabId,
737
+ mcpSessionId: data.mcpSessionId,
738
+ timestamp: data.timestamp
739
+ });
740
+ });
741
+
742
+ browserMcpControl.on('control-end', (data) => {
743
+ debug.log('preview', `🚀 Forwarding mcp-control-end to project ${projectId}:`, data);
744
+ ws.emit.project(projectId, 'preview:browser-mcp-control-end', {
745
+ browserSessionId: data.browserTabId,
746
+ timestamp: data.timestamp
747
+ });
748
+ });
749
+
750
+ // Forward MCP cursor events
751
+ browserMcpControl.on('cursor-position', (data) => {
752
+ ws.emit.project(projectId, 'preview:browser-mcp-cursor-position', {
753
+ sessionId: data.tabId,
754
+ x: data.x,
755
+ y: data.y,
756
+ timestamp: data.timestamp,
757
+ source: 'mcp'
758
+ });
759
+ });
760
+
761
+ browserMcpControl.on('cursor-click', (data) => {
762
+ ws.emit.project(projectId, 'preview:browser-mcp-cursor-click', {
763
+ sessionId: data.tabId,
764
+ x: data.x,
765
+ y: data.y,
766
+ timestamp: data.timestamp,
767
+ source: 'mcp'
768
+ });
769
+ });
770
+
771
+ browserMcpControl.on('test-completed', (data) => {
772
+ ws.emit.project(projectId, 'preview:browser-mcp-test-completed', {
773
+ sessionId: data.tabId,
774
+ timestamp: data.timestamp,
775
+ source: 'mcp'
776
+ });
777
+ });
778
+
779
+ debug.log('preview', `🎉 All WebSocket event listeners registered for project: ${projectId}`);
780
+ }
781
+
782
+ /**
783
+ * Check if a service exists for a project
784
+ */
785
+ hasService(projectId: string): boolean {
786
+ if (!projectId) {
787
+ throw new Error('projectId is required and cannot be empty');
788
+ }
789
+ return this.services.has(projectId);
790
+ }
791
+
792
+ /**
793
+ * Remove a service for a project (cleanup)
794
+ */
795
+ async removeService(projectId: string): Promise<void> {
796
+ if (!projectId) {
797
+ throw new Error('projectId is required and cannot be empty');
798
+ }
799
+
800
+ const service = this.services.get(projectId);
801
+
802
+ if (service) {
803
+ await service.forceCleanupAll();
804
+ this.services.delete(projectId);
805
+ }
806
+ }
807
+
808
+ /**
809
+ * Cleanup all services
810
+ */
811
+ async cleanup(): Promise<void> {
812
+ const cleanupPromises = Array.from(this.services.values()).map(service =>
813
+ service.forceCleanupAll().catch(error => {
814
+ console.error('Error cleaning up service:', error);
815
+ })
816
+ );
817
+
818
+ await Promise.all(cleanupPromises);
819
+ this.services.clear();
820
+ }
821
+
822
+ /**
823
+ * Get all active project IDs
824
+ */
825
+ getActiveProjects(): string[] {
826
+ return Array.from(this.services.keys());
827
+ }
828
+
829
+ /**
830
+ * Get stats for all services
831
+ */
832
+ getStats() {
833
+ const stats = new Map<string, any>();
834
+
835
+ for (const [projectId, service] of this.services.entries()) {
836
+ stats.set(projectId, {
837
+ projectId,
838
+ tabs: service.getTabsStatus()
839
+ });
840
+ }
841
+
842
+ return stats;
843
+ }
844
+ }
845
+
846
+ // Service manager instance (singleton)
847
+ export const browserPreviewServiceManager = new BrowserPreviewServiceManager();
848
+
849
+ // Graceful shutdown handlers
850
+ const gracefulShutdown = async (signal: string) => {
851
+ try {
852
+ await browserPreviewServiceManager.cleanup();
853
+ process.exit(0);
854
+ } catch (error) {
855
+ process.exit(1);
856
+ }
857
+ };
858
+
859
+ // Handle various termination signals
860
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
861
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
862
+ process.on('SIGHUP', () => gracefulShutdown('SIGHUP'));
863
+
864
+ // Handle Windows-specific signals
865
+ if (process.platform === 'win32') {
866
+ process.on('SIGBREAK', () => gracefulShutdown('SIGBREAK'));
867
+ }
868
+
869
+ // Handle uncaught exceptions and unhandled rejections
870
+ process.on('uncaughtException', async (error) => {
871
+ await browserPreviewServiceManager.cleanup();
872
+ process.exit(1);
873
+ });
874
+
875
+ process.on('unhandledRejection', async (reason, promise) => {
876
+ await browserPreviewServiceManager.cleanup();
877
+ process.exit(1);
878
+ });
879
+
880
+ // Handle process exit
881
+ process.on('exit', (code) => {
882
+ });