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,760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Chat Channel Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements the ChannelAdapter interface using Google Chat API.
|
|
5
|
+
* Supports direct messages, spaces, and threaded conversations.
|
|
6
|
+
*
|
|
7
|
+
* Authentication: Service Account with Domain-Wide Delegation
|
|
8
|
+
* Message reception: HTTP webhook or Pub/Sub subscription
|
|
9
|
+
* Message sending: Google Chat REST API
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as http from 'http';
|
|
13
|
+
import * as https from 'https';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import * as crypto from 'crypto';
|
|
17
|
+
import {
|
|
18
|
+
ChannelAdapter,
|
|
19
|
+
ChannelStatus,
|
|
20
|
+
IncomingMessage,
|
|
21
|
+
OutgoingMessage,
|
|
22
|
+
MessageHandler,
|
|
23
|
+
ErrorHandler,
|
|
24
|
+
StatusHandler,
|
|
25
|
+
ChannelInfo,
|
|
26
|
+
GoogleChatConfig,
|
|
27
|
+
} from './types';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Google Chat event types
|
|
31
|
+
*/
|
|
32
|
+
interface GoogleChatEvent {
|
|
33
|
+
type: 'MESSAGE' | 'ADDED_TO_SPACE' | 'REMOVED_FROM_SPACE' | 'CARD_CLICKED';
|
|
34
|
+
eventTime: string;
|
|
35
|
+
token?: string;
|
|
36
|
+
message?: GoogleChatMessage;
|
|
37
|
+
user?: GoogleChatUser;
|
|
38
|
+
space?: GoogleChatSpace;
|
|
39
|
+
action?: GoogleChatAction;
|
|
40
|
+
configCompleteRedirectUrl?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface GoogleChatMessage {
|
|
44
|
+
name: string;
|
|
45
|
+
sender: GoogleChatUser;
|
|
46
|
+
createTime: string;
|
|
47
|
+
text?: string;
|
|
48
|
+
formattedText?: string;
|
|
49
|
+
thread?: { name: string };
|
|
50
|
+
space: GoogleChatSpace;
|
|
51
|
+
argumentText?: string;
|
|
52
|
+
attachment?: GoogleChatAttachment[];
|
|
53
|
+
slashCommand?: { commandId: string };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface GoogleChatUser {
|
|
57
|
+
name: string;
|
|
58
|
+
displayName: string;
|
|
59
|
+
avatarUrl?: string;
|
|
60
|
+
email?: string;
|
|
61
|
+
type: 'HUMAN' | 'BOT';
|
|
62
|
+
domainId?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface GoogleChatSpace {
|
|
66
|
+
name: string;
|
|
67
|
+
type: 'ROOM' | 'DM' | 'SPACE';
|
|
68
|
+
displayName?: string;
|
|
69
|
+
spaceThreadingState?: string;
|
|
70
|
+
singleUserBotDm?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface GoogleChatAttachment {
|
|
74
|
+
name: string;
|
|
75
|
+
contentName: string;
|
|
76
|
+
contentType: string;
|
|
77
|
+
thumbnailUri?: string;
|
|
78
|
+
downloadUri?: string;
|
|
79
|
+
source: 'DRIVE_FILE' | 'UPLOADED_CONTENT';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface GoogleChatAction {
|
|
83
|
+
actionMethodName: string;
|
|
84
|
+
parameters?: Array<{ key: string; value: string }>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Simple TTL cache for message deduplication
|
|
89
|
+
*/
|
|
90
|
+
class MessageDeduplicationCache {
|
|
91
|
+
private cache: Map<string, number> = new Map();
|
|
92
|
+
private readonly ttlMs: number;
|
|
93
|
+
private cleanupInterval: NodeJS.Timeout;
|
|
94
|
+
|
|
95
|
+
constructor(ttlMs: number = 60000) {
|
|
96
|
+
this.ttlMs = ttlMs;
|
|
97
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
has(messageId: string): boolean {
|
|
101
|
+
const timestamp = this.cache.get(messageId);
|
|
102
|
+
if (!timestamp) return false;
|
|
103
|
+
if (Date.now() - timestamp > this.ttlMs) {
|
|
104
|
+
this.cache.delete(messageId);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
add(messageId: string): void {
|
|
111
|
+
this.cache.set(messageId, Date.now());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private cleanup(): void {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
for (const [key, timestamp] of this.cache.entries()) {
|
|
117
|
+
if (now - timestamp > this.ttlMs) {
|
|
118
|
+
this.cache.delete(key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
destroy(): void {
|
|
124
|
+
clearInterval(this.cleanupInterval);
|
|
125
|
+
this.cache.clear();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Google OAuth2 token manager for service account
|
|
131
|
+
*/
|
|
132
|
+
class GoogleAuthManager {
|
|
133
|
+
private accessToken: string | null = null;
|
|
134
|
+
private tokenExpiry: number = 0;
|
|
135
|
+
private credentials: {
|
|
136
|
+
client_email: string;
|
|
137
|
+
private_key: string;
|
|
138
|
+
project_id: string;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
constructor(credentials: { client_email: string; private_key: string; project_id: string }) {
|
|
142
|
+
this.credentials = credentials;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getAccessToken(): Promise<string> {
|
|
146
|
+
if (this.accessToken && Date.now() < this.tokenExpiry - 60000) {
|
|
147
|
+
return this.accessToken;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const jwt = this.createJWT();
|
|
151
|
+
const token = await this.exchangeJWTForToken(jwt);
|
|
152
|
+
this.accessToken = token.access_token;
|
|
153
|
+
this.tokenExpiry = Date.now() + (token.expires_in * 1000);
|
|
154
|
+
return this.accessToken;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private createJWT(): string {
|
|
158
|
+
const header = {
|
|
159
|
+
alg: 'RS256',
|
|
160
|
+
typ: 'JWT',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const now = Math.floor(Date.now() / 1000);
|
|
164
|
+
const payload = {
|
|
165
|
+
iss: this.credentials.client_email,
|
|
166
|
+
scope: 'https://www.googleapis.com/auth/chat.bot',
|
|
167
|
+
aud: 'https://oauth2.googleapis.com/token',
|
|
168
|
+
iat: now,
|
|
169
|
+
exp: now + 3600,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
173
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
174
|
+
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
175
|
+
|
|
176
|
+
const sign = crypto.createSign('RSA-SHA256');
|
|
177
|
+
sign.update(signatureInput);
|
|
178
|
+
const signature = sign.sign(this.credentials.private_key, 'base64url');
|
|
179
|
+
|
|
180
|
+
return `${signatureInput}.${signature}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async exchangeJWTForToken(jwt: string): Promise<{ access_token: string; expires_in: number }> {
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const postData = new URLSearchParams({
|
|
186
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
187
|
+
assertion: jwt,
|
|
188
|
+
}).toString();
|
|
189
|
+
|
|
190
|
+
const options = {
|
|
191
|
+
hostname: 'oauth2.googleapis.com',
|
|
192
|
+
port: 443,
|
|
193
|
+
path: '/token',
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: {
|
|
196
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
197
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const req = https.request(options, (res) => {
|
|
202
|
+
let data = '';
|
|
203
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
204
|
+
res.on('end', () => {
|
|
205
|
+
try {
|
|
206
|
+
const parsed = JSON.parse(data);
|
|
207
|
+
if (parsed.error) {
|
|
208
|
+
reject(new Error(`Token exchange failed: ${parsed.error_description || parsed.error}`));
|
|
209
|
+
} else {
|
|
210
|
+
resolve(parsed);
|
|
211
|
+
}
|
|
212
|
+
} catch (e) {
|
|
213
|
+
reject(new Error(`Failed to parse token response: ${data}`));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
req.on('error', reject);
|
|
219
|
+
req.write(postData);
|
|
220
|
+
req.end();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export class GoogleChatAdapter implements ChannelAdapter {
|
|
226
|
+
readonly type = 'googlechat' as const;
|
|
227
|
+
|
|
228
|
+
private server: http.Server | null = null;
|
|
229
|
+
private authManager: GoogleAuthManager | null = null;
|
|
230
|
+
private _status: ChannelStatus = 'disconnected';
|
|
231
|
+
private _botUsername?: string;
|
|
232
|
+
private messageHandlers: MessageHandler[] = [];
|
|
233
|
+
private errorHandlers: ErrorHandler[] = [];
|
|
234
|
+
private statusHandlers: StatusHandler[] = [];
|
|
235
|
+
private config: GoogleChatConfig;
|
|
236
|
+
private deduplicationCache: MessageDeduplicationCache;
|
|
237
|
+
private reconnectAttempts = 0;
|
|
238
|
+
private reconnectTimer: NodeJS.Timeout | null = null;
|
|
239
|
+
|
|
240
|
+
constructor(config: GoogleChatConfig) {
|
|
241
|
+
this.config = config;
|
|
242
|
+
this.deduplicationCache = new MessageDeduplicationCache();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
get status(): ChannelStatus {
|
|
246
|
+
return this._status;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
get botUsername(): string | undefined {
|
|
250
|
+
return this._botUsername;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Connect to Google Chat via webhook server
|
|
255
|
+
*/
|
|
256
|
+
async connect(): Promise<void> {
|
|
257
|
+
if (this._status === 'connected' || this._status === 'connecting') {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.setStatus('connecting');
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
// Load service account credentials
|
|
265
|
+
const credentials = await this.loadCredentials();
|
|
266
|
+
this.authManager = new GoogleAuthManager(credentials);
|
|
267
|
+
|
|
268
|
+
// Verify credentials by getting a token
|
|
269
|
+
await this.authManager.getAccessToken();
|
|
270
|
+
|
|
271
|
+
// Set bot info from config
|
|
272
|
+
this._botUsername = this.config.displayName || 'Google Chat Bot';
|
|
273
|
+
|
|
274
|
+
// Start the HTTP server for receiving webhooks
|
|
275
|
+
await this.startWebhookServer();
|
|
276
|
+
|
|
277
|
+
console.log(`Google Chat bot "${this._botUsername}" is connected on port ${this.config.webhookPort || 3979}`);
|
|
278
|
+
this.setStatus('connected');
|
|
279
|
+
this.reconnectAttempts = 0;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
282
|
+
this.setStatus('error', err);
|
|
283
|
+
this.scheduleReconnect();
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Load service account credentials
|
|
290
|
+
*/
|
|
291
|
+
private async loadCredentials(): Promise<{ client_email: string; private_key: string; project_id: string }> {
|
|
292
|
+
// Check for inline credentials first
|
|
293
|
+
if (this.config.serviceAccountKey) {
|
|
294
|
+
return this.config.serviceAccountKey;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check for key file path
|
|
298
|
+
if (this.config.serviceAccountKeyPath) {
|
|
299
|
+
const keyPath = this.config.serviceAccountKeyPath;
|
|
300
|
+
if (!fs.existsSync(keyPath)) {
|
|
301
|
+
throw new Error(`Service account key file not found: ${keyPath}`);
|
|
302
|
+
}
|
|
303
|
+
const keyContent = fs.readFileSync(keyPath, 'utf-8');
|
|
304
|
+
const key = JSON.parse(keyContent);
|
|
305
|
+
return {
|
|
306
|
+
client_email: key.client_email,
|
|
307
|
+
private_key: key.private_key,
|
|
308
|
+
project_id: key.project_id,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check for GOOGLE_APPLICATION_CREDENTIALS environment variable
|
|
313
|
+
const envPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
314
|
+
if (envPath && fs.existsSync(envPath)) {
|
|
315
|
+
const keyContent = fs.readFileSync(envPath, 'utf-8');
|
|
316
|
+
const key = JSON.parse(keyContent);
|
|
317
|
+
return {
|
|
318
|
+
client_email: key.client_email,
|
|
319
|
+
private_key: key.private_key,
|
|
320
|
+
project_id: key.project_id,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
throw new Error('Google Chat credentials not configured. Provide serviceAccountKey or serviceAccountKeyPath.');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Start the webhook server to receive messages from Google Chat
|
|
329
|
+
*/
|
|
330
|
+
private async startWebhookServer(): Promise<void> {
|
|
331
|
+
const port = this.config.webhookPort || 3979;
|
|
332
|
+
const webhookPath = this.config.webhookPath || '/googlechat/webhook';
|
|
333
|
+
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
this.server = http.createServer(async (req, res) => {
|
|
336
|
+
if (req.method === 'POST' && req.url === webhookPath) {
|
|
337
|
+
let body = '';
|
|
338
|
+
req.on('data', chunk => {
|
|
339
|
+
body += chunk.toString();
|
|
340
|
+
});
|
|
341
|
+
req.on('end', async () => {
|
|
342
|
+
try {
|
|
343
|
+
await this.processIncomingEvent(req, res, body);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error('Error processing Google Chat event:', error);
|
|
346
|
+
res.writeHead(500);
|
|
347
|
+
res.end(JSON.stringify({ error: 'Internal Server Error' }));
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
} else if (req.method === 'GET' && req.url === '/health') {
|
|
351
|
+
// Health check endpoint
|
|
352
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
353
|
+
res.end(JSON.stringify({ status: 'ok', bot: this._botUsername }));
|
|
354
|
+
} else {
|
|
355
|
+
res.writeHead(404);
|
|
356
|
+
res.end('Not Found');
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
this.server.on('error', (error: NodeJS.ErrnoException) => {
|
|
361
|
+
if (error.code === 'EADDRINUSE') {
|
|
362
|
+
reject(new Error(`Port ${port} is already in use. Please choose a different webhook port.`));
|
|
363
|
+
} else if (error.code === 'EACCES') {
|
|
364
|
+
reject(new Error(`Permission denied to use port ${port}. Try a port above 1024.`));
|
|
365
|
+
} else {
|
|
366
|
+
reject(error);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
this.server.listen(port, () => {
|
|
371
|
+
console.log(`Google Chat webhook server listening on port ${port} at ${webhookPath}`);
|
|
372
|
+
resolve();
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Process incoming event from Google Chat
|
|
379
|
+
*/
|
|
380
|
+
private async processIncomingEvent(
|
|
381
|
+
req: http.IncomingMessage,
|
|
382
|
+
res: http.ServerResponse,
|
|
383
|
+
body: string
|
|
384
|
+
): Promise<void> {
|
|
385
|
+
try {
|
|
386
|
+
const event: GoogleChatEvent = JSON.parse(body);
|
|
387
|
+
|
|
388
|
+
// Handle different event types
|
|
389
|
+
switch (event.type) {
|
|
390
|
+
case 'MESSAGE':
|
|
391
|
+
await this.handleMessage(event);
|
|
392
|
+
// Respond with empty JSON to acknowledge
|
|
393
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
394
|
+
res.end('{}');
|
|
395
|
+
break;
|
|
396
|
+
|
|
397
|
+
case 'ADDED_TO_SPACE':
|
|
398
|
+
console.log(`Bot added to space: ${event.space?.displayName || event.space?.name}`);
|
|
399
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
400
|
+
res.end(JSON.stringify({
|
|
401
|
+
text: `Hello! I'm ${this._botUsername}. How can I help you today?`,
|
|
402
|
+
}));
|
|
403
|
+
break;
|
|
404
|
+
|
|
405
|
+
case 'REMOVED_FROM_SPACE':
|
|
406
|
+
console.log(`Bot removed from space: ${event.space?.displayName || event.space?.name}`);
|
|
407
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
408
|
+
res.end('{}');
|
|
409
|
+
break;
|
|
410
|
+
|
|
411
|
+
case 'CARD_CLICKED':
|
|
412
|
+
// Handle interactive card clicks
|
|
413
|
+
console.log(`Card clicked: ${event.action?.actionMethodName}`);
|
|
414
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
415
|
+
res.end('{}');
|
|
416
|
+
break;
|
|
417
|
+
|
|
418
|
+
default:
|
|
419
|
+
console.log(`Unhandled Google Chat event type: ${event.type}`);
|
|
420
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
421
|
+
res.end('{}');
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error('Error parsing Google Chat event:', error);
|
|
425
|
+
res.writeHead(400);
|
|
426
|
+
res.end(JSON.stringify({ error: 'Bad Request' }));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Handle incoming message event
|
|
432
|
+
*/
|
|
433
|
+
private async handleMessage(event: GoogleChatEvent): Promise<void> {
|
|
434
|
+
const message = event.message;
|
|
435
|
+
if (!message) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Skip bot's own messages
|
|
440
|
+
if (message.sender?.type === 'BOT') {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Get message text (use argumentText for @mentions, otherwise use text)
|
|
445
|
+
const text = message.argumentText?.trim() || message.text?.trim();
|
|
446
|
+
if (!text) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Extract message ID from name (format: spaces/{space}/messages/{message})
|
|
451
|
+
const messageId = message.name.split('/').pop() || message.name;
|
|
452
|
+
|
|
453
|
+
// Deduplication check
|
|
454
|
+
if (this.config.deduplicationEnabled !== false && this.deduplicationCache.has(messageId)) {
|
|
455
|
+
console.log(`Skipping duplicate Google Chat message: ${messageId}`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.deduplicationCache.add(messageId);
|
|
459
|
+
|
|
460
|
+
// Extract space ID (format: spaces/{space})
|
|
461
|
+
const spaceId = message.space.name;
|
|
462
|
+
|
|
463
|
+
// Get user info
|
|
464
|
+
const userName = message.sender?.displayName || 'Unknown User';
|
|
465
|
+
const userId = message.sender?.name || '';
|
|
466
|
+
|
|
467
|
+
// Map to IncomingMessage format
|
|
468
|
+
const incomingMessage: IncomingMessage = {
|
|
469
|
+
messageId: messageId,
|
|
470
|
+
channel: 'googlechat',
|
|
471
|
+
userId: userId,
|
|
472
|
+
userName: userName,
|
|
473
|
+
chatId: spaceId,
|
|
474
|
+
text: text,
|
|
475
|
+
timestamp: message.createTime ? new Date(message.createTime) : new Date(),
|
|
476
|
+
threadId: message.thread?.name,
|
|
477
|
+
raw: event,
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
console.log(`Processing Google Chat message from ${userName}: ${text.slice(0, 50)}...`);
|
|
481
|
+
await this.handleIncomingMessage(incomingMessage);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Schedule reconnection with exponential backoff
|
|
486
|
+
*/
|
|
487
|
+
private scheduleReconnect(): void {
|
|
488
|
+
if (!this.config.autoReconnect) return;
|
|
489
|
+
|
|
490
|
+
const maxAttempts = this.config.maxReconnectAttempts || 5;
|
|
491
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
492
|
+
console.error(`Google Chat: Max reconnection attempts (${maxAttempts}) reached`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
497
|
+
this.reconnectAttempts++;
|
|
498
|
+
|
|
499
|
+
console.log(`Google Chat: Scheduling reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
500
|
+
|
|
501
|
+
if (this.reconnectTimer) {
|
|
502
|
+
clearTimeout(this.reconnectTimer);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
506
|
+
try {
|
|
507
|
+
await this.connect();
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.error('Google Chat reconnection failed:', error);
|
|
510
|
+
}
|
|
511
|
+
}, delay);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Disconnect from Google Chat
|
|
516
|
+
*/
|
|
517
|
+
async disconnect(): Promise<void> {
|
|
518
|
+
if (this.reconnectTimer) {
|
|
519
|
+
clearTimeout(this.reconnectTimer);
|
|
520
|
+
this.reconnectTimer = null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (this.server) {
|
|
524
|
+
await new Promise<void>((resolve) => {
|
|
525
|
+
this.server!.close(() => resolve());
|
|
526
|
+
});
|
|
527
|
+
this.server = null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
this.deduplicationCache.destroy();
|
|
531
|
+
this.authManager = null;
|
|
532
|
+
this._botUsername = undefined;
|
|
533
|
+
this.setStatus('disconnected');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Send a message to a Google Chat space
|
|
538
|
+
*/
|
|
539
|
+
async sendMessage(message: OutgoingMessage): Promise<string> {
|
|
540
|
+
if (!this.authManager || this._status !== 'connected') {
|
|
541
|
+
throw new Error('Google Chat bot is not connected');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const accessToken = await this.authManager.getAccessToken();
|
|
545
|
+
|
|
546
|
+
// Prepare message payload
|
|
547
|
+
const payload: Record<string, unknown> = {
|
|
548
|
+
text: message.text,
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// Add thread if specified
|
|
552
|
+
if (message.threadId) {
|
|
553
|
+
payload.thread = { name: message.threadId };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Build the API URL
|
|
557
|
+
// Format: spaces/{space}/messages
|
|
558
|
+
const apiUrl = `https://chat.googleapis.com/v1/${message.chatId}/messages`;
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
const response = await this.makeApiRequest('POST', apiUrl, accessToken, payload);
|
|
562
|
+
return (response.name as string) || '';
|
|
563
|
+
} catch (error: unknown) {
|
|
564
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
565
|
+
console.error('Error sending Google Chat message:', errorMessage);
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Make an authenticated API request to Google Chat
|
|
572
|
+
*/
|
|
573
|
+
private async makeApiRequest(
|
|
574
|
+
method: string,
|
|
575
|
+
url: string,
|
|
576
|
+
accessToken: string,
|
|
577
|
+
body?: Record<string, unknown>
|
|
578
|
+
): Promise<Record<string, unknown>> {
|
|
579
|
+
return new Promise((resolve, reject) => {
|
|
580
|
+
const urlObj = new URL(url);
|
|
581
|
+
const postData = body ? JSON.stringify(body) : undefined;
|
|
582
|
+
|
|
583
|
+
const options: https.RequestOptions = {
|
|
584
|
+
hostname: urlObj.hostname,
|
|
585
|
+
port: 443,
|
|
586
|
+
path: urlObj.pathname + urlObj.search,
|
|
587
|
+
method: method,
|
|
588
|
+
headers: {
|
|
589
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
590
|
+
'Content-Type': 'application/json',
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
if (postData) {
|
|
595
|
+
(options.headers as Record<string, string | number>)['Content-Length'] = Buffer.byteLength(postData);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const req = https.request(options, (res) => {
|
|
599
|
+
let data = '';
|
|
600
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
601
|
+
res.on('end', () => {
|
|
602
|
+
try {
|
|
603
|
+
const parsed = JSON.parse(data);
|
|
604
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
605
|
+
reject(new Error(`API error ${res.statusCode}: ${parsed.error?.message || data}`));
|
|
606
|
+
} else {
|
|
607
|
+
resolve(parsed);
|
|
608
|
+
}
|
|
609
|
+
} catch (e) {
|
|
610
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
611
|
+
reject(new Error(`API error ${res.statusCode}: ${data}`));
|
|
612
|
+
} else {
|
|
613
|
+
resolve({});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
req.on('error', reject);
|
|
620
|
+
if (postData) {
|
|
621
|
+
req.write(postData);
|
|
622
|
+
}
|
|
623
|
+
req.end();
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Edit an existing message
|
|
629
|
+
*/
|
|
630
|
+
async editMessage(chatId: string, messageId: string, text: string): Promise<void> {
|
|
631
|
+
if (!this.authManager || this._status !== 'connected') {
|
|
632
|
+
throw new Error('Google Chat bot is not connected');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const accessToken = await this.authManager.getAccessToken();
|
|
636
|
+
|
|
637
|
+
// Build the message name (format: spaces/{space}/messages/{message})
|
|
638
|
+
const messageName = messageId.includes('/') ? messageId : `${chatId}/messages/${messageId}`;
|
|
639
|
+
const apiUrl = `https://chat.googleapis.com/v1/${messageName}?updateMask=text`;
|
|
640
|
+
|
|
641
|
+
const payload = { text };
|
|
642
|
+
|
|
643
|
+
await this.makeApiRequest('PATCH', apiUrl, accessToken, payload);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Delete a message
|
|
648
|
+
*/
|
|
649
|
+
async deleteMessage(chatId: string, messageId: string): Promise<void> {
|
|
650
|
+
if (!this.authManager || this._status !== 'connected') {
|
|
651
|
+
throw new Error('Google Chat bot is not connected');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const accessToken = await this.authManager.getAccessToken();
|
|
655
|
+
|
|
656
|
+
// Build the message name
|
|
657
|
+
const messageName = messageId.includes('/') ? messageId : `${chatId}/messages/${messageId}`;
|
|
658
|
+
const apiUrl = `https://chat.googleapis.com/v1/${messageName}`;
|
|
659
|
+
|
|
660
|
+
await this.makeApiRequest('DELETE', apiUrl, accessToken);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Send a document/file to a space
|
|
665
|
+
* Note: Google Chat has limited file attachment support via API
|
|
666
|
+
*/
|
|
667
|
+
async sendDocument(chatId: string, filePath: string, caption?: string): Promise<string> {
|
|
668
|
+
// Google Chat API doesn't support direct file uploads
|
|
669
|
+
// Files must be uploaded to Google Drive first
|
|
670
|
+
// For now, send a message with the file path/name
|
|
671
|
+
const fileName = path.basename(filePath);
|
|
672
|
+
const message = caption ? `${caption}\nš ${fileName}` : `š ${fileName}`;
|
|
673
|
+
|
|
674
|
+
return this.sendMessage({
|
|
675
|
+
chatId,
|
|
676
|
+
text: message,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Register a message handler
|
|
682
|
+
*/
|
|
683
|
+
onMessage(handler: MessageHandler): void {
|
|
684
|
+
this.messageHandlers.push(handler);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Register an error handler
|
|
689
|
+
*/
|
|
690
|
+
onError(handler: ErrorHandler): void {
|
|
691
|
+
this.errorHandlers.push(handler);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Register a status change handler
|
|
696
|
+
*/
|
|
697
|
+
onStatusChange(handler: StatusHandler): void {
|
|
698
|
+
this.statusHandlers.push(handler);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Get channel info
|
|
703
|
+
*/
|
|
704
|
+
async getInfo(): Promise<ChannelInfo> {
|
|
705
|
+
return {
|
|
706
|
+
type: 'googlechat',
|
|
707
|
+
status: this._status,
|
|
708
|
+
botUsername: this._botUsername,
|
|
709
|
+
botDisplayName: this._botUsername,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Private helper methods
|
|
714
|
+
|
|
715
|
+
private async handleIncomingMessage(message: IncomingMessage): Promise<void> {
|
|
716
|
+
for (const handler of this.messageHandlers) {
|
|
717
|
+
try {
|
|
718
|
+
await handler(message);
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.error('Error in message handler:', error);
|
|
721
|
+
this.handleError(
|
|
722
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
723
|
+
'messageHandler'
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private handleError(error: Error, context?: string): void {
|
|
730
|
+
for (const handler of this.errorHandlers) {
|
|
731
|
+
try {
|
|
732
|
+
handler(error, context);
|
|
733
|
+
} catch (e) {
|
|
734
|
+
console.error('Error in error handler:', e);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
private setStatus(status: ChannelStatus, error?: Error): void {
|
|
740
|
+
this._status = status;
|
|
741
|
+
for (const handler of this.statusHandlers) {
|
|
742
|
+
try {
|
|
743
|
+
handler(status, error);
|
|
744
|
+
} catch (e) {
|
|
745
|
+
console.error('Error in status handler:', e);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Create a Google Chat adapter from configuration
|
|
753
|
+
*/
|
|
754
|
+
export function createGoogleChatAdapter(config: GoogleChatConfig): GoogleChatAdapter {
|
|
755
|
+
// At least one credential source must be provided
|
|
756
|
+
if (!config.serviceAccountKey && !config.serviceAccountKeyPath && !process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
757
|
+
throw new Error('Google Chat requires service account credentials (serviceAccountKey, serviceAccountKeyPath, or GOOGLE_APPLICATION_CREDENTIALS)');
|
|
758
|
+
}
|
|
759
|
+
return new GoogleChatAdapter(config);
|
|
760
|
+
}
|