@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,222 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { Page, Dialog } from 'puppeteer';
3
+ import type { BrowserTab, BrowserDialogEvent, BrowserDialogResponse, BrowserPrintEvent } from './types';
4
+ import { debug } from '$shared/utils/logger';
5
+ import { nanoid } from 'nanoid';
6
+
7
+ /**
8
+ * Browser Dialog Handler
9
+ *
10
+ * Intercepts native browser dialogs (alert, confirm, prompt) from headless browser
11
+ * and emits events to frontend for re-rendering as native host dialogs.
12
+ *
13
+ * Also intercepts window.print() calls and emits print events.
14
+ */
15
+ export class BrowserDialogHandler extends EventEmitter {
16
+ // Store pending dialogs waiting for response
17
+ private pendingDialogs = new Map<string, Dialog>();
18
+
19
+ constructor() {
20
+ super();
21
+ }
22
+
23
+ /**
24
+ * Setup dialog interception for a browser session
25
+ */
26
+ /**
27
+ * Setup dialog bindings - MUST be called BEFORE navigation
28
+ * This includes exposeFunction calls which require page to not be navigated yet
29
+ */
30
+ async setupDialogBindings(sessionId: string, page: Page) {
31
+ debug.log('preview', `🎭 Setting up dialog bindings (pre-navigation) for session: ${sessionId}`);
32
+
33
+ // Setup print interception bindings - requires exposeFunction
34
+ await this.setupPrintInterception(sessionId, page);
35
+
36
+ debug.log('preview', `✅ Dialog bindings setup complete for session: ${sessionId}`);
37
+ }
38
+
39
+ /**
40
+ * Setup dialog event listeners - can be called AFTER navigation
41
+ */
42
+ async setupDialogHandling(sessionId: string, page: Page, session: BrowserTab) {
43
+ debug.log('preview', `🎭 Setting up dialog event listeners for session: ${sessionId}`);
44
+
45
+ // Intercept Puppeteer dialog events
46
+ page.on('dialog', async (dialog: Dialog) => {
47
+ await this.handleDialog(sessionId, dialog);
48
+ });
49
+
50
+ debug.log('preview', `✅ Dialog event listeners setup complete for session: ${sessionId}`);
51
+ }
52
+
53
+ /**
54
+ * Handle Puppeteer dialog event
55
+ */
56
+ private async handleDialog(sessionId: string, dialog: Dialog) {
57
+ const dialogId = nanoid(10);
58
+ const dialogType = dialog.type();
59
+ const message = dialog.message();
60
+ const defaultValue = dialog.defaultValue();
61
+
62
+ debug.log('preview', `🎭 Dialog detected - Type: ${dialogType}, Session: ${sessionId}`);
63
+
64
+ // Store pending dialog for later response
65
+ this.pendingDialogs.set(dialogId, dialog);
66
+
67
+ // Emit dialog event to frontend (sessionId will be converted to tabId by previewService)
68
+ const dialogEvent: any = {
69
+ sessionId, // Internal use only, converted to tabId at previewService layer
70
+ dialogId,
71
+ type: dialogType as 'alert' | 'confirm' | 'prompt' | 'beforeunload',
72
+ message,
73
+ defaultValue,
74
+ timestamp: Date.now()
75
+ };
76
+
77
+ this.emit('dialog', dialogEvent);
78
+
79
+ debug.log('preview', `📤 Dialog event emitted to frontend: ${dialogId}`);
80
+ }
81
+
82
+ /**
83
+ * Handle dialog response from frontend
84
+ */
85
+ async respondToDialog(response: BrowserDialogResponse) {
86
+ const { dialogId, accept, promptText } = response;
87
+
88
+ debug.log('preview', `🔍 Responding to dialog - dialogId: ${dialogId}, accept: ${accept}, pending dialogs: ${this.pendingDialogs.size}`);
89
+
90
+ const dialog = this.pendingDialogs.get(dialogId);
91
+ if (!dialog) {
92
+ debug.warn('preview', `⚠️ Dialog not found in pendingDialogs: ${dialogId}`);
93
+ debug.warn('preview', ` Available dialog IDs: ${Array.from(this.pendingDialogs.keys()).join(', ') || '(none)'}`);
94
+ return false;
95
+ }
96
+
97
+ debug.log('preview', `✅ Dialog found in pendingDialogs - Type: ${dialog.type()}, Message: "${dialog.message()}"`);
98
+
99
+ try {
100
+ if (accept) {
101
+ // User accepted (OK/Yes)
102
+ if (dialog.type() === 'prompt' && promptText !== undefined) {
103
+ debug.log('preview', `📝 Accepting prompt dialog with text: "${promptText}"`);
104
+ await dialog.accept(promptText);
105
+ debug.log('preview', `✅ Prompt accepted successfully`);
106
+ } else {
107
+ debug.log('preview', `📝 Accepting ${dialog.type()} dialog`);
108
+ await dialog.accept();
109
+ debug.log('preview', `✅ Dialog accepted successfully: ${dialogId}`);
110
+ }
111
+ } else {
112
+ // User dismissed (Cancel/No)
113
+ debug.log('preview', `📝 Dismissing ${dialog.type()} dialog`);
114
+ await dialog.dismiss();
115
+ debug.log('preview', `✅ Dialog dismissed successfully: ${dialogId}`);
116
+ }
117
+
118
+ // Remove from pending dialogs
119
+ this.pendingDialogs.delete(dialogId);
120
+ debug.log('preview', `🗑️ Removed dialog from pendingDialogs - remaining: ${this.pendingDialogs.size}`);
121
+ return true;
122
+ } catch (error) {
123
+ debug.error('preview', `💥 Error responding to dialog ${dialogId}:`, error);
124
+ this.pendingDialogs.delete(dialogId);
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Setup window.print() interception
131
+ */
132
+ private async setupPrintInterception(sessionId: string, page: Page) {
133
+ try {
134
+ // IMPORTANT: exposeFunction must be called BEFORE any navigation or evaluateOnNewDocument
135
+ // Listen for print requests
136
+ await page.exposeFunction('__notifyPrintRequest__', () => {
137
+ const printEvent: any = {
138
+ sessionId, // Internal use only, converted to tabId at previewService layer
139
+ timestamp: Date.now()
140
+ };
141
+
142
+ debug.log('preview', `🖨️ Print request detected for session: ${sessionId}`);
143
+ this.emit('print', printEvent);
144
+ });
145
+
146
+ // Override window.print() in the page context
147
+ await page.evaluateOnNewDocument(() => {
148
+ // Store original print function
149
+ const originalPrint = window.print;
150
+
151
+ // Override with custom handler
152
+ window.print = function() {
153
+ // Emit custom event that we can intercept
154
+ window.dispatchEvent(new CustomEvent('__puppeteer_print_requested__'));
155
+
156
+ // Don't call original print() in headless mode - it would fail
157
+ // We'll handle it via the event
158
+ };
159
+ });
160
+
161
+ // Setup event listener for print requests
162
+ await page.evaluateOnNewDocument(() => {
163
+ window.addEventListener('__puppeteer_print_requested__', () => {
164
+ // Notify backend about print request
165
+ if ((window as any).__notifyPrintRequest__) {
166
+ (window as any).__notifyPrintRequest__();
167
+ }
168
+ });
169
+ });
170
+
171
+ debug.log('preview', `✅ Print interception setup successfully for session: ${sessionId}`);
172
+ } catch (error) {
173
+ // If exposeFunction fails (e.g., target closed), print interception won't work
174
+ // but we shouldn't fail the entire session creation
175
+ debug.warn('preview', `⚠️ Print interception setup failed for session ${sessionId}: ${error instanceof Error ? error.message : 'Unknown error'}`);
176
+ debug.warn('preview', ` Print functionality will not be available for this session`);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Clear pending dialogs for a session
182
+ */
183
+ clearSessionDialogs(sessionId: string) {
184
+ // Find and dismiss all dialogs for this session
185
+ const dialogsToRemove: string[] = [];
186
+
187
+ for (const [dialogId, dialog] of this.pendingDialogs.entries()) {
188
+ // Check if dialog belongs to this session (we store sessionId in the dialogId via Map)
189
+ // Since we don't have direct sessionId mapping, we'll clear all pending dialogs
190
+ // This is safe as each session has its own page instance
191
+ try {
192
+ dialog.dismiss().catch(() => {});
193
+ } catch (error) {
194
+ // Dialog might already be closed
195
+ }
196
+ dialogsToRemove.push(dialogId);
197
+ }
198
+
199
+ // Remove from pending map
200
+ dialogsToRemove.forEach(id => this.pendingDialogs.delete(id));
201
+
202
+ debug.log('preview', `🧹 Cleared ${dialogsToRemove.length} pending dialogs for session: ${sessionId}`);
203
+ }
204
+
205
+ /**
206
+ * Clear all pending dialogs
207
+ */
208
+ clearAllDialogs() {
209
+ const count = this.pendingDialogs.size;
210
+
211
+ for (const [dialogId, dialog] of this.pendingDialogs.entries()) {
212
+ try {
213
+ dialog.dismiss().catch(() => {});
214
+ } catch (error) {
215
+ // Dialog might already be closed
216
+ }
217
+ }
218
+
219
+ this.pendingDialogs.clear();
220
+ debug.log('preview', `🧹 Cleared ${count} pending dialogs`);
221
+ }
222
+ }
@@ -0,0 +1,421 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { Page, KeyInput } from 'puppeteer';
3
+ import type { BrowserAutonomousAction, BrowserTab } from './types';
4
+ import { debug } from '$shared/utils/logger';
5
+ import { sleep } from '$shared/utils/async';
6
+ import { browserMcpControl } from './browser-mcp-control';
7
+
8
+ export class BrowserInteractionHandler extends EventEmitter {
9
+ // Track cursor positions per session
10
+ private sessionCursorPositions: Map<string, {x: number, y: number}> = new Map();
11
+
12
+ constructor() {
13
+ super();
14
+ }
15
+
16
+ // MCP-optimized mouse movement with smooth curves and ease-out easing
17
+ private async mcpMouseMove(page: Page, fromX: number, fromY: number, toX: number, toY: number, steps: number = 8) {
18
+ const controlPointX = fromX + (toX - fromX) * 0.5 + (Math.random() - 0.5) * 50;
19
+ const controlPointY = fromY + (toY - fromY) * 0.5 + (Math.random() - 0.5) * 50;
20
+
21
+ // Get session for emitting cursor position
22
+ const sessionId = (page as any).__sessionId;
23
+
24
+ for (let i = 0; i <= steps; i++) {
25
+ const t = i / steps;
26
+
27
+ // Apply ease-out cubic easing: starts fast, ends slow
28
+ const easedT = 1 - Math.pow(1 - t, 3);
29
+
30
+ // Quadratic Bezier curve for natural movement with ease-out easing
31
+ const x = Math.round((1 - easedT) * (1 - easedT) * fromX + 2 * (1 - easedT) * easedT * controlPointX + easedT * easedT * toX);
32
+ const y = Math.round((1 - easedT) * (1 - easedT) * fromY + 2 * (1 - easedT) * easedT * controlPointY + easedT * easedT * toY);
33
+
34
+ await page.mouse.move(x, y);
35
+
36
+ // Emit cursor position for visual tracking
37
+ if (sessionId) {
38
+ this.updateCursorPosition(sessionId, x, y);
39
+
40
+ // Emit to WebSocket for frontend virtual cursor
41
+ browserMcpControl.emitCursorPosition(sessionId, x, y);
42
+
43
+ this.emit('cursor-position', {
44
+ sessionId,
45
+ x,
46
+ y,
47
+ timestamp: Date.now()
48
+ });
49
+ }
50
+
51
+ // Smooth delay between movements (15-25ms) for MCP automation
52
+ const moveDelay = 15 + Math.random() * 10;
53
+ await sleep(moveDelay);
54
+ }
55
+ }
56
+
57
+ // MCP-optimized typing with fast, consistent speed
58
+ private async mcpType(page: Page, text: string, baseDelay: number = 30) {
59
+ for (let i = 0; i < text.length; i++) {
60
+ const char = text[i];
61
+
62
+ // Fast typing with slight variance (20-40ms) for MCP automation
63
+ const delay = baseDelay + Math.random() * 10;
64
+
65
+ await page.keyboard.type(char, { delay });
66
+ }
67
+ }
68
+
69
+ // Get current mouse position from session tracking
70
+ private getCurrentMousePosition(sessionId: string): {x: number, y: number} {
71
+ const stored = this.sessionCursorPositions.get(sessionId);
72
+ if (stored) {
73
+ return stored;
74
+ }
75
+
76
+ // Default to center of viewport for first action
77
+ const defaultPos = { x: 400, y: 300 };
78
+ this.sessionCursorPositions.set(sessionId, defaultPos);
79
+ return defaultPos;
80
+ }
81
+
82
+ // Update and persist cursor position for session
83
+ private updateCursorPosition(sessionId: string, x: number, y: number) {
84
+ const position = { x, y };
85
+ this.sessionCursorPositions.set(sessionId, position);
86
+ }
87
+
88
+ // Clear cursor position for session (when session ends)
89
+ public clearSessionCursor(sessionId: string) {
90
+ this.sessionCursorPositions.delete(sessionId);
91
+ }
92
+
93
+ // Clear all session cursors (when cleaning up all sessions)
94
+ public clearAllSessionCursors() {
95
+ const sessionCount = this.sessionCursorPositions.size;
96
+ this.sessionCursorPositions.clear();
97
+ }
98
+
99
+ async performAutonomousActions(
100
+ sessionId: string,
101
+ session: BrowserTab,
102
+ actions: BrowserAutonomousAction[],
103
+ isValidSession: () => boolean
104
+ ) {
105
+ let currentMousePos = this.getCurrentMousePosition(sessionId);
106
+ const results: any[] = [];
107
+
108
+ // Store session ID on page for cursor tracking
109
+ (session.page as any).__sessionId = sessionId;
110
+
111
+ for (let i = 0; i < actions.length; i++) {
112
+ // Check if session is still valid before each action
113
+ if (!isValidSession()) {
114
+ debug.warn('preview', `⚠️ Session ${sessionId} is no longer valid, stopping autonomous actions at ${i + 1}/${actions.length}`);
115
+ return;
116
+ }
117
+
118
+ const action = actions[i];
119
+
120
+ try {
121
+ switch (action.type) {
122
+ case 'wait':
123
+ await sleep(action.delay || 1000);
124
+ break;
125
+
126
+ case 'move':
127
+ if (action.x !== undefined && action.y !== undefined) {
128
+ // Always use MCP-optimized movement
129
+ await this.mcpMouseMove(
130
+ session.page,
131
+ currentMousePos.x,
132
+ currentMousePos.y,
133
+ action.x,
134
+ action.y,
135
+ action.steps || 8
136
+ );
137
+ currentMousePos = { x: action.x, y: action.y };
138
+ this.updateCursorPosition(sessionId, action.x, action.y);
139
+
140
+ // Emit to WebSocket for frontend virtual cursor
141
+ browserMcpControl.emitCursorPosition(sessionId, action.x, action.y);
142
+
143
+ this.emit('cursor-position', {
144
+ sessionId: session.id,
145
+ x: action.x,
146
+ y: action.y,
147
+ timestamp: Date.now()
148
+ });
149
+ }
150
+ break;
151
+
152
+ case 'type': {
153
+ // Clear existing input first if specified (default: true for MCP autonomous)
154
+ const shouldClear = action.clearFirst !== false; // default true
155
+
156
+ if (shouldClear && action.text) {
157
+ // Select all text first (Ctrl+A), then type will overwrite
158
+ await session.page.keyboard.down('Control');
159
+ await session.page.keyboard.press('KeyA');
160
+ await session.page.keyboard.up('Control');
161
+ await sleep(30); // Fast delay for MCP automation
162
+ }
163
+
164
+ // Native keyboard: type text OR press single key
165
+ if (action.text) {
166
+ // Always use MCP-optimized typing
167
+ await this.mcpType(session.page, action.text, action.delay || 30);
168
+ } else if (action.key) {
169
+ await session.page.keyboard.press(action.key as KeyInput);
170
+ }
171
+ break;
172
+ }
173
+
174
+ case 'click': {
175
+ // Native mouse click at coordinates
176
+ if (action.x === undefined || action.y === undefined) break;
177
+
178
+ const button = action.click || 'left';
179
+
180
+ // Move mouse to target position with MCP-optimized movement
181
+ await this.mcpMouseMove(
182
+ session.page,
183
+ currentMousePos.x,
184
+ currentMousePos.y,
185
+ action.x,
186
+ action.y,
187
+ 8
188
+ );
189
+
190
+ currentMousePos = { x: action.x, y: action.y };
191
+ this.updateCursorPosition(sessionId, action.x, action.y);
192
+
193
+ // Emit cursor position to WebSocket for frontend virtual cursor
194
+ browserMcpControl.emitCursorPosition(sessionId, action.x, action.y);
195
+
196
+ this.emit('cursor-position', {
197
+ sessionId: session.id,
198
+ x: action.x,
199
+ y: action.y,
200
+ timestamp: Date.now()
201
+ });
202
+
203
+ await sleep(30);
204
+
205
+ // Emit click event to WebSocket for frontend virtual cursor
206
+ browserMcpControl.emitCursorClick(sessionId, action.x, action.y);
207
+
208
+ this.emit('cursor-click', {
209
+ sessionId: session.id,
210
+ x: action.x,
211
+ y: action.y,
212
+ timestamp: Date.now()
213
+ });
214
+
215
+ // Native mouse click with slight variance for MCP automation
216
+ const finalX = action.x + (Math.random() - 0.5) * 2;
217
+ const finalY = action.y + (Math.random() - 0.5) * 2;
218
+ await session.page.mouse.click(finalX, finalY, { button });
219
+ break;
220
+ }
221
+
222
+ case 'scroll':
223
+ // Move cursor to target area first if coordinates provided
224
+ // This ensures scroll happens in the correct scrollable container (like human behavior)
225
+ if (action.x !== undefined && action.y !== undefined) {
226
+ await session.page.mouse.move(action.x, action.y, { steps: 1 });
227
+ currentMousePos = { x: action.x, y: action.y };
228
+ this.updateCursorPosition(sessionId, action.x, action.y);
229
+
230
+ // Emit cursor position for visual tracking
231
+ browserMcpControl.emitCursorPosition(sessionId, action.x, action.y);
232
+ this.emit('cursor-position', {
233
+ sessionId: session.id,
234
+ x: action.x,
235
+ y: action.y,
236
+ timestamp: Date.now()
237
+ });
238
+
239
+ await sleep(50); // Small delay after positioning
240
+ }
241
+
242
+ // Perform scroll using deltaX/deltaY
243
+ if (action.deltaX !== undefined || action.deltaY !== undefined) {
244
+ if (action.smooth) {
245
+ const steps = 5;
246
+ const stepX = (action.deltaX || 0) / steps;
247
+ const stepY = (action.deltaY || 0) / steps;
248
+
249
+ for (let s = 0; s < steps; s++) {
250
+ await session.page.mouse.wheel({ deltaX: stepX, deltaY: stepY });
251
+ await sleep(50);
252
+ }
253
+ } else {
254
+ await session.page.mouse.wheel({
255
+ deltaX: action.deltaX || 0,
256
+ deltaY: action.deltaY || 0
257
+ });
258
+ }
259
+ }
260
+ break;
261
+
262
+ case 'extract_data': {
263
+ // Extract data from DOM element - fully automatic selector and attribute detection
264
+ if (!action.selector) {
265
+ debug.warn('preview', `⚠️ extract_data action requires selector`);
266
+ results.push({
267
+ action: 'extract_data',
268
+ selector: undefined,
269
+ data: null,
270
+ error: 'selector is required',
271
+ timestamp: Date.now()
272
+ });
273
+ break;
274
+ }
275
+
276
+ const identifier = action.selector;
277
+
278
+ try {
279
+ // Smart extraction - try all selector patterns and all attributes automatically
280
+ const result = await session.page.evaluate((id: string) => {
281
+ // Try multiple selector patterns
282
+ const selectors = [
283
+ id, // exact (user might already include # or .)
284
+ `#${id}`, // ID selector
285
+ `.${id}`, // class selector
286
+ `[id="${id}"]`, // attribute exact match
287
+ `[id*="${id}"]`, // ID contains
288
+ `[class*="${id}"]`, // class contains
289
+ `[data-testid="${id}"]`, // test ID
290
+ `[name="${id}"]`, // name attribute
291
+ id.toLowerCase(), // lowercase tag
292
+ `#${id.toLowerCase()}`, // lowercase ID
293
+ `.${id.toLowerCase()}`, // lowercase class
294
+ ];
295
+
296
+ let element: Element | null = null;
297
+ let usedSelector = '';
298
+
299
+ // Try each selector until one works
300
+ for (const selector of selectors) {
301
+ try {
302
+ element = document.querySelector(selector);
303
+ if (element) {
304
+ usedSelector = selector;
305
+ break;
306
+ }
307
+ } catch (e) {
308
+ // Invalid selector, skip
309
+ continue;
310
+ }
311
+ }
312
+
313
+ if (!element) {
314
+ return { data: null, selector: null, attribute: null, tried: selectors.slice(0, 5) };
315
+ }
316
+
317
+ // Smart attribute extraction - try all common attributes and return first non-empty
318
+ const extractors = [
319
+ { name: 'value', fn: () => (element as HTMLInputElement).value },
320
+ { name: 'textContent', fn: () => element!.textContent?.trim() },
321
+ { name: 'innerText', fn: () => (element as HTMLElement).innerText?.trim() },
322
+ { name: 'innerHTML', fn: () => element!.innerHTML?.trim() },
323
+ ];
324
+
325
+ let data = null;
326
+ let usedAttribute = '';
327
+
328
+ for (const extractor of extractors) {
329
+ try {
330
+ const extracted = extractor.fn();
331
+ if (extracted && extracted.length > 0) {
332
+ data = extracted;
333
+ usedAttribute = extractor.name;
334
+ break;
335
+ }
336
+ } catch (e) {
337
+ // Attribute doesn't exist or failed, continue
338
+ continue;
339
+ }
340
+ }
341
+
342
+ return {
343
+ data,
344
+ selector: usedSelector,
345
+ attribute: usedAttribute,
346
+ tried: data ? [usedSelector] : selectors.slice(0, 5)
347
+ };
348
+ }, identifier);
349
+
350
+ if (result.data !== null) {
351
+ results.push({
352
+ action: 'extract_data',
353
+ selector: result.selector,
354
+ attribute: result.attribute,
355
+ data: result.data,
356
+ timestamp: Date.now()
357
+ });
358
+ } else {
359
+ results.push({
360
+ action: 'extract_data',
361
+ selector: identifier,
362
+ attribute: null,
363
+ data: null,
364
+ error: `Element not found or empty. Tried selectors: ${result.tried.join(', ')}`,
365
+ timestamp: Date.now()
366
+ });
367
+ }
368
+ } catch (extractError) {
369
+ debug.error('preview', `❌ Error extracting data from ${identifier}:`, extractError);
370
+ results.push({
371
+ action: 'extract_data',
372
+ selector: identifier,
373
+ attribute: null,
374
+ data: null,
375
+ error: (extractError as Error)?.message || 'Unknown error',
376
+ timestamp: Date.now()
377
+ });
378
+ }
379
+ break;
380
+ }
381
+
382
+ default:
383
+ debug.warn('preview', `⚠️ Unknown action type: ${(action as any).type}`);
384
+ }
385
+
386
+ // Fast delay between actions for MCP automation (50-100ms)
387
+ const betweenActionDelay = 50 + Math.random() * 50;
388
+
389
+ // Don't add delay after explicit wait actions
390
+ if (action.type !== 'wait') {
391
+ await sleep(betweenActionDelay);
392
+ }
393
+
394
+ } catch (error) {
395
+ debug.error('preview', `❌ Error performing action ${action.type}:`, error);
396
+
397
+ // Check if error is due to closed page/browser
398
+ const errorMessage = (error as Error)?.message || '';
399
+ if (errorMessage.includes('Target page, context or browser has been closed') ||
400
+ errorMessage.includes('Browser has been closed') ||
401
+ errorMessage.includes('Page has been closed')) {
402
+
403
+ debug.warn('preview', `⚠️ Browser/page closed during action ${i + 1}/${actions.length}, stopping autonomous actions`);
404
+ return;
405
+ }
406
+
407
+ // Continue with next action for other errors
408
+ }
409
+ }
410
+
411
+
412
+ // Emit test completed event to hide virtual cursor
413
+ this.emit('test-completed', {
414
+ sessionId: session.id,
415
+ timestamp: Date.now()
416
+ });
417
+
418
+ return results;
419
+ }
420
+
421
+ }