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,651 @@
1
+ import { spawn, ChildProcess, execSync } from 'child_process';
2
+ import { Workspace, CommandTerminationReason } from '../../../shared/types';
3
+ import { AgentDaemon } from '../daemon';
4
+ import { GuardrailManager } from '../../guardrails/guardrail-manager';
5
+
6
+ // Limits to prevent runaway commands
7
+ const MAX_TIMEOUT = 5 * 60 * 1000; // 5 minutes max
8
+ const DEFAULT_TIMEOUT = 60 * 1000; // 1 minute default
9
+ const MAX_OUTPUT_SIZE = 100 * 1024; // 100KB max output
10
+
11
+ /**
12
+ * Validate that a PID is a safe positive integer
13
+ * Prevents command injection if PID is somehow not a number
14
+ */
15
+ function isValidPid(pid: unknown): pid is number {
16
+ return typeof pid === 'number' &&
17
+ Number.isInteger(pid) &&
18
+ pid > 0 &&
19
+ pid <= 4194304; // Max PID on Linux (can be configured higher, but this is safe default)
20
+ }
21
+
22
+ /**
23
+ * Check if a process with the given PID exists and is owned by the current user
24
+ * Returns false if the process doesn't exist or is owned by another user
25
+ */
26
+ function isProcessOwnedByCurrentUser(pid: number): boolean {
27
+ if (!isValidPid(pid)) return false;
28
+
29
+ try {
30
+ // Use kill with signal 0 to check if process exists and we have permission to signal it
31
+ // This will throw EPERM if process exists but is owned by another user
32
+ // This will throw ESRCH if process doesn't exist
33
+ process.kill(pid, 0);
34
+ return true;
35
+ } catch (error: any) {
36
+ // ESRCH = no such process (that's fine, process exited)
37
+ // EPERM = permission denied (process exists but owned by another user - DON'T KILL)
38
+ if (error.code === 'EPERM') {
39
+ console.warn(`[ShellTools] Process ${pid} exists but is owned by another user, skipping`);
40
+ return false;
41
+ }
42
+ // Process doesn't exist, that's fine
43
+ return false;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Validate username for safe use in shell commands
49
+ * Prevents command injection via USER environment variable
50
+ */
51
+ function isValidUsername(username: string | undefined): username is string {
52
+ if (!username) return false;
53
+ // Username must be alphanumeric, underscore, or dash (standard POSIX username chars)
54
+ // Max length 32 chars (common limit)
55
+ return /^[a-zA-Z0-9_-]{1,32}$/.test(username);
56
+ }
57
+
58
+ /**
59
+ * Get all descendant process IDs for a given parent PID
60
+ * Uses pgrep to find child processes recursively
61
+ * Only returns processes owned by the current user for security
62
+ */
63
+ function getDescendantPids(parentPid: number): number[] {
64
+ if (!isValidPid(parentPid)) {
65
+ console.error(`[ShellTools] Invalid parent PID: ${parentPid}`);
66
+ return [];
67
+ }
68
+
69
+ const currentUser = process.env.USER;
70
+ // Validate username to prevent command injection
71
+ const safeUser = isValidUsername(currentUser) ? currentUser : undefined;
72
+ if (currentUser && !safeUser) {
73
+ console.warn(`[ShellTools] Invalid USER env var: ${currentUser}, skipping user filter`);
74
+ }
75
+
76
+ const descendants: number[] = [];
77
+ const toProcess: number[] = [parentPid];
78
+ const seen = new Set<number>(); // Prevent infinite loops from circular references
79
+
80
+ while (toProcess.length > 0) {
81
+ const pid = toProcess.pop()!;
82
+ if (seen.has(pid)) continue;
83
+ seen.add(pid);
84
+
85
+ try {
86
+ // pgrep -P finds direct children of the given PID
87
+ // Add -U $USER to only find processes owned by current user (security)
88
+ const pgrepCmd = safeUser
89
+ ? `pgrep -P ${pid} -U ${safeUser}`
90
+ : `pgrep -P ${pid}`;
91
+
92
+ const output = execSync(pgrepCmd, {
93
+ encoding: 'utf-8',
94
+ timeout: 1000,
95
+ // Don't inherit env to avoid any injection via environment
96
+ env: { PATH: '/usr/bin:/bin' },
97
+ });
98
+
99
+ const childPids = output.trim().split('\n')
100
+ .filter(line => line.length > 0)
101
+ .map(line => parseInt(line, 10))
102
+ .filter(childPid => isValidPid(childPid) && !seen.has(childPid));
103
+
104
+ descendants.push(...childPids);
105
+ toProcess.push(...childPids);
106
+ } catch {
107
+ // pgrep returns non-zero if no children found, which is fine
108
+ }
109
+ }
110
+
111
+ return descendants;
112
+ }
113
+
114
+ /**
115
+ * Kill a process and all its descendants
116
+ * Sends the signal to children first, then to the parent (bottom-up killing)
117
+ * Only kills processes owned by the current user for security
118
+ */
119
+ function killProcessTree(pid: number, signal: NodeJS.Signals): void {
120
+ if (!isValidPid(pid)) {
121
+ console.error(`[ShellTools] Refusing to kill invalid PID: ${pid}`);
122
+ return;
123
+ }
124
+
125
+ const descendants = getDescendantPids(pid);
126
+
127
+ // Kill descendants first (in reverse order, deepest children first)
128
+ for (const descendantPid of descendants.reverse()) {
129
+ // Double-check ownership before killing each process
130
+ if (isProcessOwnedByCurrentUser(descendantPid)) {
131
+ try {
132
+ process.kill(descendantPid, signal);
133
+ } catch {
134
+ // Process may have already exited
135
+ }
136
+ }
137
+ }
138
+
139
+ // Kill the parent process (also verify ownership)
140
+ if (isProcessOwnedByCurrentUser(pid)) {
141
+ try {
142
+ process.kill(pid, signal);
143
+ } catch {
144
+ // Process may have already exited
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * ShellTools implements shell command execution with user approval
151
+ */
152
+ export class ShellTools {
153
+ private readonly recentApprovals = new Map<string, { approvedAt: number; count: number }>();
154
+ private readonly approvalWindowMs = 2 * 60 * 1000;
155
+ // Track the currently running child process for stdin support
156
+ private activeProcess: ChildProcess | null = null;
157
+ // Track escalation timeouts so we can cancel them when process exits
158
+ private escalationTimeouts: ReturnType<typeof setTimeout>[] = [];
159
+ // Prevent multiple concurrent kill attempts
160
+ private killInProgress = false;
161
+ // Unique identifier for the current process session (prevents PID reuse issues)
162
+ private processSessionId = 0;
163
+ // Track user-initiated kills to signal termination reason to agent
164
+ private userKillRequested = false;
165
+
166
+ constructor(
167
+ private workspace: Workspace,
168
+ private daemon: AgentDaemon,
169
+ private taskId: string
170
+ ) {}
171
+
172
+ /**
173
+ * Update the workspace for this tool
174
+ */
175
+ setWorkspace(workspace: Workspace): void {
176
+ this.workspace = workspace;
177
+ }
178
+
179
+ /**
180
+ * Clear all pending escalation timeouts
181
+ * Called when process exits to prevent killing reused PIDs
182
+ */
183
+ private clearEscalationTimeouts(): void {
184
+ for (const timeout of this.escalationTimeouts) {
185
+ clearTimeout(timeout);
186
+ }
187
+ this.escalationTimeouts = [];
188
+ this.killInProgress = false;
189
+ }
190
+
191
+ /**
192
+ * Send input to the currently running command's stdin
193
+ */
194
+ sendStdin(input: string): boolean {
195
+ if (!this.activeProcess || !this.activeProcess.stdin || this.activeProcess.killed) {
196
+ return false;
197
+ }
198
+ try {
199
+ this.activeProcess.stdin.write(input);
200
+ // Echo the input to show it was sent
201
+ this.daemon.logEvent(this.taskId, 'command_output', {
202
+ type: 'stdin',
203
+ output: input,
204
+ });
205
+ return true;
206
+ } catch (error) {
207
+ console.error('Failed to write to stdin:', error);
208
+ return false;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Check if a command is currently running
214
+ */
215
+ hasActiveProcess(): boolean {
216
+ return this.activeProcess !== null && !this.activeProcess.killed;
217
+ }
218
+
219
+ /**
220
+ * Kill the currently running command and all its child processes
221
+ * @param force - If true, send SIGKILL immediately. Otherwise, try SIGINT first, then SIGTERM, then SIGKILL.
222
+ */
223
+ killProcess(force: boolean = false): boolean {
224
+ if (!this.activeProcess || this.activeProcess.killed) {
225
+ return false;
226
+ }
227
+
228
+ const pid = this.activeProcess.pid;
229
+ if (!isValidPid(pid)) {
230
+ console.error(`[ShellTools] Invalid PID for kill: ${pid}`);
231
+ return false;
232
+ }
233
+
234
+ // Prevent multiple concurrent kill chains (security: avoid race conditions)
235
+ if (this.killInProgress && !force) {
236
+ console.log(`[ShellTools] Kill already in progress, ignoring duplicate request`);
237
+ return true; // Return true since a kill is already underway
238
+ }
239
+
240
+ // Capture session ID to verify we're killing the right process in escalation timeouts
241
+ const currentSessionId = this.processSessionId;
242
+
243
+ // Mark this as a user-initiated kill so the close handler can signal the agent
244
+ this.userKillRequested = true;
245
+
246
+ if (force) {
247
+ // Force kill - immediate SIGKILL to entire process tree
248
+ // Clear any pending escalation timeouts first
249
+ this.clearEscalationTimeouts();
250
+
251
+ try {
252
+ killProcessTree(pid, 'SIGKILL');
253
+ this.daemon.logEvent(this.taskId, 'command_output', {
254
+ type: 'error',
255
+ output: '\n[Process tree force killed by user]\n',
256
+ });
257
+ return true;
258
+ } catch (error) {
259
+ console.error('Failed to force kill process tree:', error);
260
+ return false;
261
+ }
262
+ }
263
+
264
+ // Mark kill as in progress to prevent duplicate escalation chains
265
+ this.killInProgress = true;
266
+
267
+ try {
268
+ // Send SIGINT (Ctrl+C) to gracefully interrupt the process tree
269
+ killProcessTree(pid, 'SIGINT');
270
+ this.daemon.logEvent(this.taskId, 'command_output', {
271
+ type: 'error',
272
+ output: '\n^C [Process tree interrupted by user]\n',
273
+ });
274
+
275
+ // Set up escalation: if still running after 2s, send SIGTERM to tree
276
+ // If still running after 4s, send SIGKILL to tree
277
+ // These timeouts are tracked so they can be cancelled if process exits
278
+ const childProcess = this.activeProcess;
279
+
280
+ const sigtermTimeout = setTimeout(() => {
281
+ // Verify this is still the same process session (prevents PID reuse attacks)
282
+ if (currentSessionId !== this.processSessionId) {
283
+ console.log(`[ShellTools] Session ID mismatch, skipping SIGTERM escalation`);
284
+ return;
285
+ }
286
+ if (childProcess && !childProcess.killed && childProcess.pid === pid) {
287
+ // Additional safety: verify we own this process before killing
288
+ if (!isProcessOwnedByCurrentUser(pid)) {
289
+ console.warn(`[ShellTools] Process ${pid} no longer owned by current user, skipping SIGTERM`);
290
+ return;
291
+ }
292
+ try {
293
+ killProcessTree(pid, 'SIGTERM');
294
+ this.daemon.logEvent(this.taskId, 'command_output', {
295
+ type: 'error',
296
+ output: '[Escalating to SIGTERM for process tree...]\n',
297
+ });
298
+ } catch { /* Process may have exited */ }
299
+ }
300
+ }, 2000);
301
+ this.escalationTimeouts.push(sigtermTimeout);
302
+
303
+ const sigkillTimeout = setTimeout(() => {
304
+ // Verify this is still the same process session (prevents PID reuse attacks)
305
+ if (currentSessionId !== this.processSessionId) {
306
+ console.log(`[ShellTools] Session ID mismatch, skipping SIGKILL escalation`);
307
+ return;
308
+ }
309
+ if (childProcess && !childProcess.killed && childProcess.pid === pid) {
310
+ // Additional safety: verify we own this process before killing
311
+ if (!isProcessOwnedByCurrentUser(pid)) {
312
+ console.warn(`[ShellTools] Process ${pid} no longer owned by current user, skipping SIGKILL`);
313
+ return;
314
+ }
315
+ try {
316
+ killProcessTree(pid, 'SIGKILL');
317
+ this.daemon.logEvent(this.taskId, 'command_output', {
318
+ type: 'error',
319
+ output: '[Escalating to SIGKILL for process tree...]\n',
320
+ });
321
+ } catch { /* Process may have exited */ }
322
+ }
323
+ }, 4000);
324
+ this.escalationTimeouts.push(sigkillTimeout);
325
+
326
+ return true;
327
+ } catch (error) {
328
+ console.error('Failed to kill process tree:', error);
329
+ this.killInProgress = false;
330
+
331
+ // Try SIGTERM as fallback
332
+ try {
333
+ killProcessTree(pid, 'SIGTERM');
334
+ return true;
335
+ } catch {
336
+ // Last resort: SIGKILL
337
+ try {
338
+ killProcessTree(pid, 'SIGKILL');
339
+ return true;
340
+ } catch {
341
+ return false;
342
+ }
343
+ }
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Execute a shell command (requires user approval)
349
+ * Note: We don't check workspace.permissions.shell here because
350
+ * shell commands always require explicit user approval via requestApproval()
351
+ */
352
+ async runCommand(
353
+ command: string,
354
+ options?: {
355
+ cwd?: string;
356
+ timeout?: number;
357
+ env?: Record<string, string>;
358
+ }
359
+ ): Promise<{
360
+ success: boolean;
361
+ stdout: string;
362
+ stderr: string;
363
+ exitCode: number | null;
364
+ truncated?: boolean;
365
+ terminationReason?: CommandTerminationReason;
366
+ }> {
367
+ // Check if command is blocked by guardrails BEFORE anything else
368
+ const blockCheck = GuardrailManager.isCommandBlocked(command);
369
+ if (blockCheck.blocked) {
370
+ throw new Error(
371
+ `Command blocked by guardrails: "${command}"\n` +
372
+ `Matched pattern: ${blockCheck.pattern}\n` +
373
+ `This command has been blocked for safety. You can modify blocked patterns in Settings > Guardrails.`
374
+ );
375
+ }
376
+
377
+ // Check if command is trusted (auto-approve without user confirmation)
378
+ const trustCheck = GuardrailManager.isCommandTrusted(command);
379
+ let approved = false;
380
+
381
+ if (trustCheck.trusted) {
382
+ // Auto-approve trusted commands
383
+ approved = true;
384
+ this.daemon.logEvent(this.taskId, 'log', {
385
+ message: `Auto-approved trusted command (matched: ${trustCheck.pattern})`,
386
+ command,
387
+ });
388
+ } else {
389
+ const signature = this.getCommandSignature(command);
390
+ const previousApproval = signature ? this.recentApprovals.get(signature) : undefined;
391
+ const now = Date.now();
392
+
393
+ if (
394
+ signature &&
395
+ previousApproval &&
396
+ now - previousApproval.approvedAt <= this.approvalWindowMs &&
397
+ this.isAutoApprovalSafe(command)
398
+ ) {
399
+ approved = true;
400
+ previousApproval.count += 1;
401
+ previousApproval.approvedAt = now;
402
+ this.recentApprovals.set(signature, previousApproval);
403
+ this.daemon.logEvent(this.taskId, 'log', {
404
+ message: `Auto-approved similar command (approved ${previousApproval.count}x in last ${Math.round(this.approvalWindowMs / 1000)}s)`,
405
+ command,
406
+ });
407
+ } else {
408
+ // Request user approval before executing
409
+ approved = await this.daemon.requestApproval(
410
+ this.taskId,
411
+ 'run_command',
412
+ `Run command: ${command}`,
413
+ {
414
+ command,
415
+ cwd: options?.cwd || this.workspace.path,
416
+ timeout: options?.timeout || DEFAULT_TIMEOUT,
417
+ }
418
+ );
419
+
420
+ if (approved && signature) {
421
+ this.recentApprovals.set(signature, { approvedAt: now, count: 1 });
422
+ }
423
+ }
424
+ }
425
+
426
+ if (!approved) {
427
+ throw new Error('User denied command execution');
428
+ }
429
+
430
+ // Log the command execution attempt
431
+ this.daemon.logEvent(this.taskId, 'tool_call', {
432
+ tool: 'run_command',
433
+ command,
434
+ cwd: options?.cwd || this.workspace.path,
435
+ });
436
+
437
+ const timeout = Math.min(options?.timeout || DEFAULT_TIMEOUT, MAX_TIMEOUT);
438
+
439
+ // Create a minimal, safe environment (don't leak sensitive process.env vars like API keys)
440
+ const safeEnv: Record<string, string> = {
441
+ // Essential system variables only
442
+ PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',
443
+ HOME: process.env.HOME || '',
444
+ USER: process.env.USER || '',
445
+ SHELL: process.env.SHELL || '/bin/bash',
446
+ LANG: process.env.LANG || 'en_US.UTF-8',
447
+ TERM: process.env.TERM || 'xterm-256color',
448
+ TMPDIR: process.env.TMPDIR || '/tmp',
449
+ // Add any user-provided env vars (explicitly passed by caller)
450
+ ...options?.env,
451
+ };
452
+
453
+ const cwd = options?.cwd || this.workspace.path;
454
+
455
+ // Emit the command being executed
456
+ this.daemon.logEvent(this.taskId, 'command_output', {
457
+ command,
458
+ cwd,
459
+ type: 'start',
460
+ output: `$ ${command}\n`,
461
+ });
462
+
463
+ return new Promise((resolve) => {
464
+ let stdout = '';
465
+ let stderr = '';
466
+ let killed = false;
467
+
468
+ // Increment session ID to invalidate any pending escalation timeouts from previous commands
469
+ this.processSessionId++;
470
+ // Clear any leftover escalation timeouts from previous commands
471
+ this.clearEscalationTimeouts();
472
+
473
+ // Use shell to handle complex commands with pipes, redirects, etc.
474
+ const shell = process.env.SHELL || '/bin/bash';
475
+ const child = spawn(shell, ['-c', command], {
476
+ cwd,
477
+ env: safeEnv,
478
+ stdio: ['pipe', 'pipe', 'pipe'], // Enable stdin for interactive commands
479
+ });
480
+
481
+ // Store reference to active process for stdin support
482
+ this.activeProcess = child;
483
+
484
+ // Set timeout
485
+ const timeoutId = setTimeout(() => {
486
+ killed = true;
487
+ child.kill('SIGTERM');
488
+ this.daemon.logEvent(this.taskId, 'command_output', {
489
+ command,
490
+ type: 'error',
491
+ output: `\n[Command timed out after ${timeout / 1000}s]\n`,
492
+ });
493
+ }, timeout);
494
+
495
+ // Stream stdout
496
+ child.stdout.on('data', (data: Buffer) => {
497
+ const chunk = data.toString('utf-8');
498
+ stdout += chunk;
499
+ // Emit live output
500
+ this.daemon.logEvent(this.taskId, 'command_output', {
501
+ command,
502
+ type: 'stdout',
503
+ output: chunk,
504
+ });
505
+ });
506
+
507
+ // Stream stderr
508
+ child.stderr.on('data', (data: Buffer) => {
509
+ const chunk = data.toString('utf-8');
510
+ stderr += chunk;
511
+ // Emit live output
512
+ this.daemon.logEvent(this.taskId, 'command_output', {
513
+ command,
514
+ type: 'stderr',
515
+ output: chunk,
516
+ });
517
+ });
518
+
519
+ child.on('close', (code: number | null) => {
520
+ clearTimeout(timeoutId);
521
+ this.activeProcess = null; // Clear active process reference
522
+ // Clear any pending escalation timeouts to prevent killing reused PIDs
523
+ this.clearEscalationTimeouts();
524
+
525
+ // Determine termination reason to signal the agent
526
+ let terminationReason: CommandTerminationReason = 'normal';
527
+ if (this.userKillRequested) {
528
+ terminationReason = 'user_stopped';
529
+ } else if (killed) {
530
+ terminationReason = 'timeout';
531
+ }
532
+
533
+ // Reset for next command
534
+ this.userKillRequested = false;
535
+
536
+ const success = terminationReason === 'normal' && code === 0;
537
+ const truncatedStdout = this.truncateOutput(stdout);
538
+ const truncatedStderr = this.truncateOutput(stderr);
539
+ const exitCodeLabel = code === null ? 'unknown' : String(code);
540
+ const errorMessage = terminationReason === 'timeout'
541
+ ? 'Command timed out'
542
+ : terminationReason === 'user_stopped'
543
+ ? 'Command stopped by user'
544
+ : !success
545
+ ? `Command exited with code ${exitCodeLabel}`
546
+ : undefined;
547
+
548
+ // Emit command completion with termination reason
549
+ this.daemon.logEvent(this.taskId, 'command_output', {
550
+ command,
551
+ type: 'end',
552
+ exitCode: code,
553
+ success,
554
+ terminationReason,
555
+ });
556
+
557
+ this.daemon.logEvent(this.taskId, 'tool_result', {
558
+ tool: 'run_command',
559
+ success,
560
+ exitCode: code,
561
+ terminationReason,
562
+ error: errorMessage,
563
+ });
564
+
565
+ resolve({
566
+ success,
567
+ stdout: truncatedStdout,
568
+ stderr: truncatedStderr,
569
+ exitCode: code,
570
+ truncated: stdout.length > MAX_OUTPUT_SIZE || stderr.length > MAX_OUTPUT_SIZE,
571
+ terminationReason,
572
+ });
573
+ });
574
+
575
+ child.on('error', (error: Error) => {
576
+ clearTimeout(timeoutId);
577
+ this.activeProcess = null; // Clear active process reference
578
+ // Clear any pending escalation timeouts to prevent killing reused PIDs
579
+ this.clearEscalationTimeouts();
580
+ // Reset user kill flag
581
+ this.userKillRequested = false;
582
+
583
+ const terminationReason: CommandTerminationReason = 'error';
584
+
585
+ this.daemon.logEvent(this.taskId, 'command_output', {
586
+ command,
587
+ type: 'error',
588
+ output: `\n[Error: ${error.message}]\n`,
589
+ terminationReason,
590
+ });
591
+
592
+ this.daemon.logEvent(this.taskId, 'tool_result', {
593
+ tool: 'run_command',
594
+ success: false,
595
+ error: error.message,
596
+ terminationReason,
597
+ });
598
+
599
+ resolve({
600
+ success: false,
601
+ stdout: this.truncateOutput(stdout),
602
+ stderr: error.message,
603
+ exitCode: null,
604
+ terminationReason,
605
+ });
606
+ });
607
+ });
608
+ }
609
+
610
+ /**
611
+ * Generate a normalized signature for a command to detect similar repeats
612
+ */
613
+ private getCommandSignature(command: string): string {
614
+ if (!command) return '';
615
+ let signature = command.trim();
616
+ signature = signature.replace(/\s+/g, ' ');
617
+ signature = signature.replace(/"(?:[^"\\]|\\.)*"/g, '"<arg>"');
618
+ signature = signature.replace(/'(?:[^'\\]|\\.)*'/g, "'<arg>'");
619
+ signature = signature.replace(/(?:\/Users\/[^\s]+|~\/[^\s]+|\/[^\s]+)/g, '<path>');
620
+ return signature;
621
+ }
622
+
623
+ /**
624
+ * Safety check for auto-approving similar commands
625
+ */
626
+ private isAutoApprovalSafe(command: string): boolean {
627
+ return !/(^|\s)(sudo|rm|dd|mkfs|diskutil|shutdown|reboot|killall)\b/i.test(command);
628
+ }
629
+
630
+ /**
631
+ * Truncate output to prevent context overflow
632
+ */
633
+ private truncateOutput(output: string): string {
634
+ if (output.length <= MAX_OUTPUT_SIZE) {
635
+ return output;
636
+ }
637
+ return (
638
+ output.slice(0, MAX_OUTPUT_SIZE) +
639
+ `\n\n[... Output truncated. Showing first ${Math.round(MAX_OUTPUT_SIZE / 1024)}KB ...]`
640
+ );
641
+ }
642
+ }
643
+
644
+ // Export validation functions for testing
645
+ export const _testUtils = {
646
+ isValidPid,
647
+ isValidUsername,
648
+ isProcessOwnedByCurrentUser,
649
+ getDescendantPids,
650
+ killProcessTree,
651
+ };