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.
Files changed (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1638 -0
  3. package/bin/cowork.js +42 -0
  4. package/build/entitlements.mac.plist +16 -0
  5. package/build/icon.icns +0 -0
  6. package/build/icon.png +0 -0
  7. package/dist/electron/electron/activity/ActivityRepository.js +190 -0
  8. package/dist/electron/electron/agent/browser/browser-service.js +639 -0
  9. package/dist/electron/electron/agent/context-manager.js +225 -0
  10. package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
  11. package/dist/electron/electron/agent/daemon.js +975 -0
  12. package/dist/electron/electron/agent/executor.js +3561 -0
  13. package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
  14. package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
  15. package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
  16. package/dist/electron/electron/agent/llm/index.js +34 -0
  17. package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
  18. package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
  19. package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
  20. package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
  21. package/dist/electron/electron/agent/llm/pricing.js +160 -0
  22. package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
  23. package/dist/electron/electron/agent/llm/types.js +178 -0
  24. package/dist/electron/electron/agent/queue-manager.js +378 -0
  25. package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
  26. package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
  27. package/dist/electron/electron/agent/sandbox/runner.js +410 -0
  28. package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
  29. package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
  30. package/dist/electron/electron/agent/search/brave-provider.js +119 -0
  31. package/dist/electron/electron/agent/search/google-provider.js +100 -0
  32. package/dist/electron/electron/agent/search/index.js +28 -0
  33. package/dist/electron/electron/agent/search/provider-factory.js +395 -0
  34. package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
  35. package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
  36. package/dist/electron/electron/agent/search/types.js +40 -0
  37. package/dist/electron/electron/agent/security/index.js +12 -0
  38. package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
  39. package/dist/electron/electron/agent/security/output-filter.js +217 -0
  40. package/dist/electron/electron/agent/skill-eligibility.js +281 -0
  41. package/dist/electron/electron/agent/skill-registry.js +396 -0
  42. package/dist/electron/electron/agent/skills/document.js +878 -0
  43. package/dist/electron/electron/agent/skills/image-generator.js +225 -0
  44. package/dist/electron/electron/agent/skills/organizer.js +141 -0
  45. package/dist/electron/electron/agent/skills/presentation.js +367 -0
  46. package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
  47. package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
  48. package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
  49. package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
  50. package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
  51. package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
  52. package/dist/electron/electron/agent/tools/file-tools.js +719 -0
  53. package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
  54. package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
  55. package/dist/electron/electron/agent/tools/image-tools.js +111 -0
  56. package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
  57. package/dist/electron/electron/agent/tools/node-tools.js +476 -0
  58. package/dist/electron/electron/agent/tools/registry.js +2719 -0
  59. package/dist/electron/electron/agent/tools/search-tools.js +91 -0
  60. package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
  61. package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
  62. package/dist/electron/electron/agent/tools/system-tools.js +578 -0
  63. package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
  64. package/dist/electron/electron/agent/tools/x-tools.js +264 -0
  65. package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
  66. package/dist/electron/electron/agents/HeartbeatService.js +356 -0
  67. package/dist/electron/electron/agents/MentionRepository.js +197 -0
  68. package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
  69. package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
  70. package/dist/electron/electron/canvas/canvas-manager.js +714 -0
  71. package/dist/electron/electron/canvas/canvas-preload.js +53 -0
  72. package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
  73. package/dist/electron/electron/canvas/canvas-store.js +174 -0
  74. package/dist/electron/electron/canvas/index.js +13 -0
  75. package/dist/electron/electron/control-plane/client.js +364 -0
  76. package/dist/electron/electron/control-plane/handlers.js +572 -0
  77. package/dist/electron/electron/control-plane/index.js +41 -0
  78. package/dist/electron/electron/control-plane/node-manager.js +264 -0
  79. package/dist/electron/electron/control-plane/protocol.js +194 -0
  80. package/dist/electron/electron/control-plane/remote-client.js +437 -0
  81. package/dist/electron/electron/control-plane/server.js +640 -0
  82. package/dist/electron/electron/control-plane/settings.js +369 -0
  83. package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
  84. package/dist/electron/electron/cron/index.js +30 -0
  85. package/dist/electron/electron/cron/schedule.js +190 -0
  86. package/dist/electron/electron/cron/service.js +614 -0
  87. package/dist/electron/electron/cron/store.js +155 -0
  88. package/dist/electron/electron/cron/types.js +82 -0
  89. package/dist/electron/electron/cron/webhook.js +258 -0
  90. package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
  91. package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
  92. package/dist/electron/electron/database/repositories.js +1781 -0
  93. package/dist/electron/electron/database/schema.js +978 -0
  94. package/dist/electron/electron/extensions/index.js +33 -0
  95. package/dist/electron/electron/extensions/loader.js +313 -0
  96. package/dist/electron/electron/extensions/registry.js +485 -0
  97. package/dist/electron/electron/extensions/types.js +11 -0
  98. package/dist/electron/electron/gateway/channel-registry.js +1102 -0
  99. package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
  100. package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
  101. package/dist/electron/electron/gateway/channels/discord.js +975 -0
  102. package/dist/electron/electron/gateway/channels/email-client.js +593 -0
  103. package/dist/electron/electron/gateway/channels/email.js +443 -0
  104. package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
  105. package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
  106. package/dist/electron/electron/gateway/channels/imessage.js +465 -0
  107. package/dist/electron/electron/gateway/channels/index.js +36 -0
  108. package/dist/electron/electron/gateway/channels/line-client.js +470 -0
  109. package/dist/electron/electron/gateway/channels/line.js +479 -0
  110. package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
  111. package/dist/electron/electron/gateway/channels/matrix.js +592 -0
  112. package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
  113. package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
  114. package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
  115. package/dist/electron/electron/gateway/channels/signal.js +582 -0
  116. package/dist/electron/electron/gateway/channels/slack.js +415 -0
  117. package/dist/electron/electron/gateway/channels/teams.js +596 -0
  118. package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
  119. package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
  120. package/dist/electron/electron/gateway/channels/twitch.js +396 -0
  121. package/dist/electron/electron/gateway/channels/types.js +8 -0
  122. package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
  123. package/dist/electron/electron/gateway/context-policy.js +268 -0
  124. package/dist/electron/electron/gateway/index.js +1063 -0
  125. package/dist/electron/electron/gateway/infrastructure.js +496 -0
  126. package/dist/electron/electron/gateway/router.js +2700 -0
  127. package/dist/electron/electron/gateway/security.js +375 -0
  128. package/dist/electron/electron/gateway/session.js +115 -0
  129. package/dist/electron/electron/gateway/tunnel.js +503 -0
  130. package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
  131. package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
  132. package/dist/electron/electron/hooks/index.js +46 -0
  133. package/dist/electron/electron/hooks/mappings.js +381 -0
  134. package/dist/electron/electron/hooks/server.js +480 -0
  135. package/dist/electron/electron/hooks/settings.js +447 -0
  136. package/dist/electron/electron/hooks/types.js +41 -0
  137. package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
  138. package/dist/electron/electron/ipc/handlers.js +3138 -0
  139. package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
  140. package/dist/electron/electron/main.js +448 -0
  141. package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
  142. package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
  143. package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
  144. package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
  145. package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
  146. package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
  147. package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
  148. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
  149. package/dist/electron/electron/mcp/settings.js +446 -0
  150. package/dist/electron/electron/mcp/types.js +59 -0
  151. package/dist/electron/electron/memory/MemoryService.js +435 -0
  152. package/dist/electron/electron/notifications/index.js +17 -0
  153. package/dist/electron/electron/notifications/service.js +118 -0
  154. package/dist/electron/electron/notifications/store.js +144 -0
  155. package/dist/electron/electron/preload.js +842 -0
  156. package/dist/electron/electron/reports/StandupReportService.js +272 -0
  157. package/dist/electron/electron/security/concurrency.js +293 -0
  158. package/dist/electron/electron/security/index.js +15 -0
  159. package/dist/electron/electron/security/policy-manager.js +435 -0
  160. package/dist/electron/electron/settings/appearance-manager.js +193 -0
  161. package/dist/electron/electron/settings/personality-manager.js +724 -0
  162. package/dist/electron/electron/settings/x-manager.js +58 -0
  163. package/dist/electron/electron/tailscale/exposure.js +188 -0
  164. package/dist/electron/electron/tailscale/index.js +28 -0
  165. package/dist/electron/electron/tailscale/settings.js +205 -0
  166. package/dist/electron/electron/tailscale/tailscale.js +355 -0
  167. package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
  168. package/dist/electron/electron/tray/TrayManager.js +895 -0
  169. package/dist/electron/electron/tray/index.js +9 -0
  170. package/dist/electron/electron/updater/index.js +6 -0
  171. package/dist/electron/electron/updater/update-manager.js +418 -0
  172. package/dist/electron/electron/utils/env-migration.js +209 -0
  173. package/dist/electron/electron/utils/process.js +102 -0
  174. package/dist/electron/electron/utils/rate-limiter.js +104 -0
  175. package/dist/electron/electron/utils/validation.js +419 -0
  176. package/dist/electron/electron/utils/x-cli.js +177 -0
  177. package/dist/electron/electron/voice/VoiceService.js +507 -0
  178. package/dist/electron/electron/voice/index.js +14 -0
  179. package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
  180. package/dist/electron/shared/channelMessages.js +170 -0
  181. package/dist/electron/shared/types.js +1185 -0
  182. package/package.json +159 -0
  183. package/resources/skills/1password.json +10 -0
  184. package/resources/skills/add-documentation.json +31 -0
  185. package/resources/skills/analyze-csv.json +17 -0
  186. package/resources/skills/apple-notes.json +10 -0
  187. package/resources/skills/apple-reminders.json +10 -0
  188. package/resources/skills/auto-commenter.json +10 -0
  189. package/resources/skills/bear-notes.json +10 -0
  190. package/resources/skills/bird.json +35 -0
  191. package/resources/skills/blogwatcher.json +10 -0
  192. package/resources/skills/blucli.json +10 -0
  193. package/resources/skills/bluebubbles.json +10 -0
  194. package/resources/skills/camsnap.json +10 -0
  195. package/resources/skills/clean-imports.json +18 -0
  196. package/resources/skills/code-review.json +18 -0
  197. package/resources/skills/coding-agent.json +10 -0
  198. package/resources/skills/compare-files.json +23 -0
  199. package/resources/skills/convert-code.json +34 -0
  200. package/resources/skills/create-changelog.json +24 -0
  201. package/resources/skills/debug-error.json +17 -0
  202. package/resources/skills/dependency-check.json +10 -0
  203. package/resources/skills/discord.json +10 -0
  204. package/resources/skills/eightctl.json +10 -0
  205. package/resources/skills/explain-code.json +29 -0
  206. package/resources/skills/extract-todos.json +18 -0
  207. package/resources/skills/food-order.json +10 -0
  208. package/resources/skills/gemini.json +10 -0
  209. package/resources/skills/generate-readme.json +10 -0
  210. package/resources/skills/gifgrep.json +10 -0
  211. package/resources/skills/git-commit.json +10 -0
  212. package/resources/skills/github.json +10 -0
  213. package/resources/skills/gog.json +10 -0
  214. package/resources/skills/goplaces.json +10 -0
  215. package/resources/skills/himalaya.json +10 -0
  216. package/resources/skills/imsg.json +10 -0
  217. package/resources/skills/karpathy-guidelines.json +12 -0
  218. package/resources/skills/last30days.json +26 -0
  219. package/resources/skills/local-places.json +10 -0
  220. package/resources/skills/mcporter.json +10 -0
  221. package/resources/skills/model-usage.json +10 -0
  222. package/resources/skills/nano-banana-pro.json +10 -0
  223. package/resources/skills/nano-pdf.json +10 -0
  224. package/resources/skills/notion.json +10 -0
  225. package/resources/skills/obsidian.json +10 -0
  226. package/resources/skills/openai-image-gen.json +10 -0
  227. package/resources/skills/openai-whisper-api.json +10 -0
  228. package/resources/skills/openai-whisper.json +10 -0
  229. package/resources/skills/openhue.json +10 -0
  230. package/resources/skills/oracle.json +10 -0
  231. package/resources/skills/ordercli.json +10 -0
  232. package/resources/skills/peekaboo.json +10 -0
  233. package/resources/skills/project-structure.json +10 -0
  234. package/resources/skills/proofread.json +17 -0
  235. package/resources/skills/refactor-code.json +31 -0
  236. package/resources/skills/rename-symbol.json +23 -0
  237. package/resources/skills/sag.json +10 -0
  238. package/resources/skills/security-audit.json +18 -0
  239. package/resources/skills/session-logs.json +10 -0
  240. package/resources/skills/sherpa-onnx-tts.json +10 -0
  241. package/resources/skills/skill-creator.json +15 -0
  242. package/resources/skills/skill-hub.json +29 -0
  243. package/resources/skills/slack.json +10 -0
  244. package/resources/skills/songsee.json +10 -0
  245. package/resources/skills/sonoscli.json +10 -0
  246. package/resources/skills/spotify-player.json +10 -0
  247. package/resources/skills/startup-cfo.json +55 -0
  248. package/resources/skills/summarize-folder.json +18 -0
  249. package/resources/skills/summarize.json +10 -0
  250. package/resources/skills/things-mac.json +10 -0
  251. package/resources/skills/tmux.json +10 -0
  252. package/resources/skills/translate.json +36 -0
  253. package/resources/skills/trello.json +10 -0
  254. package/resources/skills/video-frames.json +10 -0
  255. package/resources/skills/voice-call.json +10 -0
  256. package/resources/skills/wacli.json +10 -0
  257. package/resources/skills/weather.json +10 -0
  258. package/resources/skills/write-tests.json +31 -0
  259. package/src/electron/activity/ActivityRepository.ts +238 -0
  260. package/src/electron/agent/browser/browser-service.ts +721 -0
  261. package/src/electron/agent/context-manager.ts +257 -0
  262. package/src/electron/agent/custom-skill-loader.ts +634 -0
  263. package/src/electron/agent/daemon.ts +1097 -0
  264. package/src/electron/agent/executor.ts +4017 -0
  265. package/src/electron/agent/llm/anthropic-provider.ts +175 -0
  266. package/src/electron/agent/llm/bedrock-provider.ts +236 -0
  267. package/src/electron/agent/llm/gemini-provider.ts +422 -0
  268. package/src/electron/agent/llm/index.ts +9 -0
  269. package/src/electron/agent/llm/ollama-provider.ts +347 -0
  270. package/src/electron/agent/llm/openai-oauth.ts +127 -0
  271. package/src/electron/agent/llm/openai-provider.ts +686 -0
  272. package/src/electron/agent/llm/openrouter-provider.ts +273 -0
  273. package/src/electron/agent/llm/pricing.ts +180 -0
  274. package/src/electron/agent/llm/provider-factory.ts +971 -0
  275. package/src/electron/agent/llm/types.ts +291 -0
  276. package/src/electron/agent/queue-manager.ts +408 -0
  277. package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
  278. package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
  279. package/src/electron/agent/sandbox/runner.ts +453 -0
  280. package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
  281. package/src/electron/agent/sandbox/security-utils.ts +251 -0
  282. package/src/electron/agent/search/brave-provider.ts +141 -0
  283. package/src/electron/agent/search/google-provider.ts +131 -0
  284. package/src/electron/agent/search/index.ts +6 -0
  285. package/src/electron/agent/search/provider-factory.ts +450 -0
  286. package/src/electron/agent/search/serpapi-provider.ts +138 -0
  287. package/src/electron/agent/search/tavily-provider.ts +108 -0
  288. package/src/electron/agent/search/types.ts +118 -0
  289. package/src/electron/agent/security/index.ts +20 -0
  290. package/src/electron/agent/security/input-sanitizer.ts +380 -0
  291. package/src/electron/agent/security/output-filter.ts +259 -0
  292. package/src/electron/agent/skill-eligibility.ts +334 -0
  293. package/src/electron/agent/skill-registry.ts +457 -0
  294. package/src/electron/agent/skills/document.ts +1070 -0
  295. package/src/electron/agent/skills/image-generator.ts +272 -0
  296. package/src/electron/agent/skills/organizer.ts +131 -0
  297. package/src/electron/agent/skills/presentation.ts +418 -0
  298. package/src/electron/agent/skills/spreadsheet.ts +166 -0
  299. package/src/electron/agent/tools/browser-tools.ts +546 -0
  300. package/src/electron/agent/tools/builtin-settings.ts +422 -0
  301. package/src/electron/agent/tools/canvas-tools.ts +572 -0
  302. package/src/electron/agent/tools/cron-tools.ts +723 -0
  303. package/src/electron/agent/tools/edit-tools.ts +196 -0
  304. package/src/electron/agent/tools/file-tools.ts +811 -0
  305. package/src/electron/agent/tools/glob-tools.ts +303 -0
  306. package/src/electron/agent/tools/grep-tools.ts +432 -0
  307. package/src/electron/agent/tools/image-tools.ts +126 -0
  308. package/src/electron/agent/tools/mention-tools.ts +371 -0
  309. package/src/electron/agent/tools/node-tools.ts +550 -0
  310. package/src/electron/agent/tools/registry.ts +3052 -0
  311. package/src/electron/agent/tools/search-tools.ts +111 -0
  312. package/src/electron/agent/tools/shell-tools.ts +651 -0
  313. package/src/electron/agent/tools/skill-tools.ts +340 -0
  314. package/src/electron/agent/tools/system-tools.ts +665 -0
  315. package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
  316. package/src/electron/agent/tools/x-tools.ts +267 -0
  317. package/src/electron/agents/AgentRoleRepository.ts +557 -0
  318. package/src/electron/agents/HeartbeatService.ts +469 -0
  319. package/src/electron/agents/MentionRepository.ts +242 -0
  320. package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
  321. package/src/electron/agents/WorkingStateRepository.ts +278 -0
  322. package/src/electron/canvas/canvas-manager.ts +818 -0
  323. package/src/electron/canvas/canvas-preload.ts +102 -0
  324. package/src/electron/canvas/canvas-protocol.ts +174 -0
  325. package/src/electron/canvas/canvas-store.ts +200 -0
  326. package/src/electron/canvas/index.ts +8 -0
  327. package/src/electron/control-plane/client.ts +527 -0
  328. package/src/electron/control-plane/handlers.ts +723 -0
  329. package/src/electron/control-plane/index.ts +51 -0
  330. package/src/electron/control-plane/node-manager.ts +322 -0
  331. package/src/electron/control-plane/protocol.ts +269 -0
  332. package/src/electron/control-plane/remote-client.ts +517 -0
  333. package/src/electron/control-plane/server.ts +853 -0
  334. package/src/electron/control-plane/settings.ts +401 -0
  335. package/src/electron/control-plane/ssh-tunnel.ts +624 -0
  336. package/src/electron/cron/index.ts +9 -0
  337. package/src/electron/cron/schedule.ts +217 -0
  338. package/src/electron/cron/service.ts +743 -0
  339. package/src/electron/cron/store.ts +165 -0
  340. package/src/electron/cron/types.ts +291 -0
  341. package/src/electron/cron/webhook.ts +303 -0
  342. package/src/electron/database/SecureSettingsRepository.ts +514 -0
  343. package/src/electron/database/TaskLabelRepository.ts +148 -0
  344. package/src/electron/database/repositories.ts +2397 -0
  345. package/src/electron/database/schema.ts +1017 -0
  346. package/src/electron/extensions/index.ts +18 -0
  347. package/src/electron/extensions/loader.ts +336 -0
  348. package/src/electron/extensions/registry.ts +546 -0
  349. package/src/electron/extensions/types.ts +372 -0
  350. package/src/electron/gateway/channel-registry.ts +1267 -0
  351. package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
  352. package/src/electron/gateway/channels/bluebubbles.ts +509 -0
  353. package/src/electron/gateway/channels/discord.ts +1150 -0
  354. package/src/electron/gateway/channels/email-client.ts +708 -0
  355. package/src/electron/gateway/channels/email.ts +516 -0
  356. package/src/electron/gateway/channels/google-chat.ts +760 -0
  357. package/src/electron/gateway/channels/imessage-client.ts +473 -0
  358. package/src/electron/gateway/channels/imessage.ts +520 -0
  359. package/src/electron/gateway/channels/index.ts +21 -0
  360. package/src/electron/gateway/channels/line-client.ts +598 -0
  361. package/src/electron/gateway/channels/line.ts +559 -0
  362. package/src/electron/gateway/channels/matrix-client.ts +632 -0
  363. package/src/electron/gateway/channels/matrix.ts +655 -0
  364. package/src/electron/gateway/channels/mattermost-client.ts +526 -0
  365. package/src/electron/gateway/channels/mattermost.ts +550 -0
  366. package/src/electron/gateway/channels/signal-client.ts +722 -0
  367. package/src/electron/gateway/channels/signal.ts +666 -0
  368. package/src/electron/gateway/channels/slack.ts +458 -0
  369. package/src/electron/gateway/channels/teams.ts +681 -0
  370. package/src/electron/gateway/channels/telegram.ts +1727 -0
  371. package/src/electron/gateway/channels/twitch-client.ts +665 -0
  372. package/src/electron/gateway/channels/twitch.ts +468 -0
  373. package/src/electron/gateway/channels/types.ts +1002 -0
  374. package/src/electron/gateway/channels/whatsapp.ts +1101 -0
  375. package/src/electron/gateway/context-policy.ts +382 -0
  376. package/src/electron/gateway/index.ts +1274 -0
  377. package/src/electron/gateway/infrastructure.ts +645 -0
  378. package/src/electron/gateway/router.ts +3206 -0
  379. package/src/electron/gateway/security.ts +422 -0
  380. package/src/electron/gateway/session.ts +144 -0
  381. package/src/electron/gateway/tunnel.ts +626 -0
  382. package/src/electron/guardrails/guardrail-manager.ts +380 -0
  383. package/src/electron/hooks/gmail-watcher.ts +355 -0
  384. package/src/electron/hooks/index.ts +30 -0
  385. package/src/electron/hooks/mappings.ts +404 -0
  386. package/src/electron/hooks/server.ts +574 -0
  387. package/src/electron/hooks/settings.ts +466 -0
  388. package/src/electron/hooks/types.ts +245 -0
  389. package/src/electron/ipc/canvas-handlers.ts +223 -0
  390. package/src/electron/ipc/handlers.ts +3661 -0
  391. package/src/electron/ipc/mission-control-handlers.ts +182 -0
  392. package/src/electron/main.ts +496 -0
  393. package/src/electron/mcp/client/MCPClientManager.ts +406 -0
  394. package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
  395. package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
  396. package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
  397. package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
  398. package/src/electron/mcp/host/MCPHostServer.ts +388 -0
  399. package/src/electron/mcp/host/ToolAdapter.ts +140 -0
  400. package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
  401. package/src/electron/mcp/settings.ts +468 -0
  402. package/src/electron/mcp/types.ts +371 -0
  403. package/src/electron/memory/MemoryService.ts +523 -0
  404. package/src/electron/notifications/index.ts +16 -0
  405. package/src/electron/notifications/service.ts +161 -0
  406. package/src/electron/notifications/store.ts +163 -0
  407. package/src/electron/preload.ts +2845 -0
  408. package/src/electron/reports/StandupReportService.ts +356 -0
  409. package/src/electron/security/concurrency.ts +333 -0
  410. package/src/electron/security/index.ts +17 -0
  411. package/src/electron/security/policy-manager.ts +539 -0
  412. package/src/electron/settings/appearance-manager.ts +182 -0
  413. package/src/electron/settings/personality-manager.ts +800 -0
  414. package/src/electron/settings/x-manager.ts +62 -0
  415. package/src/electron/tailscale/exposure.ts +262 -0
  416. package/src/electron/tailscale/index.ts +34 -0
  417. package/src/electron/tailscale/settings.ts +218 -0
  418. package/src/electron/tailscale/tailscale.ts +379 -0
  419. package/src/electron/tray/QuickInputWindow.ts +609 -0
  420. package/src/electron/tray/TrayManager.ts +1005 -0
  421. package/src/electron/tray/index.ts +6 -0
  422. package/src/electron/updater/index.ts +1 -0
  423. package/src/electron/updater/update-manager.ts +447 -0
  424. package/src/electron/utils/env-migration.ts +203 -0
  425. package/src/electron/utils/process.ts +124 -0
  426. package/src/electron/utils/rate-limiter.ts +130 -0
  427. package/src/electron/utils/validation.ts +493 -0
  428. package/src/electron/utils/x-cli.ts +198 -0
  429. package/src/electron/voice/VoiceService.ts +583 -0
  430. package/src/electron/voice/index.ts +9 -0
  431. package/src/electron/voice/voice-settings-manager.ts +403 -0
  432. package/src/renderer/App.tsx +775 -0
  433. package/src/renderer/components/ActivityFeed.tsx +407 -0
  434. package/src/renderer/components/ActivityFeedItem.tsx +285 -0
  435. package/src/renderer/components/AgentRoleCard.tsx +343 -0
  436. package/src/renderer/components/AgentRoleEditor.tsx +805 -0
  437. package/src/renderer/components/AgentSquadSettings.tsx +295 -0
  438. package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
  439. package/src/renderer/components/AppearanceSettings.tsx +122 -0
  440. package/src/renderer/components/ApprovalDialog.tsx +100 -0
  441. package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
  442. package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
  443. package/src/renderer/components/CanvasPreview.tsx +1189 -0
  444. package/src/renderer/components/CommandOutput.tsx +202 -0
  445. package/src/renderer/components/ContextPolicySettings.tsx +523 -0
  446. package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
  447. package/src/renderer/components/DisclaimerModal.tsx +124 -0
  448. package/src/renderer/components/DiscordSettings.tsx +436 -0
  449. package/src/renderer/components/EmailSettings.tsx +606 -0
  450. package/src/renderer/components/ExtensionsSettings.tsx +542 -0
  451. package/src/renderer/components/FileViewer.tsx +224 -0
  452. package/src/renderer/components/GoogleChatSettings.tsx +535 -0
  453. package/src/renderer/components/GuardrailSettings.tsx +487 -0
  454. package/src/renderer/components/HooksSettings.tsx +581 -0
  455. package/src/renderer/components/ImessageSettings.tsx +484 -0
  456. package/src/renderer/components/LineSettings.tsx +483 -0
  457. package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
  458. package/src/renderer/components/MCPSettings.tsx +943 -0
  459. package/src/renderer/components/MainContent.tsx +2433 -0
  460. package/src/renderer/components/MatrixSettings.tsx +510 -0
  461. package/src/renderer/components/MattermostSettings.tsx +473 -0
  462. package/src/renderer/components/MemorySettings.tsx +247 -0
  463. package/src/renderer/components/MentionBadge.tsx +87 -0
  464. package/src/renderer/components/MentionInput.tsx +409 -0
  465. package/src/renderer/components/MentionList.tsx +476 -0
  466. package/src/renderer/components/MissionControlPanel.tsx +1995 -0
  467. package/src/renderer/components/NodesSettings.tsx +316 -0
  468. package/src/renderer/components/NotificationPanel.tsx +481 -0
  469. package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
  470. package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
  471. package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
  472. package/src/renderer/components/Onboarding/index.ts +3 -0
  473. package/src/renderer/components/OnboardingModal.tsx +698 -0
  474. package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
  475. package/src/renderer/components/PersonalitySettings.tsx +597 -0
  476. package/src/renderer/components/QueueSettings.tsx +119 -0
  477. package/src/renderer/components/QuickTaskFAB.tsx +71 -0
  478. package/src/renderer/components/RightPanel.tsx +413 -0
  479. package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
  480. package/src/renderer/components/SearchSettings.tsx +328 -0
  481. package/src/renderer/components/Settings.tsx +1504 -0
  482. package/src/renderer/components/Sidebar.tsx +344 -0
  483. package/src/renderer/components/SignalSettings.tsx +673 -0
  484. package/src/renderer/components/SkillHubBrowser.tsx +458 -0
  485. package/src/renderer/components/SkillParameterModal.tsx +185 -0
  486. package/src/renderer/components/SkillsSettings.tsx +451 -0
  487. package/src/renderer/components/SlackSettings.tsx +442 -0
  488. package/src/renderer/components/StandupReportViewer.tsx +614 -0
  489. package/src/renderer/components/TaskBoard.tsx +498 -0
  490. package/src/renderer/components/TaskBoardCard.tsx +357 -0
  491. package/src/renderer/components/TaskBoardColumn.tsx +211 -0
  492. package/src/renderer/components/TaskLabelManager.tsx +472 -0
  493. package/src/renderer/components/TaskQueuePanel.tsx +144 -0
  494. package/src/renderer/components/TaskQuickActions.tsx +492 -0
  495. package/src/renderer/components/TaskTimeline.tsx +216 -0
  496. package/src/renderer/components/TaskView.tsx +162 -0
  497. package/src/renderer/components/TeamsSettings.tsx +518 -0
  498. package/src/renderer/components/TelegramSettings.tsx +421 -0
  499. package/src/renderer/components/Toast.tsx +76 -0
  500. package/src/renderer/components/TraySettings.tsx +189 -0
  501. package/src/renderer/components/TwitchSettings.tsx +511 -0
  502. package/src/renderer/components/UpdateSettings.tsx +295 -0
  503. package/src/renderer/components/VoiceIndicator.tsx +270 -0
  504. package/src/renderer/components/VoiceSettings.tsx +867 -0
  505. package/src/renderer/components/WhatsAppSettings.tsx +721 -0
  506. package/src/renderer/components/WorkingStateEditor.tsx +309 -0
  507. package/src/renderer/components/WorkingStateHistory.tsx +481 -0
  508. package/src/renderer/components/WorkspaceSelector.tsx +150 -0
  509. package/src/renderer/components/XSettings.tsx +311 -0
  510. package/src/renderer/global.d.ts +9 -0
  511. package/src/renderer/hooks/useAgentContext.ts +153 -0
  512. package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
  513. package/src/renderer/hooks/useVoiceInput.ts +268 -0
  514. package/src/renderer/index.html +12 -0
  515. package/src/renderer/main.tsx +10 -0
  516. package/src/renderer/public/cowork-os-logo.png +0 -0
  517. package/src/renderer/quick-input.html +164 -0
  518. package/src/renderer/styles/index.css +14504 -0
  519. package/src/renderer/utils/agentMessages.ts +749 -0
  520. package/src/renderer/utils/voice-directives.ts +169 -0
  521. package/src/shared/channelMessages.ts +213 -0
  522. package/src/shared/types.ts +3608 -0
  523. package/tsconfig.electron.json +26 -0
  524. package/tsconfig.json +26 -0
  525. package/tsconfig.node.json +10 -0
  526. package/vite.config.ts +23 -0
@@ -0,0 +1,867 @@
1
+ import { useState, useEffect, useRef, useCallback } from 'react';
2
+ import {
3
+ VoiceSettings as VoiceSettingsType,
4
+ VoiceProvider,
5
+ VoiceInputMode,
6
+ VoiceResponseMode,
7
+ VoiceState,
8
+ ElevenLabsVoice,
9
+ OPENAI_VOICES,
10
+ VOICE_LANGUAGES,
11
+ DEFAULT_VOICE_SETTINGS,
12
+ } from '../../shared/types';
13
+
14
+ // Audio playback helper for renderer process
15
+ async function playAudioData(audioData: number[], volume: number): Promise<void> {
16
+ const audioContext = new AudioContext();
17
+ const arrayBuffer = new Uint8Array(audioData).buffer;
18
+
19
+ try {
20
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
21
+ const gainNode = audioContext.createGain();
22
+ gainNode.gain.value = volume / 100;
23
+ gainNode.connect(audioContext.destination);
24
+
25
+ const source = audioContext.createBufferSource();
26
+ source.buffer = audioBuffer;
27
+ source.connect(gainNode);
28
+
29
+ return new Promise((resolve) => {
30
+ source.onended = () => {
31
+ audioContext.close();
32
+ resolve();
33
+ };
34
+ source.start(0);
35
+ });
36
+ } catch (error) {
37
+ audioContext.close();
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ interface VoiceSettingsProps {
43
+ onStateChange?: (state: VoiceState) => void;
44
+ }
45
+
46
+ export function VoiceSettings({ onStateChange }: VoiceSettingsProps) {
47
+ const [settings, setSettings] = useState<VoiceSettingsType>(DEFAULT_VOICE_SETTINGS);
48
+ const [voiceState, setVoiceState] = useState<VoiceState>({
49
+ isActive: false,
50
+ isListening: false,
51
+ isSpeaking: false,
52
+ isProcessing: false,
53
+ audioLevel: 0,
54
+ });
55
+ const [loading, setLoading] = useState(true);
56
+ const [saving, setSaving] = useState(false);
57
+ const [elevenLabsVoices, setElevenLabsVoices] = useState<ElevenLabsVoice[]>([]);
58
+ const [loadingVoices, setLoadingVoices] = useState(false);
59
+
60
+ // Test connection states
61
+ const [testingElevenLabs, setTestingElevenLabs] = useState(false);
62
+ const [elevenLabsTestResult, setElevenLabsTestResult] = useState<{
63
+ success: boolean;
64
+ message: string;
65
+ } | null>(null);
66
+ const [testingOpenAI, setTestingOpenAI] = useState(false);
67
+ const [openAITestResult, setOpenAITestResult] = useState<{
68
+ success: boolean;
69
+ message: string;
70
+ } | null>(null);
71
+ const [testingAzure, setTestingAzure] = useState(false);
72
+ const [azureTestResult, setAzureTestResult] = useState<{
73
+ success: boolean;
74
+ message: string;
75
+ } | null>(null);
76
+
77
+ // Test speech state
78
+ const [testingSpeech, setTestingSpeech] = useState(false);
79
+
80
+ // Debounce ref for text input saves to prevent race conditions
81
+ const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
82
+ const pendingSettingsRef = useRef<Partial<VoiceSettingsType>>({});
83
+
84
+ useEffect(() => {
85
+ loadSettings();
86
+
87
+ // Subscribe to voice events
88
+ const unsubscribe = window.electronAPI.onVoiceEvent((event) => {
89
+ if (event.type === 'voice:state-changed') {
90
+ const newState = event.data as VoiceState;
91
+ setVoiceState(newState);
92
+ onStateChange?.(newState);
93
+ }
94
+ });
95
+
96
+ return () => {
97
+ unsubscribe();
98
+ // Clean up pending save on unmount
99
+ if (saveTimeoutRef.current) {
100
+ clearTimeout(saveTimeoutRef.current);
101
+ }
102
+ };
103
+ }, [onStateChange]);
104
+
105
+ const loadSettings = async () => {
106
+ try {
107
+ setLoading(true);
108
+ const loaded = await window.electronAPI.getVoiceSettings();
109
+ setSettings(loaded);
110
+
111
+ // Load ElevenLabs voices if API key is configured
112
+ if (loaded.elevenLabsApiKey) {
113
+ await loadElevenLabsVoices();
114
+ }
115
+ } catch (error) {
116
+ console.error('Failed to load voice settings:', error);
117
+ } finally {
118
+ setLoading(false);
119
+ }
120
+ };
121
+
122
+ const loadElevenLabsVoices = async () => {
123
+ try {
124
+ setLoadingVoices(true);
125
+ const voices = await window.electronAPI.getElevenLabsVoices();
126
+ setElevenLabsVoices(voices);
127
+ } catch (error) {
128
+ console.error('Failed to load ElevenLabs voices:', error);
129
+ } finally {
130
+ setLoadingVoices(false);
131
+ }
132
+ };
133
+
134
+ const saveSettings = async (newSettings: Partial<VoiceSettingsType>) => {
135
+ try {
136
+ setSaving(true);
137
+ const updated = await window.electronAPI.saveVoiceSettings(newSettings);
138
+ setSettings(updated);
139
+ } catch (error) {
140
+ console.error('Failed to save voice settings:', error);
141
+ } finally {
142
+ setSaving(false);
143
+ }
144
+ };
145
+
146
+ // Debounced save for text inputs - prevents race conditions when typing
147
+ const debouncedSave = useCallback((newSettings: Partial<VoiceSettingsType>) => {
148
+ // Merge with any pending settings
149
+ pendingSettingsRef.current = { ...pendingSettingsRef.current, ...newSettings };
150
+
151
+ // Update local state immediately for responsive UI
152
+ setSettings(prev => ({ ...prev, ...newSettings }));
153
+
154
+ // Clear existing timeout
155
+ if (saveTimeoutRef.current) {
156
+ clearTimeout(saveTimeoutRef.current);
157
+ }
158
+
159
+ // Schedule save after user stops typing
160
+ saveTimeoutRef.current = setTimeout(async () => {
161
+ const toSave = pendingSettingsRef.current;
162
+ pendingSettingsRef.current = {};
163
+
164
+ try {
165
+ setSaving(true);
166
+ const updated = await window.electronAPI.saveVoiceSettings(toSave);
167
+ setSettings(updated);
168
+ } catch (error) {
169
+ console.error('Failed to save voice settings:', error);
170
+ } finally {
171
+ setSaving(false);
172
+ }
173
+ }, 500); // Wait 500ms after last keystroke before saving
174
+ }, []);
175
+
176
+ const handleToggleEnabled = async () => {
177
+ await saveSettings({ enabled: !settings.enabled });
178
+ };
179
+
180
+ const handleTTSProviderChange = async (provider: VoiceProvider) => {
181
+ // When switching to Azure, also switch STT to Azure for consistency
182
+ // When switching away from Azure, switch STT to OpenAI (most common)
183
+ if (provider === 'azure' && settings.sttProvider !== 'azure') {
184
+ await saveSettings({ ttsProvider: provider, sttProvider: 'azure' });
185
+ } else if (provider !== 'azure' && settings.sttProvider === 'azure') {
186
+ await saveSettings({ ttsProvider: provider, sttProvider: 'openai' });
187
+ } else {
188
+ await saveSettings({ ttsProvider: provider });
189
+ }
190
+ };
191
+
192
+ const handleSTTProviderChange = async (provider: VoiceProvider) => {
193
+ await saveSettings({ sttProvider: provider });
194
+ };
195
+
196
+ // Text input handlers use debounced save to prevent race conditions
197
+ const handleElevenLabsApiKeyChange = (apiKey: string) => {
198
+ debouncedSave({ elevenLabsApiKey: apiKey });
199
+ // Load voices after debounce completes
200
+ if (apiKey) {
201
+ // Delay voice loading to match save timing
202
+ setTimeout(() => loadElevenLabsVoices(), 600);
203
+ } else {
204
+ setElevenLabsVoices([]);
205
+ }
206
+ };
207
+
208
+ const handleOpenAIApiKeyChange = (apiKey: string) => {
209
+ debouncedSave({ openaiApiKey: apiKey });
210
+ };
211
+
212
+ const handleAzureEndpointChange = (endpoint: string) => {
213
+ debouncedSave({ azureEndpoint: endpoint });
214
+ };
215
+
216
+ const handleAzureApiKeyChange = (apiKey: string) => {
217
+ debouncedSave({ azureApiKey: apiKey });
218
+ };
219
+
220
+ const handleAzureTtsDeploymentChange = (deploymentName: string) => {
221
+ debouncedSave({ azureTtsDeploymentName: deploymentName });
222
+ };
223
+
224
+ const handleAzureSttDeploymentChange = (deploymentName: string) => {
225
+ debouncedSave({ azureSttDeploymentName: deploymentName });
226
+ };
227
+
228
+ const handleAzureVoiceChange = async (voice: string) => {
229
+ await saveSettings({
230
+ azureVoice: voice as 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer',
231
+ });
232
+ };
233
+
234
+ const handleVoiceChange = async (voiceId: string) => {
235
+ if (settings.ttsProvider === 'elevenlabs') {
236
+ await saveSettings({ elevenLabsVoiceId: voiceId });
237
+ } else if (settings.ttsProvider === 'openai') {
238
+ await saveSettings({
239
+ openaiVoice: voiceId as 'alloy' | 'echo' | 'fable' | 'onyx' | 'nova' | 'shimmer',
240
+ });
241
+ }
242
+ };
243
+
244
+ const handleInputModeChange = async (mode: VoiceInputMode) => {
245
+ await saveSettings({ inputMode: mode });
246
+ };
247
+
248
+ const handleResponseModeChange = async (mode: VoiceResponseMode) => {
249
+ await saveSettings({ responseMode: mode });
250
+ };
251
+
252
+ const handleVolumeChange = async (volume: number) => {
253
+ await saveSettings({ volume });
254
+ };
255
+
256
+ const handleSpeechRateChange = async (rate: number) => {
257
+ await saveSettings({ speechRate: rate });
258
+ };
259
+
260
+ const handleLanguageChange = async (language: string) => {
261
+ await saveSettings({ language });
262
+ };
263
+
264
+ const handleTestElevenLabs = async () => {
265
+ setTestingElevenLabs(true);
266
+ setElevenLabsTestResult(null);
267
+ try {
268
+ const result = await window.electronAPI.testElevenLabsConnection();
269
+ setElevenLabsTestResult({
270
+ success: result.success,
271
+ message: result.success
272
+ ? `Connected! Found ${result.voiceCount} voices.`
273
+ : result.error || 'Connection failed',
274
+ });
275
+ } catch (error: any) {
276
+ setElevenLabsTestResult({
277
+ success: false,
278
+ message: error.message || 'Connection failed',
279
+ });
280
+ } finally {
281
+ setTestingElevenLabs(false);
282
+ }
283
+ };
284
+
285
+ const handleTestOpenAI = async () => {
286
+ setTestingOpenAI(true);
287
+ setOpenAITestResult(null);
288
+ try {
289
+ const result = await window.electronAPI.testOpenAIVoiceConnection();
290
+ setOpenAITestResult({
291
+ success: result.success,
292
+ message: result.success ? 'Connected!' : result.error || 'Connection failed',
293
+ });
294
+ } catch (error: any) {
295
+ setOpenAITestResult({
296
+ success: false,
297
+ message: error.message || 'Connection failed',
298
+ });
299
+ } finally {
300
+ setTestingOpenAI(false);
301
+ }
302
+ };
303
+
304
+ const handleTestAzure = async () => {
305
+ setTestingAzure(true);
306
+ setAzureTestResult(null);
307
+ try {
308
+ const result = await window.electronAPI.testAzureVoiceConnection();
309
+ setAzureTestResult({
310
+ success: result.success,
311
+ message: result.success ? 'Connected!' : result.error || 'Connection failed',
312
+ });
313
+ } catch (error: any) {
314
+ setAzureTestResult({
315
+ success: false,
316
+ message: error.message || 'Connection failed',
317
+ });
318
+ } finally {
319
+ setTestingAzure(false);
320
+ }
321
+ };
322
+
323
+ const handleTestSpeech = async () => {
324
+ setTestingSpeech(true);
325
+ try {
326
+ const result = await window.electronAPI.voiceSpeak('Hello! This is a test of the text to speech system.');
327
+ if (result.success && result.audioData) {
328
+ // Play audio in renderer process
329
+ await playAudioData(result.audioData, settings.volume);
330
+ } else if (!result.success) {
331
+ console.error('Test speech failed:', result.error);
332
+ }
333
+ } catch (error) {
334
+ console.error('Test speech failed:', error);
335
+ } finally {
336
+ setTestingSpeech(false);
337
+ }
338
+ };
339
+
340
+ const handleStopSpeaking = async () => {
341
+ await window.electronAPI.voiceStopSpeaking();
342
+ setTestingSpeech(false);
343
+ };
344
+
345
+ if (loading) {
346
+ return <div className="settings-loading">Loading voice settings...</div>;
347
+ }
348
+
349
+ return (
350
+ <div className="voice-settings">
351
+ {/* Enable/Disable */}
352
+ <div className="settings-section">
353
+ <div className="settings-header-row">
354
+ <div>
355
+ <h3>Voice Mode</h3>
356
+ <p className="settings-description">
357
+ Enable hands-free interaction with text-to-speech and speech-to-text.
358
+ </p>
359
+ </div>
360
+ <label className="toggle-switch">
361
+ <input
362
+ type="checkbox"
363
+ checked={settings.enabled}
364
+ onChange={handleToggleEnabled}
365
+ disabled={saving}
366
+ />
367
+ <span className="toggle-slider" />
368
+ </label>
369
+ </div>
370
+
371
+ {/* Status indicator */}
372
+ {settings.enabled && (
373
+ <div className={`voice-status ${voiceState.isActive ? 'active' : 'inactive'}`}>
374
+ <span className="status-dot" />
375
+ <span className="status-text">
376
+ {voiceState.isSpeaking
377
+ ? 'Speaking...'
378
+ : voiceState.isListening
379
+ ? 'Listening...'
380
+ : voiceState.isProcessing
381
+ ? 'Processing...'
382
+ : voiceState.isActive
383
+ ? 'Ready'
384
+ : 'Inactive'}
385
+ </span>
386
+ </div>
387
+ )}
388
+ </div>
389
+
390
+ {/* TTS Provider */}
391
+ <div className="settings-section">
392
+ <h4>Text-to-Speech Provider</h4>
393
+ <p className="settings-description">Choose the voice synthesis provider.</p>
394
+ <div className="provider-options">
395
+ <button
396
+ className={`provider-option ${settings.ttsProvider === 'elevenlabs' ? 'selected' : ''} ${settings.elevenLabsApiKey ? 'configured' : ''}`}
397
+ onClick={() => handleTTSProviderChange('elevenlabs')}
398
+ disabled={saving}
399
+ >
400
+ <div className="provider-option-header">
401
+ <span className="provider-name">ElevenLabs</span>
402
+ {settings.elevenLabsApiKey && (
403
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
404
+ <path d="M20 6L9 17l-5-5" />
405
+ </svg>
406
+ )}
407
+ </div>
408
+ <span className="provider-badge">Premium</span>
409
+ </button>
410
+ <button
411
+ className={`provider-option ${settings.ttsProvider === 'openai' ? 'selected' : ''} ${settings.openaiApiKey ? 'configured' : ''}`}
412
+ onClick={() => handleTTSProviderChange('openai')}
413
+ disabled={saving}
414
+ >
415
+ <div className="provider-option-header">
416
+ <span className="provider-name">OpenAI</span>
417
+ {settings.openaiApiKey && (
418
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
419
+ <path d="M20 6L9 17l-5-5" />
420
+ </svg>
421
+ )}
422
+ </div>
423
+ </button>
424
+ <button
425
+ className={`provider-option ${settings.ttsProvider === 'azure' ? 'selected' : ''} ${settings.azureApiKey && settings.azureEndpoint ? 'configured' : ''}`}
426
+ onClick={() => handleTTSProviderChange('azure')}
427
+ disabled={saving}
428
+ >
429
+ <div className="provider-option-header">
430
+ <span className="provider-name">Azure OpenAI</span>
431
+ {settings.azureApiKey && settings.azureEndpoint && (
432
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
433
+ <path d="M20 6L9 17l-5-5" />
434
+ </svg>
435
+ )}
436
+ </div>
437
+ <span className="provider-badge">Enterprise</span>
438
+ </button>
439
+ <button
440
+ className={`provider-option ${settings.ttsProvider === 'local' ? 'selected' : ''} configured`}
441
+ onClick={() => handleTTSProviderChange('local')}
442
+ disabled={saving}
443
+ >
444
+ <div className="provider-option-header">
445
+ <span className="provider-name">System</span>
446
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
447
+ <path d="M20 6L9 17l-5-5" />
448
+ </svg>
449
+ </div>
450
+ <span className="provider-badge">Free</span>
451
+ </button>
452
+ </div>
453
+ </div>
454
+
455
+ {/* ElevenLabs Configuration */}
456
+ {settings.ttsProvider === 'elevenlabs' && (
457
+ <div className="settings-section">
458
+ <h4>ElevenLabs Configuration</h4>
459
+
460
+ <div className="settings-field">
461
+ <label>API Key</label>
462
+ <div className="input-with-button">
463
+ <input
464
+ type="password"
465
+ className="settings-input"
466
+ placeholder="Enter your ElevenLabs API key"
467
+ value={settings.elevenLabsApiKey || ''}
468
+ onChange={(e) => handleElevenLabsApiKeyChange(e.target.value)}
469
+ />
470
+ <button
471
+ className="button-secondary"
472
+ onClick={handleTestElevenLabs}
473
+ disabled={testingElevenLabs || !settings.elevenLabsApiKey}
474
+ >
475
+ {testingElevenLabs ? 'Testing...' : 'Test'}
476
+ </button>
477
+ </div>
478
+ <p className="settings-hint">
479
+ Get your API key from{' '}
480
+ <a
481
+ href="https://elevenlabs.io/app/settings/api-keys"
482
+ target="_blank"
483
+ rel="noopener noreferrer"
484
+ >
485
+ ElevenLabs Dashboard
486
+ </a>
487
+ </p>
488
+ {elevenLabsTestResult && (
489
+ <div
490
+ className={`test-result ${elevenLabsTestResult.success ? 'success' : 'error'}`}
491
+ >
492
+ {elevenLabsTestResult.message}
493
+ </div>
494
+ )}
495
+ </div>
496
+
497
+ <div className="settings-field">
498
+ <label>Voice</label>
499
+ <select
500
+ className="settings-select"
501
+ value={settings.elevenLabsVoiceId || ''}
502
+ onChange={(e) => handleVoiceChange(e.target.value)}
503
+ disabled={loadingVoices || elevenLabsVoices.length === 0}
504
+ >
505
+ <option value="">
506
+ {loadingVoices
507
+ ? 'Loading voices...'
508
+ : elevenLabsVoices.length === 0
509
+ ? 'Enter API key to load voices'
510
+ : 'Select a voice'}
511
+ </option>
512
+ {elevenLabsVoices.map((voice) => (
513
+ <option key={voice.voice_id} value={voice.voice_id}>
514
+ {voice.name}
515
+ {voice.category && ` (${voice.category})`}
516
+ </option>
517
+ ))}
518
+ </select>
519
+ </div>
520
+ </div>
521
+ )}
522
+
523
+ {/* OpenAI Configuration - show when TTS or STT uses OpenAI */}
524
+ {(settings.ttsProvider === 'openai' || settings.sttProvider === 'openai') && (
525
+ <div className="settings-section">
526
+ <h4>OpenAI Configuration</h4>
527
+
528
+ <div className="settings-field">
529
+ <label>API Key</label>
530
+ <div className="input-with-button">
531
+ <input
532
+ type="password"
533
+ className="settings-input"
534
+ placeholder="Enter your OpenAI API key"
535
+ value={settings.openaiApiKey || ''}
536
+ onChange={(e) => handleOpenAIApiKeyChange(e.target.value)}
537
+ />
538
+ <button
539
+ className="button-secondary"
540
+ onClick={handleTestOpenAI}
541
+ disabled={testingOpenAI}
542
+ >
543
+ {testingOpenAI ? 'Testing...' : 'Test'}
544
+ </button>
545
+ </div>
546
+ <p className="settings-hint">
547
+ Required for {settings.ttsProvider === 'openai' && settings.sttProvider === 'openai'
548
+ ? 'TTS and STT'
549
+ : settings.ttsProvider === 'openai'
550
+ ? 'TTS'
551
+ : 'STT (Whisper)'}.
552
+ </p>
553
+ {openAITestResult && (
554
+ <div
555
+ className={`test-result ${openAITestResult.success ? 'success' : 'error'}`}
556
+ >
557
+ {openAITestResult.message}
558
+ </div>
559
+ )}
560
+ </div>
561
+
562
+ {/* Voice selection only when using OpenAI for TTS */}
563
+ {settings.ttsProvider === 'openai' && (
564
+ <div className="settings-field">
565
+ <label>Voice</label>
566
+ <div className="voice-grid">
567
+ {OPENAI_VOICES.map((voice) => (
568
+ <button
569
+ key={voice.id}
570
+ className={`voice-option ${settings.openaiVoice === voice.id ? 'selected' : ''}`}
571
+ onClick={() => handleVoiceChange(voice.id)}
572
+ title={voice.description}
573
+ >
574
+ <span className="voice-name">{voice.name}</span>
575
+ <span className="voice-description">{voice.description}</span>
576
+ </button>
577
+ ))}
578
+ </div>
579
+ </div>
580
+ )}
581
+ </div>
582
+ )}
583
+
584
+ {/* Azure OpenAI Configuration - show when TTS or STT uses Azure */}
585
+ {(settings.ttsProvider === 'azure' || settings.sttProvider === 'azure') && (
586
+ <div className="settings-section">
587
+ <h4>Azure OpenAI Configuration</h4>
588
+
589
+ <div className="settings-field">
590
+ <label>Endpoint URL</label>
591
+ <input
592
+ type="text"
593
+ className="settings-input"
594
+ placeholder="https://your-resource.openai.azure.com"
595
+ value={settings.azureEndpoint || ''}
596
+ onChange={(e) => handleAzureEndpointChange(e.target.value)}
597
+ />
598
+ <p className="settings-hint">
599
+ Your Azure OpenAI resource endpoint (e.g., https://your-resource.openai.azure.com)
600
+ </p>
601
+ </div>
602
+
603
+ <div className="settings-field">
604
+ <label>API Key</label>
605
+ <div className="input-with-button">
606
+ <input
607
+ type="password"
608
+ className="settings-input"
609
+ placeholder="Enter your Azure OpenAI API key"
610
+ value={settings.azureApiKey || ''}
611
+ onChange={(e) => handleAzureApiKeyChange(e.target.value)}
612
+ />
613
+ <button
614
+ className="button-secondary"
615
+ onClick={handleTestAzure}
616
+ disabled={testingAzure || !settings.azureApiKey || !settings.azureEndpoint}
617
+ >
618
+ {testingAzure ? 'Testing...' : 'Test'}
619
+ </button>
620
+ </div>
621
+ <p className="settings-hint">
622
+ Get your API key from the{' '}
623
+ <a
624
+ href="https://portal.azure.com"
625
+ target="_blank"
626
+ rel="noopener noreferrer"
627
+ >
628
+ Azure Portal
629
+ </a>
630
+ {' '}under your OpenAI resource → Keys and Endpoint.
631
+ </p>
632
+ {azureTestResult && (
633
+ <div
634
+ className={`test-result ${azureTestResult.success ? 'success' : 'error'}`}
635
+ >
636
+ {azureTestResult.message}
637
+ </div>
638
+ )}
639
+ </div>
640
+
641
+ {/* TTS Deployment Name - only show when using Azure for TTS */}
642
+ {settings.ttsProvider === 'azure' && (
643
+ <div className="settings-field">
644
+ <label>TTS Deployment Name</label>
645
+ <input
646
+ type="text"
647
+ className="settings-input"
648
+ placeholder="e.g., tts-1"
649
+ value={settings.azureTtsDeploymentName || ''}
650
+ onChange={(e) => handleAzureTtsDeploymentChange(e.target.value)}
651
+ />
652
+ <p className="settings-hint">
653
+ The deployment name for your TTS model in Azure OpenAI.
654
+ </p>
655
+ </div>
656
+ )}
657
+
658
+ {/* STT Deployment Name - only show when using Azure for STT */}
659
+ {settings.sttProvider === 'azure' && (
660
+ <div className="settings-field">
661
+ <label>STT (Whisper) Deployment Name</label>
662
+ <input
663
+ type="text"
664
+ className="settings-input"
665
+ placeholder="e.g., whisper-1"
666
+ value={settings.azureSttDeploymentName || ''}
667
+ onChange={(e) => handleAzureSttDeploymentChange(e.target.value)}
668
+ />
669
+ <p className="settings-hint">
670
+ The deployment name for your Whisper model in Azure OpenAI.
671
+ </p>
672
+ </div>
673
+ )}
674
+
675
+ {/* Voice selection - only when using Azure for TTS */}
676
+ {settings.ttsProvider === 'azure' && (
677
+ <div className="settings-field">
678
+ <label>Voice</label>
679
+ <div className="voice-grid">
680
+ {OPENAI_VOICES.map((voice) => (
681
+ <button
682
+ key={voice.id}
683
+ className={`voice-option ${settings.azureVoice === voice.id ? 'selected' : ''}`}
684
+ onClick={() => handleAzureVoiceChange(voice.id)}
685
+ title={voice.description}
686
+ >
687
+ <span className="voice-name">{voice.name}</span>
688
+ <span className="voice-description">{voice.description}</span>
689
+ </button>
690
+ ))}
691
+ </div>
692
+ </div>
693
+ )}
694
+ </div>
695
+ )}
696
+
697
+ {/* Speech-to-Text Provider */}
698
+ <div className="settings-section">
699
+ <h4>Speech-to-Text Provider</h4>
700
+ <p className="settings-description">Choose the speech recognition provider.</p>
701
+ <div className="provider-options">
702
+ <button
703
+ className={`provider-option ${settings.sttProvider === 'openai' ? 'selected' : ''} ${settings.openaiApiKey ? 'configured' : ''}`}
704
+ onClick={() => handleSTTProviderChange('openai')}
705
+ disabled={saving}
706
+ >
707
+ <div className="provider-option-header">
708
+ <span className="provider-name">OpenAI Whisper</span>
709
+ {settings.openaiApiKey && (
710
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
711
+ <path d="M20 6L9 17l-5-5" />
712
+ </svg>
713
+ )}
714
+ </div>
715
+ <span className="provider-badge">Recommended</span>
716
+ </button>
717
+ <button
718
+ className={`provider-option ${settings.sttProvider === 'azure' ? 'selected' : ''} ${settings.azureApiKey && settings.azureEndpoint ? 'configured' : ''}`}
719
+ onClick={() => handleSTTProviderChange('azure')}
720
+ disabled={saving}
721
+ >
722
+ <div className="provider-option-header">
723
+ <span className="provider-name">Azure Whisper</span>
724
+ {settings.azureApiKey && settings.azureEndpoint && (
725
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
726
+ <path d="M20 6L9 17l-5-5" />
727
+ </svg>
728
+ )}
729
+ </div>
730
+ <span className="provider-badge">Enterprise</span>
731
+ </button>
732
+ <button
733
+ className={`provider-option ${settings.sttProvider === 'local' ? 'selected' : ''} configured`}
734
+ onClick={() => handleSTTProviderChange('local')}
735
+ disabled={saving}
736
+ >
737
+ <div className="provider-option-header">
738
+ <span className="provider-name">System</span>
739
+ <svg className="provider-configured-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
740
+ <path d="M20 6L9 17l-5-5" />
741
+ </svg>
742
+ </div>
743
+ <span className="provider-badge">Free</span>
744
+ </button>
745
+ </div>
746
+ </div>
747
+
748
+ {/* Voice Input Mode */}
749
+ <div className="settings-section">
750
+ <h4>Voice Input Mode</h4>
751
+ <div className="provider-options">
752
+ <button
753
+ className={`provider-option ${settings.inputMode === 'push_to_talk' ? 'selected' : ''}`}
754
+ onClick={() => handleInputModeChange('push_to_talk')}
755
+ disabled={saving}
756
+ >
757
+ <span className="provider-name">Push to Talk</span>
758
+ </button>
759
+ <button
760
+ className={`provider-option ${settings.inputMode === 'voice_activity' ? 'selected' : ''}`}
761
+ onClick={() => handleInputModeChange('voice_activity')}
762
+ disabled={saving}
763
+ >
764
+ <span className="provider-name">Voice Activity</span>
765
+ </button>
766
+ <button
767
+ className={`provider-option ${settings.inputMode === 'disabled' ? 'selected' : ''}`}
768
+ onClick={() => handleInputModeChange('disabled')}
769
+ disabled={saving}
770
+ >
771
+ <span className="provider-name">Disabled</span>
772
+ </button>
773
+ </div>
774
+ <p className="settings-hint">
775
+ {settings.inputMode === 'push_to_talk'
776
+ ? `Hold ${settings.pushToTalkKey} to speak`
777
+ : settings.inputMode === 'voice_activity'
778
+ ? 'Automatically detects when you speak'
779
+ : 'Voice input is disabled'}
780
+ </p>
781
+ </div>
782
+
783
+ {/* Response Mode */}
784
+ <div className="settings-section">
785
+ <h4>Response Mode</h4>
786
+ <p className="settings-description">When should responses be spoken aloud?</p>
787
+ <select
788
+ className="settings-select"
789
+ value={settings.responseMode}
790
+ onChange={(e) => handleResponseModeChange(e.target.value as VoiceResponseMode)}
791
+ disabled={saving}
792
+ >
793
+ <option value="auto">Auto - All responses</option>
794
+ <option value="smart">Smart - Only important responses</option>
795
+ <option value="manual">Manual - Only when requested</option>
796
+ </select>
797
+ </div>
798
+
799
+ {/* Volume and Speech Rate */}
800
+ <div className="settings-section">
801
+ <h4>Voice Settings</h4>
802
+
803
+ <div className="settings-field">
804
+ <label>Volume: {settings.volume}%</label>
805
+ <input
806
+ type="range"
807
+ min="0"
808
+ max="100"
809
+ value={settings.volume}
810
+ onChange={(e) => handleVolumeChange(parseInt(e.target.value))}
811
+ className="settings-slider"
812
+ />
813
+ </div>
814
+
815
+ <div className="settings-field">
816
+ <label>Speech Rate: {settings.speechRate}x</label>
817
+ <input
818
+ type="range"
819
+ min="0.5"
820
+ max="2"
821
+ step="0.1"
822
+ value={settings.speechRate}
823
+ onChange={(e) => handleSpeechRateChange(parseFloat(e.target.value))}
824
+ className="settings-slider"
825
+ />
826
+ </div>
827
+ </div>
828
+
829
+ {/* Language */}
830
+ <div className="settings-section">
831
+ <h4>Language</h4>
832
+ <select
833
+ className="settings-select"
834
+ value={settings.language}
835
+ onChange={(e) => handleLanguageChange(e.target.value)}
836
+ disabled={saving}
837
+ >
838
+ {VOICE_LANGUAGES.map((lang) => (
839
+ <option key={lang.code} value={lang.code}>
840
+ {lang.name}
841
+ </option>
842
+ ))}
843
+ </select>
844
+ </div>
845
+
846
+ {/* Test Speech */}
847
+ <div className="settings-section">
848
+ <h4>Test Voice</h4>
849
+ <p className="settings-description">Test the current voice configuration.</p>
850
+ <div className="button-group">
851
+ <button
852
+ className="button-primary"
853
+ onClick={handleTestSpeech}
854
+ disabled={testingSpeech || !settings.enabled}
855
+ >
856
+ {testingSpeech ? 'Speaking...' : 'Test Speech'}
857
+ </button>
858
+ {(testingSpeech || voiceState.isSpeaking) && (
859
+ <button className="button-secondary" onClick={handleStopSpeaking}>
860
+ Stop
861
+ </button>
862
+ )}
863
+ </div>
864
+ </div>
865
+ </div>
866
+ );
867
+ }