@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.
- package/.env.example +6 -0
- package/.github/workflows/release.yml +60 -0
- package/.github/workflows/test.yml +40 -0
- package/CONTRIBUTING.md +499 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/backend/index.ts +156 -0
- package/backend/lib/chat/helpers.ts +42 -0
- package/backend/lib/chat/index.ts +2 -0
- package/backend/lib/chat/stream-manager.ts +1126 -0
- package/backend/lib/database/README.md +77 -0
- package/backend/lib/database/index.ts +119 -0
- package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
- package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
- package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
- package/backend/lib/database/migrations/index.ts +154 -0
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
- package/backend/lib/database/queries/engine-queries.ts +75 -0
- package/backend/lib/database/queries/index.ts +9 -0
- package/backend/lib/database/queries/message-queries.ts +472 -0
- package/backend/lib/database/queries/project-queries.ts +117 -0
- package/backend/lib/database/queries/session-queries.ts +271 -0
- package/backend/lib/database/queries/settings-queries.ts +34 -0
- package/backend/lib/database/queries/snapshot-queries.ts +326 -0
- package/backend/lib/database/queries/utils-queries.ts +59 -0
- package/backend/lib/database/seeders/index.ts +13 -0
- package/backend/lib/database/seeders/settings_seeder.ts +84 -0
- package/backend/lib/database/utils/connection.ts +174 -0
- package/backend/lib/database/utils/index.ts +4 -0
- package/backend/lib/database/utils/migration-runner.ts +118 -0
- package/backend/lib/database/utils/seeder-runner.ts +121 -0
- package/backend/lib/engine/adapters/claude/environment.ts +164 -0
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
- package/backend/lib/engine/adapters/claude/index.ts +1 -0
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
- package/backend/lib/engine/adapters/claude/stream.ts +177 -0
- package/backend/lib/engine/adapters/opencode/index.ts +2 -0
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
- package/backend/lib/engine/adapters/opencode/server.ts +104 -0
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
- package/backend/lib/engine/index.ts +196 -0
- package/backend/lib/engine/types.ts +58 -0
- package/backend/lib/files/file-operations.ts +478 -0
- package/backend/lib/files/file-reading.ts +308 -0
- package/backend/lib/files/file-watcher.ts +383 -0
- package/backend/lib/files/path-browsing.ts +382 -0
- package/backend/lib/git/git-executor.ts +88 -0
- package/backend/lib/git/git-parser.ts +411 -0
- package/backend/lib/git/git-service.ts +505 -0
- package/backend/lib/mcp/README.md +1144 -0
- package/backend/lib/mcp/config.ts +316 -0
- package/backend/lib/mcp/index.ts +35 -0
- package/backend/lib/mcp/project-context.ts +236 -0
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
- package/backend/lib/mcp/servers/helper.ts +143 -0
- package/backend/lib/mcp/servers/index.ts +45 -0
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
- package/backend/lib/mcp/servers/weather/index.ts +31 -0
- package/backend/lib/mcp/stdio-server.ts +103 -0
- package/backend/lib/mcp/types.ts +65 -0
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
- package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
- package/backend/lib/preview/browser/browser-pool.ts +357 -0
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
- package/backend/lib/preview/browser/types.ts +359 -0
- package/backend/lib/preview/index.ts +24 -0
- package/backend/lib/project/index.ts +2 -0
- package/backend/lib/project/status-manager.ts +182 -0
- package/backend/lib/shared/index.ts +2 -0
- package/backend/lib/shared/port-utils.ts +25 -0
- package/backend/lib/shared/process-manager.ts +281 -0
- package/backend/lib/snapshot/blob-store.ts +227 -0
- package/backend/lib/snapshot/gitignore.ts +307 -0
- package/backend/lib/snapshot/helpers.ts +397 -0
- package/backend/lib/snapshot/snapshot-service.ts +483 -0
- package/backend/lib/terminal/helpers.ts +14 -0
- package/backend/lib/terminal/index.ts +8 -0
- package/backend/lib/terminal/pty-manager.ts +4 -0
- package/backend/lib/terminal/pty-session-manager.ts +387 -0
- package/backend/lib/terminal/shell-utils.ts +313 -0
- package/backend/lib/terminal/stream-manager.ts +293 -0
- package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
- package/backend/lib/user/helpers.ts +87 -0
- package/backend/lib/utils/ws.ts +944 -0
- package/backend/lib/vite-dev.ts +353 -0
- package/backend/middleware/cors.ts +15 -0
- package/backend/middleware/error-handler.ts +49 -0
- package/backend/middleware/logger.ts +9 -0
- package/backend/types/api.ts +24 -0
- package/backend/ws/README.md +1505 -0
- package/backend/ws/chat/background.ts +198 -0
- package/backend/ws/chat/index.ts +21 -0
- package/backend/ws/chat/stream.ts +707 -0
- package/backend/ws/engine/claude/accounts.ts +401 -0
- package/backend/ws/engine/claude/index.ts +13 -0
- package/backend/ws/engine/claude/status.ts +43 -0
- package/backend/ws/engine/index.ts +14 -0
- package/backend/ws/engine/opencode/index.ts +11 -0
- package/backend/ws/engine/opencode/status.ts +30 -0
- package/backend/ws/engine/utils.ts +36 -0
- package/backend/ws/files/index.ts +30 -0
- package/backend/ws/files/read.ts +189 -0
- package/backend/ws/files/search.ts +453 -0
- package/backend/ws/files/watch.ts +124 -0
- package/backend/ws/files/write.ts +143 -0
- package/backend/ws/git/branch.ts +106 -0
- package/backend/ws/git/commit.ts +39 -0
- package/backend/ws/git/conflict.ts +68 -0
- package/backend/ws/git/diff.ts +69 -0
- package/backend/ws/git/index.ts +24 -0
- package/backend/ws/git/log.ts +41 -0
- package/backend/ws/git/remote.ts +214 -0
- package/backend/ws/git/staging.ts +84 -0
- package/backend/ws/git/status.ts +90 -0
- package/backend/ws/index.ts +69 -0
- package/backend/ws/mcp/index.ts +61 -0
- package/backend/ws/messages/crud.ts +74 -0
- package/backend/ws/messages/index.ts +14 -0
- package/backend/ws/preview/browser/cleanup.ts +129 -0
- package/backend/ws/preview/browser/console.ts +114 -0
- package/backend/ws/preview/browser/interact.ts +513 -0
- package/backend/ws/preview/browser/mcp.ts +129 -0
- package/backend/ws/preview/browser/native-ui.ts +235 -0
- package/backend/ws/preview/browser/stats.ts +55 -0
- package/backend/ws/preview/browser/tab-info.ts +126 -0
- package/backend/ws/preview/browser/tab.ts +166 -0
- package/backend/ws/preview/browser/webcodecs.ts +293 -0
- package/backend/ws/preview/index.ts +146 -0
- package/backend/ws/projects/crud.ts +113 -0
- package/backend/ws/projects/index.ts +25 -0
- package/backend/ws/projects/presence.ts +46 -0
- package/backend/ws/projects/status.ts +116 -0
- package/backend/ws/sessions/crud.ts +327 -0
- package/backend/ws/sessions/index.ts +33 -0
- package/backend/ws/settings/crud.ts +112 -0
- package/backend/ws/settings/index.ts +14 -0
- package/backend/ws/snapshot/index.ts +17 -0
- package/backend/ws/snapshot/restore.ts +173 -0
- package/backend/ws/snapshot/timeline.ts +141 -0
- package/backend/ws/system/index.ts +14 -0
- package/backend/ws/system/operations.ts +49 -0
- package/backend/ws/terminal/index.ts +40 -0
- package/backend/ws/terminal/persistence.ts +153 -0
- package/backend/ws/terminal/session.ts +382 -0
- package/backend/ws/terminal/stream.ts +79 -0
- package/backend/ws/tunnel/index.ts +14 -0
- package/backend/ws/tunnel/operations.ts +91 -0
- package/backend/ws/types.ts +20 -0
- package/backend/ws/user/crud.ts +156 -0
- package/backend/ws/user/index.ts +14 -0
- package/bin/clopen.ts +307 -0
- package/bun.lock +1352 -0
- package/frontend/App.svelte +34 -0
- package/frontend/app.css +313 -0
- package/frontend/lib/app-environment.ts +10 -0
- package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
- package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
- package/frontend/lib/components/chat/formatters/index.ts +3 -0
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
- package/frontend/lib/components/chat/shared/index.ts +2 -0
- package/frontend/lib/components/chat/shared/utils.ts +116 -0
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
- package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
- package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
- package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
- package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
- package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
- package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
- package/frontend/lib/components/chat/tools/components/index.ts +7 -0
- package/frontend/lib/components/chat/tools/index.ts +26 -0
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
- package/frontend/lib/components/common/Alert.svelte +139 -0
- package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
- package/frontend/lib/components/common/Button.svelte +71 -0
- package/frontend/lib/components/common/Card.svelte +102 -0
- package/frontend/lib/components/common/Checkbox.svelte +48 -0
- package/frontend/lib/components/common/Dialog.svelte +249 -0
- package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
- package/frontend/lib/components/common/Icon.svelte +58 -0
- package/frontend/lib/components/common/Input.svelte +72 -0
- package/frontend/lib/components/common/Lightbox.svelte +233 -0
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
- package/frontend/lib/components/common/Modal.svelte +177 -0
- package/frontend/lib/components/common/ModalProvider.svelte +28 -0
- package/frontend/lib/components/common/ModelSelector.svelte +110 -0
- package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
- package/frontend/lib/components/common/NotificationToast.svelte +113 -0
- package/frontend/lib/components/common/PageTemplate.svelte +76 -0
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
- package/frontend/lib/components/common/Select.svelte +98 -0
- package/frontend/lib/components/common/Textarea.svelte +80 -0
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
- package/frontend/lib/components/common/lucide-icons.ts +1642 -0
- package/frontend/lib/components/common/material-icons.ts +1082 -0
- package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
- package/frontend/lib/components/common/xterm/index.ts +16 -0
- package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
- package/frontend/lib/components/common/xterm/types.ts +31 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
- package/frontend/lib/components/files/FileNode.svelte +384 -0
- package/frontend/lib/components/files/FileTree.svelte +681 -0
- package/frontend/lib/components/files/FileViewer.svelte +728 -0
- package/frontend/lib/components/files/SearchResults.svelte +303 -0
- package/frontend/lib/components/git/BranchManager.svelte +458 -0
- package/frontend/lib/components/git/ChangesSection.svelte +107 -0
- package/frontend/lib/components/git/CommitForm.svelte +76 -0
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
- package/frontend/lib/components/git/DiffViewer.svelte +364 -0
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
- package/frontend/lib/components/git/GitButton.svelte +33 -0
- package/frontend/lib/components/git/GitLog.svelte +361 -0
- package/frontend/lib/components/git/GitModal.svelte +80 -0
- package/frontend/lib/components/history/HistoryModal.svelte +563 -0
- package/frontend/lib/components/history/HistoryView.svelte +615 -0
- package/frontend/lib/components/index.ts +34 -0
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
- package/frontend/lib/components/preview/index.ts +2 -0
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
- package/frontend/lib/components/settings/SettingsView.svelte +36 -0
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
- package/frontend/lib/components/terminal/Terminal.svelte +368 -0
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
- package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
- package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
- package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
- package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
- package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
- package/frontend/lib/constants/preview.ts +45 -0
- package/frontend/lib/services/chat/chat.service.ts +704 -0
- package/frontend/lib/services/chat/index.ts +7 -0
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
- package/frontend/lib/services/notification/index.ts +8 -0
- package/frontend/lib/services/notification/push.service.ts +144 -0
- package/frontend/lib/services/notification/sound.service.ts +127 -0
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
- package/frontend/lib/services/preview/index.ts +23 -0
- package/frontend/lib/services/project/index.ts +8 -0
- package/frontend/lib/services/project/status.service.ts +159 -0
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
- package/frontend/lib/services/terminal/background/index.ts +130 -0
- package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
- package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
- package/frontend/lib/services/terminal/index.ts +14 -0
- package/frontend/lib/services/terminal/persistence.service.ts +260 -0
- package/frontend/lib/services/terminal/project.service.ts +953 -0
- package/frontend/lib/services/terminal/session.service.ts +364 -0
- package/frontend/lib/services/terminal/terminal.service.ts +369 -0
- package/frontend/lib/stores/core/app.svelte.ts +117 -0
- package/frontend/lib/stores/core/files.svelte.ts +73 -0
- package/frontend/lib/stores/core/presence.svelte.ts +48 -0
- package/frontend/lib/stores/core/projects.svelte.ts +317 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
- package/frontend/lib/stores/features/models.svelte.ts +89 -0
- package/frontend/lib/stores/features/settings.svelte.ts +87 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
- package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
- package/frontend/lib/stores/features/user.svelte.ts +96 -0
- package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
- package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
- package/frontend/lib/types/native-ui.ts +73 -0
- package/frontend/lib/utils/chat/date-separator.ts +39 -0
- package/frontend/lib/utils/chat/message-grouper.ts +219 -0
- package/frontend/lib/utils/chat/message-processor.ts +135 -0
- package/frontend/lib/utils/chat/tool-handler.ts +161 -0
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
- package/frontend/lib/utils/click-outside.ts +20 -0
- package/frontend/lib/utils/context-manager.ts +257 -0
- package/frontend/lib/utils/file-icon-mappings.ts +769 -0
- package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
- package/frontend/lib/utils/git-status.ts +68 -0
- package/frontend/lib/utils/platform.ts +113 -0
- package/frontend/lib/utils/port-check.ts +65 -0
- package/frontend/lib/utils/terminalFormatter.ts +207 -0
- package/frontend/lib/utils/theme.ts +6 -0
- package/frontend/lib/utils/tree-visualizer.ts +320 -0
- package/frontend/lib/utils/ws.ts +44 -0
- package/frontend/main.ts +13 -0
- package/index.html +70 -0
- package/package.json +111 -0
- package/scripts/generate-icons.ts +87 -0
- package/scripts/pre-publish-check.sh +142 -0
- package/scripts/setup-hooks.sh +134 -0
- package/scripts/validate-branch-name.sh +47 -0
- package/scripts/validate-commit-msg.sh +42 -0
- package/shared/constants/engines.ts +134 -0
- package/shared/types/database/connection.ts +16 -0
- package/shared/types/database/index.ts +6 -0
- package/shared/types/database/schema.ts +141 -0
- package/shared/types/engine/index.ts +45 -0
- package/shared/types/filesystem/index.ts +22 -0
- package/shared/types/git.ts +171 -0
- package/shared/types/messaging/index.ts +239 -0
- package/shared/types/messaging/tool.ts +526 -0
- package/shared/types/network/api.ts +18 -0
- package/shared/types/network/index.ts +5 -0
- package/shared/types/stores/app.ts +23 -0
- package/shared/types/stores/dialog.ts +21 -0
- package/shared/types/stores/index.ts +3 -0
- package/shared/types/stores/settings.ts +15 -0
- package/shared/types/terminal/index.ts +44 -0
- package/shared/types/ui/components.ts +61 -0
- package/shared/types/ui/icons.ts +23 -0
- package/shared/types/ui/index.ts +22 -0
- package/shared/types/ui/notifications.ts +14 -0
- package/shared/types/ui/theme.ts +12 -0
- package/shared/types/websocket/index.ts +43 -0
- package/shared/types/window.d.ts +13 -0
- package/shared/utils/anonymous-user.ts +168 -0
- package/shared/utils/async.ts +10 -0
- package/shared/utils/diff-calculator.ts +184 -0
- package/shared/utils/file-type-detection.ts +166 -0
- package/shared/utils/logger.ts +158 -0
- package/shared/utils/message-formatter.ts +79 -0
- package/shared/utils/path.ts +47 -0
- package/shared/utils/ws-client.ts +768 -0
- package/shared/utils/ws-server.ts +660 -0
- package/static/audio/notification.ogg +0 -0
- package/static/favicon.svg +8 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
- package/static/fonts/dm-sans.css +96 -0
- package/svelte.config.js +20 -0
- 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
|
+
};
|