@cloudcli-ai/cloudcli 1.33.3 → 1.35.0
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/README.de.md +9 -1
- package/README.ja.md +8 -0
- package/README.ko.md +9 -1
- package/README.md +16 -3
- package/README.ru.md +9 -1
- package/README.tr.md +8 -1
- package/README.zh-CN.md +9 -1
- package/README.zh-TW.md +9 -1
- package/dist/api-docs.html +36 -18
- package/dist/assets/index-CtwxqzGG.js +1407 -0
- package/dist/assets/index-DlvtxFyt.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/browser-use-mcp.js +350 -0
- package/dist-server/server/browser-use-mcp.js.map +1 -0
- package/dist-server/server/claude-sdk.js +26 -6
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +14 -6
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/computer-use-agent.js +242 -0
- package/dist-server/server/computer-use-agent.js.map +1 -0
- package/dist-server/server/computer-use-mcp.js +519 -0
- package/dist-server/server/computer-use-mcp.js.map +1 -0
- package/dist-server/server/cursor-cli.js +27 -11
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +17 -2
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/index.js +149 -40
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +1 -1
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js +55 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js.map +1 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-types.js +2 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-types.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-executor.js +212 -0
- package/dist-server/server/modules/computer-use/computer-executor.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-semantics.service.js +422 -0
- package/dist-server/server/modules/computer-use/computer-semantics.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js +126 -0
- package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use.routes.js +196 -0
- package/dist-server/server/modules/computer-use/computer-use.routes.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use.service.js +759 -0
- package/dist-server/server/modules/computer-use/computer-use.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js +126 -0
- package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/index.js +3 -0
- package/dist-server/server/modules/computer-use/index.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js +59 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js +5 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js +2 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js +5 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js +106 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js +78 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js +68 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js +16 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-types.js +2 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-types.js.map +1 -0
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +23 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +26 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +4 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +5 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +123 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +1 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +39 -0
- package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +10 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +24 -14
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +21 -8
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-cli.test.js +19 -1
- package/dist-server/server/opencode-cli.test.js.map +1 -1
- package/dist-server/server/routes/agent.js +1 -1
- package/dist-server/server/routes/settings.js +1 -1
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +1 -176
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
- package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
- package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +73 -1
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/voice-proxy.js +226 -0
- package/dist-server/server/voice-proxy.js.map +1 -0
- package/electron/assets/logo-macos.icns +0 -0
- package/electron/assets/logo-macos.png +0 -0
- package/electron/assets/logo-windows.ico +0 -0
- package/electron/cloud.js +260 -0
- package/electron/computerAgent.js +290 -0
- package/electron/desktopNotifications.js +378 -0
- package/electron/desktopWindow.js +781 -0
- package/electron/launcher/index.html +14 -0
- package/electron/launcher/launcher.css +801 -0
- package/electron/launcher/launcher.js +829 -0
- package/electron/localServer.js +550 -0
- package/electron/main.js +1060 -0
- package/electron/preload.cjs +63 -0
- package/electron/scripts/generate-macos-icon.js +62 -0
- package/electron/serverInstaller.js +277 -0
- package/electron/tabs.js +87 -0
- package/electron/viewHost.js +331 -0
- package/package.json +86 -5
- package/public/api-docs.html +36 -18
- package/scripts/build-computer-semantics.mjs +133 -0
- package/scripts/copy-computer-semantics-bin.mjs +24 -0
- package/scripts/release/build-server-bundle.js +176 -0
- package/scripts/release/prepare-desktop-app.js +158 -0
- package/server/browser-use-mcp.ts +384 -0
- package/server/claude-sdk.js +28 -6
- package/server/cli.js +15 -6
- package/server/computer-use-agent.ts +279 -0
- package/server/computer-use-mcp.ts +574 -0
- package/server/cursor-cli.js +27 -11
- package/server/gemini-cli.js +17 -2
- package/server/index.js +151 -47
- package/server/load-env.js +1 -1
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +836 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/computer-use/actions/raw-action-dispatcher.ts +67 -0
- package/server/modules/computer-use/actions/raw-action-types.ts +28 -0
- package/server/modules/computer-use/computer-executor.ts +242 -0
- package/server/modules/computer-use/computer-semantics.service.ts +460 -0
- package/server/modules/computer-use/computer-use-mcp.routes.ts +141 -0
- package/server/modules/computer-use/computer-use.routes.ts +211 -0
- package/server/modules/computer-use/computer-use.service.ts +920 -0
- package/server/modules/computer-use/desktop-agent-relay.service.ts +158 -0
- package/server/modules/computer-use/index.ts +2 -0
- package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +82 -0
- package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +5 -0
- package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +23 -0
- package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +5 -0
- package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
- package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
- package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
- package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
- package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +467 -0
- package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +124 -0
- package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +97 -0
- package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +11 -0
- package/server/modules/computer-use/semantics/helpers/windows/Program.cs +534 -0
- package/server/modules/computer-use/semantics/semantic-session-store.ts +87 -0
- package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +17 -0
- package/server/modules/computer-use/semantics/semantic-types.ts +58 -0
- package/server/modules/database/index.ts +1 -0
- package/server/modules/database/migrations.ts +25 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +12 -1
- package/server/modules/database/repositories/sessions.db.ts +227 -23
- package/server/modules/database/schema.ts +27 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
- package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
- package/server/modules/notifications/index.ts +13 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/projects/services/project-management.service.ts +0 -8
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
- package/server/modules/providers/README.md +1 -1
- package/server/modules/providers/index.ts +1 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +5 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
- package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
- package/server/modules/providers/provider.routes.ts +167 -1
- package/server/modules/providers/services/mcp.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
- package/server/modules/providers/services/provider-models.service.ts +1 -1
- package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
- package/server/modules/providers/services/sessions.service.ts +79 -4
- package/server/modules/providers/services/skills.service.ts +25 -1
- package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
- package/server/modules/providers/tests/skills.test.ts +212 -0
- package/server/modules/websocket/README.md +33 -27
- package/server/modules/websocket/index.ts +1 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
- package/server/modules/websocket/services/desktop-agent-websocket.service.ts +51 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
- package/server/modules/websocket/services/websocket-server.service.ts +12 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
- package/server/openai-codex.js +24 -14
- package/server/opencode-cli.js +21 -8
- package/server/opencode-cli.test.js +20 -1
- package/server/routes/agent.js +1 -1
- package/server/routes/settings.js +6 -1
- package/server/services/notification-orchestrator.js +3 -224
- package/server/services/tests/notification-orchestrator.test.js +80 -0
- package/server/shared/interfaces.ts +15 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +84 -1
- package/server/shared/utils.ts +95 -1
- package/server/voice-proxy.js +224 -0
- package/dist/assets/index-73Xw3i9v.css +0 -32
- package/dist/assets/index-9n7A21AT.js +0 -1361
- package/dist/modelConstants.js +0 -848
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
- package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
- package/public/modelConstants.js +0 -848
- /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
- /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
- /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { appConfigDb } from '../../modules/database/index.js';
|
|
6
|
+
import { providerMcpService } from '../../modules/providers/index.js';
|
|
7
|
+
import { getModuleDir } from '../../utils/runtime-paths.js';
|
|
8
|
+
import { getRuntimeReadiness as getExecutorReadiness, } from '../../modules/computer-use/computer-executor.js';
|
|
9
|
+
import { runRawComputerAction } from '../../modules/computer-use/actions/raw-action-dispatcher.js';
|
|
10
|
+
import { desktopAgentRelay } from '../../modules/computer-use/desktop-agent-relay.service.js';
|
|
11
|
+
import { computerSemanticsService } from '../../modules/computer-use/computer-semantics.service.js';
|
|
12
|
+
import { semanticOperationNames } from '../../modules/computer-use/semantics/semantic-tool-dispatcher.js';
|
|
13
|
+
const __dirname = getModuleDir(import.meta.url);
|
|
14
|
+
const IS_PLATFORM = process.env.VITE_IS_PLATFORM === 'true';
|
|
15
|
+
const MAX_SESSIONS_PER_OWNER = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE_MAX_SESSIONS_PER_OWNER || '1', 10);
|
|
16
|
+
const SESSION_TTL_MS = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE_SESSION_TTL_MS || String(30 * 60 * 1000), 10);
|
|
17
|
+
const STOPPED_SESSION_RETENTION_MS = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE_STOPPED_SESSION_RETENTION_MS || String(30 * 60 * 1000), 10);
|
|
18
|
+
const MAX_STORED_SESSIONS = Number.parseInt(process.env.CLOUDCLI_COMPUTER_USE_MAX_STORED_SESSIONS || '100', 10);
|
|
19
|
+
const COMPUTER_USE_SETTINGS_KEY = 'computer_use_settings';
|
|
20
|
+
const COMPUTER_USE_MCP_TOKEN_KEY = 'computer_use_mcp_token';
|
|
21
|
+
const sessions = new Map();
|
|
22
|
+
let installPromise = null;
|
|
23
|
+
let lastInstallMessage = null;
|
|
24
|
+
const DEFAULT_SETTINGS = {
|
|
25
|
+
enabled: false,
|
|
26
|
+
};
|
|
27
|
+
const AGENT_OWNER_ID = 'agent';
|
|
28
|
+
const MCP_SERVER_NAME = 'cloudcli-computer-use';
|
|
29
|
+
const MCP_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini', 'opencode'];
|
|
30
|
+
function getRuntime() {
|
|
31
|
+
return IS_PLATFORM ? 'cloud' : 'local';
|
|
32
|
+
}
|
|
33
|
+
function readSettings() {
|
|
34
|
+
try {
|
|
35
|
+
const raw = appConfigDb.get(COMPUTER_USE_SETTINGS_KEY);
|
|
36
|
+
if (!raw) {
|
|
37
|
+
return DEFAULT_SETTINGS;
|
|
38
|
+
}
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
return {
|
|
41
|
+
enabled: parsed.enabled === true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.warn('[Computer Use] Failed to read settings:', error?.message || error);
|
|
46
|
+
return DEFAULT_SETTINGS;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function writeSettings(settings) {
|
|
50
|
+
const normalized = {
|
|
51
|
+
enabled: settings.enabled === true,
|
|
52
|
+
};
|
|
53
|
+
appConfigDb.set(COMPUTER_USE_SETTINGS_KEY, JSON.stringify(normalized));
|
|
54
|
+
return normalized;
|
|
55
|
+
}
|
|
56
|
+
function getOrCreateMcpToken() {
|
|
57
|
+
const existing = appConfigDb.get(COMPUTER_USE_MCP_TOKEN_KEY);
|
|
58
|
+
if (existing) {
|
|
59
|
+
return existing;
|
|
60
|
+
}
|
|
61
|
+
const token = randomBytes(32).toString('hex');
|
|
62
|
+
appConfigDb.set(COMPUTER_USE_MCP_TOKEN_KEY, token);
|
|
63
|
+
return token;
|
|
64
|
+
}
|
|
65
|
+
function getSetupMessage(settings, readiness) {
|
|
66
|
+
if (!settings.enabled) {
|
|
67
|
+
return 'Computer Use is disabled in settings.';
|
|
68
|
+
}
|
|
69
|
+
if (getRuntime() === 'cloud') {
|
|
70
|
+
return 'Open CloudCLI Desktop on this computer, connect the same account, and enable Computer Use.';
|
|
71
|
+
}
|
|
72
|
+
if (!readiness.nutInstalled || !readiness.screenshotInstalled) {
|
|
73
|
+
return 'Install the desktop control runtime to capture the screen and drive the mouse and keyboard.';
|
|
74
|
+
}
|
|
75
|
+
return readiness.installMessage || 'Computer Use runtime is not ready.';
|
|
76
|
+
}
|
|
77
|
+
function getMcpCommand() {
|
|
78
|
+
const serverDir = path.resolve(__dirname, '..', '..');
|
|
79
|
+
const mcpScriptPath = path.join(serverDir, 'computer-use-mcp.js');
|
|
80
|
+
if (fs.existsSync(mcpScriptPath)) {
|
|
81
|
+
return {
|
|
82
|
+
command: process.execPath,
|
|
83
|
+
args: [mcpScriptPath],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
command: 'cloudcli',
|
|
88
|
+
args: ['computer-use-mcp'],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function getMcpApiUrl() {
|
|
92
|
+
const port = process.env.SERVER_PORT || process.env.PORT || '3001';
|
|
93
|
+
return `http://127.0.0.1:${port}/api/computer-use-mcp`;
|
|
94
|
+
}
|
|
95
|
+
function getRuntimeReadiness() {
|
|
96
|
+
const base = getExecutorReadiness();
|
|
97
|
+
return {
|
|
98
|
+
...base,
|
|
99
|
+
installInProgress: Boolean(installPromise),
|
|
100
|
+
installMessage: lastInstallMessage,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function runCommand(command, args) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const child = spawn(command, args, {
|
|
106
|
+
cwd: process.cwd(),
|
|
107
|
+
env: process.env,
|
|
108
|
+
shell: false,
|
|
109
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
110
|
+
});
|
|
111
|
+
const output = [];
|
|
112
|
+
child.stdout.on('data', (chunk) => output.push(String(chunk)));
|
|
113
|
+
child.stderr.on('data', (chunk) => output.push(String(chunk)));
|
|
114
|
+
child.on('error', reject);
|
|
115
|
+
child.on('close', (code) => {
|
|
116
|
+
if (code === 0) {
|
|
117
|
+
resolve();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
reject(new Error(output.join('').trim() || `${command} ${args.join(' ')} exited with code ${code}`));
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function formatInstallError(error) {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
if (process.platform === 'linux' && /libxtst|x11|xtst|libpng|imagemagick|scrot/i.test(message)) {
|
|
127
|
+
return [
|
|
128
|
+
'Installing the desktop control runtime needs system packages.',
|
|
129
|
+
'On Debian/Ubuntu run: sudo apt-get install -y libxtst-dev libpng-dev imagemagick',
|
|
130
|
+
'then try again.',
|
|
131
|
+
].join(' ');
|
|
132
|
+
}
|
|
133
|
+
return message || 'Failed to install the Computer Use runtime.';
|
|
134
|
+
}
|
|
135
|
+
function isPackagedElectronNodeRuntime() {
|
|
136
|
+
return process.env.ELECTRON_RUN_AS_NODE === '1' && Boolean(process.versions.electron);
|
|
137
|
+
}
|
|
138
|
+
async function installRuntime() {
|
|
139
|
+
if (installPromise) {
|
|
140
|
+
return installPromise;
|
|
141
|
+
}
|
|
142
|
+
const readiness = getExecutorReadiness();
|
|
143
|
+
if (readiness.nutInstalled && readiness.screenshotInstalled) {
|
|
144
|
+
lastInstallMessage = 'Computer Use runtime is available.';
|
|
145
|
+
return { success: true, message: lastInstallMessage };
|
|
146
|
+
}
|
|
147
|
+
if (isPackagedElectronNodeRuntime()) {
|
|
148
|
+
lastInstallMessage = 'Computer Use runtime was not bundled with this desktop build.';
|
|
149
|
+
return { success: false, message: lastInstallMessage };
|
|
150
|
+
}
|
|
151
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
152
|
+
installPromise = (async () => {
|
|
153
|
+
try {
|
|
154
|
+
lastInstallMessage = 'Installing desktop control runtime…';
|
|
155
|
+
await runCommand(npmCommand, [
|
|
156
|
+
'install',
|
|
157
|
+
'--no-save',
|
|
158
|
+
'--no-package-lock',
|
|
159
|
+
'@nut-tree-fork/nut-js',
|
|
160
|
+
'screenshot-desktop',
|
|
161
|
+
]);
|
|
162
|
+
lastInstallMessage = 'Computer Use runtime installed.';
|
|
163
|
+
return { success: true, message: lastInstallMessage };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
lastInstallMessage = formatInstallError(error);
|
|
167
|
+
return { success: false, message: lastInstallMessage };
|
|
168
|
+
}
|
|
169
|
+
})();
|
|
170
|
+
try {
|
|
171
|
+
return await installPromise;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
installPromise = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function getOwnerId(owner) {
|
|
178
|
+
if (owner.id === undefined || owner.id === null || String(owner.id).trim() === '') {
|
|
179
|
+
throw new Error('Authenticated user is required.');
|
|
180
|
+
}
|
|
181
|
+
return String(owner.id);
|
|
182
|
+
}
|
|
183
|
+
function publicSession(session) {
|
|
184
|
+
const { ownerId: _ownerId, ...publicFields } = session;
|
|
185
|
+
return publicFields;
|
|
186
|
+
}
|
|
187
|
+
function ownerSessions(ownerId) {
|
|
188
|
+
return [...sessions.values()].filter((session) => session.ownerId === ownerId);
|
|
189
|
+
}
|
|
190
|
+
function canAccessSession(ownerId, session) {
|
|
191
|
+
return session.ownerId === ownerId || session.ownerId === AGENT_OWNER_ID;
|
|
192
|
+
}
|
|
193
|
+
function normalizeSessionId(sessionId) {
|
|
194
|
+
if (typeof sessionId !== 'string') {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const trimmed = sessionId.trim();
|
|
198
|
+
return trimmed ? trimmed : null;
|
|
199
|
+
}
|
|
200
|
+
function findActiveAgentSession() {
|
|
201
|
+
return ownerSessions(AGENT_OWNER_ID)
|
|
202
|
+
.filter((session) => session.status === 'ready')
|
|
203
|
+
.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0] || null;
|
|
204
|
+
}
|
|
205
|
+
function positiveDuration(value, fallback) {
|
|
206
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
207
|
+
}
|
|
208
|
+
async function expireStaleSessions(now = Date.now()) {
|
|
209
|
+
const sessionTtl = positiveDuration(SESSION_TTL_MS, 30 * 60 * 1000);
|
|
210
|
+
const stoppedRetention = positiveDuration(STOPPED_SESSION_RETENTION_MS, sessionTtl);
|
|
211
|
+
for (const [sessionId, session] of sessions.entries()) {
|
|
212
|
+
const updatedAt = Date.parse(session.updatedAt);
|
|
213
|
+
if (!Number.isFinite(updatedAt)) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (session.status === 'ready') {
|
|
217
|
+
if (now - updatedAt <= sessionTtl) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
session.status = 'stopped';
|
|
221
|
+
session.agentAccessEnabled = false;
|
|
222
|
+
session.updatedAt = new Date(now).toISOString();
|
|
223
|
+
session.lastAction = 'expire';
|
|
224
|
+
session.message = 'Computer Use session expired after inactivity.';
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (now - updatedAt > stoppedRetention) {
|
|
228
|
+
sessions.delete(sessionId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const maxStoredSessions = Number.isFinite(MAX_STORED_SESSIONS) && MAX_STORED_SESSIONS > 0
|
|
232
|
+
? MAX_STORED_SESSIONS
|
|
233
|
+
: 100;
|
|
234
|
+
if (sessions.size <= maxStoredSessions) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const removable = [...sessions.values()]
|
|
238
|
+
.filter((session) => session.status !== 'ready')
|
|
239
|
+
.sort((a, b) => Date.parse(a.updatedAt) - Date.parse(b.updatedAt));
|
|
240
|
+
for (const session of removable) {
|
|
241
|
+
if (sessions.size <= maxStoredSessions) {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
sessions.delete(session.id);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function applyRelayResult(session, result) {
|
|
248
|
+
if (typeof result.screenshotDataUrl === 'string') {
|
|
249
|
+
session.screenshotDataUrl = result.screenshotDataUrl;
|
|
250
|
+
}
|
|
251
|
+
if (result.displaySize) {
|
|
252
|
+
session.displaySize = result.displaySize;
|
|
253
|
+
}
|
|
254
|
+
if (result.cursor) {
|
|
255
|
+
session.cursor = { x: result.cursor.x, y: result.cursor.y, actor: session.cursor?.actor ?? 'agent' };
|
|
256
|
+
}
|
|
257
|
+
session.updatedAt = new Date().toISOString();
|
|
258
|
+
}
|
|
259
|
+
function stripSessionArgs(args) {
|
|
260
|
+
const { sessionId: _sessionId, ...toolArgs } = args;
|
|
261
|
+
return toolArgs;
|
|
262
|
+
}
|
|
263
|
+
async function refreshScreenshot(session) {
|
|
264
|
+
if (getRuntime() === 'cloud') {
|
|
265
|
+
const result = (await desktopAgentRelay.relay('screenshot', { sessionId: session.id }));
|
|
266
|
+
applyRelayResult(session, result);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
applyRelayResult(session, await runRawComputerAction({ type: 'screenshot' }, session));
|
|
270
|
+
}
|
|
271
|
+
/** Runs one action and refreshes the session screenshot afterwards. */
|
|
272
|
+
async function performAction(session, action) {
|
|
273
|
+
if (getRuntime() === 'cloud') {
|
|
274
|
+
const result = (await desktopAgentRelay.relay(action.type, {
|
|
275
|
+
...action,
|
|
276
|
+
sessionId: session.id,
|
|
277
|
+
displaySize: session.displaySize,
|
|
278
|
+
}));
|
|
279
|
+
applyRelayResult(session, result);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
applyRelayResult(session, await runRawComputerAction(action, session));
|
|
283
|
+
}
|
|
284
|
+
/** Reads the current cursor position in screenshot-pixel space. */
|
|
285
|
+
async function getCursorPosition(session) {
|
|
286
|
+
if (getRuntime() === 'cloud') {
|
|
287
|
+
const result = (await desktopAgentRelay.relay('cursor_position', {
|
|
288
|
+
sessionId: session.id,
|
|
289
|
+
displaySize: session.displaySize,
|
|
290
|
+
}));
|
|
291
|
+
applyRelayResult(session, result);
|
|
292
|
+
if (result.position) {
|
|
293
|
+
return result.position;
|
|
294
|
+
}
|
|
295
|
+
return session.cursor ? { x: session.cursor.x, y: session.cursor.y } : { x: 0, y: 0 };
|
|
296
|
+
}
|
|
297
|
+
const result = await runRawComputerAction({ type: 'cursor_position' }, session);
|
|
298
|
+
applyRelayResult(session, result);
|
|
299
|
+
return result.position || session.cursor || { x: 0, y: 0 };
|
|
300
|
+
}
|
|
301
|
+
function assertReady(session) {
|
|
302
|
+
if (session.status !== 'ready') {
|
|
303
|
+
throw new Error(session.message || 'Computer Use session is not available.');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function agentToolsAvailable() {
|
|
307
|
+
const settings = readSettings();
|
|
308
|
+
if (!settings.enabled) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
if (getRuntime() === 'cloud') {
|
|
312
|
+
return desktopAgentRelay.isConnected();
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
function assertAgentToolsAvailable() {
|
|
317
|
+
if (agentToolsAvailable()) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const settings = readSettings();
|
|
321
|
+
if (!settings.enabled) {
|
|
322
|
+
throw new Error('Computer Use agent tools are disabled.');
|
|
323
|
+
}
|
|
324
|
+
throw new Error(getRuntime() === 'cloud'
|
|
325
|
+
? 'No desktop is linked. Open CloudCLI Desktop on this computer, connect the same account, and enable Computer Use.'
|
|
326
|
+
: 'Computer Use agent tools are disabled.');
|
|
327
|
+
}
|
|
328
|
+
function stopSessions(lastAction, message) {
|
|
329
|
+
for (const session of sessions.values()) {
|
|
330
|
+
session.status = 'stopped';
|
|
331
|
+
session.agentAccessEnabled = false;
|
|
332
|
+
session.updatedAt = new Date().toISOString();
|
|
333
|
+
session.lastAction = lastAction;
|
|
334
|
+
session.message = message;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
export const computerUseService = {
|
|
338
|
+
async getSettings() {
|
|
339
|
+
return readSettings();
|
|
340
|
+
},
|
|
341
|
+
async updateSettings(settings) {
|
|
342
|
+
const current = readSettings();
|
|
343
|
+
const enabled = typeof settings.enabled === 'boolean' ? settings.enabled : current.enabled;
|
|
344
|
+
const next = writeSettings({ enabled });
|
|
345
|
+
if (next.enabled) {
|
|
346
|
+
await this.registerAgentMcp();
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
await this.unregisterAgentMcp();
|
|
350
|
+
desktopAgentRelay.disconnectAll('Computer Use was disabled in this environment.');
|
|
351
|
+
stopSessions('settings:disabled', 'Computer Use was disabled in settings.');
|
|
352
|
+
}
|
|
353
|
+
return next;
|
|
354
|
+
},
|
|
355
|
+
async getStatus() {
|
|
356
|
+
const settings = readSettings();
|
|
357
|
+
const readiness = getRuntimeReadiness();
|
|
358
|
+
const isCloud = getRuntime() === 'cloud';
|
|
359
|
+
const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled;
|
|
360
|
+
// Cloud mode still respects the saved feature setting. When enabled, cloud
|
|
361
|
+
// availability comes from a linked desktop agent because the hosted server
|
|
362
|
+
// has no screen of its own.
|
|
363
|
+
const desktopAgentConnected = desktopAgentRelay.isConnected();
|
|
364
|
+
const available = settings.enabled && (isCloud
|
|
365
|
+
? desktopAgentConnected
|
|
366
|
+
: runtimeReady);
|
|
367
|
+
return {
|
|
368
|
+
enabled: settings.enabled,
|
|
369
|
+
runtime: getRuntime(),
|
|
370
|
+
available,
|
|
371
|
+
desktopAgentConnected,
|
|
372
|
+
desktopAgentCount: desktopAgentRelay.connectedCount(),
|
|
373
|
+
nutInstalled: readiness.nutInstalled,
|
|
374
|
+
screenshotInstalled: readiness.screenshotInstalled,
|
|
375
|
+
installInProgress: readiness.installInProgress,
|
|
376
|
+
sessionCount: sessions.size,
|
|
377
|
+
message: available ? 'Computer Use runtime is available.' : getSetupMessage(settings, readiness),
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
async registerAgentMcp() {
|
|
381
|
+
const { command, args } = getMcpCommand();
|
|
382
|
+
const results = await providerMcpService.addMcpServerToAllProviders({
|
|
383
|
+
name: MCP_SERVER_NAME,
|
|
384
|
+
scope: 'user',
|
|
385
|
+
transport: 'stdio',
|
|
386
|
+
command,
|
|
387
|
+
args,
|
|
388
|
+
env: {
|
|
389
|
+
CLOUDCLI_COMPUTER_USE_MCP_TOKEN: getOrCreateMcpToken(),
|
|
390
|
+
CLOUDCLI_COMPUTER_USE_API_URL: getMcpApiUrl(),
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
return { name: MCP_SERVER_NAME, command, args, results };
|
|
394
|
+
},
|
|
395
|
+
getMcpToken() {
|
|
396
|
+
return getOrCreateMcpToken();
|
|
397
|
+
},
|
|
398
|
+
async unregisterAgentMcp() {
|
|
399
|
+
const results = await Promise.all(MCP_PROVIDERS.map(async (provider) => {
|
|
400
|
+
try {
|
|
401
|
+
const result = await providerMcpService.removeProviderMcpServer(provider, {
|
|
402
|
+
name: MCP_SERVER_NAME,
|
|
403
|
+
scope: 'user',
|
|
404
|
+
});
|
|
405
|
+
return { provider, removed: result.removed };
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
return {
|
|
409
|
+
provider,
|
|
410
|
+
removed: false,
|
|
411
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}));
|
|
415
|
+
return { name: MCP_SERVER_NAME, results };
|
|
416
|
+
},
|
|
417
|
+
async installRuntime() {
|
|
418
|
+
const result = await installRuntime();
|
|
419
|
+
return {
|
|
420
|
+
...result,
|
|
421
|
+
status: await this.getStatus(),
|
|
422
|
+
};
|
|
423
|
+
},
|
|
424
|
+
async listSessions(owner) {
|
|
425
|
+
const ownerId = getOwnerId(owner);
|
|
426
|
+
await expireStaleSessions();
|
|
427
|
+
return [...sessions.values()]
|
|
428
|
+
.filter((session) => canAccessSession(ownerId, session))
|
|
429
|
+
.map(publicSession);
|
|
430
|
+
},
|
|
431
|
+
async createSession(owner, options) {
|
|
432
|
+
const ownerId = getOwnerId(owner);
|
|
433
|
+
await expireStaleSessions();
|
|
434
|
+
const createdBy = options?.createdBy ?? 'user';
|
|
435
|
+
const now = new Date().toISOString();
|
|
436
|
+
const session = {
|
|
437
|
+
id: randomUUID(),
|
|
438
|
+
ownerId,
|
|
439
|
+
createdBy,
|
|
440
|
+
runtime: getRuntime(),
|
|
441
|
+
status: 'unavailable',
|
|
442
|
+
screenshotDataUrl: null,
|
|
443
|
+
createdAt: now,
|
|
444
|
+
updatedAt: now,
|
|
445
|
+
lastAction: 'create',
|
|
446
|
+
// Consent is always OFF at creation — the user must explicitly grant control,
|
|
447
|
+
// even for agent-initiated sessions controlling the full desktop.
|
|
448
|
+
agentAccessEnabled: false,
|
|
449
|
+
displaySize: null,
|
|
450
|
+
message: null,
|
|
451
|
+
cursor: null,
|
|
452
|
+
};
|
|
453
|
+
const activeOwnerSessions = ownerSessions(ownerId).filter((item) => item.status === 'ready');
|
|
454
|
+
if (activeOwnerSessions.length >= MAX_SESSIONS_PER_OWNER) {
|
|
455
|
+
throw new Error(`Computer Use is limited to ${MAX_SESSIONS_PER_OWNER} active session(s).`);
|
|
456
|
+
}
|
|
457
|
+
const settings = readSettings();
|
|
458
|
+
const readiness = getRuntimeReadiness();
|
|
459
|
+
const isCloud = getRuntime() === 'cloud';
|
|
460
|
+
const runtimeReady = readiness.nutInstalled && readiness.screenshotInstalled;
|
|
461
|
+
const ready = settings.enabled && (isCloud
|
|
462
|
+
? desktopAgentRelay.isConnected()
|
|
463
|
+
: runtimeReady);
|
|
464
|
+
if (!ready) {
|
|
465
|
+
session.message = getSetupMessage(settings, readiness);
|
|
466
|
+
sessions.set(session.id, session);
|
|
467
|
+
return publicSession(session);
|
|
468
|
+
}
|
|
469
|
+
// In cloud mode the linked desktop agent is the consent authority and prompts
|
|
470
|
+
// the user per its own consent mode, so the relay is allowed to act. In local
|
|
471
|
+
// mode the user must still grant control from the panel.
|
|
472
|
+
if (isCloud) {
|
|
473
|
+
session.agentAccessEnabled = true;
|
|
474
|
+
}
|
|
475
|
+
session.status = 'ready';
|
|
476
|
+
session.message = isCloud
|
|
477
|
+
? 'Computer Use session is ready on the linked desktop.'
|
|
478
|
+
: 'Computer Use session is ready. Grant control to let agents act.';
|
|
479
|
+
sessions.set(session.id, session);
|
|
480
|
+
try {
|
|
481
|
+
await refreshScreenshot(session);
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
session.status = 'unavailable';
|
|
485
|
+
session.message = error instanceof Error ? error.message : 'Failed to capture the screen.';
|
|
486
|
+
}
|
|
487
|
+
return publicSession(session);
|
|
488
|
+
},
|
|
489
|
+
async grantAgentAccess(owner, sessionId) {
|
|
490
|
+
const ownerId = getOwnerId(owner);
|
|
491
|
+
const session = sessions.get(sessionId);
|
|
492
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
493
|
+
throw new Error('Computer Use session not found.');
|
|
494
|
+
}
|
|
495
|
+
session.agentAccessEnabled = true;
|
|
496
|
+
session.updatedAt = new Date().toISOString();
|
|
497
|
+
session.lastAction = 'consent:grant';
|
|
498
|
+
return publicSession(session);
|
|
499
|
+
},
|
|
500
|
+
async revokeAgentAccess(owner, sessionId) {
|
|
501
|
+
const ownerId = getOwnerId(owner);
|
|
502
|
+
const session = sessions.get(sessionId);
|
|
503
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
504
|
+
throw new Error('Computer Use session not found.');
|
|
505
|
+
}
|
|
506
|
+
session.agentAccessEnabled = false;
|
|
507
|
+
session.updatedAt = new Date().toISOString();
|
|
508
|
+
session.lastAction = 'consent:revoke';
|
|
509
|
+
return publicSession(session);
|
|
510
|
+
},
|
|
511
|
+
async stopSession(owner, sessionId) {
|
|
512
|
+
const ownerId = getOwnerId(owner);
|
|
513
|
+
const session = sessions.get(sessionId);
|
|
514
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
515
|
+
return { stopped: false };
|
|
516
|
+
}
|
|
517
|
+
session.status = 'stopped';
|
|
518
|
+
session.agentAccessEnabled = false;
|
|
519
|
+
session.updatedAt = new Date().toISOString();
|
|
520
|
+
session.lastAction = 'stop';
|
|
521
|
+
session.message = 'Computer Use session stopped. Agent control is revoked.';
|
|
522
|
+
if (getRuntime() === 'cloud' && desktopAgentRelay.isConnected()) {
|
|
523
|
+
// Best-effort: tell the desktop agent to forget this session's consent.
|
|
524
|
+
void desktopAgentRelay.relay('stop_session', { sessionId }).catch(() => undefined);
|
|
525
|
+
}
|
|
526
|
+
return { stopped: true, session: publicSession(session) };
|
|
527
|
+
},
|
|
528
|
+
async deleteSession(owner, sessionId) {
|
|
529
|
+
const ownerId = getOwnerId(owner);
|
|
530
|
+
const session = sessions.get(sessionId);
|
|
531
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
532
|
+
return { deleted: false };
|
|
533
|
+
}
|
|
534
|
+
sessions.delete(sessionId);
|
|
535
|
+
return { deleted: true, sessionId };
|
|
536
|
+
},
|
|
537
|
+
// --- User-initiated actions (from the panel) -------------------------------
|
|
538
|
+
async userScreenshot(owner, sessionId) {
|
|
539
|
+
const ownerId = getOwnerId(owner);
|
|
540
|
+
const session = sessions.get(sessionId);
|
|
541
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
542
|
+
throw new Error('Computer Use session not found.');
|
|
543
|
+
}
|
|
544
|
+
assertReady(session);
|
|
545
|
+
await refreshScreenshot(session);
|
|
546
|
+
session.lastAction = 'screenshot';
|
|
547
|
+
return publicSession(session);
|
|
548
|
+
},
|
|
549
|
+
async userClick(owner, sessionId, input) {
|
|
550
|
+
const ownerId = getOwnerId(owner);
|
|
551
|
+
const session = sessions.get(sessionId);
|
|
552
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
553
|
+
throw new Error('Computer Use session not found.');
|
|
554
|
+
}
|
|
555
|
+
assertReady(session);
|
|
556
|
+
await performAction(session, {
|
|
557
|
+
type: 'click',
|
|
558
|
+
button: input.button || 'left',
|
|
559
|
+
point: { x: input.x, y: input.y },
|
|
560
|
+
double: input.double === true,
|
|
561
|
+
});
|
|
562
|
+
session.cursor = { x: input.x, y: input.y, actor: 'user' };
|
|
563
|
+
session.lastAction = input.double ? 'double_click' : 'click';
|
|
564
|
+
return publicSession(session);
|
|
565
|
+
},
|
|
566
|
+
async userPressKey(owner, sessionId, key) {
|
|
567
|
+
const ownerId = getOwnerId(owner);
|
|
568
|
+
const session = sessions.get(sessionId);
|
|
569
|
+
if (!session || !canAccessSession(ownerId, session)) {
|
|
570
|
+
throw new Error('Computer Use session not found.');
|
|
571
|
+
}
|
|
572
|
+
assertReady(session);
|
|
573
|
+
await performAction(session, { type: 'key', key });
|
|
574
|
+
session.lastAction = `key:${key}`;
|
|
575
|
+
return publicSession(session);
|
|
576
|
+
},
|
|
577
|
+
// --- Agent-initiated actions (via MCP) ------------------------------------
|
|
578
|
+
/**
|
|
579
|
+
* Resolves a session the agent is allowed to act on. In local mode this
|
|
580
|
+
* enforces the in-process per-session consent flag. In cloud mode the linked
|
|
581
|
+
* desktop agent is the consent authority (it prompts the user per its own
|
|
582
|
+
* consent mode), so this only requires the relay to be connected.
|
|
583
|
+
*/
|
|
584
|
+
async getOrCreateAgentSession() {
|
|
585
|
+
assertAgentToolsAvailable();
|
|
586
|
+
await expireStaleSessions();
|
|
587
|
+
const existing = findActiveAgentSession();
|
|
588
|
+
if (existing) {
|
|
589
|
+
return existing;
|
|
590
|
+
}
|
|
591
|
+
const created = await this.createSession({ id: AGENT_OWNER_ID }, { createdBy: 'agent' });
|
|
592
|
+
const session = sessions.get(created.id);
|
|
593
|
+
if (!session) {
|
|
594
|
+
throw new Error('Computer Use session could not be created.');
|
|
595
|
+
}
|
|
596
|
+
return session;
|
|
597
|
+
},
|
|
598
|
+
async getConsentedSession(sessionId) {
|
|
599
|
+
assertAgentToolsAvailable();
|
|
600
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
601
|
+
const session = normalizedSessionId
|
|
602
|
+
? sessions.get(normalizedSessionId)
|
|
603
|
+
: await this.getOrCreateAgentSession();
|
|
604
|
+
if (!session) {
|
|
605
|
+
throw new Error('Computer Use session not found.');
|
|
606
|
+
}
|
|
607
|
+
if (getRuntime() !== 'cloud' && !session.agentAccessEnabled) {
|
|
608
|
+
throw new Error(`Computer Use session ${session.id} is awaiting user consent. Ask the user to grant control in the Computer panel.`);
|
|
609
|
+
}
|
|
610
|
+
assertReady(session);
|
|
611
|
+
return session;
|
|
612
|
+
},
|
|
613
|
+
async agentScreenshot(sessionId) {
|
|
614
|
+
const session = await this.getConsentedSession(sessionId);
|
|
615
|
+
await refreshScreenshot(session);
|
|
616
|
+
session.lastAction = 'screenshot';
|
|
617
|
+
return publicSession(session);
|
|
618
|
+
},
|
|
619
|
+
async agentCursorPosition(sessionId) {
|
|
620
|
+
const session = await this.getConsentedSession(sessionId);
|
|
621
|
+
const point = await getCursorPosition(session);
|
|
622
|
+
session.cursor = { ...point, actor: 'agent' };
|
|
623
|
+
session.lastAction = 'cursor_position';
|
|
624
|
+
return { session: publicSession(session), position: point };
|
|
625
|
+
},
|
|
626
|
+
async agentMouseMove(sessionId, point) {
|
|
627
|
+
const session = await this.getConsentedSession(sessionId);
|
|
628
|
+
await performAction(session, { type: 'mouse_move', point });
|
|
629
|
+
session.cursor = { ...point, actor: 'agent' };
|
|
630
|
+
session.lastAction = 'mouse_move';
|
|
631
|
+
return publicSession(session);
|
|
632
|
+
},
|
|
633
|
+
async agentUnifiedClick(sessionId, input) {
|
|
634
|
+
const session = await this.getConsentedSession(sessionId);
|
|
635
|
+
const button = input.button || 'left';
|
|
636
|
+
const clickCount = Math.max(1, Math.min(Math.trunc(input.clickCount || 1), 5));
|
|
637
|
+
for (let index = 0; index < clickCount; index += 1) {
|
|
638
|
+
await performAction(session, { type: 'click', button, point: input.point, double: false });
|
|
639
|
+
}
|
|
640
|
+
if (input.point) {
|
|
641
|
+
session.cursor = { ...input.point, actor: 'agent' };
|
|
642
|
+
}
|
|
643
|
+
session.lastAction = clickCount > 1 ? `${button}_click:${clickCount}` : `${button}_click`;
|
|
644
|
+
return publicSession(session);
|
|
645
|
+
},
|
|
646
|
+
async agentDrag(sessionId, from, to, button = 'left') {
|
|
647
|
+
const session = await this.getConsentedSession(sessionId);
|
|
648
|
+
await performAction(session, { type: 'drag', from, to, button });
|
|
649
|
+
session.cursor = { ...to, actor: 'agent' };
|
|
650
|
+
session.lastAction = `${button}_drag`;
|
|
651
|
+
return publicSession(session);
|
|
652
|
+
},
|
|
653
|
+
async agentType(sessionId, text) {
|
|
654
|
+
const session = await this.getConsentedSession(sessionId);
|
|
655
|
+
await performAction(session, { type: 'type', text });
|
|
656
|
+
session.lastAction = 'type';
|
|
657
|
+
return publicSession(session);
|
|
658
|
+
},
|
|
659
|
+
async agentKey(sessionId, key) {
|
|
660
|
+
const session = await this.getConsentedSession(sessionId);
|
|
661
|
+
await performAction(session, { type: 'key', key });
|
|
662
|
+
session.lastAction = `key:${key}`;
|
|
663
|
+
return publicSession(session);
|
|
664
|
+
},
|
|
665
|
+
async agentScroll(sessionId, input) {
|
|
666
|
+
const session = await this.getConsentedSession(sessionId);
|
|
667
|
+
const point = typeof input.x === 'number' && typeof input.y === 'number' ? { x: input.x, y: input.y } : undefined;
|
|
668
|
+
await performAction(session, { type: 'scroll', direction: input.direction, amount: input.amount, point });
|
|
669
|
+
if (point) {
|
|
670
|
+
session.cursor = { ...point, actor: 'agent' };
|
|
671
|
+
}
|
|
672
|
+
session.lastAction = `scroll:${input.direction}`;
|
|
673
|
+
return publicSession(session);
|
|
674
|
+
},
|
|
675
|
+
async agentWait(sessionId, timeoutMs) {
|
|
676
|
+
const session = await this.getConsentedSession(sessionId);
|
|
677
|
+
await performAction(session, { type: 'wait', ms: timeoutMs });
|
|
678
|
+
session.lastAction = 'wait';
|
|
679
|
+
return publicSession(session);
|
|
680
|
+
},
|
|
681
|
+
async agentStopSession(sessionId) {
|
|
682
|
+
assertAgentToolsAvailable();
|
|
683
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
684
|
+
if (normalizedSessionId) {
|
|
685
|
+
return this.stopSession({ id: AGENT_OWNER_ID }, normalizedSessionId);
|
|
686
|
+
}
|
|
687
|
+
await expireStaleSessions();
|
|
688
|
+
const existing = findActiveAgentSession();
|
|
689
|
+
if (!existing) {
|
|
690
|
+
return { stopped: false };
|
|
691
|
+
}
|
|
692
|
+
return this.stopSession({ id: AGENT_OWNER_ID }, existing.id);
|
|
693
|
+
},
|
|
694
|
+
async callSemanticTool(toolName, args) {
|
|
695
|
+
if (!semanticOperationNames.has(toolName)) {
|
|
696
|
+
throw new Error(`Unsupported semantic Computer Use tool: ${toolName}`);
|
|
697
|
+
}
|
|
698
|
+
const sessionId = typeof args.sessionId === 'string' ? args.sessionId : undefined;
|
|
699
|
+
const session = await this.getConsentedSession(normalizeSessionId(sessionId) ?? undefined);
|
|
700
|
+
const toolArgs = { ...stripSessionArgs(args), sessionId: session.id };
|
|
701
|
+
const semanticResult = getRuntime() === 'cloud'
|
|
702
|
+
? await desktopAgentRelay.relay('semantic_tool', {
|
|
703
|
+
sessionId: session.id,
|
|
704
|
+
displaySize: session.displaySize,
|
|
705
|
+
toolName,
|
|
706
|
+
arguments: toolArgs,
|
|
707
|
+
})
|
|
708
|
+
: await computerSemanticsService.callTool(toolName, toolArgs);
|
|
709
|
+
applyRelayResult(session, semanticResult);
|
|
710
|
+
session.lastAction = `semantic:${toolName}`;
|
|
711
|
+
return { session: publicSession(session), result: semanticResult };
|
|
712
|
+
},
|
|
713
|
+
/**
|
|
714
|
+
* Cloud only: when a desktop agent links to this hosted environment, expose
|
|
715
|
+
* the computer_* MCP tools only if the user enabled Computer Use in settings.
|
|
716
|
+
*/
|
|
717
|
+
async onDesktopAgentConnected() {
|
|
718
|
+
if (getRuntime() !== 'cloud') {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (!readSettings().enabled) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
await this.registerAgentMcp();
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
console.warn('[Computer Use] Failed to register MCP for linked desktop agent:', error instanceof Error ? error.message : error);
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
/** Cloud only: tear down sessions when the last desktop agent disconnects. */
|
|
732
|
+
async onDesktopAgentDisconnected() {
|
|
733
|
+
if (getRuntime() !== 'cloud' || desktopAgentRelay.isConnected()) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
for (const session of sessions.values()) {
|
|
737
|
+
if (session.status === 'ready') {
|
|
738
|
+
session.status = 'stopped';
|
|
739
|
+
session.agentAccessEnabled = false;
|
|
740
|
+
session.updatedAt = new Date().toISOString();
|
|
741
|
+
session.lastAction = 'agent-disconnected';
|
|
742
|
+
session.message = 'The linked desktop agent disconnected.';
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
async stopAllSessions() {
|
|
747
|
+
stopSessions('shutdown', 'Computer Use session stopped during server shutdown.');
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
// Drive cloud MCP exposure + session teardown off desktop-agent connectivity.
|
|
751
|
+
desktopAgentRelay.setHooks({
|
|
752
|
+
canAcceptConnection: () => getRuntime() === 'cloud' && readSettings().enabled,
|
|
753
|
+
onFirstConnect: () => computerUseService.onDesktopAgentConnected(),
|
|
754
|
+
onLastDisconnect: () => computerUseService.onDesktopAgentDisconnected(),
|
|
755
|
+
});
|
|
756
|
+
process.once('beforeExit', () => {
|
|
757
|
+
void computerUseService.stopAllSessions();
|
|
758
|
+
});
|
|
759
|
+
//# sourceMappingURL=computer-use.service.js.map
|