@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,222 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Page, Dialog } from 'puppeteer';
|
|
3
|
+
import type { BrowserTab, BrowserDialogEvent, BrowserDialogResponse, BrowserPrintEvent } from './types';
|
|
4
|
+
import { debug } from '$shared/utils/logger';
|
|
5
|
+
import { nanoid } from 'nanoid';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Browser Dialog Handler
|
|
9
|
+
*
|
|
10
|
+
* Intercepts native browser dialogs (alert, confirm, prompt) from headless browser
|
|
11
|
+
* and emits events to frontend for re-rendering as native host dialogs.
|
|
12
|
+
*
|
|
13
|
+
* Also intercepts window.print() calls and emits print events.
|
|
14
|
+
*/
|
|
15
|
+
export class BrowserDialogHandler extends EventEmitter {
|
|
16
|
+
// Store pending dialogs waiting for response
|
|
17
|
+
private pendingDialogs = new Map<string, Dialog>();
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Setup dialog interception for a browser session
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Setup dialog bindings - MUST be called BEFORE navigation
|
|
28
|
+
* This includes exposeFunction calls which require page to not be navigated yet
|
|
29
|
+
*/
|
|
30
|
+
async setupDialogBindings(sessionId: string, page: Page) {
|
|
31
|
+
debug.log('preview', `🎭 Setting up dialog bindings (pre-navigation) for session: ${sessionId}`);
|
|
32
|
+
|
|
33
|
+
// Setup print interception bindings - requires exposeFunction
|
|
34
|
+
await this.setupPrintInterception(sessionId, page);
|
|
35
|
+
|
|
36
|
+
debug.log('preview', `✅ Dialog bindings setup complete for session: ${sessionId}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Setup dialog event listeners - can be called AFTER navigation
|
|
41
|
+
*/
|
|
42
|
+
async setupDialogHandling(sessionId: string, page: Page, session: BrowserTab) {
|
|
43
|
+
debug.log('preview', `🎭 Setting up dialog event listeners for session: ${sessionId}`);
|
|
44
|
+
|
|
45
|
+
// Intercept Puppeteer dialog events
|
|
46
|
+
page.on('dialog', async (dialog: Dialog) => {
|
|
47
|
+
await this.handleDialog(sessionId, dialog);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
debug.log('preview', `✅ Dialog event listeners setup complete for session: ${sessionId}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Handle Puppeteer dialog event
|
|
55
|
+
*/
|
|
56
|
+
private async handleDialog(sessionId: string, dialog: Dialog) {
|
|
57
|
+
const dialogId = nanoid(10);
|
|
58
|
+
const dialogType = dialog.type();
|
|
59
|
+
const message = dialog.message();
|
|
60
|
+
const defaultValue = dialog.defaultValue();
|
|
61
|
+
|
|
62
|
+
debug.log('preview', `🎭 Dialog detected - Type: ${dialogType}, Session: ${sessionId}`);
|
|
63
|
+
|
|
64
|
+
// Store pending dialog for later response
|
|
65
|
+
this.pendingDialogs.set(dialogId, dialog);
|
|
66
|
+
|
|
67
|
+
// Emit dialog event to frontend (sessionId will be converted to tabId by previewService)
|
|
68
|
+
const dialogEvent: any = {
|
|
69
|
+
sessionId, // Internal use only, converted to tabId at previewService layer
|
|
70
|
+
dialogId,
|
|
71
|
+
type: dialogType as 'alert' | 'confirm' | 'prompt' | 'beforeunload',
|
|
72
|
+
message,
|
|
73
|
+
defaultValue,
|
|
74
|
+
timestamp: Date.now()
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.emit('dialog', dialogEvent);
|
|
78
|
+
|
|
79
|
+
debug.log('preview', `📤 Dialog event emitted to frontend: ${dialogId}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle dialog response from frontend
|
|
84
|
+
*/
|
|
85
|
+
async respondToDialog(response: BrowserDialogResponse) {
|
|
86
|
+
const { dialogId, accept, promptText } = response;
|
|
87
|
+
|
|
88
|
+
debug.log('preview', `🔍 Responding to dialog - dialogId: ${dialogId}, accept: ${accept}, pending dialogs: ${this.pendingDialogs.size}`);
|
|
89
|
+
|
|
90
|
+
const dialog = this.pendingDialogs.get(dialogId);
|
|
91
|
+
if (!dialog) {
|
|
92
|
+
debug.warn('preview', `⚠️ Dialog not found in pendingDialogs: ${dialogId}`);
|
|
93
|
+
debug.warn('preview', ` Available dialog IDs: ${Array.from(this.pendingDialogs.keys()).join(', ') || '(none)'}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
debug.log('preview', `✅ Dialog found in pendingDialogs - Type: ${dialog.type()}, Message: "${dialog.message()}"`);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
if (accept) {
|
|
101
|
+
// User accepted (OK/Yes)
|
|
102
|
+
if (dialog.type() === 'prompt' && promptText !== undefined) {
|
|
103
|
+
debug.log('preview', `📝 Accepting prompt dialog with text: "${promptText}"`);
|
|
104
|
+
await dialog.accept(promptText);
|
|
105
|
+
debug.log('preview', `✅ Prompt accepted successfully`);
|
|
106
|
+
} else {
|
|
107
|
+
debug.log('preview', `📝 Accepting ${dialog.type()} dialog`);
|
|
108
|
+
await dialog.accept();
|
|
109
|
+
debug.log('preview', `✅ Dialog accepted successfully: ${dialogId}`);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// User dismissed (Cancel/No)
|
|
113
|
+
debug.log('preview', `📝 Dismissing ${dialog.type()} dialog`);
|
|
114
|
+
await dialog.dismiss();
|
|
115
|
+
debug.log('preview', `✅ Dialog dismissed successfully: ${dialogId}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Remove from pending dialogs
|
|
119
|
+
this.pendingDialogs.delete(dialogId);
|
|
120
|
+
debug.log('preview', `🗑️ Removed dialog from pendingDialogs - remaining: ${this.pendingDialogs.size}`);
|
|
121
|
+
return true;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
debug.error('preview', `💥 Error responding to dialog ${dialogId}:`, error);
|
|
124
|
+
this.pendingDialogs.delete(dialogId);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Setup window.print() interception
|
|
131
|
+
*/
|
|
132
|
+
private async setupPrintInterception(sessionId: string, page: Page) {
|
|
133
|
+
try {
|
|
134
|
+
// IMPORTANT: exposeFunction must be called BEFORE any navigation or evaluateOnNewDocument
|
|
135
|
+
// Listen for print requests
|
|
136
|
+
await page.exposeFunction('__notifyPrintRequest__', () => {
|
|
137
|
+
const printEvent: any = {
|
|
138
|
+
sessionId, // Internal use only, converted to tabId at previewService layer
|
|
139
|
+
timestamp: Date.now()
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
debug.log('preview', `🖨️ Print request detected for session: ${sessionId}`);
|
|
143
|
+
this.emit('print', printEvent);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Override window.print() in the page context
|
|
147
|
+
await page.evaluateOnNewDocument(() => {
|
|
148
|
+
// Store original print function
|
|
149
|
+
const originalPrint = window.print;
|
|
150
|
+
|
|
151
|
+
// Override with custom handler
|
|
152
|
+
window.print = function() {
|
|
153
|
+
// Emit custom event that we can intercept
|
|
154
|
+
window.dispatchEvent(new CustomEvent('__puppeteer_print_requested__'));
|
|
155
|
+
|
|
156
|
+
// Don't call original print() in headless mode - it would fail
|
|
157
|
+
// We'll handle it via the event
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Setup event listener for print requests
|
|
162
|
+
await page.evaluateOnNewDocument(() => {
|
|
163
|
+
window.addEventListener('__puppeteer_print_requested__', () => {
|
|
164
|
+
// Notify backend about print request
|
|
165
|
+
if ((window as any).__notifyPrintRequest__) {
|
|
166
|
+
(window as any).__notifyPrintRequest__();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
debug.log('preview', `✅ Print interception setup successfully for session: ${sessionId}`);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// If exposeFunction fails (e.g., target closed), print interception won't work
|
|
174
|
+
// but we shouldn't fail the entire session creation
|
|
175
|
+
debug.warn('preview', `⚠️ Print interception setup failed for session ${sessionId}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
176
|
+
debug.warn('preview', ` Print functionality will not be available for this session`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Clear pending dialogs for a session
|
|
182
|
+
*/
|
|
183
|
+
clearSessionDialogs(sessionId: string) {
|
|
184
|
+
// Find and dismiss all dialogs for this session
|
|
185
|
+
const dialogsToRemove: string[] = [];
|
|
186
|
+
|
|
187
|
+
for (const [dialogId, dialog] of this.pendingDialogs.entries()) {
|
|
188
|
+
// Check if dialog belongs to this session (we store sessionId in the dialogId via Map)
|
|
189
|
+
// Since we don't have direct sessionId mapping, we'll clear all pending dialogs
|
|
190
|
+
// This is safe as each session has its own page instance
|
|
191
|
+
try {
|
|
192
|
+
dialog.dismiss().catch(() => {});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// Dialog might already be closed
|
|
195
|
+
}
|
|
196
|
+
dialogsToRemove.push(dialogId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Remove from pending map
|
|
200
|
+
dialogsToRemove.forEach(id => this.pendingDialogs.delete(id));
|
|
201
|
+
|
|
202
|
+
debug.log('preview', `🧹 Cleared ${dialogsToRemove.length} pending dialogs for session: ${sessionId}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Clear all pending dialogs
|
|
207
|
+
*/
|
|
208
|
+
clearAllDialogs() {
|
|
209
|
+
const count = this.pendingDialogs.size;
|
|
210
|
+
|
|
211
|
+
for (const [dialogId, dialog] of this.pendingDialogs.entries()) {
|
|
212
|
+
try {
|
|
213
|
+
dialog.dismiss().catch(() => {});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
// Dialog might already be closed
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.pendingDialogs.clear();
|
|
220
|
+
debug.log('preview', `🧹 Cleared ${count} pending dialogs`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Page, KeyInput } from 'puppeteer';
|
|
3
|
+
import type { BrowserAutonomousAction, BrowserTab } from './types';
|
|
4
|
+
import { debug } from '$shared/utils/logger';
|
|
5
|
+
import { sleep } from '$shared/utils/async';
|
|
6
|
+
import { browserMcpControl } from './browser-mcp-control';
|
|
7
|
+
|
|
8
|
+
export class BrowserInteractionHandler extends EventEmitter {
|
|
9
|
+
// Track cursor positions per session
|
|
10
|
+
private sessionCursorPositions: Map<string, {x: number, y: number}> = new Map();
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// MCP-optimized mouse movement with smooth curves and ease-out easing
|
|
17
|
+
private async mcpMouseMove(page: Page, fromX: number, fromY: number, toX: number, toY: number, steps: number = 8) {
|
|
18
|
+
const controlPointX = fromX + (toX - fromX) * 0.5 + (Math.random() - 0.5) * 50;
|
|
19
|
+
const controlPointY = fromY + (toY - fromY) * 0.5 + (Math.random() - 0.5) * 50;
|
|
20
|
+
|
|
21
|
+
// Get session for emitting cursor position
|
|
22
|
+
const sessionId = (page as any).__sessionId;
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i <= steps; i++) {
|
|
25
|
+
const t = i / steps;
|
|
26
|
+
|
|
27
|
+
// Apply ease-out cubic easing: starts fast, ends slow
|
|
28
|
+
const easedT = 1 - Math.pow(1 - t, 3);
|
|
29
|
+
|
|
30
|
+
// Quadratic Bezier curve for natural movement with ease-out easing
|
|
31
|
+
const x = Math.round((1 - easedT) * (1 - easedT) * fromX + 2 * (1 - easedT) * easedT * controlPointX + easedT * easedT * toX);
|
|
32
|
+
const y = Math.round((1 - easedT) * (1 - easedT) * fromY + 2 * (1 - easedT) * easedT * controlPointY + easedT * easedT * toY);
|
|
33
|
+
|
|
34
|
+
await page.mouse.move(x, y);
|
|
35
|
+
|
|
36
|
+
// Emit cursor position for visual tracking
|
|
37
|
+
if (sessionId) {
|
|
38
|
+
this.updateCursorPosition(sessionId, x, y);
|
|
39
|
+
|
|
40
|
+
// Emit to WebSocket for frontend virtual cursor
|
|
41
|
+
browserMcpControl.emitCursorPosition(sessionId, x, y);
|
|
42
|
+
|
|
43
|
+
this.emit('cursor-position', {
|
|
44
|
+
sessionId,
|
|
45
|
+
x,
|
|
46
|
+
y,
|
|
47
|
+
timestamp: Date.now()
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Smooth delay between movements (15-25ms) for MCP automation
|
|
52
|
+
const moveDelay = 15 + Math.random() * 10;
|
|
53
|
+
await sleep(moveDelay);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// MCP-optimized typing with fast, consistent speed
|
|
58
|
+
private async mcpType(page: Page, text: string, baseDelay: number = 30) {
|
|
59
|
+
for (let i = 0; i < text.length; i++) {
|
|
60
|
+
const char = text[i];
|
|
61
|
+
|
|
62
|
+
// Fast typing with slight variance (20-40ms) for MCP automation
|
|
63
|
+
const delay = baseDelay + Math.random() * 10;
|
|
64
|
+
|
|
65
|
+
await page.keyboard.type(char, { delay });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get current mouse position from session tracking
|
|
70
|
+
private getCurrentMousePosition(sessionId: string): {x: number, y: number} {
|
|
71
|
+
const stored = this.sessionCursorPositions.get(sessionId);
|
|
72
|
+
if (stored) {
|
|
73
|
+
return stored;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default to center of viewport for first action
|
|
77
|
+
const defaultPos = { x: 400, y: 300 };
|
|
78
|
+
this.sessionCursorPositions.set(sessionId, defaultPos);
|
|
79
|
+
return defaultPos;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Update and persist cursor position for session
|
|
83
|
+
private updateCursorPosition(sessionId: string, x: number, y: number) {
|
|
84
|
+
const position = { x, y };
|
|
85
|
+
this.sessionCursorPositions.set(sessionId, position);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Clear cursor position for session (when session ends)
|
|
89
|
+
public clearSessionCursor(sessionId: string) {
|
|
90
|
+
this.sessionCursorPositions.delete(sessionId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Clear all session cursors (when cleaning up all sessions)
|
|
94
|
+
public clearAllSessionCursors() {
|
|
95
|
+
const sessionCount = this.sessionCursorPositions.size;
|
|
96
|
+
this.sessionCursorPositions.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async performAutonomousActions(
|
|
100
|
+
sessionId: string,
|
|
101
|
+
session: BrowserTab,
|
|
102
|
+
actions: BrowserAutonomousAction[],
|
|
103
|
+
isValidSession: () => boolean
|
|
104
|
+
) {
|
|
105
|
+
let currentMousePos = this.getCurrentMousePosition(sessionId);
|
|
106
|
+
const results: any[] = [];
|
|
107
|
+
|
|
108
|
+
// Store session ID on page for cursor tracking
|
|
109
|
+
(session.page as any).__sessionId = sessionId;
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < actions.length; i++) {
|
|
112
|
+
// Check if session is still valid before each action
|
|
113
|
+
if (!isValidSession()) {
|
|
114
|
+
debug.warn('preview', `⚠️ Session ${sessionId} is no longer valid, stopping autonomous actions at ${i + 1}/${actions.length}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const action = actions[i];
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
switch (action.type) {
|
|
122
|
+
case 'wait':
|
|
123
|
+
await sleep(action.delay || 1000);
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 'move':
|
|
127
|
+
if (action.x !== undefined && action.y !== undefined) {
|
|
128
|
+
// Always use MCP-optimized movement
|
|
129
|
+
await this.mcpMouseMove(
|
|
130
|
+
session.page,
|
|
131
|
+
currentMousePos.x,
|
|
132
|
+
currentMousePos.y,
|
|
133
|
+
action.x,
|
|
134
|
+
action.y,
|
|
135
|
+
action.steps || 8
|
|
136
|
+
);
|
|
137
|
+
currentMousePos = { x: action.x, y: action.y };
|
|
138
|
+
this.updateCursorPosition(sessionId, action.x, action.y);
|
|
139
|
+
|
|
140
|
+
// Emit to WebSocket for frontend virtual cursor
|
|
141
|
+
browserMcpControl.emitCursorPosition(sessionId, action.x, action.y);
|
|
142
|
+
|
|
143
|
+
this.emit('cursor-position', {
|
|
144
|
+
sessionId: session.id,
|
|
145
|
+
x: action.x,
|
|
146
|
+
y: action.y,
|
|
147
|
+
timestamp: Date.now()
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case 'type': {
|
|
153
|
+
// Clear existing input first if specified (default: true for MCP autonomous)
|
|
154
|
+
const shouldClear = action.clearFirst !== false; // default true
|
|
155
|
+
|
|
156
|
+
if (shouldClear && action.text) {
|
|
157
|
+
// Select all text first (Ctrl+A), then type will overwrite
|
|
158
|
+
await session.page.keyboard.down('Control');
|
|
159
|
+
await session.page.keyboard.press('KeyA');
|
|
160
|
+
await session.page.keyboard.up('Control');
|
|
161
|
+
await sleep(30); // Fast delay for MCP automation
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Native keyboard: type text OR press single key
|
|
165
|
+
if (action.text) {
|
|
166
|
+
// Always use MCP-optimized typing
|
|
167
|
+
await this.mcpType(session.page, action.text, action.delay || 30);
|
|
168
|
+
} else if (action.key) {
|
|
169
|
+
await session.page.keyboard.press(action.key as KeyInput);
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 'click': {
|
|
175
|
+
// Native mouse click at coordinates
|
|
176
|
+
if (action.x === undefined || action.y === undefined) break;
|
|
177
|
+
|
|
178
|
+
const button = action.click || 'left';
|
|
179
|
+
|
|
180
|
+
// Move mouse to target position with MCP-optimized movement
|
|
181
|
+
await this.mcpMouseMove(
|
|
182
|
+
session.page,
|
|
183
|
+
currentMousePos.x,
|
|
184
|
+
currentMousePos.y,
|
|
185
|
+
action.x,
|
|
186
|
+
action.y,
|
|
187
|
+
8
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
currentMousePos = { x: action.x, y: action.y };
|
|
191
|
+
this.updateCursorPosition(sessionId, action.x, action.y);
|
|
192
|
+
|
|
193
|
+
// Emit cursor position to WebSocket for frontend virtual cursor
|
|
194
|
+
browserMcpControl.emitCursorPosition(sessionId, action.x, action.y);
|
|
195
|
+
|
|
196
|
+
this.emit('cursor-position', {
|
|
197
|
+
sessionId: session.id,
|
|
198
|
+
x: action.x,
|
|
199
|
+
y: action.y,
|
|
200
|
+
timestamp: Date.now()
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await sleep(30);
|
|
204
|
+
|
|
205
|
+
// Emit click event to WebSocket for frontend virtual cursor
|
|
206
|
+
browserMcpControl.emitCursorClick(sessionId, action.x, action.y);
|
|
207
|
+
|
|
208
|
+
this.emit('cursor-click', {
|
|
209
|
+
sessionId: session.id,
|
|
210
|
+
x: action.x,
|
|
211
|
+
y: action.y,
|
|
212
|
+
timestamp: Date.now()
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Native mouse click with slight variance for MCP automation
|
|
216
|
+
const finalX = action.x + (Math.random() - 0.5) * 2;
|
|
217
|
+
const finalY = action.y + (Math.random() - 0.5) * 2;
|
|
218
|
+
await session.page.mouse.click(finalX, finalY, { button });
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
case 'scroll':
|
|
223
|
+
// Move cursor to target area first if coordinates provided
|
|
224
|
+
// This ensures scroll happens in the correct scrollable container (like human behavior)
|
|
225
|
+
if (action.x !== undefined && action.y !== undefined) {
|
|
226
|
+
await session.page.mouse.move(action.x, action.y, { steps: 1 });
|
|
227
|
+
currentMousePos = { x: action.x, y: action.y };
|
|
228
|
+
this.updateCursorPosition(sessionId, action.x, action.y);
|
|
229
|
+
|
|
230
|
+
// Emit cursor position for visual tracking
|
|
231
|
+
browserMcpControl.emitCursorPosition(sessionId, action.x, action.y);
|
|
232
|
+
this.emit('cursor-position', {
|
|
233
|
+
sessionId: session.id,
|
|
234
|
+
x: action.x,
|
|
235
|
+
y: action.y,
|
|
236
|
+
timestamp: Date.now()
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await sleep(50); // Small delay after positioning
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Perform scroll using deltaX/deltaY
|
|
243
|
+
if (action.deltaX !== undefined || action.deltaY !== undefined) {
|
|
244
|
+
if (action.smooth) {
|
|
245
|
+
const steps = 5;
|
|
246
|
+
const stepX = (action.deltaX || 0) / steps;
|
|
247
|
+
const stepY = (action.deltaY || 0) / steps;
|
|
248
|
+
|
|
249
|
+
for (let s = 0; s < steps; s++) {
|
|
250
|
+
await session.page.mouse.wheel({ deltaX: stepX, deltaY: stepY });
|
|
251
|
+
await sleep(50);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
await session.page.mouse.wheel({
|
|
255
|
+
deltaX: action.deltaX || 0,
|
|
256
|
+
deltaY: action.deltaY || 0
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case 'extract_data': {
|
|
263
|
+
// Extract data from DOM element - fully automatic selector and attribute detection
|
|
264
|
+
if (!action.selector) {
|
|
265
|
+
debug.warn('preview', `⚠️ extract_data action requires selector`);
|
|
266
|
+
results.push({
|
|
267
|
+
action: 'extract_data',
|
|
268
|
+
selector: undefined,
|
|
269
|
+
data: null,
|
|
270
|
+
error: 'selector is required',
|
|
271
|
+
timestamp: Date.now()
|
|
272
|
+
});
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const identifier = action.selector;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// Smart extraction - try all selector patterns and all attributes automatically
|
|
280
|
+
const result = await session.page.evaluate((id: string) => {
|
|
281
|
+
// Try multiple selector patterns
|
|
282
|
+
const selectors = [
|
|
283
|
+
id, // exact (user might already include # or .)
|
|
284
|
+
`#${id}`, // ID selector
|
|
285
|
+
`.${id}`, // class selector
|
|
286
|
+
`[id="${id}"]`, // attribute exact match
|
|
287
|
+
`[id*="${id}"]`, // ID contains
|
|
288
|
+
`[class*="${id}"]`, // class contains
|
|
289
|
+
`[data-testid="${id}"]`, // test ID
|
|
290
|
+
`[name="${id}"]`, // name attribute
|
|
291
|
+
id.toLowerCase(), // lowercase tag
|
|
292
|
+
`#${id.toLowerCase()}`, // lowercase ID
|
|
293
|
+
`.${id.toLowerCase()}`, // lowercase class
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
let element: Element | null = null;
|
|
297
|
+
let usedSelector = '';
|
|
298
|
+
|
|
299
|
+
// Try each selector until one works
|
|
300
|
+
for (const selector of selectors) {
|
|
301
|
+
try {
|
|
302
|
+
element = document.querySelector(selector);
|
|
303
|
+
if (element) {
|
|
304
|
+
usedSelector = selector;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {
|
|
308
|
+
// Invalid selector, skip
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!element) {
|
|
314
|
+
return { data: null, selector: null, attribute: null, tried: selectors.slice(0, 5) };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Smart attribute extraction - try all common attributes and return first non-empty
|
|
318
|
+
const extractors = [
|
|
319
|
+
{ name: 'value', fn: () => (element as HTMLInputElement).value },
|
|
320
|
+
{ name: 'textContent', fn: () => element!.textContent?.trim() },
|
|
321
|
+
{ name: 'innerText', fn: () => (element as HTMLElement).innerText?.trim() },
|
|
322
|
+
{ name: 'innerHTML', fn: () => element!.innerHTML?.trim() },
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
let data = null;
|
|
326
|
+
let usedAttribute = '';
|
|
327
|
+
|
|
328
|
+
for (const extractor of extractors) {
|
|
329
|
+
try {
|
|
330
|
+
const extracted = extractor.fn();
|
|
331
|
+
if (extracted && extracted.length > 0) {
|
|
332
|
+
data = extracted;
|
|
333
|
+
usedAttribute = extractor.name;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
// Attribute doesn't exist or failed, continue
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
data,
|
|
344
|
+
selector: usedSelector,
|
|
345
|
+
attribute: usedAttribute,
|
|
346
|
+
tried: data ? [usedSelector] : selectors.slice(0, 5)
|
|
347
|
+
};
|
|
348
|
+
}, identifier);
|
|
349
|
+
|
|
350
|
+
if (result.data !== null) {
|
|
351
|
+
results.push({
|
|
352
|
+
action: 'extract_data',
|
|
353
|
+
selector: result.selector,
|
|
354
|
+
attribute: result.attribute,
|
|
355
|
+
data: result.data,
|
|
356
|
+
timestamp: Date.now()
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
results.push({
|
|
360
|
+
action: 'extract_data',
|
|
361
|
+
selector: identifier,
|
|
362
|
+
attribute: null,
|
|
363
|
+
data: null,
|
|
364
|
+
error: `Element not found or empty. Tried selectors: ${result.tried.join(', ')}`,
|
|
365
|
+
timestamp: Date.now()
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
} catch (extractError) {
|
|
369
|
+
debug.error('preview', `❌ Error extracting data from ${identifier}:`, extractError);
|
|
370
|
+
results.push({
|
|
371
|
+
action: 'extract_data',
|
|
372
|
+
selector: identifier,
|
|
373
|
+
attribute: null,
|
|
374
|
+
data: null,
|
|
375
|
+
error: (extractError as Error)?.message || 'Unknown error',
|
|
376
|
+
timestamp: Date.now()
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
default:
|
|
383
|
+
debug.warn('preview', `⚠️ Unknown action type: ${(action as any).type}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Fast delay between actions for MCP automation (50-100ms)
|
|
387
|
+
const betweenActionDelay = 50 + Math.random() * 50;
|
|
388
|
+
|
|
389
|
+
// Don't add delay after explicit wait actions
|
|
390
|
+
if (action.type !== 'wait') {
|
|
391
|
+
await sleep(betweenActionDelay);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
} catch (error) {
|
|
395
|
+
debug.error('preview', `❌ Error performing action ${action.type}:`, error);
|
|
396
|
+
|
|
397
|
+
// Check if error is due to closed page/browser
|
|
398
|
+
const errorMessage = (error as Error)?.message || '';
|
|
399
|
+
if (errorMessage.includes('Target page, context or browser has been closed') ||
|
|
400
|
+
errorMessage.includes('Browser has been closed') ||
|
|
401
|
+
errorMessage.includes('Page has been closed')) {
|
|
402
|
+
|
|
403
|
+
debug.warn('preview', `⚠️ Browser/page closed during action ${i + 1}/${actions.length}, stopping autonomous actions`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Continue with next action for other errors
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
// Emit test completed event to hide virtual cursor
|
|
413
|
+
this.emit('test-completed', {
|
|
414
|
+
sessionId: session.id,
|
|
415
|
+
timestamp: Date.now()
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return results;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
}
|