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