@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,707 @@
1
+ /**
2
+ * Chat Streaming Events (Optimized)
3
+ *
4
+ * High-performance event-driven chat streaming with:
5
+ * - Zero polling (pure EventEmitter push)
6
+ * - Automatic cleanup on disconnect
7
+ * - Support for stream cancellation
8
+ */
9
+
10
+ import { t } from 'elysia';
11
+ import { createRouter } from '$shared/utils/ws-server';
12
+ import { streamManager, type StreamEvent } from '../../lib/chat/stream-manager';
13
+ import { debug } from '$shared/utils/logger';
14
+ import { ws } from '$backend/lib/utils/ws';
15
+ import { broadcastPresence } from '../projects/status';
16
+ import { sessionQueries } from '../../lib/database/queries';
17
+
18
+ // ============================================================================
19
+ // Global stream lifecycle handler (module-level, not per-connection)
20
+ //
21
+ // This fires for ALL stream completions, even when no per-connection subscriber
22
+ // exists (e.g., after browser refresh when user is on a different project).
23
+ // Ensures cross-project notifications (presence update, sound, push) always work.
24
+ // ============================================================================
25
+ streamManager.on('stream:lifecycle', (event: { status: string; streamId: string; projectId?: string; chatSessionId?: string; timestamp: string }) => {
26
+ const { status, projectId, chatSessionId, timestamp } = event;
27
+ if (!projectId) return;
28
+
29
+ debug.log('chat', `Stream lifecycle: ${status} for project ${projectId} session ${chatSessionId}`);
30
+
31
+ // Notify all project members (cross-project notification for sound + push)
32
+ ws.emit.projectMembers(projectId, 'chat:stream-finished', {
33
+ projectId,
34
+ chatSessionId: chatSessionId || '',
35
+ status: status as 'completed' | 'error' | 'cancelled',
36
+ timestamp
37
+ });
38
+
39
+ // Broadcast updated presence (status indicators for all projects)
40
+ broadcastPresence().catch(() => {});
41
+ });
42
+
43
+ // In-memory store for latest chat input state per chat session (keyed by chatSessionId)
44
+ const chatSessionInputState = new Map<string, { text: string; senderId: string; attachments?: any[] }>();
45
+
46
+ // In-memory store for edit mode state per chat session (keyed by chatSessionId)
47
+ const chatSessionEditMode = new Map<string, { isEditing: boolean; messageId: string | null; messageTimestamp: string | null }>();
48
+
49
+ // In-memory store for model state per chat session (keyed by chatSessionId)
50
+ const chatSessionModelState = new Map<string, { engine: string; model: string; senderId: string }>();
51
+
52
+ // In-memory store for account state per chat session (keyed by chatSessionId)
53
+ const chatSessionAccountState = new Map<string, { claudeAccountId: number | null; senderId: string }>();
54
+
55
+ export const streamHandler = createRouter()
56
+ // Join a chat session room (subscribe to chat events for this session)
57
+ .on('chat:join-session', {
58
+ data: t.Object({
59
+ chatSessionId: t.String()
60
+ })
61
+ }, ({ data, conn }) => {
62
+ // Leave all previous chat sessions first (1 session at a time per connection)
63
+ ws.leaveAllChatSessions(conn);
64
+ ws.joinChatSession(conn, data.chatSessionId);
65
+ // Broadcast presence so all clients see updated chatSessionUsers
66
+ broadcastPresence().catch(() => {});
67
+ })
68
+
69
+ // Leave a chat session room
70
+ .on('chat:leave-session', {
71
+ data: t.Object({
72
+ chatSessionId: t.String()
73
+ })
74
+ }, ({ data, conn }) => {
75
+ ws.leaveChatSession(conn, data.chatSessionId);
76
+ // Broadcast presence so all clients see updated chatSessionUsers
77
+ broadcastPresence().catch(() => {});
78
+ })
79
+
80
+ // Start chat stream
81
+ .on('chat:stream', {
82
+ data: t.Object({
83
+ sessionId: t.String(),
84
+ chatSessionId: t.String(),
85
+ projectPath: t.String(),
86
+ prompt: t.Any(), // SDKUserMessage object
87
+ messages: t.Optional(t.Array(t.Any())),
88
+ engine: t.Optional(t.Union([t.Literal('claude-code'), t.Literal('opencode')])),
89
+ model: t.Optional(t.String()),
90
+ temperature: t.Optional(t.Number()),
91
+ senderId: t.Optional(t.String()),
92
+ senderName: t.Optional(t.String()),
93
+ claudeAccountId: t.Optional(t.Number())
94
+ })
95
+ }, async ({ data, conn }) => {
96
+ const projectId = ws.getProjectId(conn);
97
+
98
+ try {
99
+ debug.log('chat', 'WS chat:stream received:', {
100
+ chatSessionId: data.chatSessionId,
101
+ projectId,
102
+ messagesCount: data.messages?.length || 0
103
+ });
104
+
105
+ // Start background stream
106
+ const streamId = await streamManager.startStream({
107
+ projectPath: data.projectPath,
108
+ projectId,
109
+ prompt: data.prompt,
110
+ messages: data.messages || [],
111
+ chatSessionId: data.chatSessionId,
112
+ engine: data.engine || 'claude-code',
113
+ model: data.model,
114
+ temperature: data.temperature,
115
+ senderId: data.senderId,
116
+ senderName: data.senderName,
117
+ claudeAccountId: data.claudeAccountId
118
+ });
119
+
120
+ debug.log('chat', 'Stream started with ID:', streamId);
121
+
122
+ // Emit connection event to chat session room and broadcast presence immediately
123
+ // (the connection event from startStream() fires before subscription, so we emit it manually)
124
+ const stream = streamManager.getStream(streamId);
125
+ if (stream) {
126
+ ws.emit.chatSession(data.chatSessionId, 'chat:connection', {
127
+ processId: stream.processId,
128
+ timestamp: stream.startedAt.toISOString(),
129
+ seq: 1
130
+ });
131
+ // User message is broadcast by stream-manager via event subscription below
132
+ // (includes resume, sender info, and saved message ID)
133
+ }
134
+ broadcastPresence().catch(() => {});
135
+
136
+ // Subscribe to stream events (event-driven, no polling)
137
+ // Use ws.emit.chatSession() for session-scoped chat events
138
+ // Only users who joined this chat session room receive events
139
+ const chatSessionId = data.chatSessionId;
140
+ const handleStreamEvent = (event: StreamEvent) => {
141
+ try {
142
+ switch (event.type) {
143
+ case 'connection':
144
+ ws.emit.chatSession(chatSessionId, 'chat:connection', {
145
+ processId: event.data.processId,
146
+ timestamp: event.data.timestamp,
147
+ seq: event.seq
148
+ });
149
+ broadcastPresence().catch(() => {});
150
+ break;
151
+
152
+ case 'message':
153
+ ws.emit.chatSession(chatSessionId, 'chat:message', {
154
+ processId: event.processId,
155
+ message: event.data.message,
156
+ usage: event.data.usage,
157
+ timestamp: event.data.timestamp,
158
+ message_id: event.data.message_id,
159
+ parent_message_id: event.data.parent_message_id,
160
+ sender_id: event.data.sender_id,
161
+ sender_name: event.data.sender_name,
162
+ engine: event.data.engine,
163
+ seq: event.seq
164
+ });
165
+ break;
166
+
167
+ case 'partial':
168
+ ws.emit.chatSession(chatSessionId, 'chat:partial', {
169
+ processId: event.processId,
170
+ eventType: event.data.eventType as any,
171
+ partialText: event.data.partialText || '',
172
+ deltaText: event.data.deltaText || '',
173
+ ...(event.data.reasoning && { reasoning: true }),
174
+ timestamp: event.data.timestamp,
175
+ seq: event.seq
176
+ });
177
+ break;
178
+
179
+ case 'notification':
180
+ ws.emit.chatSession(chatSessionId, 'chat:notification', {
181
+ notification: event.data.notification,
182
+ timestamp: event.data.timestamp,
183
+ seq: event.seq
184
+ });
185
+ break;
186
+
187
+ case 'complete':
188
+ ws.emit.chatSession(chatSessionId, 'chat:complete', {
189
+ processId: event.processId,
190
+ timestamp: event.data.timestamp,
191
+ seq: event.seq
192
+ });
193
+ // Cross-project notifications (stream-finished, presence) are handled
194
+ // by the global stream:lifecycle listener at the module level.
195
+ unsubscribe();
196
+ break;
197
+
198
+ case 'error':
199
+ ws.emit.chatSession(chatSessionId, 'chat:error', {
200
+ processId: event.processId,
201
+ error: event.data.error,
202
+ timestamp: event.data.timestamp,
203
+ seq: event.seq
204
+ });
205
+ // Cross-project notifications handled by stream:lifecycle listener
206
+ unsubscribe();
207
+ break;
208
+
209
+ case 'cancelled':
210
+ ws.emit.chatSession(chatSessionId, 'chat:error', {
211
+ processId: event.processId,
212
+ error: 'Stream cancelled',
213
+ timestamp: event.data.timestamp,
214
+ seq: event.seq
215
+ });
216
+ // Cross-project notifications handled by stream:lifecycle listener
217
+ unsubscribe();
218
+ break;
219
+ }
220
+ } catch (err) {
221
+ debug.error('chat', 'Error handling stream event:', err);
222
+ unsubscribe();
223
+ }
224
+ };
225
+
226
+ // Subscribe to stream events
227
+ const unsubscribe = streamManager.subscribeToStream(streamId, handleStreamEvent);
228
+
229
+ // Register cleanup with WSServer (called automatically on connection close)
230
+ ws.addCleanup(conn, unsubscribe);
231
+
232
+ } catch (error) {
233
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
234
+ debug.error('chat', 'WS chat:stream error:', errorMessage);
235
+
236
+ ws.emit.chatSession(data.chatSessionId, 'chat:error', {
237
+ processId: crypto.randomUUID(),
238
+ error: errorMessage,
239
+ timestamp: new Date().toISOString()
240
+ });
241
+ }
242
+ })
243
+
244
+ // Reconnect to an active stream (after browser refresh / project switch)
245
+ // Re-subscribes the new connection to receive live stream events
246
+ .on('chat:reconnect', {
247
+ data: t.Object({
248
+ chatSessionId: t.String()
249
+ })
250
+ }, async ({ data, conn }) => {
251
+ const projectId = ws.getProjectId(conn);
252
+
253
+ try {
254
+ const chatSessionId = data.chatSessionId;
255
+ const streamState = streamManager.getSessionStream(chatSessionId, projectId);
256
+ if (!streamState || streamState.status !== 'active') {
257
+ // No active stream - nothing to reconnect
258
+ return;
259
+ }
260
+
261
+ debug.log('chat', 'Reconnecting to active stream:', streamState.streamId);
262
+
263
+ // Subscribe this connection to the stream's events (session-scoped)
264
+ const handleStreamEvent = (event: StreamEvent) => {
265
+ try {
266
+ switch (event.type) {
267
+ case 'connection':
268
+ ws.emit.chatSession(chatSessionId, 'chat:connection', {
269
+ processId: event.data.processId,
270
+ timestamp: event.data.timestamp,
271
+ seq: event.seq
272
+ });
273
+ break;
274
+
275
+ case 'message':
276
+ ws.emit.chatSession(chatSessionId, 'chat:message', {
277
+ processId: event.processId,
278
+ message: event.data.message,
279
+ usage: event.data.usage,
280
+ timestamp: event.data.timestamp,
281
+ message_id: event.data.message_id,
282
+ parent_message_id: event.data.parent_message_id,
283
+ sender_id: event.data.sender_id,
284
+ sender_name: event.data.sender_name,
285
+ engine: event.data.engine,
286
+ seq: event.seq
287
+ });
288
+ break;
289
+
290
+ case 'partial':
291
+ ws.emit.chatSession(chatSessionId, 'chat:partial', {
292
+ processId: event.processId,
293
+ eventType: event.data.eventType as any,
294
+ partialText: event.data.partialText || '',
295
+ deltaText: event.data.deltaText || '',
296
+ ...(event.data.reasoning && { reasoning: true }),
297
+ timestamp: event.data.timestamp,
298
+ seq: event.seq
299
+ });
300
+ break;
301
+
302
+ case 'notification':
303
+ ws.emit.chatSession(chatSessionId, 'chat:notification', {
304
+ notification: event.data.notification,
305
+ timestamp: event.data.timestamp,
306
+ seq: event.seq
307
+ });
308
+ break;
309
+
310
+ case 'complete':
311
+ ws.emit.chatSession(chatSessionId, 'chat:complete', {
312
+ processId: event.processId,
313
+ timestamp: event.data.timestamp,
314
+ seq: event.seq
315
+ });
316
+ // Cross-project notifications handled by stream:lifecycle listener
317
+ unsubscribe();
318
+ break;
319
+
320
+ case 'error':
321
+ ws.emit.chatSession(chatSessionId, 'chat:error', {
322
+ processId: event.processId,
323
+ error: event.data.error,
324
+ timestamp: event.data.timestamp,
325
+ seq: event.seq
326
+ });
327
+ // Cross-project notifications handled by stream:lifecycle listener
328
+ unsubscribe();
329
+ break;
330
+
331
+ case 'cancelled':
332
+ ws.emit.chatSession(chatSessionId, 'chat:error', {
333
+ processId: event.processId,
334
+ error: 'Stream cancelled',
335
+ timestamp: event.data.timestamp,
336
+ seq: event.seq
337
+ });
338
+ // Cross-project notifications handled by stream:lifecycle listener
339
+ unsubscribe();
340
+ break;
341
+ }
342
+ } catch (err) {
343
+ debug.error('chat', 'Error handling reconnected stream event:', err);
344
+ unsubscribe();
345
+ }
346
+ };
347
+
348
+ const unsubscribe = streamManager.subscribeToStream(streamState.streamId, handleStreamEvent);
349
+ ws.addCleanup(conn, unsubscribe);
350
+
351
+ // Send current state snapshot to chat session room so frontend can catch up
352
+ ws.emit.chatSession(chatSessionId, 'chat:connection', {
353
+ processId: streamState.processId,
354
+ timestamp: streamState.startedAt.toISOString(),
355
+ seq: streamState.eventSeq
356
+ });
357
+
358
+ } catch (error) {
359
+ debug.error('chat', 'Error reconnecting to stream:', error);
360
+ }
361
+ })
362
+
363
+ // Cancel stream
364
+ .on('chat:cancel', {
365
+ data: t.Object({
366
+ sessionId: t.String(),
367
+ chatSessionId: t.String()
368
+ })
369
+ }, async ({ data, conn }) => {
370
+ const projectId = ws.getProjectId(conn);
371
+ const chatSessionId = data.chatSessionId;
372
+
373
+ try {
374
+ // Find stream by session
375
+ const streamState = streamManager.getSessionStream(chatSessionId, projectId);
376
+ if (!streamState) {
377
+ // Stream not found - could already be completed/cleaned up
378
+ // Send cancellation to chat session room so frontend stops loading
379
+ ws.emit.chatSession(chatSessionId, 'chat:cancelled', {
380
+ status: 'cancelled',
381
+ processId: ''
382
+ });
383
+ broadcastPresence().catch(() => {});
384
+ return;
385
+ }
386
+
387
+ const cancelled = await streamManager.cancelStream(streamState.streamId);
388
+ // Always send cancelled to chat session room to clear UI
389
+ ws.emit.chatSession(chatSessionId, 'chat:cancelled', {
390
+ status: 'cancelled',
391
+ processId: streamState.processId
392
+ });
393
+ // Always broadcast presence after cancel attempt to update all clients
394
+ broadcastPresence().catch(() => {});
395
+ } catch (error) {
396
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
397
+ ws.emit.chatSession(chatSessionId, 'chat:error', {
398
+ processId: '',
399
+ error: errorMessage,
400
+ timestamp: new Date().toISOString()
401
+ });
402
+ broadcastPresence().catch(() => {});
403
+ }
404
+ })
405
+
406
+ // Collaborative edit mode - broadcast and store edit mode state per chat session
407
+ .on('chat:edit-mode', {
408
+ data: t.Object({
409
+ senderId: t.String(),
410
+ chatSessionId: t.String(),
411
+ isEditing: t.Boolean(),
412
+ messageId: t.Union([t.String(), t.Null()]),
413
+ messageTimestamp: t.Union([t.String(), t.Null()])
414
+ })
415
+ }, ({ data }) => {
416
+ const chatSessionId = data.chatSessionId;
417
+
418
+ // Store on server for late joiners / refresh (keyed by chatSessionId)
419
+ if (data.isEditing && data.messageId) {
420
+ chatSessionEditMode.set(chatSessionId, {
421
+ isEditing: true,
422
+ messageId: data.messageId,
423
+ messageTimestamp: data.messageTimestamp
424
+ });
425
+ } else {
426
+ chatSessionEditMode.delete(chatSessionId);
427
+ }
428
+
429
+ ws.emit.chatSession(chatSessionId, 'chat:edit-mode', {
430
+ senderId: data.senderId,
431
+ isEditing: data.isEditing,
432
+ messageId: data.messageId,
433
+ messageTimestamp: data.messageTimestamp
434
+ });
435
+ })
436
+
437
+ // Get current edit mode state for a chat session (for refresh / late joiners)
438
+ .http('chat:get-edit-mode', {
439
+ data: t.Object({
440
+ chatSessionId: t.Optional(t.String())
441
+ }),
442
+ response: t.Object({
443
+ isEditing: t.Boolean(),
444
+ messageId: t.Union([t.String(), t.Null()]),
445
+ messageTimestamp: t.Union([t.String(), t.Null()])
446
+ })
447
+ }, ({ data }) => {
448
+ const chatSessionId = data.chatSessionId || '';
449
+ const editState = chatSessionEditMode.get(chatSessionId);
450
+ return editState || { isEditing: false, messageId: null, messageTimestamp: null };
451
+ })
452
+
453
+ // Collaborative input sync - broadcast typing/attachments to other users in the same chat session
454
+ .on('chat:input-sync', {
455
+ data: t.Object({
456
+ text: t.String(),
457
+ senderId: t.String(),
458
+ chatSessionId: t.String(),
459
+ attachments: t.Optional(t.Array(t.Object({
460
+ id: t.String(),
461
+ fileName: t.String(),
462
+ type: t.String(),
463
+ mediaType: t.String(),
464
+ base64: t.String()
465
+ })))
466
+ })
467
+ }, ({ data }) => {
468
+ const chatSessionId = data.chatSessionId;
469
+
470
+ // Store latest input state on server for late-joining users (keyed by chatSessionId)
471
+ chatSessionInputState.set(chatSessionId, {
472
+ text: data.text,
473
+ senderId: data.senderId,
474
+ attachments: data.attachments
475
+ });
476
+
477
+ ws.emit.chatSession(chatSessionId, 'chat:input-sync', {
478
+ text: data.text,
479
+ senderId: data.senderId,
480
+ attachments: data.attachments
481
+ });
482
+ })
483
+
484
+ // Get latest input state for a chat session (for users switching sessions)
485
+ .http('chat:get-input-state', {
486
+ data: t.Object({
487
+ chatSessionId: t.Optional(t.String())
488
+ }),
489
+ response: t.Object({
490
+ text: t.String(),
491
+ senderId: t.String(),
492
+ attachments: t.Optional(t.Array(t.Object({
493
+ id: t.String(),
494
+ fileName: t.String(),
495
+ type: t.String(),
496
+ mediaType: t.String(),
497
+ base64: t.String()
498
+ })))
499
+ })
500
+ }, ({ data }) => {
501
+ const chatSessionId = data.chatSessionId || '';
502
+ const state = chatSessionInputState.get(chatSessionId);
503
+ return state || { text: '', senderId: '' };
504
+ })
505
+
506
+ // Collaborative model sync - broadcast model changes to other users in the same chat session
507
+ .on('chat:model-sync', {
508
+ data: t.Object({
509
+ senderId: t.String(),
510
+ chatSessionId: t.String(),
511
+ engine: t.String(),
512
+ model: t.String()
513
+ })
514
+ }, ({ data }) => {
515
+ const chatSessionId = data.chatSessionId;
516
+
517
+ // Store latest model state on server for late joiners / refresh
518
+ chatSessionModelState.set(chatSessionId, {
519
+ engine: data.engine,
520
+ model: data.model,
521
+ senderId: data.senderId
522
+ });
523
+
524
+ // Persist engine/model to the session record in the database
525
+ // so refreshes and late joiners get the correct model
526
+ try {
527
+ sessionQueries.updateEngineModel(chatSessionId, data.engine, data.model);
528
+ } catch (err) {
529
+ debug.error('chat', 'Failed to persist model sync to DB:', err);
530
+ }
531
+
532
+ // Broadcast to all users in the same chat session
533
+ ws.emit.chatSession(chatSessionId, 'chat:model-sync', {
534
+ senderId: data.senderId,
535
+ engine: data.engine,
536
+ model: data.model
537
+ });
538
+ })
539
+
540
+ // Get current model state for a chat session (for refresh / late joiners)
541
+ .http('chat:get-model-state', {
542
+ data: t.Object({
543
+ chatSessionId: t.Optional(t.String())
544
+ }),
545
+ response: t.Object({
546
+ engine: t.String(),
547
+ model: t.String(),
548
+ senderId: t.String()
549
+ })
550
+ }, ({ data }) => {
551
+ const chatSessionId = data.chatSessionId || '';
552
+ const state = chatSessionModelState.get(chatSessionId);
553
+ return state || { engine: '', model: '', senderId: '' };
554
+ })
555
+
556
+ // Collaborative account sync - broadcast account changes to other users in the same chat session
557
+ .on('chat:account-sync', {
558
+ data: t.Object({
559
+ senderId: t.String(),
560
+ chatSessionId: t.String(),
561
+ claudeAccountId: t.Union([t.Number(), t.Null()])
562
+ })
563
+ }, ({ data }) => {
564
+ const chatSessionId = data.chatSessionId;
565
+
566
+ // Store latest account state on server for late joiners / refresh
567
+ chatSessionAccountState.set(chatSessionId, {
568
+ claudeAccountId: data.claudeAccountId,
569
+ senderId: data.senderId
570
+ });
571
+
572
+ // Persist claude_account_id to the session record in the database
573
+ try {
574
+ sessionQueries.updateClaudeAccountId(chatSessionId, data.claudeAccountId);
575
+ } catch (err) {
576
+ debug.error('chat', 'Failed to persist account sync to DB:', err);
577
+ }
578
+
579
+ // Broadcast to all users in the same chat session
580
+ ws.emit.chatSession(chatSessionId, 'chat:account-sync', {
581
+ senderId: data.senderId,
582
+ claudeAccountId: data.claudeAccountId
583
+ });
584
+ })
585
+
586
+ // Get current account state for a chat session (for refresh / late joiners)
587
+ .http('chat:get-account-state', {
588
+ data: t.Object({
589
+ chatSessionId: t.Optional(t.String())
590
+ }),
591
+ response: t.Object({
592
+ claudeAccountId: t.Union([t.Number(), t.Null()]),
593
+ senderId: t.String()
594
+ })
595
+ }, ({ data }) => {
596
+ const chatSessionId = data.chatSessionId || '';
597
+ const state = chatSessionAccountState.get(chatSessionId);
598
+ return state || { claudeAccountId: null, senderId: '' };
599
+ })
600
+
601
+ // Event declarations
602
+ .emit('chat:edit-mode', t.Object({
603
+ senderId: t.String(),
604
+ isEditing: t.Boolean(),
605
+ messageId: t.Union([t.String(), t.Null()]),
606
+ messageTimestamp: t.Union([t.String(), t.Null()])
607
+ }))
608
+
609
+ .emit('chat:input-sync', t.Object({
610
+ text: t.String(),
611
+ senderId: t.String(),
612
+ attachments: t.Optional(t.Array(t.Object({
613
+ id: t.String(),
614
+ fileName: t.String(),
615
+ type: t.String(),
616
+ mediaType: t.String(),
617
+ base64: t.String()
618
+ }))),
619
+ chatSessionId: t.Optional(t.String())
620
+ }))
621
+
622
+ .emit('chat:model-sync', t.Object({
623
+ senderId: t.String(),
624
+ engine: t.String(),
625
+ model: t.String()
626
+ }))
627
+
628
+ .emit('chat:account-sync', t.Object({
629
+ senderId: t.String(),
630
+ claudeAccountId: t.Union([t.Number(), t.Null()])
631
+ }))
632
+
633
+ .emit('chat:connection', t.Object({
634
+ processId: t.String(),
635
+ timestamp: t.String(),
636
+ seq: t.Optional(t.Number())
637
+ }))
638
+
639
+ .emit('chat:message', t.Object({
640
+ processId: t.String(),
641
+ message: t.Any(), // SDKMessage
642
+ usage: t.Optional(t.Any()),
643
+ timestamp: t.String(),
644
+ message_id: t.Optional(t.String()),
645
+ parent_message_id: t.Optional(t.Union([t.String(), t.Null()])),
646
+ sender_id: t.Optional(t.String()),
647
+ sender_name: t.Optional(t.String()),
648
+ engine: t.Optional(t.String()),
649
+ seq: t.Optional(t.Number())
650
+ }))
651
+
652
+ .emit('chat:partial', t.Object({
653
+ processId: t.String(),
654
+ eventType: t.Union([
655
+ t.Literal('start'),
656
+ t.Literal('update'),
657
+ t.Literal('end')
658
+ ]),
659
+ partialText: t.String(),
660
+ deltaText: t.String(),
661
+ reasoning: t.Optional(t.Boolean()),
662
+ timestamp: t.String(),
663
+ seq: t.Optional(t.Number())
664
+ }))
665
+
666
+ .emit('chat:notification', t.Object({
667
+ notification: t.Object({
668
+ type: t.String(),
669
+ title: t.String(),
670
+ message: t.String(),
671
+ icon: t.Optional(t.String())
672
+ }),
673
+ timestamp: t.String(),
674
+ seq: t.Optional(t.Number())
675
+ }))
676
+
677
+ .emit('chat:complete', t.Object({
678
+ processId: t.String(),
679
+ timestamp: t.String(),
680
+ seq: t.Optional(t.Number())
681
+ }))
682
+
683
+ .emit('chat:error', t.Object({
684
+ processId: t.String(),
685
+ error: t.String(),
686
+ timestamp: t.String(),
687
+ seq: t.Optional(t.Number())
688
+ }))
689
+
690
+ .emit('chat:cancelled', t.Object({
691
+ status: t.Literal('cancelled'),
692
+ processId: t.Optional(t.String()),
693
+ seq: t.Optional(t.Number())
694
+ }))
695
+
696
+ .emit('chat:messages-changed', t.Object({
697
+ sessionId: t.String(),
698
+ reason: t.String(),
699
+ timestamp: t.String()
700
+ }))
701
+
702
+ .emit('chat:stream-finished', t.Object({
703
+ projectId: t.String(),
704
+ chatSessionId: t.String(),
705
+ status: t.Union([t.Literal('completed'), t.Literal('error'), t.Literal('cancelled')]),
706
+ timestamp: t.String()
707
+ }));