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,975 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Discord Channel Adapter
|
|
4
|
+
*
|
|
5
|
+
* Implements the ChannelAdapter interface using discord.js for Discord Bot API.
|
|
6
|
+
* Supports slash commands, direct messages, button components, embeds, and threads.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.DiscordAdapter = void 0;
|
|
43
|
+
exports.createDiscordAdapter = createDiscordAdapter;
|
|
44
|
+
const discord_js_1 = require("discord.js");
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
/**
|
|
48
|
+
* Embed color constants for different message types
|
|
49
|
+
*/
|
|
50
|
+
const EMBED_COLORS = {
|
|
51
|
+
pending: 0xffa500, // Orange
|
|
52
|
+
success: 0x57f287, // Green
|
|
53
|
+
error: 0xed4245, // Red
|
|
54
|
+
info: 0x5865f2, // Blue (Discord blurple)
|
|
55
|
+
warning: 0xfee75c, // Yellow
|
|
56
|
+
neutral: 0x99aab5, // Gray
|
|
57
|
+
};
|
|
58
|
+
class DiscordAdapter {
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.type = 'discord';
|
|
61
|
+
this.client = null;
|
|
62
|
+
this._status = 'disconnected';
|
|
63
|
+
this.messageHandlers = [];
|
|
64
|
+
this.errorHandlers = [];
|
|
65
|
+
this.statusHandlers = [];
|
|
66
|
+
this.callbackQueryHandlers = [];
|
|
67
|
+
this.selectMenuHandlers = [];
|
|
68
|
+
// Track pending interactions that need reply (chatId -> interaction)
|
|
69
|
+
this.pendingInteractions = new Map();
|
|
70
|
+
// Track thread starters for context (threadId -> starter info)
|
|
71
|
+
this.threadStarterCache = new Map();
|
|
72
|
+
this.config = config;
|
|
73
|
+
}
|
|
74
|
+
get status() {
|
|
75
|
+
return this._status;
|
|
76
|
+
}
|
|
77
|
+
get botUsername() {
|
|
78
|
+
return this._botUsername;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Connect to Discord
|
|
82
|
+
*/
|
|
83
|
+
async connect() {
|
|
84
|
+
if (this._status === 'connected' || this._status === 'connecting') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.setStatus('connecting');
|
|
88
|
+
try {
|
|
89
|
+
// Create client instance with required intents and partials
|
|
90
|
+
// Partials.Channel is required to receive DM messages
|
|
91
|
+
this.client = new discord_js_1.Client({
|
|
92
|
+
intents: [
|
|
93
|
+
discord_js_1.GatewayIntentBits.Guilds,
|
|
94
|
+
discord_js_1.GatewayIntentBits.GuildMessages,
|
|
95
|
+
discord_js_1.GatewayIntentBits.DirectMessages,
|
|
96
|
+
discord_js_1.GatewayIntentBits.MessageContent,
|
|
97
|
+
],
|
|
98
|
+
partials: [
|
|
99
|
+
discord_js_1.Partials.Channel, // Required to receive DMs
|
|
100
|
+
discord_js_1.Partials.Message, // Required for uncached message events
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
// Set up event handlers
|
|
104
|
+
this.client.once(discord_js_1.Events.ClientReady, async (client) => {
|
|
105
|
+
this._botUsername = client.user.username;
|
|
106
|
+
this._botId = client.user.id;
|
|
107
|
+
console.log(`Discord bot @${this._botUsername} is ready`);
|
|
108
|
+
// Register slash commands
|
|
109
|
+
await this.registerSlashCommands();
|
|
110
|
+
this.setStatus('connected');
|
|
111
|
+
});
|
|
112
|
+
// Handle regular messages (for conversations)
|
|
113
|
+
this.client.on(discord_js_1.Events.MessageCreate, async (message) => {
|
|
114
|
+
// Ignore bot messages
|
|
115
|
+
if (message.author.bot)
|
|
116
|
+
return;
|
|
117
|
+
// Handle DMs and mentions in guilds
|
|
118
|
+
const isDM = message.channel.type === discord_js_1.ChannelType.DM;
|
|
119
|
+
const isMentioned = message.mentions.has(this.client.user);
|
|
120
|
+
const isThread = message.channel.isThread();
|
|
121
|
+
console.log(`Discord message received: isDM=${isDM}, isMentioned=${isMentioned}, isThread=${isThread}, content="${message.content.slice(0, 50)}"`);
|
|
122
|
+
if (isDM || isMentioned) {
|
|
123
|
+
const incomingMessage = this.mapMessageToIncoming(message);
|
|
124
|
+
console.log(`Processing Discord message from ${message.author.username}: ${incomingMessage.text.slice(0, 50)}`);
|
|
125
|
+
await this.handleIncomingMessage(incomingMessage);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Handle slash command, button, and select menu interactions
|
|
129
|
+
this.client.on(discord_js_1.Events.InteractionCreate, async (interaction) => {
|
|
130
|
+
// Handle button interactions
|
|
131
|
+
if (interaction.isButton()) {
|
|
132
|
+
await this.handleButtonInteraction(interaction);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Handle select menu interactions
|
|
136
|
+
if (interaction.isStringSelectMenu()) {
|
|
137
|
+
await this.handleSelectMenuInteraction(interaction);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!interaction.isChatInputCommand())
|
|
141
|
+
return;
|
|
142
|
+
// Defer the reply FIRST to avoid interaction timeout (Discord requires response within 3 seconds)
|
|
143
|
+
try {
|
|
144
|
+
await interaction.deferReply();
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error('Failed to defer reply:', error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Store the interaction so sendMessage can use editReply for the first response
|
|
151
|
+
if (interaction.channelId) {
|
|
152
|
+
this.pendingInteractions.set(interaction.channelId, interaction);
|
|
153
|
+
// Auto-clear after 14 minutes (interactions expire after 15 minutes)
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
this.pendingInteractions.delete(interaction.channelId);
|
|
156
|
+
}, 14 * 60 * 1000);
|
|
157
|
+
}
|
|
158
|
+
// Convert slash command to message format
|
|
159
|
+
const incomingMessage = this.mapInteractionToIncoming(interaction);
|
|
160
|
+
await this.handleIncomingMessage(incomingMessage);
|
|
161
|
+
});
|
|
162
|
+
// Handle errors
|
|
163
|
+
this.client.on(discord_js_1.Events.Error, (error) => {
|
|
164
|
+
console.error('Discord client error:', error);
|
|
165
|
+
this.handleError(error, 'client.error');
|
|
166
|
+
});
|
|
167
|
+
// Login
|
|
168
|
+
await this.client.login(this.config.botToken);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
172
|
+
this.setStatus('error', err);
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Handle button interaction (callback query equivalent)
|
|
178
|
+
*/
|
|
179
|
+
async handleButtonInteraction(interaction) {
|
|
180
|
+
const customId = interaction.customId;
|
|
181
|
+
// Create callback query object matching our interface
|
|
182
|
+
const callbackQuery = {
|
|
183
|
+
id: interaction.id,
|
|
184
|
+
userId: interaction.user.id,
|
|
185
|
+
userName: interaction.user.displayName || interaction.user.username,
|
|
186
|
+
chatId: interaction.channelId,
|
|
187
|
+
messageId: interaction.message.id,
|
|
188
|
+
data: customId,
|
|
189
|
+
threadId: interaction.channel?.isThread() ? interaction.channelId : undefined,
|
|
190
|
+
raw: interaction,
|
|
191
|
+
};
|
|
192
|
+
// Notify all registered handlers
|
|
193
|
+
for (const handler of this.callbackQueryHandlers) {
|
|
194
|
+
try {
|
|
195
|
+
await handler(callbackQuery);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
console.error('Error in callback query handler:', error);
|
|
199
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)), 'callbackQueryHandler');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Register slash commands with Discord
|
|
205
|
+
*/
|
|
206
|
+
async registerSlashCommands() {
|
|
207
|
+
if (!this.client?.user)
|
|
208
|
+
return;
|
|
209
|
+
const commands = [
|
|
210
|
+
new discord_js_1.SlashCommandBuilder()
|
|
211
|
+
.setName('start')
|
|
212
|
+
.setDescription('Start the bot and get help'),
|
|
213
|
+
new discord_js_1.SlashCommandBuilder()
|
|
214
|
+
.setName('help')
|
|
215
|
+
.setDescription('Show available commands'),
|
|
216
|
+
new discord_js_1.SlashCommandBuilder()
|
|
217
|
+
.setName('workspaces')
|
|
218
|
+
.setDescription('List available workspaces'),
|
|
219
|
+
new discord_js_1.SlashCommandBuilder()
|
|
220
|
+
.setName('workspace')
|
|
221
|
+
.setDescription('Select or show current workspace')
|
|
222
|
+
.addStringOption(option => option.setName('path')
|
|
223
|
+
.setDescription('Workspace path to select')
|
|
224
|
+
.setRequired(false)),
|
|
225
|
+
new discord_js_1.SlashCommandBuilder()
|
|
226
|
+
.setName('addworkspace')
|
|
227
|
+
.setDescription('Add a new workspace by path')
|
|
228
|
+
.addStringOption(option => option.setName('path')
|
|
229
|
+
.setDescription('Path to the workspace folder')
|
|
230
|
+
.setRequired(true)),
|
|
231
|
+
new discord_js_1.SlashCommandBuilder()
|
|
232
|
+
.setName('newtask')
|
|
233
|
+
.setDescription('Start a fresh task/conversation'),
|
|
234
|
+
new discord_js_1.SlashCommandBuilder()
|
|
235
|
+
.setName('provider')
|
|
236
|
+
.setDescription('Change or show current LLM provider')
|
|
237
|
+
.addStringOption(option => option.setName('name')
|
|
238
|
+
.setDescription('Provider name (anthropic, gemini, openrouter, bedrock, ollama)')
|
|
239
|
+
.setRequired(false)),
|
|
240
|
+
new discord_js_1.SlashCommandBuilder()
|
|
241
|
+
.setName('providers')
|
|
242
|
+
.setDescription('List all available providers'),
|
|
243
|
+
new discord_js_1.SlashCommandBuilder()
|
|
244
|
+
.setName('models')
|
|
245
|
+
.setDescription('List available AI models'),
|
|
246
|
+
new discord_js_1.SlashCommandBuilder()
|
|
247
|
+
.setName('model')
|
|
248
|
+
.setDescription('Change or show current model')
|
|
249
|
+
.addStringOption(option => option.setName('name')
|
|
250
|
+
.setDescription('Model name to use')
|
|
251
|
+
.setRequired(false)),
|
|
252
|
+
new discord_js_1.SlashCommandBuilder()
|
|
253
|
+
.setName('status')
|
|
254
|
+
.setDescription('Check bot status'),
|
|
255
|
+
new discord_js_1.SlashCommandBuilder()
|
|
256
|
+
.setName('cancel')
|
|
257
|
+
.setDescription('Cancel current task'),
|
|
258
|
+
new discord_js_1.SlashCommandBuilder()
|
|
259
|
+
.setName('task')
|
|
260
|
+
.setDescription('Run a task')
|
|
261
|
+
.addStringOption(option => option.setName('prompt')
|
|
262
|
+
.setDescription('Task description')
|
|
263
|
+
.setRequired(true)),
|
|
264
|
+
new discord_js_1.SlashCommandBuilder()
|
|
265
|
+
.setName('pair')
|
|
266
|
+
.setDescription('Pair with a pairing code to gain access')
|
|
267
|
+
.addStringOption(option => option.setName('code')
|
|
268
|
+
.setDescription('The pairing code from CoWork OS app')
|
|
269
|
+
.setRequired(true)),
|
|
270
|
+
new discord_js_1.SlashCommandBuilder()
|
|
271
|
+
.setName('approve')
|
|
272
|
+
.setDescription('Approve the pending action'),
|
|
273
|
+
new discord_js_1.SlashCommandBuilder()
|
|
274
|
+
.setName('deny')
|
|
275
|
+
.setDescription('Deny the pending action'),
|
|
276
|
+
];
|
|
277
|
+
const rest = new discord_js_1.REST().setToken(this.config.botToken);
|
|
278
|
+
try {
|
|
279
|
+
console.log('Registering Discord slash commands...');
|
|
280
|
+
// Register commands globally or to specific guilds
|
|
281
|
+
if (this.config.guildIds && this.config.guildIds.length > 0) {
|
|
282
|
+
// Register to specific guilds (faster for development)
|
|
283
|
+
for (const guildId of this.config.guildIds) {
|
|
284
|
+
await rest.put(discord_js_1.Routes.applicationGuildCommands(this.config.applicationId, guildId), { body: commands.map(c => c.toJSON()) });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// Register globally (takes up to 1 hour to propagate)
|
|
289
|
+
await rest.put(discord_js_1.Routes.applicationCommands(this.config.applicationId), { body: commands.map(c => c.toJSON()) });
|
|
290
|
+
}
|
|
291
|
+
console.log('Discord slash commands registered');
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
console.error('Failed to register Discord slash commands:', error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Disconnect from Discord
|
|
299
|
+
*/
|
|
300
|
+
async disconnect() {
|
|
301
|
+
if (this.client) {
|
|
302
|
+
this.client.destroy();
|
|
303
|
+
this.client = null;
|
|
304
|
+
}
|
|
305
|
+
this._botUsername = undefined;
|
|
306
|
+
this._botId = undefined;
|
|
307
|
+
this.setStatus('disconnected');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Send a message to a Discord channel
|
|
311
|
+
*/
|
|
312
|
+
async sendMessage(message) {
|
|
313
|
+
if (!this.client || this._status !== 'connected') {
|
|
314
|
+
throw new Error('Discord bot is not connected');
|
|
315
|
+
}
|
|
316
|
+
// Process text for Discord compatibility
|
|
317
|
+
let processedText = message.text;
|
|
318
|
+
if (message.parseMode === 'markdown') {
|
|
319
|
+
processedText = this.convertMarkdownForDiscord(message.text);
|
|
320
|
+
}
|
|
321
|
+
// Build button components if inline keyboard is provided
|
|
322
|
+
const components = message.inlineKeyboard && message.inlineKeyboard.length > 0
|
|
323
|
+
? this.buildButtonComponents(message.inlineKeyboard)
|
|
324
|
+
: [];
|
|
325
|
+
// Use smart chunking that preserves code fences
|
|
326
|
+
const chunks = this.splitMessageSmart(processedText, 2000);
|
|
327
|
+
let lastMessageId = '';
|
|
328
|
+
// Check if there's a pending interaction for this chat that needs reply
|
|
329
|
+
const pendingInteraction = this.pendingInteractions.get(message.chatId);
|
|
330
|
+
// Determine target channel (could be a thread)
|
|
331
|
+
let targetChannelId = message.chatId;
|
|
332
|
+
if (message.threadId) {
|
|
333
|
+
targetChannelId = message.threadId;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
337
|
+
const chunk = chunks[i];
|
|
338
|
+
const isLastChunk = i === chunks.length - 1;
|
|
339
|
+
// Only add buttons to the last chunk
|
|
340
|
+
const chunkComponents = isLastChunk ? components : [];
|
|
341
|
+
// First chunk: use interaction reply if available
|
|
342
|
+
if (i === 0 && pendingInteraction) {
|
|
343
|
+
try {
|
|
344
|
+
const reply = await pendingInteraction.editReply({
|
|
345
|
+
content: chunk,
|
|
346
|
+
components: chunkComponents,
|
|
347
|
+
});
|
|
348
|
+
lastMessageId = typeof reply === 'object' && 'id' in reply ? reply.id : pendingInteraction.id;
|
|
349
|
+
// Clear the pending interaction after first reply
|
|
350
|
+
this.pendingInteractions.delete(message.chatId);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
catch (interactionError) {
|
|
354
|
+
// Interaction may have expired, fall back to channel.send
|
|
355
|
+
console.warn('Interaction reply failed, falling back to channel.send:', interactionError);
|
|
356
|
+
this.pendingInteractions.delete(message.chatId);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Regular channel message
|
|
360
|
+
const channel = await this.client.channels.fetch(targetChannelId);
|
|
361
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
362
|
+
throw new Error('Invalid channel or channel is not text-based');
|
|
363
|
+
}
|
|
364
|
+
const sent = await channel.send({
|
|
365
|
+
content: chunk,
|
|
366
|
+
components: chunkComponents,
|
|
367
|
+
reply: message.replyTo && i === 0 ? { messageReference: message.replyTo } : undefined,
|
|
368
|
+
});
|
|
369
|
+
lastMessageId = sent.id;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
// If markdown parsing fails, retry without formatting
|
|
374
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
375
|
+
if (errorMessage.includes('parse') || errorMessage.includes('format')) {
|
|
376
|
+
console.log('Markdown parsing failed, retrying without formatting');
|
|
377
|
+
return this.sendMessagePlain(targetChannelId, message.text, message.replyTo, components);
|
|
378
|
+
}
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
return lastMessageId;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Send a message with an embed (rich format)
|
|
385
|
+
*/
|
|
386
|
+
async sendEmbed(chatId, options, buttons) {
|
|
387
|
+
if (!this.client || this._status !== 'connected') {
|
|
388
|
+
throw new Error('Discord bot is not connected');
|
|
389
|
+
}
|
|
390
|
+
const embed = new discord_js_1.EmbedBuilder();
|
|
391
|
+
if (options.title)
|
|
392
|
+
embed.setTitle(options.title);
|
|
393
|
+
if (options.description)
|
|
394
|
+
embed.setDescription(options.description);
|
|
395
|
+
if (options.color)
|
|
396
|
+
embed.setColor(EMBED_COLORS[options.color]);
|
|
397
|
+
if (options.fields) {
|
|
398
|
+
for (const field of options.fields) {
|
|
399
|
+
embed.addFields({ name: field.name, value: field.value, inline: field.inline });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (options.footer)
|
|
403
|
+
embed.setFooter({ text: options.footer });
|
|
404
|
+
if (options.timestamp)
|
|
405
|
+
embed.setTimestamp();
|
|
406
|
+
const components = buttons ? this.buildButtonComponents(buttons) : [];
|
|
407
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
408
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
409
|
+
throw new Error('Invalid channel');
|
|
410
|
+
}
|
|
411
|
+
const sent = await channel.send({
|
|
412
|
+
embeds: [embed],
|
|
413
|
+
components,
|
|
414
|
+
});
|
|
415
|
+
return sent.id;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Build Discord button components from our button format
|
|
419
|
+
*/
|
|
420
|
+
buildButtonComponents(buttons) {
|
|
421
|
+
const rows = [];
|
|
422
|
+
for (const rowButtons of buttons) {
|
|
423
|
+
if (rowButtons.length === 0)
|
|
424
|
+
continue;
|
|
425
|
+
const row = new discord_js_1.ActionRowBuilder();
|
|
426
|
+
let buttonCount = 0;
|
|
427
|
+
for (const button of rowButtons) {
|
|
428
|
+
if (buttonCount >= 5)
|
|
429
|
+
break; // Discord max 5 buttons per row
|
|
430
|
+
const discordButton = new discord_js_1.ButtonBuilder()
|
|
431
|
+
.setLabel(button.text.substring(0, 80)); // Discord max 80 chars
|
|
432
|
+
if (button.url) {
|
|
433
|
+
discordButton.setStyle(discord_js_1.ButtonStyle.Link);
|
|
434
|
+
discordButton.setURL(button.url);
|
|
435
|
+
}
|
|
436
|
+
else if (button.callbackData) {
|
|
437
|
+
// Determine button style based on callback data
|
|
438
|
+
if (button.callbackData.startsWith('approve')) {
|
|
439
|
+
discordButton.setStyle(discord_js_1.ButtonStyle.Success);
|
|
440
|
+
}
|
|
441
|
+
else if (button.callbackData.startsWith('deny')) {
|
|
442
|
+
discordButton.setStyle(discord_js_1.ButtonStyle.Danger);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
discordButton.setStyle(discord_js_1.ButtonStyle.Primary);
|
|
446
|
+
}
|
|
447
|
+
discordButton.setCustomId(button.callbackData.substring(0, 100)); // Discord max 100 chars
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
continue; // Skip buttons without action
|
|
451
|
+
}
|
|
452
|
+
row.addComponents(discordButton);
|
|
453
|
+
buttonCount++;
|
|
454
|
+
}
|
|
455
|
+
if (buttonCount > 0) {
|
|
456
|
+
rows.push(row);
|
|
457
|
+
}
|
|
458
|
+
if (rows.length >= 5)
|
|
459
|
+
break; // Discord max 5 rows
|
|
460
|
+
}
|
|
461
|
+
return rows;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Send a plain text message without formatting
|
|
465
|
+
*/
|
|
466
|
+
async sendMessagePlain(chatId, text, replyTo, components = []) {
|
|
467
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
468
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
469
|
+
throw new Error('Invalid channel');
|
|
470
|
+
}
|
|
471
|
+
const chunks = this.splitMessageSmart(text, 2000);
|
|
472
|
+
let lastMessageId = '';
|
|
473
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
474
|
+
const isLastChunk = i === chunks.length - 1;
|
|
475
|
+
const sent = await channel.send({
|
|
476
|
+
content: chunks[i],
|
|
477
|
+
components: isLastChunk ? components : [],
|
|
478
|
+
reply: replyTo && i === 0 ? { messageReference: replyTo } : undefined,
|
|
479
|
+
});
|
|
480
|
+
lastMessageId = sent.id;
|
|
481
|
+
}
|
|
482
|
+
return lastMessageId;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Convert GitHub-flavored markdown to Discord-compatible format
|
|
486
|
+
*/
|
|
487
|
+
convertMarkdownForDiscord(text) {
|
|
488
|
+
let result = text;
|
|
489
|
+
// Convert markdown headers (## Header) to bold (**Header**)
|
|
490
|
+
result = result.replace(/^#{1,6}\s+(.+)$/gm, '**$1**');
|
|
491
|
+
// Convert horizontal rules (---, ***) to a line
|
|
492
|
+
result = result.replace(/^[-*]{3,}$/gm, '───────────────────');
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Smart message splitting that preserves code fences
|
|
497
|
+
*/
|
|
498
|
+
splitMessageSmart(text, maxLength) {
|
|
499
|
+
if (text.length <= maxLength) {
|
|
500
|
+
return [text];
|
|
501
|
+
}
|
|
502
|
+
const chunks = [];
|
|
503
|
+
let remaining = text;
|
|
504
|
+
let inCodeBlock = false;
|
|
505
|
+
let codeBlockLang = '';
|
|
506
|
+
while (remaining.length > 0) {
|
|
507
|
+
if (remaining.length <= maxLength) {
|
|
508
|
+
// Close any open code block at the end
|
|
509
|
+
if (inCodeBlock) {
|
|
510
|
+
chunks.push(remaining);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
chunks.push(remaining);
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
// Find the best breaking point
|
|
518
|
+
let breakIndex = this.findBreakPoint(remaining, maxLength, inCodeBlock);
|
|
519
|
+
let chunk = remaining.substring(0, breakIndex);
|
|
520
|
+
// Check if we're entering or leaving a code block
|
|
521
|
+
const codeBlockMatches = chunk.match(/```(\w*)/g) || [];
|
|
522
|
+
for (const match of codeBlockMatches) {
|
|
523
|
+
if (inCodeBlock) {
|
|
524
|
+
inCodeBlock = false;
|
|
525
|
+
codeBlockLang = '';
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
inCodeBlock = true;
|
|
529
|
+
codeBlockLang = match.replace('```', '');
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// If we're in a code block and the chunk doesn't close it, close it manually
|
|
533
|
+
if (inCodeBlock && !chunk.endsWith('```')) {
|
|
534
|
+
chunk += '\n```';
|
|
535
|
+
}
|
|
536
|
+
chunks.push(chunk);
|
|
537
|
+
remaining = remaining.substring(breakIndex).trimStart();
|
|
538
|
+
// If we closed a code block, reopen it in the next chunk
|
|
539
|
+
if (inCodeBlock && remaining.length > 0) {
|
|
540
|
+
remaining = '```' + codeBlockLang + '\n' + remaining;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return chunks;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Find the best break point for message splitting
|
|
547
|
+
*/
|
|
548
|
+
findBreakPoint(text, maxLength, inCodeBlock) {
|
|
549
|
+
// Reserve space for potential code fence closure
|
|
550
|
+
const reservedSpace = inCodeBlock ? 4 : 0;
|
|
551
|
+
const effectiveMax = maxLength - reservedSpace;
|
|
552
|
+
// Try to break at a newline
|
|
553
|
+
let breakIndex = text.lastIndexOf('\n', effectiveMax);
|
|
554
|
+
if (breakIndex > effectiveMax / 2) {
|
|
555
|
+
return breakIndex + 1;
|
|
556
|
+
}
|
|
557
|
+
// Try to break at a space
|
|
558
|
+
breakIndex = text.lastIndexOf(' ', effectiveMax);
|
|
559
|
+
if (breakIndex > effectiveMax / 2) {
|
|
560
|
+
return breakIndex + 1;
|
|
561
|
+
}
|
|
562
|
+
// Force break at max length
|
|
563
|
+
return effectiveMax;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Legacy split method for compatibility
|
|
567
|
+
*/
|
|
568
|
+
splitMessage(text, maxLength) {
|
|
569
|
+
return this.splitMessageSmart(text, maxLength);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Edit an existing message
|
|
573
|
+
*/
|
|
574
|
+
async editMessage(chatId, messageId, text) {
|
|
575
|
+
if (!this.client || this._status !== 'connected') {
|
|
576
|
+
throw new Error('Discord bot is not connected');
|
|
577
|
+
}
|
|
578
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
579
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
580
|
+
throw new Error('Invalid channel');
|
|
581
|
+
}
|
|
582
|
+
const message = await channel.messages.fetch(messageId);
|
|
583
|
+
await message.edit(text);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Delete a message
|
|
587
|
+
*/
|
|
588
|
+
async deleteMessage(chatId, messageId) {
|
|
589
|
+
if (!this.client || this._status !== 'connected') {
|
|
590
|
+
throw new Error('Discord bot is not connected');
|
|
591
|
+
}
|
|
592
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
593
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
594
|
+
throw new Error('Invalid channel');
|
|
595
|
+
}
|
|
596
|
+
const message = await channel.messages.fetch(messageId);
|
|
597
|
+
await message.delete();
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Send a document/file to a channel
|
|
601
|
+
*/
|
|
602
|
+
async sendDocument(chatId, filePath, caption) {
|
|
603
|
+
if (!this.client || this._status !== 'connected') {
|
|
604
|
+
throw new Error('Discord bot is not connected');
|
|
605
|
+
}
|
|
606
|
+
// Check if file exists
|
|
607
|
+
if (!fs.existsSync(filePath)) {
|
|
608
|
+
throw new Error(`File not found: ${filePath}`);
|
|
609
|
+
}
|
|
610
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
611
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
612
|
+
throw new Error('Invalid channel');
|
|
613
|
+
}
|
|
614
|
+
const fileName = path.basename(filePath);
|
|
615
|
+
const attachment = new discord_js_1.AttachmentBuilder(filePath, { name: fileName });
|
|
616
|
+
const sent = await channel.send({
|
|
617
|
+
content: caption,
|
|
618
|
+
files: [attachment],
|
|
619
|
+
});
|
|
620
|
+
return sent.id;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Register a message handler
|
|
624
|
+
*/
|
|
625
|
+
onMessage(handler) {
|
|
626
|
+
this.messageHandlers.push(handler);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Register a callback query handler (for button interactions)
|
|
630
|
+
*/
|
|
631
|
+
onCallbackQuery(handler) {
|
|
632
|
+
this.callbackQueryHandlers.push(handler);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Answer a callback query (acknowledge button press)
|
|
636
|
+
* For Discord, this updates the message or sends an ephemeral response
|
|
637
|
+
*/
|
|
638
|
+
async answerCallbackQuery(queryId, text, showAlert) {
|
|
639
|
+
// In Discord, we need to use the interaction object stored in the raw field
|
|
640
|
+
// The queryId is the interaction ID, but we need the actual interaction object
|
|
641
|
+
// This is typically handled directly in handleButtonInteraction
|
|
642
|
+
// This method provides API compatibility with Telegram
|
|
643
|
+
console.log(`answerCallbackQuery called: ${queryId}, text: ${text}, showAlert: ${showAlert}`);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Edit a message with a new inline keyboard
|
|
647
|
+
*/
|
|
648
|
+
async editMessageWithKeyboard(chatId, messageId, text, inlineKeyboard) {
|
|
649
|
+
if (!this.client || this._status !== 'connected') {
|
|
650
|
+
throw new Error('Discord bot is not connected');
|
|
651
|
+
}
|
|
652
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
653
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
654
|
+
throw new Error('Invalid channel');
|
|
655
|
+
}
|
|
656
|
+
const message = await channel.messages.fetch(messageId);
|
|
657
|
+
const components = inlineKeyboard ? this.buildButtonComponents(inlineKeyboard) : [];
|
|
658
|
+
await message.edit({
|
|
659
|
+
content: text || message.content,
|
|
660
|
+
components,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
// ============================================================================
|
|
664
|
+
// Extended Features
|
|
665
|
+
// ============================================================================
|
|
666
|
+
/**
|
|
667
|
+
* Send typing indicator
|
|
668
|
+
*/
|
|
669
|
+
async sendTyping(chatId) {
|
|
670
|
+
if (!this.client || this._status !== 'connected') {
|
|
671
|
+
throw new Error('Discord bot is not connected');
|
|
672
|
+
}
|
|
673
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
674
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
675
|
+
throw new Error('Invalid channel');
|
|
676
|
+
}
|
|
677
|
+
await channel.sendTyping();
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Add reaction to a message
|
|
681
|
+
*/
|
|
682
|
+
async addReaction(chatId, messageId, emoji) {
|
|
683
|
+
if (!this.client || this._status !== 'connected') {
|
|
684
|
+
throw new Error('Discord bot is not connected');
|
|
685
|
+
}
|
|
686
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
687
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
688
|
+
throw new Error('Invalid channel');
|
|
689
|
+
}
|
|
690
|
+
const message = await channel.messages.fetch(messageId);
|
|
691
|
+
await message.react(emoji);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Remove reaction from a message
|
|
695
|
+
*/
|
|
696
|
+
async removeReaction(chatId, messageId, emoji) {
|
|
697
|
+
if (!this.client || this._status !== 'connected') {
|
|
698
|
+
throw new Error('Discord bot is not connected');
|
|
699
|
+
}
|
|
700
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
701
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
702
|
+
throw new Error('Invalid channel');
|
|
703
|
+
}
|
|
704
|
+
const message = await channel.messages.fetch(messageId);
|
|
705
|
+
const reaction = message.reactions.cache.get(emoji);
|
|
706
|
+
if (reaction && this._botId) {
|
|
707
|
+
await reaction.users.remove(this._botId);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Send a poll (Discord native polls)
|
|
712
|
+
*/
|
|
713
|
+
async sendPoll(chatId, poll) {
|
|
714
|
+
if (!this.client || this._status !== 'connected') {
|
|
715
|
+
throw new Error('Discord bot is not connected');
|
|
716
|
+
}
|
|
717
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
718
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
719
|
+
throw new Error('Invalid channel');
|
|
720
|
+
}
|
|
721
|
+
// Discord polls require specific formatting
|
|
722
|
+
const pollData = {
|
|
723
|
+
question: { text: poll.question },
|
|
724
|
+
answers: poll.options.map(opt => ({ text: opt.text })),
|
|
725
|
+
duration: poll.openPeriod ? Math.ceil(poll.openPeriod / 3600) : 24, // Convert seconds to hours
|
|
726
|
+
allow_multiselect: poll.allowsMultipleAnswers ?? false,
|
|
727
|
+
};
|
|
728
|
+
const sent = await channel.send({
|
|
729
|
+
poll: pollData,
|
|
730
|
+
});
|
|
731
|
+
return sent.id;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Send a message with a select menu (dropdown)
|
|
735
|
+
*/
|
|
736
|
+
async sendWithSelectMenu(chatId, text, menu) {
|
|
737
|
+
if (!this.client || this._status !== 'connected') {
|
|
738
|
+
throw new Error('Discord bot is not connected');
|
|
739
|
+
}
|
|
740
|
+
const channel = await this.client.channels.fetch(chatId);
|
|
741
|
+
if (!channel || !this.isTextBasedChannel(channel)) {
|
|
742
|
+
throw new Error('Invalid channel');
|
|
743
|
+
}
|
|
744
|
+
const selectMenu = new discord_js_1.StringSelectMenuBuilder()
|
|
745
|
+
.setCustomId(menu.customId)
|
|
746
|
+
.setPlaceholder(menu.placeholder || 'Select an option')
|
|
747
|
+
.setMinValues(menu.minValues ?? 1)
|
|
748
|
+
.setMaxValues(menu.maxValues ?? 1)
|
|
749
|
+
.addOptions(menu.options.map(opt => ({
|
|
750
|
+
label: opt.label,
|
|
751
|
+
value: opt.value,
|
|
752
|
+
description: opt.description,
|
|
753
|
+
emoji: opt.emoji ? { name: opt.emoji } : undefined,
|
|
754
|
+
default: opt.default,
|
|
755
|
+
})));
|
|
756
|
+
if (menu.disabled) {
|
|
757
|
+
selectMenu.setDisabled(true);
|
|
758
|
+
}
|
|
759
|
+
const row = new discord_js_1.ActionRowBuilder().addComponents(selectMenu);
|
|
760
|
+
const sent = await channel.send({
|
|
761
|
+
content: text,
|
|
762
|
+
components: [row],
|
|
763
|
+
});
|
|
764
|
+
return sent.id;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Register a select menu handler
|
|
768
|
+
*/
|
|
769
|
+
onSelectMenu(handler) {
|
|
770
|
+
this.selectMenuHandlers.push(handler);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Handle select menu interaction
|
|
774
|
+
*/
|
|
775
|
+
async handleSelectMenuInteraction(interaction) {
|
|
776
|
+
const customId = interaction.customId;
|
|
777
|
+
const values = interaction.values;
|
|
778
|
+
// Acknowledge the interaction
|
|
779
|
+
try {
|
|
780
|
+
await interaction.deferUpdate();
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
console.error('Failed to defer select menu update:', error);
|
|
784
|
+
}
|
|
785
|
+
// Notify all registered handlers
|
|
786
|
+
for (const handler of this.selectMenuHandlers) {
|
|
787
|
+
try {
|
|
788
|
+
await handler(customId, values, interaction.user.id, interaction.channelId, interaction.message.id, interaction);
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
console.error('Error in select menu handler:', error);
|
|
792
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)), 'selectMenuHandler');
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// ============================================================================
|
|
797
|
+
// Handler Registration
|
|
798
|
+
// ============================================================================
|
|
799
|
+
/**
|
|
800
|
+
* Register an error handler
|
|
801
|
+
*/
|
|
802
|
+
onError(handler) {
|
|
803
|
+
this.errorHandlers.push(handler);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Register a status change handler
|
|
807
|
+
*/
|
|
808
|
+
onStatusChange(handler) {
|
|
809
|
+
this.statusHandlers.push(handler);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get channel info
|
|
813
|
+
*/
|
|
814
|
+
async getInfo() {
|
|
815
|
+
return {
|
|
816
|
+
type: 'discord',
|
|
817
|
+
status: this._status,
|
|
818
|
+
botId: this._botId,
|
|
819
|
+
botUsername: this._botUsername,
|
|
820
|
+
botDisplayName: this._botUsername,
|
|
821
|
+
extra: {
|
|
822
|
+
applicationId: this.config.applicationId,
|
|
823
|
+
guildIds: this.config.guildIds,
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
// Private methods
|
|
828
|
+
isTextBasedChannel(channel) {
|
|
829
|
+
const ch = channel;
|
|
830
|
+
return ch.type === discord_js_1.ChannelType.GuildText ||
|
|
831
|
+
ch.type === discord_js_1.ChannelType.DM ||
|
|
832
|
+
ch.type === discord_js_1.ChannelType.PublicThread ||
|
|
833
|
+
ch.type === discord_js_1.ChannelType.PrivateThread;
|
|
834
|
+
}
|
|
835
|
+
mapMessageToIncoming(message) {
|
|
836
|
+
// Remove bot mention from the text if present
|
|
837
|
+
let text = message.content;
|
|
838
|
+
if (this._botId) {
|
|
839
|
+
text = text.replace(new RegExp(`<@!?${this._botId}>\\s*`, 'g'), '').trim();
|
|
840
|
+
}
|
|
841
|
+
// Map Discord message to command format if it looks like a command
|
|
842
|
+
const commandText = this.parseCommand(text);
|
|
843
|
+
// Check for thread context
|
|
844
|
+
const isThread = message.channel.isThread();
|
|
845
|
+
const threadId = isThread ? message.channelId : undefined;
|
|
846
|
+
return {
|
|
847
|
+
messageId: message.id,
|
|
848
|
+
channel: 'discord',
|
|
849
|
+
userId: message.author.id,
|
|
850
|
+
userName: message.author.displayName || message.author.username,
|
|
851
|
+
chatId: isThread ? message.channel.parentId : message.channelId,
|
|
852
|
+
text: commandText || text,
|
|
853
|
+
timestamp: message.createdAt,
|
|
854
|
+
replyTo: message.reference?.messageId,
|
|
855
|
+
threadId,
|
|
856
|
+
isForumTopic: isThread,
|
|
857
|
+
raw: message,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
mapInteractionToIncoming(interaction) {
|
|
861
|
+
const commandName = interaction.commandName;
|
|
862
|
+
let text = `/${commandName}`;
|
|
863
|
+
// Add options to the command text
|
|
864
|
+
const options = interaction.options;
|
|
865
|
+
// Handle specific commands with their options
|
|
866
|
+
switch (commandName) {
|
|
867
|
+
case 'workspace': {
|
|
868
|
+
const wsPath = options.getString('path');
|
|
869
|
+
if (wsPath)
|
|
870
|
+
text += ` ${wsPath}`;
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
case 'addworkspace': {
|
|
874
|
+
const addPath = options.getString('path');
|
|
875
|
+
if (addPath)
|
|
876
|
+
text += ` ${addPath}`;
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case 'provider': {
|
|
880
|
+
const provider = options.getString('name');
|
|
881
|
+
if (provider)
|
|
882
|
+
text += ` ${provider}`;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case 'model': {
|
|
886
|
+
const model = options.getString('name');
|
|
887
|
+
if (model)
|
|
888
|
+
text += ` ${model}`;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
case 'task': {
|
|
892
|
+
const prompt = options.getString('prompt');
|
|
893
|
+
if (prompt)
|
|
894
|
+
text = prompt; // Task prompt becomes the text directly
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case 'pair': {
|
|
898
|
+
const code = options.getString('code');
|
|
899
|
+
if (code)
|
|
900
|
+
text += ` ${code}`;
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// Check for thread context
|
|
905
|
+
const isThread = interaction.channel?.isThread() ?? false;
|
|
906
|
+
return {
|
|
907
|
+
messageId: interaction.id,
|
|
908
|
+
channel: 'discord',
|
|
909
|
+
userId: interaction.user.id,
|
|
910
|
+
userName: interaction.user.displayName || interaction.user.username,
|
|
911
|
+
chatId: interaction.channelId,
|
|
912
|
+
text,
|
|
913
|
+
timestamp: new Date(interaction.createdTimestamp),
|
|
914
|
+
threadId: isThread ? interaction.channelId : undefined,
|
|
915
|
+
isForumTopic: isThread,
|
|
916
|
+
raw: interaction,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Parse text to see if it's a command (starts with /)
|
|
921
|
+
*/
|
|
922
|
+
parseCommand(text) {
|
|
923
|
+
// Check if text starts with a command
|
|
924
|
+
const commandMatch = text.match(/^\/(\w+)(?:\s+(.*))?$/);
|
|
925
|
+
if (commandMatch) {
|
|
926
|
+
return text; // Already in command format
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
async handleIncomingMessage(message) {
|
|
931
|
+
for (const handler of this.messageHandlers) {
|
|
932
|
+
try {
|
|
933
|
+
await handler(message);
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
console.error('Error in message handler:', error);
|
|
937
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)), 'messageHandler');
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
handleError(error, context) {
|
|
942
|
+
for (const handler of this.errorHandlers) {
|
|
943
|
+
try {
|
|
944
|
+
handler(error, context);
|
|
945
|
+
}
|
|
946
|
+
catch (e) {
|
|
947
|
+
console.error('Error in error handler:', e);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
setStatus(status, error) {
|
|
952
|
+
this._status = status;
|
|
953
|
+
for (const handler of this.statusHandlers) {
|
|
954
|
+
try {
|
|
955
|
+
handler(status, error);
|
|
956
|
+
}
|
|
957
|
+
catch (e) {
|
|
958
|
+
console.error('Error in status handler:', e);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
exports.DiscordAdapter = DiscordAdapter;
|
|
964
|
+
/**
|
|
965
|
+
* Create a Discord adapter from configuration
|
|
966
|
+
*/
|
|
967
|
+
function createDiscordAdapter(config) {
|
|
968
|
+
if (!config.botToken) {
|
|
969
|
+
throw new Error('Discord bot token is required');
|
|
970
|
+
}
|
|
971
|
+
if (!config.applicationId) {
|
|
972
|
+
throw new Error('Discord application ID is required');
|
|
973
|
+
}
|
|
974
|
+
return new DiscordAdapter(config);
|
|
975
|
+
}
|