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,3138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getNotificationService = getNotificationService;
|
|
40
|
+
exports.setupIpcHandlers = setupIpcHandlers;
|
|
41
|
+
exports.getHooksServer = getHooksServer;
|
|
42
|
+
const electron_1 = require("electron");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const fs = __importStar(require("fs/promises"));
|
|
45
|
+
const fsSync = __importStar(require("fs"));
|
|
46
|
+
const mammoth_1 = __importDefault(require("mammoth"));
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
|
+
const pdfParseModule = require('pdf-parse');
|
|
49
|
+
// Handle both ESM default export and CommonJS module.exports
|
|
50
|
+
const pdfParse = (typeof pdfParseModule === 'function' ? pdfParseModule : pdfParseModule.default);
|
|
51
|
+
const schema_1 = require("../database/schema");
|
|
52
|
+
const repositories_1 = require("../database/repositories");
|
|
53
|
+
const AgentRoleRepository_1 = require("../agents/AgentRoleRepository");
|
|
54
|
+
const ActivityRepository_1 = require("../activity/ActivityRepository");
|
|
55
|
+
const MentionRepository_1 = require("../agents/MentionRepository");
|
|
56
|
+
const TaskLabelRepository_1 = require("../database/TaskLabelRepository");
|
|
57
|
+
const WorkingStateRepository_1 = require("../agents/WorkingStateRepository");
|
|
58
|
+
const context_policy_1 = require("../gateway/context-policy");
|
|
59
|
+
const types_1 = require("../../shared/types");
|
|
60
|
+
const os = __importStar(require("os"));
|
|
61
|
+
const llm_1 = require("../agent/llm");
|
|
62
|
+
const search_1 = require("../agent/search");
|
|
63
|
+
const updater_1 = require("../updater");
|
|
64
|
+
const rate_limiter_1 = require("../utils/rate-limiter");
|
|
65
|
+
const validation_1 = require("../utils/validation");
|
|
66
|
+
const guardrail_manager_1 = require("../guardrails/guardrail-manager");
|
|
67
|
+
const appearance_manager_1 = require("../settings/appearance-manager");
|
|
68
|
+
const personality_manager_1 = require("../settings/personality-manager");
|
|
69
|
+
const normalizeMentionToken = (value) => value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
70
|
+
const buildAgentMentionIndex = (roles) => {
|
|
71
|
+
const index = new Map();
|
|
72
|
+
roles.forEach((role) => {
|
|
73
|
+
const baseTokens = [
|
|
74
|
+
role.name,
|
|
75
|
+
role.displayName,
|
|
76
|
+
role.name.replace(/[_-]+/g, ''),
|
|
77
|
+
role.displayName.replace(/\s+/g, ''),
|
|
78
|
+
role.displayName.replace(/\s+/g, '_'),
|
|
79
|
+
role.displayName.replace(/\s+/g, '-'),
|
|
80
|
+
];
|
|
81
|
+
baseTokens.forEach((token) => {
|
|
82
|
+
const normalized = normalizeMentionToken(token);
|
|
83
|
+
if (normalized) {
|
|
84
|
+
index.set(normalized, role);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
return index;
|
|
89
|
+
};
|
|
90
|
+
const CAPABILITY_KEYWORDS = {
|
|
91
|
+
code: ['code', 'implement', 'build', 'develop', 'feature', 'api', 'backend', 'frontend', 'refactor', 'bug', 'fix'],
|
|
92
|
+
review: ['review', 'audit', 'best practices', 'quality', 'lint'],
|
|
93
|
+
test: ['test', 'testing', 'qa', 'unit', 'integration', 'e2e', 'regression', 'coverage'],
|
|
94
|
+
design: ['design', 'ui', 'ux', 'wireframe', 'mockup', 'figma', 'layout', 'visual', 'brand'],
|
|
95
|
+
ops: ['deploy', 'ci', 'cd', 'devops', 'infra', 'infrastructure', 'docker', 'kubernetes', 'pipeline', 'monitor'],
|
|
96
|
+
security: ['security', 'vulnerability', 'threat', 'audit', 'compliance', 'encryption'],
|
|
97
|
+
research: ['research', 'investigate', 'compare', 'comparison', 'competitive', 'competitor', 'benchmark', 'study'],
|
|
98
|
+
analyze: ['analyze', 'analysis', 'data', 'metrics', 'insights', 'report', 'trend', 'dashboard'],
|
|
99
|
+
plan: ['plan', 'strategy', 'roadmap', 'architecture', 'outline', 'spec'],
|
|
100
|
+
document: ['document', 'documentation', 'docs', 'guide', 'manual', 'readme', 'spec'],
|
|
101
|
+
write: ['write', 'draft', 'copy', 'blog', 'post', 'article', 'content', 'summary'],
|
|
102
|
+
communicate: ['email', 'support', 'customer', 'communication', 'outreach', 'reply', 'respond'],
|
|
103
|
+
market: ['marketing', 'growth', 'campaign', 'social', 'seo', 'launch', 'newsletter', 'ads'],
|
|
104
|
+
manage: ['manage', 'project', 'timeline', 'milestone', 'coordination', 'sprint', 'backlog'],
|
|
105
|
+
product: ['product', 'feature', 'user story', 'requirements', 'prioritize', 'mvp'],
|
|
106
|
+
};
|
|
107
|
+
const scoreAgentForTask = (role, text) => {
|
|
108
|
+
const lowerText = text.toLowerCase();
|
|
109
|
+
let score = 0;
|
|
110
|
+
const roleText = `${role.name} ${role.displayName} ${role.description ?? ''}`.toLowerCase();
|
|
111
|
+
const tokens = roleText.split(/[^a-z0-9]+/).filter((token) => token.length > 2);
|
|
112
|
+
tokens.forEach((token) => {
|
|
113
|
+
if (lowerText.includes(token)) {
|
|
114
|
+
score += 1;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
if (role.capabilities) {
|
|
118
|
+
role.capabilities.forEach((capability) => {
|
|
119
|
+
const keywords = CAPABILITY_KEYWORDS[capability];
|
|
120
|
+
if (keywords && keywords.some((keyword) => lowerText.includes(keyword))) {
|
|
121
|
+
score += 3;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return score;
|
|
126
|
+
};
|
|
127
|
+
const selectBestAgentsForTask = (text, roles) => {
|
|
128
|
+
if (roles.length === 0)
|
|
129
|
+
return roles;
|
|
130
|
+
const scored = roles
|
|
131
|
+
.map((role) => ({ role, score: scoreAgentForTask(role, text) }))
|
|
132
|
+
.sort((a, b) => {
|
|
133
|
+
if (b.score !== a.score)
|
|
134
|
+
return b.score - a.score;
|
|
135
|
+
return (a.role.sortOrder ?? 0) - (b.role.sortOrder ?? 0);
|
|
136
|
+
});
|
|
137
|
+
const withScore = scored.filter((entry) => entry.score > 0);
|
|
138
|
+
if (withScore.length > 0) {
|
|
139
|
+
const maxScore = withScore[0].score;
|
|
140
|
+
const threshold = Math.max(1, maxScore - 2);
|
|
141
|
+
const selected = withScore
|
|
142
|
+
.filter((entry) => entry.score >= threshold)
|
|
143
|
+
.slice(0, 4)
|
|
144
|
+
.map((entry) => entry.role);
|
|
145
|
+
return selected.length > 0 ? selected : withScore.slice(0, 3).map((entry) => entry.role);
|
|
146
|
+
}
|
|
147
|
+
const leads = roles
|
|
148
|
+
.filter((role) => role.autonomyLevel === 'lead')
|
|
149
|
+
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
|
150
|
+
if (leads.length > 0) {
|
|
151
|
+
return leads.slice(0, 3);
|
|
152
|
+
}
|
|
153
|
+
return roles.slice(0, Math.min(3, roles.length));
|
|
154
|
+
};
|
|
155
|
+
const extractMentionedRoles = (text, roles) => {
|
|
156
|
+
const normalizedText = text.toLowerCase();
|
|
157
|
+
const useSmartSelection = /\B@everybody\b/.test(normalizedText);
|
|
158
|
+
if (/\B@all\b/.test(normalizedText) || /\B@everyone\b/.test(normalizedText)) {
|
|
159
|
+
return roles;
|
|
160
|
+
}
|
|
161
|
+
const index = buildAgentMentionIndex(roles);
|
|
162
|
+
const matches = new Map();
|
|
163
|
+
const regex = /@([a-zA-Z0-9][a-zA-Z0-9 _-]{0,50})/g;
|
|
164
|
+
let match;
|
|
165
|
+
while ((match = regex.exec(text)) !== null) {
|
|
166
|
+
const raw = match[1].replace(/[.,:;!?)]*$/, '').trim();
|
|
167
|
+
const token = normalizeMentionToken(raw);
|
|
168
|
+
const role = index.get(token);
|
|
169
|
+
if (role) {
|
|
170
|
+
matches.set(role.id, role);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (matches.size > 0) {
|
|
174
|
+
if (useSmartSelection) {
|
|
175
|
+
const selected = selectBestAgentsForTask(text, roles);
|
|
176
|
+
const merged = new Map();
|
|
177
|
+
selected.forEach((role) => merged.set(role.id, role));
|
|
178
|
+
matches.forEach((role) => merged.set(role.id, role));
|
|
179
|
+
return Array.from(merged.values());
|
|
180
|
+
}
|
|
181
|
+
return Array.from(matches.values());
|
|
182
|
+
}
|
|
183
|
+
const normalizedWithAt = text
|
|
184
|
+
.toLowerCase()
|
|
185
|
+
.replace(/[^a-z0-9@]/g, '');
|
|
186
|
+
index.forEach((role, token) => {
|
|
187
|
+
if (normalizedWithAt.includes(`@${token}`)) {
|
|
188
|
+
matches.set(role.id, role);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (useSmartSelection) {
|
|
192
|
+
return selectBestAgentsForTask(text, roles);
|
|
193
|
+
}
|
|
194
|
+
return Array.from(matches.values());
|
|
195
|
+
};
|
|
196
|
+
const buildSoulSummary = (soul) => {
|
|
197
|
+
if (!soul)
|
|
198
|
+
return null;
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(soul);
|
|
201
|
+
const parts = [];
|
|
202
|
+
if (typeof parsed.name === 'string')
|
|
203
|
+
parts.push(`Name: ${parsed.name}`);
|
|
204
|
+
if (typeof parsed.role === 'string')
|
|
205
|
+
parts.push(`Role: ${parsed.role}`);
|
|
206
|
+
if (typeof parsed.personality === 'string')
|
|
207
|
+
parts.push(`Personality: ${parsed.personality}`);
|
|
208
|
+
if (typeof parsed.communicationStyle === 'string')
|
|
209
|
+
parts.push(`Style: ${parsed.communicationStyle}`);
|
|
210
|
+
if (Array.isArray(parsed.focusAreas))
|
|
211
|
+
parts.push(`Focus: ${parsed.focusAreas.join(', ')}`);
|
|
212
|
+
if (Array.isArray(parsed.strengths))
|
|
213
|
+
parts.push(`Strengths: ${parsed.strengths.join(', ')}`);
|
|
214
|
+
if (parts.length === 0) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
return parts.join('\n');
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return soul;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const buildAgentDispatchPrompt = (role, parentTask) => {
|
|
224
|
+
const lines = [
|
|
225
|
+
`You are ${role.displayName}${role.description ? ` — ${role.description}` : ''}.`,
|
|
226
|
+
];
|
|
227
|
+
if (role.capabilities && role.capabilities.length > 0) {
|
|
228
|
+
lines.push(`Capabilities: ${role.capabilities.join(', ')}`);
|
|
229
|
+
}
|
|
230
|
+
if (role.systemPrompt) {
|
|
231
|
+
lines.push('System guidance:');
|
|
232
|
+
lines.push(role.systemPrompt);
|
|
233
|
+
}
|
|
234
|
+
const soulSummary = buildSoulSummary(role.soul || undefined);
|
|
235
|
+
if (soulSummary) {
|
|
236
|
+
lines.push('Role notes:');
|
|
237
|
+
lines.push(soulSummary);
|
|
238
|
+
}
|
|
239
|
+
lines.push('');
|
|
240
|
+
lines.push(`Parent task: ${parentTask.title}`);
|
|
241
|
+
lines.push('Request:');
|
|
242
|
+
lines.push(parentTask.prompt);
|
|
243
|
+
lines.push('');
|
|
244
|
+
lines.push('Deliverables:');
|
|
245
|
+
lines.push('- Provide a concise summary of your findings.');
|
|
246
|
+
lines.push('- Call out risks or open questions.');
|
|
247
|
+
lines.push('- Recommend next steps.');
|
|
248
|
+
return lines.join('\n');
|
|
249
|
+
};
|
|
250
|
+
const x_manager_1 = require("../settings/x-manager");
|
|
251
|
+
const x_cli_1 = require("../utils/x-cli");
|
|
252
|
+
const custom_skill_loader_1 = require("../agent/custom-skill-loader");
|
|
253
|
+
const settings_1 = require("../mcp/settings");
|
|
254
|
+
const MCPClientManager_1 = require("../mcp/client/MCPClientManager");
|
|
255
|
+
const MCPRegistryManager_1 = require("../mcp/registry/MCPRegistryManager");
|
|
256
|
+
const MCPHostServer_1 = require("../mcp/host/MCPHostServer");
|
|
257
|
+
const builtin_settings_1 = require("../agent/tools/builtin-settings");
|
|
258
|
+
const validation_2 = require("../utils/validation");
|
|
259
|
+
const notifications_1 = require("../notifications");
|
|
260
|
+
const hooks_1 = require("../hooks");
|
|
261
|
+
const MemoryService_1 = require("../memory/MemoryService");
|
|
262
|
+
const voice_settings_manager_1 = require("../voice/voice-settings-manager");
|
|
263
|
+
const VoiceService_1 = require("../voice/VoiceService");
|
|
264
|
+
// Global notification service instance
|
|
265
|
+
let notificationService = null;
|
|
266
|
+
/**
|
|
267
|
+
* Get the notification service instance
|
|
268
|
+
*/
|
|
269
|
+
function getNotificationService() {
|
|
270
|
+
return notificationService;
|
|
271
|
+
}
|
|
272
|
+
// Helper to check rate limit and throw if exceeded
|
|
273
|
+
function checkRateLimit(channel, config = rate_limiter_1.RATE_LIMIT_CONFIGS.standard) {
|
|
274
|
+
if (!rate_limiter_1.rateLimiter.check(channel)) {
|
|
275
|
+
const resetMs = rate_limiter_1.rateLimiter.getResetTime(channel);
|
|
276
|
+
const resetSec = Math.ceil(resetMs / 1000);
|
|
277
|
+
throw new Error(`Rate limit exceeded. Try again in ${resetSec} seconds.`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Configure rate limits for sensitive channels
|
|
281
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.TASK_CREATE, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
282
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.TASK_SEND_MESSAGE, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
283
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
284
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_TEST_PROVIDER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
285
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_OLLAMA_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
|
|
286
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_GEMINI_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
|
|
287
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
|
|
288
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.LLM_GET_BEDROCK_MODELS, rate_limiter_1.RATE_LIMIT_CONFIGS.standard);
|
|
289
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.SEARCH_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
290
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.SEARCH_TEST_PROVIDER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
291
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.GATEWAY_ADD_CHANNEL, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
292
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.GATEWAY_TEST_CHANNEL, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
293
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.GUARDRAIL_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
294
|
+
// Helper function to get the main window
|
|
295
|
+
function getMainWindow() {
|
|
296
|
+
const windows = electron_1.BrowserWindow.getAllWindows();
|
|
297
|
+
return windows.length > 0 ? windows[0] : null;
|
|
298
|
+
}
|
|
299
|
+
async function setupIpcHandlers(dbManager, agentDaemon, gateway) {
|
|
300
|
+
const db = dbManager.getDatabase();
|
|
301
|
+
const workspaceRepo = new repositories_1.WorkspaceRepository(db);
|
|
302
|
+
const taskRepo = new repositories_1.TaskRepository(db);
|
|
303
|
+
const taskEventRepo = new repositories_1.TaskEventRepository(db);
|
|
304
|
+
const artifactRepo = new repositories_1.ArtifactRepository(db);
|
|
305
|
+
const skillRepo = new repositories_1.SkillRepository(db);
|
|
306
|
+
const llmModelRepo = new repositories_1.LLMModelRepository(db);
|
|
307
|
+
const agentRoleRepo = new AgentRoleRepository_1.AgentRoleRepository(db);
|
|
308
|
+
const activityRepo = new ActivityRepository_1.ActivityRepository(db);
|
|
309
|
+
const mentionRepo = new MentionRepository_1.MentionRepository(db);
|
|
310
|
+
const taskLabelRepo = new TaskLabelRepository_1.TaskLabelRepository(db);
|
|
311
|
+
const workingStateRepo = new WorkingStateRepository_1.WorkingStateRepository(db);
|
|
312
|
+
const contextPolicyManager = new context_policy_1.ContextPolicyManager(db);
|
|
313
|
+
// Seed default agent roles if none exist
|
|
314
|
+
agentRoleRepo.seedDefaults();
|
|
315
|
+
// Helper to validate path is within workspace (prevent path traversal attacks)
|
|
316
|
+
const isPathWithinWorkspace = (filePath, workspacePath) => {
|
|
317
|
+
const normalizedWorkspace = path.resolve(workspacePath);
|
|
318
|
+
const normalizedFile = path.resolve(normalizedWorkspace, filePath);
|
|
319
|
+
const relative = path.relative(normalizedWorkspace, normalizedFile);
|
|
320
|
+
// If relative path starts with '..' or is absolute, it's outside workspace
|
|
321
|
+
return !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
322
|
+
};
|
|
323
|
+
// Temp workspace management
|
|
324
|
+
// The temp workspace is created on-demand and stored in the database with a special ID
|
|
325
|
+
// It uses the system's temp directory and is filtered from the workspace list shown to users
|
|
326
|
+
const getOrCreateTempWorkspace = async () => {
|
|
327
|
+
// Check if temp workspace already exists in database
|
|
328
|
+
const existing = workspaceRepo.findById(types_1.TEMP_WORKSPACE_ID);
|
|
329
|
+
if (existing) {
|
|
330
|
+
const updatedPermissions = {
|
|
331
|
+
...existing.permissions,
|
|
332
|
+
read: true,
|
|
333
|
+
write: true,
|
|
334
|
+
delete: true,
|
|
335
|
+
network: true,
|
|
336
|
+
shell: existing.permissions.shell ?? false,
|
|
337
|
+
unrestrictedFileAccess: true,
|
|
338
|
+
};
|
|
339
|
+
if (!existing.permissions.unrestrictedFileAccess) {
|
|
340
|
+
workspaceRepo.updatePermissions(existing.id, updatedPermissions);
|
|
341
|
+
}
|
|
342
|
+
// Verify the temp directory still exists, recreate if not
|
|
343
|
+
try {
|
|
344
|
+
await fs.access(existing.path);
|
|
345
|
+
return { ...existing, permissions: updatedPermissions, isTemp: true };
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Directory was deleted, delete the workspace record and recreate
|
|
349
|
+
workspaceRepo.delete(types_1.TEMP_WORKSPACE_ID);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Create temp directory
|
|
353
|
+
const tempDir = path.join(os.tmpdir(), 'cowork-os-temp');
|
|
354
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
355
|
+
// Create the temp workspace with a known ID
|
|
356
|
+
const tempWorkspace = {
|
|
357
|
+
id: types_1.TEMP_WORKSPACE_ID,
|
|
358
|
+
name: types_1.TEMP_WORKSPACE_NAME,
|
|
359
|
+
path: tempDir,
|
|
360
|
+
createdAt: Date.now(),
|
|
361
|
+
permissions: {
|
|
362
|
+
read: true,
|
|
363
|
+
write: true,
|
|
364
|
+
delete: true,
|
|
365
|
+
network: true,
|
|
366
|
+
shell: false,
|
|
367
|
+
unrestrictedFileAccess: true,
|
|
368
|
+
},
|
|
369
|
+
isTemp: true,
|
|
370
|
+
};
|
|
371
|
+
// Insert directly using raw SQL to use our specific ID
|
|
372
|
+
const stmt = db.prepare(`
|
|
373
|
+
INSERT OR REPLACE INTO workspaces (id, name, path, created_at, permissions)
|
|
374
|
+
VALUES (?, ?, ?, ?, ?)
|
|
375
|
+
`);
|
|
376
|
+
stmt.run(tempWorkspace.id, tempWorkspace.name, tempWorkspace.path, tempWorkspace.createdAt, JSON.stringify(tempWorkspace.permissions));
|
|
377
|
+
return tempWorkspace;
|
|
378
|
+
};
|
|
379
|
+
// File handlers - open files and show in Finder
|
|
380
|
+
electron_1.ipcMain.handle('file:open', async (_, filePath, workspacePath) => {
|
|
381
|
+
// Security: require workspacePath and validate path is within it
|
|
382
|
+
if (!workspacePath) {
|
|
383
|
+
throw new Error('Workspace path is required for file operations');
|
|
384
|
+
}
|
|
385
|
+
// Resolve the path relative to workspace
|
|
386
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
387
|
+
? filePath
|
|
388
|
+
: path.resolve(workspacePath, filePath);
|
|
389
|
+
// Validate path is within workspace (prevent path traversal)
|
|
390
|
+
if (!isPathWithinWorkspace(resolvedPath, workspacePath)) {
|
|
391
|
+
throw new Error('Access denied: file path is outside the workspace');
|
|
392
|
+
}
|
|
393
|
+
return electron_1.shell.openPath(resolvedPath);
|
|
394
|
+
});
|
|
395
|
+
electron_1.ipcMain.handle('file:showInFinder', async (_, filePath, workspacePath) => {
|
|
396
|
+
// Security: require workspacePath and validate path is within it
|
|
397
|
+
if (!workspacePath) {
|
|
398
|
+
throw new Error('Workspace path is required for file operations');
|
|
399
|
+
}
|
|
400
|
+
// Resolve the path relative to workspace
|
|
401
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
402
|
+
? filePath
|
|
403
|
+
: path.resolve(workspacePath, filePath);
|
|
404
|
+
// Validate path is within workspace (prevent path traversal)
|
|
405
|
+
if (!isPathWithinWorkspace(resolvedPath, workspacePath)) {
|
|
406
|
+
throw new Error('Access denied: file path is outside the workspace');
|
|
407
|
+
}
|
|
408
|
+
electron_1.shell.showItemInFolder(resolvedPath);
|
|
409
|
+
});
|
|
410
|
+
// Open external URL in system browser
|
|
411
|
+
electron_1.ipcMain.handle('shell:openExternal', async (_, url) => {
|
|
412
|
+
// Validate URL to prevent security issues
|
|
413
|
+
try {
|
|
414
|
+
const parsedUrl = new URL(url);
|
|
415
|
+
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
|
416
|
+
throw new Error('Only http and https URLs are allowed');
|
|
417
|
+
}
|
|
418
|
+
await electron_1.shell.openExternal(url);
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
throw new Error(`Failed to open URL: ${error.message}`);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
// File viewer handler - read file content for in-app preview
|
|
425
|
+
// Note: This handler allows viewing any file on the system for convenience.
|
|
426
|
+
// File operations like open/showInFinder remain workspace-restricted.
|
|
427
|
+
electron_1.ipcMain.handle('file:readForViewer', async (_, data) => {
|
|
428
|
+
const { filePath, workspacePath } = data;
|
|
429
|
+
// Resolve the path - if absolute use directly, otherwise resolve relative to workspace or cwd
|
|
430
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
431
|
+
? filePath
|
|
432
|
+
: workspacePath
|
|
433
|
+
? path.resolve(workspacePath, filePath)
|
|
434
|
+
: path.resolve(filePath);
|
|
435
|
+
// Check if file exists
|
|
436
|
+
try {
|
|
437
|
+
await fs.access(resolvedPath);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return { success: false, error: 'File not found' };
|
|
441
|
+
}
|
|
442
|
+
// Get file stats
|
|
443
|
+
const stats = await fs.stat(resolvedPath);
|
|
444
|
+
const extension = path.extname(resolvedPath).toLowerCase();
|
|
445
|
+
const fileName = path.basename(resolvedPath);
|
|
446
|
+
// Determine file type
|
|
447
|
+
const getFileType = (ext) => {
|
|
448
|
+
const codeExtensions = ['.js', '.ts', '.tsx', '.jsx', '.py', '.java', '.go', '.rs', '.c', '.cpp', '.h', '.css', '.scss', '.xml', '.json', '.yaml', '.yml', '.toml', '.sh', '.bash', '.zsh', '.sql', '.graphql', '.vue', '.svelte', '.rb', '.php', '.swift', '.kt', '.scala'];
|
|
449
|
+
const textExtensions = ['.txt', '.log', '.csv', '.env', '.gitignore', '.dockerignore', '.editorconfig', '.prettierrc', '.eslintrc'];
|
|
450
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.ico'];
|
|
451
|
+
if (ext === '.md' || ext === '.markdown')
|
|
452
|
+
return 'markdown';
|
|
453
|
+
if (ext === '.html' || ext === '.htm')
|
|
454
|
+
return 'html';
|
|
455
|
+
if (ext === '.docx')
|
|
456
|
+
return 'docx';
|
|
457
|
+
if (ext === '.pdf')
|
|
458
|
+
return 'pdf';
|
|
459
|
+
if (ext === '.pptx')
|
|
460
|
+
return 'pptx';
|
|
461
|
+
if (imageExtensions.includes(ext))
|
|
462
|
+
return 'image';
|
|
463
|
+
if (codeExtensions.includes(ext))
|
|
464
|
+
return 'code';
|
|
465
|
+
if (textExtensions.includes(ext))
|
|
466
|
+
return 'text';
|
|
467
|
+
return 'unsupported';
|
|
468
|
+
};
|
|
469
|
+
const fileType = getFileType(extension);
|
|
470
|
+
// Size limits
|
|
471
|
+
const MAX_TEXT_SIZE = 5 * 1024 * 1024; // 5MB
|
|
472
|
+
const MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
473
|
+
if (fileType === 'image' && stats.size > MAX_IMAGE_SIZE) {
|
|
474
|
+
return { success: false, error: 'File too large for preview (max 10MB for images)' };
|
|
475
|
+
}
|
|
476
|
+
if (fileType !== 'image' && fileType !== 'unsupported' && stats.size > MAX_TEXT_SIZE) {
|
|
477
|
+
return { success: false, error: 'File too large for preview (max 5MB for text files)' };
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
let content = null;
|
|
481
|
+
let htmlContent;
|
|
482
|
+
switch (fileType) {
|
|
483
|
+
case 'markdown':
|
|
484
|
+
case 'code':
|
|
485
|
+
case 'text': {
|
|
486
|
+
content = await fs.readFile(resolvedPath, 'utf-8');
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case 'docx': {
|
|
490
|
+
const buffer = await fs.readFile(resolvedPath);
|
|
491
|
+
const result = await mammoth_1.default.convertToHtml({ buffer });
|
|
492
|
+
htmlContent = result.value;
|
|
493
|
+
content = null; // HTML content is in htmlContent
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case 'pdf': {
|
|
497
|
+
const buffer = await fs.readFile(resolvedPath);
|
|
498
|
+
const pdfData = await pdfParse(buffer);
|
|
499
|
+
content = pdfData.text;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
case 'image': {
|
|
503
|
+
const buffer = await fs.readFile(resolvedPath);
|
|
504
|
+
const mimeTypes = {
|
|
505
|
+
'.png': 'image/png',
|
|
506
|
+
'.jpg': 'image/jpeg',
|
|
507
|
+
'.jpeg': 'image/jpeg',
|
|
508
|
+
'.gif': 'image/gif',
|
|
509
|
+
'.webp': 'image/webp',
|
|
510
|
+
'.svg': 'image/svg+xml',
|
|
511
|
+
'.bmp': 'image/bmp',
|
|
512
|
+
'.ico': 'image/x-icon',
|
|
513
|
+
};
|
|
514
|
+
const mimeType = mimeTypes[extension] || 'image/png';
|
|
515
|
+
content = `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case 'html': {
|
|
519
|
+
htmlContent = await fs.readFile(resolvedPath, 'utf-8');
|
|
520
|
+
content = null; // HTML content is in htmlContent
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
case 'pptx':
|
|
524
|
+
// PowerPoint files are complex to render, return placeholder
|
|
525
|
+
content = null;
|
|
526
|
+
break;
|
|
527
|
+
default:
|
|
528
|
+
return { success: false, error: 'Unsupported file type', fileType: 'unsupported' };
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
success: true,
|
|
532
|
+
data: {
|
|
533
|
+
path: resolvedPath,
|
|
534
|
+
fileName,
|
|
535
|
+
fileType,
|
|
536
|
+
content,
|
|
537
|
+
htmlContent,
|
|
538
|
+
size: stats.size,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
return { success: false, error: `Failed to read file: ${error.message}` };
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
// Workspace handlers
|
|
547
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_CREATE, async (_, data) => {
|
|
548
|
+
const validated = (0, validation_1.validateInput)(validation_1.WorkspaceCreateSchema, data, 'workspace');
|
|
549
|
+
const { name, path, permissions } = validated;
|
|
550
|
+
// Check if workspace with this path already exists
|
|
551
|
+
if (workspaceRepo.existsByPath(path)) {
|
|
552
|
+
throw new Error(`A workspace with path "${path}" already exists. Please choose a different folder.`);
|
|
553
|
+
}
|
|
554
|
+
// Provide default permissions if not specified
|
|
555
|
+
// Note: network is enabled by default for browser tools (web access)
|
|
556
|
+
const defaultPermissions = {
|
|
557
|
+
read: true,
|
|
558
|
+
write: true,
|
|
559
|
+
delete: false,
|
|
560
|
+
network: true,
|
|
561
|
+
shell: false,
|
|
562
|
+
};
|
|
563
|
+
return workspaceRepo.create(name, path, permissions ?? defaultPermissions);
|
|
564
|
+
});
|
|
565
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_LIST, async () => {
|
|
566
|
+
// Filter out the temp workspace from the list - users shouldn't see it in their workspaces
|
|
567
|
+
const allWorkspaces = workspaceRepo.findAll();
|
|
568
|
+
return allWorkspaces.filter(w => w.id !== types_1.TEMP_WORKSPACE_ID);
|
|
569
|
+
});
|
|
570
|
+
// Get or create the temp workspace (used when no workspace is selected)
|
|
571
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_GET_TEMP, async () => {
|
|
572
|
+
return getOrCreateTempWorkspace();
|
|
573
|
+
});
|
|
574
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_SELECT, async (_, id) => {
|
|
575
|
+
return workspaceRepo.findById(id);
|
|
576
|
+
});
|
|
577
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKSPACE_UPDATE_PERMISSIONS, async (_, id, permissions) => {
|
|
578
|
+
const workspace = workspaceRepo.findById(id);
|
|
579
|
+
if (!workspace) {
|
|
580
|
+
throw new Error(`Workspace not found: ${id}`);
|
|
581
|
+
}
|
|
582
|
+
const updatedPermissions = { ...workspace.permissions, ...permissions };
|
|
583
|
+
workspaceRepo.updatePermissions(id, updatedPermissions);
|
|
584
|
+
return workspaceRepo.findById(id);
|
|
585
|
+
});
|
|
586
|
+
// Task handlers
|
|
587
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_CREATE, async (_, data) => {
|
|
588
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_CREATE);
|
|
589
|
+
const validated = (0, validation_1.validateInput)(validation_1.TaskCreateSchema, data, 'task');
|
|
590
|
+
const { title, prompt, workspaceId, budgetTokens, budgetCost } = validated;
|
|
591
|
+
const task = taskRepo.create({
|
|
592
|
+
title,
|
|
593
|
+
prompt,
|
|
594
|
+
status: 'pending',
|
|
595
|
+
workspaceId,
|
|
596
|
+
budgetTokens,
|
|
597
|
+
budgetCost,
|
|
598
|
+
});
|
|
599
|
+
// Start task execution in agent daemon
|
|
600
|
+
try {
|
|
601
|
+
await agentDaemon.startTask(task);
|
|
602
|
+
}
|
|
603
|
+
catch (error) {
|
|
604
|
+
// Update task status to failed if we can't start it
|
|
605
|
+
taskRepo.update(task.id, {
|
|
606
|
+
status: 'failed',
|
|
607
|
+
error: error.message || 'Failed to start task',
|
|
608
|
+
});
|
|
609
|
+
throw new Error(error.message || 'Failed to start task. Please check your LLM provider settings.');
|
|
610
|
+
}
|
|
611
|
+
// Dispatch to mentioned agents (e.g., "Please review @Vision @Loki")
|
|
612
|
+
try {
|
|
613
|
+
const activeRoles = agentRoleRepo.findAll(false).filter((role) => role.isActive);
|
|
614
|
+
const mentionedRoles = extractMentionedRoles(`${title}\n${prompt}`, activeRoles);
|
|
615
|
+
const dispatchRoles = mentionedRoles.length > 0 ? mentionedRoles : activeRoles;
|
|
616
|
+
if (dispatchRoles.length > 0) {
|
|
617
|
+
taskRepo.update(task.id, {
|
|
618
|
+
mentionedAgentRoleIds: dispatchRoles.map((role) => role.id),
|
|
619
|
+
});
|
|
620
|
+
for (const role of dispatchRoles) {
|
|
621
|
+
const childPrompt = buildAgentDispatchPrompt(role, task);
|
|
622
|
+
const childTask = await agentDaemon.createChildTask({
|
|
623
|
+
title: `@${role.displayName}: ${task.title}`,
|
|
624
|
+
prompt: childPrompt,
|
|
625
|
+
workspaceId: task.workspaceId,
|
|
626
|
+
parentTaskId: task.id,
|
|
627
|
+
agentType: 'sub',
|
|
628
|
+
agentConfig: {
|
|
629
|
+
...(role.modelKey ? { modelKey: role.modelKey } : {}),
|
|
630
|
+
...(role.personalityId ? { personalityId: role.personalityId } : {}),
|
|
631
|
+
retainMemory: false,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
taskRepo.update(childTask.id, {
|
|
635
|
+
assignedAgentRoleId: role.id,
|
|
636
|
+
boardColumn: 'todo',
|
|
637
|
+
});
|
|
638
|
+
const dispatchActivity = activityRepo.create({
|
|
639
|
+
workspaceId: task.workspaceId,
|
|
640
|
+
taskId: task.id,
|
|
641
|
+
agentRoleId: role.id,
|
|
642
|
+
actorType: 'system',
|
|
643
|
+
activityType: 'agent_assigned',
|
|
644
|
+
title: `Dispatched to ${role.displayName}`,
|
|
645
|
+
description: childTask.title,
|
|
646
|
+
});
|
|
647
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: dispatchActivity });
|
|
648
|
+
const mention = mentionRepo.create({
|
|
649
|
+
workspaceId: task.workspaceId,
|
|
650
|
+
taskId: task.id,
|
|
651
|
+
toAgentRoleId: role.id,
|
|
652
|
+
mentionType: 'request',
|
|
653
|
+
context: `New task: ${task.title}`,
|
|
654
|
+
});
|
|
655
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'created', mention });
|
|
656
|
+
const mentionActivity = activityRepo.create({
|
|
657
|
+
workspaceId: task.workspaceId,
|
|
658
|
+
taskId: task.id,
|
|
659
|
+
agentRoleId: role.id,
|
|
660
|
+
actorType: 'user',
|
|
661
|
+
activityType: 'mention',
|
|
662
|
+
title: `@${role.displayName} mentioned`,
|
|
663
|
+
description: mention.context,
|
|
664
|
+
metadata: { mentionId: mention.id, mentionType: mention.mentionType },
|
|
665
|
+
});
|
|
666
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity: mentionActivity });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
console.error('Failed to dispatch to mentioned agents:', error);
|
|
672
|
+
}
|
|
673
|
+
return task;
|
|
674
|
+
});
|
|
675
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_GET, async (_, id) => {
|
|
676
|
+
return taskRepo.findById(id);
|
|
677
|
+
});
|
|
678
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_LIST, async () => {
|
|
679
|
+
return taskRepo.findAll();
|
|
680
|
+
});
|
|
681
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_CANCEL, async (_, id) => {
|
|
682
|
+
try {
|
|
683
|
+
await agentDaemon.cancelTask(id);
|
|
684
|
+
}
|
|
685
|
+
finally {
|
|
686
|
+
// Always update status even if daemon cancel fails
|
|
687
|
+
taskRepo.update(id, { status: 'cancelled' });
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_PAUSE, async (_, id) => {
|
|
691
|
+
// Pause daemon first - if it fails, exception propagates and status won't be updated
|
|
692
|
+
await agentDaemon.pauseTask(id);
|
|
693
|
+
taskRepo.update(id, { status: 'paused' });
|
|
694
|
+
});
|
|
695
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_RESUME, async (_, id) => {
|
|
696
|
+
// Resume daemon first - if it fails, exception propagates and status won't be updated
|
|
697
|
+
await agentDaemon.resumeTask(id);
|
|
698
|
+
taskRepo.update(id, { status: 'executing' });
|
|
699
|
+
});
|
|
700
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_SEND_STDIN, async (_, data) => {
|
|
701
|
+
return agentDaemon.sendStdinToTask(data.taskId, data.input);
|
|
702
|
+
});
|
|
703
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_KILL_COMMAND, async (_, data) => {
|
|
704
|
+
return agentDaemon.killCommandInTask(data.taskId, data.force);
|
|
705
|
+
});
|
|
706
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_RENAME, async (_, data) => {
|
|
707
|
+
const validated = (0, validation_1.validateInput)(validation_1.TaskRenameSchema, data, 'task rename');
|
|
708
|
+
taskRepo.update(validated.id, { title: validated.title });
|
|
709
|
+
});
|
|
710
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_DELETE, async (_, id) => {
|
|
711
|
+
// Cancel the task if it's running
|
|
712
|
+
await agentDaemon.cancelTask(id);
|
|
713
|
+
// Delete from database
|
|
714
|
+
taskRepo.delete(id);
|
|
715
|
+
});
|
|
716
|
+
// ============ Sub-Agent / Parallel Agent Handlers ============
|
|
717
|
+
// Get child tasks for a parent task
|
|
718
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_GET_CHILDREN, async (_, parentTaskId) => {
|
|
719
|
+
return agentDaemon.getChildTasks(parentTaskId);
|
|
720
|
+
});
|
|
721
|
+
// Get status of specific agents
|
|
722
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_GET_STATUS, async (_, taskIds) => {
|
|
723
|
+
const tasks = [];
|
|
724
|
+
for (const id of taskIds) {
|
|
725
|
+
const task = await agentDaemon.getTaskById(id);
|
|
726
|
+
if (task) {
|
|
727
|
+
tasks.push({
|
|
728
|
+
taskId: id,
|
|
729
|
+
status: task.status,
|
|
730
|
+
title: task.title,
|
|
731
|
+
agentType: task.agentType,
|
|
732
|
+
resultSummary: task.resultSummary,
|
|
733
|
+
error: task.error,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return tasks;
|
|
738
|
+
});
|
|
739
|
+
// Task events handler - get historical events from database
|
|
740
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_EVENTS, async (_, taskId) => {
|
|
741
|
+
return taskEventRepo.findByTaskId(taskId);
|
|
742
|
+
});
|
|
743
|
+
// Send follow-up message to a task
|
|
744
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_SEND_MESSAGE, async (_, data) => {
|
|
745
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_SEND_MESSAGE);
|
|
746
|
+
const validated = (0, validation_1.validateInput)(validation_1.TaskMessageSchema, data, 'task message');
|
|
747
|
+
await agentDaemon.sendMessage(validated.taskId, validated.message);
|
|
748
|
+
});
|
|
749
|
+
// Approval handlers
|
|
750
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APPROVAL_RESPOND, async (_, data) => {
|
|
751
|
+
const validated = (0, validation_1.validateInput)(validation_1.ApprovalResponseSchema, data, 'approval response');
|
|
752
|
+
await agentDaemon.respondToApproval(validated.approvalId, validated.approved);
|
|
753
|
+
});
|
|
754
|
+
// Artifact handlers
|
|
755
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ARTIFACT_LIST, async (_, taskId) => {
|
|
756
|
+
return artifactRepo.findByTaskId(taskId);
|
|
757
|
+
});
|
|
758
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ARTIFACT_PREVIEW, async (_, id) => {
|
|
759
|
+
// TODO: Implement artifact preview
|
|
760
|
+
return null;
|
|
761
|
+
});
|
|
762
|
+
// Skill handlers
|
|
763
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_LIST, async () => {
|
|
764
|
+
return skillRepo.findAll();
|
|
765
|
+
});
|
|
766
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_GET, async (_, id) => {
|
|
767
|
+
return skillRepo.findById(id);
|
|
768
|
+
});
|
|
769
|
+
// Custom User Skills handlers
|
|
770
|
+
const customSkillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
771
|
+
// Initialize custom skill loader
|
|
772
|
+
customSkillLoader.initialize().catch(error => {
|
|
773
|
+
console.error('[IPC] Failed to initialize custom skill loader:', error);
|
|
774
|
+
});
|
|
775
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_LIST, async () => {
|
|
776
|
+
return customSkillLoader.listSkills();
|
|
777
|
+
});
|
|
778
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_LIST_TASKS, async () => {
|
|
779
|
+
return customSkillLoader.listTaskSkills();
|
|
780
|
+
});
|
|
781
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_LIST_GUIDELINES, async () => {
|
|
782
|
+
return customSkillLoader.listGuidelineSkills();
|
|
783
|
+
});
|
|
784
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_GET, async (_, id) => {
|
|
785
|
+
return customSkillLoader.getSkill(id);
|
|
786
|
+
});
|
|
787
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_CREATE, async (_, skillData) => {
|
|
788
|
+
return customSkillLoader.createSkill(skillData);
|
|
789
|
+
});
|
|
790
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_UPDATE, async (_, id, updates) => {
|
|
791
|
+
return customSkillLoader.updateSkill(id, updates);
|
|
792
|
+
});
|
|
793
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_DELETE, async (_, id) => {
|
|
794
|
+
return customSkillLoader.deleteSkill(id);
|
|
795
|
+
});
|
|
796
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_RELOAD, async () => {
|
|
797
|
+
return customSkillLoader.reloadSkills();
|
|
798
|
+
});
|
|
799
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CUSTOM_SKILL_OPEN_FOLDER, async () => {
|
|
800
|
+
return customSkillLoader.openSkillsFolder();
|
|
801
|
+
});
|
|
802
|
+
// Skill Registry (SkillHub) handlers
|
|
803
|
+
const { getSkillRegistry } = await Promise.resolve().then(() => __importStar(require('../agent/skill-registry')));
|
|
804
|
+
const skillRegistry = getSkillRegistry();
|
|
805
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_SEARCH, async (_, query, options) => {
|
|
806
|
+
return skillRegistry.search(query, options);
|
|
807
|
+
});
|
|
808
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_GET_DETAILS, async (_, skillId) => {
|
|
809
|
+
return skillRegistry.getSkillDetails(skillId);
|
|
810
|
+
});
|
|
811
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_INSTALL, async (_, skillId, version) => {
|
|
812
|
+
const result = await skillRegistry.install(skillId, version);
|
|
813
|
+
if (result.success) {
|
|
814
|
+
// Reload skills to pick up the new one
|
|
815
|
+
await customSkillLoader.reloadSkills();
|
|
816
|
+
// Clear eligibility cache in case new dependencies were installed
|
|
817
|
+
customSkillLoader.clearEligibilityCache();
|
|
818
|
+
}
|
|
819
|
+
return result;
|
|
820
|
+
});
|
|
821
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_UPDATE, async (_, skillId, version) => {
|
|
822
|
+
const result = await skillRegistry.update(skillId, version);
|
|
823
|
+
if (result.success) {
|
|
824
|
+
await customSkillLoader.reloadSkills();
|
|
825
|
+
customSkillLoader.clearEligibilityCache();
|
|
826
|
+
}
|
|
827
|
+
return result;
|
|
828
|
+
});
|
|
829
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_UPDATE_ALL, async () => {
|
|
830
|
+
const result = await skillRegistry.updateAll();
|
|
831
|
+
await customSkillLoader.reloadSkills();
|
|
832
|
+
customSkillLoader.clearEligibilityCache();
|
|
833
|
+
return result;
|
|
834
|
+
});
|
|
835
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_UNINSTALL, async (_, skillId) => {
|
|
836
|
+
const result = skillRegistry.uninstall(skillId);
|
|
837
|
+
if (result.success) {
|
|
838
|
+
await customSkillLoader.reloadSkills();
|
|
839
|
+
}
|
|
840
|
+
return result;
|
|
841
|
+
});
|
|
842
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_LIST_MANAGED, async () => {
|
|
843
|
+
return skillRegistry.listManagedSkills();
|
|
844
|
+
});
|
|
845
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_CHECK_UPDATES, async (_, skillId) => {
|
|
846
|
+
return skillRegistry.checkForUpdates(skillId);
|
|
847
|
+
});
|
|
848
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_GET_STATUS, async () => {
|
|
849
|
+
return customSkillLoader.getSkillStatus();
|
|
850
|
+
});
|
|
851
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SKILL_REGISTRY_GET_ELIGIBLE, async () => {
|
|
852
|
+
return customSkillLoader.getEligibleSkills();
|
|
853
|
+
});
|
|
854
|
+
// LLM Settings handlers
|
|
855
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_SETTINGS, async () => {
|
|
856
|
+
return llm_1.LLMProviderFactory.loadSettings();
|
|
857
|
+
});
|
|
858
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_SAVE_SETTINGS, async (_, settings) => {
|
|
859
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_SAVE_SETTINGS);
|
|
860
|
+
const validated = (0, validation_1.validateInput)(validation_1.LLMSettingsSchema, settings, 'LLM settings');
|
|
861
|
+
// Load existing settings to preserve cached models and OAuth tokens
|
|
862
|
+
const existingSettings = llm_1.LLMProviderFactory.loadSettings();
|
|
863
|
+
// Build OpenAI settings, preserving OAuth tokens from existing settings
|
|
864
|
+
let openaiSettings = validated.openai;
|
|
865
|
+
if (existingSettings.openai?.authMethod === 'oauth') {
|
|
866
|
+
// Preserve OAuth tokens when saving settings
|
|
867
|
+
openaiSettings = {
|
|
868
|
+
...validated.openai,
|
|
869
|
+
accessToken: existingSettings.openai.accessToken,
|
|
870
|
+
refreshToken: existingSettings.openai.refreshToken,
|
|
871
|
+
tokenExpiresAt: existingSettings.openai.tokenExpiresAt,
|
|
872
|
+
authMethod: existingSettings.openai.authMethod,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
llm_1.LLMProviderFactory.saveSettings({
|
|
876
|
+
providerType: validated.providerType,
|
|
877
|
+
modelKey: validated.modelKey,
|
|
878
|
+
anthropic: validated.anthropic,
|
|
879
|
+
bedrock: validated.bedrock,
|
|
880
|
+
ollama: validated.ollama,
|
|
881
|
+
gemini: validated.gemini,
|
|
882
|
+
openrouter: validated.openrouter,
|
|
883
|
+
openai: openaiSettings,
|
|
884
|
+
// Preserve cached models from existing settings
|
|
885
|
+
cachedGeminiModels: existingSettings.cachedGeminiModels,
|
|
886
|
+
cachedOpenRouterModels: existingSettings.cachedOpenRouterModels,
|
|
887
|
+
cachedOllamaModels: existingSettings.cachedOllamaModels,
|
|
888
|
+
cachedBedrockModels: existingSettings.cachedBedrockModels,
|
|
889
|
+
cachedOpenAIModels: existingSettings.cachedOpenAIModels,
|
|
890
|
+
});
|
|
891
|
+
// Clear cache so next task uses new settings
|
|
892
|
+
llm_1.LLMProviderFactory.clearCache();
|
|
893
|
+
return { success: true };
|
|
894
|
+
});
|
|
895
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_TEST_PROVIDER, async (_, config) => {
|
|
896
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_TEST_PROVIDER);
|
|
897
|
+
// For OpenAI OAuth, get tokens from stored settings if authMethod is 'oauth'
|
|
898
|
+
let openaiAccessToken;
|
|
899
|
+
let openaiRefreshToken;
|
|
900
|
+
if (config.providerType === 'openai' && config.openai?.authMethod === 'oauth') {
|
|
901
|
+
const settings = llm_1.LLMProviderFactory.loadSettings();
|
|
902
|
+
openaiAccessToken = settings.openai?.accessToken;
|
|
903
|
+
openaiRefreshToken = settings.openai?.refreshToken;
|
|
904
|
+
}
|
|
905
|
+
const providerConfig = {
|
|
906
|
+
type: config.providerType,
|
|
907
|
+
model: llm_1.LLMProviderFactory.getModelId(config.modelKey, config.providerType, config.ollama?.model, config.gemini?.model, config.openrouter?.model, config.openai?.model),
|
|
908
|
+
anthropicApiKey: config.anthropic?.apiKey,
|
|
909
|
+
awsRegion: config.bedrock?.region,
|
|
910
|
+
awsAccessKeyId: config.bedrock?.accessKeyId,
|
|
911
|
+
awsSecretAccessKey: config.bedrock?.secretAccessKey,
|
|
912
|
+
awsSessionToken: config.bedrock?.sessionToken,
|
|
913
|
+
awsProfile: config.bedrock?.profile,
|
|
914
|
+
ollamaBaseUrl: config.ollama?.baseUrl,
|
|
915
|
+
ollamaApiKey: config.ollama?.apiKey,
|
|
916
|
+
geminiApiKey: config.gemini?.apiKey,
|
|
917
|
+
openrouterApiKey: config.openrouter?.apiKey,
|
|
918
|
+
openaiApiKey: config.openai?.apiKey,
|
|
919
|
+
openaiAccessToken: openaiAccessToken,
|
|
920
|
+
openaiRefreshToken: openaiRefreshToken,
|
|
921
|
+
};
|
|
922
|
+
return llm_1.LLMProviderFactory.testProvider(providerConfig);
|
|
923
|
+
});
|
|
924
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_MODELS, async () => {
|
|
925
|
+
// Get models from database
|
|
926
|
+
const dbModels = llmModelRepo.findAll();
|
|
927
|
+
return dbModels.map(m => ({
|
|
928
|
+
key: m.key,
|
|
929
|
+
displayName: m.displayName,
|
|
930
|
+
description: m.description,
|
|
931
|
+
}));
|
|
932
|
+
});
|
|
933
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_CONFIG_STATUS, async () => {
|
|
934
|
+
const settings = llm_1.LLMProviderFactory.loadSettings();
|
|
935
|
+
const providers = llm_1.LLMProviderFactory.getAvailableProviders();
|
|
936
|
+
// Get models based on the current provider type
|
|
937
|
+
let models = [];
|
|
938
|
+
let currentModel = settings.modelKey;
|
|
939
|
+
switch (settings.providerType) {
|
|
940
|
+
case 'anthropic':
|
|
941
|
+
case 'bedrock':
|
|
942
|
+
// Use Anthropic/Bedrock models from MODELS
|
|
943
|
+
models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
|
|
944
|
+
key,
|
|
945
|
+
displayName: value.displayName,
|
|
946
|
+
description: key.includes('opus') ? 'Most capable for complex work' :
|
|
947
|
+
key.includes('sonnet') ? 'Balanced performance and speed' :
|
|
948
|
+
'Fast and efficient',
|
|
949
|
+
}));
|
|
950
|
+
break;
|
|
951
|
+
case 'gemini': {
|
|
952
|
+
// For Gemini, use the specific model from settings (full model ID)
|
|
953
|
+
currentModel = settings.gemini?.model || 'gemini-2.0-flash';
|
|
954
|
+
// Use cached models if available, otherwise fall back to static list
|
|
955
|
+
const cachedGemini = llm_1.LLMProviderFactory.getCachedModels('gemini');
|
|
956
|
+
if (cachedGemini && cachedGemini.length > 0) {
|
|
957
|
+
models = cachedGemini;
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
// Fall back to static models
|
|
961
|
+
models = Object.values(llm_1.GEMINI_MODELS).map((value) => ({
|
|
962
|
+
key: value.id,
|
|
963
|
+
displayName: value.displayName,
|
|
964
|
+
description: value.description,
|
|
965
|
+
}));
|
|
966
|
+
}
|
|
967
|
+
// Ensure the currently selected model is in the list
|
|
968
|
+
if (currentModel && !models.some(m => m.key === currentModel)) {
|
|
969
|
+
models.unshift({
|
|
970
|
+
key: currentModel,
|
|
971
|
+
displayName: currentModel,
|
|
972
|
+
description: 'Selected model',
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
case 'openrouter': {
|
|
978
|
+
// For OpenRouter, use the specific model from settings (full model ID)
|
|
979
|
+
currentModel = settings.openrouter?.model || 'anthropic/claude-3.5-sonnet';
|
|
980
|
+
// Use cached models if available, otherwise fall back to static list
|
|
981
|
+
const cachedOpenRouter = llm_1.LLMProviderFactory.getCachedModels('openrouter');
|
|
982
|
+
if (cachedOpenRouter && cachedOpenRouter.length > 0) {
|
|
983
|
+
models = cachedOpenRouter;
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
// Fall back to static models
|
|
987
|
+
models = Object.values(llm_1.OPENROUTER_MODELS).map((value) => ({
|
|
988
|
+
key: value.id,
|
|
989
|
+
displayName: value.displayName,
|
|
990
|
+
description: value.description,
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
// Ensure the currently selected model is in the list
|
|
994
|
+
if (currentModel && !models.some(m => m.key === currentModel)) {
|
|
995
|
+
models.unshift({
|
|
996
|
+
key: currentModel,
|
|
997
|
+
displayName: currentModel,
|
|
998
|
+
description: 'Selected model',
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
case 'ollama': {
|
|
1004
|
+
// For Ollama, use the specific model from settings
|
|
1005
|
+
currentModel = settings.ollama?.model || 'llama3.2';
|
|
1006
|
+
// Use cached models if available, otherwise fall back to static list
|
|
1007
|
+
const cachedOllama = llm_1.LLMProviderFactory.getCachedModels('ollama');
|
|
1008
|
+
if (cachedOllama && cachedOllama.length > 0) {
|
|
1009
|
+
models = cachedOllama;
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
// Fall back to static models
|
|
1013
|
+
models = Object.entries(llm_1.OLLAMA_MODELS).map(([key, value]) => ({
|
|
1014
|
+
key,
|
|
1015
|
+
displayName: value.displayName,
|
|
1016
|
+
description: `${value.size} parameter model`,
|
|
1017
|
+
}));
|
|
1018
|
+
}
|
|
1019
|
+
// Ensure the currently selected model is in the list
|
|
1020
|
+
if (currentModel && !models.some(m => m.key === currentModel)) {
|
|
1021
|
+
models.unshift({
|
|
1022
|
+
key: currentModel,
|
|
1023
|
+
displayName: currentModel,
|
|
1024
|
+
description: 'Selected model',
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
case 'openai': {
|
|
1030
|
+
// For OpenAI, use the specific model from settings
|
|
1031
|
+
currentModel = settings.openai?.model || 'gpt-4o-mini';
|
|
1032
|
+
// Use cached models if available, otherwise fall back to static list
|
|
1033
|
+
const cachedOpenAI = llm_1.LLMProviderFactory.getCachedModels('openai');
|
|
1034
|
+
if (cachedOpenAI && cachedOpenAI.length > 0) {
|
|
1035
|
+
models = cachedOpenAI;
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
// Fall back to static models
|
|
1039
|
+
models = [
|
|
1040
|
+
{ key: 'gpt-4o', displayName: 'GPT-4o', description: 'Most capable model for complex tasks' },
|
|
1041
|
+
{ key: 'gpt-4o-mini', displayName: 'GPT-4o Mini', description: 'Fast and affordable for most tasks' },
|
|
1042
|
+
{ key: 'gpt-4-turbo', displayName: 'GPT-4 Turbo', description: 'Previous generation flagship' },
|
|
1043
|
+
{ key: 'gpt-3.5-turbo', displayName: 'GPT-3.5 Turbo', description: 'Fast and cost-effective' },
|
|
1044
|
+
{ key: 'o1', displayName: 'o1', description: 'Advanced reasoning model' },
|
|
1045
|
+
{ key: 'o1-mini', displayName: 'o1 Mini', description: 'Fast reasoning model' },
|
|
1046
|
+
];
|
|
1047
|
+
}
|
|
1048
|
+
// Ensure the currently selected model is in the list
|
|
1049
|
+
if (currentModel && !models.some(m => m.key === currentModel)) {
|
|
1050
|
+
models.unshift({
|
|
1051
|
+
key: currentModel,
|
|
1052
|
+
displayName: currentModel,
|
|
1053
|
+
description: 'Selected model',
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
default:
|
|
1059
|
+
// Fallback to Anthropic models
|
|
1060
|
+
models = Object.entries(llm_1.MODELS).map(([key, value]) => ({
|
|
1061
|
+
key,
|
|
1062
|
+
displayName: value.displayName,
|
|
1063
|
+
description: 'Claude model',
|
|
1064
|
+
}));
|
|
1065
|
+
}
|
|
1066
|
+
return {
|
|
1067
|
+
currentProvider: settings.providerType,
|
|
1068
|
+
currentModel,
|
|
1069
|
+
providers,
|
|
1070
|
+
models,
|
|
1071
|
+
};
|
|
1072
|
+
});
|
|
1073
|
+
// Set the current model (persists selection across sessions)
|
|
1074
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_SET_MODEL, async (_, modelKey) => {
|
|
1075
|
+
const settings = llm_1.LLMProviderFactory.loadSettings();
|
|
1076
|
+
// Update the model key based on the current provider
|
|
1077
|
+
switch (settings.providerType) {
|
|
1078
|
+
case 'gemini':
|
|
1079
|
+
settings.gemini = { ...settings.gemini, model: modelKey };
|
|
1080
|
+
break;
|
|
1081
|
+
case 'openrouter':
|
|
1082
|
+
settings.openrouter = { ...settings.openrouter, model: modelKey };
|
|
1083
|
+
break;
|
|
1084
|
+
case 'ollama':
|
|
1085
|
+
settings.ollama = { ...settings.ollama, model: modelKey };
|
|
1086
|
+
break;
|
|
1087
|
+
case 'openai':
|
|
1088
|
+
settings.openai = { ...settings.openai, model: modelKey };
|
|
1089
|
+
break;
|
|
1090
|
+
case 'anthropic':
|
|
1091
|
+
case 'bedrock':
|
|
1092
|
+
default:
|
|
1093
|
+
// For Anthropic/Bedrock, use the modelKey field
|
|
1094
|
+
settings.modelKey = modelKey;
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
llm_1.LLMProviderFactory.saveSettings(settings);
|
|
1098
|
+
return { success: true };
|
|
1099
|
+
});
|
|
1100
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OLLAMA_MODELS, async (_, baseUrl) => {
|
|
1101
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_OLLAMA_MODELS);
|
|
1102
|
+
console.log('[IPC] Handling LLM_GET_OLLAMA_MODELS request');
|
|
1103
|
+
const models = await llm_1.LLMProviderFactory.getOllamaModels(baseUrl);
|
|
1104
|
+
// Cache the models for use in config status
|
|
1105
|
+
const cachedModels = models.map(m => ({
|
|
1106
|
+
key: m.name,
|
|
1107
|
+
displayName: m.name,
|
|
1108
|
+
description: `${Math.round(m.size / 1e9)}B parameter model`,
|
|
1109
|
+
size: m.size,
|
|
1110
|
+
}));
|
|
1111
|
+
llm_1.LLMProviderFactory.saveCachedModels('ollama', cachedModels);
|
|
1112
|
+
return models;
|
|
1113
|
+
});
|
|
1114
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_GEMINI_MODELS, async (_, apiKey) => {
|
|
1115
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_GEMINI_MODELS);
|
|
1116
|
+
const models = await llm_1.LLMProviderFactory.getGeminiModels(apiKey);
|
|
1117
|
+
// Cache the models for use in config status
|
|
1118
|
+
const cachedModels = models.map(m => ({
|
|
1119
|
+
key: m.name,
|
|
1120
|
+
displayName: m.displayName,
|
|
1121
|
+
description: m.description,
|
|
1122
|
+
}));
|
|
1123
|
+
llm_1.LLMProviderFactory.saveCachedModels('gemini', cachedModels);
|
|
1124
|
+
return models;
|
|
1125
|
+
});
|
|
1126
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS, async (_, apiKey) => {
|
|
1127
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_OPENROUTER_MODELS);
|
|
1128
|
+
const models = await llm_1.LLMProviderFactory.getOpenRouterModels(apiKey);
|
|
1129
|
+
// Cache the models for use in config status
|
|
1130
|
+
const cachedModels = models.map(m => ({
|
|
1131
|
+
key: m.id,
|
|
1132
|
+
displayName: m.name,
|
|
1133
|
+
description: `Context: ${Math.round(m.context_length / 1000)}k tokens`,
|
|
1134
|
+
contextLength: m.context_length,
|
|
1135
|
+
}));
|
|
1136
|
+
llm_1.LLMProviderFactory.saveCachedModels('openrouter', cachedModels);
|
|
1137
|
+
return models;
|
|
1138
|
+
});
|
|
1139
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_OPENAI_MODELS, async (_, apiKey) => {
|
|
1140
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_OPENAI_MODELS);
|
|
1141
|
+
const models = await llm_1.LLMProviderFactory.getOpenAIModels(apiKey);
|
|
1142
|
+
// Cache the models for use in config status
|
|
1143
|
+
const cachedModels = models.map(m => ({
|
|
1144
|
+
key: m.id,
|
|
1145
|
+
displayName: m.name,
|
|
1146
|
+
description: m.description,
|
|
1147
|
+
}));
|
|
1148
|
+
llm_1.LLMProviderFactory.saveCachedModels('openai', cachedModels);
|
|
1149
|
+
return models;
|
|
1150
|
+
});
|
|
1151
|
+
// OpenAI OAuth handlers
|
|
1152
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_START, async () => {
|
|
1153
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_START);
|
|
1154
|
+
console.log('[IPC] Starting OpenAI OAuth flow with pi-ai SDK...');
|
|
1155
|
+
try {
|
|
1156
|
+
const oauth = new llm_1.OpenAIOAuth();
|
|
1157
|
+
const tokens = await oauth.authenticate();
|
|
1158
|
+
// Save tokens to settings
|
|
1159
|
+
const settings = llm_1.LLMProviderFactory.loadSettings();
|
|
1160
|
+
settings.openai = {
|
|
1161
|
+
...settings.openai,
|
|
1162
|
+
accessToken: tokens.access_token,
|
|
1163
|
+
refreshToken: tokens.refresh_token,
|
|
1164
|
+
tokenExpiresAt: tokens.expires_at,
|
|
1165
|
+
authMethod: 'oauth',
|
|
1166
|
+
// Clear API key when using OAuth
|
|
1167
|
+
apiKey: undefined,
|
|
1168
|
+
};
|
|
1169
|
+
llm_1.LLMProviderFactory.saveSettings(settings);
|
|
1170
|
+
llm_1.LLMProviderFactory.clearCache();
|
|
1171
|
+
console.log('[IPC] OpenAI OAuth successful!');
|
|
1172
|
+
if (tokens.email) {
|
|
1173
|
+
console.log('[IPC] Logged in as:', tokens.email);
|
|
1174
|
+
}
|
|
1175
|
+
return { success: true, email: tokens.email };
|
|
1176
|
+
}
|
|
1177
|
+
catch (error) {
|
|
1178
|
+
console.error('[IPC] OpenAI OAuth failed:', error.message);
|
|
1179
|
+
return { success: false, error: error.message };
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_LOGOUT, async () => {
|
|
1183
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_OPENAI_OAUTH_LOGOUT);
|
|
1184
|
+
console.log('[IPC] Logging out of OpenAI OAuth...');
|
|
1185
|
+
// Clear OAuth tokens from settings
|
|
1186
|
+
const settings = llm_1.LLMProviderFactory.loadSettings();
|
|
1187
|
+
if (settings.openai) {
|
|
1188
|
+
settings.openai = {
|
|
1189
|
+
...settings.openai,
|
|
1190
|
+
accessToken: undefined,
|
|
1191
|
+
refreshToken: undefined,
|
|
1192
|
+
tokenExpiresAt: undefined,
|
|
1193
|
+
authMethod: undefined,
|
|
1194
|
+
};
|
|
1195
|
+
llm_1.LLMProviderFactory.saveSettings(settings);
|
|
1196
|
+
}
|
|
1197
|
+
return { success: true };
|
|
1198
|
+
});
|
|
1199
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.LLM_GET_BEDROCK_MODELS, async (_, config) => {
|
|
1200
|
+
checkRateLimit(types_1.IPC_CHANNELS.LLM_GET_BEDROCK_MODELS);
|
|
1201
|
+
const models = await llm_1.LLMProviderFactory.getBedrockModels(config);
|
|
1202
|
+
// Cache the models for use in config status
|
|
1203
|
+
const cachedModels = models.map(m => ({
|
|
1204
|
+
key: m.id,
|
|
1205
|
+
displayName: m.name,
|
|
1206
|
+
description: m.description,
|
|
1207
|
+
}));
|
|
1208
|
+
llm_1.LLMProviderFactory.saveCachedModels('bedrock', cachedModels);
|
|
1209
|
+
return models;
|
|
1210
|
+
});
|
|
1211
|
+
// Search Settings handlers
|
|
1212
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SEARCH_GET_SETTINGS, async () => {
|
|
1213
|
+
return search_1.SearchProviderFactory.loadSettings();
|
|
1214
|
+
});
|
|
1215
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SEARCH_SAVE_SETTINGS, async (_, settings) => {
|
|
1216
|
+
checkRateLimit(types_1.IPC_CHANNELS.SEARCH_SAVE_SETTINGS);
|
|
1217
|
+
const validated = (0, validation_1.validateInput)(validation_1.SearchSettingsSchema, settings, 'search settings');
|
|
1218
|
+
search_1.SearchProviderFactory.saveSettings(validated);
|
|
1219
|
+
search_1.SearchProviderFactory.clearCache();
|
|
1220
|
+
return { success: true };
|
|
1221
|
+
});
|
|
1222
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SEARCH_GET_CONFIG_STATUS, async () => {
|
|
1223
|
+
return search_1.SearchProviderFactory.getConfigStatus();
|
|
1224
|
+
});
|
|
1225
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.SEARCH_TEST_PROVIDER, async (_, providerType) => {
|
|
1226
|
+
checkRateLimit(types_1.IPC_CHANNELS.SEARCH_TEST_PROVIDER);
|
|
1227
|
+
return search_1.SearchProviderFactory.testProvider(providerType);
|
|
1228
|
+
});
|
|
1229
|
+
// X/Twitter Settings handlers
|
|
1230
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.X_GET_SETTINGS, async () => {
|
|
1231
|
+
return x_manager_1.XSettingsManager.loadSettings();
|
|
1232
|
+
});
|
|
1233
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.X_SAVE_SETTINGS, async (_, settings) => {
|
|
1234
|
+
checkRateLimit(types_1.IPC_CHANNELS.X_SAVE_SETTINGS);
|
|
1235
|
+
const validated = (0, validation_1.validateInput)(validation_1.XSettingsSchema, settings, 'x settings');
|
|
1236
|
+
x_manager_1.XSettingsManager.saveSettings(validated);
|
|
1237
|
+
x_manager_1.XSettingsManager.clearCache();
|
|
1238
|
+
return { success: true };
|
|
1239
|
+
});
|
|
1240
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.X_TEST_CONNECTION, async () => {
|
|
1241
|
+
checkRateLimit(types_1.IPC_CHANNELS.X_TEST_CONNECTION);
|
|
1242
|
+
const settings = x_manager_1.XSettingsManager.loadSettings();
|
|
1243
|
+
return (0, x_cli_1.testXConnection)(settings);
|
|
1244
|
+
});
|
|
1245
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.X_GET_STATUS, async () => {
|
|
1246
|
+
checkRateLimit(types_1.IPC_CHANNELS.X_GET_STATUS);
|
|
1247
|
+
const installStatus = await (0, x_cli_1.checkBirdInstalled)();
|
|
1248
|
+
if (!installStatus.installed) {
|
|
1249
|
+
return { installed: false, connected: false };
|
|
1250
|
+
}
|
|
1251
|
+
const settings = x_manager_1.XSettingsManager.loadSettings();
|
|
1252
|
+
if (!settings.enabled) {
|
|
1253
|
+
return { installed: true, connected: false };
|
|
1254
|
+
}
|
|
1255
|
+
const result = await (0, x_cli_1.testXConnection)(settings);
|
|
1256
|
+
return {
|
|
1257
|
+
installed: true,
|
|
1258
|
+
connected: result.success,
|
|
1259
|
+
username: result.username,
|
|
1260
|
+
error: result.success ? undefined : result.error,
|
|
1261
|
+
};
|
|
1262
|
+
});
|
|
1263
|
+
// Gateway / Channel handlers
|
|
1264
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_GET_CHANNELS, async () => {
|
|
1265
|
+
if (!gateway)
|
|
1266
|
+
return [];
|
|
1267
|
+
return gateway.getChannels().map(ch => ({
|
|
1268
|
+
id: ch.id,
|
|
1269
|
+
type: ch.type,
|
|
1270
|
+
name: ch.name,
|
|
1271
|
+
enabled: ch.enabled,
|
|
1272
|
+
status: ch.status,
|
|
1273
|
+
botUsername: ch.botUsername,
|
|
1274
|
+
securityMode: ch.securityConfig.mode,
|
|
1275
|
+
createdAt: ch.createdAt,
|
|
1276
|
+
config: ch.config,
|
|
1277
|
+
}));
|
|
1278
|
+
});
|
|
1279
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_ADD_CHANNEL, async (_, data) => {
|
|
1280
|
+
checkRateLimit(types_1.IPC_CHANNELS.GATEWAY_ADD_CHANNEL);
|
|
1281
|
+
if (!gateway)
|
|
1282
|
+
throw new Error('Gateway not initialized');
|
|
1283
|
+
const validated = (0, validation_1.validateInput)(validation_1.AddChannelSchema, data, 'channel');
|
|
1284
|
+
if (validated.type === 'telegram') {
|
|
1285
|
+
const channel = await gateway.addTelegramChannel(validated.name, validated.botToken, validated.securityMode || 'pairing');
|
|
1286
|
+
return {
|
|
1287
|
+
id: channel.id,
|
|
1288
|
+
type: channel.type,
|
|
1289
|
+
name: channel.name,
|
|
1290
|
+
enabled: channel.enabled,
|
|
1291
|
+
status: channel.status,
|
|
1292
|
+
securityMode: channel.securityConfig.mode,
|
|
1293
|
+
createdAt: channel.createdAt,
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
if (validated.type === 'discord') {
|
|
1297
|
+
const channel = await gateway.addDiscordChannel(validated.name, validated.botToken, validated.applicationId, validated.guildIds, validated.securityMode || 'pairing');
|
|
1298
|
+
return {
|
|
1299
|
+
id: channel.id,
|
|
1300
|
+
type: channel.type,
|
|
1301
|
+
name: channel.name,
|
|
1302
|
+
enabled: channel.enabled,
|
|
1303
|
+
status: channel.status,
|
|
1304
|
+
securityMode: channel.securityConfig.mode,
|
|
1305
|
+
createdAt: channel.createdAt,
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
if (validated.type === 'slack') {
|
|
1309
|
+
const channel = await gateway.addSlackChannel(validated.name, validated.botToken, validated.appToken, validated.signingSecret, validated.securityMode || 'pairing');
|
|
1310
|
+
return {
|
|
1311
|
+
id: channel.id,
|
|
1312
|
+
type: channel.type,
|
|
1313
|
+
name: channel.name,
|
|
1314
|
+
enabled: channel.enabled,
|
|
1315
|
+
status: channel.status,
|
|
1316
|
+
securityMode: channel.securityConfig.mode,
|
|
1317
|
+
createdAt: channel.createdAt,
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
if (validated.type === 'whatsapp') {
|
|
1321
|
+
const channel = await gateway.addWhatsAppChannel(validated.name, validated.allowedNumbers, validated.securityMode || 'pairing', validated.selfChatMode ?? true, validated.responsePrefix ?? '🤖');
|
|
1322
|
+
// Automatically enable and connect WhatsApp to start QR code generation
|
|
1323
|
+
// This is done asynchronously to not block the response
|
|
1324
|
+
gateway.enableWhatsAppWithQRForwarding(channel.id).catch((err) => {
|
|
1325
|
+
console.error('Failed to enable WhatsApp channel:', err);
|
|
1326
|
+
});
|
|
1327
|
+
return {
|
|
1328
|
+
id: channel.id,
|
|
1329
|
+
type: channel.type,
|
|
1330
|
+
name: channel.name,
|
|
1331
|
+
enabled: channel.enabled,
|
|
1332
|
+
status: 'connecting', // Indicate we're connecting
|
|
1333
|
+
securityMode: channel.securityConfig.mode,
|
|
1334
|
+
createdAt: channel.createdAt,
|
|
1335
|
+
config: channel.config,
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
if (validated.type === 'imessage') {
|
|
1339
|
+
const channel = await gateway.addImessageChannel(validated.name, validated.cliPath, validated.dbPath, validated.allowedContacts, validated.securityMode || 'pairing', validated.dmPolicy || 'pairing', validated.groupPolicy || 'allowlist');
|
|
1340
|
+
// Automatically enable and connect iMessage
|
|
1341
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1342
|
+
console.error('Failed to enable iMessage channel:', err);
|
|
1343
|
+
});
|
|
1344
|
+
return {
|
|
1345
|
+
id: channel.id,
|
|
1346
|
+
type: channel.type,
|
|
1347
|
+
name: channel.name,
|
|
1348
|
+
enabled: channel.enabled,
|
|
1349
|
+
status: 'connecting', // Indicate we're connecting
|
|
1350
|
+
securityMode: channel.securityConfig.mode,
|
|
1351
|
+
createdAt: channel.createdAt,
|
|
1352
|
+
config: channel.config,
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
if (validated.type === 'signal') {
|
|
1356
|
+
const channel = await gateway.addSignalChannel(validated.name, validated.phoneNumber, validated.dataDir, validated.securityMode || 'pairing', validated.mode || 'native', validated.trustMode || 'tofu', validated.dmPolicy || 'pairing', validated.groupPolicy || 'allowlist', validated.sendReadReceipts ?? true, validated.sendTypingIndicators ?? true);
|
|
1357
|
+
// Automatically enable and connect Signal
|
|
1358
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1359
|
+
console.error('Failed to enable Signal channel:', err);
|
|
1360
|
+
});
|
|
1361
|
+
return {
|
|
1362
|
+
id: channel.id,
|
|
1363
|
+
type: channel.type,
|
|
1364
|
+
name: channel.name,
|
|
1365
|
+
enabled: channel.enabled,
|
|
1366
|
+
status: 'connecting', // Indicate we're connecting
|
|
1367
|
+
securityMode: channel.securityConfig.mode,
|
|
1368
|
+
createdAt: channel.createdAt,
|
|
1369
|
+
config: channel.config,
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
if (validated.type === 'mattermost') {
|
|
1373
|
+
const channel = await gateway.addMattermostChannel(validated.name, validated.mattermostServerUrl, validated.mattermostToken, validated.mattermostTeamId, validated.securityMode || 'pairing');
|
|
1374
|
+
// Automatically enable and connect Mattermost
|
|
1375
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1376
|
+
console.error('Failed to enable Mattermost channel:', err);
|
|
1377
|
+
});
|
|
1378
|
+
return {
|
|
1379
|
+
id: channel.id,
|
|
1380
|
+
type: channel.type,
|
|
1381
|
+
name: channel.name,
|
|
1382
|
+
enabled: channel.enabled,
|
|
1383
|
+
status: 'connecting',
|
|
1384
|
+
securityMode: channel.securityConfig.mode,
|
|
1385
|
+
createdAt: channel.createdAt,
|
|
1386
|
+
config: channel.config,
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
if (validated.type === 'matrix') {
|
|
1390
|
+
const channel = await gateway.addMatrixChannel(validated.name, validated.matrixHomeserver, validated.matrixUserId, validated.matrixAccessToken, validated.matrixDeviceId, validated.matrixRoomIds, validated.securityMode || 'pairing');
|
|
1391
|
+
// Automatically enable and connect Matrix
|
|
1392
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1393
|
+
console.error('Failed to enable Matrix channel:', err);
|
|
1394
|
+
});
|
|
1395
|
+
return {
|
|
1396
|
+
id: channel.id,
|
|
1397
|
+
type: channel.type,
|
|
1398
|
+
name: channel.name,
|
|
1399
|
+
enabled: channel.enabled,
|
|
1400
|
+
status: 'connecting',
|
|
1401
|
+
securityMode: channel.securityConfig.mode,
|
|
1402
|
+
createdAt: channel.createdAt,
|
|
1403
|
+
config: channel.config,
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
if (validated.type === 'twitch') {
|
|
1407
|
+
const channel = await gateway.addTwitchChannel(validated.name, validated.twitchUsername, validated.twitchOauthToken, validated.twitchChannels || [], validated.twitchAllowWhispers ?? false, validated.securityMode || 'pairing');
|
|
1408
|
+
// Automatically enable and connect Twitch
|
|
1409
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1410
|
+
console.error('Failed to enable Twitch channel:', err);
|
|
1411
|
+
});
|
|
1412
|
+
return {
|
|
1413
|
+
id: channel.id,
|
|
1414
|
+
type: channel.type,
|
|
1415
|
+
name: channel.name,
|
|
1416
|
+
enabled: channel.enabled,
|
|
1417
|
+
status: 'connecting',
|
|
1418
|
+
securityMode: channel.securityConfig.mode,
|
|
1419
|
+
createdAt: channel.createdAt,
|
|
1420
|
+
config: channel.config,
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
if (validated.type === 'line') {
|
|
1424
|
+
const channel = await gateway.addLineChannel(validated.name, validated.lineChannelAccessToken, validated.lineChannelSecret, validated.lineWebhookPort ?? 3100, validated.securityMode || 'pairing');
|
|
1425
|
+
// Automatically enable and connect LINE
|
|
1426
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1427
|
+
console.error('Failed to enable LINE channel:', err);
|
|
1428
|
+
});
|
|
1429
|
+
return {
|
|
1430
|
+
id: channel.id,
|
|
1431
|
+
type: channel.type,
|
|
1432
|
+
name: channel.name,
|
|
1433
|
+
enabled: channel.enabled,
|
|
1434
|
+
status: 'connecting',
|
|
1435
|
+
securityMode: channel.securityConfig.mode,
|
|
1436
|
+
createdAt: channel.createdAt,
|
|
1437
|
+
config: channel.config,
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
if (validated.type === 'bluebubbles') {
|
|
1441
|
+
const channel = await gateway.addBlueBubblesChannel(validated.name, validated.blueBubblesServerUrl, validated.blueBubblesPassword, validated.blueBubblesWebhookPort ?? 3101, validated.blueBubblesAllowedContacts, validated.securityMode || 'pairing');
|
|
1442
|
+
// Automatically enable and connect BlueBubbles
|
|
1443
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1444
|
+
console.error('Failed to enable BlueBubbles channel:', err);
|
|
1445
|
+
});
|
|
1446
|
+
return {
|
|
1447
|
+
id: channel.id,
|
|
1448
|
+
type: channel.type,
|
|
1449
|
+
name: channel.name,
|
|
1450
|
+
enabled: channel.enabled,
|
|
1451
|
+
status: 'connecting',
|
|
1452
|
+
securityMode: channel.securityConfig.mode,
|
|
1453
|
+
createdAt: channel.createdAt,
|
|
1454
|
+
config: channel.config,
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
if (validated.type === 'email') {
|
|
1458
|
+
const channel = await gateway.addEmailChannel(validated.name, validated.emailAddress, validated.emailPassword, validated.emailImapHost, validated.emailSmtpHost, validated.emailDisplayName, validated.emailAllowedSenders, validated.emailSubjectFilter, validated.securityMode || 'pairing');
|
|
1459
|
+
// Automatically enable and connect Email
|
|
1460
|
+
gateway.enableChannel(channel.id).catch((err) => {
|
|
1461
|
+
console.error('Failed to enable Email channel:', err);
|
|
1462
|
+
});
|
|
1463
|
+
return {
|
|
1464
|
+
id: channel.id,
|
|
1465
|
+
type: channel.type,
|
|
1466
|
+
name: channel.name,
|
|
1467
|
+
enabled: channel.enabled,
|
|
1468
|
+
status: 'connecting',
|
|
1469
|
+
securityMode: channel.securityConfig.mode,
|
|
1470
|
+
createdAt: channel.createdAt,
|
|
1471
|
+
config: channel.config,
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
// TypeScript exhaustiveness check - should never reach here due to discriminated union
|
|
1475
|
+
throw new Error(`Unsupported channel type`);
|
|
1476
|
+
});
|
|
1477
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_UPDATE_CHANNEL, async (_, data) => {
|
|
1478
|
+
if (!gateway)
|
|
1479
|
+
throw new Error('Gateway not initialized');
|
|
1480
|
+
const validated = (0, validation_1.validateInput)(validation_1.UpdateChannelSchema, data, 'channel update');
|
|
1481
|
+
const channel = gateway.getChannel(validated.id);
|
|
1482
|
+
if (!channel)
|
|
1483
|
+
throw new Error('Channel not found');
|
|
1484
|
+
const updates = {};
|
|
1485
|
+
if (validated.name !== undefined)
|
|
1486
|
+
updates.name = validated.name;
|
|
1487
|
+
if (validated.securityMode !== undefined) {
|
|
1488
|
+
updates.securityConfig = { ...channel.securityConfig, mode: validated.securityMode };
|
|
1489
|
+
}
|
|
1490
|
+
if (validated.config !== undefined) {
|
|
1491
|
+
updates.config = { ...channel.config, ...validated.config };
|
|
1492
|
+
}
|
|
1493
|
+
gateway.updateChannel(validated.id, updates);
|
|
1494
|
+
});
|
|
1495
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_REMOVE_CHANNEL, async (_, id) => {
|
|
1496
|
+
if (!gateway)
|
|
1497
|
+
throw new Error('Gateway not initialized');
|
|
1498
|
+
await gateway.removeChannel(id);
|
|
1499
|
+
});
|
|
1500
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_ENABLE_CHANNEL, async (_, id) => {
|
|
1501
|
+
if (!gateway)
|
|
1502
|
+
throw new Error('Gateway not initialized');
|
|
1503
|
+
await gateway.enableChannel(id);
|
|
1504
|
+
});
|
|
1505
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_DISABLE_CHANNEL, async (_, id) => {
|
|
1506
|
+
if (!gateway)
|
|
1507
|
+
throw new Error('Gateway not initialized');
|
|
1508
|
+
await gateway.disableChannel(id);
|
|
1509
|
+
});
|
|
1510
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_TEST_CHANNEL, async (_, id) => {
|
|
1511
|
+
checkRateLimit(types_1.IPC_CHANNELS.GATEWAY_TEST_CHANNEL);
|
|
1512
|
+
if (!gateway)
|
|
1513
|
+
return { success: false, error: 'Gateway not initialized' };
|
|
1514
|
+
return gateway.testChannel(id);
|
|
1515
|
+
});
|
|
1516
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_GET_USERS, async (_, channelId) => {
|
|
1517
|
+
if (!gateway)
|
|
1518
|
+
return [];
|
|
1519
|
+
return gateway.getChannelUsers(channelId).map(u => ({
|
|
1520
|
+
id: u.id,
|
|
1521
|
+
channelId: u.channelId,
|
|
1522
|
+
channelUserId: u.channelUserId,
|
|
1523
|
+
displayName: u.displayName,
|
|
1524
|
+
username: u.username,
|
|
1525
|
+
allowed: u.allowed,
|
|
1526
|
+
lastSeenAt: u.lastSeenAt,
|
|
1527
|
+
}));
|
|
1528
|
+
});
|
|
1529
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_GRANT_ACCESS, async (_, data) => {
|
|
1530
|
+
if (!gateway)
|
|
1531
|
+
throw new Error('Gateway not initialized');
|
|
1532
|
+
const validated = (0, validation_1.validateInput)(validation_1.GrantAccessSchema, data, 'grant access');
|
|
1533
|
+
gateway.grantUserAccess(validated.channelId, validated.userId, validated.displayName);
|
|
1534
|
+
});
|
|
1535
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_REVOKE_ACCESS, async (_, data) => {
|
|
1536
|
+
if (!gateway)
|
|
1537
|
+
throw new Error('Gateway not initialized');
|
|
1538
|
+
const validated = (0, validation_1.validateInput)(validation_1.RevokeAccessSchema, data, 'revoke access');
|
|
1539
|
+
gateway.revokeUserAccess(validated.channelId, validated.userId);
|
|
1540
|
+
});
|
|
1541
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GATEWAY_GENERATE_PAIRING, async (_, data) => {
|
|
1542
|
+
if (!gateway)
|
|
1543
|
+
throw new Error('Gateway not initialized');
|
|
1544
|
+
const validated = (0, validation_1.validateInput)(validation_1.GeneratePairingSchema, data, 'generate pairing');
|
|
1545
|
+
return gateway.generatePairingCode(validated.channelId, validated.userId, validated.displayName);
|
|
1546
|
+
});
|
|
1547
|
+
// WhatsApp-specific handlers
|
|
1548
|
+
electron_1.ipcMain.handle('whatsapp:get-info', async () => {
|
|
1549
|
+
if (!gateway)
|
|
1550
|
+
return {};
|
|
1551
|
+
return gateway.getWhatsAppInfo();
|
|
1552
|
+
});
|
|
1553
|
+
electron_1.ipcMain.handle('whatsapp:logout', async () => {
|
|
1554
|
+
if (!gateway)
|
|
1555
|
+
throw new Error('Gateway not initialized');
|
|
1556
|
+
await gateway.whatsAppLogout();
|
|
1557
|
+
});
|
|
1558
|
+
// App Update handlers
|
|
1559
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APP_GET_VERSION, async () => {
|
|
1560
|
+
return updater_1.updateManager.getVersionInfo();
|
|
1561
|
+
});
|
|
1562
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APP_CHECK_UPDATES, async () => {
|
|
1563
|
+
return updater_1.updateManager.checkForUpdates();
|
|
1564
|
+
});
|
|
1565
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APP_DOWNLOAD_UPDATE, async (_, updateInfo) => {
|
|
1566
|
+
await updater_1.updateManager.downloadAndInstallUpdate(updateInfo);
|
|
1567
|
+
return { success: true };
|
|
1568
|
+
});
|
|
1569
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APP_INSTALL_UPDATE, async () => {
|
|
1570
|
+
await updater_1.updateManager.installUpdateAndRestart();
|
|
1571
|
+
return { success: true };
|
|
1572
|
+
});
|
|
1573
|
+
// Guardrail Settings handlers
|
|
1574
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GUARDRAIL_GET_SETTINGS, async () => {
|
|
1575
|
+
return guardrail_manager_1.GuardrailManager.loadSettings();
|
|
1576
|
+
});
|
|
1577
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GUARDRAIL_SAVE_SETTINGS, async (_, settings) => {
|
|
1578
|
+
checkRateLimit(types_1.IPC_CHANNELS.GUARDRAIL_SAVE_SETTINGS);
|
|
1579
|
+
const validated = (0, validation_1.validateInput)(validation_1.GuardrailSettingsSchema, settings, 'guardrail settings');
|
|
1580
|
+
guardrail_manager_1.GuardrailManager.saveSettings(validated);
|
|
1581
|
+
guardrail_manager_1.GuardrailManager.clearCache();
|
|
1582
|
+
return { success: true };
|
|
1583
|
+
});
|
|
1584
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.GUARDRAIL_GET_DEFAULTS, async () => {
|
|
1585
|
+
return guardrail_manager_1.GuardrailManager.getDefaults();
|
|
1586
|
+
});
|
|
1587
|
+
// Appearance Settings handlers
|
|
1588
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APPEARANCE_GET_SETTINGS, async () => {
|
|
1589
|
+
return appearance_manager_1.AppearanceManager.loadSettings();
|
|
1590
|
+
});
|
|
1591
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.APPEARANCE_SAVE_SETTINGS, async (_, settings) => {
|
|
1592
|
+
appearance_manager_1.AppearanceManager.saveSettings(settings);
|
|
1593
|
+
return { success: true };
|
|
1594
|
+
});
|
|
1595
|
+
// Personality Settings handlers
|
|
1596
|
+
// Subscribe to PersonalityManager events to broadcast changes to UI
|
|
1597
|
+
// This handles both IPC changes and tool-based changes
|
|
1598
|
+
personality_manager_1.PersonalityManager.onSettingsChanged((settings) => {
|
|
1599
|
+
broadcastPersonalitySettingsChanged(settings);
|
|
1600
|
+
});
|
|
1601
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_GET_SETTINGS, async () => {
|
|
1602
|
+
return personality_manager_1.PersonalityManager.loadSettings();
|
|
1603
|
+
});
|
|
1604
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_SAVE_SETTINGS, async (_, settings) => {
|
|
1605
|
+
personality_manager_1.PersonalityManager.saveSettings(settings);
|
|
1606
|
+
// Event emission is handled by PersonalityManager.saveSettings()
|
|
1607
|
+
return { success: true };
|
|
1608
|
+
});
|
|
1609
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_GET_DEFINITIONS, async () => {
|
|
1610
|
+
return personality_manager_1.PersonalityManager.getDefinitions();
|
|
1611
|
+
});
|
|
1612
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_GET_PERSONAS, async () => {
|
|
1613
|
+
return personality_manager_1.PersonalityManager.getPersonaDefinitions();
|
|
1614
|
+
});
|
|
1615
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_GET_RELATIONSHIP_STATS, async () => {
|
|
1616
|
+
return personality_manager_1.PersonalityManager.getRelationshipStats();
|
|
1617
|
+
});
|
|
1618
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_SET_ACTIVE, async (_, personalityId) => {
|
|
1619
|
+
personality_manager_1.PersonalityManager.setActivePersonality(personalityId);
|
|
1620
|
+
// Event emission is handled by PersonalityManager.saveSettings()
|
|
1621
|
+
return { success: true };
|
|
1622
|
+
});
|
|
1623
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_SET_PERSONA, async (_, personaId) => {
|
|
1624
|
+
personality_manager_1.PersonalityManager.setActivePersona(personaId);
|
|
1625
|
+
// Event emission is handled by PersonalityManager.saveSettings()
|
|
1626
|
+
return { success: true };
|
|
1627
|
+
});
|
|
1628
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.PERSONALITY_RESET, async (_, preserveRelationship) => {
|
|
1629
|
+
checkRateLimit(types_1.IPC_CHANNELS.PERSONALITY_RESET);
|
|
1630
|
+
personality_manager_1.PersonalityManager.resetToDefaults(preserveRelationship);
|
|
1631
|
+
// Event emission is handled by PersonalityManager.resetToDefaults()
|
|
1632
|
+
return { success: true };
|
|
1633
|
+
});
|
|
1634
|
+
// Agent Role / Squad handlers
|
|
1635
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_LIST, async (_, includeInactive) => {
|
|
1636
|
+
return agentRoleRepo.findAll(includeInactive ?? false);
|
|
1637
|
+
});
|
|
1638
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_GET, async (_, id) => {
|
|
1639
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'agent role ID');
|
|
1640
|
+
return agentRoleRepo.findById(validated);
|
|
1641
|
+
});
|
|
1642
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_CREATE, async (_, request) => {
|
|
1643
|
+
checkRateLimit(types_1.IPC_CHANNELS.AGENT_ROLE_CREATE);
|
|
1644
|
+
// Validate name format (lowercase, alphanumeric, hyphens)
|
|
1645
|
+
if (!/^[a-z0-9-]+$/.test(request.name)) {
|
|
1646
|
+
throw new Error('Agent role name must be lowercase alphanumeric with hyphens only');
|
|
1647
|
+
}
|
|
1648
|
+
// Check for duplicate name
|
|
1649
|
+
if (agentRoleRepo.findByName(request.name)) {
|
|
1650
|
+
throw new Error(`Agent role with name "${request.name}" already exists`);
|
|
1651
|
+
}
|
|
1652
|
+
return agentRoleRepo.create(request);
|
|
1653
|
+
});
|
|
1654
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_UPDATE, async (_, request) => {
|
|
1655
|
+
checkRateLimit(types_1.IPC_CHANNELS.AGENT_ROLE_UPDATE);
|
|
1656
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, request.id, 'agent role ID');
|
|
1657
|
+
const result = agentRoleRepo.update({ ...request, id: validated });
|
|
1658
|
+
if (!result) {
|
|
1659
|
+
throw new Error('Agent role not found');
|
|
1660
|
+
}
|
|
1661
|
+
return result;
|
|
1662
|
+
});
|
|
1663
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_DELETE, async (_, id) => {
|
|
1664
|
+
checkRateLimit(types_1.IPC_CHANNELS.AGENT_ROLE_DELETE);
|
|
1665
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'agent role ID');
|
|
1666
|
+
const success = agentRoleRepo.delete(validated);
|
|
1667
|
+
if (!success) {
|
|
1668
|
+
throw new Error('Agent role not found or cannot be deleted');
|
|
1669
|
+
}
|
|
1670
|
+
return { success: true };
|
|
1671
|
+
});
|
|
1672
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_ASSIGN_TO_TASK, async (_, taskId, agentRoleId) => {
|
|
1673
|
+
checkRateLimit(types_1.IPC_CHANNELS.AGENT_ROLE_ASSIGN_TO_TASK);
|
|
1674
|
+
const validatedTaskId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1675
|
+
if (agentRoleId !== null) {
|
|
1676
|
+
const validatedRoleId = (0, validation_1.validateInput)(validation_1.UUIDSchema, agentRoleId, 'agent role ID');
|
|
1677
|
+
const role = agentRoleRepo.findById(validatedRoleId);
|
|
1678
|
+
if (!role) {
|
|
1679
|
+
throw new Error('Agent role not found');
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
taskRepo.update(validatedTaskId, { assignedAgentRoleId: agentRoleId ?? undefined });
|
|
1683
|
+
const task = taskRepo.findById(validatedTaskId);
|
|
1684
|
+
if (task) {
|
|
1685
|
+
if (agentRoleId) {
|
|
1686
|
+
const role = agentRoleRepo.findById(agentRoleId);
|
|
1687
|
+
const activity = activityRepo.create({
|
|
1688
|
+
workspaceId: task.workspaceId,
|
|
1689
|
+
taskId: task.id,
|
|
1690
|
+
agentRoleId,
|
|
1691
|
+
actorType: 'system',
|
|
1692
|
+
activityType: 'agent_assigned',
|
|
1693
|
+
title: `Assigned to ${role?.displayName || 'agent'}`,
|
|
1694
|
+
description: task.title,
|
|
1695
|
+
});
|
|
1696
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
|
|
1697
|
+
}
|
|
1698
|
+
else {
|
|
1699
|
+
const activity = activityRepo.create({
|
|
1700
|
+
workspaceId: task.workspaceId,
|
|
1701
|
+
taskId: task.id,
|
|
1702
|
+
actorType: 'system',
|
|
1703
|
+
activityType: 'info',
|
|
1704
|
+
title: 'Task unassigned',
|
|
1705
|
+
description: task.title,
|
|
1706
|
+
});
|
|
1707
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
return { success: true };
|
|
1711
|
+
});
|
|
1712
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_GET_DEFAULTS, async () => {
|
|
1713
|
+
const { DEFAULT_AGENT_ROLES } = await Promise.resolve().then(() => __importStar(require('../../shared/types')));
|
|
1714
|
+
return DEFAULT_AGENT_ROLES;
|
|
1715
|
+
});
|
|
1716
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_SEED_DEFAULTS, async () => {
|
|
1717
|
+
checkRateLimit(types_1.IPC_CHANNELS.AGENT_ROLE_SEED_DEFAULTS);
|
|
1718
|
+
return agentRoleRepo.seedDefaults();
|
|
1719
|
+
});
|
|
1720
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.AGENT_ROLE_SYNC_DEFAULTS, async () => {
|
|
1721
|
+
checkRateLimit(types_1.IPC_CHANNELS.AGENT_ROLE_SYNC_DEFAULTS);
|
|
1722
|
+
return agentRoleRepo.syncNewDefaults();
|
|
1723
|
+
});
|
|
1724
|
+
// Activity Feed handlers
|
|
1725
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ACTIVITY_LIST, async (_, query) => {
|
|
1726
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, query.workspaceId, 'workspace ID');
|
|
1727
|
+
return activityRepo.list({ ...query, workspaceId: validated });
|
|
1728
|
+
});
|
|
1729
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ACTIVITY_CREATE, async (_, request) => {
|
|
1730
|
+
checkRateLimit(types_1.IPC_CHANNELS.ACTIVITY_CREATE);
|
|
1731
|
+
const validatedWorkspaceId = (0, validation_1.validateInput)(validation_1.UUIDSchema, request.workspaceId, 'workspace ID');
|
|
1732
|
+
const activity = activityRepo.create({ ...request, workspaceId: validatedWorkspaceId });
|
|
1733
|
+
// Emit activity event for real-time updates
|
|
1734
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
|
|
1735
|
+
return activity;
|
|
1736
|
+
});
|
|
1737
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ACTIVITY_MARK_READ, async (_, id) => {
|
|
1738
|
+
checkRateLimit(types_1.IPC_CHANNELS.ACTIVITY_MARK_READ);
|
|
1739
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'activity ID');
|
|
1740
|
+
const success = activityRepo.markRead(validated);
|
|
1741
|
+
if (success) {
|
|
1742
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'read', id: validated });
|
|
1743
|
+
}
|
|
1744
|
+
return { success };
|
|
1745
|
+
});
|
|
1746
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ACTIVITY_MARK_ALL_READ, async (_, workspaceId) => {
|
|
1747
|
+
checkRateLimit(types_1.IPC_CHANNELS.ACTIVITY_MARK_ALL_READ);
|
|
1748
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, workspaceId, 'workspace ID');
|
|
1749
|
+
const count = activityRepo.markAllRead(validated);
|
|
1750
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'all_read', workspaceId: validated });
|
|
1751
|
+
return { count };
|
|
1752
|
+
});
|
|
1753
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ACTIVITY_PIN, async (_, id) => {
|
|
1754
|
+
checkRateLimit(types_1.IPC_CHANNELS.ACTIVITY_PIN);
|
|
1755
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'activity ID');
|
|
1756
|
+
const activity = activityRepo.togglePin(validated);
|
|
1757
|
+
if (activity) {
|
|
1758
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'pinned', activity });
|
|
1759
|
+
}
|
|
1760
|
+
return activity;
|
|
1761
|
+
});
|
|
1762
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.ACTIVITY_DELETE, async (_, id) => {
|
|
1763
|
+
checkRateLimit(types_1.IPC_CHANNELS.ACTIVITY_DELETE);
|
|
1764
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'activity ID');
|
|
1765
|
+
const success = activityRepo.delete(validated);
|
|
1766
|
+
if (success) {
|
|
1767
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'deleted', id: validated });
|
|
1768
|
+
}
|
|
1769
|
+
return { success };
|
|
1770
|
+
});
|
|
1771
|
+
// @Mention handlers
|
|
1772
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MENTION_LIST, async (_, query) => {
|
|
1773
|
+
return mentionRepo.list(query);
|
|
1774
|
+
});
|
|
1775
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MENTION_CREATE, async (_, request) => {
|
|
1776
|
+
checkRateLimit(types_1.IPC_CHANNELS.MENTION_CREATE);
|
|
1777
|
+
const validatedWorkspaceId = (0, validation_1.validateInput)(validation_1.UUIDSchema, request.workspaceId, 'workspace ID');
|
|
1778
|
+
const mention = mentionRepo.create({ ...request, workspaceId: validatedWorkspaceId });
|
|
1779
|
+
// Emit mention event for real-time updates
|
|
1780
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'created', mention });
|
|
1781
|
+
// Also create an activity entry for the mention
|
|
1782
|
+
const fromAgent = request.fromAgentRoleId ? agentRoleRepo.findById(request.fromAgentRoleId) : null;
|
|
1783
|
+
const toAgent = agentRoleRepo.findById(request.toAgentRoleId);
|
|
1784
|
+
activityRepo.create({
|
|
1785
|
+
workspaceId: validatedWorkspaceId,
|
|
1786
|
+
taskId: request.taskId,
|
|
1787
|
+
agentRoleId: request.toAgentRoleId,
|
|
1788
|
+
actorType: fromAgent ? 'agent' : 'user',
|
|
1789
|
+
activityType: 'mention',
|
|
1790
|
+
title: `@${toAgent?.displayName || 'Agent'} mentioned`,
|
|
1791
|
+
description: request.context,
|
|
1792
|
+
metadata: { mentionId: mention.id, mentionType: request.mentionType },
|
|
1793
|
+
});
|
|
1794
|
+
return mention;
|
|
1795
|
+
});
|
|
1796
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MENTION_ACKNOWLEDGE, async (_, id) => {
|
|
1797
|
+
checkRateLimit(types_1.IPC_CHANNELS.MENTION_ACKNOWLEDGE);
|
|
1798
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'mention ID');
|
|
1799
|
+
const mention = mentionRepo.acknowledge(validated);
|
|
1800
|
+
if (mention) {
|
|
1801
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'acknowledged', mention });
|
|
1802
|
+
}
|
|
1803
|
+
return mention;
|
|
1804
|
+
});
|
|
1805
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MENTION_COMPLETE, async (_, id) => {
|
|
1806
|
+
checkRateLimit(types_1.IPC_CHANNELS.MENTION_COMPLETE);
|
|
1807
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'mention ID');
|
|
1808
|
+
const mention = mentionRepo.complete(validated);
|
|
1809
|
+
if (mention) {
|
|
1810
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'completed', mention });
|
|
1811
|
+
}
|
|
1812
|
+
return mention;
|
|
1813
|
+
});
|
|
1814
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MENTION_DISMISS, async (_, id) => {
|
|
1815
|
+
checkRateLimit(types_1.IPC_CHANNELS.MENTION_DISMISS);
|
|
1816
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'mention ID');
|
|
1817
|
+
const mention = mentionRepo.dismiss(validated);
|
|
1818
|
+
if (mention) {
|
|
1819
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'dismissed', mention });
|
|
1820
|
+
}
|
|
1821
|
+
return mention;
|
|
1822
|
+
});
|
|
1823
|
+
// Task Board handlers
|
|
1824
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_MOVE_COLUMN, async (_, taskId, column) => {
|
|
1825
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_MOVE_COLUMN);
|
|
1826
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1827
|
+
const task = taskRepo.moveToColumn(validatedId, column);
|
|
1828
|
+
if (task) {
|
|
1829
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.TASK_BOARD_EVENT, { type: 'moved', task, column });
|
|
1830
|
+
const columnLabels = {
|
|
1831
|
+
backlog: 'Inbox',
|
|
1832
|
+
todo: 'Assigned',
|
|
1833
|
+
in_progress: 'In Progress',
|
|
1834
|
+
review: 'Review',
|
|
1835
|
+
done: 'Done',
|
|
1836
|
+
};
|
|
1837
|
+
const activity = activityRepo.create({
|
|
1838
|
+
workspaceId: task.workspaceId,
|
|
1839
|
+
taskId: task.id,
|
|
1840
|
+
agentRoleId: task.assignedAgentRoleId,
|
|
1841
|
+
actorType: 'system',
|
|
1842
|
+
activityType: 'info',
|
|
1843
|
+
title: `Moved to ${columnLabels[column] || column}`,
|
|
1844
|
+
description: task.title,
|
|
1845
|
+
});
|
|
1846
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
|
|
1847
|
+
}
|
|
1848
|
+
return task;
|
|
1849
|
+
});
|
|
1850
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_SET_PRIORITY, async (_, taskId, priority) => {
|
|
1851
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_SET_PRIORITY);
|
|
1852
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1853
|
+
const task = taskRepo.setPriority(validatedId, priority);
|
|
1854
|
+
if (task) {
|
|
1855
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.TASK_BOARD_EVENT, { type: 'priority_changed', task });
|
|
1856
|
+
}
|
|
1857
|
+
return task;
|
|
1858
|
+
});
|
|
1859
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_SET_DUE_DATE, async (_, taskId, dueDate) => {
|
|
1860
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_SET_DUE_DATE);
|
|
1861
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1862
|
+
const task = taskRepo.setDueDate(validatedId, dueDate);
|
|
1863
|
+
if (task) {
|
|
1864
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.TASK_BOARD_EVENT, { type: 'due_date_changed', task });
|
|
1865
|
+
}
|
|
1866
|
+
return task;
|
|
1867
|
+
});
|
|
1868
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_SET_ESTIMATE, async (_, taskId, minutes) => {
|
|
1869
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_SET_ESTIMATE);
|
|
1870
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1871
|
+
const task = taskRepo.setEstimate(validatedId, minutes);
|
|
1872
|
+
if (task) {
|
|
1873
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.TASK_BOARD_EVENT, { type: 'estimate_changed', task });
|
|
1874
|
+
}
|
|
1875
|
+
return task;
|
|
1876
|
+
});
|
|
1877
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_ADD_LABEL, async (_, taskId, labelId) => {
|
|
1878
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_ADD_LABEL);
|
|
1879
|
+
const validatedTaskId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1880
|
+
const validatedLabelId = (0, validation_1.validateInput)(validation_1.UUIDSchema, labelId, 'label ID');
|
|
1881
|
+
const task = taskRepo.addLabel(validatedTaskId, validatedLabelId);
|
|
1882
|
+
if (task) {
|
|
1883
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.TASK_BOARD_EVENT, { type: 'label_added', task, labelId: validatedLabelId });
|
|
1884
|
+
}
|
|
1885
|
+
return task;
|
|
1886
|
+
});
|
|
1887
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_REMOVE_LABEL, async (_, taskId, labelId) => {
|
|
1888
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_REMOVE_LABEL);
|
|
1889
|
+
const validatedTaskId = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1890
|
+
const validatedLabelId = (0, validation_1.validateInput)(validation_1.UUIDSchema, labelId, 'label ID');
|
|
1891
|
+
const task = taskRepo.removeLabel(validatedTaskId, validatedLabelId);
|
|
1892
|
+
if (task) {
|
|
1893
|
+
getMainWindow()?.webContents.send(types_1.IPC_CHANNELS.TASK_BOARD_EVENT, { type: 'label_removed', task, labelId: validatedLabelId });
|
|
1894
|
+
}
|
|
1895
|
+
return task;
|
|
1896
|
+
});
|
|
1897
|
+
// Task Label handlers
|
|
1898
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_LABEL_LIST, async (_, workspaceId) => {
|
|
1899
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, workspaceId, 'workspace ID');
|
|
1900
|
+
return taskLabelRepo.list({ workspaceId: validated });
|
|
1901
|
+
});
|
|
1902
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_LABEL_CREATE, async (_, request) => {
|
|
1903
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_LABEL_CREATE);
|
|
1904
|
+
const validatedWorkspaceId = (0, validation_1.validateInput)(validation_1.UUIDSchema, request.workspaceId, 'workspace ID');
|
|
1905
|
+
return taskLabelRepo.create({ ...request, workspaceId: validatedWorkspaceId });
|
|
1906
|
+
});
|
|
1907
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_LABEL_UPDATE, async (_, id, request) => {
|
|
1908
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_LABEL_UPDATE);
|
|
1909
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'label ID');
|
|
1910
|
+
return taskLabelRepo.update(validated, request);
|
|
1911
|
+
});
|
|
1912
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TASK_LABEL_DELETE, async (_, id) => {
|
|
1913
|
+
checkRateLimit(types_1.IPC_CHANNELS.TASK_LABEL_DELETE);
|
|
1914
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'label ID');
|
|
1915
|
+
return { success: taskLabelRepo.delete(validated) };
|
|
1916
|
+
});
|
|
1917
|
+
// Working State handlers
|
|
1918
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_GET, async (_, id) => {
|
|
1919
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'working state ID');
|
|
1920
|
+
return workingStateRepo.findById(validated);
|
|
1921
|
+
});
|
|
1922
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_GET_CURRENT, async (_, query) => {
|
|
1923
|
+
const validatedAgentRoleId = (0, validation_1.validateInput)(validation_1.UUIDSchema, query.agentRoleId, 'agent role ID');
|
|
1924
|
+
const validatedWorkspaceId = (0, validation_1.validateInput)(validation_1.UUIDSchema, query.workspaceId, 'workspace ID');
|
|
1925
|
+
return workingStateRepo.getCurrent({
|
|
1926
|
+
agentRoleId: validatedAgentRoleId,
|
|
1927
|
+
workspaceId: validatedWorkspaceId,
|
|
1928
|
+
taskId: query.taskId,
|
|
1929
|
+
stateType: query.stateType,
|
|
1930
|
+
});
|
|
1931
|
+
});
|
|
1932
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_UPDATE, async (_, request) => {
|
|
1933
|
+
checkRateLimit(types_1.IPC_CHANNELS.WORKING_STATE_UPDATE);
|
|
1934
|
+
const validatedAgentRoleId = (0, validation_1.validateInput)(validation_1.UUIDSchema, request.agentRoleId, 'agent role ID');
|
|
1935
|
+
const validatedWorkspaceId = (0, validation_1.validateInput)(validation_1.UUIDSchema, request.workspaceId, 'workspace ID');
|
|
1936
|
+
return workingStateRepo.update({
|
|
1937
|
+
agentRoleId: validatedAgentRoleId,
|
|
1938
|
+
workspaceId: validatedWorkspaceId,
|
|
1939
|
+
taskId: request.taskId,
|
|
1940
|
+
stateType: request.stateType,
|
|
1941
|
+
content: request.content,
|
|
1942
|
+
fileReferences: request.fileReferences,
|
|
1943
|
+
});
|
|
1944
|
+
});
|
|
1945
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_HISTORY, async (_, query) => {
|
|
1946
|
+
const validatedAgentRoleId = (0, validation_1.validateInput)(validation_1.UUIDSchema, query.agentRoleId, 'agent role ID');
|
|
1947
|
+
const validatedWorkspaceId = (0, validation_1.validateInput)(validation_1.UUIDSchema, query.workspaceId, 'workspace ID');
|
|
1948
|
+
return workingStateRepo.getHistory({
|
|
1949
|
+
agentRoleId: validatedAgentRoleId,
|
|
1950
|
+
workspaceId: validatedWorkspaceId,
|
|
1951
|
+
limit: query.limit,
|
|
1952
|
+
offset: query.offset,
|
|
1953
|
+
});
|
|
1954
|
+
});
|
|
1955
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_RESTORE, async (_, id) => {
|
|
1956
|
+
checkRateLimit(types_1.IPC_CHANNELS.WORKING_STATE_RESTORE);
|
|
1957
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'working state ID');
|
|
1958
|
+
return workingStateRepo.restore(validated);
|
|
1959
|
+
});
|
|
1960
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_DELETE, async (_, id) => {
|
|
1961
|
+
checkRateLimit(types_1.IPC_CHANNELS.WORKING_STATE_DELETE);
|
|
1962
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, id, 'working state ID');
|
|
1963
|
+
return { success: workingStateRepo.delete(validated) };
|
|
1964
|
+
});
|
|
1965
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.WORKING_STATE_LIST_FOR_TASK, async (_, taskId) => {
|
|
1966
|
+
const validated = (0, validation_1.validateInput)(validation_1.UUIDSchema, taskId, 'task ID');
|
|
1967
|
+
return workingStateRepo.listForTask(validated);
|
|
1968
|
+
});
|
|
1969
|
+
// Context Policy handlers (per-context security DM vs group)
|
|
1970
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_GET, async (_, channelId, contextType) => {
|
|
1971
|
+
return contextPolicyManager.getPolicy(channelId, contextType);
|
|
1972
|
+
});
|
|
1973
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_GET_FOR_CHAT, async (_, channelId, chatId, isGroup) => {
|
|
1974
|
+
return contextPolicyManager.getPolicyForChat(channelId, chatId, isGroup);
|
|
1975
|
+
});
|
|
1976
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_LIST, async (_, channelId) => {
|
|
1977
|
+
return contextPolicyManager.getPoliciesForChannel(channelId);
|
|
1978
|
+
});
|
|
1979
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_UPDATE, async (_, channelId, contextType, options) => {
|
|
1980
|
+
checkRateLimit(types_1.IPC_CHANNELS.CONTEXT_POLICY_UPDATE);
|
|
1981
|
+
return contextPolicyManager.updateByContext(channelId, contextType, {
|
|
1982
|
+
securityMode: options.securityMode,
|
|
1983
|
+
toolRestrictions: options.toolRestrictions,
|
|
1984
|
+
});
|
|
1985
|
+
});
|
|
1986
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_DELETE, async (_, channelId) => {
|
|
1987
|
+
checkRateLimit(types_1.IPC_CHANNELS.CONTEXT_POLICY_DELETE);
|
|
1988
|
+
return { count: contextPolicyManager.deleteByChannel(channelId) };
|
|
1989
|
+
});
|
|
1990
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_CREATE_DEFAULTS, async (_, channelId) => {
|
|
1991
|
+
checkRateLimit(types_1.IPC_CHANNELS.CONTEXT_POLICY_CREATE_DEFAULTS);
|
|
1992
|
+
contextPolicyManager.createDefaultPolicies(channelId);
|
|
1993
|
+
return { success: true };
|
|
1994
|
+
});
|
|
1995
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CONTEXT_POLICY_IS_TOOL_ALLOWED, async (_, channelId, contextType, toolName, toolGroups) => {
|
|
1996
|
+
return { allowed: contextPolicyManager.isToolAllowed(channelId, contextType, toolName, toolGroups) };
|
|
1997
|
+
});
|
|
1998
|
+
// Queue handlers
|
|
1999
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.QUEUE_GET_STATUS, async () => {
|
|
2000
|
+
return agentDaemon.getQueueStatus();
|
|
2001
|
+
});
|
|
2002
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.QUEUE_GET_SETTINGS, async () => {
|
|
2003
|
+
return agentDaemon.getQueueSettings();
|
|
2004
|
+
});
|
|
2005
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.QUEUE_SAVE_SETTINGS, async (_, settings) => {
|
|
2006
|
+
checkRateLimit(types_1.IPC_CHANNELS.QUEUE_SAVE_SETTINGS);
|
|
2007
|
+
agentDaemon.saveQueueSettings(settings);
|
|
2008
|
+
return { success: true };
|
|
2009
|
+
});
|
|
2010
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.QUEUE_CLEAR, async () => {
|
|
2011
|
+
checkRateLimit(types_1.IPC_CHANNELS.QUEUE_CLEAR);
|
|
2012
|
+
const result = await agentDaemon.clearStuckTasks();
|
|
2013
|
+
return { success: true, ...result };
|
|
2014
|
+
});
|
|
2015
|
+
// MCP handlers
|
|
2016
|
+
setupMCPHandlers();
|
|
2017
|
+
// Notification handlers
|
|
2018
|
+
setupNotificationHandlers();
|
|
2019
|
+
// Hooks (Webhooks & Gmail Pub/Sub) handlers
|
|
2020
|
+
setupHooksHandlers(agentDaemon);
|
|
2021
|
+
// Memory system handlers
|
|
2022
|
+
setupMemoryHandlers();
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Set up MCP (Model Context Protocol) IPC handlers
|
|
2026
|
+
*/
|
|
2027
|
+
function setupMCPHandlers() {
|
|
2028
|
+
// Configure rate limits for MCP channels
|
|
2029
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2030
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_CONNECT_SERVER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
2031
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_TEST_SERVER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
2032
|
+
rate_limiter_1.rateLimiter.configure(types_1.IPC_CHANNELS.MCP_REGISTRY_INSTALL, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
2033
|
+
// Initialize MCP settings manager
|
|
2034
|
+
settings_1.MCPSettingsManager.initialize();
|
|
2035
|
+
// Get settings
|
|
2036
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_GET_SETTINGS, async () => {
|
|
2037
|
+
return settings_1.MCPSettingsManager.getSettingsForDisplay();
|
|
2038
|
+
});
|
|
2039
|
+
// Save settings
|
|
2040
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_SAVE_SETTINGS, async (_, settings) => {
|
|
2041
|
+
checkRateLimit(types_1.IPC_CHANNELS.MCP_SAVE_SETTINGS);
|
|
2042
|
+
const validated = (0, validation_1.validateInput)(validation_2.MCPSettingsSchema, settings, 'MCP settings');
|
|
2043
|
+
settings_1.MCPSettingsManager.saveSettings(validated);
|
|
2044
|
+
settings_1.MCPSettingsManager.clearCache();
|
|
2045
|
+
return { success: true };
|
|
2046
|
+
});
|
|
2047
|
+
// Get all servers
|
|
2048
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_GET_SERVERS, async () => {
|
|
2049
|
+
const settings = settings_1.MCPSettingsManager.loadSettings();
|
|
2050
|
+
return settings.servers;
|
|
2051
|
+
});
|
|
2052
|
+
// Add a server
|
|
2053
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_ADD_SERVER, async (_, serverConfig) => {
|
|
2054
|
+
checkRateLimit(types_1.IPC_CHANNELS.MCP_ADD_SERVER);
|
|
2055
|
+
const validated = (0, validation_1.validateInput)(validation_2.MCPServerConfigSchema, serverConfig, 'MCP server config');
|
|
2056
|
+
const { id, ...configWithoutId } = validated;
|
|
2057
|
+
return settings_1.MCPSettingsManager.addServer(configWithoutId);
|
|
2058
|
+
});
|
|
2059
|
+
// Update a server
|
|
2060
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_UPDATE_SERVER, async (_, serverId, updates) => {
|
|
2061
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2062
|
+
const validatedUpdates = (0, validation_1.validateInput)(validation_2.MCPServerUpdateSchema, updates, 'server updates');
|
|
2063
|
+
return settings_1.MCPSettingsManager.updateServer(validatedId, validatedUpdates);
|
|
2064
|
+
});
|
|
2065
|
+
// Remove a server
|
|
2066
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REMOVE_SERVER, async (_, serverId) => {
|
|
2067
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2068
|
+
// Disconnect if connected
|
|
2069
|
+
try {
|
|
2070
|
+
await MCPClientManager_1.MCPClientManager.getInstance().disconnectServer(validatedId);
|
|
2071
|
+
}
|
|
2072
|
+
catch {
|
|
2073
|
+
// Ignore errors
|
|
2074
|
+
}
|
|
2075
|
+
return settings_1.MCPSettingsManager.removeServer(validatedId);
|
|
2076
|
+
});
|
|
2077
|
+
// Connect to a server
|
|
2078
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_CONNECT_SERVER, async (_, serverId) => {
|
|
2079
|
+
checkRateLimit(types_1.IPC_CHANNELS.MCP_CONNECT_SERVER);
|
|
2080
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2081
|
+
await MCPClientManager_1.MCPClientManager.getInstance().connectServer(validatedId);
|
|
2082
|
+
return { success: true };
|
|
2083
|
+
});
|
|
2084
|
+
// Disconnect from a server
|
|
2085
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_DISCONNECT_SERVER, async (_, serverId) => {
|
|
2086
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2087
|
+
await MCPClientManager_1.MCPClientManager.getInstance().disconnectServer(validatedId);
|
|
2088
|
+
return { success: true };
|
|
2089
|
+
});
|
|
2090
|
+
// Get status of all servers
|
|
2091
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_GET_STATUS, async () => {
|
|
2092
|
+
return MCPClientManager_1.MCPClientManager.getInstance().getStatus();
|
|
2093
|
+
});
|
|
2094
|
+
// Get tools from a specific server
|
|
2095
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_GET_SERVER_TOOLS, async (_, serverId) => {
|
|
2096
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2097
|
+
return MCPClientManager_1.MCPClientManager.getInstance().getServerTools(validatedId);
|
|
2098
|
+
});
|
|
2099
|
+
// Test server connection
|
|
2100
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_TEST_SERVER, async (_, serverId) => {
|
|
2101
|
+
checkRateLimit(types_1.IPC_CHANNELS.MCP_TEST_SERVER);
|
|
2102
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2103
|
+
return MCPClientManager_1.MCPClientManager.getInstance().testServer(validatedId);
|
|
2104
|
+
});
|
|
2105
|
+
// MCP Registry handlers
|
|
2106
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REGISTRY_FETCH, async () => {
|
|
2107
|
+
const registry = await MCPRegistryManager_1.MCPRegistryManager.fetchRegistry();
|
|
2108
|
+
const categories = await MCPRegistryManager_1.MCPRegistryManager.getCategories();
|
|
2109
|
+
const featured = registry.servers.filter(s => s.featured);
|
|
2110
|
+
return { ...registry, categories, featured };
|
|
2111
|
+
});
|
|
2112
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REGISTRY_SEARCH, async (_, options) => {
|
|
2113
|
+
const validatedOptions = (0, validation_1.validateInput)(validation_2.MCPRegistrySearchSchema, options, 'registry search options');
|
|
2114
|
+
return MCPRegistryManager_1.MCPRegistryManager.searchServers(validatedOptions);
|
|
2115
|
+
});
|
|
2116
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REGISTRY_INSTALL, async (_, entryId) => {
|
|
2117
|
+
checkRateLimit(types_1.IPC_CHANNELS.MCP_REGISTRY_INSTALL);
|
|
2118
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.StringIdSchema, entryId, 'registry entry ID');
|
|
2119
|
+
return MCPRegistryManager_1.MCPRegistryManager.installServer(validatedId);
|
|
2120
|
+
});
|
|
2121
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REGISTRY_UNINSTALL, async (_, serverId) => {
|
|
2122
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2123
|
+
// Disconnect if connected
|
|
2124
|
+
try {
|
|
2125
|
+
await MCPClientManager_1.MCPClientManager.getInstance().disconnectServer(validatedId);
|
|
2126
|
+
}
|
|
2127
|
+
catch {
|
|
2128
|
+
// Ignore errors
|
|
2129
|
+
}
|
|
2130
|
+
await MCPRegistryManager_1.MCPRegistryManager.uninstallServer(validatedId);
|
|
2131
|
+
});
|
|
2132
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REGISTRY_CHECK_UPDATES, async () => {
|
|
2133
|
+
return MCPRegistryManager_1.MCPRegistryManager.checkForUpdates();
|
|
2134
|
+
});
|
|
2135
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_REGISTRY_UPDATE_SERVER, async (_, serverId) => {
|
|
2136
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.UUIDSchema, serverId, 'server ID');
|
|
2137
|
+
return MCPRegistryManager_1.MCPRegistryManager.updateServer(validatedId);
|
|
2138
|
+
});
|
|
2139
|
+
// MCP Host handlers
|
|
2140
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_HOST_START, async () => {
|
|
2141
|
+
const hostServer = MCPHostServer_1.MCPHostServer.getInstance();
|
|
2142
|
+
// If no tool provider is set, create a minimal one that exposes MCP tools
|
|
2143
|
+
// from connected servers (useful for tool aggregation/forwarding)
|
|
2144
|
+
if (!hostServer.hasToolProvider()) {
|
|
2145
|
+
const mcpClientManager = MCPClientManager_1.MCPClientManager.getInstance();
|
|
2146
|
+
// Create a minimal tool provider that exposes MCP tools
|
|
2147
|
+
hostServer.setToolProvider({
|
|
2148
|
+
getTools() {
|
|
2149
|
+
return mcpClientManager.getAllTools();
|
|
2150
|
+
},
|
|
2151
|
+
async executeTool(name, args) {
|
|
2152
|
+
return mcpClientManager.callTool(name, args);
|
|
2153
|
+
},
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
await hostServer.startStdio();
|
|
2157
|
+
return { success: true };
|
|
2158
|
+
});
|
|
2159
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_HOST_STOP, async () => {
|
|
2160
|
+
const hostServer = MCPHostServer_1.MCPHostServer.getInstance();
|
|
2161
|
+
await hostServer.stop();
|
|
2162
|
+
return { success: true };
|
|
2163
|
+
});
|
|
2164
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MCP_HOST_GET_STATUS, async () => {
|
|
2165
|
+
const hostServer = MCPHostServer_1.MCPHostServer.getInstance();
|
|
2166
|
+
return {
|
|
2167
|
+
running: hostServer.isRunning(),
|
|
2168
|
+
toolCount: hostServer.hasToolProvider() ? MCPClientManager_1.MCPClientManager.getInstance().getAllTools().length : 0,
|
|
2169
|
+
};
|
|
2170
|
+
});
|
|
2171
|
+
// =====================
|
|
2172
|
+
// Built-in Tools Settings Handlers
|
|
2173
|
+
// =====================
|
|
2174
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BUILTIN_TOOLS_GET_SETTINGS, async () => {
|
|
2175
|
+
return builtin_settings_1.BuiltinToolsSettingsManager.loadSettings();
|
|
2176
|
+
});
|
|
2177
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BUILTIN_TOOLS_SAVE_SETTINGS, async (_, settings) => {
|
|
2178
|
+
builtin_settings_1.BuiltinToolsSettingsManager.saveSettings(settings);
|
|
2179
|
+
builtin_settings_1.BuiltinToolsSettingsManager.clearCache(); // Clear cache to force reload
|
|
2180
|
+
return { success: true };
|
|
2181
|
+
});
|
|
2182
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.BUILTIN_TOOLS_GET_CATEGORIES, async () => {
|
|
2183
|
+
return builtin_settings_1.BuiltinToolsSettingsManager.getToolsByCategory();
|
|
2184
|
+
});
|
|
2185
|
+
// =====================
|
|
2186
|
+
// Tray (Menu Bar) Handlers
|
|
2187
|
+
// =====================
|
|
2188
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TRAY_GET_SETTINGS, async () => {
|
|
2189
|
+
// Import trayManager lazily to avoid circular dependencies
|
|
2190
|
+
const { trayManager } = await Promise.resolve().then(() => __importStar(require('../tray')));
|
|
2191
|
+
return trayManager.getSettings();
|
|
2192
|
+
});
|
|
2193
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TRAY_SAVE_SETTINGS, async (_, settings) => {
|
|
2194
|
+
const { trayManager } = await Promise.resolve().then(() => __importStar(require('../tray')));
|
|
2195
|
+
trayManager.saveSettings(settings);
|
|
2196
|
+
return { success: true };
|
|
2197
|
+
});
|
|
2198
|
+
// =====================
|
|
2199
|
+
// Cron (Scheduled Tasks) Handlers
|
|
2200
|
+
// =====================
|
|
2201
|
+
setupCronHandlers();
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Set up Cron (Scheduled Tasks) IPC handlers
|
|
2205
|
+
*/
|
|
2206
|
+
function setupCronHandlers() {
|
|
2207
|
+
const { getCronService } = require('../cron');
|
|
2208
|
+
// Get service status
|
|
2209
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_GET_STATUS, async () => {
|
|
2210
|
+
const service = getCronService();
|
|
2211
|
+
if (!service) {
|
|
2212
|
+
return {
|
|
2213
|
+
enabled: false,
|
|
2214
|
+
storePath: '',
|
|
2215
|
+
jobCount: 0,
|
|
2216
|
+
enabledJobCount: 0,
|
|
2217
|
+
nextWakeAtMs: null,
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
return service.status();
|
|
2221
|
+
});
|
|
2222
|
+
// List all jobs
|
|
2223
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_LIST_JOBS, async (_, opts) => {
|
|
2224
|
+
const service = getCronService();
|
|
2225
|
+
if (!service)
|
|
2226
|
+
return [];
|
|
2227
|
+
return service.list(opts);
|
|
2228
|
+
});
|
|
2229
|
+
// Get a single job
|
|
2230
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_GET_JOB, async (_, id) => {
|
|
2231
|
+
const service = getCronService();
|
|
2232
|
+
if (!service)
|
|
2233
|
+
return null;
|
|
2234
|
+
return service.get(id);
|
|
2235
|
+
});
|
|
2236
|
+
// Add a new job
|
|
2237
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_ADD_JOB, async (_, jobData) => {
|
|
2238
|
+
const service = getCronService();
|
|
2239
|
+
if (!service) {
|
|
2240
|
+
return { ok: false, error: 'Cron service not initialized' };
|
|
2241
|
+
}
|
|
2242
|
+
return service.add(jobData);
|
|
2243
|
+
});
|
|
2244
|
+
// Update an existing job
|
|
2245
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_UPDATE_JOB, async (_, id, patch) => {
|
|
2246
|
+
const service = getCronService();
|
|
2247
|
+
if (!service) {
|
|
2248
|
+
return { ok: false, error: 'Cron service not initialized' };
|
|
2249
|
+
}
|
|
2250
|
+
return service.update(id, patch);
|
|
2251
|
+
});
|
|
2252
|
+
// Remove a job
|
|
2253
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_REMOVE_JOB, async (_, id) => {
|
|
2254
|
+
const service = getCronService();
|
|
2255
|
+
if (!service) {
|
|
2256
|
+
return { ok: false, removed: false, error: 'Cron service not initialized' };
|
|
2257
|
+
}
|
|
2258
|
+
return service.remove(id);
|
|
2259
|
+
});
|
|
2260
|
+
// Run a job immediately
|
|
2261
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.CRON_RUN_JOB, async (_, id, mode) => {
|
|
2262
|
+
const service = getCronService();
|
|
2263
|
+
if (!service) {
|
|
2264
|
+
return { ok: false, error: 'Cron service not initialized' };
|
|
2265
|
+
}
|
|
2266
|
+
return service.run(id, mode);
|
|
2267
|
+
});
|
|
2268
|
+
// Get run history for a job
|
|
2269
|
+
electron_1.ipcMain.handle('cron:getRunHistory', async (_, id) => {
|
|
2270
|
+
const service = getCronService();
|
|
2271
|
+
if (!service)
|
|
2272
|
+
return null;
|
|
2273
|
+
return service.getRunHistory(id);
|
|
2274
|
+
});
|
|
2275
|
+
// Clear run history for a job
|
|
2276
|
+
electron_1.ipcMain.handle('cron:clearRunHistory', async (_, id) => {
|
|
2277
|
+
const service = getCronService();
|
|
2278
|
+
if (!service)
|
|
2279
|
+
return false;
|
|
2280
|
+
return service.clearRunHistory(id);
|
|
2281
|
+
});
|
|
2282
|
+
// Get webhook status
|
|
2283
|
+
electron_1.ipcMain.handle('cron:getWebhookStatus', async () => {
|
|
2284
|
+
const service = getCronService();
|
|
2285
|
+
if (!service)
|
|
2286
|
+
return { enabled: false };
|
|
2287
|
+
const status = await service.status();
|
|
2288
|
+
return status.webhook ?? { enabled: false };
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Set up Notification IPC handlers
|
|
2293
|
+
*/
|
|
2294
|
+
function setupNotificationHandlers() {
|
|
2295
|
+
// Initialize notification service with event forwarding to main window
|
|
2296
|
+
notificationService = new notifications_1.NotificationService({
|
|
2297
|
+
onEvent: (event) => {
|
|
2298
|
+
// Forward notification events to renderer
|
|
2299
|
+
// We need to import BrowserWindow from electron to send to all windows
|
|
2300
|
+
const { BrowserWindow } = require('electron');
|
|
2301
|
+
const windows = BrowserWindow.getAllWindows();
|
|
2302
|
+
for (const win of windows) {
|
|
2303
|
+
if (win.webContents) {
|
|
2304
|
+
win.webContents.send(types_1.IPC_CHANNELS.NOTIFICATION_EVENT, event);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
},
|
|
2308
|
+
});
|
|
2309
|
+
console.log('[Notifications] Service initialized');
|
|
2310
|
+
// List all notifications
|
|
2311
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTIFICATION_LIST, async () => {
|
|
2312
|
+
if (!notificationService)
|
|
2313
|
+
return [];
|
|
2314
|
+
return notificationService.list();
|
|
2315
|
+
});
|
|
2316
|
+
// Get unread count
|
|
2317
|
+
electron_1.ipcMain.handle('notification:unreadCount', async () => {
|
|
2318
|
+
if (!notificationService)
|
|
2319
|
+
return 0;
|
|
2320
|
+
return notificationService.getUnreadCount();
|
|
2321
|
+
});
|
|
2322
|
+
// Mark notification as read
|
|
2323
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTIFICATION_MARK_READ, async (_, id) => {
|
|
2324
|
+
if (!notificationService)
|
|
2325
|
+
return null;
|
|
2326
|
+
return notificationService.markRead(id);
|
|
2327
|
+
});
|
|
2328
|
+
// Mark all notifications as read
|
|
2329
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTIFICATION_MARK_ALL_READ, async () => {
|
|
2330
|
+
if (!notificationService)
|
|
2331
|
+
return;
|
|
2332
|
+
await notificationService.markAllRead();
|
|
2333
|
+
});
|
|
2334
|
+
// Delete a notification
|
|
2335
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTIFICATION_DELETE, async (_, id) => {
|
|
2336
|
+
if (!notificationService)
|
|
2337
|
+
return false;
|
|
2338
|
+
return notificationService.delete(id);
|
|
2339
|
+
});
|
|
2340
|
+
// Delete all notifications
|
|
2341
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTIFICATION_DELETE_ALL, async () => {
|
|
2342
|
+
if (!notificationService)
|
|
2343
|
+
return;
|
|
2344
|
+
await notificationService.deleteAll();
|
|
2345
|
+
});
|
|
2346
|
+
// Add a notification (internal use, for programmatic notifications)
|
|
2347
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.NOTIFICATION_ADD, async (_, data) => {
|
|
2348
|
+
if (!notificationService)
|
|
2349
|
+
return null;
|
|
2350
|
+
return notificationService.add(data);
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
// Global hooks server instance
|
|
2354
|
+
let hooksServer = null;
|
|
2355
|
+
let hooksServerStarting = false; // Lock to prevent concurrent server creation
|
|
2356
|
+
/**
|
|
2357
|
+
* Get the hooks server instance
|
|
2358
|
+
*/
|
|
2359
|
+
function getHooksServer() {
|
|
2360
|
+
return hooksServer;
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Set up Hooks (Webhooks & Gmail Pub/Sub) IPC handlers
|
|
2364
|
+
*/
|
|
2365
|
+
function setupHooksHandlers(agentDaemon) {
|
|
2366
|
+
// Initialize settings manager
|
|
2367
|
+
hooks_1.HooksSettingsManager.initialize();
|
|
2368
|
+
// Get hooks settings
|
|
2369
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_GET_SETTINGS, async () => {
|
|
2370
|
+
const settings = hooks_1.HooksSettingsManager.getSettingsForDisplay();
|
|
2371
|
+
return {
|
|
2372
|
+
enabled: settings.enabled,
|
|
2373
|
+
token: settings.token,
|
|
2374
|
+
path: settings.path,
|
|
2375
|
+
maxBodyBytes: settings.maxBodyBytes,
|
|
2376
|
+
port: hooks_1.DEFAULT_HOOKS_PORT,
|
|
2377
|
+
host: '127.0.0.1',
|
|
2378
|
+
presets: settings.presets,
|
|
2379
|
+
mappings: settings.mappings,
|
|
2380
|
+
gmail: settings.gmail,
|
|
2381
|
+
};
|
|
2382
|
+
});
|
|
2383
|
+
// Save hooks settings
|
|
2384
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_SAVE_SETTINGS, async (_, data) => {
|
|
2385
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2386
|
+
const currentSettings = hooks_1.HooksSettingsManager.loadSettings();
|
|
2387
|
+
const updated = hooks_1.HooksSettingsManager.updateConfig({
|
|
2388
|
+
...currentSettings,
|
|
2389
|
+
enabled: data.enabled ?? currentSettings.enabled,
|
|
2390
|
+
token: data.token ?? currentSettings.token,
|
|
2391
|
+
path: data.path ?? currentSettings.path,
|
|
2392
|
+
maxBodyBytes: data.maxBodyBytes ?? currentSettings.maxBodyBytes,
|
|
2393
|
+
presets: data.presets ?? currentSettings.presets,
|
|
2394
|
+
mappings: data.mappings ?? currentSettings.mappings,
|
|
2395
|
+
gmail: data.gmail ?? currentSettings.gmail,
|
|
2396
|
+
});
|
|
2397
|
+
// Restart hooks server if needed
|
|
2398
|
+
if (hooksServer && updated.enabled) {
|
|
2399
|
+
hooksServer.setHooksConfig(updated);
|
|
2400
|
+
}
|
|
2401
|
+
return {
|
|
2402
|
+
enabled: updated.enabled,
|
|
2403
|
+
token: updated.token ? '***configured***' : '',
|
|
2404
|
+
path: updated.path,
|
|
2405
|
+
maxBodyBytes: updated.maxBodyBytes,
|
|
2406
|
+
port: hooks_1.DEFAULT_HOOKS_PORT,
|
|
2407
|
+
host: '127.0.0.1',
|
|
2408
|
+
presets: updated.presets,
|
|
2409
|
+
mappings: updated.mappings,
|
|
2410
|
+
gmail: updated.gmail,
|
|
2411
|
+
};
|
|
2412
|
+
});
|
|
2413
|
+
// Enable hooks
|
|
2414
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_ENABLE, async () => {
|
|
2415
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_ENABLE, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2416
|
+
// Prevent concurrent enable attempts
|
|
2417
|
+
if (hooksServerStarting) {
|
|
2418
|
+
throw new Error('Hooks server is already starting. Please wait.');
|
|
2419
|
+
}
|
|
2420
|
+
const settings = hooks_1.HooksSettingsManager.enableHooks();
|
|
2421
|
+
// Start the hooks server if not running
|
|
2422
|
+
if (!hooksServer) {
|
|
2423
|
+
hooksServerStarting = true;
|
|
2424
|
+
const server = new hooks_1.HooksServer({
|
|
2425
|
+
port: hooks_1.DEFAULT_HOOKS_PORT,
|
|
2426
|
+
host: '127.0.0.1',
|
|
2427
|
+
enabled: true,
|
|
2428
|
+
});
|
|
2429
|
+
server.setHooksConfig(settings);
|
|
2430
|
+
// Set up handlers for hook actions
|
|
2431
|
+
server.setHandlers({
|
|
2432
|
+
onWake: async (action) => {
|
|
2433
|
+
console.log('[Hooks] Wake action:', action);
|
|
2434
|
+
// For now, just log. In the future, this could trigger a heartbeat
|
|
2435
|
+
},
|
|
2436
|
+
onAgent: async (action) => {
|
|
2437
|
+
console.log('[Hooks] Agent action:', action.message.substring(0, 100));
|
|
2438
|
+
// Create a task for the agent action
|
|
2439
|
+
const task = await agentDaemon.createTask({
|
|
2440
|
+
title: action.name || 'Webhook Task',
|
|
2441
|
+
prompt: action.message,
|
|
2442
|
+
workspaceId: action.workspaceId || types_1.TEMP_WORKSPACE_ID,
|
|
2443
|
+
});
|
|
2444
|
+
return { taskId: task.id };
|
|
2445
|
+
},
|
|
2446
|
+
onEvent: (event) => {
|
|
2447
|
+
console.log('[Hooks] Server event:', event.action);
|
|
2448
|
+
// Forward events to renderer (with error handling for destroyed windows)
|
|
2449
|
+
const windows = electron_1.BrowserWindow.getAllWindows();
|
|
2450
|
+
for (const win of windows) {
|
|
2451
|
+
try {
|
|
2452
|
+
if (win.webContents && !win.isDestroyed()) {
|
|
2453
|
+
win.webContents.send(types_1.IPC_CHANNELS.HOOKS_EVENT, event);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
catch (err) {
|
|
2457
|
+
// Window may have been destroyed between check and send
|
|
2458
|
+
console.warn('[Hooks] Failed to send event to window:', err);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
},
|
|
2462
|
+
});
|
|
2463
|
+
try {
|
|
2464
|
+
await server.start();
|
|
2465
|
+
hooksServer = server;
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
console.error('[Hooks] Failed to start hooks server:', err);
|
|
2469
|
+
throw new Error(`Failed to start hooks server: ${err instanceof Error ? err.message : String(err)}`);
|
|
2470
|
+
}
|
|
2471
|
+
finally {
|
|
2472
|
+
hooksServerStarting = false;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
// Start Gmail watcher if configured (capture result for response)
|
|
2476
|
+
let gmailWatcherError;
|
|
2477
|
+
if (settings.gmail?.account) {
|
|
2478
|
+
try {
|
|
2479
|
+
const result = await (0, hooks_1.startGmailWatcher)(settings);
|
|
2480
|
+
if (!result.started) {
|
|
2481
|
+
gmailWatcherError = result.reason;
|
|
2482
|
+
console.warn('[Hooks] Gmail watcher not started:', result.reason);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
catch (err) {
|
|
2486
|
+
gmailWatcherError = err instanceof Error ? err.message : String(err);
|
|
2487
|
+
console.error('[Hooks] Failed to start Gmail watcher:', err);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
return { enabled: true, gmailWatcherError };
|
|
2491
|
+
});
|
|
2492
|
+
// Disable hooks
|
|
2493
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_DISABLE, async () => {
|
|
2494
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_DISABLE, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2495
|
+
hooks_1.HooksSettingsManager.disableHooks();
|
|
2496
|
+
// Stop the hooks server
|
|
2497
|
+
if (hooksServer) {
|
|
2498
|
+
await hooksServer.stop();
|
|
2499
|
+
hooksServer = null;
|
|
2500
|
+
}
|
|
2501
|
+
// Stop Gmail watcher
|
|
2502
|
+
await (0, hooks_1.stopGmailWatcher)();
|
|
2503
|
+
return { enabled: false };
|
|
2504
|
+
});
|
|
2505
|
+
// Regenerate hook token
|
|
2506
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_REGENERATE_TOKEN, async () => {
|
|
2507
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_REGENERATE_TOKEN, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2508
|
+
const newToken = hooks_1.HooksSettingsManager.regenerateToken();
|
|
2509
|
+
// Update the running server with new token
|
|
2510
|
+
if (hooksServer) {
|
|
2511
|
+
const settings = hooks_1.HooksSettingsManager.loadSettings();
|
|
2512
|
+
hooksServer.setHooksConfig(settings);
|
|
2513
|
+
}
|
|
2514
|
+
return { token: newToken };
|
|
2515
|
+
});
|
|
2516
|
+
// Get hooks status
|
|
2517
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_GET_STATUS, async () => {
|
|
2518
|
+
const settings = hooks_1.HooksSettingsManager.loadSettings();
|
|
2519
|
+
const gogAvailable = await (0, hooks_1.isGogAvailable)();
|
|
2520
|
+
return {
|
|
2521
|
+
enabled: settings.enabled,
|
|
2522
|
+
serverRunning: hooksServer?.isRunning() ?? false,
|
|
2523
|
+
serverAddress: hooksServer?.getAddress() ?? undefined,
|
|
2524
|
+
gmailWatcherRunning: (0, hooks_1.isGmailWatcherRunning)(),
|
|
2525
|
+
gmailAccount: settings.gmail?.account,
|
|
2526
|
+
gogAvailable,
|
|
2527
|
+
};
|
|
2528
|
+
});
|
|
2529
|
+
// Add a hook mapping
|
|
2530
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_ADD_MAPPING, async (_, mapping) => {
|
|
2531
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_ADD_MAPPING, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2532
|
+
// Validate the mapping input
|
|
2533
|
+
const validated = (0, validation_1.validateInput)(validation_2.HookMappingSchema, mapping, 'hook mapping');
|
|
2534
|
+
const settings = hooks_1.HooksSettingsManager.addMapping(validated);
|
|
2535
|
+
// Update the server config if running
|
|
2536
|
+
if (hooksServer) {
|
|
2537
|
+
hooksServer.setHooksConfig(settings);
|
|
2538
|
+
}
|
|
2539
|
+
return { ok: true };
|
|
2540
|
+
});
|
|
2541
|
+
// Remove a hook mapping
|
|
2542
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_REMOVE_MAPPING, async (_, id) => {
|
|
2543
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_REMOVE_MAPPING, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2544
|
+
// Validate the mapping ID
|
|
2545
|
+
const validatedId = (0, validation_1.validateInput)(validation_1.StringIdSchema, id, 'mapping ID');
|
|
2546
|
+
const settings = hooks_1.HooksSettingsManager.removeMapping(validatedId);
|
|
2547
|
+
// Update the server config if running
|
|
2548
|
+
if (hooksServer) {
|
|
2549
|
+
hooksServer.setHooksConfig(settings);
|
|
2550
|
+
}
|
|
2551
|
+
return { ok: true };
|
|
2552
|
+
});
|
|
2553
|
+
// Configure Gmail hooks
|
|
2554
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_CONFIGURE_GMAIL, async (_, config) => {
|
|
2555
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_CONFIGURE_GMAIL, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2556
|
+
// Generate push token if not provided
|
|
2557
|
+
if (!config.pushToken) {
|
|
2558
|
+
config.pushToken = (0, hooks_1.generateHookToken)();
|
|
2559
|
+
}
|
|
2560
|
+
const settings = hooks_1.HooksSettingsManager.configureGmail(config);
|
|
2561
|
+
// Update the server config if running
|
|
2562
|
+
if (hooksServer) {
|
|
2563
|
+
hooksServer.setHooksConfig(settings);
|
|
2564
|
+
}
|
|
2565
|
+
return {
|
|
2566
|
+
ok: true,
|
|
2567
|
+
gmail: hooks_1.HooksSettingsManager.getGmailConfig(),
|
|
2568
|
+
};
|
|
2569
|
+
});
|
|
2570
|
+
// Get Gmail watcher status
|
|
2571
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_GET_GMAIL_STATUS, async () => {
|
|
2572
|
+
const settings = hooks_1.HooksSettingsManager.loadSettings();
|
|
2573
|
+
const gogAvailable = await (0, hooks_1.isGogAvailable)();
|
|
2574
|
+
return {
|
|
2575
|
+
configured: hooks_1.HooksSettingsManager.isGmailConfigured(),
|
|
2576
|
+
running: (0, hooks_1.isGmailWatcherRunning)(),
|
|
2577
|
+
account: settings.gmail?.account,
|
|
2578
|
+
topic: settings.gmail?.topic,
|
|
2579
|
+
gogAvailable,
|
|
2580
|
+
};
|
|
2581
|
+
});
|
|
2582
|
+
// Start Gmail watcher manually
|
|
2583
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_START_GMAIL_WATCHER, async () => {
|
|
2584
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_START_GMAIL_WATCHER, rate_limiter_1.RATE_LIMIT_CONFIGS.expensive);
|
|
2585
|
+
const settings = hooks_1.HooksSettingsManager.loadSettings();
|
|
2586
|
+
if (!settings.enabled) {
|
|
2587
|
+
return { ok: false, error: 'Hooks must be enabled first' };
|
|
2588
|
+
}
|
|
2589
|
+
if (!hooks_1.HooksSettingsManager.isGmailConfigured()) {
|
|
2590
|
+
return { ok: false, error: 'Gmail hooks not configured' };
|
|
2591
|
+
}
|
|
2592
|
+
const result = await (0, hooks_1.startGmailWatcher)(settings);
|
|
2593
|
+
return { ok: result.started, error: result.reason };
|
|
2594
|
+
});
|
|
2595
|
+
// Stop Gmail watcher manually
|
|
2596
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.HOOKS_STOP_GMAIL_WATCHER, async () => {
|
|
2597
|
+
checkRateLimit(types_1.IPC_CHANNELS.HOOKS_STOP_GMAIL_WATCHER, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2598
|
+
await (0, hooks_1.stopGmailWatcher)();
|
|
2599
|
+
return { ok: true };
|
|
2600
|
+
});
|
|
2601
|
+
console.log('[Hooks] IPC handlers initialized');
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Broadcast personality settings changed event to all renderer windows.
|
|
2605
|
+
* This allows the UI to stay in sync when settings are changed via tools.
|
|
2606
|
+
*/
|
|
2607
|
+
function broadcastPersonalitySettingsChanged(settings) {
|
|
2608
|
+
try {
|
|
2609
|
+
const windows = electron_1.BrowserWindow.getAllWindows();
|
|
2610
|
+
for (const win of windows) {
|
|
2611
|
+
try {
|
|
2612
|
+
if (win.webContents && !win.isDestroyed()) {
|
|
2613
|
+
win.webContents.send(types_1.IPC_CHANNELS.PERSONALITY_SETTINGS_CHANGED, settings);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
catch (err) {
|
|
2617
|
+
// Window may have been destroyed between check and send
|
|
2618
|
+
console.warn('[Personality] Failed to send settings changed event to window:', err);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
catch (err) {
|
|
2623
|
+
console.error('[Personality] Failed to broadcast settings changed:', err);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Set up Memory System IPC handlers
|
|
2628
|
+
*/
|
|
2629
|
+
function setupMemoryHandlers() {
|
|
2630
|
+
// Get memory settings for a workspace
|
|
2631
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_GET_SETTINGS, async (_, workspaceId) => {
|
|
2632
|
+
try {
|
|
2633
|
+
return MemoryService_1.MemoryService.getSettings(workspaceId);
|
|
2634
|
+
}
|
|
2635
|
+
catch (error) {
|
|
2636
|
+
console.error('[Memory] Failed to get settings:', error);
|
|
2637
|
+
// Return default settings if service not initialized
|
|
2638
|
+
return {
|
|
2639
|
+
workspaceId,
|
|
2640
|
+
enabled: true,
|
|
2641
|
+
autoCapture: true,
|
|
2642
|
+
compressionEnabled: true,
|
|
2643
|
+
retentionDays: 90,
|
|
2644
|
+
maxStorageMb: 100,
|
|
2645
|
+
privacyMode: 'normal',
|
|
2646
|
+
excludedPatterns: [],
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2650
|
+
// Save memory settings for a workspace
|
|
2651
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_SAVE_SETTINGS, async (_, data) => {
|
|
2652
|
+
checkRateLimit(types_1.IPC_CHANNELS.MEMORY_SAVE_SETTINGS, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2653
|
+
try {
|
|
2654
|
+
MemoryService_1.MemoryService.updateSettings(data.workspaceId, data.settings);
|
|
2655
|
+
return { success: true };
|
|
2656
|
+
}
|
|
2657
|
+
catch (error) {
|
|
2658
|
+
console.error('[Memory] Failed to save settings:', error);
|
|
2659
|
+
throw error;
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
// Search memories
|
|
2663
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_SEARCH, async (_, data) => {
|
|
2664
|
+
try {
|
|
2665
|
+
return MemoryService_1.MemoryService.search(data.workspaceId, data.query, data.limit);
|
|
2666
|
+
}
|
|
2667
|
+
catch (error) {
|
|
2668
|
+
console.error('[Memory] Failed to search:', error);
|
|
2669
|
+
return [];
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
// Get timeline context (Layer 2)
|
|
2673
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_GET_TIMELINE, async (_, data) => {
|
|
2674
|
+
try {
|
|
2675
|
+
return MemoryService_1.MemoryService.getTimelineContext(data.memoryId, data.windowSize);
|
|
2676
|
+
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
console.error('[Memory] Failed to get timeline:', error);
|
|
2679
|
+
return [];
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
// Get full details (Layer 3)
|
|
2683
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_GET_DETAILS, async (_, ids) => {
|
|
2684
|
+
try {
|
|
2685
|
+
return MemoryService_1.MemoryService.getFullDetails(ids);
|
|
2686
|
+
}
|
|
2687
|
+
catch (error) {
|
|
2688
|
+
console.error('[Memory] Failed to get details:', error);
|
|
2689
|
+
return [];
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
// Get recent memories
|
|
2693
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_GET_RECENT, async (_, data) => {
|
|
2694
|
+
try {
|
|
2695
|
+
return MemoryService_1.MemoryService.getRecent(data.workspaceId, data.limit);
|
|
2696
|
+
}
|
|
2697
|
+
catch (error) {
|
|
2698
|
+
console.error('[Memory] Failed to get recent:', error);
|
|
2699
|
+
return [];
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
// Get memory statistics
|
|
2703
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_GET_STATS, async (_, workspaceId) => {
|
|
2704
|
+
try {
|
|
2705
|
+
return MemoryService_1.MemoryService.getStats(workspaceId);
|
|
2706
|
+
}
|
|
2707
|
+
catch (error) {
|
|
2708
|
+
console.error('[Memory] Failed to get stats:', error);
|
|
2709
|
+
return { count: 0, totalTokens: 0, compressedCount: 0, compressionRatio: 0 };
|
|
2710
|
+
}
|
|
2711
|
+
});
|
|
2712
|
+
// Clear all memories for a workspace
|
|
2713
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MEMORY_CLEAR, async (_, workspaceId) => {
|
|
2714
|
+
checkRateLimit(types_1.IPC_CHANNELS.MEMORY_CLEAR, rate_limiter_1.RATE_LIMIT_CONFIGS.limited);
|
|
2715
|
+
try {
|
|
2716
|
+
MemoryService_1.MemoryService.clearWorkspace(workspaceId);
|
|
2717
|
+
return { success: true };
|
|
2718
|
+
}
|
|
2719
|
+
catch (error) {
|
|
2720
|
+
console.error('[Memory] Failed to clear:', error);
|
|
2721
|
+
throw error;
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
console.log('[Memory] Handlers initialized');
|
|
2725
|
+
// === Migration Status Handlers ===
|
|
2726
|
+
// These handlers help show one-time notifications after app migration (cowork-oss → cowork-os)
|
|
2727
|
+
const userDataPath = electron_1.app.getPath('userData');
|
|
2728
|
+
const migrationMarkerPath = path.join(userDataPath, '.migrated-from-cowork-oss');
|
|
2729
|
+
const notificationDismissedPath = path.join(userDataPath, '.migration-notification-dismissed');
|
|
2730
|
+
// Get migration status
|
|
2731
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MIGRATION_GET_STATUS, async () => {
|
|
2732
|
+
try {
|
|
2733
|
+
const migrated = fsSync.existsSync(migrationMarkerPath);
|
|
2734
|
+
const notificationDismissed = fsSync.existsSync(notificationDismissedPath);
|
|
2735
|
+
let timestamp;
|
|
2736
|
+
if (migrated) {
|
|
2737
|
+
try {
|
|
2738
|
+
const markerContent = fsSync.readFileSync(migrationMarkerPath, 'utf-8');
|
|
2739
|
+
const markerData = JSON.parse(markerContent);
|
|
2740
|
+
timestamp = markerData.timestamp;
|
|
2741
|
+
}
|
|
2742
|
+
catch {
|
|
2743
|
+
// Old format marker or read error
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
return {
|
|
2747
|
+
migrated,
|
|
2748
|
+
notificationDismissed,
|
|
2749
|
+
timestamp,
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
catch (error) {
|
|
2753
|
+
console.error('[Migration] Failed to get status:', error);
|
|
2754
|
+
return { migrated: false, notificationDismissed: true }; // Default to no notification on error
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
// Dismiss migration notification (user has acknowledged it)
|
|
2758
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.MIGRATION_DISMISS_NOTIFICATION, async () => {
|
|
2759
|
+
try {
|
|
2760
|
+
fsSync.writeFileSync(notificationDismissedPath, JSON.stringify({
|
|
2761
|
+
dismissedAt: new Date().toISOString(),
|
|
2762
|
+
}));
|
|
2763
|
+
console.log('[Migration] Notification dismissed');
|
|
2764
|
+
return { success: true };
|
|
2765
|
+
}
|
|
2766
|
+
catch (error) {
|
|
2767
|
+
console.error('[Migration] Failed to dismiss notification:', error);
|
|
2768
|
+
throw error;
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
console.log('[Migration] Handlers initialized');
|
|
2772
|
+
// === Extension / Plugin Handlers ===
|
|
2773
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
2774
|
+
const { getPluginRegistry } = require('../extensions/registry');
|
|
2775
|
+
// List all extensions
|
|
2776
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_LIST, async () => {
|
|
2777
|
+
try {
|
|
2778
|
+
const registry = getPluginRegistry();
|
|
2779
|
+
const plugins = registry.getPlugins();
|
|
2780
|
+
return plugins.map((p) => ({
|
|
2781
|
+
name: p.manifest.name,
|
|
2782
|
+
displayName: p.manifest.displayName,
|
|
2783
|
+
version: p.manifest.version,
|
|
2784
|
+
description: p.manifest.description,
|
|
2785
|
+
author: p.manifest.author,
|
|
2786
|
+
type: p.manifest.type,
|
|
2787
|
+
state: p.state,
|
|
2788
|
+
path: p.path,
|
|
2789
|
+
loadedAt: p.loadedAt.getTime(),
|
|
2790
|
+
error: p.error?.message,
|
|
2791
|
+
capabilities: p.manifest.capabilities,
|
|
2792
|
+
configSchema: p.manifest.configSchema,
|
|
2793
|
+
}));
|
|
2794
|
+
}
|
|
2795
|
+
catch (error) {
|
|
2796
|
+
console.error('[Extensions] Failed to list:', error);
|
|
2797
|
+
return [];
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
// Get single extension
|
|
2801
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_GET, async (_, name) => {
|
|
2802
|
+
try {
|
|
2803
|
+
const registry = getPluginRegistry();
|
|
2804
|
+
const plugin = registry.getPlugin(name);
|
|
2805
|
+
if (!plugin)
|
|
2806
|
+
return null;
|
|
2807
|
+
return {
|
|
2808
|
+
name: plugin.manifest.name,
|
|
2809
|
+
displayName: plugin.manifest.displayName,
|
|
2810
|
+
version: plugin.manifest.version,
|
|
2811
|
+
description: plugin.manifest.description,
|
|
2812
|
+
author: plugin.manifest.author,
|
|
2813
|
+
type: plugin.manifest.type,
|
|
2814
|
+
state: plugin.state,
|
|
2815
|
+
path: plugin.path,
|
|
2816
|
+
loadedAt: plugin.loadedAt.getTime(),
|
|
2817
|
+
error: plugin.error?.message,
|
|
2818
|
+
capabilities: plugin.manifest.capabilities,
|
|
2819
|
+
configSchema: plugin.manifest.configSchema,
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
catch (error) {
|
|
2823
|
+
console.error('[Extensions] Failed to get:', error);
|
|
2824
|
+
return null;
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
// Enable extension
|
|
2828
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_ENABLE, async (_, name) => {
|
|
2829
|
+
try {
|
|
2830
|
+
const registry = getPluginRegistry();
|
|
2831
|
+
await registry.enablePlugin(name);
|
|
2832
|
+
return { success: true };
|
|
2833
|
+
}
|
|
2834
|
+
catch (error) {
|
|
2835
|
+
console.error('[Extensions] Failed to enable:', error);
|
|
2836
|
+
return { success: false, error: error.message };
|
|
2837
|
+
}
|
|
2838
|
+
});
|
|
2839
|
+
// Disable extension
|
|
2840
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_DISABLE, async (_, name) => {
|
|
2841
|
+
try {
|
|
2842
|
+
const registry = getPluginRegistry();
|
|
2843
|
+
await registry.disablePlugin(name);
|
|
2844
|
+
return { success: true };
|
|
2845
|
+
}
|
|
2846
|
+
catch (error) {
|
|
2847
|
+
console.error('[Extensions] Failed to disable:', error);
|
|
2848
|
+
return { success: false, error: error.message };
|
|
2849
|
+
}
|
|
2850
|
+
});
|
|
2851
|
+
// Reload extension
|
|
2852
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_RELOAD, async (_, name) => {
|
|
2853
|
+
try {
|
|
2854
|
+
const registry = getPluginRegistry();
|
|
2855
|
+
await registry.reloadPlugin(name);
|
|
2856
|
+
return { success: true };
|
|
2857
|
+
}
|
|
2858
|
+
catch (error) {
|
|
2859
|
+
console.error('[Extensions] Failed to reload:', error);
|
|
2860
|
+
return { success: false, error: error.message };
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
// Get extension config
|
|
2864
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_GET_CONFIG, async (_, name) => {
|
|
2865
|
+
try {
|
|
2866
|
+
const registry = getPluginRegistry();
|
|
2867
|
+
return registry.getPluginConfig(name) || {};
|
|
2868
|
+
}
|
|
2869
|
+
catch (error) {
|
|
2870
|
+
console.error('[Extensions] Failed to get config:', error);
|
|
2871
|
+
return {};
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
// Set extension config
|
|
2875
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_SET_CONFIG, async (_, data) => {
|
|
2876
|
+
try {
|
|
2877
|
+
const registry = getPluginRegistry();
|
|
2878
|
+
await registry.setPluginConfig(data.name, data.config);
|
|
2879
|
+
return { success: true };
|
|
2880
|
+
}
|
|
2881
|
+
catch (error) {
|
|
2882
|
+
console.error('[Extensions] Failed to set config:', error);
|
|
2883
|
+
return { success: false, error: error.message };
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
// Discover extensions (re-scan directories)
|
|
2887
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.EXTENSIONS_DISCOVER, async () => {
|
|
2888
|
+
try {
|
|
2889
|
+
const registry = getPluginRegistry();
|
|
2890
|
+
await registry.initialize();
|
|
2891
|
+
const plugins = registry.getPlugins();
|
|
2892
|
+
return plugins.map((p) => ({
|
|
2893
|
+
name: p.manifest.name,
|
|
2894
|
+
displayName: p.manifest.displayName,
|
|
2895
|
+
version: p.manifest.version,
|
|
2896
|
+
description: p.manifest.description,
|
|
2897
|
+
type: p.manifest.type,
|
|
2898
|
+
state: p.state,
|
|
2899
|
+
}));
|
|
2900
|
+
}
|
|
2901
|
+
catch (error) {
|
|
2902
|
+
console.error('[Extensions] Failed to discover:', error);
|
|
2903
|
+
return [];
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
console.log('[Extensions] Handlers initialized');
|
|
2907
|
+
// === Webhook Tunnel Handlers ===
|
|
2908
|
+
let tunnelManager = null;
|
|
2909
|
+
// Get tunnel status
|
|
2910
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TUNNEL_GET_STATUS, async () => {
|
|
2911
|
+
try {
|
|
2912
|
+
if (!tunnelManager) {
|
|
2913
|
+
return { status: 'stopped' };
|
|
2914
|
+
}
|
|
2915
|
+
return {
|
|
2916
|
+
status: tunnelManager.status,
|
|
2917
|
+
provider: tunnelManager.config?.provider,
|
|
2918
|
+
url: tunnelManager.url,
|
|
2919
|
+
error: tunnelManager.error?.message,
|
|
2920
|
+
startedAt: tunnelManager.startedAt?.getTime(),
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
catch (error) {
|
|
2924
|
+
console.error('[Tunnel] Failed to get status:', error);
|
|
2925
|
+
return { status: 'stopped' };
|
|
2926
|
+
}
|
|
2927
|
+
});
|
|
2928
|
+
// Start tunnel
|
|
2929
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TUNNEL_START, async (_, config) => {
|
|
2930
|
+
try {
|
|
2931
|
+
const { TunnelManager } = await Promise.resolve().then(() => __importStar(require('../gateway/tunnel')));
|
|
2932
|
+
if (tunnelManager) {
|
|
2933
|
+
await tunnelManager.stop();
|
|
2934
|
+
}
|
|
2935
|
+
tunnelManager = new TunnelManager(config);
|
|
2936
|
+
const url = await tunnelManager.start();
|
|
2937
|
+
return { success: true, url };
|
|
2938
|
+
}
|
|
2939
|
+
catch (error) {
|
|
2940
|
+
console.error('[Tunnel] Failed to start:', error);
|
|
2941
|
+
return { success: false, error: error.message };
|
|
2942
|
+
}
|
|
2943
|
+
});
|
|
2944
|
+
// Stop tunnel
|
|
2945
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.TUNNEL_STOP, async () => {
|
|
2946
|
+
try {
|
|
2947
|
+
if (tunnelManager) {
|
|
2948
|
+
await tunnelManager.stop();
|
|
2949
|
+
tunnelManager = null;
|
|
2950
|
+
}
|
|
2951
|
+
return { success: true };
|
|
2952
|
+
}
|
|
2953
|
+
catch (error) {
|
|
2954
|
+
console.error('[Tunnel] Failed to stop:', error);
|
|
2955
|
+
return { success: false, error: error.message };
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
console.log('[Tunnel] Handlers initialized');
|
|
2959
|
+
// === Voice Mode Handlers ===
|
|
2960
|
+
// Initialize voice settings manager with secure database storage
|
|
2961
|
+
const voiceDb = schema_1.DatabaseManager.getInstance().getDatabase();
|
|
2962
|
+
voice_settings_manager_1.VoiceSettingsManager.initialize(voiceDb);
|
|
2963
|
+
// Get voice settings
|
|
2964
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_GET_SETTINGS, async () => {
|
|
2965
|
+
try {
|
|
2966
|
+
return voice_settings_manager_1.VoiceSettingsManager.loadSettings();
|
|
2967
|
+
}
|
|
2968
|
+
catch (error) {
|
|
2969
|
+
console.error('[Voice] Failed to get settings:', error);
|
|
2970
|
+
throw error;
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
// Save voice settings
|
|
2974
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_SAVE_SETTINGS, async (_, settings) => {
|
|
2975
|
+
try {
|
|
2976
|
+
const updated = voice_settings_manager_1.VoiceSettingsManager.updateSettings(settings);
|
|
2977
|
+
// Update the voice service with new settings
|
|
2978
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
2979
|
+
voiceService.updateSettings(updated);
|
|
2980
|
+
return updated;
|
|
2981
|
+
}
|
|
2982
|
+
catch (error) {
|
|
2983
|
+
console.error('[Voice] Failed to save settings:', error);
|
|
2984
|
+
throw error;
|
|
2985
|
+
}
|
|
2986
|
+
});
|
|
2987
|
+
// Get voice state
|
|
2988
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_GET_STATE, async () => {
|
|
2989
|
+
try {
|
|
2990
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
2991
|
+
return voiceService.getState();
|
|
2992
|
+
}
|
|
2993
|
+
catch (error) {
|
|
2994
|
+
console.error('[Voice] Failed to get state:', error);
|
|
2995
|
+
throw error;
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
// Speak text - returns audio data for renderer to play
|
|
2999
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_SPEAK, async (_, text) => {
|
|
3000
|
+
try {
|
|
3001
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3002
|
+
const audioBuffer = await voiceService.speak(text);
|
|
3003
|
+
if (audioBuffer) {
|
|
3004
|
+
// Return audio data as array for serialization over IPC
|
|
3005
|
+
return { success: true, audioData: Array.from(audioBuffer) };
|
|
3006
|
+
}
|
|
3007
|
+
return { success: true, audioData: null };
|
|
3008
|
+
}
|
|
3009
|
+
catch (error) {
|
|
3010
|
+
console.error('[Voice] Failed to speak:', error);
|
|
3011
|
+
return { success: false, error: error.message, audioData: null };
|
|
3012
|
+
}
|
|
3013
|
+
});
|
|
3014
|
+
// Stop speaking
|
|
3015
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_STOP_SPEAKING, async () => {
|
|
3016
|
+
try {
|
|
3017
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3018
|
+
voiceService.stopSpeaking();
|
|
3019
|
+
return { success: true };
|
|
3020
|
+
}
|
|
3021
|
+
catch (error) {
|
|
3022
|
+
console.error('[Voice] Failed to stop speaking:', error);
|
|
3023
|
+
return { success: false, error: error.message };
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
// Transcribe audio - accepts audio data as array from renderer
|
|
3027
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_TRANSCRIBE, async (_, audioData) => {
|
|
3028
|
+
try {
|
|
3029
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3030
|
+
// Convert array back to Buffer
|
|
3031
|
+
const audioBuffer = Buffer.from(audioData);
|
|
3032
|
+
const text = await voiceService.transcribe(audioBuffer);
|
|
3033
|
+
return { text };
|
|
3034
|
+
}
|
|
3035
|
+
catch (error) {
|
|
3036
|
+
console.error('[Voice] Failed to transcribe:', error);
|
|
3037
|
+
return { text: '', error: error.message };
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
// Get ElevenLabs voices
|
|
3041
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_GET_ELEVENLABS_VOICES, async () => {
|
|
3042
|
+
try {
|
|
3043
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3044
|
+
return await voiceService.getElevenLabsVoices();
|
|
3045
|
+
}
|
|
3046
|
+
catch (error) {
|
|
3047
|
+
console.error('[Voice] Failed to get ElevenLabs voices:', error);
|
|
3048
|
+
return [];
|
|
3049
|
+
}
|
|
3050
|
+
});
|
|
3051
|
+
// Test ElevenLabs connection
|
|
3052
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_TEST_ELEVENLABS, async () => {
|
|
3053
|
+
try {
|
|
3054
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3055
|
+
return await voiceService.testElevenLabsConnection();
|
|
3056
|
+
}
|
|
3057
|
+
catch (error) {
|
|
3058
|
+
console.error('[Voice] Failed to test ElevenLabs:', error);
|
|
3059
|
+
return { success: false, error: error.message };
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
// Test OpenAI voice connection
|
|
3063
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_TEST_OPENAI, async () => {
|
|
3064
|
+
try {
|
|
3065
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3066
|
+
return await voiceService.testOpenAIConnection();
|
|
3067
|
+
}
|
|
3068
|
+
catch (error) {
|
|
3069
|
+
console.error('[Voice] Failed to test OpenAI voice:', error);
|
|
3070
|
+
return { success: false, error: error.message };
|
|
3071
|
+
}
|
|
3072
|
+
});
|
|
3073
|
+
// Test Azure OpenAI voice connection
|
|
3074
|
+
electron_1.ipcMain.handle(types_1.IPC_CHANNELS.VOICE_TEST_AZURE, async () => {
|
|
3075
|
+
try {
|
|
3076
|
+
const voiceService = (0, VoiceService_1.getVoiceService)();
|
|
3077
|
+
return await voiceService.testAzureConnection();
|
|
3078
|
+
}
|
|
3079
|
+
catch (error) {
|
|
3080
|
+
console.error('[Voice] Failed to test Azure OpenAI voice:', error);
|
|
3081
|
+
return { success: false, error: error.message };
|
|
3082
|
+
}
|
|
3083
|
+
});
|
|
3084
|
+
// Initialize voice service with saved settings
|
|
3085
|
+
const savedVoiceSettings = voice_settings_manager_1.VoiceSettingsManager.loadSettings();
|
|
3086
|
+
const voiceService = (0, VoiceService_1.getVoiceService)({ settings: savedVoiceSettings });
|
|
3087
|
+
// Forward voice events to renderer
|
|
3088
|
+
voiceService.on('stateChange', (state) => {
|
|
3089
|
+
const mainWindow = getMainWindow();
|
|
3090
|
+
if (mainWindow) {
|
|
3091
|
+
mainWindow.webContents.send(types_1.IPC_CHANNELS.VOICE_EVENT, {
|
|
3092
|
+
type: 'voice:state-changed',
|
|
3093
|
+
data: state,
|
|
3094
|
+
});
|
|
3095
|
+
}
|
|
3096
|
+
});
|
|
3097
|
+
voiceService.on('speakingStart', (text) => {
|
|
3098
|
+
const mainWindow = getMainWindow();
|
|
3099
|
+
if (mainWindow) {
|
|
3100
|
+
mainWindow.webContents.send(types_1.IPC_CHANNELS.VOICE_EVENT, {
|
|
3101
|
+
type: 'voice:speaking-start',
|
|
3102
|
+
data: text,
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
voiceService.on('speakingEnd', () => {
|
|
3107
|
+
const mainWindow = getMainWindow();
|
|
3108
|
+
if (mainWindow) {
|
|
3109
|
+
mainWindow.webContents.send(types_1.IPC_CHANNELS.VOICE_EVENT, {
|
|
3110
|
+
type: 'voice:speaking-end',
|
|
3111
|
+
data: null,
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
});
|
|
3115
|
+
voiceService.on('transcript', (text) => {
|
|
3116
|
+
const mainWindow = getMainWindow();
|
|
3117
|
+
if (mainWindow) {
|
|
3118
|
+
mainWindow.webContents.send(types_1.IPC_CHANNELS.VOICE_EVENT, {
|
|
3119
|
+
type: 'voice:transcript',
|
|
3120
|
+
data: text,
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
voiceService.on('error', (error) => {
|
|
3125
|
+
const mainWindow = getMainWindow();
|
|
3126
|
+
if (mainWindow) {
|
|
3127
|
+
mainWindow.webContents.send(types_1.IPC_CHANNELS.VOICE_EVENT, {
|
|
3128
|
+
type: 'voice:error',
|
|
3129
|
+
data: { message: error.message },
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
});
|
|
3133
|
+
// Initialize voice service
|
|
3134
|
+
voiceService.initialize().catch((err) => {
|
|
3135
|
+
console.error('[Voice] Failed to initialize:', err);
|
|
3136
|
+
});
|
|
3137
|
+
console.log('[Voice] Handlers initialized');
|
|
3138
|
+
}
|