@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,701 @@
1
+ /**
2
+ * Terminal Session Store
3
+ * Manages terminal sessions, command history, and state
4
+ */
5
+
6
+ import type { TerminalSession, TerminalLine, TerminalCommand } from '$shared/types/terminal';
7
+ import { terminalService, type StreamingResponse } from '$frontend/lib/services/terminal';
8
+ import { addNotification } from '../ui/notification.svelte';
9
+
10
+ import { debug } from '$shared/utils/logger';
11
+ interface TerminalState {
12
+ sessions: TerminalSession[];
13
+ activeSessionId: string | null;
14
+ nextSessionId: number;
15
+ isExecuting: boolean;
16
+ lastCommandWasCancelled: boolean;
17
+ executingSessionIds: Set<string>; // Track multiple executing sessions
18
+ sessionExecutionStates: Map<string, boolean>; // Track execution state per session
19
+ lineBuffers: Map<string, string>; // Line buffering for chunked PTY output
20
+ }
21
+
22
+ // Terminal store state
23
+ const terminalState = $state<TerminalState>({
24
+ sessions: [],
25
+ activeSessionId: null,
26
+ nextSessionId: 1,
27
+ isExecuting: false,
28
+ lastCommandWasCancelled: false,
29
+ executingSessionIds: new Set(),
30
+ sessionExecutionStates: new Map(),
31
+ lineBuffers: new Map()
32
+ });
33
+
34
+ // Computed properties
35
+ export const terminalStore = {
36
+ get sessions() { return terminalState.sessions; },
37
+ get activeSessionId() { return terminalState.activeSessionId; },
38
+ get isExecuting() { return terminalState.isExecuting; },
39
+ get lastCommandWasCancelled() { return terminalState.lastCommandWasCancelled; },
40
+
41
+ get activeSession() {
42
+ return terminalState.sessions.find(session => session.id === terminalState.activeSessionId) || null;
43
+ },
44
+
45
+ // Get execution state for a specific session
46
+ isSessionExecuting(sessionId: string): boolean {
47
+ return terminalState.sessionExecutionStates.get(sessionId) || false;
48
+ },
49
+
50
+ // Session Management
51
+ createNewSession(directory?: string, projectPath?: string, projectId?: string): string {
52
+ // Terminal should only be created when there's an active project
53
+ if (!projectId) {
54
+ // Terminal session cannot be created without a projectId
55
+ throw new Error('ProjectId is required to create a terminal session');
56
+ }
57
+
58
+ const sessionNumber = terminalState.nextSessionId++;
59
+ // Always include projectId in sessionId to ensure uniqueness across projects
60
+ const sanitizedProjectId = projectId.replace(/[^a-zA-Z0-9]/g, '').substring(0, 8);
61
+ const sessionId = `${sanitizedProjectId}-terminal-${sessionNumber}`;
62
+ const sessionName = `Terminal ${sessionNumber}`;
63
+ // Use provided directory or project path, but default to current working directory if invalid
64
+ const workingDirectory = directory || projectPath || '~';
65
+
66
+ // Check if session already exists (shouldn't happen but be safe)
67
+ if (terminalState.sessions.find(s => s.id === sessionId)) {
68
+ return this.createNewSession(directory, projectPath, projectId);
69
+ }
70
+
71
+ // Clear any existing buffer for this session
72
+ terminalState.lineBuffers.delete(sessionId);
73
+
74
+ const newSession: TerminalSession = {
75
+ id: sessionId,
76
+ name: sessionName,
77
+ directory: workingDirectory,
78
+ lines: [],
79
+ commandHistory: [],
80
+ isActive: true,
81
+ createdAt: new Date(),
82
+ lastUsedAt: new Date(),
83
+ shellType: 'Unknown', // Will be detected later
84
+ terminalBuffer: undefined
85
+ };
86
+
87
+ // Set all other sessions as inactive
88
+ terminalState.sessions = terminalState.sessions.map(session => ({
89
+ ...session,
90
+ isActive: false
91
+ }));
92
+
93
+ // Add new session
94
+ terminalState.sessions.push(newSession);
95
+ terminalState.activeSessionId = sessionId;
96
+
97
+ // Detect shell type asynchronously without blocking
98
+ // Add delay to ensure server is ready
99
+ if (typeof window !== 'undefined') {
100
+ setTimeout(() => {
101
+ this.detectShellType(sessionId);
102
+ }, 100);
103
+ }
104
+
105
+ return sessionId;
106
+ },
107
+
108
+ switchToSession(sessionId: string): void {
109
+ // Check for duplicates before switching
110
+ const uniqueIds = new Set<string>();
111
+ const hasDuplicates = terminalState.sessions.some(session => {
112
+ if (uniqueIds.has(session.id)) {
113
+ // Duplicate session ID found
114
+ return true;
115
+ }
116
+ uniqueIds.add(session.id);
117
+ return false;
118
+ });
119
+
120
+ if (hasDuplicates) {
121
+ // Remove duplicates
122
+ const seen = new Set<string>();
123
+ terminalState.sessions = terminalState.sessions.filter(session => {
124
+ if (seen.has(session.id)) {
125
+ // Removing duplicate session
126
+ return false;
127
+ }
128
+ seen.add(session.id);
129
+ return true;
130
+ });
131
+ }
132
+
133
+ // Update active session
134
+ terminalState.sessions = terminalState.sessions.map(session => ({
135
+ ...session,
136
+ isActive: session.id === sessionId,
137
+ lastUsedAt: session.id === sessionId ? new Date() : session.lastUsedAt
138
+ }));
139
+
140
+ terminalState.activeSessionId = sessionId;
141
+ },
142
+
143
+ async closeSession(sessionId: string): Promise<boolean> {
144
+ debug.log('terminal', `🔴 [closeSession] Starting close for session: ${sessionId}`);
145
+
146
+ // Check if the session being closed has a running command
147
+ const isSessionExecuting = this.isSessionExecuting(sessionId);
148
+ debug.log('terminal', `🔴 [closeSession] Session executing: ${isSessionExecuting}`);
149
+
150
+ // If command is running in this session, cancel it first
151
+ if (isSessionExecuting) {
152
+ debug.log('terminal', `🔴 [closeSession] Cancelling running command first`);
153
+
154
+ // Store the current active session to restore later if needed
155
+ const previousActiveSessionId = terminalState.activeSessionId;
156
+
157
+ // Temporarily switch to the session being closed if it's not active
158
+ if (sessionId !== terminalState.activeSessionId) {
159
+ terminalState.activeSessionId = sessionId;
160
+ }
161
+
162
+ // Cancel the command
163
+ try {
164
+ await this.cancelCommand();
165
+ debug.log('terminal', `🔴 [closeSession] Command cancelled successfully`);
166
+ } catch (error) {
167
+ debug.error('terminal', `🔴 [closeSession] Error cancelling command:`, error);
168
+ }
169
+
170
+ // Restore the previous active session if we temporarily switched
171
+ if (sessionId !== previousActiveSessionId && previousActiveSessionId) {
172
+ terminalState.activeSessionId = previousActiveSessionId;
173
+ }
174
+ }
175
+
176
+ // CRITICAL: Kill PTY session on server
177
+ debug.log('terminal', `🔴 [closeSession] Attempting to kill PTY session on server...`);
178
+ try {
179
+ const killed = await terminalService.killSession(sessionId);
180
+ debug.log('terminal', `🔴 [closeSession] Kill PTY response:`, { success: killed });
181
+ } catch (error) {
182
+ debug.error('terminal', `🔴 [closeSession] Failed to kill PTY session:`, error);
183
+ }
184
+
185
+ // Clear persistence data for this session
186
+ debug.log('terminal', `🔴 [closeSession] Clearing persistence data...`);
187
+ try {
188
+ const { terminalPersistenceManager } = await import('$frontend/lib/services/terminal/persistence.service');
189
+ // Remove entire session from persistence (not just stream info)
190
+ terminalPersistenceManager.removeSession(sessionId);
191
+ debug.log('terminal', `🔴 [closeSession] Persistence data cleared`);
192
+ } catch (error) {
193
+ debug.error('terminal', `🔴 [closeSession] Failed to clear persistence:`, error);
194
+ }
195
+
196
+ // CRITICAL: Also remove from terminalProjectManager context
197
+ // This prevents session from being recreated on browser refresh
198
+ debug.log('terminal', `🔴 [closeSession] Removing from project context...`);
199
+ try {
200
+ const { terminalProjectManager } = await import('$frontend/lib/services/terminal/project.service');
201
+ terminalProjectManager.removeSessionFromContext(sessionId);
202
+ debug.log('terminal', `🔴 [closeSession] Removed from project context`);
203
+ } catch (error) {
204
+ debug.error('terminal', `🔴 [closeSession] Failed to remove from project context:`, error);
205
+ }
206
+
207
+ // Clear any buffered content for this session
208
+ terminalState.lineBuffers.delete(sessionId);
209
+
210
+ // Remove execution states for this session
211
+ terminalState.sessionExecutionStates.delete(sessionId);
212
+ terminalState.executingSessionIds.delete(sessionId);
213
+
214
+ terminalState.sessions = terminalState.sessions.filter(session => session.id !== sessionId);
215
+ debug.log('terminal', `🔴 [closeSession] Session removed from state. Remaining sessions: ${terminalState.sessions.length}`);
216
+
217
+ // If we closed the active session, switch to another one (if available)
218
+ if (sessionId === terminalState.activeSessionId) {
219
+ const newActiveSession = terminalState.sessions[0];
220
+ if (newActiveSession) {
221
+ debug.log('terminal', `🔴 [closeSession] Switching to session: ${newActiveSession.id}`);
222
+ this.switchToSession(newActiveSession.id);
223
+ }
224
+ }
225
+
226
+ debug.log('terminal', `🔴 [closeSession] Close completed for session: ${sessionId}`);
227
+ return true;
228
+ },
229
+
230
+ // Update session directory (used when working directory changes)
231
+ updateSessionDirectory(sessionId: string, newDirectory: string): void {
232
+ const session = terminalState.sessions.find(s => s.id === sessionId);
233
+ if (session) {
234
+ // Update the session's current directory
235
+ session.directory = newDirectory;
236
+
237
+ // DO NOT update historical input lines - they should preserve their original directory
238
+ // Historical prompts should show the directory at the time the command was executed
239
+ }
240
+ },
241
+
242
+ // Connect to PTY session (for initial auto-connect)
243
+ async connectToSession(projectPath?: string, projectId?: string, terminalSize?: { cols: number; rows: number }): Promise<void> {
244
+ const activeSession = this.activeSession;
245
+ if (!activeSession) {
246
+ return;
247
+ }
248
+
249
+ // Connect to PTY session via SSE
250
+ try {
251
+ await terminalService.connectToSession(
252
+ {
253
+ sessionId: activeSession.id,
254
+ workingDirectory: activeSession.directory,
255
+ projectPath,
256
+ projectId,
257
+ terminalSize
258
+ },
259
+ (data: StreamingResponse) => this.handleStreamingData(activeSession.id, data)
260
+ );
261
+ } catch (error) {
262
+ // Silently handle connection errors
263
+ }
264
+ },
265
+
266
+ async cancelCommand(): Promise<void> {
267
+ const activeSession = this.activeSession;
268
+ if (!activeSession) {
269
+ return;
270
+ }
271
+
272
+ // Always send Ctrl+C signal regardless of execution state
273
+ // This provides a utility shortcut for mobile accessibility
274
+ const wasExecuting = this.isSessionExecuting(activeSession.id);
275
+
276
+ // Flush any buffered content before cancel (if was executing)
277
+ if (wasExecuting) {
278
+ const remainingBuffer = terminalState.lineBuffers.get(activeSession.id);
279
+ if (remainingBuffer && remainingBuffer.length > 0) {
280
+ this.addLineToSession(activeSession.id, {
281
+ content: remainingBuffer,
282
+ type: 'output',
283
+ timestamp: new Date()
284
+ });
285
+ terminalState.lineBuffers.set(activeSession.id, '');
286
+ }
287
+
288
+ // Mark that we're attempting to cancel
289
+ terminalState.lastCommandWasCancelled = true;
290
+
291
+ // Add ^C message to terminal with proper newline
292
+ this.addLineToSession(activeSession.id, {
293
+ content: '^C\r\n',
294
+ type: 'error',
295
+ timestamp: new Date()
296
+ });
297
+ }
298
+
299
+ try {
300
+ const success = await terminalService.cancelCommand(activeSession.id);
301
+
302
+ // Clear the restoration flag regardless of success (if was executing)
303
+ if (wasExecuting && typeof window !== 'undefined') {
304
+ sessionStorage.removeItem('terminal-restored-' + activeSession.id);
305
+ }
306
+
307
+ // Stop execution state and show prompt after cancel attempt (if was executing)
308
+ if (wasExecuting) {
309
+ terminalState.sessionExecutionStates.set(activeSession.id, false);
310
+ terminalState.executingSessionIds.delete(activeSession.id);
311
+
312
+ // Update global state only if active session
313
+ if (activeSession.id === terminalState.activeSessionId) {
314
+ terminalState.isExecuting = false;
315
+ }
316
+
317
+ // Show prompt after a short delay
318
+ setTimeout(() => {
319
+ this.triggerPromptDisplay(activeSession.id);
320
+ }, 200);
321
+ }
322
+
323
+ } catch (error) {
324
+ // Only log error to console, don't show to user
325
+ debug.error('terminal', 'Terminal cancel error:', error);
326
+
327
+ // Even on error, stop execution state (if was executing)
328
+ if (wasExecuting) {
329
+ terminalState.sessionExecutionStates.set(activeSession.id, false);
330
+ terminalState.executingSessionIds.delete(activeSession.id);
331
+
332
+ if (activeSession.id === terminalState.activeSessionId) {
333
+ terminalState.isExecuting = false;
334
+ }
335
+
336
+ // Still trigger prompt even on error
337
+ setTimeout(() => {
338
+ this.triggerPromptDisplay(activeSession.id);
339
+ }, 200);
340
+ }
341
+ }
342
+ },
343
+
344
+ // Process buffered output to handle chunked PTY data properly
345
+ processBufferedOutput(sessionId: string, content: string, type: 'output' | 'error'): void {
346
+ // Buffer incomplete lines to avoid splitting words like "Reply" into "R" and "eply"
347
+ let buffer = terminalState.lineBuffers.get(sessionId) || '';
348
+ buffer += content;
349
+
350
+ // Only send complete chunks to avoid word splitting
351
+ // If buffer ends with a partial ANSI sequence or in middle of a word, wait for more
352
+ if (buffer.length < 2) {
353
+ // Very short buffer, likely incomplete - wait for more
354
+ terminalState.lineBuffers.set(sessionId, buffer);
355
+ return;
356
+ }
357
+
358
+ // Check if we're in the middle of an ANSI escape sequence
359
+ const lastEscIndex = buffer.lastIndexOf('\x1b');
360
+ if (lastEscIndex >= 0 && lastEscIndex > buffer.length - 10) {
361
+ // Might be in middle of escape sequence, check if it's complete
362
+ const remaining = buffer.substring(lastEscIndex);
363
+ if (!/^(\x1b\[[0-9;]*[a-zA-Z]|\x1b\[\?[0-9]+[lh])/.test(remaining)) {
364
+ // Incomplete escape sequence, wait for more
365
+ terminalState.lineBuffers.set(sessionId, buffer);
366
+ return;
367
+ }
368
+ }
369
+
370
+ // Send the buffer and clear it
371
+ if (buffer.length > 0) {
372
+ this.addLineToSession(sessionId, {
373
+ content: buffer,
374
+ type: type,
375
+ timestamp: new Date()
376
+ });
377
+ terminalState.lineBuffers.set(sessionId, '');
378
+ }
379
+ },
380
+
381
+ // Session Content Management
382
+ addLineToSession(sessionId: string, line: TerminalLine): void {
383
+ terminalState.sessions = terminalState.sessions.map(session =>
384
+ session.id === sessionId
385
+ ? {
386
+ ...session,
387
+ lines: [...session.lines, line],
388
+ lastUsedAt: new Date()
389
+ }
390
+ : session
391
+ );
392
+ },
393
+
394
+ updateSessionHistory(sessionId: string, history: string[]): void {
395
+ terminalState.sessions = terminalState.sessions.map(session =>
396
+ session.id === sessionId
397
+ ? { ...session, commandHistory: history }
398
+ : session
399
+ );
400
+ },
401
+
402
+
403
+ clearSession(sessionId: string): void {
404
+ // Clear any buffered content for this session
405
+ terminalState.lineBuffers.delete(sessionId);
406
+
407
+ // CRITICAL FIX: Actually clear the session lines history
408
+ // This ensures when switching tabs, the cleared terminal stays clear
409
+ terminalState.sessions = terminalState.sessions.map(session =>
410
+ session.id === sessionId
411
+ ? {
412
+ ...session,
413
+ lines: [], // Clear all lines history
414
+ // Keep command history intact (user might want to access previous commands)
415
+ commandHistory: session.commandHistory,
416
+ // Clear terminal buffer as well
417
+ terminalBuffer: undefined
418
+ }
419
+ : session
420
+ );
421
+
422
+ // Add a special "clear" marker line to trigger visual clear
423
+ // This tells the XTerm component to clear the visual display
424
+ const clearLine: TerminalLine = {
425
+ content: '',
426
+ type: 'clear-screen', // Now properly typed in TerminalLine interface
427
+ timestamp: new Date()
428
+ };
429
+
430
+ // Then add the clear marker
431
+ terminalState.sessions = terminalState.sessions.map(session =>
432
+ session.id === sessionId
433
+ ? {
434
+ ...session,
435
+ lines: [clearLine] // Start fresh with just the clear marker
436
+ }
437
+ : session
438
+ );
439
+ },
440
+
441
+ // Handle streaming data from terminal service
442
+ handleStreamingData(sessionId: string, data: StreamingResponse): void {
443
+ switch (data.type) {
444
+ case 'clear-screen':
445
+ // Handle clear screen command
446
+ this.clearSession(sessionId);
447
+ // Trigger prompt display after clear
448
+ setTimeout(() => {
449
+ this.triggerPromptDisplay(sessionId);
450
+ }, 100);
451
+ break;
452
+
453
+ case 'output':
454
+ if (data.content) {
455
+ // Use line buffering for output to handle chunked PTY data
456
+ this.processBufferedOutput(sessionId, data.content, 'output');
457
+ }
458
+ break;
459
+
460
+ case 'error':
461
+ if (data.content) {
462
+ // Error messages are usually complete, but still buffer for safety
463
+ this.processBufferedOutput(sessionId, data.content, 'error');
464
+ }
465
+ break;
466
+
467
+ case 'directory':
468
+ if (data.newDirectory) {
469
+ // Update session directory
470
+ this.updateSessionDirectory(sessionId, data.newDirectory);
471
+ }
472
+ break;
473
+
474
+ case 'exit':
475
+ // Flush any remaining buffer content
476
+ const remainingBuffer = terminalState.lineBuffers.get(sessionId);
477
+ if (remainingBuffer && remainingBuffer.length > 0) {
478
+ this.addLineToSession(sessionId, {
479
+ content: remainingBuffer,
480
+ type: 'output',
481
+ timestamp: new Date()
482
+ });
483
+ terminalState.lineBuffers.set(sessionId, '');
484
+ }
485
+
486
+ // Check if command was cancelled
487
+ if (data.content?.includes('interrupted')) {
488
+ terminalState.lastCommandWasCancelled = true;
489
+ }
490
+ // Add prompt trigger after command exits
491
+ this.addLineToSession(sessionId, {
492
+ content: '',
493
+ type: 'prompt-trigger',
494
+ timestamp: new Date()
495
+ });
496
+ break;
497
+
498
+ case 'complete':
499
+ // Remove session from executing set
500
+ terminalState.executingSessionIds.delete(sessionId);
501
+ terminalState.sessionExecutionStates.set(sessionId, false);
502
+ // Update global executing state only if it's the active session
503
+ if (sessionId === terminalState.activeSessionId) {
504
+ terminalState.isExecuting = false;
505
+ }
506
+ // Always trigger prompt display for the completed session
507
+ this.triggerPromptDisplay(sessionId);
508
+ break;
509
+ }
510
+ },
511
+
512
+ // Detect shell type for a session
513
+ async detectShellType(sessionId: string): Promise<void> {
514
+ // Skip detection if we're in SSR
515
+ if (typeof window === 'undefined') {
516
+ return;
517
+ }
518
+
519
+ try {
520
+ // Check shell availability via WebSocket
521
+ const data = await terminalService.checkShellAvailability();
522
+
523
+ if (data.available) {
524
+ const shellType = data.shellType || 'Unknown';
525
+
526
+ // Update session with detected shell type
527
+ const session = terminalState.sessions.find(s => s.id === sessionId);
528
+ if (session) {
529
+ session.shellType = shellType;
530
+ }
531
+ } else {
532
+ throw new Error('Shell not available');
533
+ }
534
+ } catch (error) {
535
+ // Silently fallback to default - don't spam console
536
+ const defaultShellType = navigator.platform.includes('Win')
537
+ ? 'PowerShell'
538
+ : 'Bash';
539
+
540
+ const session = terminalState.sessions.find(s => s.id === sessionId);
541
+ if (session) {
542
+ session.shellType = defaultShellType;
543
+ }
544
+ }
545
+ },
546
+
547
+ // Helper methods for background service
548
+ getSession(sessionId: string): TerminalSession | undefined {
549
+ return terminalState.sessions.find(s => s.id === sessionId);
550
+ },
551
+
552
+ updateNextSessionId(nextId: number): void {
553
+ if (nextId > terminalState.nextSessionId) {
554
+ terminalState.nextSessionId = nextId;
555
+ // Updated nextSessionId to avoid conflicts
556
+ }
557
+ },
558
+
559
+ addSession(session: TerminalSession): void {
560
+ // Check if session already exists to prevent duplicates
561
+ const existingSession = terminalState.sessions.find(s => s.id === session.id);
562
+ if (existingSession) {
563
+ return;
564
+ }
565
+ terminalState.sessions.push(session);
566
+
567
+ // Update nextSessionId to be higher than any existing session ID
568
+ const match = session.id.match(/terminal-(\d+)/);
569
+ if (match) {
570
+ const sessionNumber = parseInt(match[1], 10);
571
+ if (sessionNumber >= terminalState.nextSessionId) {
572
+ terminalState.nextSessionId = sessionNumber + 1;
573
+ }
574
+ }
575
+ },
576
+
577
+ setActiveSession(sessionId: string): void {
578
+ this.switchToSession(sessionId);
579
+ },
580
+
581
+ /**
582
+ * Remove a session from store without killing PTY (used for collaborative sync).
583
+ * When another user closes a tab, we only need to remove it from our UI.
584
+ */
585
+ removeSessionFromStore(sessionId: string): void {
586
+ terminalState.lineBuffers.delete(sessionId);
587
+ terminalState.sessionExecutionStates.delete(sessionId);
588
+ terminalState.executingSessionIds.delete(sessionId);
589
+ terminalState.sessions = terminalState.sessions.filter(s => s.id !== sessionId);
590
+
591
+ // Switch to another session if this was active
592
+ if (terminalState.activeSessionId === sessionId) {
593
+ const newActive = terminalState.sessions[0];
594
+ if (newActive) {
595
+ this.switchToSession(newActive.id);
596
+ } else {
597
+ terminalState.activeSessionId = null;
598
+ }
599
+ }
600
+ },
601
+
602
+ addOutput(sessionId: string, line: TerminalLine): void {
603
+ this.addLineToSession(sessionId, line);
604
+ },
605
+
606
+ setExecutingState(sessionId: string, isExecuting: boolean): void {
607
+ // Update both Maps to ensure consistency
608
+ if (isExecuting) {
609
+ terminalState.executingSessionIds.add(sessionId);
610
+ terminalState.sessionExecutionStates.set(sessionId, true);
611
+ } else {
612
+ terminalState.executingSessionIds.delete(sessionId);
613
+ terminalState.sessionExecutionStates.set(sessionId, false);
614
+ }
615
+
616
+ // Update global executing state based on active session
617
+ if (sessionId === terminalState.activeSessionId) {
618
+ terminalState.isExecuting = isExecuting;
619
+ }
620
+
621
+ // setExecutingState for session
622
+ },
623
+
624
+ updateWorkingDirectory(sessionId: string, newDirectory: string): void {
625
+ this.updateSessionDirectory(sessionId, newDirectory);
626
+ },
627
+
628
+ triggerPromptDisplay(sessionId: string): void {
629
+ // This method is called when terminal needs to show prompt after restoration
630
+ // Add a special line to trigger prompt display in XTerm
631
+ const session = this.getSession(sessionId);
632
+ if (session) {
633
+ const promptTriggerLine: TerminalLine = {
634
+ type: 'prompt-trigger',
635
+ content: '',
636
+ timestamp: new Date()
637
+ };
638
+ terminalState.sessions = terminalState.sessions.map(s =>
639
+ s.id === sessionId
640
+ ? { ...s, lines: [...s.lines, promptTriggerLine] }
641
+ : s
642
+ );
643
+ }
644
+ },
645
+
646
+ // Initialize terminal store
647
+ // Save terminal buffer state for a session
648
+ saveTerminalBuffer(sessionId: string, buffer: import('$shared/types/terminal').TerminalBuffer): void {
649
+ const session = terminalState.sessions.find(s => s.id === sessionId);
650
+ if (session) {
651
+ session.terminalBuffer = buffer;
652
+ }
653
+ },
654
+
655
+ // Get terminal buffer for a session
656
+ getTerminalBuffer(sessionId: string): import('$shared/types/terminal').TerminalBuffer | undefined {
657
+ const session = terminalState.sessions.find(s => s.id === sessionId);
658
+ return session?.terminalBuffer;
659
+ },
660
+
661
+ initialize(hasActiveProject: boolean, projectPath?: string): void {
662
+ // If there's an active project, let terminalProjectManager handle session creation
663
+ // Otherwise, create a default session for non-project use
664
+ if (terminalState.sessions.length === 0) {
665
+ if (hasActiveProject) {
666
+ // Don't create session here - terminalProjectManager will handle it
667
+ // Active project detected, waiting for terminalProjectManager to create sessions
668
+ } else {
669
+ // No active project - terminal should not be accessible
670
+ // No active project, terminal not available
671
+ // Don't create any session - UI should show "No Active Project" message
672
+ }
673
+ } else {
674
+ // Found existing sessions
675
+ }
676
+ },
677
+
678
+ // Note: Shell availability is now checked on-demand in the server
679
+ // PowerShell on Windows, Bash on Unix systems
680
+
681
+ /**
682
+ * Clear all terminal sessions and related data
683
+ * Used when Clear All Data is triggered
684
+ */
685
+ clearAllSessions(): void {
686
+ // Clear terminal state
687
+ terminalState.sessions = [];
688
+ terminalState.activeSessionId = null;
689
+ terminalState.nextSessionId = 1;
690
+ terminalState.isExecuting = false;
691
+ terminalState.lastCommandWasCancelled = false;
692
+ terminalState.executingSessionIds.clear();
693
+ terminalState.sessionExecutionStates.clear();
694
+ terminalState.lineBuffers.clear();
695
+
696
+ // Clear terminal persistence data (in-memory)
697
+ import('$frontend/lib/services/terminal/persistence.service').then(({ terminalPersistenceManager }) => {
698
+ terminalPersistenceManager.clearAll();
699
+ });
700
+ }
701
+ };