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