@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,87 @@
1
+ /**
2
+ * Checkpoint Tree State Queries
3
+ *
4
+ * Manages the "active child" tracking per checkpoint node.
5
+ * This determines which child continues the straight/main line
6
+ * in the tree display, and which children are branches.
7
+ */
8
+
9
+ import { getDatabase } from '../index';
10
+ import { debug } from '$shared/utils/logger';
11
+
12
+ export const checkpointQueries = {
13
+ /**
14
+ * Get active child for a checkpoint
15
+ */
16
+ getActiveChild(sessionId: string, parentCheckpointId: string): string | null {
17
+ const db = getDatabase();
18
+ const row = db.prepare(`
19
+ SELECT active_child_id FROM checkpoint_tree_state
20
+ WHERE session_id = ? AND parent_checkpoint_id = ?
21
+ `).get(sessionId, parentCheckpointId) as { active_child_id: string } | null;
22
+
23
+ return row?.active_child_id || null;
24
+ },
25
+
26
+ /**
27
+ * Set active child for a checkpoint
28
+ */
29
+ setActiveChild(sessionId: string, parentCheckpointId: string, activeChildId: string): void {
30
+ const db = getDatabase();
31
+ db.prepare(`
32
+ INSERT OR REPLACE INTO checkpoint_tree_state
33
+ (session_id, parent_checkpoint_id, active_child_id)
34
+ VALUES (?, ?, ?)
35
+ `).run(sessionId, parentCheckpointId, activeChildId);
36
+ },
37
+
38
+ /**
39
+ * Get all active children for a session (for building the tree)
40
+ */
41
+ getAllActiveChildren(sessionId: string): Map<string, string> {
42
+ const db = getDatabase();
43
+ const rows = db.prepare(`
44
+ SELECT parent_checkpoint_id, active_child_id FROM checkpoint_tree_state
45
+ WHERE session_id = ?
46
+ `).all(sessionId) as { parent_checkpoint_id: string; active_child_id: string }[];
47
+
48
+ const map = new Map<string, string>();
49
+ for (const row of rows) {
50
+ map.set(row.parent_checkpoint_id, row.active_child_id);
51
+ }
52
+ return map;
53
+ },
54
+
55
+ /**
56
+ * Update active children along a path from root to target checkpoint.
57
+ * This is called when restoring to a checkpoint or creating a new one.
58
+ *
59
+ * @param sessionId - The chat session ID
60
+ * @param checkpointPath - Ordered list of checkpoint IDs from root to target
61
+ */
62
+ updateActiveChildrenAlongPath(sessionId: string, checkpointPath: string[]): void {
63
+ const db = getDatabase();
64
+
65
+ const stmt = db.prepare(`
66
+ INSERT OR REPLACE INTO checkpoint_tree_state
67
+ (session_id, parent_checkpoint_id, active_child_id)
68
+ VALUES (?, ?, ?)
69
+ `);
70
+
71
+ for (let i = 0; i < checkpointPath.length - 1; i++) {
72
+ stmt.run(sessionId, checkpointPath[i], checkpointPath[i + 1]);
73
+ }
74
+
75
+ debug.log('snapshot', `Updated ${checkpointPath.length - 1} active child links for session ${sessionId}`);
76
+ },
77
+
78
+ /**
79
+ * Delete all tree state for a session (cleanup)
80
+ */
81
+ deleteForSession(sessionId: string): void {
82
+ const db = getDatabase();
83
+ db.prepare(`
84
+ DELETE FROM checkpoint_tree_state WHERE session_id = ?
85
+ `).run(sessionId);
86
+ }
87
+ };
@@ -0,0 +1,75 @@
1
+ import { getDatabase } from '../index';
2
+
3
+ export interface ClaudeAccount {
4
+ id: number;
5
+ name: string;
6
+ oauth_token: string;
7
+ is_active: number;
8
+ created_at: string;
9
+ }
10
+
11
+ export const engineQueries = {
12
+ getClaudeAccounts(): ClaudeAccount[] {
13
+ const db = getDatabase();
14
+ return db.prepare(`
15
+ SELECT * FROM claude_accounts
16
+ ORDER BY created_at ASC
17
+ `).all() as ClaudeAccount[];
18
+ },
19
+
20
+ getActiveClaudeAccount(): ClaudeAccount | null {
21
+ const db = getDatabase();
22
+ return db.prepare(`
23
+ SELECT * FROM claude_accounts WHERE is_active = 1
24
+ `).get() as ClaudeAccount | null;
25
+ },
26
+
27
+ createClaudeAccount(name: string, token: string): ClaudeAccount {
28
+ const db = getDatabase();
29
+
30
+ // Check if this is the first account
31
+ const count = (db.prepare('SELECT COUNT(*) as count FROM claude_accounts').get() as { count: number }).count;
32
+ const isActive = count === 0 ? 1 : 0;
33
+
34
+ db.prepare(`
35
+ INSERT INTO claude_accounts (name, oauth_token, is_active)
36
+ VALUES (?, ?, ?)
37
+ `).run(name, token, isActive);
38
+
39
+ const inserted = db.prepare('SELECT last_insert_rowid() as id').get() as { id: number };
40
+ const id = inserted.id;
41
+
42
+ return db.prepare('SELECT * FROM claude_accounts WHERE id = ?').get(id) as ClaudeAccount;
43
+ },
44
+
45
+ switchClaudeAccount(id: number): void {
46
+ const db = getDatabase();
47
+ db.prepare('UPDATE claude_accounts SET is_active = 0').run();
48
+ db.prepare('UPDATE claude_accounts SET is_active = 1 WHERE id = ?').run(id);
49
+ },
50
+
51
+ deleteClaudeAccount(id: number): void {
52
+ const db = getDatabase();
53
+
54
+ // Check if the account being deleted is active
55
+ const account = db.prepare('SELECT * FROM claude_accounts WHERE id = ?').get(id) as ClaudeAccount | null;
56
+ if (!account) return;
57
+
58
+ const wasActive = account.is_active === 1;
59
+
60
+ db.prepare('DELETE FROM claude_accounts WHERE id = ?').run(id);
61
+
62
+ // If the deleted account was active, activate the first remaining account
63
+ if (wasActive) {
64
+ const remaining = db.prepare('SELECT id FROM claude_accounts ORDER BY created_at ASC LIMIT 1').get() as { id: number } | null;
65
+ if (remaining) {
66
+ db.prepare('UPDATE claude_accounts SET is_active = 1 WHERE id = ?').run(remaining.id);
67
+ }
68
+ }
69
+ },
70
+
71
+ renameClaudeAccount(id: number, name: string): void {
72
+ const db = getDatabase();
73
+ db.prepare('UPDATE claude_accounts SET name = ? WHERE id = ?').run(name, id);
74
+ }
75
+ };
@@ -0,0 +1,9 @@
1
+ // Re-export all query modules for backward compatibility and clean imports
2
+ export { projectQueries } from './project-queries';
3
+ export { sessionQueries } from './session-queries';
4
+ export { messageQueries } from './message-queries';
5
+ export { settingsQueries } from './settings-queries';
6
+ export { dbUtils } from './utils-queries';
7
+ export { snapshotQueries } from './snapshot-queries';
8
+ export { checkpointQueries } from './checkpoint-queries';
9
+ export { engineQueries } from './engine-queries';
@@ -0,0 +1,472 @@
1
+ import { getDatabase } from '../index';
2
+ import type { DatabaseMessage, SDKMessageFormatter } from '$shared/types/database/schema';
3
+ import type { SDKMessage } from '$shared/types/messaging';
4
+ import { formatDatabaseMessage } from '$shared/utils/message-formatter';
5
+ import { debug } from '$shared/utils/logger';
6
+
7
+ export const messageQueries = {
8
+ /**
9
+ * Get visible messages for a session (git-like: from HEAD to root)
10
+ * This is the main function used to display messages in UI
11
+ */
12
+ getBySessionId(sessionId: string): SDKMessageFormatter[] {
13
+ const db = getDatabase();
14
+
15
+ // Get current HEAD from session
16
+ const session = db.prepare(`
17
+ SELECT current_head_message_id FROM chat_sessions WHERE id = ?
18
+ `).get(sessionId) as { current_head_message_id: string | null } | null;
19
+
20
+ if (!session || !session.current_head_message_id) {
21
+ return [];
22
+ }
23
+
24
+ // Build path from HEAD to root (git-like)
25
+ const path = this.getPathToRoot(session.current_head_message_id);
26
+
27
+ // Parse SDK messages — propagate sender from user to subsequent messages
28
+ let lastSenderId: string | null = null;
29
+ let lastSenderName: string | null = null;
30
+
31
+ return path.map(msg => {
32
+ // Track sender from messages that have it stored
33
+ if (msg.sender_id) {
34
+ lastSenderId = msg.sender_id;
35
+ lastSenderName = msg.sender_name || null;
36
+ }
37
+
38
+ return formatDatabaseMessage(msg, {
39
+ sender_id: msg.sender_id || lastSenderId,
40
+ sender_name: msg.sender_name || lastSenderName,
41
+ });
42
+ });
43
+ },
44
+
45
+ /**
46
+ * Get all messages for a session including deleted ones (for timeline view)
47
+ */
48
+ getAllBySessionId(sessionId: string): DatabaseMessage[] {
49
+ const db = getDatabase();
50
+ const messages = db.prepare(`
51
+ SELECT * FROM messages
52
+ WHERE session_id = ?
53
+ ORDER BY timestamp ASC
54
+ `).all(sessionId) as DatabaseMessage[];
55
+
56
+ return messages;
57
+ },
58
+
59
+ getById(id: string): DatabaseMessage | null {
60
+ const db = getDatabase();
61
+ const message = db.prepare(`
62
+ SELECT * FROM messages WHERE id = ?
63
+ `).get(id) as DatabaseMessage | null;
64
+
65
+ return message;
66
+ },
67
+
68
+ create(messageData: {
69
+ session_id: string;
70
+ sdk_message: SDKMessage;
71
+ timestamp?: string;
72
+ sender_id?: string;
73
+ sender_name?: string;
74
+ branch_id?: string;
75
+ parent_message_id?: string;
76
+ }): DatabaseMessage {
77
+ const db = getDatabase();
78
+ const id = crypto.randomUUID();
79
+
80
+ const timestamp = messageData.timestamp || new Date().toISOString();
81
+
82
+ // Save SDK message with git-like parent pointer
83
+ db.prepare(`
84
+ INSERT INTO messages (id, session_id, timestamp, sdk_message, sender_id, sender_name, is_deleted, branch_id, parent_message_id)
85
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
86
+ `).run(
87
+ id,
88
+ messageData.session_id,
89
+ timestamp,
90
+ JSON.stringify(messageData.sdk_message),
91
+ messageData.sender_id || null,
92
+ messageData.sender_name || null,
93
+ messageData.branch_id || null,
94
+ messageData.parent_message_id || null
95
+ );
96
+
97
+ return {
98
+ id,
99
+ session_id: messageData.session_id,
100
+ timestamp,
101
+ sdk_message: JSON.stringify(messageData.sdk_message),
102
+ sender_id: messageData.sender_id || null,
103
+ sender_name: messageData.sender_name || null,
104
+ is_deleted: 0,
105
+ branch_id: messageData.branch_id || null,
106
+ parent_message_id: messageData.parent_message_id || null
107
+ };
108
+ },
109
+
110
+ delete(id: string): void {
111
+ const db = getDatabase();
112
+ db.prepare('DELETE FROM messages WHERE id = ?').run(id);
113
+ },
114
+
115
+ deleteBySessionId(sessionId: string): void {
116
+ const db = getDatabase();
117
+ db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
118
+ },
119
+
120
+ deleteAfterTimestamp(sessionId: string, timestamp: string): void {
121
+ const db = getDatabase();
122
+ db.prepare(`
123
+ DELETE FROM messages
124
+ WHERE session_id = ? AND timestamp >= ?
125
+ `).run(sessionId, timestamp);
126
+ },
127
+
128
+ /**
129
+ * Soft delete messages after a specific checkpoint message (for undo with branch support)
130
+ * Preserves checkpoint conversation (user + assistant responses) and deletes from next user message onward
131
+ */
132
+ softDeleteAfterTimestamp(sessionId: string, checkpointTimestamp: string, branchId: string): void {
133
+ const db = getDatabase();
134
+
135
+ // Get all messages with their SDK message for type checking
136
+ const allMessages = db.prepare(`
137
+ SELECT id, timestamp, sdk_message FROM messages
138
+ WHERE session_id = ?
139
+ ORDER BY timestamp ASC
140
+ `).all(sessionId) as { id: string; timestamp: string; sdk_message: string }[];
141
+
142
+ // Find the checkpoint message index
143
+ const checkpointIndex = allMessages.findIndex(msg => msg.timestamp === checkpointTimestamp);
144
+
145
+ if (checkpointIndex === -1) {
146
+ debug.warn('database', `Checkpoint message with timestamp ${checkpointTimestamp} not found`);
147
+ return;
148
+ }
149
+
150
+ // Find next USER message after checkpoint (this is where we start deleting)
151
+ let deleteFromIndex = -1;
152
+ for (let i = checkpointIndex + 1; i < allMessages.length; i++) {
153
+ const sdkMessage = JSON.parse(allMessages[i].sdk_message);
154
+ if (sdkMessage.type === 'user') {
155
+ deleteFromIndex = i;
156
+ break;
157
+ }
158
+ }
159
+
160
+ // If no user message found after checkpoint, nothing to delete
161
+ if (deleteFromIndex === -1) {
162
+ debug.log('database', 'No user messages to soft delete after checkpoint');
163
+ return;
164
+ }
165
+
166
+ // Get IDs of messages from next user message onward
167
+ const messagesToDelete = allMessages
168
+ .slice(deleteFromIndex)
169
+ .map(msg => msg.id);
170
+
171
+ if (messagesToDelete.length === 0) {
172
+ debug.log('database', 'No messages to soft delete after checkpoint');
173
+ return;
174
+ }
175
+
176
+ // Soft delete messages
177
+ const placeholders = messagesToDelete.map(() => '?').join(',');
178
+ db.prepare(`
179
+ UPDATE messages
180
+ SET is_deleted = 1, branch_id = ?
181
+ WHERE id IN (${placeholders}) AND (is_deleted IS NULL OR is_deleted = 0)
182
+ `).run(branchId, ...messagesToDelete);
183
+
184
+ debug.log('database', `Soft deleted ${messagesToDelete.length} messages from next user message after checkpoint`);
185
+ },
186
+
187
+ /**
188
+ * Restore messages from a specific branch (for redo)
189
+ */
190
+ restoreBranch(sessionId: string, branchId: string): void {
191
+ const db = getDatabase();
192
+ // Mark current active messages as deleted (switch to another branch)
193
+ db.prepare(`
194
+ UPDATE messages
195
+ SET is_deleted = 1
196
+ WHERE session_id = ? AND (is_deleted IS NULL OR is_deleted = 0)
197
+ `).run(sessionId);
198
+
199
+ // Restore messages from target branch
200
+ db.prepare(`
201
+ UPDATE messages
202
+ SET is_deleted = 0
203
+ WHERE session_id = ? AND branch_id = ?
204
+ `).run(sessionId, branchId);
205
+
206
+ // Also restore messages that are on main branch (no branch_id) up to the branching point
207
+ // We need to restore all messages before the first message in the target branch
208
+ const firstBranchMessage = db.prepare(`
209
+ SELECT MIN(timestamp) as min_timestamp
210
+ FROM messages
211
+ WHERE session_id = ? AND branch_id = ?
212
+ `).get(sessionId, branchId) as { min_timestamp: string } | undefined;
213
+
214
+ if (firstBranchMessage?.min_timestamp) {
215
+ db.prepare(`
216
+ UPDATE messages
217
+ SET is_deleted = 0
218
+ WHERE session_id = ? AND (branch_id IS NULL OR branch_id = '') AND timestamp < ?
219
+ `).run(sessionId, firstBranchMessage.min_timestamp);
220
+ }
221
+ },
222
+
223
+ /**
224
+ * Get messages by branch ID
225
+ */
226
+ getByBranchId(sessionId: string, branchId: string): DatabaseMessage[] {
227
+ const db = getDatabase();
228
+ const messages = db.prepare(`
229
+ SELECT * FROM messages
230
+ WHERE session_id = ? AND branch_id = ?
231
+ ORDER BY timestamp ASC
232
+ `).all(sessionId, branchId) as DatabaseMessage[];
233
+
234
+ return messages;
235
+ },
236
+
237
+ /**
238
+ * Get all branches for a session
239
+ */
240
+ getBranches(sessionId: string): string[] {
241
+ const db = getDatabase();
242
+ const branches = db.prepare(`
243
+ SELECT DISTINCT branch_id
244
+ FROM messages
245
+ WHERE session_id = ? AND branch_id IS NOT NULL AND branch_id != ''
246
+ ORDER BY branch_id ASC
247
+ `).all(sessionId) as { branch_id: string }[];
248
+
249
+ return branches.map(b => b.branch_id);
250
+ },
251
+
252
+ /**
253
+ * Get the last message before a specific timestamp in a session
254
+ * Used to find the SDK session ID to resume from after restore
255
+ */
256
+ getLastBeforeTimestamp(sessionId: string, timestamp: string): DatabaseMessage | null {
257
+ const db = getDatabase();
258
+ const message = db.prepare(`
259
+ SELECT * FROM messages
260
+ WHERE session_id = ? AND timestamp < ? AND (is_deleted IS NULL OR is_deleted = 0)
261
+ ORDER BY timestamp DESC
262
+ LIMIT 1
263
+ `).get(sessionId, timestamp) as DatabaseMessage | null;
264
+
265
+ return message;
266
+ },
267
+
268
+ /**
269
+ * Get the first assistant message after a specific timestamp in a session
270
+ * Used to find the SDK session ID from the response to the checkpoint message
271
+ */
272
+ getFirstAssistantAfterTimestamp(sessionId: string, timestamp: string): DatabaseMessage | null {
273
+ const db = getDatabase();
274
+ const messages = db.prepare(`
275
+ SELECT * FROM messages
276
+ WHERE session_id = ? AND timestamp > ? AND (is_deleted IS NULL OR is_deleted = 0)
277
+ ORDER BY timestamp ASC
278
+ `).all(sessionId, timestamp) as DatabaseMessage[];
279
+
280
+ // Find first assistant message
281
+ for (const message of messages) {
282
+ const sdkMessage = JSON.parse(message.sdk_message);
283
+ if (sdkMessage.type === 'assistant') {
284
+ return message;
285
+ }
286
+ }
287
+
288
+ return null;
289
+ },
290
+
291
+ // ==================== GIT-LIKE GRAPH OPERATIONS ====================
292
+
293
+ /**
294
+ * Get children of a message (messages that have this message as parent)
295
+ */
296
+ getChildren(messageId: string): DatabaseMessage[] {
297
+ const db = getDatabase();
298
+ const messages = db.prepare(`
299
+ SELECT * FROM messages
300
+ WHERE parent_message_id = ?
301
+ ORDER BY timestamp ASC
302
+ `).all(messageId) as DatabaseMessage[];
303
+
304
+ return messages;
305
+ },
306
+
307
+ /**
308
+ * Get all messages in the path from a message to the root (first message)
309
+ * Used to build the main branch path
310
+ */
311
+ getPathToRoot(messageId: string): DatabaseMessage[] {
312
+ const db = getDatabase();
313
+ const path: DatabaseMessage[] = [];
314
+ let currentId: string | null = messageId;
315
+
316
+ while (currentId) {
317
+ const message = db.prepare(`
318
+ SELECT * FROM messages WHERE id = ?
319
+ `).get(currentId) as DatabaseMessage | null;
320
+
321
+ if (!message) break;
322
+
323
+ path.unshift(message); // Add to beginning
324
+ currentId = message.parent_message_id || null;
325
+ }
326
+
327
+ return path;
328
+ },
329
+
330
+ /**
331
+ * Get all descendants of a message (entire subtree)
332
+ */
333
+ getDescendants(messageId: string): DatabaseMessage[] {
334
+ const db = getDatabase();
335
+ const descendants: DatabaseMessage[] = [];
336
+ const queue: string[] = [messageId];
337
+
338
+ while (queue.length > 0) {
339
+ const currentId = queue.shift()!;
340
+ const children = this.getChildren(currentId);
341
+
342
+ for (const child of children) {
343
+ descendants.push(child);
344
+ queue.push(child.id);
345
+ }
346
+ }
347
+
348
+ return descendants;
349
+ },
350
+
351
+ /**
352
+ * Mark messages with a branch_id
353
+ * Used when creating a new branch after undo
354
+ */
355
+ setBranchForMessages(messageIds: string[], branchId: string): void {
356
+ const db = getDatabase();
357
+ if (messageIds.length === 0) return;
358
+
359
+ const placeholders = messageIds.map(() => '?').join(',');
360
+ db.prepare(`
361
+ UPDATE messages
362
+ SET branch_id = ?
363
+ WHERE id IN (${placeholders})
364
+ `).run(branchId, ...messageIds);
365
+ },
366
+
367
+ /**
368
+ * Clear branch_id for messages (make them part of main branch)
369
+ */
370
+ clearBranchForMessages(messageIds: string[]): void {
371
+ const db = getDatabase();
372
+ if (messageIds.length === 0) return;
373
+
374
+ const placeholders = messageIds.map(() => '?').join(',');
375
+ db.prepare(`
376
+ UPDATE messages
377
+ SET branch_id = NULL
378
+ WHERE id IN (${placeholders})
379
+ `).run(...messageIds);
380
+ },
381
+
382
+ /**
383
+ * Get all messages in a session as a graph structure
384
+ * Returns map of messageId -> message for easy traversal
385
+ */
386
+ getMessageGraph(sessionId: string): Map<string, DatabaseMessage> {
387
+ const db = getDatabase();
388
+ const messages = db.prepare(`
389
+ SELECT * FROM messages
390
+ WHERE session_id = ?
391
+ ORDER BY timestamp ASC
392
+ `).all(sessionId) as DatabaseMessage[];
393
+
394
+ const graph = new Map<string, DatabaseMessage>();
395
+ for (const message of messages) {
396
+ graph.set(message.id, message);
397
+ }
398
+
399
+ return graph;
400
+ },
401
+
402
+ /**
403
+ * Find branch root for a message (first ancestor NOT in HEAD path)
404
+ * This is used to group orphaned messages into proper branches
405
+ *
406
+ * Example:
407
+ * HEAD path: A → B → C → C1
408
+ * Message F with path: A → B → C → C2 → D → E → F
409
+ * Result: C2 (first message NOT in HEAD path, diverges from C)
410
+ */
411
+ findBranchRoot(messageId: string, headPathIds: Set<string>): DatabaseMessage | null {
412
+ const db = getDatabase();
413
+ let currentId: string | null = messageId;
414
+ let candidateRoot: DatabaseMessage | null = null;
415
+
416
+ // Walk up the parent chain
417
+ while (currentId) {
418
+ const message = db.prepare(`
419
+ SELECT * FROM messages WHERE id = ?
420
+ `).get(currentId) as DatabaseMessage | null;
421
+
422
+ if (!message) break;
423
+
424
+ // If message is IN HEAD path, we've gone too far
425
+ // Return the last candidate (first message NOT in HEAD path)
426
+ if (headPathIds.has(message.id)) {
427
+ return candidateRoot;
428
+ }
429
+
430
+ // This message is NOT in HEAD path, it's a candidate root
431
+ candidateRoot = message;
432
+ currentId = message.parent_message_id || null;
433
+ }
434
+
435
+ // If we reached the root without finding HEAD path, return the candidate
436
+ // This happens when the entire path is orphaned (shouldn't happen normally)
437
+ return candidateRoot;
438
+ },
439
+
440
+ /**
441
+ * Group orphaned messages by their branch root
442
+ * Returns map of branchRootId -> [orphaned messages]
443
+ *
444
+ * This ensures that multi-level trees are preserved:
445
+ * - If C2 is orphaned and D, E, F are children of C2
446
+ * - All of them will be grouped under C2's branch
447
+ */
448
+ groupOrphanedByBranchRoot(
449
+ orphanedMessages: DatabaseMessage[],
450
+ headPathIds: Set<string>
451
+ ): Map<string, DatabaseMessage[]> {
452
+ const groups = new Map<string, DatabaseMessage[]>();
453
+
454
+ for (const msg of orphanedMessages) {
455
+ // Find the branch root for this message
456
+ const branchRoot = this.findBranchRoot(msg.id, headPathIds);
457
+
458
+ if (!branchRoot) {
459
+ debug.warn('database', `No branch root found for orphaned message ${msg.id}`);
460
+ continue;
461
+ }
462
+
463
+ // Group messages by their branch root
464
+ if (!groups.has(branchRoot.id)) {
465
+ groups.set(branchRoot.id, []);
466
+ }
467
+ groups.get(branchRoot.id)!.push(msg);
468
+ }
469
+
470
+ return groups;
471
+ }
472
+ };