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,1005 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrayManager - macOS Menu Bar App Integration
|
|
3
|
+
*
|
|
4
|
+
* Provides a native menu bar icon with:
|
|
5
|
+
* - Status indicator (connected/disconnected channels)
|
|
6
|
+
* - Quick actions menu (new task, workspaces, settings)
|
|
7
|
+
* - Show/hide main window on click
|
|
8
|
+
* - Gateway status monitoring
|
|
9
|
+
*
|
|
10
|
+
* Settings are stored encrypted in the database using SecureSettingsRepository.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { app, Tray, Menu, nativeImage, BrowserWindow, shell, NativeImage, globalShortcut } from 'electron';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import * as os from 'os';
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import { ChannelGateway } from '../gateway';
|
|
18
|
+
import { DatabaseManager } from '../database/schema';
|
|
19
|
+
import { TaskRepository, WorkspaceRepository } from '../database/repositories';
|
|
20
|
+
import { AgentDaemon } from '../agent/daemon';
|
|
21
|
+
import { QuickInputWindow } from './QuickInputWindow';
|
|
22
|
+
import { TEMP_WORKSPACE_ID, TEMP_WORKSPACE_NAME, Workspace } from '../../shared/types';
|
|
23
|
+
import { SecureSettingsRepository } from '../database/SecureSettingsRepository';
|
|
24
|
+
|
|
25
|
+
const LEGACY_SETTINGS_FILE = 'tray-settings.json';
|
|
26
|
+
|
|
27
|
+
export interface TrayManagerOptions {
|
|
28
|
+
showDockIcon?: boolean;
|
|
29
|
+
startMinimized?: boolean;
|
|
30
|
+
closeToTray?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TraySettings {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
showDockIcon: boolean;
|
|
36
|
+
startMinimized: boolean;
|
|
37
|
+
closeToTray: boolean;
|
|
38
|
+
showNotifications: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEFAULT_SETTINGS: TraySettings = {
|
|
42
|
+
enabled: true,
|
|
43
|
+
showDockIcon: true,
|
|
44
|
+
startMinimized: false,
|
|
45
|
+
closeToTray: true,
|
|
46
|
+
showNotifications: true,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export class TrayManager {
|
|
50
|
+
private tray: Tray | null = null;
|
|
51
|
+
private mainWindow: BrowserWindow | null = null;
|
|
52
|
+
private gateway: ChannelGateway | null = null;
|
|
53
|
+
private dbManager: DatabaseManager | null = null;
|
|
54
|
+
private agentDaemon: AgentDaemon | null = null;
|
|
55
|
+
private taskRepo: TaskRepository | null = null;
|
|
56
|
+
private workspaceRepo: WorkspaceRepository | null = null;
|
|
57
|
+
private settings: TraySettings = DEFAULT_SETTINGS;
|
|
58
|
+
private connectedChannels: number = 0;
|
|
59
|
+
private activeTaskCount: number = 0;
|
|
60
|
+
private quickInputWindow: QuickInputWindow | null = null;
|
|
61
|
+
private currentQuickTaskId: string | null = null;
|
|
62
|
+
private quickTaskAccumulatedResponse: string = '';
|
|
63
|
+
private currentStepInfo: string = '';
|
|
64
|
+
private legacySettingsPath: string;
|
|
65
|
+
|
|
66
|
+
private static instance: TrayManager | null = null;
|
|
67
|
+
private static migrationCompleted = false;
|
|
68
|
+
|
|
69
|
+
static getInstance(): TrayManager {
|
|
70
|
+
if (!TrayManager.instance) {
|
|
71
|
+
TrayManager.instance = new TrayManager();
|
|
72
|
+
}
|
|
73
|
+
return TrayManager.instance;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private constructor() {
|
|
77
|
+
const userDataPath = app.getPath('userData');
|
|
78
|
+
this.legacySettingsPath = path.join(userDataPath, LEGACY_SETTINGS_FILE);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize the tray manager
|
|
83
|
+
*/
|
|
84
|
+
async initialize(
|
|
85
|
+
mainWindow: BrowserWindow,
|
|
86
|
+
gateway: ChannelGateway,
|
|
87
|
+
dbManager: DatabaseManager,
|
|
88
|
+
agentDaemon?: AgentDaemon,
|
|
89
|
+
options: TrayManagerOptions = {}
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
this.mainWindow = mainWindow;
|
|
92
|
+
this.gateway = gateway;
|
|
93
|
+
this.dbManager = dbManager;
|
|
94
|
+
this.agentDaemon = agentDaemon || null;
|
|
95
|
+
|
|
96
|
+
// Initialize repositories
|
|
97
|
+
const db = dbManager.getDatabase();
|
|
98
|
+
this.taskRepo = new TaskRepository(db);
|
|
99
|
+
this.workspaceRepo = new WorkspaceRepository(db);
|
|
100
|
+
|
|
101
|
+
// Load settings
|
|
102
|
+
this.loadSettings();
|
|
103
|
+
|
|
104
|
+
// Apply options overrides
|
|
105
|
+
if (options.showDockIcon !== undefined) {
|
|
106
|
+
this.settings.showDockIcon = options.showDockIcon;
|
|
107
|
+
}
|
|
108
|
+
if (options.startMinimized !== undefined) {
|
|
109
|
+
this.settings.startMinimized = options.startMinimized;
|
|
110
|
+
}
|
|
111
|
+
if (options.closeToTray !== undefined) {
|
|
112
|
+
this.settings.closeToTray = options.closeToTray;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Create tray if enabled
|
|
116
|
+
if (this.settings.enabled) {
|
|
117
|
+
this.createTray();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Apply dock icon setting (macOS only)
|
|
121
|
+
this.applyDockIconSetting();
|
|
122
|
+
|
|
123
|
+
// Handle start minimized
|
|
124
|
+
if (this.settings.startMinimized && this.mainWindow) {
|
|
125
|
+
this.mainWindow.hide();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Set up window close behavior
|
|
129
|
+
this.setupCloseToTray();
|
|
130
|
+
|
|
131
|
+
// Update status periodically
|
|
132
|
+
this.startStatusUpdates();
|
|
133
|
+
|
|
134
|
+
// Set up task event listening for quick input responses
|
|
135
|
+
this.setupTaskEventListener();
|
|
136
|
+
|
|
137
|
+
// Initialize quick input window
|
|
138
|
+
this.quickInputWindow = new QuickInputWindow();
|
|
139
|
+
this.quickInputWindow.setOnSubmit((task, workspaceId) => {
|
|
140
|
+
this.handleQuickTaskSubmit(task, workspaceId);
|
|
141
|
+
});
|
|
142
|
+
this.quickInputWindow.setOnOpenMain(() => {
|
|
143
|
+
this.showMainWindow();
|
|
144
|
+
this.quickInputWindow?.hide();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Register global shortcut for quick input (Cmd+Shift+Space)
|
|
148
|
+
this.registerGlobalShortcut();
|
|
149
|
+
|
|
150
|
+
console.log('[TrayManager] Initialized');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set up listener for task events to stream to quick input
|
|
155
|
+
*/
|
|
156
|
+
private setupTaskEventListener(): void {
|
|
157
|
+
if (!this.agentDaemon) return;
|
|
158
|
+
|
|
159
|
+
// Listen for assistant messages (the main text response)
|
|
160
|
+
this.agentDaemon.on('assistant_message', (event: { taskId: string; message?: string }) => {
|
|
161
|
+
if (event.taskId !== this.currentQuickTaskId) return;
|
|
162
|
+
const message = event.message || '';
|
|
163
|
+
if (message) {
|
|
164
|
+
// Append to accumulated response (assistant may send multiple messages)
|
|
165
|
+
if (this.quickTaskAccumulatedResponse) {
|
|
166
|
+
this.quickTaskAccumulatedResponse += '\n\n' + message;
|
|
167
|
+
} else {
|
|
168
|
+
this.quickTaskAccumulatedResponse = message;
|
|
169
|
+
}
|
|
170
|
+
this.quickInputWindow?.updateResponse(
|
|
171
|
+
this.formatResponseWithQuestion(this.quickTaskAccumulatedResponse),
|
|
172
|
+
false
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Listen for progress updates
|
|
178
|
+
this.agentDaemon.on('progress_update', (event: { taskId: string; message?: string; progress?: number }) => {
|
|
179
|
+
if (event.taskId !== this.currentQuickTaskId) return;
|
|
180
|
+
// Only show progress if we don't have response content yet
|
|
181
|
+
if (!this.quickTaskAccumulatedResponse && event.message) {
|
|
182
|
+
this.quickInputWindow?.updateResponse(
|
|
183
|
+
`<p style="color: rgba(255,255,255,0.6);">${event.message}</p>`,
|
|
184
|
+
false
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Listen for task completion
|
|
190
|
+
this.agentDaemon.on('task_completed', (event: { taskId: string; message?: string; result?: string }) => {
|
|
191
|
+
if (event.taskId !== this.currentQuickTaskId) return;
|
|
192
|
+
// Show the accumulated response as complete (without step prefix)
|
|
193
|
+
const finalContent = this.quickTaskAccumulatedResponse || event.result || event.message || 'Task completed successfully';
|
|
194
|
+
this.quickInputWindow?.updateResponse(
|
|
195
|
+
this.formatResponseWithQuestion(finalContent),
|
|
196
|
+
true
|
|
197
|
+
);
|
|
198
|
+
this.currentQuickTaskId = null;
|
|
199
|
+
this.quickTaskAccumulatedResponse = '';
|
|
200
|
+
this.currentStepInfo = '';
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Listen for errors
|
|
204
|
+
this.agentDaemon.on('error', (event: { taskId: string; message?: string }) => {
|
|
205
|
+
if (event.taskId !== this.currentQuickTaskId) return;
|
|
206
|
+
const question = this.quickInputWindow?.getCurrentQuestion() || '';
|
|
207
|
+
const questionHtml = question ? `<div class="user-question"><strong>You:</strong> ${question.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</div>` : '';
|
|
208
|
+
this.quickInputWindow?.updateResponse(
|
|
209
|
+
`${questionHtml}<div class="error-message">Error: ${event.message || 'An error occurred'}</div>`,
|
|
210
|
+
true
|
|
211
|
+
);
|
|
212
|
+
this.currentQuickTaskId = null;
|
|
213
|
+
this.quickTaskAccumulatedResponse = '';
|
|
214
|
+
this.currentStepInfo = '';
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Listen for step started (show what step is being executed)
|
|
218
|
+
this.agentDaemon.on('step_started', (event: { taskId: string; step?: { id: number; description: string } }) => {
|
|
219
|
+
if (event.taskId !== this.currentQuickTaskId) return;
|
|
220
|
+
// Show step info above the response
|
|
221
|
+
if (event.step?.description) {
|
|
222
|
+
const stepInfo = `**Step ${event.step.id}:** ${event.step.description}\n\n`;
|
|
223
|
+
// Prepend step info (it will be replaced by next step)
|
|
224
|
+
this.currentStepInfo = stepInfo;
|
|
225
|
+
this.quickInputWindow?.updateResponse(
|
|
226
|
+
this.formatResponseWithQuestion(this.currentStepInfo + this.quickTaskAccumulatedResponse),
|
|
227
|
+
false
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Listen for plan created (show what the agent is going to do)
|
|
233
|
+
this.agentDaemon.on('plan_created', (event: { taskId: string; plan?: { steps: Array<{ id: number; description: string }> } }) => {
|
|
234
|
+
if (event.taskId !== this.currentQuickTaskId) return;
|
|
235
|
+
if (event.plan?.steps && event.plan.steps.length > 0) {
|
|
236
|
+
const planSummary = event.plan.steps.map((s, i) => `${i + 1}. ${s.description}`).join('\n');
|
|
237
|
+
this.quickTaskAccumulatedResponse = `**Plan:**\n${planSummary}\n\n`;
|
|
238
|
+
this.quickInputWindow?.updateResponse(
|
|
239
|
+
this.formatResponseWithQuestion(this.quickTaskAccumulatedResponse),
|
|
240
|
+
false
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Format response text for HTML display
|
|
248
|
+
*/
|
|
249
|
+
private formatResponseForDisplay(text: string): string {
|
|
250
|
+
// Basic markdown-like formatting
|
|
251
|
+
return text
|
|
252
|
+
// Escape HTML
|
|
253
|
+
.replace(/&/g, '&')
|
|
254
|
+
.replace(/</g, '<')
|
|
255
|
+
.replace(/>/g, '>')
|
|
256
|
+
// Bold
|
|
257
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
258
|
+
// Code blocks
|
|
259
|
+
.replace(/```(\w*)\n?([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
|
|
260
|
+
// Inline code
|
|
261
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
262
|
+
// Line breaks
|
|
263
|
+
.replace(/\n/g, '<br>');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Format response with user's question prepended
|
|
268
|
+
*/
|
|
269
|
+
private formatResponseWithQuestion(text: string): string {
|
|
270
|
+
const question = this.quickInputWindow?.getCurrentQuestion() || '';
|
|
271
|
+
const formattedResponse = this.formatResponseForDisplay(text);
|
|
272
|
+
|
|
273
|
+
if (question) {
|
|
274
|
+
const escapedQuestion = question
|
|
275
|
+
.replace(/&/g, '&')
|
|
276
|
+
.replace(/</g, '<')
|
|
277
|
+
.replace(/>/g, '>');
|
|
278
|
+
return `<div class="user-question"><strong>You:</strong> ${escapedQuestion}</div>${formattedResponse}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return formattedResponse;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get or create the temp workspace
|
|
286
|
+
*/
|
|
287
|
+
private async getOrCreateTempWorkspace(): Promise<Workspace> {
|
|
288
|
+
if (!this.dbManager) throw new Error('Database not available');
|
|
289
|
+
|
|
290
|
+
const db = this.dbManager.getDatabase();
|
|
291
|
+
|
|
292
|
+
// Check if temp workspace exists
|
|
293
|
+
const existing = this.workspaceRepo?.findById(TEMP_WORKSPACE_ID);
|
|
294
|
+
if (existing) {
|
|
295
|
+
const updatedPermissions = {
|
|
296
|
+
...existing.permissions,
|
|
297
|
+
read: true,
|
|
298
|
+
write: true,
|
|
299
|
+
delete: true,
|
|
300
|
+
network: true,
|
|
301
|
+
shell: existing.permissions.shell ?? false,
|
|
302
|
+
unrestrictedFileAccess: true,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (!existing.permissions.unrestrictedFileAccess) {
|
|
306
|
+
this.workspaceRepo?.updatePermissions(existing.id, updatedPermissions);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Verify directory exists
|
|
310
|
+
if (fs.existsSync(existing.path)) {
|
|
311
|
+
return { ...existing, permissions: updatedPermissions, isTemp: true };
|
|
312
|
+
}
|
|
313
|
+
// Directory deleted, remove and recreate
|
|
314
|
+
this.workspaceRepo?.delete(TEMP_WORKSPACE_ID);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Create temp directory
|
|
318
|
+
const tempDir = path.join(os.tmpdir(), 'cowork-os-temp');
|
|
319
|
+
if (!fs.existsSync(tempDir)) {
|
|
320
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Create workspace record
|
|
324
|
+
const tempWorkspace: Workspace = {
|
|
325
|
+
id: TEMP_WORKSPACE_ID,
|
|
326
|
+
name: TEMP_WORKSPACE_NAME,
|
|
327
|
+
path: tempDir,
|
|
328
|
+
createdAt: Date.now(),
|
|
329
|
+
permissions: {
|
|
330
|
+
read: true,
|
|
331
|
+
write: true,
|
|
332
|
+
delete: true,
|
|
333
|
+
network: true,
|
|
334
|
+
shell: false,
|
|
335
|
+
unrestrictedFileAccess: true,
|
|
336
|
+
},
|
|
337
|
+
isTemp: true,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const stmt = db.prepare(`
|
|
341
|
+
INSERT OR REPLACE INTO workspaces (id, name, path, created_at, permissions)
|
|
342
|
+
VALUES (?, ?, ?, ?, ?)
|
|
343
|
+
`);
|
|
344
|
+
stmt.run(
|
|
345
|
+
tempWorkspace.id,
|
|
346
|
+
tempWorkspace.name,
|
|
347
|
+
tempWorkspace.path,
|
|
348
|
+
tempWorkspace.createdAt,
|
|
349
|
+
JSON.stringify(tempWorkspace.permissions)
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
return tempWorkspace;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Handle quick task submission - create and run task
|
|
357
|
+
*/
|
|
358
|
+
private async handleQuickTaskSubmit(prompt: string, workspaceId?: string): Promise<void> {
|
|
359
|
+
if (!this.taskRepo || !this.workspaceRepo || !this.agentDaemon) {
|
|
360
|
+
// Fall back to sending to main window
|
|
361
|
+
console.log('[TrayManager] Agent daemon not available, falling back to main window');
|
|
362
|
+
this.showMainWindow();
|
|
363
|
+
this.mainWindow?.webContents.send('tray:quick-task', { task: prompt, workspaceId });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Show loading state and reset accumulated response
|
|
368
|
+
this.quickInputWindow?.showLoading();
|
|
369
|
+
this.quickTaskAccumulatedResponse = '';
|
|
370
|
+
this.currentStepInfo = '';
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
// Get or select workspace
|
|
374
|
+
let wsId = workspaceId;
|
|
375
|
+
if (!wsId) {
|
|
376
|
+
// Get the first non-temp workspace, or use temp workspace as fallback
|
|
377
|
+
const workspaces = this.workspaceRepo.findAll().filter(w => w.id !== TEMP_WORKSPACE_ID);
|
|
378
|
+
if (workspaces.length > 0) {
|
|
379
|
+
wsId = workspaces[0].id;
|
|
380
|
+
} else {
|
|
381
|
+
// No user workspaces, use temp workspace
|
|
382
|
+
const tempWorkspace = await this.getOrCreateTempWorkspace();
|
|
383
|
+
wsId = tempWorkspace.id;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Create task
|
|
388
|
+
const task = this.taskRepo.create({
|
|
389
|
+
title: prompt.slice(0, 50) + (prompt.length > 50 ? '...' : ''),
|
|
390
|
+
prompt,
|
|
391
|
+
workspaceId: wsId,
|
|
392
|
+
status: 'queued',
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
this.currentQuickTaskId = task.id;
|
|
396
|
+
|
|
397
|
+
// Start task execution
|
|
398
|
+
await this.agentDaemon.startTask(task);
|
|
399
|
+
|
|
400
|
+
// Also notify main window so it updates the task list
|
|
401
|
+
this.mainWindow?.webContents.send('tray:task-created', { taskId: task.id });
|
|
402
|
+
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error('[TrayManager] Failed to create quick task:', error);
|
|
405
|
+
const question = this.quickInputWindow?.getCurrentQuestion() || '';
|
|
406
|
+
const questionHtml = question ? `<div class="user-question"><strong>You:</strong> ${question.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</div>` : '';
|
|
407
|
+
this.quickInputWindow?.updateResponse(
|
|
408
|
+
`${questionHtml}<div class="error-message">Failed to create task: ${error instanceof Error ? error.message : 'Unknown error'}</div>`,
|
|
409
|
+
true
|
|
410
|
+
);
|
|
411
|
+
this.currentQuickTaskId = null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Show the quick input window
|
|
417
|
+
*/
|
|
418
|
+
showQuickInput(): void {
|
|
419
|
+
this.quickInputWindow?.show();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Toggle the quick input window
|
|
424
|
+
*/
|
|
425
|
+
toggleQuickInput(): void {
|
|
426
|
+
this.quickInputWindow?.toggle();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Register global keyboard shortcut for quick input
|
|
431
|
+
*/
|
|
432
|
+
private registerGlobalShortcut(): void {
|
|
433
|
+
try {
|
|
434
|
+
// Unregister first in case it's already registered
|
|
435
|
+
globalShortcut.unregister('CommandOrControl+Shift+Space');
|
|
436
|
+
|
|
437
|
+
const registered = globalShortcut.register('CommandOrControl+Shift+Space', () => {
|
|
438
|
+
this.showQuickInput();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
if (registered) {
|
|
442
|
+
console.log('[TrayManager] Global shortcut registered: Cmd+Shift+Space');
|
|
443
|
+
} else {
|
|
444
|
+
console.warn('[TrayManager] Failed to register global shortcut - may be in use by another app');
|
|
445
|
+
}
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error('[TrayManager] Error registering global shortcut:', error);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Unregister global keyboard shortcut
|
|
453
|
+
*/
|
|
454
|
+
private unregisterGlobalShortcut(): void {
|
|
455
|
+
try {
|
|
456
|
+
globalShortcut.unregister('CommandOrControl+Shift+Space');
|
|
457
|
+
console.log('[TrayManager] Global shortcut unregistered');
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.error('[TrayManager] Error unregistering global shortcut:', error);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Create the system tray icon
|
|
465
|
+
*/
|
|
466
|
+
private createTray(): void {
|
|
467
|
+
if (this.tray) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
// Create tray icon (use template image for macOS)
|
|
473
|
+
const icon = this.getTrayIcon('idle');
|
|
474
|
+
|
|
475
|
+
this.tray = new Tray(icon);
|
|
476
|
+
this.tray.setToolTip('CoWork OS');
|
|
477
|
+
|
|
478
|
+
// Build and set context menu
|
|
479
|
+
this.updateContextMenu();
|
|
480
|
+
|
|
481
|
+
// Handle click events - always show context menu on click
|
|
482
|
+
this.tray.on('click', () => {
|
|
483
|
+
this.tray?.popUpContextMenu();
|
|
484
|
+
});
|
|
485
|
+
} catch (error) {
|
|
486
|
+
console.error('[TrayManager] Failed to create tray:', error);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Get or create tray icon
|
|
492
|
+
*/
|
|
493
|
+
private getTrayIcon(state: 'idle' | 'active' | 'error'): NativeImage {
|
|
494
|
+
// Try to load from file first
|
|
495
|
+
const iconPath = this.getIconPath(state === 'active' ? 'trayActiveTemplate' : 'trayTemplate');
|
|
496
|
+
const fs = require('fs');
|
|
497
|
+
|
|
498
|
+
if (fs.existsSync(iconPath)) {
|
|
499
|
+
const icon = nativeImage.createFromPath(iconPath);
|
|
500
|
+
if (process.platform === 'darwin') {
|
|
501
|
+
icon.setTemplateImage(true);
|
|
502
|
+
}
|
|
503
|
+
return icon;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Create programmatic icon if file doesn't exist
|
|
507
|
+
return this.createProgrammaticIcon(state);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Create a programmatic tray icon using raw RGBA bitmap
|
|
512
|
+
* More reliable than SVG data URLs for Electron tray icons
|
|
513
|
+
*/
|
|
514
|
+
private createProgrammaticIcon(state: 'idle' | 'active' | 'error'): NativeImage {
|
|
515
|
+
// Standard macOS menu bar icon size (16x16 for 1x, 32x32 for 2x retina)
|
|
516
|
+
const size = 16;
|
|
517
|
+
const scale = 2; // Create at 2x for retina
|
|
518
|
+
const actualSize = size * scale;
|
|
519
|
+
|
|
520
|
+
// Create RGBA buffer (4 bytes per pixel)
|
|
521
|
+
const buffer = Buffer.alloc(actualSize * actualSize * 4);
|
|
522
|
+
|
|
523
|
+
// Get color based on state
|
|
524
|
+
const [r, g, b] = state === 'error' ? [255, 59, 48] : // Red
|
|
525
|
+
state === 'active' ? [0, 122, 255] : // Blue
|
|
526
|
+
[255, 255, 255]; // White
|
|
527
|
+
|
|
528
|
+
// Draw a simple filled circle
|
|
529
|
+
const centerX = actualSize / 2;
|
|
530
|
+
const centerY = actualSize / 2;
|
|
531
|
+
const outerRadius = actualSize / 2 - 2;
|
|
532
|
+
const innerRadius = outerRadius - 4;
|
|
533
|
+
|
|
534
|
+
for (let y = 0; y < actualSize; y++) {
|
|
535
|
+
for (let x = 0; x < actualSize; x++) {
|
|
536
|
+
const dx = x - centerX;
|
|
537
|
+
const dy = y - centerY;
|
|
538
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
539
|
+
|
|
540
|
+
const idx = (y * actualSize + x) * 4;
|
|
541
|
+
|
|
542
|
+
// Draw ring (between inner and outer radius)
|
|
543
|
+
if (distance <= outerRadius && distance >= innerRadius) {
|
|
544
|
+
// Anti-aliasing at edges
|
|
545
|
+
let alpha = 255;
|
|
546
|
+
if (distance > outerRadius - 1) {
|
|
547
|
+
alpha = Math.round(255 * (outerRadius - distance));
|
|
548
|
+
} else if (distance < innerRadius + 1) {
|
|
549
|
+
alpha = Math.round(255 * (distance - innerRadius));
|
|
550
|
+
}
|
|
551
|
+
alpha = Math.max(0, Math.min(255, alpha));
|
|
552
|
+
|
|
553
|
+
buffer[idx] = r;
|
|
554
|
+
buffer[idx + 1] = g;
|
|
555
|
+
buffer[idx + 2] = b;
|
|
556
|
+
buffer[idx + 3] = alpha;
|
|
557
|
+
} else {
|
|
558
|
+
// Transparent
|
|
559
|
+
buffer[idx] = 0;
|
|
560
|
+
buffer[idx + 1] = 0;
|
|
561
|
+
buffer[idx + 2] = 0;
|
|
562
|
+
buffer[idx + 3] = 0;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return nativeImage.createFromBuffer(buffer, {
|
|
568
|
+
width: actualSize,
|
|
569
|
+
height: actualSize,
|
|
570
|
+
scaleFactor: scale,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Get the path to a tray icon
|
|
576
|
+
*/
|
|
577
|
+
private getIconPath(name: string): string {
|
|
578
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
579
|
+
const basePath = isDev
|
|
580
|
+
? path.join(__dirname, '../../../assets/tray')
|
|
581
|
+
: path.join(process.resourcesPath, 'assets/tray');
|
|
582
|
+
|
|
583
|
+
// Use PNG for cross-platform compatibility
|
|
584
|
+
const extension = process.platform === 'darwin' ? 'png' : 'png';
|
|
585
|
+
return path.join(basePath, `${name}.${extension}`);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Update the tray context menu
|
|
590
|
+
*/
|
|
591
|
+
private updateContextMenu(): void {
|
|
592
|
+
if (!this.tray) return;
|
|
593
|
+
|
|
594
|
+
const statusText = this.getStatusText();
|
|
595
|
+
const workspaces = this.getWorkspaces();
|
|
596
|
+
|
|
597
|
+
const menuTemplate: Electron.MenuItemConstructorOptions[] = [
|
|
598
|
+
// Status section
|
|
599
|
+
{
|
|
600
|
+
label: statusText,
|
|
601
|
+
enabled: false,
|
|
602
|
+
icon: this.getStatusIcon(),
|
|
603
|
+
},
|
|
604
|
+
{ type: 'separator' },
|
|
605
|
+
|
|
606
|
+
// Quick actions
|
|
607
|
+
{
|
|
608
|
+
label: 'Quick Task...',
|
|
609
|
+
accelerator: 'CmdOrCtrl+Shift+Space',
|
|
610
|
+
click: () => {
|
|
611
|
+
this.showQuickInput();
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
label: 'New Task...',
|
|
616
|
+
accelerator: 'CmdOrCtrl+N',
|
|
617
|
+
click: () => {
|
|
618
|
+
this.showMainWindow();
|
|
619
|
+
this.mainWindow?.webContents.send('tray:new-task');
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
{ type: 'separator' },
|
|
623
|
+
|
|
624
|
+
// Workspaces submenu
|
|
625
|
+
{
|
|
626
|
+
label: 'Workspaces',
|
|
627
|
+
submenu: workspaces.length > 0
|
|
628
|
+
? workspaces.map((ws) => ({
|
|
629
|
+
label: ws.name,
|
|
630
|
+
click: () => {
|
|
631
|
+
this.showMainWindow();
|
|
632
|
+
this.mainWindow?.webContents.send('tray:select-workspace', ws.id);
|
|
633
|
+
},
|
|
634
|
+
}))
|
|
635
|
+
: [{ label: 'No workspaces', enabled: false }],
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
// Channels submenu
|
|
639
|
+
{
|
|
640
|
+
label: 'Channels',
|
|
641
|
+
submenu: this.buildChannelsSubmenu(),
|
|
642
|
+
},
|
|
643
|
+
{ type: 'separator' },
|
|
644
|
+
|
|
645
|
+
// Window controls
|
|
646
|
+
{
|
|
647
|
+
label: this.mainWindow?.isVisible() ? 'Hide Window' : 'Show Window',
|
|
648
|
+
accelerator: 'CmdOrCtrl+H',
|
|
649
|
+
click: () => this.toggleMainWindow(),
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
label: 'Settings...',
|
|
653
|
+
accelerator: 'CmdOrCtrl+,',
|
|
654
|
+
click: () => {
|
|
655
|
+
this.showMainWindow();
|
|
656
|
+
this.mainWindow?.webContents.send('tray:open-settings');
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
{ type: 'separator' },
|
|
660
|
+
|
|
661
|
+
// App controls
|
|
662
|
+
{
|
|
663
|
+
label: 'About CoWork OS',
|
|
664
|
+
click: () => {
|
|
665
|
+
this.showMainWindow();
|
|
666
|
+
this.mainWindow?.webContents.send('tray:open-about');
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
label: 'Check for Updates...',
|
|
671
|
+
click: () => {
|
|
672
|
+
this.showMainWindow();
|
|
673
|
+
this.mainWindow?.webContents.send('tray:check-updates');
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
{ type: 'separator' },
|
|
677
|
+
{
|
|
678
|
+
label: 'Quit CoWork OS',
|
|
679
|
+
accelerator: 'CmdOrCtrl+Q',
|
|
680
|
+
click: () => {
|
|
681
|
+
// Force quit (bypass close-to-tray)
|
|
682
|
+
this.settings.closeToTray = false;
|
|
683
|
+
app.quit();
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
];
|
|
687
|
+
|
|
688
|
+
const contextMenu = Menu.buildFromTemplate(menuTemplate);
|
|
689
|
+
this.tray.setContextMenu(contextMenu);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Build the channels submenu
|
|
694
|
+
*/
|
|
695
|
+
private buildChannelsSubmenu(): Electron.MenuItemConstructorOptions[] {
|
|
696
|
+
const channels = this.gateway?.getChannels() || [];
|
|
697
|
+
|
|
698
|
+
if (channels.length === 0) {
|
|
699
|
+
return [{ label: 'No channels configured', enabled: false }];
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return channels.map((channel) => {
|
|
703
|
+
const statusIcon = channel.status === 'connected' ? '🟢' :
|
|
704
|
+
channel.status === 'connecting' ? '🟡' :
|
|
705
|
+
channel.status === 'error' ? '🔴' : '⚪';
|
|
706
|
+
return {
|
|
707
|
+
label: `${statusIcon} ${channel.name} (${channel.type})`,
|
|
708
|
+
enabled: false,
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Get status text for the menu
|
|
715
|
+
*/
|
|
716
|
+
private getStatusText(): string {
|
|
717
|
+
const channels = this.gateway?.getChannels() || [];
|
|
718
|
+
this.connectedChannels = channels.filter((c) => c.status === 'connected').length;
|
|
719
|
+
|
|
720
|
+
if (this.activeTaskCount > 0) {
|
|
721
|
+
return `Working on ${this.activeTaskCount} task${this.activeTaskCount > 1 ? 's' : ''}`;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (this.connectedChannels > 0) {
|
|
725
|
+
return `${this.connectedChannels} channel${this.connectedChannels > 1 ? 's' : ''} connected`;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return 'Ready';
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Get status icon for the menu
|
|
733
|
+
*/
|
|
734
|
+
private getStatusIcon(): NativeImage | undefined {
|
|
735
|
+
// Return undefined for now - icons in menu items can be complex
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Get workspaces from database (excluding temp workspace)
|
|
741
|
+
*/
|
|
742
|
+
private getWorkspaces(): Array<{ id: string; name: string; path: string }> {
|
|
743
|
+
if (!this.dbManager) return [];
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
const db = this.dbManager.getDatabase();
|
|
747
|
+
const stmt = db.prepare('SELECT id, name, path FROM workspaces WHERE id != ? ORDER BY name');
|
|
748
|
+
return stmt.all(TEMP_WORKSPACE_ID) as Array<{ id: string; name: string; path: string }>;
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error('[TrayManager] Failed to get workspaces:', error);
|
|
751
|
+
return [];
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Toggle main window visibility
|
|
757
|
+
*/
|
|
758
|
+
private toggleMainWindow(): void {
|
|
759
|
+
if (!this.mainWindow) return;
|
|
760
|
+
|
|
761
|
+
if (this.mainWindow.isVisible()) {
|
|
762
|
+
this.mainWindow.hide();
|
|
763
|
+
} else {
|
|
764
|
+
this.showMainWindow();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Update menu to reflect new state
|
|
768
|
+
this.updateContextMenu();
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Show and focus the main window
|
|
773
|
+
*/
|
|
774
|
+
private showMainWindow(): void {
|
|
775
|
+
if (!this.mainWindow) return;
|
|
776
|
+
|
|
777
|
+
this.mainWindow.show();
|
|
778
|
+
this.mainWindow.focus();
|
|
779
|
+
|
|
780
|
+
// On macOS, also bring app to foreground
|
|
781
|
+
if (process.platform === 'darwin') {
|
|
782
|
+
app.dock?.show();
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Set up close-to-tray behavior
|
|
788
|
+
*/
|
|
789
|
+
private setupCloseToTray(): void {
|
|
790
|
+
if (!this.mainWindow) return;
|
|
791
|
+
|
|
792
|
+
this.mainWindow.on('close', (event) => {
|
|
793
|
+
if (this.settings.closeToTray && this.tray) {
|
|
794
|
+
event.preventDefault();
|
|
795
|
+
this.mainWindow?.hide();
|
|
796
|
+
|
|
797
|
+
// On macOS, hide from dock when minimized to tray
|
|
798
|
+
if (process.platform === 'darwin' && !this.settings.showDockIcon) {
|
|
799
|
+
app.dock?.hide();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Apply dock icon visibility setting (macOS only)
|
|
807
|
+
*/
|
|
808
|
+
private applyDockIconSetting(): void {
|
|
809
|
+
if (process.platform !== 'darwin') return;
|
|
810
|
+
|
|
811
|
+
if (this.settings.showDockIcon) {
|
|
812
|
+
app.dock?.show();
|
|
813
|
+
} else {
|
|
814
|
+
app.dock?.hide();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Start periodic status updates
|
|
820
|
+
*/
|
|
821
|
+
private startStatusUpdates(): void {
|
|
822
|
+
// Update every 5 seconds
|
|
823
|
+
setInterval(() => {
|
|
824
|
+
this.updateContextMenu();
|
|
825
|
+
this.updateTrayIcon();
|
|
826
|
+
}, 5000);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Update tray icon based on status
|
|
831
|
+
*/
|
|
832
|
+
private updateTrayIcon(): void {
|
|
833
|
+
if (!this.tray) return;
|
|
834
|
+
|
|
835
|
+
// Determine icon state based on app status
|
|
836
|
+
const state: 'idle' | 'active' | 'error' = this.activeTaskCount > 0 ? 'active' : 'idle';
|
|
837
|
+
const icon = this.getTrayIcon(state);
|
|
838
|
+
this.tray.setImage(icon);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Update active task count
|
|
843
|
+
*/
|
|
844
|
+
setActiveTaskCount(count: number): void {
|
|
845
|
+
this.activeTaskCount = count;
|
|
846
|
+
this.updateContextMenu();
|
|
847
|
+
this.updateTrayIcon();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Migrate settings from legacy JSON file to encrypted database
|
|
852
|
+
*/
|
|
853
|
+
private migrateFromLegacyFile(): void {
|
|
854
|
+
if (TrayManager.migrationCompleted) return;
|
|
855
|
+
|
|
856
|
+
try {
|
|
857
|
+
if (!SecureSettingsRepository.isInitialized()) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const repository = SecureSettingsRepository.getInstance();
|
|
862
|
+
|
|
863
|
+
// Check if already migrated
|
|
864
|
+
if (repository.exists('tray')) {
|
|
865
|
+
TrayManager.migrationCompleted = true;
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Check if legacy file exists
|
|
870
|
+
if (!fs.existsSync(this.legacySettingsPath)) {
|
|
871
|
+
TrayManager.migrationCompleted = true;
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
console.log('[TrayManager] Migrating settings from legacy JSON file to encrypted database...');
|
|
876
|
+
|
|
877
|
+
// Create backup before migration
|
|
878
|
+
const backupPath = this.legacySettingsPath + '.migration-backup';
|
|
879
|
+
fs.copyFileSync(this.legacySettingsPath, backupPath);
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
const data = fs.readFileSync(this.legacySettingsPath, 'utf-8');
|
|
883
|
+
const parsed = JSON.parse(data);
|
|
884
|
+
const merged = { ...DEFAULT_SETTINGS, ...parsed };
|
|
885
|
+
|
|
886
|
+
repository.save('tray', merged);
|
|
887
|
+
console.log('[TrayManager] Settings migrated to encrypted database');
|
|
888
|
+
|
|
889
|
+
// Migration successful - delete backup and original
|
|
890
|
+
fs.unlinkSync(backupPath);
|
|
891
|
+
fs.unlinkSync(this.legacySettingsPath);
|
|
892
|
+
console.log('[TrayManager] Migration complete, cleaned up legacy files');
|
|
893
|
+
|
|
894
|
+
TrayManager.migrationCompleted = true;
|
|
895
|
+
} catch (migrationError) {
|
|
896
|
+
console.error('[TrayManager] Migration failed, backup preserved at:', backupPath);
|
|
897
|
+
throw migrationError;
|
|
898
|
+
}
|
|
899
|
+
} catch (error) {
|
|
900
|
+
console.error('[TrayManager] Migration failed:', error);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Load settings from encrypted database
|
|
906
|
+
*/
|
|
907
|
+
private loadSettings(): void {
|
|
908
|
+
// Migrate from legacy file if needed
|
|
909
|
+
this.migrateFromLegacyFile();
|
|
910
|
+
|
|
911
|
+
try {
|
|
912
|
+
if (SecureSettingsRepository.isInitialized()) {
|
|
913
|
+
const repository = SecureSettingsRepository.getInstance();
|
|
914
|
+
const stored = repository.load<TraySettings>('tray');
|
|
915
|
+
if (stored) {
|
|
916
|
+
this.settings = { ...DEFAULT_SETTINGS, ...stored };
|
|
917
|
+
console.log('[TrayManager] Loaded settings from encrypted database');
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
} catch (error) {
|
|
922
|
+
console.error('[TrayManager] Failed to load settings:', error);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Fall back to defaults
|
|
926
|
+
this.settings = { ...DEFAULT_SETTINGS };
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Save settings to encrypted database
|
|
931
|
+
*/
|
|
932
|
+
saveSettings(settings: Partial<TraySettings>): void {
|
|
933
|
+
this.settings = { ...this.settings, ...settings };
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
if (SecureSettingsRepository.isInitialized()) {
|
|
937
|
+
const repository = SecureSettingsRepository.getInstance();
|
|
938
|
+
repository.save('tray', this.settings);
|
|
939
|
+
console.log('[TrayManager] Settings saved to encrypted database');
|
|
940
|
+
} else {
|
|
941
|
+
console.warn('[TrayManager] SecureSettingsRepository not initialized, settings not persisted');
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Apply settings immediately
|
|
945
|
+
this.applyDockIconSetting();
|
|
946
|
+
|
|
947
|
+
// Recreate tray if enabled status changed
|
|
948
|
+
if (settings.enabled !== undefined) {
|
|
949
|
+
if (settings.enabled && !this.tray) {
|
|
950
|
+
this.createTray();
|
|
951
|
+
} else if (!settings.enabled && this.tray) {
|
|
952
|
+
this.destroy();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
} catch (error) {
|
|
956
|
+
console.error('[TrayManager] Failed to save settings:', error);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Get current settings
|
|
962
|
+
*/
|
|
963
|
+
getSettings(): TraySettings {
|
|
964
|
+
return { ...this.settings };
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Show a notification from the tray
|
|
969
|
+
*/
|
|
970
|
+
showNotification(title: string, body: string): void {
|
|
971
|
+
if (!this.settings.showNotifications) return;
|
|
972
|
+
|
|
973
|
+
const { Notification } = require('electron');
|
|
974
|
+
if (Notification.isSupported()) {
|
|
975
|
+
const notification = new Notification({
|
|
976
|
+
title,
|
|
977
|
+
body,
|
|
978
|
+
silent: false,
|
|
979
|
+
});
|
|
980
|
+
notification.on('click', () => {
|
|
981
|
+
this.showMainWindow();
|
|
982
|
+
});
|
|
983
|
+
notification.show();
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Destroy the tray
|
|
989
|
+
*/
|
|
990
|
+
destroy(): void {
|
|
991
|
+
// Unregister global shortcut
|
|
992
|
+
this.unregisterGlobalShortcut();
|
|
993
|
+
|
|
994
|
+
if (this.quickInputWindow) {
|
|
995
|
+
this.quickInputWindow.destroy();
|
|
996
|
+
this.quickInputWindow = null;
|
|
997
|
+
}
|
|
998
|
+
if (this.tray) {
|
|
999
|
+
this.tray.destroy();
|
|
1000
|
+
this.tray = null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export const trayManager = TrayManager.getInstance();
|