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