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,811 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { shell } from 'electron';
4
+ import { Workspace } from '../../../shared/types';
5
+ import { AgentDaemon } from '../daemon';
6
+ import { GuardrailManager } from '../../guardrails/guardrail-manager';
7
+ import mammoth from 'mammoth';
8
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
9
+ const pdfParseModule = require('pdf-parse');
10
+ // Handle both ESM default export and CommonJS module.exports
11
+ const pdfParse = (typeof pdfParseModule === 'function' ? pdfParseModule : pdfParseModule.default) as (dataBuffer: Buffer) => Promise<{
12
+ numpages: number;
13
+ info: { Title?: string; Author?: string };
14
+ text: string;
15
+ }>;
16
+
17
+ // Limits to prevent context overflow
18
+ const MAX_FILE_SIZE = 100 * 1024; // 100KB max for file reads
19
+ const MAX_DIR_ENTRIES = 100; // Max files to list per directory
20
+ const MAX_SEARCH_RESULTS = 50; // Max search results
21
+ const MAX_NAME_PAD = 48; // Cap for aligned directory listings
22
+
23
+ /**
24
+ * FileTools implements safe file operations within the workspace
25
+ */
26
+ export class FileTools {
27
+ constructor(
28
+ private workspace: Workspace,
29
+ private daemon: AgentDaemon,
30
+ private taskId: string
31
+ ) {}
32
+
33
+ /**
34
+ * Update the workspace for this tool
35
+ */
36
+ setWorkspace(workspace: Workspace): void {
37
+ this.workspace = workspace;
38
+ }
39
+
40
+ /**
41
+ * Dangerous paths that should never be written to, even with unrestricted access
42
+ */
43
+ private static readonly PROTECTED_PATHS = [
44
+ '/System',
45
+ '/Library',
46
+ '/usr',
47
+ '/bin',
48
+ '/sbin',
49
+ '/etc',
50
+ '/var',
51
+ '/private',
52
+ 'C:\\Windows',
53
+ 'C:\\Program Files',
54
+ 'C:\\Program Files (x86)',
55
+ ];
56
+
57
+ /**
58
+ * Check if a path is in a protected system location
59
+ */
60
+ private isProtectedPath(absolutePath: string): boolean {
61
+ const normalizedPath = path.normalize(absolutePath).toLowerCase();
62
+ return FileTools.PROTECTED_PATHS.some(protectedPath =>
63
+ normalizedPath.startsWith(protectedPath.toLowerCase())
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Check if path is allowed based on allowedPaths configuration
69
+ */
70
+ private isPathAllowed(absolutePath: string): boolean {
71
+ const allowedPaths = this.workspace.permissions.allowedPaths;
72
+ if (!allowedPaths || allowedPaths.length === 0) {
73
+ return false;
74
+ }
75
+
76
+ const normalizedPath = path.normalize(absolutePath);
77
+ return allowedPaths.some(allowed => {
78
+ const normalizedAllowed = path.normalize(allowed);
79
+ // Check if the path starts with or equals an allowed path
80
+ return normalizedPath === normalizedAllowed ||
81
+ normalizedPath.startsWith(normalizedAllowed + path.sep);
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Resolve path, supporting both workspace-relative and absolute paths
87
+ * When unrestrictedFileAccess is enabled, allows absolute paths anywhere (except protected locations)
88
+ * When allowedPaths is configured, allows specific paths outside workspace
89
+ */
90
+ private resolvePath(inputPath: string, operation: 'read' | 'write' | 'delete' = 'read'): string {
91
+ const normalizedWorkspace = path.resolve(this.workspace.path);
92
+
93
+ // Handle absolute paths
94
+ if (path.isAbsolute(inputPath)) {
95
+ const absolutePath = path.normalize(inputPath);
96
+
97
+ // Check if it's inside workspace (always allowed)
98
+ const relativeToWorkspace = path.relative(normalizedWorkspace, absolutePath);
99
+ if (!relativeToWorkspace.startsWith('..') && !path.isAbsolute(relativeToWorkspace)) {
100
+ return absolutePath;
101
+ }
102
+
103
+ // Outside workspace - check permissions
104
+ if (this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess) {
105
+ // With unrestricted access, block protected paths for writes
106
+ if (operation !== 'read' && this.isProtectedPath(absolutePath)) {
107
+ throw new Error(`Cannot ${operation} protected system path: ${absolutePath}`);
108
+ }
109
+ return absolutePath;
110
+ }
111
+
112
+ // Check if in allowed paths
113
+ if (this.isPathAllowed(absolutePath)) {
114
+ if (operation !== 'read' && this.isProtectedPath(absolutePath)) {
115
+ throw new Error(`Cannot ${operation} protected system path: ${absolutePath}`);
116
+ }
117
+ return absolutePath;
118
+ }
119
+
120
+ throw new Error(
121
+ 'Path is outside workspace boundary. Enable "Unrestricted File Access" in workspace settings ' +
122
+ 'or add specific paths to "Allowed Paths" to access files outside the workspace.'
123
+ );
124
+ }
125
+
126
+ // Handle relative paths (relative to workspace)
127
+ const resolved = path.resolve(normalizedWorkspace, inputPath);
128
+ const relative = path.relative(normalizedWorkspace, resolved);
129
+
130
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
131
+ // Path escapes workspace via ../ traversal
132
+ if (this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess) {
133
+ if (operation !== 'read' && this.isProtectedPath(resolved)) {
134
+ throw new Error(`Cannot ${operation} protected system path: ${resolved}`);
135
+ }
136
+ return resolved;
137
+ }
138
+
139
+ if (this.isPathAllowed(resolved)) {
140
+ if (operation !== 'read' && this.isProtectedPath(resolved)) {
141
+ throw new Error(`Cannot ${operation} protected system path: ${resolved}`);
142
+ }
143
+ return resolved;
144
+ }
145
+
146
+ throw new Error(
147
+ 'Path traversal outside workspace is not allowed. Enable "Unrestricted File Access" ' +
148
+ 'in workspace settings to access files outside the workspace.'
149
+ );
150
+ }
151
+
152
+ return resolved;
153
+ }
154
+
155
+ /**
156
+ * Check if operation is allowed based on permissions
157
+ */
158
+ private checkPermission(operation: 'read' | 'write' | 'delete'): void {
159
+ if (operation === 'read' && !this.workspace.permissions.read) {
160
+ throw new Error('Read permission not granted');
161
+ }
162
+ if (operation === 'write' && !this.workspace.permissions.write) {
163
+ throw new Error('Write permission not granted');
164
+ }
165
+ if (operation === 'delete' && !this.workspace.permissions.delete) {
166
+ throw new Error('Delete permission not granted');
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Read file contents (with size limit to prevent context overflow)
172
+ * Supports plain text, DOCX, and PDF files
173
+ */
174
+ async readFile(relativePath: string): Promise<{ content: string; size: number; truncated?: boolean; format?: string }> {
175
+ // Validate input
176
+ if (!relativePath || typeof relativePath !== 'string') {
177
+ throw new Error('Invalid path: path must be a non-empty string');
178
+ }
179
+
180
+ this.checkPermission('read');
181
+ const fullPath = this.resolvePath(relativePath, 'read');
182
+ const ext = path.extname(fullPath).toLowerCase();
183
+
184
+ try {
185
+ const stats = await fs.stat(fullPath);
186
+
187
+ // Handle DOCX files
188
+ if (ext === '.docx') {
189
+ return await this.readDocxFile(fullPath, stats.size);
190
+ }
191
+
192
+ // Handle PDF files
193
+ if (ext === '.pdf') {
194
+ return await this.readPdfFile(fullPath, stats.size);
195
+ }
196
+
197
+ // Handle plain text files
198
+ // Check file size before reading
199
+ if (stats.size > MAX_FILE_SIZE) {
200
+ // Read only the first portion of large files
201
+ const fileHandle = await fs.open(fullPath, 'r');
202
+ try {
203
+ const buffer = Buffer.alloc(MAX_FILE_SIZE);
204
+ await fileHandle.read(buffer, 0, MAX_FILE_SIZE, 0);
205
+
206
+ const content = buffer.toString('utf-8');
207
+ return {
208
+ content: content + `\n\n[... File truncated. Showing first ${Math.round(MAX_FILE_SIZE / 1024)}KB of ${Math.round(stats.size / 1024)}KB ...]`,
209
+ size: stats.size,
210
+ truncated: true,
211
+ };
212
+ } finally {
213
+ await fileHandle.close();
214
+ }
215
+ }
216
+
217
+ const content = await fs.readFile(fullPath, 'utf-8');
218
+ return {
219
+ content,
220
+ size: stats.size,
221
+ };
222
+ } catch (error: any) {
223
+ throw new Error(`Failed to read file: ${error.message}`);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Read DOCX file and extract text content
229
+ */
230
+ private async readDocxFile(fullPath: string, size: number): Promise<{ content: string; size: number; truncated?: boolean; format: string }> {
231
+ try {
232
+ const result = await mammoth.extractRawText({ path: fullPath });
233
+ let content = result.value;
234
+
235
+ // Check if extracted text exceeds limit
236
+ const truncated = content.length > MAX_FILE_SIZE;
237
+ if (truncated) {
238
+ content = content.slice(0, MAX_FILE_SIZE) +
239
+ `\n\n[... Content truncated. Showing first ${Math.round(MAX_FILE_SIZE / 1024)}KB of extracted text ...]`;
240
+ }
241
+
242
+ // Add any warnings from mammoth
243
+ if (result.messages && result.messages.length > 0) {
244
+ const warnings = result.messages.map(m => m.message).join('\n');
245
+ content = `[Document warnings: ${warnings}]\n\n${content}`;
246
+ }
247
+
248
+ return {
249
+ content,
250
+ size,
251
+ truncated,
252
+ format: 'docx',
253
+ };
254
+ } catch (error: any) {
255
+ throw new Error(`Failed to read DOCX file: ${error.message}`);
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Read PDF file and extract text content
261
+ */
262
+ private async readPdfFile(fullPath: string, size: number): Promise<{ content: string; size: number; truncated?: boolean; format: string }> {
263
+ try {
264
+ const dataBuffer = await fs.readFile(fullPath);
265
+ const data = await pdfParse(dataBuffer);
266
+
267
+ let content = data.text;
268
+
269
+ // Add metadata header
270
+ const metadata: string[] = [];
271
+ if (data.numpages) metadata.push(`Pages: ${data.numpages}`);
272
+ if (data.info?.Title) metadata.push(`Title: ${data.info.Title}`);
273
+ if (data.info?.Author) metadata.push(`Author: ${data.info.Author}`);
274
+
275
+ if (metadata.length > 0) {
276
+ content = `[PDF Metadata: ${metadata.join(' | ')}]\n\n${content}`;
277
+ }
278
+
279
+ // Check if extracted text exceeds limit
280
+ const truncated = content.length > MAX_FILE_SIZE;
281
+ if (truncated) {
282
+ content = content.slice(0, MAX_FILE_SIZE) +
283
+ `\n\n[... Content truncated. Showing first ${Math.round(MAX_FILE_SIZE / 1024)}KB of extracted text ...]`;
284
+ }
285
+
286
+ return {
287
+ content,
288
+ size,
289
+ truncated,
290
+ format: 'pdf',
291
+ };
292
+ } catch (error: any) {
293
+ throw new Error(`Failed to read PDF file: ${error.message}`);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Write file contents
299
+ */
300
+ async writeFile(relativePath: string, content: string): Promise<{ success: boolean; path: string }> {
301
+ // Validate inputs before proceeding
302
+ if (!relativePath || typeof relativePath !== 'string') {
303
+ throw new Error('Invalid path: path must be a non-empty string');
304
+ }
305
+
306
+ // Check for binary file extensions that shouldn't be written with write_file
307
+ const ext = path.extname(relativePath).toLowerCase();
308
+ const binaryExtensions = ['.docx', '.xlsx', '.pptx', '.pdf', '.zip', '.png', '.jpg', '.jpeg', '.gif', '.mp3', '.mp4', '.exe', '.dmg'];
309
+ if (binaryExtensions.includes(ext)) {
310
+ const suggestions: Record<string, string> = {
311
+ '.docx': 'Use "create_document" or "edit_document" tool instead',
312
+ '.xlsx': 'Use "create_spreadsheet" tool instead',
313
+ '.pptx': 'Use "create_presentation" tool instead',
314
+ '.pdf': 'Use "create_document" with format="pdf" instead',
315
+ };
316
+ const suggestion = suggestions[ext] || 'Use the appropriate skill tool for binary files';
317
+ throw new Error(
318
+ `Cannot use write_file for binary file type "${ext}". ` +
319
+ `The write_file tool is for text files only. ${suggestion}.`
320
+ );
321
+ }
322
+
323
+ if (content === undefined || content === null) {
324
+ throw new Error('Invalid content: content parameter is required but was not provided');
325
+ }
326
+ if (typeof content !== 'string') {
327
+ throw new Error(`Invalid content: expected string but received ${typeof content}`);
328
+ }
329
+
330
+ this.checkPermission('write');
331
+ const fullPath = this.resolvePath(relativePath, 'write');
332
+
333
+ // Check file size against guardrail limits
334
+ const contentSizeBytes = Buffer.byteLength(content, 'utf-8');
335
+ const sizeCheck = GuardrailManager.isFileSizeExceeded(contentSizeBytes);
336
+ if (sizeCheck.exceeded) {
337
+ throw new Error(
338
+ `File size limit exceeded: ${sizeCheck.sizeMB.toFixed(2)}MB exceeds limit of ${sizeCheck.limitMB}MB.\n` +
339
+ `You can adjust this limit in Settings > Guardrails.`
340
+ );
341
+ }
342
+
343
+ try {
344
+ // Ensure directory exists
345
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
346
+
347
+ // Write file
348
+ await fs.writeFile(fullPath, content, 'utf-8');
349
+
350
+ // Log artifact
351
+ this.daemon.logEvent(this.taskId, 'file_created', {
352
+ path: relativePath,
353
+ size: content.length,
354
+ });
355
+
356
+ return {
357
+ success: true,
358
+ path: relativePath,
359
+ };
360
+ } catch (error: any) {
361
+ throw new Error(`Failed to write file: ${error.message}`);
362
+ }
363
+ }
364
+
365
+ /**
366
+ * List directory contents (limited to prevent context overflow)
367
+ */
368
+ async listDirectory(relativePath: string = '.'): Promise<{
369
+ files: Array<{ name: string; type: 'file' | 'directory'; size: number }>;
370
+ totalCount: number;
371
+ truncated?: boolean;
372
+ }> {
373
+ // Validate and normalize input (use default if null/undefined)
374
+ const pathToUse = (relativePath && typeof relativePath === 'string') ? relativePath : '.';
375
+
376
+ this.checkPermission('read');
377
+ const fullPath = this.resolvePath(pathToUse, 'read');
378
+
379
+ try {
380
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
381
+ const totalCount = entries.length;
382
+
383
+ // Limit entries to prevent large responses
384
+ const limitedEntries = entries.slice(0, MAX_DIR_ENTRIES);
385
+
386
+ const files = await Promise.all(
387
+ limitedEntries.map(async entry => {
388
+ const entryPath = path.join(fullPath, entry.name);
389
+ try {
390
+ const stats = await fs.stat(entryPath);
391
+ return {
392
+ name: entry.name,
393
+ type: entry.isDirectory() ? 'directory' as const : 'file' as const,
394
+ size: stats.size,
395
+ };
396
+ } catch {
397
+ return {
398
+ name: entry.name,
399
+ type: 'file' as const,
400
+ size: 0,
401
+ };
402
+ }
403
+ })
404
+ );
405
+
406
+ return {
407
+ files,
408
+ totalCount,
409
+ truncated: totalCount > MAX_DIR_ENTRIES,
410
+ };
411
+ } catch (error: any) {
412
+ throw new Error(`Failed to list directory: ${error.message}`);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * List directory contents in a compact, size-aware format
418
+ * Mirrors MCP filesystem output for easier agent consumption.
419
+ */
420
+ async listDirectoryWithSizes(relativePath: string = '.'): Promise<{
421
+ output: string;
422
+ files: Array<{ name: string; type: 'file' | 'directory'; size: number }>;
423
+ totalCount: number;
424
+ truncated?: boolean;
425
+ combinedSize: number;
426
+ }> {
427
+ const pathToUse = (relativePath && typeof relativePath === 'string') ? relativePath : '.';
428
+
429
+ this.checkPermission('read');
430
+ const fullPath = this.resolvePath(pathToUse, 'read');
431
+
432
+ try {
433
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
434
+ const totalCount = entries.length;
435
+ const limitedEntries = entries.slice(0, MAX_DIR_ENTRIES);
436
+
437
+ const files = await Promise.all(
438
+ limitedEntries.map(async entry => {
439
+ const entryPath = path.join(fullPath, entry.name);
440
+ try {
441
+ const stats = await fs.stat(entryPath);
442
+ return {
443
+ name: entry.name,
444
+ type: entry.isDirectory() ? 'directory' as const : 'file' as const,
445
+ size: stats.size,
446
+ };
447
+ } catch {
448
+ return {
449
+ name: entry.name,
450
+ type: entry.isDirectory() ? 'directory' as const : 'file' as const,
451
+ size: 0,
452
+ };
453
+ }
454
+ })
455
+ );
456
+
457
+ const combinedSize = files.reduce((sum, entry) => sum + (entry.type === 'file' ? entry.size : 0), 0);
458
+ const output = this.formatDirectoryListing(files, combinedSize);
459
+
460
+ return {
461
+ output,
462
+ files,
463
+ totalCount,
464
+ truncated: totalCount > MAX_DIR_ENTRIES,
465
+ combinedSize,
466
+ };
467
+ } catch (error: any) {
468
+ throw new Error(`Failed to list directory: ${error.message}`);
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Get file or directory metadata
474
+ */
475
+ async getFileInfo(relativePath: string): Promise<{
476
+ size: number;
477
+ created: string;
478
+ modified: string;
479
+ accessed: string;
480
+ isDirectory: boolean;
481
+ isFile: boolean;
482
+ permissions: string;
483
+ }> {
484
+ if (!relativePath || typeof relativePath !== 'string') {
485
+ throw new Error('Invalid path: path must be a non-empty string');
486
+ }
487
+
488
+ this.checkPermission('read');
489
+ const fullPath = this.resolvePath(relativePath, 'read');
490
+
491
+ try {
492
+ const stats = await fs.stat(fullPath);
493
+ const permissions = (stats.mode & 0o777).toString(8);
494
+ return {
495
+ size: stats.size,
496
+ created: stats.birthtime.toISOString(),
497
+ modified: stats.mtime.toISOString(),
498
+ accessed: stats.atime.toISOString(),
499
+ isDirectory: stats.isDirectory(),
500
+ isFile: stats.isFile(),
501
+ permissions,
502
+ };
503
+ } catch (error: any) {
504
+ throw new Error(`Failed to get file info: ${error.message}`);
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Rename or move file
510
+ */
511
+ async renameFile(oldPath: string, newPath: string): Promise<{ success: boolean }> {
512
+ // Validate inputs
513
+ if (!oldPath || typeof oldPath !== 'string') {
514
+ throw new Error('Invalid oldPath: must be a non-empty string');
515
+ }
516
+ if (!newPath || typeof newPath !== 'string') {
517
+ throw new Error('Invalid newPath: must be a non-empty string');
518
+ }
519
+
520
+ this.checkPermission('write');
521
+ const oldFullPath = this.resolvePath(oldPath, 'write');
522
+ const newFullPath = this.resolvePath(newPath, 'write');
523
+
524
+ try {
525
+ // Ensure target directory exists
526
+ await fs.mkdir(path.dirname(newFullPath), { recursive: true });
527
+
528
+ await fs.rename(oldFullPath, newFullPath);
529
+
530
+ this.daemon.logEvent(this.taskId, 'file_modified', {
531
+ action: 'rename',
532
+ from: oldPath,
533
+ to: newPath,
534
+ });
535
+
536
+ return { success: true };
537
+ } catch (error: any) {
538
+ throw new Error(`Failed to rename file: ${error.message}`);
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Copy file (supports binary files like DOCX, PDF, images, etc.)
544
+ */
545
+ async copyFile(sourcePath: string, destPath: string): Promise<{ success: boolean; path: string }> {
546
+ // Validate inputs
547
+ if (!sourcePath || typeof sourcePath !== 'string') {
548
+ throw new Error('Invalid sourcePath: must be a non-empty string');
549
+ }
550
+ if (!destPath || typeof destPath !== 'string') {
551
+ throw new Error('Invalid destPath: must be a non-empty string');
552
+ }
553
+
554
+ this.checkPermission('read');
555
+ this.checkPermission('write');
556
+ const sourceFullPath = this.resolvePath(sourcePath, 'read');
557
+ const destFullPath = this.resolvePath(destPath, 'write');
558
+
559
+ try {
560
+ // Ensure target directory exists
561
+ await fs.mkdir(path.dirname(destFullPath), { recursive: true });
562
+
563
+ // Copy file using binary buffer (preserves exact content)
564
+ await fs.copyFile(sourceFullPath, destFullPath);
565
+
566
+ this.daemon.logEvent(this.taskId, 'file_created', {
567
+ path: destPath,
568
+ copiedFrom: sourcePath,
569
+ });
570
+
571
+ return {
572
+ success: true,
573
+ path: destPath,
574
+ };
575
+ } catch (error: any) {
576
+ throw new Error(`Failed to copy file: ${error.message}`);
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Delete file (requires approval)
582
+ * Uses shell.trashItem() for protected locations like /Applications
583
+ * Note: We don't check workspace.permissions.delete here because
584
+ * delete operations always require explicit user approval via requestApproval()
585
+ */
586
+ async deleteFile(relativePath: string): Promise<{ success: boolean; movedToTrash?: boolean }> {
587
+ // Validate input
588
+ if (!relativePath || typeof relativePath !== 'string') {
589
+ throw new Error('Invalid path: path must be a non-empty string');
590
+ }
591
+
592
+ const fullPath = this.resolvePath(relativePath, 'delete');
593
+
594
+ // Request user approval
595
+ const approved = await this.daemon.requestApproval(
596
+ this.taskId,
597
+ 'delete_file',
598
+ `Delete file: ${relativePath}`,
599
+ { path: relativePath }
600
+ );
601
+
602
+ if (!approved) {
603
+ throw new Error('User denied file deletion');
604
+ }
605
+
606
+ try {
607
+ // For .app bundles on macOS, use shell.trashItem directly (safer and expected behavior)
608
+ if (fullPath.endsWith('.app')) {
609
+ await shell.trashItem(fullPath);
610
+
611
+ this.daemon.logEvent(this.taskId, 'file_deleted', {
612
+ path: relativePath,
613
+ movedToTrash: true,
614
+ });
615
+
616
+ return { success: true, movedToTrash: true };
617
+ }
618
+
619
+ // For other files/directories, try direct deletion
620
+ const stats = await fs.stat(fullPath);
621
+ if (stats.isDirectory()) {
622
+ // Use force: true to handle read-only files and special cases
623
+ await fs.rm(fullPath, { recursive: true, force: true });
624
+ } else {
625
+ await fs.unlink(fullPath);
626
+ }
627
+
628
+ this.daemon.logEvent(this.taskId, 'file_deleted', {
629
+ path: relativePath,
630
+ });
631
+
632
+ return { success: true };
633
+ } catch (error: any) {
634
+ // If deletion fails, try moving to Trash as fallback
635
+ // This handles EPERM, EACCES, ENOTEMPTY and other filesystem errors
636
+ if (error.code === 'EPERM' || error.code === 'EACCES' || error.code === 'ENOTEMPTY' || error.code === 'EBUSY') {
637
+ try {
638
+ await shell.trashItem(fullPath);
639
+
640
+ this.daemon.logEvent(this.taskId, 'file_deleted', {
641
+ path: relativePath,
642
+ movedToTrash: true,
643
+ });
644
+
645
+ return { success: true, movedToTrash: true };
646
+ } catch (trashError: any) {
647
+ throw new Error(`Failed to delete file: ${error.code}. Could not move to Trash: ${trashError.message}`);
648
+ }
649
+ }
650
+ throw new Error(`Failed to delete file: ${error.message}`);
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Create directory
656
+ */
657
+ async createDirectory(relativePath: string): Promise<{ success: boolean }> {
658
+ // Validate input
659
+ if (!relativePath || typeof relativePath !== 'string') {
660
+ throw new Error('Invalid path: path must be a non-empty string');
661
+ }
662
+
663
+ this.checkPermission('write');
664
+ const fullPath = this.resolvePath(relativePath, 'write');
665
+
666
+ try {
667
+ await fs.mkdir(fullPath, { recursive: true });
668
+
669
+ this.daemon.logEvent(this.taskId, 'file_created', {
670
+ path: relativePath,
671
+ type: 'directory',
672
+ });
673
+
674
+ return { success: true };
675
+ } catch (error: any) {
676
+ throw new Error(`Failed to create directory: ${error.message}`);
677
+ }
678
+ }
679
+
680
+ /**
681
+ * Search files by name or content (limited to prevent context overflow)
682
+ */
683
+ async searchFiles(
684
+ query: string,
685
+ relativePath: string = '.'
686
+ ): Promise<{
687
+ matches: Array<{ path: string; type: 'filename' | 'content' }>;
688
+ totalFound: number;
689
+ truncated?: boolean;
690
+ }> {
691
+ // Validate input
692
+ if (!query || typeof query !== 'string') {
693
+ throw new Error('Invalid query: query must be a non-empty string');
694
+ }
695
+
696
+ this.checkPermission('read');
697
+ const fullPath = this.resolvePath(relativePath, 'read');
698
+ const matches: Array<{ path: string; type: 'filename' | 'content' }> = [];
699
+ let filesSearched = 0;
700
+ const maxFilesToSearch = 500; // Limit files to search for performance
701
+
702
+ const searchRecursive = async (dir: string) => {
703
+ if (matches.length >= MAX_SEARCH_RESULTS || filesSearched >= maxFilesToSearch) {
704
+ return;
705
+ }
706
+
707
+ let entries;
708
+ try {
709
+ entries = await fs.readdir(dir, { withFileTypes: true });
710
+ } catch {
711
+ return; // Skip directories we can't read
712
+ }
713
+
714
+ for (const entry of entries) {
715
+ if (matches.length >= MAX_SEARCH_RESULTS || filesSearched >= maxFilesToSearch) {
716
+ break;
717
+ }
718
+
719
+ const entryPath = path.join(dir, entry.name);
720
+ const relPath = path.relative(this.workspace.path, entryPath);
721
+
722
+ // Skip hidden files/directories and node_modules
723
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
724
+ continue;
725
+ }
726
+
727
+ // Check filename match
728
+ if (entry.name.toLowerCase().includes(query.toLowerCase())) {
729
+ matches.push({
730
+ path: relPath,
731
+ type: 'filename',
732
+ });
733
+ }
734
+
735
+ // Search content for small files only
736
+ if (entry.isFile()) {
737
+ filesSearched++;
738
+ try {
739
+ const stats = await fs.stat(entryPath);
740
+ // Only search small text files
741
+ if (stats.size < 50 * 1024) {
742
+ const content = await fs.readFile(entryPath, 'utf-8');
743
+ if (content.toLowerCase().includes(query.toLowerCase())) {
744
+ if (!matches.some(m => m.path === relPath)) {
745
+ matches.push({
746
+ path: relPath,
747
+ type: 'content',
748
+ });
749
+ }
750
+ }
751
+ }
752
+ } catch {
753
+ // Skip binary files or files that can't be read
754
+ }
755
+ } else if (entry.isDirectory()) {
756
+ await searchRecursive(entryPath);
757
+ }
758
+ }
759
+ };
760
+
761
+ try {
762
+ await searchRecursive(fullPath);
763
+ return {
764
+ matches: matches.slice(0, MAX_SEARCH_RESULTS),
765
+ totalFound: matches.length,
766
+ truncated: matches.length >= MAX_SEARCH_RESULTS,
767
+ };
768
+ } catch (error: any) {
769
+ throw new Error(`Search failed: ${error.message}`);
770
+ }
771
+ }
772
+
773
+ /**
774
+ * Format directory listing to match MCP-style output
775
+ */
776
+ private formatDirectoryListing(
777
+ entries: Array<{ name: string; type: 'file' | 'directory'; size: number }>,
778
+ combinedSize: number
779
+ ): string {
780
+ const maxNameLength = entries.reduce((max, entry) => Math.max(max, entry.name.length), 0);
781
+ const namePad = Math.min(Math.max(maxNameLength + 2, 16), MAX_NAME_PAD);
782
+
783
+ const lines = entries.map(entry => {
784
+ const label = entry.type === 'directory' ? '[DIR]' : '[FILE]';
785
+ const name = entry.name.padEnd(namePad, ' ');
786
+ const size = entry.type === 'file' ? this.formatBytes(entry.size) : '';
787
+ return `${label} ${name}${size}`.trimEnd();
788
+ });
789
+
790
+ const fileCount = entries.filter(entry => entry.type === 'file').length;
791
+ const dirCount = entries.filter(entry => entry.type === 'directory').length;
792
+ lines.push('');
793
+ lines.push(`Total: ${fileCount} files, ${dirCount} directories`);
794
+ lines.push(`Combined size: ${this.formatBytes(combinedSize)}`);
795
+
796
+ return lines.join('\n');
797
+ }
798
+
799
+ /**
800
+ * Human-readable byte formatting
801
+ */
802
+ private formatBytes(bytes: number): string {
803
+ if (bytes < 1024) return `${bytes} B`;
804
+ const kb = bytes / 1024;
805
+ if (kb < 1024) return `${kb.toFixed(2)} KB`;
806
+ const mb = kb / 1024;
807
+ if (mb < 1024) return `${mb.toFixed(2)} MB`;
808
+ const gb = mb / 1024;
809
+ return `${gb.toFixed(2)} GB`;
810
+ }
811
+ }