cowork-os 0.3.21
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/LICENSE +21 -0
- package/README.md +1638 -0
- package/bin/cowork.js +42 -0
- package/build/entitlements.mac.plist +16 -0
- package/build/icon.icns +0 -0
- package/build/icon.png +0 -0
- package/dist/electron/electron/activity/ActivityRepository.js +190 -0
- package/dist/electron/electron/agent/browser/browser-service.js +639 -0
- package/dist/electron/electron/agent/context-manager.js +225 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
- package/dist/electron/electron/agent/daemon.js +975 -0
- package/dist/electron/electron/agent/executor.js +3561 -0
- package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
- package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
- package/dist/electron/electron/agent/llm/index.js +34 -0
- package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
- package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
- package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
- package/dist/electron/electron/agent/llm/pricing.js +160 -0
- package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
- package/dist/electron/electron/agent/llm/types.js +178 -0
- package/dist/electron/electron/agent/queue-manager.js +378 -0
- package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
- package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
- package/dist/electron/electron/agent/sandbox/runner.js +410 -0
- package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
- package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
- package/dist/electron/electron/agent/search/brave-provider.js +119 -0
- package/dist/electron/electron/agent/search/google-provider.js +100 -0
- package/dist/electron/electron/agent/search/index.js +28 -0
- package/dist/electron/electron/agent/search/provider-factory.js +395 -0
- package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
- package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
- package/dist/electron/electron/agent/search/types.js +40 -0
- package/dist/electron/electron/agent/security/index.js +12 -0
- package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
- package/dist/electron/electron/agent/security/output-filter.js +217 -0
- package/dist/electron/electron/agent/skill-eligibility.js +281 -0
- package/dist/electron/electron/agent/skill-registry.js +396 -0
- package/dist/electron/electron/agent/skills/document.js +878 -0
- package/dist/electron/electron/agent/skills/image-generator.js +225 -0
- package/dist/electron/electron/agent/skills/organizer.js +141 -0
- package/dist/electron/electron/agent/skills/presentation.js +367 -0
- package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
- package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
- package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
- package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
- package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
- package/dist/electron/electron/agent/tools/file-tools.js +719 -0
- package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
- package/dist/electron/electron/agent/tools/image-tools.js +111 -0
- package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
- package/dist/electron/electron/agent/tools/node-tools.js +476 -0
- package/dist/electron/electron/agent/tools/registry.js +2719 -0
- package/dist/electron/electron/agent/tools/search-tools.js +91 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
- package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
- package/dist/electron/electron/agent/tools/system-tools.js +578 -0
- package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
- package/dist/electron/electron/agent/tools/x-tools.js +264 -0
- package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
- package/dist/electron/electron/agents/HeartbeatService.js +356 -0
- package/dist/electron/electron/agents/MentionRepository.js +197 -0
- package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
- package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
- package/dist/electron/electron/canvas/canvas-manager.js +714 -0
- package/dist/electron/electron/canvas/canvas-preload.js +53 -0
- package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
- package/dist/electron/electron/canvas/canvas-store.js +174 -0
- package/dist/electron/electron/canvas/index.js +13 -0
- package/dist/electron/electron/control-plane/client.js +364 -0
- package/dist/electron/electron/control-plane/handlers.js +572 -0
- package/dist/electron/electron/control-plane/index.js +41 -0
- package/dist/electron/electron/control-plane/node-manager.js +264 -0
- package/dist/electron/electron/control-plane/protocol.js +194 -0
- package/dist/electron/electron/control-plane/remote-client.js +437 -0
- package/dist/electron/electron/control-plane/server.js +640 -0
- package/dist/electron/electron/control-plane/settings.js +369 -0
- package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
- package/dist/electron/electron/cron/index.js +30 -0
- package/dist/electron/electron/cron/schedule.js +190 -0
- package/dist/electron/electron/cron/service.js +614 -0
- package/dist/electron/electron/cron/store.js +155 -0
- package/dist/electron/electron/cron/types.js +82 -0
- package/dist/electron/electron/cron/webhook.js +258 -0
- package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
- package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
- package/dist/electron/electron/database/repositories.js +1781 -0
- package/dist/electron/electron/database/schema.js +978 -0
- package/dist/electron/electron/extensions/index.js +33 -0
- package/dist/electron/electron/extensions/loader.js +313 -0
- package/dist/electron/electron/extensions/registry.js +485 -0
- package/dist/electron/electron/extensions/types.js +11 -0
- package/dist/electron/electron/gateway/channel-registry.js +1102 -0
- package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
- package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
- package/dist/electron/electron/gateway/channels/discord.js +975 -0
- package/dist/electron/electron/gateway/channels/email-client.js +593 -0
- package/dist/electron/electron/gateway/channels/email.js +443 -0
- package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
- package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
- package/dist/electron/electron/gateway/channels/imessage.js +465 -0
- package/dist/electron/electron/gateway/channels/index.js +36 -0
- package/dist/electron/electron/gateway/channels/line-client.js +470 -0
- package/dist/electron/electron/gateway/channels/line.js +479 -0
- package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
- package/dist/electron/electron/gateway/channels/matrix.js +592 -0
- package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
- package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
- package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
- package/dist/electron/electron/gateway/channels/signal.js +582 -0
- package/dist/electron/electron/gateway/channels/slack.js +415 -0
- package/dist/electron/electron/gateway/channels/teams.js +596 -0
- package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
- package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
- package/dist/electron/electron/gateway/channels/twitch.js +396 -0
- package/dist/electron/electron/gateway/channels/types.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
- package/dist/electron/electron/gateway/context-policy.js +268 -0
- package/dist/electron/electron/gateway/index.js +1063 -0
- package/dist/electron/electron/gateway/infrastructure.js +496 -0
- package/dist/electron/electron/gateway/router.js +2700 -0
- package/dist/electron/electron/gateway/security.js +375 -0
- package/dist/electron/electron/gateway/session.js +115 -0
- package/dist/electron/electron/gateway/tunnel.js +503 -0
- package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
- package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
- package/dist/electron/electron/hooks/index.js +46 -0
- package/dist/electron/electron/hooks/mappings.js +381 -0
- package/dist/electron/electron/hooks/server.js +480 -0
- package/dist/electron/electron/hooks/settings.js +447 -0
- package/dist/electron/electron/hooks/types.js +41 -0
- package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
- package/dist/electron/electron/ipc/handlers.js +3138 -0
- package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
- package/dist/electron/electron/main.js +448 -0
- package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
- package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
- package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
- package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
- package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
- package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
- package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
- package/dist/electron/electron/mcp/settings.js +446 -0
- package/dist/electron/electron/mcp/types.js +59 -0
- package/dist/electron/electron/memory/MemoryService.js +435 -0
- package/dist/electron/electron/notifications/index.js +17 -0
- package/dist/electron/electron/notifications/service.js +118 -0
- package/dist/electron/electron/notifications/store.js +144 -0
- package/dist/electron/electron/preload.js +842 -0
- package/dist/electron/electron/reports/StandupReportService.js +272 -0
- package/dist/electron/electron/security/concurrency.js +293 -0
- package/dist/electron/electron/security/index.js +15 -0
- package/dist/electron/electron/security/policy-manager.js +435 -0
- package/dist/electron/electron/settings/appearance-manager.js +193 -0
- package/dist/electron/electron/settings/personality-manager.js +724 -0
- package/dist/electron/electron/settings/x-manager.js +58 -0
- package/dist/electron/electron/tailscale/exposure.js +188 -0
- package/dist/electron/electron/tailscale/index.js +28 -0
- package/dist/electron/electron/tailscale/settings.js +205 -0
- package/dist/electron/electron/tailscale/tailscale.js +355 -0
- package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
- package/dist/electron/electron/tray/TrayManager.js +895 -0
- package/dist/electron/electron/tray/index.js +9 -0
- package/dist/electron/electron/updater/index.js +6 -0
- package/dist/electron/electron/updater/update-manager.js +418 -0
- package/dist/electron/electron/utils/env-migration.js +209 -0
- package/dist/electron/electron/utils/process.js +102 -0
- package/dist/electron/electron/utils/rate-limiter.js +104 -0
- package/dist/electron/electron/utils/validation.js +419 -0
- package/dist/electron/electron/utils/x-cli.js +177 -0
- package/dist/electron/electron/voice/VoiceService.js +507 -0
- package/dist/electron/electron/voice/index.js +14 -0
- package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
- package/dist/electron/shared/channelMessages.js +170 -0
- package/dist/electron/shared/types.js +1185 -0
- package/package.json +159 -0
- package/resources/skills/1password.json +10 -0
- package/resources/skills/add-documentation.json +31 -0
- package/resources/skills/analyze-csv.json +17 -0
- package/resources/skills/apple-notes.json +10 -0
- package/resources/skills/apple-reminders.json +10 -0
- package/resources/skills/auto-commenter.json +10 -0
- package/resources/skills/bear-notes.json +10 -0
- package/resources/skills/bird.json +35 -0
- package/resources/skills/blogwatcher.json +10 -0
- package/resources/skills/blucli.json +10 -0
- package/resources/skills/bluebubbles.json +10 -0
- package/resources/skills/camsnap.json +10 -0
- package/resources/skills/clean-imports.json +18 -0
- package/resources/skills/code-review.json +18 -0
- package/resources/skills/coding-agent.json +10 -0
- package/resources/skills/compare-files.json +23 -0
- package/resources/skills/convert-code.json +34 -0
- package/resources/skills/create-changelog.json +24 -0
- package/resources/skills/debug-error.json +17 -0
- package/resources/skills/dependency-check.json +10 -0
- package/resources/skills/discord.json +10 -0
- package/resources/skills/eightctl.json +10 -0
- package/resources/skills/explain-code.json +29 -0
- package/resources/skills/extract-todos.json +18 -0
- package/resources/skills/food-order.json +10 -0
- package/resources/skills/gemini.json +10 -0
- package/resources/skills/generate-readme.json +10 -0
- package/resources/skills/gifgrep.json +10 -0
- package/resources/skills/git-commit.json +10 -0
- package/resources/skills/github.json +10 -0
- package/resources/skills/gog.json +10 -0
- package/resources/skills/goplaces.json +10 -0
- package/resources/skills/himalaya.json +10 -0
- package/resources/skills/imsg.json +10 -0
- package/resources/skills/karpathy-guidelines.json +12 -0
- package/resources/skills/last30days.json +26 -0
- package/resources/skills/local-places.json +10 -0
- package/resources/skills/mcporter.json +10 -0
- package/resources/skills/model-usage.json +10 -0
- package/resources/skills/nano-banana-pro.json +10 -0
- package/resources/skills/nano-pdf.json +10 -0
- package/resources/skills/notion.json +10 -0
- package/resources/skills/obsidian.json +10 -0
- package/resources/skills/openai-image-gen.json +10 -0
- package/resources/skills/openai-whisper-api.json +10 -0
- package/resources/skills/openai-whisper.json +10 -0
- package/resources/skills/openhue.json +10 -0
- package/resources/skills/oracle.json +10 -0
- package/resources/skills/ordercli.json +10 -0
- package/resources/skills/peekaboo.json +10 -0
- package/resources/skills/project-structure.json +10 -0
- package/resources/skills/proofread.json +17 -0
- package/resources/skills/refactor-code.json +31 -0
- package/resources/skills/rename-symbol.json +23 -0
- package/resources/skills/sag.json +10 -0
- package/resources/skills/security-audit.json +18 -0
- package/resources/skills/session-logs.json +10 -0
- package/resources/skills/sherpa-onnx-tts.json +10 -0
- package/resources/skills/skill-creator.json +15 -0
- package/resources/skills/skill-hub.json +29 -0
- package/resources/skills/slack.json +10 -0
- package/resources/skills/songsee.json +10 -0
- package/resources/skills/sonoscli.json +10 -0
- package/resources/skills/spotify-player.json +10 -0
- package/resources/skills/startup-cfo.json +55 -0
- package/resources/skills/summarize-folder.json +18 -0
- package/resources/skills/summarize.json +10 -0
- package/resources/skills/things-mac.json +10 -0
- package/resources/skills/tmux.json +10 -0
- package/resources/skills/translate.json +36 -0
- package/resources/skills/trello.json +10 -0
- package/resources/skills/video-frames.json +10 -0
- package/resources/skills/voice-call.json +10 -0
- package/resources/skills/wacli.json +10 -0
- package/resources/skills/weather.json +10 -0
- package/resources/skills/write-tests.json +31 -0
- package/src/electron/activity/ActivityRepository.ts +238 -0
- package/src/electron/agent/browser/browser-service.ts +721 -0
- package/src/electron/agent/context-manager.ts +257 -0
- package/src/electron/agent/custom-skill-loader.ts +634 -0
- package/src/electron/agent/daemon.ts +1097 -0
- package/src/electron/agent/executor.ts +4017 -0
- package/src/electron/agent/llm/anthropic-provider.ts +175 -0
- package/src/electron/agent/llm/bedrock-provider.ts +236 -0
- package/src/electron/agent/llm/gemini-provider.ts +422 -0
- package/src/electron/agent/llm/index.ts +9 -0
- package/src/electron/agent/llm/ollama-provider.ts +347 -0
- package/src/electron/agent/llm/openai-oauth.ts +127 -0
- package/src/electron/agent/llm/openai-provider.ts +686 -0
- package/src/electron/agent/llm/openrouter-provider.ts +273 -0
- package/src/electron/agent/llm/pricing.ts +180 -0
- package/src/electron/agent/llm/provider-factory.ts +971 -0
- package/src/electron/agent/llm/types.ts +291 -0
- package/src/electron/agent/queue-manager.ts +408 -0
- package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
- package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
- package/src/electron/agent/sandbox/runner.ts +453 -0
- package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
- package/src/electron/agent/sandbox/security-utils.ts +251 -0
- package/src/electron/agent/search/brave-provider.ts +141 -0
- package/src/electron/agent/search/google-provider.ts +131 -0
- package/src/electron/agent/search/index.ts +6 -0
- package/src/electron/agent/search/provider-factory.ts +450 -0
- package/src/electron/agent/search/serpapi-provider.ts +138 -0
- package/src/electron/agent/search/tavily-provider.ts +108 -0
- package/src/electron/agent/search/types.ts +118 -0
- package/src/electron/agent/security/index.ts +20 -0
- package/src/electron/agent/security/input-sanitizer.ts +380 -0
- package/src/electron/agent/security/output-filter.ts +259 -0
- package/src/electron/agent/skill-eligibility.ts +334 -0
- package/src/electron/agent/skill-registry.ts +457 -0
- package/src/electron/agent/skills/document.ts +1070 -0
- package/src/electron/agent/skills/image-generator.ts +272 -0
- package/src/electron/agent/skills/organizer.ts +131 -0
- package/src/electron/agent/skills/presentation.ts +418 -0
- package/src/electron/agent/skills/spreadsheet.ts +166 -0
- package/src/electron/agent/tools/browser-tools.ts +546 -0
- package/src/electron/agent/tools/builtin-settings.ts +422 -0
- package/src/electron/agent/tools/canvas-tools.ts +572 -0
- package/src/electron/agent/tools/cron-tools.ts +723 -0
- package/src/electron/agent/tools/edit-tools.ts +196 -0
- package/src/electron/agent/tools/file-tools.ts +811 -0
- package/src/electron/agent/tools/glob-tools.ts +303 -0
- package/src/electron/agent/tools/grep-tools.ts +432 -0
- package/src/electron/agent/tools/image-tools.ts +126 -0
- package/src/electron/agent/tools/mention-tools.ts +371 -0
- package/src/electron/agent/tools/node-tools.ts +550 -0
- package/src/electron/agent/tools/registry.ts +3052 -0
- package/src/electron/agent/tools/search-tools.ts +111 -0
- package/src/electron/agent/tools/shell-tools.ts +651 -0
- package/src/electron/agent/tools/skill-tools.ts +340 -0
- package/src/electron/agent/tools/system-tools.ts +665 -0
- package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
- package/src/electron/agent/tools/x-tools.ts +267 -0
- package/src/electron/agents/AgentRoleRepository.ts +557 -0
- package/src/electron/agents/HeartbeatService.ts +469 -0
- package/src/electron/agents/MentionRepository.ts +242 -0
- package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
- package/src/electron/agents/WorkingStateRepository.ts +278 -0
- package/src/electron/canvas/canvas-manager.ts +818 -0
- package/src/electron/canvas/canvas-preload.ts +102 -0
- package/src/electron/canvas/canvas-protocol.ts +174 -0
- package/src/electron/canvas/canvas-store.ts +200 -0
- package/src/electron/canvas/index.ts +8 -0
- package/src/electron/control-plane/client.ts +527 -0
- package/src/electron/control-plane/handlers.ts +723 -0
- package/src/electron/control-plane/index.ts +51 -0
- package/src/electron/control-plane/node-manager.ts +322 -0
- package/src/electron/control-plane/protocol.ts +269 -0
- package/src/electron/control-plane/remote-client.ts +517 -0
- package/src/electron/control-plane/server.ts +853 -0
- package/src/electron/control-plane/settings.ts +401 -0
- package/src/electron/control-plane/ssh-tunnel.ts +624 -0
- package/src/electron/cron/index.ts +9 -0
- package/src/electron/cron/schedule.ts +217 -0
- package/src/electron/cron/service.ts +743 -0
- package/src/electron/cron/store.ts +165 -0
- package/src/electron/cron/types.ts +291 -0
- package/src/electron/cron/webhook.ts +303 -0
- package/src/electron/database/SecureSettingsRepository.ts +514 -0
- package/src/electron/database/TaskLabelRepository.ts +148 -0
- package/src/electron/database/repositories.ts +2397 -0
- package/src/electron/database/schema.ts +1017 -0
- package/src/electron/extensions/index.ts +18 -0
- package/src/electron/extensions/loader.ts +336 -0
- package/src/electron/extensions/registry.ts +546 -0
- package/src/electron/extensions/types.ts +372 -0
- package/src/electron/gateway/channel-registry.ts +1267 -0
- package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
- package/src/electron/gateway/channels/bluebubbles.ts +509 -0
- package/src/electron/gateway/channels/discord.ts +1150 -0
- package/src/electron/gateway/channels/email-client.ts +708 -0
- package/src/electron/gateway/channels/email.ts +516 -0
- package/src/electron/gateway/channels/google-chat.ts +760 -0
- package/src/electron/gateway/channels/imessage-client.ts +473 -0
- package/src/electron/gateway/channels/imessage.ts +520 -0
- package/src/electron/gateway/channels/index.ts +21 -0
- package/src/electron/gateway/channels/line-client.ts +598 -0
- package/src/electron/gateway/channels/line.ts +559 -0
- package/src/electron/gateway/channels/matrix-client.ts +632 -0
- package/src/electron/gateway/channels/matrix.ts +655 -0
- package/src/electron/gateway/channels/mattermost-client.ts +526 -0
- package/src/electron/gateway/channels/mattermost.ts +550 -0
- package/src/electron/gateway/channels/signal-client.ts +722 -0
- package/src/electron/gateway/channels/signal.ts +666 -0
- package/src/electron/gateway/channels/slack.ts +458 -0
- package/src/electron/gateway/channels/teams.ts +681 -0
- package/src/electron/gateway/channels/telegram.ts +1727 -0
- package/src/electron/gateway/channels/twitch-client.ts +665 -0
- package/src/electron/gateway/channels/twitch.ts +468 -0
- package/src/electron/gateway/channels/types.ts +1002 -0
- package/src/electron/gateway/channels/whatsapp.ts +1101 -0
- package/src/electron/gateway/context-policy.ts +382 -0
- package/src/electron/gateway/index.ts +1274 -0
- package/src/electron/gateway/infrastructure.ts +645 -0
- package/src/electron/gateway/router.ts +3206 -0
- package/src/electron/gateway/security.ts +422 -0
- package/src/electron/gateway/session.ts +144 -0
- package/src/electron/gateway/tunnel.ts +626 -0
- package/src/electron/guardrails/guardrail-manager.ts +380 -0
- package/src/electron/hooks/gmail-watcher.ts +355 -0
- package/src/electron/hooks/index.ts +30 -0
- package/src/electron/hooks/mappings.ts +404 -0
- package/src/electron/hooks/server.ts +574 -0
- package/src/electron/hooks/settings.ts +466 -0
- package/src/electron/hooks/types.ts +245 -0
- package/src/electron/ipc/canvas-handlers.ts +223 -0
- package/src/electron/ipc/handlers.ts +3661 -0
- package/src/electron/ipc/mission-control-handlers.ts +182 -0
- package/src/electron/main.ts +496 -0
- package/src/electron/mcp/client/MCPClientManager.ts +406 -0
- package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
- package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
- package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
- package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
- package/src/electron/mcp/host/MCPHostServer.ts +388 -0
- package/src/electron/mcp/host/ToolAdapter.ts +140 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
- package/src/electron/mcp/settings.ts +468 -0
- package/src/electron/mcp/types.ts +371 -0
- package/src/electron/memory/MemoryService.ts +523 -0
- package/src/electron/notifications/index.ts +16 -0
- package/src/electron/notifications/service.ts +161 -0
- package/src/electron/notifications/store.ts +163 -0
- package/src/electron/preload.ts +2845 -0
- package/src/electron/reports/StandupReportService.ts +356 -0
- package/src/electron/security/concurrency.ts +333 -0
- package/src/electron/security/index.ts +17 -0
- package/src/electron/security/policy-manager.ts +539 -0
- package/src/electron/settings/appearance-manager.ts +182 -0
- package/src/electron/settings/personality-manager.ts +800 -0
- package/src/electron/settings/x-manager.ts +62 -0
- package/src/electron/tailscale/exposure.ts +262 -0
- package/src/electron/tailscale/index.ts +34 -0
- package/src/electron/tailscale/settings.ts +218 -0
- package/src/electron/tailscale/tailscale.ts +379 -0
- package/src/electron/tray/QuickInputWindow.ts +609 -0
- package/src/electron/tray/TrayManager.ts +1005 -0
- package/src/electron/tray/index.ts +6 -0
- package/src/electron/updater/index.ts +1 -0
- package/src/electron/updater/update-manager.ts +447 -0
- package/src/electron/utils/env-migration.ts +203 -0
- package/src/electron/utils/process.ts +124 -0
- package/src/electron/utils/rate-limiter.ts +130 -0
- package/src/electron/utils/validation.ts +493 -0
- package/src/electron/utils/x-cli.ts +198 -0
- package/src/electron/voice/VoiceService.ts +583 -0
- package/src/electron/voice/index.ts +9 -0
- package/src/electron/voice/voice-settings-manager.ts +403 -0
- package/src/renderer/App.tsx +775 -0
- package/src/renderer/components/ActivityFeed.tsx +407 -0
- package/src/renderer/components/ActivityFeedItem.tsx +285 -0
- package/src/renderer/components/AgentRoleCard.tsx +343 -0
- package/src/renderer/components/AgentRoleEditor.tsx +805 -0
- package/src/renderer/components/AgentSquadSettings.tsx +295 -0
- package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
- package/src/renderer/components/AppearanceSettings.tsx +122 -0
- package/src/renderer/components/ApprovalDialog.tsx +100 -0
- package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
- package/src/renderer/components/CanvasPreview.tsx +1189 -0
- package/src/renderer/components/CommandOutput.tsx +202 -0
- package/src/renderer/components/ContextPolicySettings.tsx +523 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
- package/src/renderer/components/DisclaimerModal.tsx +124 -0
- package/src/renderer/components/DiscordSettings.tsx +436 -0
- package/src/renderer/components/EmailSettings.tsx +606 -0
- package/src/renderer/components/ExtensionsSettings.tsx +542 -0
- package/src/renderer/components/FileViewer.tsx +224 -0
- package/src/renderer/components/GoogleChatSettings.tsx +535 -0
- package/src/renderer/components/GuardrailSettings.tsx +487 -0
- package/src/renderer/components/HooksSettings.tsx +581 -0
- package/src/renderer/components/ImessageSettings.tsx +484 -0
- package/src/renderer/components/LineSettings.tsx +483 -0
- package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
- package/src/renderer/components/MCPSettings.tsx +943 -0
- package/src/renderer/components/MainContent.tsx +2433 -0
- package/src/renderer/components/MatrixSettings.tsx +510 -0
- package/src/renderer/components/MattermostSettings.tsx +473 -0
- package/src/renderer/components/MemorySettings.tsx +247 -0
- package/src/renderer/components/MentionBadge.tsx +87 -0
- package/src/renderer/components/MentionInput.tsx +409 -0
- package/src/renderer/components/MentionList.tsx +476 -0
- package/src/renderer/components/MissionControlPanel.tsx +1995 -0
- package/src/renderer/components/NodesSettings.tsx +316 -0
- package/src/renderer/components/NotificationPanel.tsx +481 -0
- package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
- package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
- package/src/renderer/components/Onboarding/index.ts +3 -0
- package/src/renderer/components/OnboardingModal.tsx +698 -0
- package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
- package/src/renderer/components/PersonalitySettings.tsx +597 -0
- package/src/renderer/components/QueueSettings.tsx +119 -0
- package/src/renderer/components/QuickTaskFAB.tsx +71 -0
- package/src/renderer/components/RightPanel.tsx +413 -0
- package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
- package/src/renderer/components/SearchSettings.tsx +328 -0
- package/src/renderer/components/Settings.tsx +1504 -0
- package/src/renderer/components/Sidebar.tsx +344 -0
- package/src/renderer/components/SignalSettings.tsx +673 -0
- package/src/renderer/components/SkillHubBrowser.tsx +458 -0
- package/src/renderer/components/SkillParameterModal.tsx +185 -0
- package/src/renderer/components/SkillsSettings.tsx +451 -0
- package/src/renderer/components/SlackSettings.tsx +442 -0
- package/src/renderer/components/StandupReportViewer.tsx +614 -0
- package/src/renderer/components/TaskBoard.tsx +498 -0
- package/src/renderer/components/TaskBoardCard.tsx +357 -0
- package/src/renderer/components/TaskBoardColumn.tsx +211 -0
- package/src/renderer/components/TaskLabelManager.tsx +472 -0
- package/src/renderer/components/TaskQueuePanel.tsx +144 -0
- package/src/renderer/components/TaskQuickActions.tsx +492 -0
- package/src/renderer/components/TaskTimeline.tsx +216 -0
- package/src/renderer/components/TaskView.tsx +162 -0
- package/src/renderer/components/TeamsSettings.tsx +518 -0
- package/src/renderer/components/TelegramSettings.tsx +421 -0
- package/src/renderer/components/Toast.tsx +76 -0
- package/src/renderer/components/TraySettings.tsx +189 -0
- package/src/renderer/components/TwitchSettings.tsx +511 -0
- package/src/renderer/components/UpdateSettings.tsx +295 -0
- package/src/renderer/components/VoiceIndicator.tsx +270 -0
- package/src/renderer/components/VoiceSettings.tsx +867 -0
- package/src/renderer/components/WhatsAppSettings.tsx +721 -0
- package/src/renderer/components/WorkingStateEditor.tsx +309 -0
- package/src/renderer/components/WorkingStateHistory.tsx +481 -0
- package/src/renderer/components/WorkspaceSelector.tsx +150 -0
- package/src/renderer/components/XSettings.tsx +311 -0
- package/src/renderer/global.d.ts +9 -0
- package/src/renderer/hooks/useAgentContext.ts +153 -0
- package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
- package/src/renderer/hooks/useVoiceInput.ts +268 -0
- package/src/renderer/index.html +12 -0
- package/src/renderer/main.tsx +10 -0
- package/src/renderer/public/cowork-os-logo.png +0 -0
- package/src/renderer/quick-input.html +164 -0
- package/src/renderer/styles/index.css +14504 -0
- package/src/renderer/utils/agentMessages.ts +749 -0
- package/src/renderer/utils/voice-directives.ts +169 -0
- package/src/shared/channelMessages.ts +213 -0
- package/src/shared/types.ts +3608 -0
- package/tsconfig.electron.json +26 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages Live Canvas sessions - agent-driven visual workspaces that render
|
|
5
|
+
* HTML/CSS/JS content in dedicated Electron BrowserWindows.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Session lifecycle management (create, show, hide, close)
|
|
9
|
+
* - Content pushing with auto-reload via file watching
|
|
10
|
+
* - JavaScript execution in canvas context
|
|
11
|
+
* - Screenshot capture
|
|
12
|
+
* - A2UI (Agent-to-UI) action handling
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { app, BrowserWindow, screen, shell } from 'electron';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as fs from 'fs/promises';
|
|
18
|
+
import { existsSync, readdirSync } from 'fs';
|
|
19
|
+
import { randomUUID } from 'crypto';
|
|
20
|
+
import chokidar, { type FSWatcher } from 'chokidar';
|
|
21
|
+
import type {
|
|
22
|
+
CanvasSession,
|
|
23
|
+
CanvasSessionMode,
|
|
24
|
+
CanvasEvent,
|
|
25
|
+
CanvasA2UIAction,
|
|
26
|
+
CanvasSnapshot,
|
|
27
|
+
} from '../../shared/types';
|
|
28
|
+
import { loadCanvasStore, saveCanvasStore } from './canvas-store';
|
|
29
|
+
|
|
30
|
+
// Default HTML scaffold for new canvas sessions
|
|
31
|
+
const DEFAULT_HTML = `<!DOCTYPE html>
|
|
32
|
+
<html>
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="UTF-8">
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
36
|
+
<title>Live Canvas</title>
|
|
37
|
+
<style>
|
|
38
|
+
body {
|
|
39
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 20px;
|
|
42
|
+
background: #0f1220;
|
|
43
|
+
color: #e7e9f2;
|
|
44
|
+
min-height: 100vh;
|
|
45
|
+
display: grid;
|
|
46
|
+
place-items: center;
|
|
47
|
+
}
|
|
48
|
+
.loading {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
gap: 16px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
.spinner {
|
|
57
|
+
width: 44px;
|
|
58
|
+
height: 44px;
|
|
59
|
+
border-radius: 50%;
|
|
60
|
+
border: 4px solid rgba(255, 255, 255, 0.15);
|
|
61
|
+
border-top-color: #52d1dc;
|
|
62
|
+
animation: spin 0.9s linear infinite;
|
|
63
|
+
box-shadow: 0 0 18px rgba(82, 209, 220, 0.35);
|
|
64
|
+
}
|
|
65
|
+
.message {
|
|
66
|
+
font-size: 1.05em;
|
|
67
|
+
color: #a3acc4;
|
|
68
|
+
letter-spacing: 0.2px;
|
|
69
|
+
}
|
|
70
|
+
@keyframes spin {
|
|
71
|
+
to { transform: rotate(360deg); }
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="loading">
|
|
77
|
+
<div class="spinner"></div>
|
|
78
|
+
<div class="message">Waiting for content...</div>
|
|
79
|
+
</div>
|
|
80
|
+
</body>
|
|
81
|
+
</html>`;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Canvas Manager Singleton
|
|
85
|
+
*/
|
|
86
|
+
export class CanvasManager {
|
|
87
|
+
private static instance: CanvasManager;
|
|
88
|
+
|
|
89
|
+
private sessions: Map<string, CanvasSession> = new Map();
|
|
90
|
+
private windows: Map<string, BrowserWindow> = new Map();
|
|
91
|
+
private watchers: Map<string, FSWatcher> = new Map();
|
|
92
|
+
private windowToSession: Map<number, string> = new Map();
|
|
93
|
+
private mainWindow: BrowserWindow | null = null;
|
|
94
|
+
private eventCallback: ((event: CanvasEvent) => void) | null = null;
|
|
95
|
+
private a2uiCallback: ((action: CanvasA2UIAction) => void) | null = null;
|
|
96
|
+
|
|
97
|
+
private constructor() {}
|
|
98
|
+
|
|
99
|
+
private getSessionMode(session: CanvasSession): CanvasSessionMode {
|
|
100
|
+
return session.mode || 'html';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private getCanvasUrl(sessionId: string): string {
|
|
104
|
+
return `canvas://${sessionId}/index.html`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private normalizeUrl(rawUrl: string): string {
|
|
108
|
+
const trimmed = rawUrl.trim();
|
|
109
|
+
if (!trimmed) {
|
|
110
|
+
throw new Error('URL cannot be empty');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
114
|
+
const parsed = new URL(withScheme);
|
|
115
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
116
|
+
throw new Error('Only http and https URLs are supported for canvas browsing');
|
|
117
|
+
}
|
|
118
|
+
return parsed.toString();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the singleton instance
|
|
123
|
+
*/
|
|
124
|
+
static getInstance(): CanvasManager {
|
|
125
|
+
if (!CanvasManager.instance) {
|
|
126
|
+
CanvasManager.instance = new CanvasManager();
|
|
127
|
+
}
|
|
128
|
+
return CanvasManager.instance;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Set the main window reference for event broadcasting
|
|
133
|
+
*/
|
|
134
|
+
setMainWindow(window: BrowserWindow): void {
|
|
135
|
+
this.mainWindow = window;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Set callback for canvas events (used for IPC broadcasting)
|
|
140
|
+
*/
|
|
141
|
+
setEventCallback(callback: (event: CanvasEvent) => void): void {
|
|
142
|
+
this.eventCallback = callback;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Set callback for A2UI actions
|
|
147
|
+
*/
|
|
148
|
+
setA2UICallback(callback: (action: CanvasA2UIAction) => void): void {
|
|
149
|
+
this.a2uiCallback = callback;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Restore sessions from disk storage
|
|
154
|
+
* Called on app startup to reload persisted sessions
|
|
155
|
+
*/
|
|
156
|
+
async restoreSessions(): Promise<void> {
|
|
157
|
+
try {
|
|
158
|
+
const store = await loadCanvasStore();
|
|
159
|
+
|
|
160
|
+
for (const session of store.sessions) {
|
|
161
|
+
// Only restore active sessions with valid directories
|
|
162
|
+
if (session.status === 'active' && existsSync(session.sessionDir)) {
|
|
163
|
+
this.sessions.set(session.id, session);
|
|
164
|
+
console.log(`[CanvasManager] Restored session ${session.id} for task ${session.taskId}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(`[CanvasManager] Restored ${this.sessions.size} sessions from disk`);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[CanvasManager] Failed to restore sessions:', error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Persist all sessions to disk
|
|
176
|
+
*/
|
|
177
|
+
async persistSessions(): Promise<void> {
|
|
178
|
+
try {
|
|
179
|
+
const sessions = Array.from(this.sessions.values());
|
|
180
|
+
await saveCanvasStore({ version: 1, sessions });
|
|
181
|
+
console.log(`[CanvasManager] Persisted ${sessions.length} sessions to disk`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('[CanvasManager] Failed to persist sessions:', error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create a new canvas session
|
|
189
|
+
*/
|
|
190
|
+
async createSession(
|
|
191
|
+
taskId: string,
|
|
192
|
+
workspaceId: string,
|
|
193
|
+
title?: string,
|
|
194
|
+
options?: { mode?: CanvasSessionMode; url?: string }
|
|
195
|
+
): Promise<CanvasSession> {
|
|
196
|
+
const sessionId = randomUUID();
|
|
197
|
+
const sessionDir = path.join(
|
|
198
|
+
app.getPath('userData'),
|
|
199
|
+
'canvas',
|
|
200
|
+
sessionId
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Create session directory
|
|
204
|
+
await fs.mkdir(sessionDir, { recursive: true });
|
|
205
|
+
|
|
206
|
+
// Write default HTML scaffold
|
|
207
|
+
await fs.writeFile(
|
|
208
|
+
path.join(sessionDir, 'index.html'),
|
|
209
|
+
DEFAULT_HTML,
|
|
210
|
+
'utf-8'
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const normalizedUrl = options?.url ? this.normalizeUrl(options.url) : undefined;
|
|
214
|
+
const normalizedMode = options?.mode === 'browser' && normalizedUrl ? 'browser' : 'html';
|
|
215
|
+
const session: CanvasSession = {
|
|
216
|
+
id: sessionId,
|
|
217
|
+
taskId,
|
|
218
|
+
workspaceId,
|
|
219
|
+
sessionDir,
|
|
220
|
+
mode: normalizedMode,
|
|
221
|
+
url: normalizedMode === 'browser' ? normalizedUrl : undefined,
|
|
222
|
+
status: 'active',
|
|
223
|
+
title: title || `Canvas ${new Date().toLocaleTimeString()}`,
|
|
224
|
+
createdAt: Date.now(),
|
|
225
|
+
lastUpdatedAt: Date.now(),
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
this.sessions.set(sessionId, session);
|
|
229
|
+
|
|
230
|
+
// Persist sessions to disk
|
|
231
|
+
await this.persistSessions();
|
|
232
|
+
|
|
233
|
+
// Emit event
|
|
234
|
+
this.emitEvent({
|
|
235
|
+
type: 'session_created',
|
|
236
|
+
sessionId,
|
|
237
|
+
taskId,
|
|
238
|
+
timestamp: Date.now(),
|
|
239
|
+
session,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log(`[CanvasManager] Created session ${sessionId} for task ${taskId}`);
|
|
243
|
+
return session;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get a session by ID
|
|
248
|
+
*/
|
|
249
|
+
getSession(sessionId: string): CanvasSession | undefined {
|
|
250
|
+
return this.sessions.get(sessionId);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get session ID from a BrowserWindow
|
|
255
|
+
*/
|
|
256
|
+
getSessionFromWindow(window: BrowserWindow): string | undefined {
|
|
257
|
+
return this.windowToSession.get(window.id);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* List all sessions for a task
|
|
262
|
+
*/
|
|
263
|
+
listSessionsForTask(taskId: string): CanvasSession[] {
|
|
264
|
+
return Array.from(this.sessions.values()).filter(
|
|
265
|
+
(s) => s.taskId === taskId
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* List all active sessions
|
|
271
|
+
*/
|
|
272
|
+
listAllSessions(): CanvasSession[] {
|
|
273
|
+
return Array.from(this.sessions.values());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Push content to a canvas session
|
|
278
|
+
*/
|
|
279
|
+
async pushContent(
|
|
280
|
+
sessionId: string,
|
|
281
|
+
content: string,
|
|
282
|
+
filename: string = 'index.html'
|
|
283
|
+
): Promise<void> {
|
|
284
|
+
const session = this.sessions.get(sessionId);
|
|
285
|
+
if (!session) {
|
|
286
|
+
const existingSessions = Array.from(this.sessions.keys());
|
|
287
|
+
console.error(`[CanvasManager] Session not found: "${sessionId}"`);
|
|
288
|
+
console.error(`[CanvasManager] Existing sessions: ${existingSessions.length > 0 ? existingSessions.join(', ') : 'none'}`);
|
|
289
|
+
throw new Error(`Canvas session not found: "${sessionId}". Available sessions: ${existingSessions.join(', ') || 'none'}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const wasBrowser = this.getSessionMode(session) === 'browser';
|
|
293
|
+
|
|
294
|
+
// Sanitize filename to prevent path traversal
|
|
295
|
+
const safeFilename = path.basename(filename);
|
|
296
|
+
const filePath = path.join(session.sessionDir, safeFilename);
|
|
297
|
+
|
|
298
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
299
|
+
|
|
300
|
+
// Switch back to HTML mode when content is pushed
|
|
301
|
+
session.mode = 'html';
|
|
302
|
+
session.url = undefined;
|
|
303
|
+
|
|
304
|
+
// Update session timestamp
|
|
305
|
+
session.lastUpdatedAt = Date.now();
|
|
306
|
+
|
|
307
|
+
// Persist sessions to disk (in background, don't await to avoid blocking)
|
|
308
|
+
this.persistSessions().catch(err => console.error('[CanvasManager] Failed to persist after push:', err));
|
|
309
|
+
|
|
310
|
+
// Ensure a hidden window exists for snapshots (NOT shown to user)
|
|
311
|
+
// The window will only be shown when user explicitly requests via showCanvas()
|
|
312
|
+
const window = await this.ensureWindowForSnapshots(sessionId);
|
|
313
|
+
|
|
314
|
+
// If the session previously loaded a remote URL, navigate back to canvas content
|
|
315
|
+
if (wasBrowser && window && !window.isDestroyed()) {
|
|
316
|
+
await window.loadURL(this.getCanvasUrl(sessionId));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Ensure watcher is running for HTML mode
|
|
320
|
+
this.startWatcher(sessionId, session.sessionDir, window);
|
|
321
|
+
|
|
322
|
+
// Emit event
|
|
323
|
+
this.emitEvent({
|
|
324
|
+
type: 'content_pushed',
|
|
325
|
+
sessionId,
|
|
326
|
+
taskId: session.taskId,
|
|
327
|
+
timestamp: Date.now(),
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
this.emitEvent({
|
|
331
|
+
type: 'session_updated',
|
|
332
|
+
sessionId,
|
|
333
|
+
taskId: session.taskId,
|
|
334
|
+
timestamp: Date.now(),
|
|
335
|
+
session,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
console.log(`[CanvasManager] Pushed ${safeFilename} to session ${sessionId}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Open a remote URL inside the canvas window (browser mode)
|
|
343
|
+
*/
|
|
344
|
+
async openUrl(
|
|
345
|
+
sessionId: string,
|
|
346
|
+
rawUrl: string,
|
|
347
|
+
options?: { show?: boolean }
|
|
348
|
+
): Promise<string> {
|
|
349
|
+
const session = this.sessions.get(sessionId);
|
|
350
|
+
if (!session) {
|
|
351
|
+
const existingSessions = Array.from(this.sessions.keys());
|
|
352
|
+
console.error(`[CanvasManager] Session not found: "${sessionId}"`);
|
|
353
|
+
console.error(`[CanvasManager] Existing sessions: ${existingSessions.length > 0 ? existingSessions.join(', ') : 'none'}`);
|
|
354
|
+
throw new Error(`Canvas session not found: "${sessionId}". Available sessions: ${existingSessions.join(', ') || 'none'}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const normalizedUrl = this.normalizeUrl(rawUrl);
|
|
358
|
+
|
|
359
|
+
session.mode = 'browser';
|
|
360
|
+
session.url = normalizedUrl;
|
|
361
|
+
session.lastUpdatedAt = Date.now();
|
|
362
|
+
|
|
363
|
+
this.persistSessions().catch(err => console.error('[CanvasManager] Failed to persist after openUrl:', err));
|
|
364
|
+
|
|
365
|
+
const window = await this.ensureWindowForSnapshots(sessionId);
|
|
366
|
+
if (window && !window.isDestroyed()) {
|
|
367
|
+
this.stopWatcher(sessionId);
|
|
368
|
+
if (window.webContents.getURL() !== normalizedUrl) {
|
|
369
|
+
await window.loadURL(normalizedUrl);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this.emitEvent({
|
|
374
|
+
type: 'session_updated',
|
|
375
|
+
sessionId,
|
|
376
|
+
taskId: session.taskId,
|
|
377
|
+
timestamp: Date.now(),
|
|
378
|
+
session,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (options?.show) {
|
|
382
|
+
await this.showCanvas(sessionId);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
console.log(`[CanvasManager] Opened URL in session ${sessionId}: ${normalizedUrl}`);
|
|
386
|
+
return normalizedUrl;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Ensure a window exists for taking snapshots (hidden by default)
|
|
391
|
+
* This creates a hidden window that can be used for previews without
|
|
392
|
+
* showing a separate window to the user
|
|
393
|
+
*/
|
|
394
|
+
private async ensureWindowForSnapshots(sessionId: string): Promise<BrowserWindow> {
|
|
395
|
+
const session = this.sessions.get(sessionId);
|
|
396
|
+
if (!session) {
|
|
397
|
+
throw new Error('Canvas session not found');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let window = this.windows.get(sessionId);
|
|
401
|
+
|
|
402
|
+
if (!window || window.isDestroyed()) {
|
|
403
|
+
// Calculate initial position - to the right of main window or right side of screen
|
|
404
|
+
let initialX: number | undefined;
|
|
405
|
+
let initialY: number | undefined;
|
|
406
|
+
let initialHeight = 700;
|
|
407
|
+
|
|
408
|
+
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
|
409
|
+
const mainBounds = this.mainWindow.getBounds();
|
|
410
|
+
initialX = mainBounds.x + mainBounds.width + 20;
|
|
411
|
+
initialY = mainBounds.y;
|
|
412
|
+
initialHeight = mainBounds.height;
|
|
413
|
+
} else {
|
|
414
|
+
// Fallback: position on right side of primary display
|
|
415
|
+
const primaryDisplay = screen.getPrimaryDisplay();
|
|
416
|
+
const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize;
|
|
417
|
+
initialX = screenWidth - 920; // 900 width + 20 margin
|
|
418
|
+
initialY = 50;
|
|
419
|
+
initialHeight = screenHeight - 100;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Create new HIDDEN window for snapshots
|
|
423
|
+
// NOT a child window - will be positioned to the side when shown
|
|
424
|
+
window = new BrowserWindow({
|
|
425
|
+
x: initialX,
|
|
426
|
+
y: initialY,
|
|
427
|
+
width: 900,
|
|
428
|
+
height: initialHeight,
|
|
429
|
+
title: session.title || 'Live Canvas',
|
|
430
|
+
show: false, // Start hidden - only show when user explicitly requests
|
|
431
|
+
// No parent - independent window that won't overlap main app
|
|
432
|
+
webPreferences: {
|
|
433
|
+
preload: path.join(__dirname, 'canvas-preload.js'),
|
|
434
|
+
contextIsolation: true,
|
|
435
|
+
nodeIntegration: false,
|
|
436
|
+
sandbox: false,
|
|
437
|
+
},
|
|
438
|
+
backgroundColor: '#1a1a2e',
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
this.windows.set(sessionId, window);
|
|
442
|
+
this.windowToSession.set(window.id, sessionId);
|
|
443
|
+
|
|
444
|
+
// Handle window close
|
|
445
|
+
window.on('closed', () => {
|
|
446
|
+
this.windows.delete(sessionId);
|
|
447
|
+
this.windowToSession.delete(window!.id);
|
|
448
|
+
this.stopWatcher(sessionId);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const mode = this.getSessionMode(session);
|
|
452
|
+
let targetUrl = this.getCanvasUrl(sessionId);
|
|
453
|
+
if (mode === 'browser' && session.url) {
|
|
454
|
+
try {
|
|
455
|
+
targetUrl = this.normalizeUrl(session.url);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.warn('[CanvasManager] Invalid stored URL, falling back to canvas content:', error);
|
|
458
|
+
session.mode = 'html';
|
|
459
|
+
session.url = undefined;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Load the canvas URL (or remote URL for browser mode)
|
|
464
|
+
await window.loadURL(targetUrl);
|
|
465
|
+
|
|
466
|
+
// Start file watcher for auto-reload only in HTML mode
|
|
467
|
+
if (mode === 'html') {
|
|
468
|
+
this.startWatcher(sessionId, session.sessionDir, window);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Ensure watcher state is correct for existing windows
|
|
473
|
+
if (window && !window.isDestroyed()) {
|
|
474
|
+
const mode = this.getSessionMode(session);
|
|
475
|
+
if (mode === 'html') {
|
|
476
|
+
this.startWatcher(sessionId, session.sessionDir, window);
|
|
477
|
+
} else {
|
|
478
|
+
this.stopWatcher(sessionId);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return window;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Show the canvas window (opens it visibly to the user)
|
|
487
|
+
*/
|
|
488
|
+
async showCanvas(sessionId: string): Promise<void> {
|
|
489
|
+
// Ensure window exists (may be hidden)
|
|
490
|
+
const window = await this.ensureWindowForSnapshots(sessionId);
|
|
491
|
+
|
|
492
|
+
let bounds: { x: number; y: number; width: number; height: number };
|
|
493
|
+
|
|
494
|
+
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
|
495
|
+
const mainBounds = this.mainWindow.getBounds();
|
|
496
|
+
console.log(`[CanvasManager] Main window bounds:`, mainBounds);
|
|
497
|
+
|
|
498
|
+
// Position canvas window completely to the RIGHT of the main window
|
|
499
|
+
// This ensures it never overlaps with the main app
|
|
500
|
+
bounds = {
|
|
501
|
+
x: mainBounds.x + mainBounds.width + 20, // 20px gap to the right
|
|
502
|
+
y: mainBounds.y,
|
|
503
|
+
width: 900,
|
|
504
|
+
height: mainBounds.height,
|
|
505
|
+
};
|
|
506
|
+
} else {
|
|
507
|
+
console.log(`[CanvasManager] WARNING: mainWindow not available, using fallback position`);
|
|
508
|
+
// Fallback: position on right side of primary display
|
|
509
|
+
const primaryDisplay = screen.getPrimaryDisplay();
|
|
510
|
+
const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize;
|
|
511
|
+
bounds = {
|
|
512
|
+
x: screenWidth - 920, // 900 width + 20 margin
|
|
513
|
+
y: 50,
|
|
514
|
+
width: 900,
|
|
515
|
+
height: screenHeight - 100,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
console.log(`[CanvasManager] Setting canvas window bounds:`, bounds);
|
|
520
|
+
|
|
521
|
+
// Always set position first to ensure correct placement
|
|
522
|
+
window.setPosition(bounds.x, bounds.y);
|
|
523
|
+
window.setSize(bounds.width, bounds.height);
|
|
524
|
+
|
|
525
|
+
// Show and focus the window so keyboard input works for interactive browsing
|
|
526
|
+
if (!window.isVisible()) {
|
|
527
|
+
window.show();
|
|
528
|
+
}
|
|
529
|
+
window.focus();
|
|
530
|
+
|
|
531
|
+
// Ensure bounds are applied after show (some systems need this)
|
|
532
|
+
window.setBounds(bounds);
|
|
533
|
+
|
|
534
|
+
this.emitEvent({
|
|
535
|
+
type: 'window_opened',
|
|
536
|
+
sessionId,
|
|
537
|
+
taskId: this.sessions.get(sessionId)!.taskId,
|
|
538
|
+
timestamp: Date.now(),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Hide the canvas window
|
|
544
|
+
*/
|
|
545
|
+
hideCanvas(sessionId: string): void {
|
|
546
|
+
const window = this.windows.get(sessionId);
|
|
547
|
+
if (window && !window.isDestroyed()) {
|
|
548
|
+
window.hide();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Close a canvas session
|
|
554
|
+
*/
|
|
555
|
+
async closeSession(sessionId: string): Promise<void> {
|
|
556
|
+
const session = this.sessions.get(sessionId);
|
|
557
|
+
if (!session) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Close window if open
|
|
562
|
+
const window = this.windows.get(sessionId);
|
|
563
|
+
if (window && !window.isDestroyed()) {
|
|
564
|
+
window.close();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Stop watcher
|
|
568
|
+
this.stopWatcher(sessionId);
|
|
569
|
+
|
|
570
|
+
// Update session status
|
|
571
|
+
session.status = 'closed';
|
|
572
|
+
|
|
573
|
+
// Persist sessions to disk (removes closed sessions)
|
|
574
|
+
await this.persistSessions();
|
|
575
|
+
|
|
576
|
+
// Emit event
|
|
577
|
+
this.emitEvent({
|
|
578
|
+
type: 'session_closed',
|
|
579
|
+
sessionId,
|
|
580
|
+
taskId: session.taskId,
|
|
581
|
+
timestamp: Date.now(),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
console.log(`[CanvasManager] Closed session ${sessionId}`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Execute JavaScript in the canvas context
|
|
589
|
+
*/
|
|
590
|
+
async evalScript(sessionId: string, script: string): Promise<unknown> {
|
|
591
|
+
// Ensure window exists (create hidden one if needed)
|
|
592
|
+
const window = await this.ensureWindowForSnapshots(sessionId);
|
|
593
|
+
if (!window || window.isDestroyed()) {
|
|
594
|
+
throw new Error('Canvas window could not be created');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return window.webContents.executeJavaScript(script);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Take a screenshot of the canvas
|
|
602
|
+
*/
|
|
603
|
+
async takeSnapshot(sessionId: string): Promise<CanvasSnapshot> {
|
|
604
|
+
// Ensure window exists (create hidden one if needed)
|
|
605
|
+
const window = await this.ensureWindowForSnapshots(sessionId);
|
|
606
|
+
if (!window || window.isDestroyed()) {
|
|
607
|
+
throw new Error('Canvas window could not be created');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const image = await window.webContents.capturePage();
|
|
611
|
+
const size = image.getSize();
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
sessionId,
|
|
615
|
+
imageBase64: image.toPNG().toString('base64'),
|
|
616
|
+
width: size.width,
|
|
617
|
+
height: size.height,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Export canvas content as a standalone HTML file
|
|
623
|
+
* Returns the HTML content with all assets inlined if possible
|
|
624
|
+
*/
|
|
625
|
+
async exportAsHTML(sessionId: string): Promise<{ content: string; filename: string }> {
|
|
626
|
+
const session = this.sessions.get(sessionId);
|
|
627
|
+
if (!session) {
|
|
628
|
+
throw new Error(`Canvas session not found: ${sessionId}`);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const htmlPath = path.join(session.sessionDir, 'index.html');
|
|
632
|
+
if (!existsSync(htmlPath)) {
|
|
633
|
+
throw new Error('Canvas index.html not found');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const content = await fs.readFile(htmlPath, 'utf-8');
|
|
637
|
+
const filename = `canvas-${session.title?.replace(/[^a-z0-9]/gi, '-') || sessionId.slice(0, 8)}.html`;
|
|
638
|
+
|
|
639
|
+
console.log(`[CanvasManager] Exported HTML for session ${sessionId}`);
|
|
640
|
+
return { content, filename };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Export all canvas files to a target directory
|
|
645
|
+
*/
|
|
646
|
+
async exportToFolder(sessionId: string, targetDir: string): Promise<{ files: string[]; targetDir: string }> {
|
|
647
|
+
const session = this.sessions.get(sessionId);
|
|
648
|
+
if (!session) {
|
|
649
|
+
throw new Error(`Canvas session not found: ${sessionId}`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (!existsSync(session.sessionDir)) {
|
|
653
|
+
throw new Error('Canvas session directory not found');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Create target directory if it doesn't exist
|
|
657
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
658
|
+
|
|
659
|
+
// Get all files in the session directory
|
|
660
|
+
const files = readdirSync(session.sessionDir);
|
|
661
|
+
const copiedFiles: string[] = [];
|
|
662
|
+
|
|
663
|
+
for (const file of files) {
|
|
664
|
+
const srcPath = path.join(session.sessionDir, file);
|
|
665
|
+
const destPath = path.join(targetDir, file);
|
|
666
|
+
await fs.copyFile(srcPath, destPath);
|
|
667
|
+
copiedFiles.push(file);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
console.log(`[CanvasManager] Exported ${copiedFiles.length} files for session ${sessionId} to ${targetDir}`);
|
|
671
|
+
return { files: copiedFiles, targetDir };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Open canvas content in the default system browser
|
|
676
|
+
*/
|
|
677
|
+
async openInBrowser(sessionId: string): Promise<{ success: boolean; path: string }> {
|
|
678
|
+
const session = this.sessions.get(sessionId);
|
|
679
|
+
if (!session) {
|
|
680
|
+
throw new Error(`Canvas session not found: ${sessionId}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (this.getSessionMode(session) === 'browser' && session.url) {
|
|
684
|
+
await shell.openExternal(session.url);
|
|
685
|
+
console.log(`[CanvasManager] Opened session ${sessionId} in browser: ${session.url}`);
|
|
686
|
+
return { success: true, path: session.url };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const htmlPath = path.join(session.sessionDir, 'index.html');
|
|
690
|
+
if (!existsSync(htmlPath)) {
|
|
691
|
+
throw new Error('Canvas index.html not found');
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Open in default browser
|
|
695
|
+
await shell.openPath(htmlPath);
|
|
696
|
+
|
|
697
|
+
console.log(`[CanvasManager] Opened session ${sessionId} in browser: ${htmlPath}`);
|
|
698
|
+
return { success: true, path: htmlPath };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Get the session directory path for external access
|
|
703
|
+
*/
|
|
704
|
+
getSessionDir(sessionId: string): string | null {
|
|
705
|
+
const session = this.sessions.get(sessionId);
|
|
706
|
+
return session?.sessionDir || null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Handle A2UI action from canvas window
|
|
711
|
+
*/
|
|
712
|
+
handleA2UIAction(
|
|
713
|
+
windowId: number,
|
|
714
|
+
action: { actionName: string; componentId?: string; context?: Record<string, unknown> }
|
|
715
|
+
): void {
|
|
716
|
+
const sessionId = this.windowToSession.get(windowId);
|
|
717
|
+
if (!sessionId) return;
|
|
718
|
+
|
|
719
|
+
const session = this.sessions.get(sessionId);
|
|
720
|
+
if (!session) return;
|
|
721
|
+
|
|
722
|
+
const a2uiAction: CanvasA2UIAction = {
|
|
723
|
+
actionName: action.actionName,
|
|
724
|
+
sessionId,
|
|
725
|
+
componentId: action.componentId,
|
|
726
|
+
context: action.context,
|
|
727
|
+
timestamp: Date.now(),
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// Emit event for UI
|
|
731
|
+
this.emitEvent({
|
|
732
|
+
type: 'a2ui_action',
|
|
733
|
+
sessionId,
|
|
734
|
+
taskId: session.taskId,
|
|
735
|
+
timestamp: Date.now(),
|
|
736
|
+
action: a2uiAction,
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// Call A2UI callback if set
|
|
740
|
+
if (this.a2uiCallback) {
|
|
741
|
+
this.a2uiCallback(a2uiAction);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Start file watcher for a session
|
|
747
|
+
*/
|
|
748
|
+
private startWatcher(
|
|
749
|
+
sessionId: string,
|
|
750
|
+
sessionDir: string,
|
|
751
|
+
window: BrowserWindow
|
|
752
|
+
): void {
|
|
753
|
+
if (this.watchers.has(sessionId)) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const watcher = chokidar.watch(sessionDir, {
|
|
758
|
+
ignoreInitial: true,
|
|
759
|
+
awaitWriteFinish: {
|
|
760
|
+
stabilityThreshold: 100,
|
|
761
|
+
pollInterval: 50,
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
watcher.on('change', () => {
|
|
766
|
+
if (!window.isDestroyed()) {
|
|
767
|
+
window.webContents.reload();
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
this.watchers.set(sessionId, watcher);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Stop file watcher for a session
|
|
776
|
+
*/
|
|
777
|
+
private stopWatcher(sessionId: string): void {
|
|
778
|
+
const watcher = this.watchers.get(sessionId);
|
|
779
|
+
if (watcher) {
|
|
780
|
+
watcher.close();
|
|
781
|
+
this.watchers.delete(sessionId);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Emit a canvas event
|
|
787
|
+
*/
|
|
788
|
+
private emitEvent(event: CanvasEvent): void {
|
|
789
|
+
// Call event callback
|
|
790
|
+
if (this.eventCallback) {
|
|
791
|
+
this.eventCallback(event);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Broadcast to main window
|
|
795
|
+
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
|
796
|
+
this.mainWindow.webContents.send('canvas:event', event);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Cleanup all sessions and resources
|
|
802
|
+
*/
|
|
803
|
+
async cleanup(): Promise<void> {
|
|
804
|
+
// Close all windows
|
|
805
|
+
for (const [sessionId, window] of this.windows) {
|
|
806
|
+
if (!window.isDestroyed()) {
|
|
807
|
+
window.close();
|
|
808
|
+
}
|
|
809
|
+
this.stopWatcher(sessionId);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
this.sessions.clear();
|
|
813
|
+
this.windows.clear();
|
|
814
|
+
this.windowToSession.clear();
|
|
815
|
+
|
|
816
|
+
console.log('[CanvasManager] Cleanup complete');
|
|
817
|
+
}
|
|
818
|
+
}
|