@myrialabs/clopen 0.0.5 โ 0.0.7
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 +12 -6
- package/.github/workflows/ci.yml +86 -86
- package/CONTRIBUTING.md +499 -499
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/backend/index.ts +144 -165
- 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 +124 -0
- package/backend/lib/shared/index.ts +5 -2
- package/backend/lib/shared/port-utils.ts +35 -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/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 +1364 -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 +114 -111
- package/scripts/dev.ts +45 -0
- 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 -144
- 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 +50 -33
- package/backend/lib/vite-dev.ts +0 -295
|
@@ -1,357 +1,357 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser Pool Module using puppeteer-cluster
|
|
3
|
-
*
|
|
4
|
-
* Uses puppeteer-cluster for efficient browser management with isolated contexts.
|
|
5
|
-
*
|
|
6
|
-
* Architecture:
|
|
7
|
-
* - puppeteer-cluster manages browser lifecycle and crash recovery
|
|
8
|
-
* - CONCURRENCY_CONTEXT mode: shared browser, isolated contexts per worker
|
|
9
|
-
* - Each user session gets its own isolated BrowserContext
|
|
10
|
-
*
|
|
11
|
-
* Isolation per session:
|
|
12
|
-
* - Separate cookies
|
|
13
|
-
* - Separate localStorage/sessionStorage
|
|
14
|
-
* - Separate cache
|
|
15
|
-
* - Separate service workers
|
|
16
|
-
* - No data leakage between users
|
|
17
|
-
*
|
|
18
|
-
* Memory Usage:
|
|
19
|
-
* - 1 user: ~300MB (browser) + ~20MB (context) = ~320MB
|
|
20
|
-
* - 10 users: ~300MB (browser) + ~200MB (contexts) = ~500MB
|
|
21
|
-
* - vs. old: 10 users = 10 browsers = 2-5GB
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { Cluster } from 'puppeteer-cluster';
|
|
25
|
-
import type { Browser, BrowserContext, Page } from 'puppeteer';
|
|
26
|
-
import { debug } from '$shared/utils/logger';
|
|
27
|
-
|
|
28
|
-
export interface PoolConfig {
|
|
29
|
-
maxConcurrency: number; // Maximum concurrent isolated contexts
|
|
30
|
-
timeout: number; // Task timeout
|
|
31
|
-
retryLimit: number; // Number of retries on failure
|
|
32
|
-
retryDelay: number; // Delay between retries
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface PooledSession {
|
|
36
|
-
context: BrowserContext;
|
|
37
|
-
page: Page;
|
|
38
|
-
createdAt: number;
|
|
39
|
-
sessionId: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const DEFAULT_CONFIG: PoolConfig = {
|
|
43
|
-
maxConcurrency: 50, // Support up to 50 concurrent users
|
|
44
|
-
timeout: 60000, // 60 second timeout
|
|
45
|
-
retryLimit: 3, // Retry 3 times on failure
|
|
46
|
-
retryDelay: 1000 // 1 second delay between retries
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Optimized Chromium launch arguments for low-resource usage
|
|
51
|
-
*/
|
|
52
|
-
const CHROMIUM_ARGS = [
|
|
53
|
-
// === CORE STABILITY (Windows compatible) ===
|
|
54
|
-
'--no-sandbox',
|
|
55
|
-
'--disable-dev-shm-usage',
|
|
56
|
-
'--disable-gpu',
|
|
57
|
-
|
|
58
|
-
// === PREVENT THROTTLING ===
|
|
59
|
-
'--disable-background-timer-throttling',
|
|
60
|
-
'--disable-backgrounding-occluded-windows',
|
|
61
|
-
'--disable-renderer-backgrounding',
|
|
62
|
-
|
|
63
|
-
// === DISABLE UNNECESSARY FEATURES ===
|
|
64
|
-
'--no-first-run',
|
|
65
|
-
'--no-default-browser-check',
|
|
66
|
-
'--disable-extensions',
|
|
67
|
-
'--disable-popup-blocking',
|
|
68
|
-
|
|
69
|
-
// === LOW-END DEVICE OPTIMIZATIONS ===
|
|
70
|
-
'--memory-pressure-off',
|
|
71
|
-
'--disable-features=TranslateUI',
|
|
72
|
-
'--disable-sync',
|
|
73
|
-
'--disable-domain-reliability',
|
|
74
|
-
'--disable-client-side-phishing-detection',
|
|
75
|
-
'--disable-software-rasterizer',
|
|
76
|
-
'--disable-smooth-scrolling',
|
|
77
|
-
'--disable-threaded-animation',
|
|
78
|
-
'--disable-threaded-scrolling',
|
|
79
|
-
'--disable-composited-antialiasing',
|
|
80
|
-
'--disable-webgl',
|
|
81
|
-
'--disable-webgl2',
|
|
82
|
-
'--disable-accelerated-2d-canvas',
|
|
83
|
-
'--disable-gpu-vsync',
|
|
84
|
-
'--disable-ipc-flooding-protection',
|
|
85
|
-
|
|
86
|
-
// === AUDIO SUPPORT ===
|
|
87
|
-
'--autoplay-policy=no-user-gesture-required',
|
|
88
|
-
'--use-fake-ui-for-media-stream'
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
class BrowserPool {
|
|
92
|
-
private cluster: Cluster | null = null;
|
|
93
|
-
private sessions = new Map<string, PooledSession>();
|
|
94
|
-
private config: PoolConfig;
|
|
95
|
-
private isInitializing = false;
|
|
96
|
-
private initPromise: Promise<Cluster> | null = null;
|
|
97
|
-
|
|
98
|
-
constructor(config: Partial<PoolConfig> = {}) {
|
|
99
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get or create the puppeteer-cluster instance
|
|
104
|
-
* Thread-safe: multiple calls during initialization will wait for the same promise
|
|
105
|
-
*/
|
|
106
|
-
async getCluster(): Promise<Cluster> {
|
|
107
|
-
if (this.cluster) {
|
|
108
|
-
return this.cluster;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (this.isInitializing && this.initPromise) {
|
|
112
|
-
return this.initPromise;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.isInitializing = true;
|
|
116
|
-
this.initPromise = this.launchCluster();
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
this.cluster = await this.initPromise;
|
|
120
|
-
return this.cluster;
|
|
121
|
-
} finally {
|
|
122
|
-
this.isInitializing = false;
|
|
123
|
-
this.initPromise = null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Launch puppeteer-cluster with CONCURRENCY_CONTEXT for isolation
|
|
129
|
-
*/
|
|
130
|
-
private async launchCluster(): Promise<Cluster> {
|
|
131
|
-
debug.log('preview', '๐ Launching puppeteer-cluster...');
|
|
132
|
-
|
|
133
|
-
const cluster = await Cluster.launch({
|
|
134
|
-
// CONCURRENCY_CONTEXT: shared browser, isolated context per worker
|
|
135
|
-
// Each context has its own cookies, localStorage, sessionStorage, cache
|
|
136
|
-
concurrency: Cluster.CONCURRENCY_CONTEXT,
|
|
137
|
-
maxConcurrency: this.config.maxConcurrency,
|
|
138
|
-
timeout: this.config.timeout,
|
|
139
|
-
retryLimit: this.config.retryLimit,
|
|
140
|
-
retryDelay: this.config.retryDelay,
|
|
141
|
-
|
|
142
|
-
puppeteerOptions: {
|
|
143
|
-
headless: true,
|
|
144
|
-
args: CHROMIUM_ARGS
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
// Monitor events
|
|
148
|
-
monitor: false // Disable built-in monitoring, we use our own logging
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Handle cluster errors
|
|
152
|
-
cluster.on('taskerror', (err, data) => {
|
|
153
|
-
debug.error('preview', `Task error for session ${data?.sessionId}:`, err.message);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
debug.log('preview', 'โ
puppeteer-cluster launched successfully');
|
|
157
|
-
debug.log('preview', `๐ Max concurrency: ${this.config.maxConcurrency}`);
|
|
158
|
-
|
|
159
|
-
return cluster;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get the shared browser instance from the cluster
|
|
164
|
-
* Note: This accesses the internal browser - use with caution
|
|
165
|
-
*/
|
|
166
|
-
async getBrowser(): Promise<Browser> {
|
|
167
|
-
const cluster = await this.getCluster();
|
|
168
|
-
|
|
169
|
-
// Access the browser through a dummy task
|
|
170
|
-
// This is a workaround since cluster doesn't expose browser directly
|
|
171
|
-
return new Promise((resolve, reject) => {
|
|
172
|
-
cluster
|
|
173
|
-
.execute(async ({ page }: { page: Page }) => {
|
|
174
|
-
const browser = page.browser();
|
|
175
|
-
resolve(browser);
|
|
176
|
-
})
|
|
177
|
-
.catch(reject);
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Create an isolated session with its own BrowserContext
|
|
183
|
-
* Uses puppeteer-cluster's task execution for proper resource management
|
|
184
|
-
*/
|
|
185
|
-
async createSession(sessionId: string): Promise<PooledSession> {
|
|
186
|
-
// Check if session already exists
|
|
187
|
-
const existing = this.sessions.get(sessionId);
|
|
188
|
-
if (existing) {
|
|
189
|
-
debug.log('preview', `โป๏ธ Reusing existing session: ${sessionId}`);
|
|
190
|
-
return existing;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
debug.log('preview', `๐ Creating isolated session: ${sessionId}`);
|
|
194
|
-
|
|
195
|
-
const cluster = await this.getCluster();
|
|
196
|
-
const browser = await this.getBrowser();
|
|
197
|
-
|
|
198
|
-
// Create isolated BrowserContext for this session
|
|
199
|
-
// This provides FULL ISOLATION: cookies, localStorage, sessionStorage, cache
|
|
200
|
-
// When sessionId is prefixed with projectId (e.g., "project123:tab-1"),
|
|
201
|
-
// this ensures complete isolation between projects
|
|
202
|
-
const context = await browser.createBrowserContext();
|
|
203
|
-
const page = await context.newPage();
|
|
204
|
-
|
|
205
|
-
const session: PooledSession = {
|
|
206
|
-
context,
|
|
207
|
-
page,
|
|
208
|
-
createdAt: Date.now(),
|
|
209
|
-
sessionId
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
this.sessions.set(sessionId, session);
|
|
213
|
-
|
|
214
|
-
debug.log('preview', `โ
Session created: ${sessionId} (total: ${this.sessions.size})`);
|
|
215
|
-
|
|
216
|
-
return session;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get an existing session
|
|
221
|
-
*/
|
|
222
|
-
getSession(sessionId: string): PooledSession | null {
|
|
223
|
-
return this.sessions.get(sessionId) ?? null;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Get the browser context for a session
|
|
228
|
-
*/
|
|
229
|
-
getContext(sessionId: string): BrowserContext | null {
|
|
230
|
-
return this.sessions.get(sessionId)?.context ?? null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Destroy a session and clean up all its resources
|
|
235
|
-
*/
|
|
236
|
-
async destroySession(sessionId: string): Promise<void> {
|
|
237
|
-
const session = this.sessions.get(sessionId);
|
|
238
|
-
if (!session) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
debug.log('preview', `๐๏ธ Destroying session: ${sessionId}`);
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
// Close the page first
|
|
246
|
-
if (session.page && !session.page.isClosed()) {
|
|
247
|
-
await session.page.close().catch((err: Error) => {
|
|
248
|
-
debug.warn('preview', `Error closing page: ${err.message}`);
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Close the context (this clears all cookies, storage, cache)
|
|
253
|
-
await session.context.close().catch((err: Error) => {
|
|
254
|
-
debug.warn('preview', `Error closing context: ${err.message}`);
|
|
255
|
-
});
|
|
256
|
-
} catch (error) {
|
|
257
|
-
debug.warn('preview', `โ ๏ธ Error destroying session: ${error}`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
this.sessions.delete(sessionId);
|
|
261
|
-
debug.log('preview', `โ
Session destroyed (remaining: ${this.sessions.size})`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Execute a task in the cluster (for one-off operations)
|
|
266
|
-
*/
|
|
267
|
-
async execute<T>(
|
|
268
|
-
taskFunction: (opts: { page: Page; data: any }) => Promise<T>,
|
|
269
|
-
data?: any
|
|
270
|
-
): Promise<T> {
|
|
271
|
-
const cluster = await this.getCluster();
|
|
272
|
-
return cluster.execute(data, taskFunction);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Queue a task in the cluster
|
|
277
|
-
*/
|
|
278
|
-
async queue(taskFunction: (opts: { page: Page; data: any }) => Promise<void>, data?: any): Promise<void> {
|
|
279
|
-
const cluster = await this.getCluster();
|
|
280
|
-
cluster.queue(data, taskFunction);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Check if a session is valid
|
|
285
|
-
*/
|
|
286
|
-
isSessionValid(sessionId: string): boolean {
|
|
287
|
-
const session = this.sessions.get(sessionId);
|
|
288
|
-
if (!session) return false;
|
|
289
|
-
|
|
290
|
-
// Check if page is still open
|
|
291
|
-
if (session.page.isClosed()) return false;
|
|
292
|
-
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get pool statistics
|
|
298
|
-
*/
|
|
299
|
-
getStats() {
|
|
300
|
-
return {
|
|
301
|
-
clusterActive: this.cluster !== null,
|
|
302
|
-
activeSessions: this.sessions.size,
|
|
303
|
-
maxConcurrency: this.config.maxConcurrency,
|
|
304
|
-
sessions: Array.from(this.sessions.entries()).map(([id, session]) => ({
|
|
305
|
-
sessionId: id,
|
|
306
|
-
createdAt: session.createdAt,
|
|
307
|
-
ageMs: Date.now() - session.createdAt,
|
|
308
|
-
pageOpen: !session.page.isClosed()
|
|
309
|
-
}))
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Wait for all queued tasks to complete
|
|
315
|
-
*/
|
|
316
|
-
async idle(): Promise<void> {
|
|
317
|
-
if (this.cluster) {
|
|
318
|
-
await this.cluster.idle();
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Clean up all resources
|
|
324
|
-
*/
|
|
325
|
-
async cleanup(): Promise<void> {
|
|
326
|
-
debug.log('preview', '๐งน Cleaning up browser pool...');
|
|
327
|
-
|
|
328
|
-
// Destroy all sessions
|
|
329
|
-
const sessionIds = Array.from(this.sessions.keys());
|
|
330
|
-
await Promise.all(sessionIds.map((id) => this.destroySession(id)));
|
|
331
|
-
|
|
332
|
-
// Close the cluster
|
|
333
|
-
if (this.cluster) {
|
|
334
|
-
try {
|
|
335
|
-
await this.cluster.idle();
|
|
336
|
-
await this.cluster.close();
|
|
337
|
-
} catch (error) {
|
|
338
|
-
debug.warn('preview', `โ ๏ธ Error closing cluster: ${error}`);
|
|
339
|
-
}
|
|
340
|
-
this.cluster = null;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
debug.log('preview', 'โ
Browser pool cleaned up');
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Singleton instance
|
|
348
|
-
export const browserPool = new BrowserPool();
|
|
349
|
-
|
|
350
|
-
// Graceful shutdown handlers
|
|
351
|
-
const gracefulShutdown = async (signal: string) => {
|
|
352
|
-
debug.log('preview', `Received ${signal}, cleaning up...`);
|
|
353
|
-
await browserPool.cleanup();
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
357
|
-
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
1
|
+
/**
|
|
2
|
+
* Browser Pool Module using puppeteer-cluster
|
|
3
|
+
*
|
|
4
|
+
* Uses puppeteer-cluster for efficient browser management with isolated contexts.
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* - puppeteer-cluster manages browser lifecycle and crash recovery
|
|
8
|
+
* - CONCURRENCY_CONTEXT mode: shared browser, isolated contexts per worker
|
|
9
|
+
* - Each user session gets its own isolated BrowserContext
|
|
10
|
+
*
|
|
11
|
+
* Isolation per session:
|
|
12
|
+
* - Separate cookies
|
|
13
|
+
* - Separate localStorage/sessionStorage
|
|
14
|
+
* - Separate cache
|
|
15
|
+
* - Separate service workers
|
|
16
|
+
* - No data leakage between users
|
|
17
|
+
*
|
|
18
|
+
* Memory Usage:
|
|
19
|
+
* - 1 user: ~300MB (browser) + ~20MB (context) = ~320MB
|
|
20
|
+
* - 10 users: ~300MB (browser) + ~200MB (contexts) = ~500MB
|
|
21
|
+
* - vs. old: 10 users = 10 browsers = 2-5GB
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { Cluster } from 'puppeteer-cluster';
|
|
25
|
+
import type { Browser, BrowserContext, Page } from 'puppeteer';
|
|
26
|
+
import { debug } from '$shared/utils/logger';
|
|
27
|
+
|
|
28
|
+
export interface PoolConfig {
|
|
29
|
+
maxConcurrency: number; // Maximum concurrent isolated contexts
|
|
30
|
+
timeout: number; // Task timeout
|
|
31
|
+
retryLimit: number; // Number of retries on failure
|
|
32
|
+
retryDelay: number; // Delay between retries
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PooledSession {
|
|
36
|
+
context: BrowserContext;
|
|
37
|
+
page: Page;
|
|
38
|
+
createdAt: number;
|
|
39
|
+
sessionId: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_CONFIG: PoolConfig = {
|
|
43
|
+
maxConcurrency: 50, // Support up to 50 concurrent users
|
|
44
|
+
timeout: 60000, // 60 second timeout
|
|
45
|
+
retryLimit: 3, // Retry 3 times on failure
|
|
46
|
+
retryDelay: 1000 // 1 second delay between retries
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Optimized Chromium launch arguments for low-resource usage
|
|
51
|
+
*/
|
|
52
|
+
const CHROMIUM_ARGS = [
|
|
53
|
+
// === CORE STABILITY (Windows compatible) ===
|
|
54
|
+
'--no-sandbox',
|
|
55
|
+
'--disable-dev-shm-usage',
|
|
56
|
+
'--disable-gpu',
|
|
57
|
+
|
|
58
|
+
// === PREVENT THROTTLING ===
|
|
59
|
+
'--disable-background-timer-throttling',
|
|
60
|
+
'--disable-backgrounding-occluded-windows',
|
|
61
|
+
'--disable-renderer-backgrounding',
|
|
62
|
+
|
|
63
|
+
// === DISABLE UNNECESSARY FEATURES ===
|
|
64
|
+
'--no-first-run',
|
|
65
|
+
'--no-default-browser-check',
|
|
66
|
+
'--disable-extensions',
|
|
67
|
+
'--disable-popup-blocking',
|
|
68
|
+
|
|
69
|
+
// === LOW-END DEVICE OPTIMIZATIONS ===
|
|
70
|
+
'--memory-pressure-off',
|
|
71
|
+
'--disable-features=TranslateUI',
|
|
72
|
+
'--disable-sync',
|
|
73
|
+
'--disable-domain-reliability',
|
|
74
|
+
'--disable-client-side-phishing-detection',
|
|
75
|
+
'--disable-software-rasterizer',
|
|
76
|
+
'--disable-smooth-scrolling',
|
|
77
|
+
'--disable-threaded-animation',
|
|
78
|
+
'--disable-threaded-scrolling',
|
|
79
|
+
'--disable-composited-antialiasing',
|
|
80
|
+
'--disable-webgl',
|
|
81
|
+
'--disable-webgl2',
|
|
82
|
+
'--disable-accelerated-2d-canvas',
|
|
83
|
+
'--disable-gpu-vsync',
|
|
84
|
+
'--disable-ipc-flooding-protection',
|
|
85
|
+
|
|
86
|
+
// === AUDIO SUPPORT ===
|
|
87
|
+
'--autoplay-policy=no-user-gesture-required',
|
|
88
|
+
'--use-fake-ui-for-media-stream'
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
class BrowserPool {
|
|
92
|
+
private cluster: Cluster | null = null;
|
|
93
|
+
private sessions = new Map<string, PooledSession>();
|
|
94
|
+
private config: PoolConfig;
|
|
95
|
+
private isInitializing = false;
|
|
96
|
+
private initPromise: Promise<Cluster> | null = null;
|
|
97
|
+
|
|
98
|
+
constructor(config: Partial<PoolConfig> = {}) {
|
|
99
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get or create the puppeteer-cluster instance
|
|
104
|
+
* Thread-safe: multiple calls during initialization will wait for the same promise
|
|
105
|
+
*/
|
|
106
|
+
async getCluster(): Promise<Cluster> {
|
|
107
|
+
if (this.cluster) {
|
|
108
|
+
return this.cluster;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.isInitializing && this.initPromise) {
|
|
112
|
+
return this.initPromise;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.isInitializing = true;
|
|
116
|
+
this.initPromise = this.launchCluster();
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
this.cluster = await this.initPromise;
|
|
120
|
+
return this.cluster;
|
|
121
|
+
} finally {
|
|
122
|
+
this.isInitializing = false;
|
|
123
|
+
this.initPromise = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Launch puppeteer-cluster with CONCURRENCY_CONTEXT for isolation
|
|
129
|
+
*/
|
|
130
|
+
private async launchCluster(): Promise<Cluster> {
|
|
131
|
+
debug.log('preview', '๐ Launching puppeteer-cluster...');
|
|
132
|
+
|
|
133
|
+
const cluster = await Cluster.launch({
|
|
134
|
+
// CONCURRENCY_CONTEXT: shared browser, isolated context per worker
|
|
135
|
+
// Each context has its own cookies, localStorage, sessionStorage, cache
|
|
136
|
+
concurrency: Cluster.CONCURRENCY_CONTEXT,
|
|
137
|
+
maxConcurrency: this.config.maxConcurrency,
|
|
138
|
+
timeout: this.config.timeout,
|
|
139
|
+
retryLimit: this.config.retryLimit,
|
|
140
|
+
retryDelay: this.config.retryDelay,
|
|
141
|
+
|
|
142
|
+
puppeteerOptions: {
|
|
143
|
+
headless: true,
|
|
144
|
+
args: CHROMIUM_ARGS
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Monitor events
|
|
148
|
+
monitor: false // Disable built-in monitoring, we use our own logging
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Handle cluster errors
|
|
152
|
+
cluster.on('taskerror', (err, data) => {
|
|
153
|
+
debug.error('preview', `Task error for session ${data?.sessionId}:`, err.message);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
debug.log('preview', 'โ
puppeteer-cluster launched successfully');
|
|
157
|
+
debug.log('preview', `๐ Max concurrency: ${this.config.maxConcurrency}`);
|
|
158
|
+
|
|
159
|
+
return cluster;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get the shared browser instance from the cluster
|
|
164
|
+
* Note: This accesses the internal browser - use with caution
|
|
165
|
+
*/
|
|
166
|
+
async getBrowser(): Promise<Browser> {
|
|
167
|
+
const cluster = await this.getCluster();
|
|
168
|
+
|
|
169
|
+
// Access the browser through a dummy task
|
|
170
|
+
// This is a workaround since cluster doesn't expose browser directly
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
cluster
|
|
173
|
+
.execute(async ({ page }: { page: Page }) => {
|
|
174
|
+
const browser = page.browser();
|
|
175
|
+
resolve(browser);
|
|
176
|
+
})
|
|
177
|
+
.catch(reject);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create an isolated session with its own BrowserContext
|
|
183
|
+
* Uses puppeteer-cluster's task execution for proper resource management
|
|
184
|
+
*/
|
|
185
|
+
async createSession(sessionId: string): Promise<PooledSession> {
|
|
186
|
+
// Check if session already exists
|
|
187
|
+
const existing = this.sessions.get(sessionId);
|
|
188
|
+
if (existing) {
|
|
189
|
+
debug.log('preview', `โป๏ธ Reusing existing session: ${sessionId}`);
|
|
190
|
+
return existing;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
debug.log('preview', `๐ Creating isolated session: ${sessionId}`);
|
|
194
|
+
|
|
195
|
+
const cluster = await this.getCluster();
|
|
196
|
+
const browser = await this.getBrowser();
|
|
197
|
+
|
|
198
|
+
// Create isolated BrowserContext for this session
|
|
199
|
+
// This provides FULL ISOLATION: cookies, localStorage, sessionStorage, cache
|
|
200
|
+
// When sessionId is prefixed with projectId (e.g., "project123:tab-1"),
|
|
201
|
+
// this ensures complete isolation between projects
|
|
202
|
+
const context = await browser.createBrowserContext();
|
|
203
|
+
const page = await context.newPage();
|
|
204
|
+
|
|
205
|
+
const session: PooledSession = {
|
|
206
|
+
context,
|
|
207
|
+
page,
|
|
208
|
+
createdAt: Date.now(),
|
|
209
|
+
sessionId
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
this.sessions.set(sessionId, session);
|
|
213
|
+
|
|
214
|
+
debug.log('preview', `โ
Session created: ${sessionId} (total: ${this.sessions.size})`);
|
|
215
|
+
|
|
216
|
+
return session;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get an existing session
|
|
221
|
+
*/
|
|
222
|
+
getSession(sessionId: string): PooledSession | null {
|
|
223
|
+
return this.sessions.get(sessionId) ?? null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the browser context for a session
|
|
228
|
+
*/
|
|
229
|
+
getContext(sessionId: string): BrowserContext | null {
|
|
230
|
+
return this.sessions.get(sessionId)?.context ?? null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Destroy a session and clean up all its resources
|
|
235
|
+
*/
|
|
236
|
+
async destroySession(sessionId: string): Promise<void> {
|
|
237
|
+
const session = this.sessions.get(sessionId);
|
|
238
|
+
if (!session) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
debug.log('preview', `๐๏ธ Destroying session: ${sessionId}`);
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
// Close the page first
|
|
246
|
+
if (session.page && !session.page.isClosed()) {
|
|
247
|
+
await session.page.close().catch((err: Error) => {
|
|
248
|
+
debug.warn('preview', `Error closing page: ${err.message}`);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Close the context (this clears all cookies, storage, cache)
|
|
253
|
+
await session.context.close().catch((err: Error) => {
|
|
254
|
+
debug.warn('preview', `Error closing context: ${err.message}`);
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
debug.warn('preview', `โ ๏ธ Error destroying session: ${error}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.sessions.delete(sessionId);
|
|
261
|
+
debug.log('preview', `โ
Session destroyed (remaining: ${this.sessions.size})`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Execute a task in the cluster (for one-off operations)
|
|
266
|
+
*/
|
|
267
|
+
async execute<T>(
|
|
268
|
+
taskFunction: (opts: { page: Page; data: any }) => Promise<T>,
|
|
269
|
+
data?: any
|
|
270
|
+
): Promise<T> {
|
|
271
|
+
const cluster = await this.getCluster();
|
|
272
|
+
return cluster.execute(data, taskFunction);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Queue a task in the cluster
|
|
277
|
+
*/
|
|
278
|
+
async queue(taskFunction: (opts: { page: Page; data: any }) => Promise<void>, data?: any): Promise<void> {
|
|
279
|
+
const cluster = await this.getCluster();
|
|
280
|
+
cluster.queue(data, taskFunction);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Check if a session is valid
|
|
285
|
+
*/
|
|
286
|
+
isSessionValid(sessionId: string): boolean {
|
|
287
|
+
const session = this.sessions.get(sessionId);
|
|
288
|
+
if (!session) return false;
|
|
289
|
+
|
|
290
|
+
// Check if page is still open
|
|
291
|
+
if (session.page.isClosed()) return false;
|
|
292
|
+
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get pool statistics
|
|
298
|
+
*/
|
|
299
|
+
getStats() {
|
|
300
|
+
return {
|
|
301
|
+
clusterActive: this.cluster !== null,
|
|
302
|
+
activeSessions: this.sessions.size,
|
|
303
|
+
maxConcurrency: this.config.maxConcurrency,
|
|
304
|
+
sessions: Array.from(this.sessions.entries()).map(([id, session]) => ({
|
|
305
|
+
sessionId: id,
|
|
306
|
+
createdAt: session.createdAt,
|
|
307
|
+
ageMs: Date.now() - session.createdAt,
|
|
308
|
+
pageOpen: !session.page.isClosed()
|
|
309
|
+
}))
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Wait for all queued tasks to complete
|
|
315
|
+
*/
|
|
316
|
+
async idle(): Promise<void> {
|
|
317
|
+
if (this.cluster) {
|
|
318
|
+
await this.cluster.idle();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Clean up all resources
|
|
324
|
+
*/
|
|
325
|
+
async cleanup(): Promise<void> {
|
|
326
|
+
debug.log('preview', '๐งน Cleaning up browser pool...');
|
|
327
|
+
|
|
328
|
+
// Destroy all sessions
|
|
329
|
+
const sessionIds = Array.from(this.sessions.keys());
|
|
330
|
+
await Promise.all(sessionIds.map((id) => this.destroySession(id)));
|
|
331
|
+
|
|
332
|
+
// Close the cluster
|
|
333
|
+
if (this.cluster) {
|
|
334
|
+
try {
|
|
335
|
+
await this.cluster.idle();
|
|
336
|
+
await this.cluster.close();
|
|
337
|
+
} catch (error) {
|
|
338
|
+
debug.warn('preview', `โ ๏ธ Error closing cluster: ${error}`);
|
|
339
|
+
}
|
|
340
|
+
this.cluster = null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
debug.log('preview', 'โ
Browser pool cleaned up');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Singleton instance
|
|
348
|
+
export const browserPool = new BrowserPool();
|
|
349
|
+
|
|
350
|
+
// Graceful shutdown handlers
|
|
351
|
+
const gracefulShutdown = async (signal: string) => {
|
|
352
|
+
debug.log('preview', `Received ${signal}, cleaning up...`);
|
|
353
|
+
await browserPool.cleanup();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
357
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|