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,2719 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ToolRegistry = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const fsPromises = __importStar(require("fs/promises"));
39
+ const path = __importStar(require("path"));
40
+ const file_tools_1 = require("./file-tools");
41
+ const skill_tools_1 = require("./skill-tools");
42
+ const search_tools_1 = require("./search-tools");
43
+ const web_fetch_tools_1 = require("./web-fetch-tools");
44
+ const glob_tools_1 = require("./glob-tools");
45
+ const grep_tools_1 = require("./grep-tools");
46
+ const edit_tools_1 = require("./edit-tools");
47
+ const browser_tools_1 = require("./browser-tools");
48
+ const shell_tools_1 = require("./shell-tools");
49
+ const image_tools_1 = require("./image-tools");
50
+ const system_tools_1 = require("./system-tools");
51
+ const cron_tools_1 = require("./cron-tools");
52
+ const canvas_tools_1 = require("./canvas-tools");
53
+ const mention_tools_1 = require("./mention-tools");
54
+ const x_tools_1 = require("./x-tools");
55
+ const search_1 = require("../search");
56
+ const MCPClientManager_1 = require("../../mcp/client/MCPClientManager");
57
+ const settings_1 = require("../../mcp/settings");
58
+ const policy_manager_1 = require("../../security/policy-manager");
59
+ const builtin_settings_1 = require("./builtin-settings");
60
+ const custom_skill_loader_1 = require("../custom-skill-loader");
61
+ const personality_manager_1 = require("../../settings/personality-manager");
62
+ const types_1 = require("../../../shared/types");
63
+ /**
64
+ * ToolRegistry manages all available tools and their execution
65
+ * Integrates with SecurityPolicyManager for context-aware tool filtering
66
+ */
67
+ class ToolRegistry {
68
+ constructor(workspace, daemon, taskId, gatewayContext) {
69
+ this.workspace = workspace;
70
+ this.daemon = daemon;
71
+ this.taskId = taskId;
72
+ this.shadowedToolsLogged = false;
73
+ this.fileTools = new file_tools_1.FileTools(workspace, daemon, taskId);
74
+ this.skillTools = new skill_tools_1.SkillTools(workspace, daemon, taskId);
75
+ this.searchTools = new search_tools_1.SearchTools(workspace, daemon, taskId);
76
+ this.webFetchTools = new web_fetch_tools_1.WebFetchTools(workspace, daemon, taskId);
77
+ this.globTools = new glob_tools_1.GlobTools(workspace, daemon, taskId);
78
+ this.grepTools = new grep_tools_1.GrepTools(workspace, daemon, taskId);
79
+ this.editTools = new edit_tools_1.EditTools(workspace, daemon, taskId);
80
+ this.browserTools = new browser_tools_1.BrowserTools(workspace, daemon, taskId);
81
+ this.shellTools = new shell_tools_1.ShellTools(workspace, daemon, taskId);
82
+ this.imageTools = new image_tools_1.ImageTools(workspace, daemon, taskId);
83
+ this.systemTools = new system_tools_1.SystemTools(workspace, daemon, taskId);
84
+ this.cronTools = new cron_tools_1.CronTools(workspace, daemon, taskId);
85
+ this.canvasTools = new canvas_tools_1.CanvasTools(workspace, daemon, taskId);
86
+ this.mentionTools = new mention_tools_1.MentionTools(workspace.id, taskId, daemon);
87
+ this.xTools = new x_tools_1.XTools(workspace, daemon, taskId);
88
+ this.gatewayContext = gatewayContext;
89
+ }
90
+ /**
91
+ * Get the current workspace
92
+ */
93
+ getWorkspace() {
94
+ return this.workspace;
95
+ }
96
+ /**
97
+ * Update the workspace for all tools
98
+ * Used when switching workspaces mid-task
99
+ */
100
+ setWorkspace(workspace) {
101
+ this.workspace = workspace;
102
+ this.fileTools.setWorkspace(workspace);
103
+ this.skillTools.setWorkspace(workspace);
104
+ this.searchTools.setWorkspace(workspace);
105
+ this.webFetchTools.setWorkspace(workspace);
106
+ this.globTools.setWorkspace(workspace);
107
+ this.grepTools.setWorkspace(workspace);
108
+ this.editTools.setWorkspace(workspace);
109
+ this.browserTools.setWorkspace(workspace);
110
+ this.shellTools.setWorkspace(workspace);
111
+ this.imageTools.setWorkspace(workspace);
112
+ this.systemTools.setWorkspace(workspace);
113
+ this.cronTools.setWorkspace(workspace);
114
+ this.canvasTools.setWorkspace(workspace);
115
+ this.xTools.setWorkspace(workspace);
116
+ }
117
+ /**
118
+ * Enforce new canvas sessions for follow-up messages by setting a cutoff timestamp.
119
+ * Sessions created before the cutoff will be rejected for canvas_push/open_url.
120
+ */
121
+ setCanvasSessionCutoff(cutoff) {
122
+ this.canvasTools.setSessionCutoff(cutoff);
123
+ }
124
+ /**
125
+ * Set the gateway context for tool filtering
126
+ * Used when task originates from Telegram/Discord/etc.
127
+ */
128
+ setGatewayContext(context) {
129
+ this.gatewayContext = context;
130
+ }
131
+ /**
132
+ * Send stdin input to the currently running shell command
133
+ */
134
+ sendStdin(input) {
135
+ return this.shellTools.sendStdin(input);
136
+ }
137
+ /**
138
+ * Check if a shell command is currently running
139
+ */
140
+ hasActiveShellProcess() {
141
+ return this.shellTools.hasActiveProcess();
142
+ }
143
+ /**
144
+ * Kill the currently running shell command (send SIGINT)
145
+ * @param force - If true, send SIGKILL immediately instead of graceful escalation
146
+ */
147
+ killShellProcess(force) {
148
+ return this.shellTools.killProcess(force);
149
+ }
150
+ /**
151
+ * Check if a tool is allowed based on security policy
152
+ */
153
+ isToolAllowed(toolName) {
154
+ return (0, policy_manager_1.isToolAllowedQuick)(toolName, this.workspace, this.gatewayContext);
155
+ }
156
+ /**
157
+ * Get all available tools in provider-agnostic format
158
+ * Filters tools based on workspace permissions, gateway context, and user settings
159
+ * Sorts tools by priority (high priority tools first)
160
+ */
161
+ getTools() {
162
+ const allTools = [
163
+ ...this.getFileToolDefinitions(),
164
+ ...this.getSkillToolDefinitions(),
165
+ ...glob_tools_1.GlobTools.getToolDefinitions(),
166
+ ...grep_tools_1.GrepTools.getToolDefinitions(),
167
+ ...edit_tools_1.EditTools.getToolDefinitions(),
168
+ ...web_fetch_tools_1.WebFetchTools.getToolDefinitions(),
169
+ ...browser_tools_1.BrowserTools.getToolDefinitions(),
170
+ ];
171
+ // Only add search tool if a provider is configured
172
+ if (search_1.SearchProviderFactory.isAnyProviderConfigured()) {
173
+ allTools.push(...this.getSearchToolDefinitions());
174
+ }
175
+ // Only add X/Twitter tool if integration is enabled
176
+ if (x_tools_1.XTools.isEnabled()) {
177
+ allTools.push(...this.getXToolDefinitions());
178
+ }
179
+ // Only add shell tool if workspace has shell permission
180
+ if (this.workspace.permissions.shell) {
181
+ allTools.push(...this.getShellToolDefinitions());
182
+ }
183
+ // Only add image tools if Gemini API is configured
184
+ if (image_tools_1.ImageTools.isAvailable()) {
185
+ allTools.push(...image_tools_1.ImageTools.getToolDefinitions());
186
+ }
187
+ // Always add system tools (they enable broader system interaction)
188
+ allTools.push(...system_tools_1.SystemTools.getToolDefinitions());
189
+ // Always add cron/scheduling tools (enables task scheduling)
190
+ allTools.push(...cron_tools_1.CronTools.getToolDefinitions());
191
+ // Always add canvas tools (enables visual workspace)
192
+ allTools.push(...canvas_tools_1.CanvasTools.getToolDefinitions());
193
+ // Always add mention tools (enables multi-agent collaboration)
194
+ allTools.push(...mention_tools_1.MentionTools.getToolDefinitions());
195
+ // Add meta tools for execution control
196
+ allTools.push(...this.getMetaToolDefinitions());
197
+ // Collect built-in tool names before adding MCP tools
198
+ const builtinToolNames = new Set(allTools.map(t => t.name));
199
+ // Add MCP tools from connected servers, filtering out those that shadow built-in tools
200
+ const settings = settings_1.MCPSettingsManager.loadSettings();
201
+ const prefix = settings.toolNamePrefix || 'mcp_';
202
+ const mcpTools = this.getMCPToolDefinitions();
203
+ const shadowedTools = [];
204
+ for (const mcpTool of mcpTools) {
205
+ const baseName = mcpTool.name.slice(prefix.length);
206
+ if (builtinToolNames.has(baseName)) {
207
+ // Skip MCP tools that shadow built-in tools - prefer built-in versions
208
+ shadowedTools.push(mcpTool.name);
209
+ }
210
+ else {
211
+ allTools.push(mcpTool);
212
+ }
213
+ }
214
+ if (shadowedTools.length > 0 && !this.shadowedToolsLogged) {
215
+ console.log(`[ToolRegistry] Skipped ${shadowedTools.length} MCP tools that shadow built-in tools:`, shadowedTools.join(', '));
216
+ this.shadowedToolsLogged = true;
217
+ }
218
+ // Filter tools based on security policy (workspace + gateway context)
219
+ let filteredTools = allTools.filter(tool => this.isToolAllowed(tool.name));
220
+ // Filter tools based on user's built-in tool settings
221
+ const disabledBySettings = [];
222
+ filteredTools = filteredTools.filter(tool => {
223
+ // MCP tools are not affected by built-in settings
224
+ if (tool.name.startsWith(prefix)) {
225
+ return true;
226
+ }
227
+ // Meta tools are always enabled
228
+ if (['revise_plan', 'set_personality', 'set_persona', 'set_agent_name', 'set_user_name', 'set_response_style', 'set_quirks', 'spawn_agent', 'wait_for_agent', 'get_agent_status', 'list_agents'].includes(tool.name)) {
229
+ return true;
230
+ }
231
+ // Check built-in tool settings
232
+ const isEnabled = builtin_settings_1.BuiltinToolsSettingsManager.isToolEnabled(tool.name);
233
+ if (!isEnabled) {
234
+ disabledBySettings.push(tool.name);
235
+ }
236
+ return isEnabled;
237
+ });
238
+ // Log filtered tools for debugging
239
+ const blockedTools = allTools.filter(tool => !this.isToolAllowed(tool.name));
240
+ if (blockedTools.length > 0 && this.gatewayContext) {
241
+ console.log(`[ToolRegistry] Blocked ${blockedTools.length} tools for ${this.gatewayContext} context:`, blockedTools.map(t => t.name).join(', '));
242
+ }
243
+ if (disabledBySettings.length > 0) {
244
+ console.log(`[ToolRegistry] Disabled ${disabledBySettings.length} tools by user settings:`, disabledBySettings.join(', '));
245
+ }
246
+ // Sort tools by priority (high first, then normal, then low)
247
+ // This helps influence which tools the LLM is more likely to choose
248
+ const priorityOrder = { high: 0, normal: 1, low: 2 };
249
+ filteredTools.sort((a, b) => {
250
+ // MCP tools always come after built-in tools at the same priority
251
+ const aIsMcp = a.name.startsWith(prefix);
252
+ const bIsMcp = b.name.startsWith(prefix);
253
+ const aPriority = aIsMcp ? 'normal' : builtin_settings_1.BuiltinToolsSettingsManager.getToolPriority(a.name);
254
+ const bPriority = bIsMcp ? 'normal' : builtin_settings_1.BuiltinToolsSettingsManager.getToolPriority(b.name);
255
+ const diff = priorityOrder[aPriority] - priorityOrder[bPriority];
256
+ if (diff !== 0)
257
+ return diff;
258
+ // Within same priority, put built-in tools first
259
+ if (aIsMcp && !bIsMcp)
260
+ return 1;
261
+ if (!aIsMcp && bIsMcp)
262
+ return -1;
263
+ return 0;
264
+ });
265
+ return filteredTools;
266
+ }
267
+ /**
268
+ * Get MCP tools from connected servers
269
+ */
270
+ getMCPToolDefinitions() {
271
+ try {
272
+ const mcpManager = MCPClientManager_1.MCPClientManager.getInstance();
273
+ const mcpTools = mcpManager.getAllTools();
274
+ const settings = settings_1.MCPSettingsManager.loadSettings();
275
+ const prefix = settings.toolNamePrefix || 'mcp_';
276
+ return mcpTools.map((tool) => ({
277
+ name: `${prefix}${tool.name}`,
278
+ description: tool.description || `MCP tool: ${tool.name}`,
279
+ input_schema: tool.inputSchema,
280
+ }));
281
+ }
282
+ catch (error) {
283
+ // MCP not initialized yet, return empty array
284
+ return [];
285
+ }
286
+ }
287
+ /**
288
+ * Set the callback for handling plan revisions
289
+ */
290
+ setPlanRevisionHandler(handler) {
291
+ this.planRevisionHandler = handler;
292
+ }
293
+ /**
294
+ * Set the callback for handling workspace switches
295
+ */
296
+ setWorkspaceSwitchHandler(handler) {
297
+ this.workspaceSwitchHandler = handler;
298
+ }
299
+ /**
300
+ * Switch to a different workspace
301
+ * Used internally by switch_workspace tool
302
+ */
303
+ async switchWorkspace(input) {
304
+ const { path: workspacePath, workspace_id } = input;
305
+ if (!workspacePath && !workspace_id) {
306
+ return {
307
+ success: false,
308
+ error: 'Either path or workspace_id must be provided',
309
+ };
310
+ }
311
+ if (!this.workspaceSwitchHandler) {
312
+ return {
313
+ success: false,
314
+ error: 'Workspace switching is not available in this context',
315
+ };
316
+ }
317
+ try {
318
+ // Look up the workspace
319
+ let newWorkspace;
320
+ if (workspace_id) {
321
+ newWorkspace = this.daemon.getWorkspaceById(workspace_id);
322
+ if (!newWorkspace) {
323
+ return {
324
+ success: false,
325
+ error: `Workspace not found with id: ${workspace_id}`,
326
+ };
327
+ }
328
+ }
329
+ else if (workspacePath) {
330
+ newWorkspace = this.daemon.getWorkspaceByPath(workspacePath);
331
+ if (!newWorkspace) {
332
+ // Try to create a new workspace for this path
333
+ const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
334
+ const fsModule = await Promise.resolve().then(() => __importStar(require('fs')));
335
+ // Check if path exists and is a directory
336
+ if (!fsModule.existsSync(workspacePath)) {
337
+ return {
338
+ success: false,
339
+ error: `Path does not exist: ${workspacePath}`,
340
+ };
341
+ }
342
+ const stats = fsModule.statSync(workspacePath);
343
+ if (!stats.isDirectory()) {
344
+ return {
345
+ success: false,
346
+ error: `Path is not a directory: ${workspacePath}`,
347
+ };
348
+ }
349
+ // Create a new workspace for this path
350
+ const name = pathModule.basename(workspacePath);
351
+ newWorkspace = this.daemon.createWorkspace(name, workspacePath);
352
+ }
353
+ }
354
+ if (!newWorkspace) {
355
+ return {
356
+ success: false,
357
+ error: 'Failed to find or create workspace',
358
+ };
359
+ }
360
+ // Call the switch handler to update executor and task
361
+ await this.workspaceSwitchHandler(newWorkspace);
362
+ // Update the local workspace reference
363
+ this.setWorkspace(newWorkspace);
364
+ return {
365
+ success: true,
366
+ workspace: {
367
+ id: newWorkspace.id,
368
+ name: newWorkspace.name,
369
+ path: newWorkspace.path,
370
+ },
371
+ };
372
+ }
373
+ catch (error) {
374
+ return {
375
+ success: false,
376
+ error: error.message || 'Failed to switch workspace',
377
+ };
378
+ }
379
+ }
380
+ /**
381
+ * Get human-readable tool descriptions
382
+ */
383
+ getToolDescriptions() {
384
+ let descriptions = `
385
+ File Operations:
386
+ - read_file: Read contents of a file (supports plain text, DOCX, and PDF)
387
+ - write_file: Write content to a file (creates or overwrites). Use edit_file for targeted changes instead.
388
+ - edit_file: Surgical text replacement (preferred over write_file for modifications)
389
+ - copy_file: Copy a file (supports binary files like DOCX, PDF, images)
390
+ - list_directory: List files and folders in a directory
391
+ - rename_file: Rename or move a file
392
+ - delete_file: Delete a file (requires approval)
393
+ - create_directory: Create a new directory
394
+ - search_files: Basic file search. Use glob for pattern matching, grep for content search instead.
395
+
396
+ Skills:
397
+ - create_spreadsheet: Create Excel spreadsheets with data and formulas
398
+ - create_document: Create Word documents or PDFs
399
+ - edit_document: Edit/append content to existing DOCX files
400
+ - create_presentation: Create PowerPoint presentations
401
+ - organize_folder: Organize and structure files in folders
402
+ - use_skill: Invoke a custom skill by ID to help accomplish tasks (see available skills below)
403
+
404
+ Skill Management (create, modify, duplicate skills):
405
+ - skill_list: List all skills with metadata (source, path, status)
406
+ - skill_get: Get full JSON content of a skill by ID
407
+ - skill_create: Create a new custom skill
408
+ - skill_duplicate: Duplicate an existing skill with modifications (great for variations)
409
+ - skill_update: Update an existing skill (managed/workspace only, not bundled)
410
+ - skill_delete: Delete a skill (managed/workspace only, not bundled)
411
+ Skills are stored in ~/Library/Application Support/cowork-os/skills/ (managed) or workspace/skills/ (workspace).
412
+
413
+ Code Tools (PREFERRED for code navigation and editing):
414
+ - glob: Fast pattern-based file search (e.g., "**/*.ts", "src/**/*.test.ts")
415
+ Use this FIRST to find files by pattern - much faster than search_files.
416
+ - grep: Powerful regex content search (e.g., "async function.*fetch", "class\\s+\\w+")
417
+ Use this FIRST for searching file contents - supports full regex.
418
+ - edit_file: Surgical text replacement in files (old_string -> new_string)
419
+ Use this INSTEAD of write_file for targeted changes - safer and preserves structure.
420
+
421
+ Web Fetch (PREFERRED for reading web content):
422
+ - web_fetch: Fetch and read content from a URL as markdown (fast, lightweight, no browser needed)
423
+ Use this FIRST when you need to read any web page, documentation, GitHub repo, or article.
424
+ - http_request: Make raw HTTP requests like curl (GET, POST, PUT, DELETE, etc.)
425
+ Use this for APIs, raw file downloads, or when you need custom headers/body.
426
+
427
+ Browser Automation (use only when interaction is needed):
428
+ - browser_navigate: Navigate to a URL (use only for pages requiring JS or when you need to interact)
429
+ - browser_screenshot: Take a screenshot of the page
430
+ - browser_get_content: Get page text, links, and forms (use after navigate, for inspecting interactive elements)
431
+ - browser_click: Click on an element
432
+ - browser_fill: Fill a form field
433
+ - browser_type: Type text character by character
434
+ - browser_press: Press a keyboard key
435
+ - browser_wait: Wait for an element to appear
436
+ - browser_scroll: Scroll the page
437
+ - browser_select: Select dropdown option
438
+ - browser_get_text: Get element text content
439
+ - browser_evaluate: Execute JavaScript
440
+ - browser_back/forward: Navigate history
441
+ - browser_reload: Reload the page
442
+ - browser_save_pdf: Save page as PDF
443
+ - browser_close: Close the browser`;
444
+ // Add search if configured
445
+ if (search_1.SearchProviderFactory.isAnyProviderConfigured()) {
446
+ descriptions += `
447
+
448
+ Web Search (for finding URLs, not reading them):
449
+ - web_search: Search the web for information (web, news, images)
450
+ Use to FIND relevant pages. To READ a specific URL, use web_fetch instead.`;
451
+ }
452
+ // Add shell if permitted
453
+ if (this.workspace.permissions.shell) {
454
+ descriptions += `
455
+
456
+ Shell Commands:
457
+ - run_command: Execute shell commands (requires user approval)`;
458
+ }
459
+ // Add image generation if Gemini is configured
460
+ if (image_tools_1.ImageTools.isAvailable()) {
461
+ descriptions += `
462
+
463
+ Image Generation (Nano Banana):
464
+ - generate_image: Generate images from text descriptions using AI
465
+ - nano-banana: Fast generation for quick iterations
466
+ - nano-banana-pro: High-quality generation for production use`;
467
+ }
468
+ // System tools are always available
469
+ descriptions += `
470
+
471
+ System Tools:
472
+ - system_info: Get OS, CPU, memory, and user info
473
+ - read_clipboard: Read system clipboard contents
474
+ - write_clipboard: Write text to system clipboard
475
+ - take_screenshot: Capture screen and save to workspace
476
+ - open_application: Open an app by name
477
+ - open_url: Open URL in default browser
478
+ - open_path: Open file/folder with default application
479
+ - show_in_folder: Reveal file in Finder/Explorer
480
+ - get_env: Read environment variable
481
+ - get_app_paths: Get system paths (home, downloads, etc.)
482
+ - run_applescript: Execute AppleScript on macOS (control apps, automate tasks)
483
+
484
+ Scheduling:
485
+ - schedule_task: Schedule tasks to run at specific times or intervals
486
+ - Create reminders: "remind me to X at Y"
487
+ - Recurring tasks: "every day at 9am, do X"
488
+ - One-time tasks: "at 3pm tomorrow, do X"
489
+ - Cron schedules: standard cron expressions supported
490
+
491
+ Live Canvas (Visual Workspace):
492
+ - canvas_create: Create a new canvas session for displaying interactive content
493
+ - canvas_push: Push HTML/CSS/JS content to the canvas. REQUIRED parameters: session_id and content (the HTML string).
494
+ Example: canvas_push({ session_id: "abc-123", content: "<!DOCTYPE html><html><body><h1>Hello</h1></body></html>" })
495
+ - canvas_open_url: Open a remote web page inside the canvas window for full in-app browsing (use for sites that block embedding)
496
+ - canvas_show: OPTIONAL - Only use if user needs full interactivity (clicking buttons, forms)
497
+ - canvas_hide: Hide the canvas window
498
+ - canvas_close: Close a canvas session
499
+ - canvas_eval: Execute JavaScript in the canvas context
500
+ - canvas_snapshot: Take a screenshot of the canvas
501
+ - canvas_list: List all active canvas sessions
502
+ IMPORTANT: When using canvas_push, you MUST provide the 'content' parameter with the full HTML string to display.
503
+
504
+ Plan Control:
505
+ - revise_plan: Modify remaining plan steps when obstacles are encountered or new information discovered
506
+ - switch_workspace: Switch to a different workspace/working directory. Use when you need to work in a different folder.
507
+ - set_personality: Change the assistant's communication style (professional, friendly, concise, creative, technical, casual).
508
+ - set_persona: Change the assistant's character persona (jarvis, friday, hal, computer, alfred, intern, sensei, pirate, noir, companion, or none).
509
+ - set_response_style: Adjust response preferences (emoji_usage, response_length, code_comments, explanation_depth).
510
+ - set_quirks: Set personality quirks (catchphrase, sign_off, analogy_domain).
511
+ - set_agent_name: Set or change the assistant's name when the user wants to give you a name.
512
+ - set_user_name: Store the user's name when they introduce themselves (e.g., "I'm Alice", "My name is Bob").`;
513
+ // Add custom skills available for use_skill
514
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
515
+ const skillDescriptions = skillLoader.getSkillDescriptionsForModel();
516
+ if (skillDescriptions) {
517
+ descriptions += `
518
+
519
+ Custom Skills (invoke with use_skill tool):
520
+ ${skillDescriptions}`;
521
+ }
522
+ return descriptions.trim();
523
+ }
524
+ /**
525
+ * Execute a tool by name
526
+ */
527
+ async executeTool(name, input) {
528
+ // File tools
529
+ if (name === 'read_file')
530
+ return await this.fileTools.readFile(input.path);
531
+ if (name === 'write_file')
532
+ return await this.fileTools.writeFile(input.path, input.content);
533
+ if (name === 'copy_file')
534
+ return await this.fileTools.copyFile(input.sourcePath, input.destPath);
535
+ if (name === 'list_directory')
536
+ return await this.fileTools.listDirectory(input.path);
537
+ if (name === 'list_directory_with_sizes')
538
+ return await this.fileTools.listDirectoryWithSizes(input.path);
539
+ if (name === 'get_file_info')
540
+ return await this.fileTools.getFileInfo(input.path);
541
+ if (name === 'rename_file')
542
+ return await this.fileTools.renameFile(input.oldPath, input.newPath);
543
+ if (name === 'delete_file')
544
+ return await this.fileTools.deleteFile(input.path);
545
+ if (name === 'create_directory')
546
+ return await this.fileTools.createDirectory(input.path);
547
+ if (name === 'search_files')
548
+ return await this.fileTools.searchFiles(input.query, input.path);
549
+ // Skill tools
550
+ if (name === 'create_spreadsheet')
551
+ return await this.skillTools.createSpreadsheet(input);
552
+ if (name === 'create_document')
553
+ return await this.skillTools.createDocument(input);
554
+ if (name === 'edit_document')
555
+ return await this.skillTools.editDocument(input);
556
+ if (name === 'create_presentation')
557
+ return await this.skillTools.createPresentation(input);
558
+ if (name === 'organize_folder')
559
+ return await this.skillTools.organizeFolder(input);
560
+ if (name === 'use_skill')
561
+ return await this.executeUseSkill(input);
562
+ // Skill management tools
563
+ if (name === 'skill_list')
564
+ return await this.executeSkillList(input);
565
+ if (name === 'skill_get')
566
+ return await this.executeSkillGet(input);
567
+ if (name === 'skill_create')
568
+ return await this.executeSkillCreate(input);
569
+ if (name === 'skill_duplicate')
570
+ return await this.executeSkillDuplicate(input);
571
+ if (name === 'skill_update')
572
+ return await this.executeSkillUpdate(input);
573
+ if (name === 'skill_delete')
574
+ return await this.executeSkillDelete(input);
575
+ // Code tools (glob, grep, edit)
576
+ if (name === 'glob')
577
+ return await this.globTools.glob(input);
578
+ if (name === 'grep')
579
+ return await this.grepTools.grep(input);
580
+ if (name === 'edit_file')
581
+ return await this.editTools.editFile(input);
582
+ // Web fetch tools (preferred for reading web content)
583
+ if (name === 'web_fetch')
584
+ return await this.webFetchTools.webFetch(input);
585
+ if (name === 'http_request')
586
+ return await this.webFetchTools.httpRequest(input);
587
+ // Browser tools
588
+ if (browser_tools_1.BrowserTools.isBrowserTool(name)) {
589
+ return await this.browserTools.executeTool(name, input);
590
+ }
591
+ // Search tools
592
+ if (name === 'web_search')
593
+ return await this.searchTools.webSearch(input);
594
+ // X/Twitter tools
595
+ if (name === 'x_action')
596
+ return await this.xTools.executeAction(input);
597
+ // Shell tools
598
+ if (name === 'run_command')
599
+ return await this.shellTools.runCommand(input.command, input);
600
+ // Image tools
601
+ if (name === 'generate_image')
602
+ return await this.imageTools.generateImage(input);
603
+ // System tools
604
+ if (name === 'system_info')
605
+ return await this.systemTools.getSystemInfo();
606
+ if (name === 'read_clipboard')
607
+ return await this.systemTools.readClipboard();
608
+ if (name === 'write_clipboard')
609
+ return await this.systemTools.writeClipboard(input.text);
610
+ if (name === 'take_screenshot')
611
+ return await this.systemTools.takeScreenshot(input);
612
+ if (name === 'open_application')
613
+ return await this.systemTools.openApplication(input.appName);
614
+ if (name === 'open_url')
615
+ return await this.systemTools.openUrl(input.url);
616
+ if (name === 'open_path')
617
+ return await this.systemTools.openPath(input.path);
618
+ if (name === 'show_in_folder')
619
+ return await this.systemTools.showInFolder(input.path);
620
+ if (name === 'get_env')
621
+ return await this.systemTools.getEnvVariable(input.name);
622
+ if (name === 'get_app_paths')
623
+ return this.systemTools.getAppPaths();
624
+ if (name === 'run_applescript')
625
+ return await this.systemTools.runAppleScript(input.script);
626
+ // Cron/scheduling tools
627
+ if (name === 'schedule_task')
628
+ return await this.cronTools.executeAction(input);
629
+ // Canvas tools
630
+ if (name === 'canvas_create')
631
+ return await this.canvasTools.createCanvas(input.title);
632
+ if (name === 'canvas_push') {
633
+ console.log(`[ToolRegistry] canvas_push input keys:`, Object.keys(input || {}));
634
+ console.log(`[ToolRegistry] canvas_push session_id:`, input?.session_id);
635
+ console.log(`[ToolRegistry] canvas_push content present:`, 'content' in (input || {}), `content length:`, input?.content?.length ?? 'N/A');
636
+ return await this.canvasTools.pushContent(input.session_id, input.content, input.filename);
637
+ }
638
+ if (name === 'canvas_open_url')
639
+ return await this.canvasTools.openUrl(input.session_id, input.url, input.show);
640
+ if (name === 'canvas_show')
641
+ return await this.canvasTools.showCanvas(input.session_id);
642
+ if (name === 'canvas_hide')
643
+ return this.canvasTools.hideCanvas(input.session_id);
644
+ if (name === 'canvas_close')
645
+ return await this.canvasTools.closeCanvas(input.session_id);
646
+ if (name === 'canvas_eval')
647
+ return await this.canvasTools.evalScript(input.session_id, input.script);
648
+ if (name === 'canvas_snapshot')
649
+ return await this.canvasTools.takeSnapshot(input.session_id);
650
+ if (name === 'canvas_list')
651
+ return this.canvasTools.listSessions();
652
+ // Mention tools (multi-agent collaboration)
653
+ if (name === 'list_agent_roles')
654
+ return await this.mentionTools.listAgentRoles();
655
+ if (name === 'mention_agent')
656
+ return await this.mentionTools.mentionAgent(input);
657
+ if (name === 'get_pending_mentions')
658
+ return await this.mentionTools.getPendingMentions();
659
+ if (name === 'acknowledge_mention')
660
+ return await this.mentionTools.acknowledgeMention(input.mentionId);
661
+ if (name === 'complete_mention')
662
+ return await this.mentionTools.completeMention(input.mentionId);
663
+ // Meta tools
664
+ if (name === 'revise_plan') {
665
+ if (!this.planRevisionHandler) {
666
+ throw new Error('Plan revision not available at this time');
667
+ }
668
+ const newSteps = input.newSteps || [];
669
+ const reason = input.reason || 'No reason provided';
670
+ const clearRemaining = input.clearRemaining || false;
671
+ this.planRevisionHandler(newSteps, reason, clearRemaining);
672
+ let message = '';
673
+ if (clearRemaining) {
674
+ message = `Plan revised: Cleared remaining steps. `;
675
+ }
676
+ if (newSteps.length > 0) {
677
+ message += `${newSteps.length} new steps added. `;
678
+ }
679
+ message += `Reason: ${reason}`;
680
+ return {
681
+ success: true,
682
+ message: message.trim(),
683
+ clearedRemaining: clearRemaining,
684
+ };
685
+ }
686
+ if (name === 'switch_workspace') {
687
+ return await this.switchWorkspace(input);
688
+ }
689
+ if (name === 'set_personality') {
690
+ return this.setPersonality(input);
691
+ }
692
+ if (name === 'set_agent_name') {
693
+ return this.setAgentName(input);
694
+ }
695
+ if (name === 'set_user_name') {
696
+ return this.setUserName(input);
697
+ }
698
+ if (name === 'set_persona') {
699
+ return this.setPersona(input);
700
+ }
701
+ if (name === 'set_response_style') {
702
+ return this.setResponseStyle(input);
703
+ }
704
+ if (name === 'set_quirks') {
705
+ return this.setQuirks(input);
706
+ }
707
+ // Sub-Agent / Parallel Agent tools
708
+ if (name === 'spawn_agent') {
709
+ return await this.spawnAgent(input);
710
+ }
711
+ if (name === 'wait_for_agent') {
712
+ return await this.waitForAgent(input);
713
+ }
714
+ if (name === 'get_agent_status') {
715
+ return await this.getAgentStatus(input);
716
+ }
717
+ if (name === 'list_agents') {
718
+ return await this.listAgents(input);
719
+ }
720
+ // MCP tools (prefixed with mcp_ by default)
721
+ const mcpToolResult = await this.tryExecuteMCPTool(name, input);
722
+ if (mcpToolResult !== null) {
723
+ return mcpToolResult;
724
+ }
725
+ throw new Error(`Unknown tool: ${name}`);
726
+ }
727
+ /**
728
+ * Try to execute an MCP tool if the name matches
729
+ */
730
+ async tryExecuteMCPTool(name, input) {
731
+ const settings = settings_1.MCPSettingsManager.loadSettings();
732
+ const prefix = settings.toolNamePrefix || 'mcp_';
733
+ // Not an MCP tool if it doesn't have the prefix
734
+ if (!name.startsWith(prefix)) {
735
+ return null;
736
+ }
737
+ const mcpToolName = name.slice(prefix.length);
738
+ // Try to get the MCP manager - if not initialized, this is not an MCP tool call
739
+ let mcpManager;
740
+ try {
741
+ mcpManager = MCPClientManager_1.MCPClientManager.getInstance();
742
+ }
743
+ catch (error) {
744
+ // MCP not initialized
745
+ return null;
746
+ }
747
+ // Check if the tool is registered
748
+ if (!mcpManager.hasTool(mcpToolName)) {
749
+ return null;
750
+ }
751
+ // Guard against using puppeteer_evaluate for Node/shell execution
752
+ if (mcpToolName === 'puppeteer_evaluate') {
753
+ const script = typeof input?.script === 'string' ? input.script : '';
754
+ if (/(require\s*\(|child_process|execSync|exec\(|spawn\()/i.test(script)) {
755
+ throw new Error("MCP tool 'puppeteer_evaluate' cannot run Node shell APIs. " +
756
+ "Use run_command for shell commands or browser_evaluate for DOM-only scripts.");
757
+ }
758
+ }
759
+ // At this point, we know it's a valid MCP tool - any errors should be propagated
760
+ console.log(`[ToolRegistry] Executing MCP tool: ${mcpToolName}`);
761
+ try {
762
+ const result = await mcpManager.callTool(mcpToolName, input);
763
+ // Format MCP result and process any generated files
764
+ return await this.formatMCPResult(result, mcpToolName, input);
765
+ }
766
+ catch (error) {
767
+ // Tool was registered but execution failed - propagate the error with context
768
+ throw new Error(`MCP tool '${mcpToolName}' failed: ${error.message}`);
769
+ }
770
+ }
771
+ /**
772
+ * Format MCP call result for agent consumption
773
+ * Also handles file artifacts (screenshots, etc.) from MCP tools
774
+ */
775
+ async formatMCPResult(result, toolName, input) {
776
+ if (!result)
777
+ return { success: true };
778
+ // Check if it's an MCP CallResult format
779
+ if (result.content && Array.isArray(result.content)) {
780
+ if (result.isError) {
781
+ throw new Error(result.content.map((c) => c.text || '').join('\n') || 'MCP tool execution failed');
782
+ }
783
+ // Handle image content from MCP tools (e.g., take_screenshot)
784
+ for (const content of result.content) {
785
+ if (content.type === 'image' && content.data) {
786
+ // Save inline image to workspace
787
+ const filename = input?.filePath
788
+ ? path.basename(input.filePath)
789
+ : `mcp-screenshot-${Date.now()}.png`;
790
+ const outputPath = path.join(this.workspace.path, filename);
791
+ try {
792
+ const imageBuffer = Buffer.from(content.data, 'base64');
793
+ await fsPromises.writeFile(outputPath, imageBuffer);
794
+ // Emit file_created event
795
+ this.daemon.logEvent(this.taskId, 'file_created', {
796
+ path: filename,
797
+ type: 'screenshot',
798
+ source: 'mcp',
799
+ });
800
+ // Register as artifact
801
+ this.daemon.registerArtifact(this.taskId, outputPath, content.mimeType || 'image/png');
802
+ console.log(`[ToolRegistry] Saved MCP image artifact: ${filename}`);
803
+ }
804
+ catch (error) {
805
+ console.error(`[ToolRegistry] Failed to save MCP image:`, error);
806
+ }
807
+ }
808
+ }
809
+ // Combine text content
810
+ const textParts = result.content
811
+ .filter((c) => c.type === 'text')
812
+ .map((c) => c.text);
813
+ if (textParts.length > 0) {
814
+ return textParts.join('\n');
815
+ }
816
+ // Return raw result if no text content
817
+ return result;
818
+ }
819
+ // Handle file paths in MCP results (when filePath parameter was provided)
820
+ if (input?.filePath && typeof input.filePath === 'string') {
821
+ const providedPath = input.filePath;
822
+ const filename = path.basename(providedPath);
823
+ const workspacePath = path.join(this.workspace.path, filename);
824
+ // Check various possible locations for the file
825
+ const possiblePaths = [
826
+ providedPath, // Absolute path as provided
827
+ path.resolve(providedPath), // Resolved relative path
828
+ path.join(process.cwd(), providedPath), // Relative to current working directory
829
+ workspacePath, // Already in workspace
830
+ ];
831
+ for (const sourcePath of possiblePaths) {
832
+ try {
833
+ if (fs.existsSync(sourcePath)) {
834
+ // File found - copy to workspace if not already there
835
+ if (sourcePath !== workspacePath && !sourcePath.startsWith(this.workspace.path)) {
836
+ await fsPromises.copyFile(sourcePath, workspacePath);
837
+ console.log(`[ToolRegistry] Copied MCP file to workspace: ${sourcePath} -> ${workspacePath}`);
838
+ }
839
+ // Emit file_created event with workspace-relative path
840
+ this.daemon.logEvent(this.taskId, 'file_created', {
841
+ path: filename,
842
+ type: 'screenshot',
843
+ source: 'mcp',
844
+ });
845
+ // Register as artifact if it's an image
846
+ const ext = path.extname(filename).toLowerCase();
847
+ const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'];
848
+ if (imageExtensions.includes(ext)) {
849
+ const mimeTypes = {
850
+ '.png': 'image/png',
851
+ '.jpg': 'image/jpeg',
852
+ '.jpeg': 'image/jpeg',
853
+ '.gif': 'image/gif',
854
+ '.webp': 'image/webp',
855
+ '.bmp': 'image/bmp',
856
+ };
857
+ this.daemon.registerArtifact(this.taskId, workspacePath, mimeTypes[ext] || 'image/png');
858
+ }
859
+ break;
860
+ }
861
+ }
862
+ catch (error) {
863
+ // Continue checking other paths
864
+ }
865
+ }
866
+ }
867
+ // Return as-is if not in MCP format
868
+ return result;
869
+ }
870
+ /**
871
+ * Cleanup resources (call when task is done)
872
+ */
873
+ async cleanup() {
874
+ await this.browserTools.cleanup();
875
+ }
876
+ /**
877
+ * Execute the use_skill tool - invokes a custom skill by ID
878
+ */
879
+ async executeUseSkill(input) {
880
+ const { skill_id, parameters = {} } = input;
881
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
882
+ const skill = skillLoader.getSkill(skill_id);
883
+ if (!skill) {
884
+ // List available skills to help the agent
885
+ const availableSkills = skillLoader.listModelInvocableSkills().map(s => s.id);
886
+ return {
887
+ success: false,
888
+ error: `Skill '${skill_id}' not found`,
889
+ available_skills: availableSkills.slice(0, 20), // Show up to 20 skills
890
+ hint: 'Use one of the available skill IDs listed above',
891
+ };
892
+ }
893
+ // Check if skill can be invoked by model
894
+ if (skill.invocation?.disableModelInvocation) {
895
+ return {
896
+ success: false,
897
+ error: `Skill '${skill_id}' cannot be invoked automatically`,
898
+ reason: 'This skill is configured for manual invocation only',
899
+ };
900
+ }
901
+ // Check for required parameters
902
+ const missingParams = [];
903
+ if (skill.parameters) {
904
+ for (const param of skill.parameters) {
905
+ if (param.required && !(param.name in parameters) && param.default === undefined) {
906
+ missingParams.push(param.name);
907
+ }
908
+ }
909
+ }
910
+ if (missingParams.length > 0) {
911
+ return {
912
+ success: false,
913
+ error: `Missing required parameters: ${missingParams.join(', ')}`,
914
+ skill_id,
915
+ parameters: skill.parameters?.map(p => ({
916
+ name: p.name,
917
+ type: p.type,
918
+ description: p.description,
919
+ required: p.required,
920
+ default: p.default,
921
+ options: p.options,
922
+ })),
923
+ };
924
+ }
925
+ // Expand the skill prompt with provided parameters
926
+ const expandedPrompt = skillLoader.expandPrompt(skill, parameters);
927
+ // Log the skill invocation
928
+ this.daemon.logEvent(this.taskId, 'log', {
929
+ message: `Using skill: ${skill.name}`,
930
+ skillId: skill_id,
931
+ parameters,
932
+ });
933
+ return {
934
+ success: true,
935
+ skill_id,
936
+ skill_name: skill.name,
937
+ skill_description: skill.description,
938
+ expanded_prompt: expandedPrompt,
939
+ instruction: 'Execute the task according to the expanded_prompt above. Follow its instructions to complete the user\'s request.',
940
+ };
941
+ }
942
+ /**
943
+ * List all skills with metadata
944
+ */
945
+ async executeSkillList(input) {
946
+ const { source = 'all', include_disabled = true } = input;
947
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
948
+ let skills = skillLoader.listSkills();
949
+ // Filter by source if specified
950
+ if (source !== 'all') {
951
+ skills = skills.filter(s => s.source === source);
952
+ }
953
+ // Filter out disabled if requested
954
+ if (!include_disabled) {
955
+ skills = skills.filter(s => s.enabled !== false);
956
+ }
957
+ // Format for agent consumption
958
+ const formattedSkills = skills.map(s => ({
959
+ id: s.id,
960
+ name: s.name,
961
+ description: s.description,
962
+ category: s.category || 'General',
963
+ icon: s.icon || '',
964
+ source: s.source,
965
+ filePath: s.filePath,
966
+ enabled: s.enabled !== false,
967
+ hasParameters: (s.parameters?.length || 0) > 0,
968
+ parameterCount: s.parameters?.length || 0,
969
+ }));
970
+ return {
971
+ success: true,
972
+ total: formattedSkills.length,
973
+ skills: formattedSkills,
974
+ directories: {
975
+ bundled: skillLoader.getBundledSkillsDir(),
976
+ managed: skillLoader.getManagedSkillsDir(),
977
+ workspace: skillLoader.getWorkspaceSkillsDir(),
978
+ },
979
+ };
980
+ }
981
+ /**
982
+ * Get full details of a specific skill
983
+ */
984
+ async executeSkillGet(input) {
985
+ const { skill_id } = input;
986
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
987
+ const skill = skillLoader.getSkill(skill_id);
988
+ if (!skill) {
989
+ const availableSkills = skillLoader.listSkills().map(s => s.id);
990
+ return {
991
+ success: false,
992
+ error: `Skill '${skill_id}' not found`,
993
+ available_skills: availableSkills.slice(0, 30),
994
+ hint: 'Use skill_list to see all available skills',
995
+ };
996
+ }
997
+ // Return full skill definition (useful for duplication/modification)
998
+ return {
999
+ success: true,
1000
+ skill: {
1001
+ id: skill.id,
1002
+ name: skill.name,
1003
+ description: skill.description,
1004
+ prompt: skill.prompt,
1005
+ icon: skill.icon,
1006
+ category: skill.category,
1007
+ priority: skill.priority,
1008
+ parameters: skill.parameters,
1009
+ enabled: skill.enabled,
1010
+ type: skill.type,
1011
+ invocation: skill.invocation,
1012
+ requires: skill.requires,
1013
+ source: skill.source,
1014
+ filePath: skill.filePath,
1015
+ },
1016
+ };
1017
+ }
1018
+ /**
1019
+ * Create a new skill
1020
+ */
1021
+ async executeSkillCreate(input) {
1022
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
1023
+ // Check if skill with this ID already exists
1024
+ const existing = skillLoader.getSkill(input.id);
1025
+ if (existing) {
1026
+ return {
1027
+ success: false,
1028
+ error: `Skill with ID '${input.id}' already exists`,
1029
+ existing_skill: {
1030
+ id: existing.id,
1031
+ name: existing.name,
1032
+ source: existing.source,
1033
+ },
1034
+ hint: 'Use a different ID or use skill_update to modify the existing skill',
1035
+ };
1036
+ }
1037
+ // Validate ID format
1038
+ if (!/^[a-z0-9-]+$/.test(input.id)) {
1039
+ return {
1040
+ success: false,
1041
+ error: 'Invalid skill ID format',
1042
+ hint: 'Skill ID should be lowercase, using only letters, numbers, and hyphens (e.g., "my-custom-skill")',
1043
+ };
1044
+ }
1045
+ try {
1046
+ const newSkill = await skillLoader.createSkill({
1047
+ id: input.id,
1048
+ name: input.name,
1049
+ description: input.description,
1050
+ prompt: input.prompt,
1051
+ icon: input.icon || '',
1052
+ category: input.category || 'Custom',
1053
+ parameters: input.parameters,
1054
+ enabled: input.enabled !== false,
1055
+ });
1056
+ this.daemon.logEvent(this.taskId, 'log', {
1057
+ message: `Created new skill: ${newSkill.name}`,
1058
+ skillId: newSkill.id,
1059
+ });
1060
+ return {
1061
+ success: true,
1062
+ message: `Skill '${newSkill.name}' created successfully`,
1063
+ skill: {
1064
+ id: newSkill.id,
1065
+ name: newSkill.name,
1066
+ source: newSkill.source,
1067
+ filePath: newSkill.filePath,
1068
+ },
1069
+ };
1070
+ }
1071
+ catch (error) {
1072
+ return {
1073
+ success: false,
1074
+ error: `Failed to create skill: ${error.message}`,
1075
+ };
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Duplicate an existing skill with modifications
1080
+ */
1081
+ async executeSkillDuplicate(input) {
1082
+ const { source_skill_id, new_id, modifications = {} } = input;
1083
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
1084
+ // Get the source skill
1085
+ const sourceSkill = skillLoader.getSkill(source_skill_id);
1086
+ if (!sourceSkill) {
1087
+ return {
1088
+ success: false,
1089
+ error: `Source skill '${source_skill_id}' not found`,
1090
+ hint: 'Use skill_list to see available skills',
1091
+ };
1092
+ }
1093
+ // Check if new ID already exists
1094
+ const existing = skillLoader.getSkill(new_id);
1095
+ if (existing) {
1096
+ return {
1097
+ success: false,
1098
+ error: `Skill with ID '${new_id}' already exists`,
1099
+ hint: 'Use a different ID for the duplicate',
1100
+ };
1101
+ }
1102
+ // Validate new ID format
1103
+ if (!/^[a-z0-9-]+$/.test(new_id)) {
1104
+ return {
1105
+ success: false,
1106
+ error: 'Invalid skill ID format',
1107
+ hint: 'Skill ID should be lowercase, using only letters, numbers, and hyphens',
1108
+ };
1109
+ }
1110
+ try {
1111
+ // Create the duplicated skill with modifications
1112
+ const newSkill = await skillLoader.createSkill({
1113
+ id: new_id,
1114
+ name: modifications.name || `${sourceSkill.name} (Copy)`,
1115
+ description: modifications.description || sourceSkill.description,
1116
+ prompt: modifications.prompt || sourceSkill.prompt,
1117
+ icon: modifications.icon || sourceSkill.icon,
1118
+ category: modifications.category || sourceSkill.category,
1119
+ parameters: modifications.parameters || sourceSkill.parameters,
1120
+ priority: sourceSkill.priority,
1121
+ enabled: true,
1122
+ });
1123
+ this.daemon.logEvent(this.taskId, 'log', {
1124
+ message: `Duplicated skill '${sourceSkill.name}' as '${newSkill.name}'`,
1125
+ sourceSkillId: source_skill_id,
1126
+ newSkillId: new_id,
1127
+ });
1128
+ return {
1129
+ success: true,
1130
+ message: `Skill duplicated successfully`,
1131
+ source_skill: {
1132
+ id: sourceSkill.id,
1133
+ name: sourceSkill.name,
1134
+ },
1135
+ new_skill: {
1136
+ id: newSkill.id,
1137
+ name: newSkill.name,
1138
+ source: newSkill.source,
1139
+ filePath: newSkill.filePath,
1140
+ },
1141
+ modifications_applied: Object.keys(modifications),
1142
+ };
1143
+ }
1144
+ catch (error) {
1145
+ return {
1146
+ success: false,
1147
+ error: `Failed to duplicate skill: ${error.message}`,
1148
+ };
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Update an existing skill
1153
+ */
1154
+ async executeSkillUpdate(input) {
1155
+ const { skill_id, updates } = input;
1156
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
1157
+ const skill = skillLoader.getSkill(skill_id);
1158
+ if (!skill) {
1159
+ return {
1160
+ success: false,
1161
+ error: `Skill '${skill_id}' not found`,
1162
+ hint: 'Use skill_list to see available skills',
1163
+ };
1164
+ }
1165
+ // Check if skill can be updated
1166
+ if (skill.source === 'bundled') {
1167
+ return {
1168
+ success: false,
1169
+ error: `Cannot update bundled skill '${skill_id}'`,
1170
+ hint: 'Bundled skills are read-only. Use skill_duplicate to create an editable copy.',
1171
+ skill_source: skill.source,
1172
+ };
1173
+ }
1174
+ try {
1175
+ const updatedSkill = await skillLoader.updateSkill(skill_id, updates);
1176
+ if (!updatedSkill) {
1177
+ return {
1178
+ success: false,
1179
+ error: 'Failed to update skill',
1180
+ };
1181
+ }
1182
+ this.daemon.logEvent(this.taskId, 'log', {
1183
+ message: `Updated skill: ${updatedSkill.name}`,
1184
+ skillId: skill_id,
1185
+ updatedFields: Object.keys(updates),
1186
+ });
1187
+ return {
1188
+ success: true,
1189
+ message: `Skill '${updatedSkill.name}' updated successfully`,
1190
+ updated_fields: Object.keys(updates),
1191
+ skill: {
1192
+ id: updatedSkill.id,
1193
+ name: updatedSkill.name,
1194
+ source: updatedSkill.source,
1195
+ filePath: updatedSkill.filePath,
1196
+ },
1197
+ };
1198
+ }
1199
+ catch (error) {
1200
+ return {
1201
+ success: false,
1202
+ error: `Failed to update skill: ${error.message}`,
1203
+ };
1204
+ }
1205
+ }
1206
+ /**
1207
+ * Delete a skill
1208
+ */
1209
+ async executeSkillDelete(input) {
1210
+ const { skill_id } = input;
1211
+ const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
1212
+ const skill = skillLoader.getSkill(skill_id);
1213
+ if (!skill) {
1214
+ return {
1215
+ success: false,
1216
+ error: `Skill '${skill_id}' not found`,
1217
+ hint: 'Use skill_list to see available skills',
1218
+ };
1219
+ }
1220
+ // Check if skill can be deleted
1221
+ if (skill.source === 'bundled') {
1222
+ return {
1223
+ success: false,
1224
+ error: `Cannot delete bundled skill '${skill_id}'`,
1225
+ hint: 'Bundled skills are read-only and cannot be deleted.',
1226
+ skill_source: skill.source,
1227
+ };
1228
+ }
1229
+ try {
1230
+ const deleted = await skillLoader.deleteSkill(skill_id);
1231
+ if (!deleted) {
1232
+ return {
1233
+ success: false,
1234
+ error: 'Failed to delete skill',
1235
+ };
1236
+ }
1237
+ this.daemon.logEvent(this.taskId, 'log', {
1238
+ message: `Deleted skill: ${skill.name}`,
1239
+ skillId: skill_id,
1240
+ });
1241
+ return {
1242
+ success: true,
1243
+ message: `Skill '${skill.name}' deleted successfully`,
1244
+ deleted_skill: {
1245
+ id: skill.id,
1246
+ name: skill.name,
1247
+ source: skill.source,
1248
+ },
1249
+ };
1250
+ }
1251
+ catch (error) {
1252
+ return {
1253
+ success: false,
1254
+ error: `Failed to delete skill: ${error.message}`,
1255
+ };
1256
+ }
1257
+ }
1258
+ /**
1259
+ * Define file operation tools
1260
+ */
1261
+ getFileToolDefinitions() {
1262
+ return [
1263
+ {
1264
+ name: 'read_file',
1265
+ description: 'Read the contents of a file in the workspace. Supports plain text files, DOCX (Word documents), and PDF files. For DOCX and PDF, extracts and returns the text content.',
1266
+ input_schema: {
1267
+ type: 'object',
1268
+ properties: {
1269
+ path: {
1270
+ type: 'string',
1271
+ description: 'Relative path to the file within the workspace',
1272
+ },
1273
+ },
1274
+ required: ['path'],
1275
+ },
1276
+ },
1277
+ {
1278
+ name: 'write_file',
1279
+ description: 'Write content to a file in the workspace (creates or overwrites)',
1280
+ input_schema: {
1281
+ type: 'object',
1282
+ properties: {
1283
+ path: {
1284
+ type: 'string',
1285
+ description: 'Relative path to the file within the workspace',
1286
+ },
1287
+ content: {
1288
+ type: 'string',
1289
+ description: 'Content to write to the file',
1290
+ },
1291
+ },
1292
+ required: ['path', 'content'],
1293
+ },
1294
+ },
1295
+ {
1296
+ name: 'copy_file',
1297
+ description: 'Copy a file to a new location. Supports binary files (DOCX, PDF, images, etc.) and preserves exact file content.',
1298
+ input_schema: {
1299
+ type: 'object',
1300
+ properties: {
1301
+ sourcePath: {
1302
+ type: 'string',
1303
+ description: 'Path to the source file to copy',
1304
+ },
1305
+ destPath: {
1306
+ type: 'string',
1307
+ description: 'Path for the destination file (the copy)',
1308
+ },
1309
+ },
1310
+ required: ['sourcePath', 'destPath'],
1311
+ },
1312
+ },
1313
+ {
1314
+ name: 'list_directory',
1315
+ description: 'List files and folders in a directory',
1316
+ input_schema: {
1317
+ type: 'object',
1318
+ properties: {
1319
+ path: {
1320
+ type: 'string',
1321
+ description: 'Relative path to the directory (or "." for workspace root)',
1322
+ },
1323
+ },
1324
+ required: ['path'],
1325
+ },
1326
+ },
1327
+ {
1328
+ name: 'list_directory_with_sizes',
1329
+ description: 'List files and folders in a directory with size summary (MCP-style output)',
1330
+ input_schema: {
1331
+ type: 'object',
1332
+ properties: {
1333
+ path: {
1334
+ type: 'string',
1335
+ description: 'Relative or absolute path to the directory',
1336
+ },
1337
+ },
1338
+ required: ['path'],
1339
+ },
1340
+ },
1341
+ {
1342
+ name: 'get_file_info',
1343
+ description: 'Get file or directory metadata (size, timestamps, permissions)',
1344
+ input_schema: {
1345
+ type: 'object',
1346
+ properties: {
1347
+ path: {
1348
+ type: 'string',
1349
+ description: 'Path to the file or directory',
1350
+ },
1351
+ },
1352
+ required: ['path'],
1353
+ },
1354
+ },
1355
+ {
1356
+ name: 'rename_file',
1357
+ description: 'Rename or move a file',
1358
+ input_schema: {
1359
+ type: 'object',
1360
+ properties: {
1361
+ oldPath: {
1362
+ type: 'string',
1363
+ description: 'Current path of the file',
1364
+ },
1365
+ newPath: {
1366
+ type: 'string',
1367
+ description: 'New path for the file',
1368
+ },
1369
+ },
1370
+ required: ['oldPath', 'newPath'],
1371
+ },
1372
+ },
1373
+ {
1374
+ name: 'delete_file',
1375
+ description: 'Delete a file (requires user approval)',
1376
+ input_schema: {
1377
+ type: 'object',
1378
+ properties: {
1379
+ path: {
1380
+ type: 'string',
1381
+ description: 'Path to the file to delete',
1382
+ },
1383
+ },
1384
+ required: ['path'],
1385
+ },
1386
+ },
1387
+ {
1388
+ name: 'create_directory',
1389
+ description: 'Create a new directory',
1390
+ input_schema: {
1391
+ type: 'object',
1392
+ properties: {
1393
+ path: {
1394
+ type: 'string',
1395
+ description: 'Path for the new directory',
1396
+ },
1397
+ },
1398
+ required: ['path'],
1399
+ },
1400
+ },
1401
+ {
1402
+ name: 'search_files',
1403
+ description: 'Search for files by name or content',
1404
+ input_schema: {
1405
+ type: 'object',
1406
+ properties: {
1407
+ query: {
1408
+ type: 'string',
1409
+ description: 'Search query (filename or content)',
1410
+ },
1411
+ path: {
1412
+ type: 'string',
1413
+ description: 'Directory to search in (optional, defaults to workspace root)',
1414
+ },
1415
+ },
1416
+ required: ['query'],
1417
+ },
1418
+ },
1419
+ ];
1420
+ }
1421
+ /**
1422
+ * Define skill tools
1423
+ */
1424
+ getSkillToolDefinitions() {
1425
+ return [
1426
+ {
1427
+ name: 'create_spreadsheet',
1428
+ description: 'Create an Excel spreadsheet with data, formulas, and formatting',
1429
+ input_schema: {
1430
+ type: 'object',
1431
+ properties: {
1432
+ filename: { type: 'string', description: 'Name of the Excel file (without extension)' },
1433
+ sheets: {
1434
+ type: 'array',
1435
+ description: 'Array of sheets to create',
1436
+ items: {
1437
+ type: 'object',
1438
+ properties: {
1439
+ name: { type: 'string', description: 'Sheet name' },
1440
+ data: {
1441
+ type: 'array',
1442
+ description: '2D array of cell values (rows of columns)',
1443
+ items: {
1444
+ type: 'array',
1445
+ description: 'Row of cell values',
1446
+ items: { type: 'string', description: 'Cell value' },
1447
+ },
1448
+ },
1449
+ },
1450
+ },
1451
+ },
1452
+ },
1453
+ required: ['filename', 'sheets'],
1454
+ },
1455
+ },
1456
+ {
1457
+ name: 'create_document',
1458
+ description: 'Create a formatted Word document or PDF',
1459
+ input_schema: {
1460
+ type: 'object',
1461
+ properties: {
1462
+ filename: { type: 'string', description: 'Name of the document' },
1463
+ format: { type: 'string', enum: ['docx', 'pdf'], description: 'Output format' },
1464
+ content: {
1465
+ type: 'array',
1466
+ description: 'Document content blocks',
1467
+ items: {
1468
+ type: 'object',
1469
+ properties: {
1470
+ type: { type: 'string', enum: ['heading', 'paragraph', 'list'] },
1471
+ text: { type: 'string' },
1472
+ level: { type: 'number', description: 'For headings: 1-6' },
1473
+ },
1474
+ },
1475
+ },
1476
+ },
1477
+ required: ['filename', 'format', 'content'],
1478
+ },
1479
+ },
1480
+ {
1481
+ name: 'edit_document',
1482
+ description: 'Edit an existing Word document (DOCX). Supports multiple actions: append (default), move_section, insert_after_section, list_sections. Use this to modify existing documents without recreating them from scratch.',
1483
+ input_schema: {
1484
+ type: 'object',
1485
+ properties: {
1486
+ sourcePath: {
1487
+ type: 'string',
1488
+ description: 'Path to the existing DOCX file to edit',
1489
+ },
1490
+ destPath: {
1491
+ type: 'string',
1492
+ description: 'Optional: Path for the output file. If not specified, the source file will be overwritten.',
1493
+ },
1494
+ action: {
1495
+ type: 'string',
1496
+ enum: ['append', 'move_section', 'insert_after_section', 'list_sections'],
1497
+ description: 'Action to perform: append (default) adds content at end, move_section moves a section to a new position, insert_after_section inserts content after a specific section, list_sections lists all sections',
1498
+ },
1499
+ newContent: {
1500
+ type: 'array',
1501
+ description: 'For append/insert_after_section: Content blocks to add',
1502
+ items: {
1503
+ type: 'object',
1504
+ properties: {
1505
+ type: {
1506
+ type: 'string',
1507
+ enum: ['heading', 'paragraph', 'list', 'table'],
1508
+ description: 'Type of content block',
1509
+ },
1510
+ text: {
1511
+ type: 'string',
1512
+ description: 'Text content for the block',
1513
+ },
1514
+ level: {
1515
+ type: 'number',
1516
+ description: 'For headings: level 1-6',
1517
+ },
1518
+ items: {
1519
+ type: 'array',
1520
+ items: { type: 'string' },
1521
+ description: 'For lists: array of list items',
1522
+ },
1523
+ rows: {
1524
+ type: 'array',
1525
+ items: {
1526
+ type: 'array',
1527
+ items: { type: 'string' },
1528
+ },
1529
+ description: 'For tables: 2D array of cell values',
1530
+ },
1531
+ },
1532
+ required: ['type', 'text'],
1533
+ },
1534
+ },
1535
+ sectionToMove: {
1536
+ type: 'string',
1537
+ description: 'For move_section: Section number or heading text to move (e.g., "8" or "Ticket Indexing")',
1538
+ },
1539
+ afterSection: {
1540
+ type: 'string',
1541
+ description: 'For move_section: Section number or heading text after which to place the moved section (e.g., "7" or "Data Storage")',
1542
+ },
1543
+ insertAfterSection: {
1544
+ type: 'string',
1545
+ description: 'For insert_after_section: Section number or heading text after which to insert new content',
1546
+ },
1547
+ },
1548
+ required: ['sourcePath'],
1549
+ },
1550
+ },
1551
+ {
1552
+ name: 'create_presentation',
1553
+ description: 'Create a PowerPoint presentation',
1554
+ input_schema: {
1555
+ type: 'object',
1556
+ properties: {
1557
+ filename: { type: 'string', description: 'Name of the presentation' },
1558
+ slides: {
1559
+ type: 'array',
1560
+ items: {
1561
+ type: 'object',
1562
+ properties: {
1563
+ title: { type: 'string' },
1564
+ content: { type: 'array', items: { type: 'string' } },
1565
+ },
1566
+ },
1567
+ },
1568
+ },
1569
+ required: ['filename', 'slides'],
1570
+ },
1571
+ },
1572
+ {
1573
+ name: 'organize_folder',
1574
+ description: 'Organize files in a folder by type, date, or custom rules',
1575
+ input_schema: {
1576
+ type: 'object',
1577
+ properties: {
1578
+ path: { type: 'string', description: 'Folder path to organize' },
1579
+ strategy: {
1580
+ type: 'string',
1581
+ enum: ['by_type', 'by_date', 'custom'],
1582
+ description: 'Organization strategy',
1583
+ },
1584
+ rules: { type: 'object', description: 'Custom organization rules (if strategy is custom)' },
1585
+ },
1586
+ required: ['path', 'strategy'],
1587
+ },
1588
+ },
1589
+ {
1590
+ name: 'use_skill',
1591
+ description: 'Use a custom skill by ID to help accomplish a task. Skills are pre-configured prompt templates ' +
1592
+ 'that provide specialized capabilities. Use this when a skill matches what you need to do. ' +
1593
+ 'The skill\'s expanded prompt will be injected into your context to guide execution.',
1594
+ input_schema: {
1595
+ type: 'object',
1596
+ properties: {
1597
+ skill_id: {
1598
+ type: 'string',
1599
+ description: 'The ID of the skill to use (e.g., "git-commit", "code-review", "translate")',
1600
+ },
1601
+ parameters: {
1602
+ type: 'object',
1603
+ description: 'Parameter values for the skill. Check skill description for required parameters.',
1604
+ additionalProperties: true,
1605
+ },
1606
+ },
1607
+ required: ['skill_id'],
1608
+ },
1609
+ },
1610
+ // Skill Management Tools
1611
+ {
1612
+ name: 'skill_list',
1613
+ description: 'List all available skills with their metadata including source (bundled, managed, workspace), ' +
1614
+ 'file paths, and status. Use this to discover what skills exist and where they are stored.',
1615
+ input_schema: {
1616
+ type: 'object',
1617
+ properties: {
1618
+ source: {
1619
+ type: 'string',
1620
+ enum: ['all', 'bundled', 'managed', 'workspace'],
1621
+ description: 'Filter skills by source. Default is "all".',
1622
+ },
1623
+ include_disabled: {
1624
+ type: 'boolean',
1625
+ description: 'Include disabled skills in the list. Default is true.',
1626
+ },
1627
+ },
1628
+ },
1629
+ },
1630
+ {
1631
+ name: 'skill_get',
1632
+ description: 'Get the full JSON content and metadata of a specific skill by ID. ' +
1633
+ 'Returns the complete skill definition including prompt, parameters, and configuration.',
1634
+ input_schema: {
1635
+ type: 'object',
1636
+ properties: {
1637
+ skill_id: {
1638
+ type: 'string',
1639
+ description: 'The ID of the skill to retrieve',
1640
+ },
1641
+ },
1642
+ required: ['skill_id'],
1643
+ },
1644
+ },
1645
+ {
1646
+ name: 'skill_create',
1647
+ description: 'Create a new custom skill. The skill will be saved to the managed skills directory ' +
1648
+ '(~/Library/Application Support/cowork-os/skills/). Provide the full skill definition.',
1649
+ input_schema: {
1650
+ type: 'object',
1651
+ properties: {
1652
+ id: {
1653
+ type: 'string',
1654
+ description: 'Unique identifier for the skill (lowercase, hyphens allowed, e.g., "my-custom-skill")',
1655
+ },
1656
+ name: {
1657
+ type: 'string',
1658
+ description: 'Human-readable name for the skill',
1659
+ },
1660
+ description: {
1661
+ type: 'string',
1662
+ description: 'Brief description of what the skill does',
1663
+ },
1664
+ prompt: {
1665
+ type: 'string',
1666
+ description: 'The prompt template. Use {{paramName}} for parameter placeholders.',
1667
+ },
1668
+ icon: {
1669
+ type: 'string',
1670
+ description: 'Emoji icon for the skill (optional)',
1671
+ },
1672
+ category: {
1673
+ type: 'string',
1674
+ description: 'Category for grouping (e.g., "Research", "Development", "Writing")',
1675
+ },
1676
+ parameters: {
1677
+ type: 'array',
1678
+ description: 'Array of parameter definitions',
1679
+ items: {
1680
+ type: 'object',
1681
+ properties: {
1682
+ name: { type: 'string', description: 'Parameter name (used in {{name}} placeholders)' },
1683
+ type: { type: 'string', enum: ['string', 'number', 'boolean', 'select'], description: 'Parameter type' },
1684
+ description: { type: 'string', description: 'Parameter description' },
1685
+ required: { type: 'boolean', description: 'Whether the parameter is required' },
1686
+ default: { type: 'string', description: 'Default value' },
1687
+ options: { type: 'array', items: { type: 'string' }, description: 'Options for select type' },
1688
+ },
1689
+ required: ['name', 'type', 'description'],
1690
+ },
1691
+ },
1692
+ enabled: {
1693
+ type: 'boolean',
1694
+ description: 'Whether the skill is enabled. Default is true.',
1695
+ },
1696
+ },
1697
+ required: ['id', 'name', 'description', 'prompt'],
1698
+ },
1699
+ },
1700
+ {
1701
+ name: 'skill_duplicate',
1702
+ description: 'Duplicate an existing skill with a new ID and optional modifications. ' +
1703
+ 'Great for creating variations of existing skills (e.g., changing time ranges, targets).',
1704
+ input_schema: {
1705
+ type: 'object',
1706
+ properties: {
1707
+ source_skill_id: {
1708
+ type: 'string',
1709
+ description: 'The ID of the skill to duplicate',
1710
+ },
1711
+ new_id: {
1712
+ type: 'string',
1713
+ description: 'The ID for the new duplicated skill',
1714
+ },
1715
+ modifications: {
1716
+ type: 'object',
1717
+ description: 'Fields to modify in the duplicated skill (name, description, prompt, etc.)',
1718
+ properties: {
1719
+ name: { type: 'string', description: 'New name for the skill' },
1720
+ description: { type: 'string', description: 'New description' },
1721
+ prompt: { type: 'string', description: 'New prompt template' },
1722
+ icon: { type: 'string', description: 'New icon' },
1723
+ category: { type: 'string', description: 'New category' },
1724
+ parameters: { type: 'array', description: 'New parameters array' },
1725
+ },
1726
+ },
1727
+ },
1728
+ required: ['source_skill_id', 'new_id'],
1729
+ },
1730
+ },
1731
+ {
1732
+ name: 'skill_update',
1733
+ description: 'Update an existing skill. Only managed and workspace skills can be updated (not bundled). ' +
1734
+ 'Provide only the fields you want to change.',
1735
+ input_schema: {
1736
+ type: 'object',
1737
+ properties: {
1738
+ skill_id: {
1739
+ type: 'string',
1740
+ description: 'The ID of the skill to update',
1741
+ },
1742
+ updates: {
1743
+ type: 'object',
1744
+ description: 'Fields to update',
1745
+ properties: {
1746
+ name: { type: 'string', description: 'New name' },
1747
+ description: { type: 'string', description: 'New description' },
1748
+ prompt: { type: 'string', description: 'New prompt template' },
1749
+ icon: { type: 'string', description: 'New icon' },
1750
+ category: { type: 'string', description: 'New category' },
1751
+ parameters: { type: 'array', description: 'New parameters array' },
1752
+ enabled: { type: 'boolean', description: 'Enable/disable the skill' },
1753
+ },
1754
+ },
1755
+ },
1756
+ required: ['skill_id', 'updates'],
1757
+ },
1758
+ },
1759
+ {
1760
+ name: 'skill_delete',
1761
+ description: 'Delete a skill. Only managed and workspace skills can be deleted (not bundled). ' +
1762
+ 'This permanently removes the skill file.',
1763
+ input_schema: {
1764
+ type: 'object',
1765
+ properties: {
1766
+ skill_id: {
1767
+ type: 'string',
1768
+ description: 'The ID of the skill to delete',
1769
+ },
1770
+ },
1771
+ required: ['skill_id'],
1772
+ },
1773
+ },
1774
+ ];
1775
+ }
1776
+ /**
1777
+ * Define search tools
1778
+ */
1779
+ getSearchToolDefinitions() {
1780
+ const providers = search_1.SearchProviderFactory.getAvailableProviders();
1781
+ const configuredProviders = providers.filter((p) => p.configured);
1782
+ const allSupportedTypes = [
1783
+ ...new Set(configuredProviders.flatMap((p) => p.supportedTypes)),
1784
+ ];
1785
+ return [
1786
+ {
1787
+ name: 'web_search',
1788
+ description: `Search the web for information. This is the PRIMARY tool for research tasks - finding news, trends, discussions, and information on any topic. ` +
1789
+ `Use this FIRST for research, then use web_fetch if you need to read specific URLs from the results. ` +
1790
+ `Do NOT use browser_navigate for research - web_search is faster and more efficient. ` +
1791
+ `Configured providers: ${configuredProviders.map((p) => p.name).join(', ')}`,
1792
+ input_schema: {
1793
+ type: 'object',
1794
+ properties: {
1795
+ query: {
1796
+ type: 'string',
1797
+ description: 'The search query',
1798
+ },
1799
+ searchType: {
1800
+ type: 'string',
1801
+ enum: allSupportedTypes,
1802
+ description: `Type of search. Available: ${allSupportedTypes.join(', ')}`,
1803
+ },
1804
+ maxResults: {
1805
+ type: 'number',
1806
+ description: 'Maximum number of results (default: 10, max: 20)',
1807
+ },
1808
+ provider: {
1809
+ type: 'string',
1810
+ enum: configuredProviders.map((p) => p.type),
1811
+ description: `Override the search provider. Available: ${configuredProviders.map((p) => p.type).join(', ')}`,
1812
+ },
1813
+ dateRange: {
1814
+ type: 'string',
1815
+ enum: ['day', 'week', 'month', 'year'],
1816
+ description: 'Filter results by date range',
1817
+ },
1818
+ region: {
1819
+ type: 'string',
1820
+ description: 'Region code for localized results (e.g., "us", "uk", "de")',
1821
+ },
1822
+ },
1823
+ required: ['query'],
1824
+ },
1825
+ },
1826
+ ];
1827
+ }
1828
+ /**
1829
+ * Define X/Twitter tools (bird CLI)
1830
+ */
1831
+ getXToolDefinitions() {
1832
+ return [
1833
+ {
1834
+ name: 'x_action',
1835
+ description: 'Use the connected X/Twitter account to read, search, and post. ' +
1836
+ 'Posting actions (tweet/reply/follow/unfollow) require user approval.',
1837
+ input_schema: {
1838
+ type: 'object',
1839
+ properties: {
1840
+ action: {
1841
+ type: 'string',
1842
+ enum: [
1843
+ 'whoami',
1844
+ 'read',
1845
+ 'thread',
1846
+ 'replies',
1847
+ 'search',
1848
+ 'user_tweets',
1849
+ 'mentions',
1850
+ 'home',
1851
+ 'tweet',
1852
+ 'reply',
1853
+ 'follow',
1854
+ 'unfollow',
1855
+ ],
1856
+ description: 'Action to perform',
1857
+ },
1858
+ id_or_url: {
1859
+ type: 'string',
1860
+ description: 'Tweet URL or ID (for read/thread/replies/reply)',
1861
+ },
1862
+ query: {
1863
+ type: 'string',
1864
+ description: 'Search query (for search)',
1865
+ },
1866
+ user: {
1867
+ type: 'string',
1868
+ description: 'User handle (with or without @) for user_tweets/mentions/follow/unfollow',
1869
+ },
1870
+ text: {
1871
+ type: 'string',
1872
+ description: 'Text for tweet/reply',
1873
+ },
1874
+ timeline: {
1875
+ type: 'string',
1876
+ enum: ['for_you', 'following'],
1877
+ description: 'Timeline for home (default: for_you)',
1878
+ },
1879
+ count: {
1880
+ type: 'number',
1881
+ description: 'Max results (1-50) for search/mentions/home/user_tweets',
1882
+ },
1883
+ media: {
1884
+ type: 'array',
1885
+ description: 'Media file paths (workspace-relative). Up to 4 images or 1 video.',
1886
+ items: { type: 'string' },
1887
+ },
1888
+ alt: {
1889
+ type: 'string',
1890
+ description: 'Alt text for media (single string)',
1891
+ },
1892
+ },
1893
+ required: ['action'],
1894
+ },
1895
+ },
1896
+ ];
1897
+ }
1898
+ /**
1899
+ * Define shell tools
1900
+ */
1901
+ getShellToolDefinitions() {
1902
+ return [
1903
+ {
1904
+ name: 'run_command',
1905
+ description: 'Execute a shell command in the workspace directory. IMPORTANT: This tool requires user approval before execution. The user will see the command and can approve or deny it. Use this for installing packages (npm, pip, brew), running build commands, git operations, or any terminal commands.',
1906
+ input_schema: {
1907
+ type: 'object',
1908
+ properties: {
1909
+ command: {
1910
+ type: 'string',
1911
+ description: 'The shell command to execute (e.g., "npm install", "git status", "ls -la")',
1912
+ },
1913
+ cwd: {
1914
+ type: 'string',
1915
+ description: 'Working directory for the command (optional, defaults to workspace root)',
1916
+ },
1917
+ timeout: {
1918
+ type: 'number',
1919
+ description: 'Timeout in milliseconds (optional, default: 60000, max: 300000)',
1920
+ },
1921
+ },
1922
+ required: ['command'],
1923
+ },
1924
+ },
1925
+ ];
1926
+ }
1927
+ /**
1928
+ * Set the agent's personality
1929
+ */
1930
+ setPersonality(input) {
1931
+ const personalityId = input.personality;
1932
+ const validIds = ['professional', 'friendly', 'concise', 'creative', 'technical', 'casual'];
1933
+ if (!validIds.includes(personalityId)) {
1934
+ throw new Error(`Invalid personality: ${personalityId}. Valid options are: ${validIds.join(', ')}`);
1935
+ }
1936
+ // Save the new personality
1937
+ personality_manager_1.PersonalityManager.setActivePersonality(personalityId);
1938
+ // Get the personality definition for the response
1939
+ const personality = types_1.PERSONALITY_DEFINITIONS.find(p => p.id === personalityId);
1940
+ const description = personality?.description || '';
1941
+ const name = personality?.name || personalityId;
1942
+ console.log(`[ToolRegistry] Personality changed to: ${personalityId}`);
1943
+ return {
1944
+ success: true,
1945
+ personality: personalityId,
1946
+ description,
1947
+ message: `Personality changed to "${name}". ${description}. This will take effect in future responses.`,
1948
+ };
1949
+ }
1950
+ /**
1951
+ * Set the agent's name
1952
+ */
1953
+ setAgentName(input) {
1954
+ const newName = input.name?.trim();
1955
+ if (!newName || newName.length === 0) {
1956
+ throw new Error('Name cannot be empty');
1957
+ }
1958
+ if (newName.length > 50) {
1959
+ throw new Error('Name is too long (max 50 characters)');
1960
+ }
1961
+ // Save the new name
1962
+ personality_manager_1.PersonalityManager.setAgentName(newName);
1963
+ console.log(`[ToolRegistry] Agent name changed to: ${newName}`);
1964
+ return {
1965
+ success: true,
1966
+ name: newName,
1967
+ message: `Great! From now on, I'll go by "${newName}". Nice to meet you!`,
1968
+ };
1969
+ }
1970
+ /**
1971
+ * Set the agent's persona (character overlay)
1972
+ */
1973
+ setPersona(input) {
1974
+ const personaId = input.persona;
1975
+ const validIds = ['none', 'jarvis', 'friday', 'hal', 'computer', 'alfred', 'intern', 'sensei', 'pirate', 'noir', 'companion'];
1976
+ if (!validIds.includes(personaId)) {
1977
+ throw new Error(`Invalid persona: ${personaId}. Valid options are: ${validIds.join(', ')}`);
1978
+ }
1979
+ // Save the new persona
1980
+ personality_manager_1.PersonalityManager.setActivePersona(personaId);
1981
+ // Get the persona definition for the response
1982
+ const persona = types_1.PERSONA_DEFINITIONS.find(p => p.id === personaId);
1983
+ const description = persona?.description || '';
1984
+ const name = persona?.name || personaId;
1985
+ console.log(`[ToolRegistry] Persona changed to: ${personaId}`);
1986
+ let message = '';
1987
+ if (personaId === 'none') {
1988
+ message = 'Persona cleared. I\'ll respond without any character overlay.';
1989
+ }
1990
+ else {
1991
+ message = `Persona changed to "${name}". ${description}. This character style will be applied in future responses.`;
1992
+ }
1993
+ return {
1994
+ success: true,
1995
+ persona: personaId,
1996
+ name,
1997
+ description,
1998
+ message,
1999
+ };
2000
+ }
2001
+ /**
2002
+ * Set the user's name (for relationship tracking)
2003
+ */
2004
+ setUserName(input) {
2005
+ const userName = input.name?.trim();
2006
+ if (!userName || userName.length === 0) {
2007
+ throw new Error('Name cannot be empty');
2008
+ }
2009
+ if (userName.length > 100) {
2010
+ throw new Error('Name is too long (max 100 characters)');
2011
+ }
2012
+ // Save the user's name
2013
+ personality_manager_1.PersonalityManager.setUserName(userName);
2014
+ console.log(`[ToolRegistry] User name set to: ${userName}`);
2015
+ const agentName = personality_manager_1.PersonalityManager.getAgentName();
2016
+ return {
2017
+ success: true,
2018
+ name: userName,
2019
+ message: `Nice to meet you, ${userName}! I'm ${agentName}. I'll remember your name for our future conversations.`,
2020
+ };
2021
+ }
2022
+ /**
2023
+ * Set response style preferences
2024
+ */
2025
+ setResponseStyle(input) {
2026
+ const changes = [];
2027
+ const style = {};
2028
+ // Validate and apply emoji usage
2029
+ if (input.emoji_usage) {
2030
+ const validEmoji = ['none', 'minimal', 'moderate', 'expressive'];
2031
+ if (!validEmoji.includes(input.emoji_usage)) {
2032
+ throw new Error(`Invalid emoji_usage: ${input.emoji_usage}. Valid options: ${validEmoji.join(', ')}`);
2033
+ }
2034
+ style.emojiUsage = input.emoji_usage;
2035
+ changes.push(`emoji usage: ${input.emoji_usage}`);
2036
+ }
2037
+ // Validate and apply response length
2038
+ if (input.response_length) {
2039
+ const validLength = ['terse', 'balanced', 'detailed'];
2040
+ if (!validLength.includes(input.response_length)) {
2041
+ throw new Error(`Invalid response_length: ${input.response_length}. Valid options: ${validLength.join(', ')}`);
2042
+ }
2043
+ style.responseLength = input.response_length;
2044
+ changes.push(`response length: ${input.response_length}`);
2045
+ }
2046
+ // Validate and apply code comment style
2047
+ if (input.code_comments) {
2048
+ const validComments = ['minimal', 'moderate', 'verbose'];
2049
+ if (!validComments.includes(input.code_comments)) {
2050
+ throw new Error(`Invalid code_comments: ${input.code_comments}. Valid options: ${validComments.join(', ')}`);
2051
+ }
2052
+ style.codeCommentStyle = input.code_comments;
2053
+ changes.push(`code comments: ${input.code_comments}`);
2054
+ }
2055
+ // Validate and apply explanation depth
2056
+ if (input.explanation_depth) {
2057
+ const validDepth = ['expert', 'balanced', 'teaching'];
2058
+ if (!validDepth.includes(input.explanation_depth)) {
2059
+ throw new Error(`Invalid explanation_depth: ${input.explanation_depth}. Valid options: ${validDepth.join(', ')}`);
2060
+ }
2061
+ style.explanationDepth = input.explanation_depth;
2062
+ changes.push(`explanation depth: ${input.explanation_depth}`);
2063
+ }
2064
+ if (changes.length === 0) {
2065
+ throw new Error('No valid style options provided. Use emoji_usage, response_length, code_comments, or explanation_depth.');
2066
+ }
2067
+ personality_manager_1.PersonalityManager.setResponseStyle(style);
2068
+ console.log(`[ToolRegistry] Response style updated:`, changes);
2069
+ return {
2070
+ success: true,
2071
+ changes,
2072
+ message: `Response style updated: ${changes.join(', ')}. Changes will apply to future responses.`,
2073
+ };
2074
+ }
2075
+ /**
2076
+ * Sanitize user input to prevent prompt injection
2077
+ * Removes control characters and limits potentially harmful patterns
2078
+ */
2079
+ sanitizeQuirkInput(input) {
2080
+ if (!input)
2081
+ return '';
2082
+ // Remove control characters and null bytes
2083
+ let sanitized = input.replace(/[\x00-\x1F\x7F]/g, '');
2084
+ // Remove patterns that could be used for prompt injection
2085
+ // These patterns try to override system instructions
2086
+ const dangerousPatterns = [
2087
+ /ignore\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/gi,
2088
+ /disregard\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/gi,
2089
+ /forget\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/gi,
2090
+ /new\s+instructions?:/gi,
2091
+ /system\s*:/gi,
2092
+ /\[INST\]/gi,
2093
+ /<<SYS>>/gi,
2094
+ /<\|im_start\|>/gi,
2095
+ /###\s*(instruction|system|human|assistant)/gi,
2096
+ ];
2097
+ for (const pattern of dangerousPatterns) {
2098
+ sanitized = sanitized.replace(pattern, '[filtered]');
2099
+ }
2100
+ return sanitized.trim();
2101
+ }
2102
+ /**
2103
+ * Set personality quirks
2104
+ */
2105
+ setQuirks(input) {
2106
+ const changes = [];
2107
+ const quirks = {};
2108
+ // Maximum lengths for quirk fields
2109
+ const MAX_CATCHPHRASE_LENGTH = 100;
2110
+ const MAX_SIGNOFF_LENGTH = 150;
2111
+ // Apply catchphrase with validation
2112
+ if (input.catchphrase !== undefined) {
2113
+ if (input.catchphrase && input.catchphrase.length > MAX_CATCHPHRASE_LENGTH) {
2114
+ throw new Error(`Catchphrase too long (max ${MAX_CATCHPHRASE_LENGTH} characters, got ${input.catchphrase.length})`);
2115
+ }
2116
+ const sanitized = this.sanitizeQuirkInput(input.catchphrase || '');
2117
+ quirks.catchphrase = sanitized;
2118
+ if (sanitized) {
2119
+ changes.push(`catchphrase: "${sanitized}"`);
2120
+ }
2121
+ else {
2122
+ changes.push('catchphrase cleared');
2123
+ }
2124
+ }
2125
+ // Apply sign-off with validation
2126
+ if (input.sign_off !== undefined) {
2127
+ if (input.sign_off && input.sign_off.length > MAX_SIGNOFF_LENGTH) {
2128
+ throw new Error(`Sign-off too long (max ${MAX_SIGNOFF_LENGTH} characters, got ${input.sign_off.length})`);
2129
+ }
2130
+ const sanitized = this.sanitizeQuirkInput(input.sign_off || '');
2131
+ quirks.signOff = sanitized;
2132
+ if (sanitized) {
2133
+ changes.push(`sign-off: "${sanitized}"`);
2134
+ }
2135
+ else {
2136
+ changes.push('sign-off cleared');
2137
+ }
2138
+ }
2139
+ // Validate and apply analogy domain
2140
+ if (input.analogy_domain !== undefined) {
2141
+ const validDomains = ['none', 'cooking', 'sports', 'space', 'music', 'nature', 'gaming', 'movies', 'construction'];
2142
+ if (!validDomains.includes(input.analogy_domain)) {
2143
+ throw new Error(`Invalid analogy_domain: ${input.analogy_domain}. Valid options: ${validDomains.join(', ')}`);
2144
+ }
2145
+ quirks.analogyDomain = input.analogy_domain;
2146
+ if (input.analogy_domain === 'none') {
2147
+ changes.push('analogy domain cleared');
2148
+ }
2149
+ else {
2150
+ changes.push(`analogy domain: ${input.analogy_domain}`);
2151
+ }
2152
+ }
2153
+ if (changes.length === 0) {
2154
+ throw new Error('No quirk options provided. Use catchphrase, sign_off, or analogy_domain.');
2155
+ }
2156
+ personality_manager_1.PersonalityManager.setQuirks(quirks);
2157
+ console.log(`[ToolRegistry] Quirks updated:`, changes);
2158
+ return {
2159
+ success: true,
2160
+ changes,
2161
+ message: `Personality quirks updated: ${changes.join(', ')}. Changes will apply to future responses.`,
2162
+ };
2163
+ }
2164
+ // ============ Sub-Agent / Parallel Agent Methods ============
2165
+ /**
2166
+ * Get the current task's depth (nesting level)
2167
+ */
2168
+ async getCurrentTaskDepth() {
2169
+ const currentTask = await this.daemon.getTaskById(this.taskId);
2170
+ return currentTask?.depth ?? 0;
2171
+ }
2172
+ /**
2173
+ * Resolve model preference to a specific model key
2174
+ */
2175
+ resolveModelPreference(preference) {
2176
+ switch (preference) {
2177
+ case 'same':
2178
+ // Use the current global settings - daemon will handle this
2179
+ return '';
2180
+ case 'cheaper':
2181
+ case 'haiku':
2182
+ return 'haiku-4-5';
2183
+ case 'smarter':
2184
+ case 'opus':
2185
+ return 'opus-4-5';
2186
+ case 'sonnet':
2187
+ return 'sonnet-4-5';
2188
+ default:
2189
+ // Default to cheaper model for sub-agents
2190
+ return 'haiku-4-5';
2191
+ }
2192
+ }
2193
+ /**
2194
+ * Resolve personality preference
2195
+ */
2196
+ resolvePersonality(preference) {
2197
+ if (!preference || preference === 'same') {
2198
+ return undefined; // Inherit from parent
2199
+ }
2200
+ const validPersonalities = ['professional', 'friendly', 'concise', 'creative', 'technical', 'casual'];
2201
+ if (validPersonalities.includes(preference)) {
2202
+ return preference;
2203
+ }
2204
+ return 'concise'; // Default for sub-agents
2205
+ }
2206
+ /**
2207
+ * Spawn a child agent to work on a subtask
2208
+ */
2209
+ async spawnAgent(input) {
2210
+ const { prompt, title, model_preference, personality, wait = false, max_turns = 20 } = input;
2211
+ // Validate prompt
2212
+ if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
2213
+ throw new Error('spawn_agent requires a non-empty prompt');
2214
+ }
2215
+ // Check depth limit to prevent runaway spawning
2216
+ const currentDepth = await this.getCurrentTaskDepth();
2217
+ const maxDepth = 3;
2218
+ if (currentDepth >= maxDepth) {
2219
+ return {
2220
+ success: false,
2221
+ message: `Cannot spawn agent: maximum nesting depth (${maxDepth}) reached. Consider breaking the task into smaller parts or completing this task first.`,
2222
+ error: 'MAX_DEPTH_REACHED',
2223
+ };
2224
+ }
2225
+ // Resolve model and personality
2226
+ const modelKey = this.resolveModelPreference(model_preference);
2227
+ const personalityId = this.resolvePersonality(personality);
2228
+ // Build agent config
2229
+ const agentConfig = {
2230
+ maxTurns: max_turns,
2231
+ retainMemory: false, // Sub-agents don't retain memory
2232
+ };
2233
+ if (modelKey) {
2234
+ agentConfig.modelKey = modelKey;
2235
+ }
2236
+ if (personalityId) {
2237
+ agentConfig.personalityId = personalityId;
2238
+ }
2239
+ // Generate title if not provided
2240
+ const taskTitle = title || `Sub-task: ${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}`;
2241
+ // Log spawn attempt
2242
+ this.daemon.logEvent(this.taskId, 'agent_spawned', {
2243
+ childTaskTitle: taskTitle,
2244
+ modelPreference: model_preference,
2245
+ personality: personality,
2246
+ maxTurns: max_turns,
2247
+ parentDepth: currentDepth,
2248
+ });
2249
+ try {
2250
+ // Create the child task via daemon
2251
+ const childTask = await this.daemon.createChildTask({
2252
+ title: taskTitle,
2253
+ prompt: prompt,
2254
+ workspaceId: this.workspace.id,
2255
+ parentTaskId: this.taskId,
2256
+ agentType: 'sub',
2257
+ agentConfig,
2258
+ depth: currentDepth + 1,
2259
+ });
2260
+ console.log(`[ToolRegistry] Spawned child agent: ${childTask.id} (depth: ${currentDepth + 1})`);
2261
+ // If wait=true, wait for completion
2262
+ if (wait) {
2263
+ const result = await this.waitForAgentInternal(childTask.id, 300);
2264
+ return {
2265
+ success: result.success,
2266
+ task_id: childTask.id,
2267
+ title: taskTitle,
2268
+ message: result.message,
2269
+ result: result.resultSummary,
2270
+ error: result.error,
2271
+ };
2272
+ }
2273
+ return {
2274
+ success: true,
2275
+ task_id: childTask.id,
2276
+ title: taskTitle,
2277
+ message: `Sub-agent spawned successfully. Task ID: ${childTask.id}. Use wait_for_agent or get_agent_status to check progress.`,
2278
+ };
2279
+ }
2280
+ catch (error) {
2281
+ console.error(`[ToolRegistry] Failed to spawn agent:`, error);
2282
+ this.daemon.logEvent(this.taskId, 'error', {
2283
+ tool: 'spawn_agent',
2284
+ error: error.message,
2285
+ });
2286
+ return {
2287
+ success: false,
2288
+ message: `Failed to spawn agent: ${error.message}`,
2289
+ error: error.message,
2290
+ };
2291
+ }
2292
+ }
2293
+ /**
2294
+ * Internal method to wait for an agent to complete
2295
+ */
2296
+ async waitForAgentInternal(taskId, timeoutSeconds) {
2297
+ const timeoutMs = timeoutSeconds * 1000;
2298
+ const startTime = Date.now();
2299
+ const pollInterval = 1000; // Check every second
2300
+ while (Date.now() - startTime < timeoutMs) {
2301
+ const task = await this.daemon.getTaskById(taskId);
2302
+ if (!task) {
2303
+ return {
2304
+ success: false,
2305
+ status: 'not_found',
2306
+ message: `Task ${taskId} not found`,
2307
+ error: 'TASK_NOT_FOUND',
2308
+ };
2309
+ }
2310
+ // Check if task is complete
2311
+ if (['completed', 'failed', 'cancelled'].includes(task.status)) {
2312
+ const isSuccess = task.status === 'completed';
2313
+ // Log result event to parent
2314
+ this.daemon.logEvent(this.taskId, isSuccess ? 'agent_completed' : 'agent_failed', {
2315
+ childTaskId: taskId,
2316
+ childStatus: task.status,
2317
+ resultSummary: task.resultSummary,
2318
+ error: task.error,
2319
+ });
2320
+ return {
2321
+ success: isSuccess,
2322
+ status: task.status,
2323
+ message: isSuccess
2324
+ ? `Agent completed successfully`
2325
+ : `Agent ${task.status}: ${task.error || 'Unknown error'}`,
2326
+ resultSummary: task.resultSummary,
2327
+ error: task.error,
2328
+ };
2329
+ }
2330
+ // Wait before polling again
2331
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
2332
+ }
2333
+ // Timeout reached
2334
+ return {
2335
+ success: false,
2336
+ status: 'timeout',
2337
+ message: `Timeout waiting for agent ${taskId} (${timeoutSeconds}s)`,
2338
+ error: 'TIMEOUT',
2339
+ };
2340
+ }
2341
+ /**
2342
+ * Wait for a spawned agent to complete
2343
+ */
2344
+ async waitForAgent(input) {
2345
+ const { task_id, timeout_seconds = 300 } = input;
2346
+ if (!task_id) {
2347
+ throw new Error('wait_for_agent requires a task_id');
2348
+ }
2349
+ const result = await this.waitForAgentInternal(task_id, timeout_seconds);
2350
+ return {
2351
+ success: result.success,
2352
+ status: result.status,
2353
+ task_id: task_id,
2354
+ message: result.message,
2355
+ result_summary: result.resultSummary,
2356
+ error: result.error,
2357
+ };
2358
+ }
2359
+ /**
2360
+ * Get status of spawned agents
2361
+ */
2362
+ async getAgentStatus(input) {
2363
+ const { task_ids } = input;
2364
+ let tasks;
2365
+ if (task_ids && task_ids.length > 0) {
2366
+ // Get specific tasks
2367
+ tasks = [];
2368
+ for (const id of task_ids) {
2369
+ const task = await this.daemon.getTaskById(id);
2370
+ if (task) {
2371
+ tasks.push(task);
2372
+ }
2373
+ }
2374
+ }
2375
+ else {
2376
+ // Get all child tasks of current task
2377
+ tasks = await this.daemon.getChildTasks(this.taskId);
2378
+ }
2379
+ const agents = tasks.map(task => ({
2380
+ task_id: task.id,
2381
+ title: task.title,
2382
+ status: task.status,
2383
+ agent_type: task.agentType || 'main',
2384
+ model_key: task.agentConfig?.modelKey,
2385
+ result_summary: task.resultSummary,
2386
+ error: task.error,
2387
+ created_at: task.createdAt,
2388
+ completed_at: task.completedAt,
2389
+ }));
2390
+ return {
2391
+ agents,
2392
+ message: `Found ${agents.length} agent(s)`,
2393
+ };
2394
+ }
2395
+ /**
2396
+ * List all spawned child agents for the current task
2397
+ */
2398
+ async listAgents(input) {
2399
+ const { status_filter = 'all' } = input;
2400
+ // Get all child tasks
2401
+ let tasks = await this.daemon.getChildTasks(this.taskId);
2402
+ // Apply filter
2403
+ if (status_filter !== 'all') {
2404
+ const runningStatuses = ['pending', 'queued', 'planning', 'executing', 'paused'];
2405
+ const completedStatuses = ['completed'];
2406
+ const failedStatuses = ['failed', 'cancelled'];
2407
+ tasks = tasks.filter(task => {
2408
+ switch (status_filter) {
2409
+ case 'running':
2410
+ return runningStatuses.includes(task.status);
2411
+ case 'completed':
2412
+ return completedStatuses.includes(task.status);
2413
+ case 'failed':
2414
+ return failedStatuses.includes(task.status);
2415
+ default:
2416
+ return true;
2417
+ }
2418
+ });
2419
+ }
2420
+ // Calculate summary from all child tasks (not filtered)
2421
+ const allTasks = await this.daemon.getChildTasks(this.taskId);
2422
+ const summary = {
2423
+ total: allTasks.length,
2424
+ running: allTasks.filter(t => ['pending', 'queued', 'planning', 'executing', 'paused'].includes(t.status)).length,
2425
+ completed: allTasks.filter(t => t.status === 'completed').length,
2426
+ failed: allTasks.filter(t => ['failed', 'cancelled'].includes(t.status)).length,
2427
+ };
2428
+ const agents = tasks.map(task => ({
2429
+ task_id: task.id,
2430
+ title: task.title,
2431
+ status: task.status,
2432
+ agent_type: task.agentType || 'main',
2433
+ model_key: task.agentConfig?.modelKey,
2434
+ depth: task.depth ?? 0,
2435
+ created_at: task.createdAt,
2436
+ }));
2437
+ return {
2438
+ agents,
2439
+ summary,
2440
+ message: status_filter === 'all'
2441
+ ? `Found ${agents.length} child agent(s)`
2442
+ : `Found ${agents.length} ${status_filter} agent(s) (${summary.total} total)`,
2443
+ };
2444
+ }
2445
+ /**
2446
+ * Define meta tools for execution control
2447
+ */
2448
+ getMetaToolDefinitions() {
2449
+ return [
2450
+ {
2451
+ name: 'revise_plan',
2452
+ description: 'Revise the execution plan. Use this when you encounter unexpected obstacles, ' +
2453
+ 'discover that the original plan is insufficient, need to stop execution, or find a better approach. ' +
2454
+ 'Can add new steps, clear remaining steps, or both.',
2455
+ input_schema: {
2456
+ type: 'object',
2457
+ properties: {
2458
+ reason: {
2459
+ type: 'string',
2460
+ description: 'Brief explanation of why the plan needs to be revised (e.g., "discovered missing dependency", "required path not found - need user input")',
2461
+ },
2462
+ clearRemaining: {
2463
+ type: 'boolean',
2464
+ description: 'Set to true to CLEAR/REMOVE all remaining pending steps. Use when the task cannot proceed (e.g., required files not found). Default is false.',
2465
+ },
2466
+ newSteps: {
2467
+ type: 'array',
2468
+ description: 'Array of new steps to add to the plan. Can be empty [] when clearing remaining steps.',
2469
+ items: {
2470
+ type: 'object',
2471
+ properties: {
2472
+ description: {
2473
+ type: 'string',
2474
+ description: 'Description of what this step should accomplish',
2475
+ },
2476
+ },
2477
+ required: ['description'],
2478
+ },
2479
+ },
2480
+ },
2481
+ required: ['reason'],
2482
+ },
2483
+ },
2484
+ {
2485
+ name: 'switch_workspace',
2486
+ description: 'Switch to a different workspace/working directory. Use this when you need to work in a different folder ' +
2487
+ 'than the current workspace. You can specify either a path to the folder or a workspace ID. ' +
2488
+ 'If the path doesn\'t have an existing workspace, a new one will be created.',
2489
+ input_schema: {
2490
+ type: 'object',
2491
+ properties: {
2492
+ path: {
2493
+ type: 'string',
2494
+ description: 'Absolute path to the folder to switch to (e.g., "/Users/user/projects/myapp")',
2495
+ },
2496
+ workspace_id: {
2497
+ type: 'string',
2498
+ description: 'ID of an existing workspace to switch to',
2499
+ },
2500
+ },
2501
+ },
2502
+ },
2503
+ {
2504
+ name: 'set_personality',
2505
+ description: 'Change the assistant\'s communication style and personality. Use this when the user asks you to be more friendly, ' +
2506
+ 'professional, concise, creative, technical, or casual. Available personalities: professional (formal, business-oriented), ' +
2507
+ 'friendly (warm, encouraging), concise (brief, to-the-point), creative (imaginative, expressive), ' +
2508
+ 'technical (detailed, precise), casual (relaxed, informal). The change takes effect for all future interactions.',
2509
+ input_schema: {
2510
+ type: 'object',
2511
+ properties: {
2512
+ personality: {
2513
+ type: 'string',
2514
+ enum: ['professional', 'friendly', 'concise', 'creative', 'technical', 'casual'],
2515
+ description: 'The personality to switch to',
2516
+ },
2517
+ },
2518
+ required: ['personality'],
2519
+ },
2520
+ },
2521
+ {
2522
+ name: 'set_persona',
2523
+ description: 'Change the assistant\'s character persona. Personas are character overlays inspired by famous AI assistants. ' +
2524
+ 'Use this when the user asks to change persona, act like a character, or wants a specific AI personality. ' +
2525
+ 'Available personas: jarvis (sophisticated butler), friday (friendly colleague), hal (calm/formal), ' +
2526
+ 'computer (Star Trek efficient), alfred (refined gentleman), intern (eager learner), sensei (wise teacher), ' +
2527
+ 'pirate (swashbuckling adventurer), noir (1940s detective), companion (warm, thoughtful presence). ' +
2528
+ 'Use "none" to remove persona overlay.',
2529
+ input_schema: {
2530
+ type: 'object',
2531
+ properties: {
2532
+ persona: {
2533
+ type: 'string',
2534
+ enum: ['none', 'jarvis', 'friday', 'hal', 'computer', 'alfred', 'intern', 'sensei', 'pirate', 'noir', 'companion'],
2535
+ description: 'The persona to adopt (or "none" to clear)',
2536
+ },
2537
+ },
2538
+ required: ['persona'],
2539
+ },
2540
+ },
2541
+ {
2542
+ name: 'set_agent_name',
2543
+ description: 'Set or change the assistant\'s name. Use this when the user wants to give you a name, rename you, or asks ' +
2544
+ '"what should I call you?" The name will be remembered and used in all future interactions. ' +
2545
+ 'Default name is "CoWork" if not customized.',
2546
+ input_schema: {
2547
+ type: 'object',
2548
+ properties: {
2549
+ name: {
2550
+ type: 'string',
2551
+ description: 'The new name for the assistant (e.g., "Jarvis", "Friday", "Max")',
2552
+ },
2553
+ },
2554
+ required: ['name'],
2555
+ },
2556
+ },
2557
+ {
2558
+ name: 'set_user_name',
2559
+ description: 'Store the user\'s name when they introduce themselves. Use this PROACTIVELY when the user tells you their name ' +
2560
+ '(e.g., "I\'m Alice", "My name is Bob", "Call me Charlie"). This helps personalize future interactions. ' +
2561
+ 'The name will be remembered across sessions and used in greetings and context.',
2562
+ input_schema: {
2563
+ type: 'object',
2564
+ properties: {
2565
+ name: {
2566
+ type: 'string',
2567
+ description: 'The user\'s name as they introduced themselves',
2568
+ },
2569
+ },
2570
+ required: ['name'],
2571
+ },
2572
+ },
2573
+ {
2574
+ name: 'set_response_style',
2575
+ description: 'Adjust how the assistant responds. Use when the user asks for different response styles like "use more emojis", ' +
2576
+ '"be more brief", "explain things simply", or "add more code comments". All parameters are optional - only set what the user wants to change.',
2577
+ input_schema: {
2578
+ type: 'object',
2579
+ properties: {
2580
+ emoji_usage: {
2581
+ type: 'string',
2582
+ enum: ['none', 'minimal', 'moderate', 'expressive'],
2583
+ description: 'How much to use emojis: none (never), minimal (rarely), moderate (sometimes), expressive (frequently)',
2584
+ },
2585
+ response_length: {
2586
+ type: 'string',
2587
+ enum: ['terse', 'balanced', 'detailed'],
2588
+ description: 'Response verbosity: terse (very brief), balanced (normal), detailed (comprehensive)',
2589
+ },
2590
+ code_comments: {
2591
+ type: 'string',
2592
+ enum: ['minimal', 'moderate', 'verbose'],
2593
+ description: 'Code commenting style: minimal (essential only), moderate (helpful comments), verbose (detailed explanations)',
2594
+ },
2595
+ explanation_depth: {
2596
+ type: 'string',
2597
+ enum: ['expert', 'balanced', 'teaching'],
2598
+ description: 'How deeply to explain: expert (assume knowledge), balanced (normal), teaching (thorough explanations)',
2599
+ },
2600
+ },
2601
+ },
2602
+ },
2603
+ {
2604
+ name: 'set_quirks',
2605
+ description: 'Set personality quirks like catchphrases, sign-offs, or analogy themes. Use when the user wants the assistant ' +
2606
+ 'to have a signature phrase, end responses a certain way, or use analogies from a specific domain. ' +
2607
+ 'Pass empty string to clear a quirk.',
2608
+ input_schema: {
2609
+ type: 'object',
2610
+ properties: {
2611
+ catchphrase: {
2612
+ type: 'string',
2613
+ description: 'A signature phrase to occasionally use (e.g., "At your service!", "Consider it done!")',
2614
+ },
2615
+ sign_off: {
2616
+ type: 'string',
2617
+ description: 'How to end longer responses (e.g., "Happy coding!", "May the force be with you!")',
2618
+ },
2619
+ analogy_domain: {
2620
+ type: 'string',
2621
+ enum: ['none', 'cooking', 'sports', 'space', 'music', 'nature', 'gaming', 'movies', 'construction'],
2622
+ description: 'Theme for analogies and examples: none (no preference), or a specific domain',
2623
+ },
2624
+ },
2625
+ },
2626
+ },
2627
+ // Sub-Agent / Parallel Agent tools
2628
+ {
2629
+ name: 'spawn_agent',
2630
+ description: 'Spawn a new agent (sub-task) to work on a specific task independently. Use this to delegate work, ' +
2631
+ 'perform parallel operations, or use a cheaper/faster model for batch work. Sub-agents do not retain ' +
2632
+ 'memory after completion. Returns immediately with the spawned task ID - use wait_for_agent or ' +
2633
+ 'get_agent_status to check progress. Maximum nesting depth is 3 levels.',
2634
+ input_schema: {
2635
+ type: 'object',
2636
+ properties: {
2637
+ prompt: {
2638
+ type: 'string',
2639
+ description: 'The task/instruction for the spawned agent. Be specific and include all context needed.',
2640
+ },
2641
+ title: {
2642
+ type: 'string',
2643
+ description: 'A short title for the subtask (optional, derived from prompt if not provided)',
2644
+ },
2645
+ model_preference: {
2646
+ type: 'string',
2647
+ enum: ['same', 'cheaper', 'smarter'],
2648
+ description: 'Model selection: "same" uses parent model, "cheaper" selects Haiku (fast/cheap), "smarter" selects Opus (most capable). Default: "cheaper" for cost optimization.',
2649
+ },
2650
+ personality: {
2651
+ type: 'string',
2652
+ enum: ['same', 'professional', 'technical', 'concise', 'creative', 'friendly'],
2653
+ description: 'Personality for the spawned agent. "same" inherits from parent. Default: "concise"',
2654
+ },
2655
+ wait: {
2656
+ type: 'boolean',
2657
+ description: 'If true, wait for the agent to complete before returning (blocking). Default: false (async)',
2658
+ },
2659
+ max_turns: {
2660
+ type: 'number',
2661
+ description: 'Maximum number of LLM turns for the sub-agent. Default: 20',
2662
+ },
2663
+ },
2664
+ required: ['prompt'],
2665
+ },
2666
+ },
2667
+ {
2668
+ name: 'wait_for_agent',
2669
+ description: 'Wait for a spawned agent to complete and retrieve its results. Returns the agent\'s final status, ' +
2670
+ 'result summary, and any error information. Use this to synchronize with sub-agents when you need ' +
2671
+ 'their results before proceeding.',
2672
+ input_schema: {
2673
+ type: 'object',
2674
+ properties: {
2675
+ task_id: {
2676
+ type: 'string',
2677
+ description: 'The task ID of the spawned agent (returned by spawn_agent)',
2678
+ },
2679
+ timeout_seconds: {
2680
+ type: 'number',
2681
+ description: 'Maximum time to wait in seconds. Default: 300 (5 minutes)',
2682
+ },
2683
+ },
2684
+ required: ['task_id'],
2685
+ },
2686
+ },
2687
+ {
2688
+ name: 'get_agent_status',
2689
+ description: 'Check the status of spawned agents. Returns current status, progress, and any results. ' +
2690
+ 'Use this for non-blocking status checks.',
2691
+ input_schema: {
2692
+ type: 'object',
2693
+ properties: {
2694
+ task_ids: {
2695
+ type: 'array',
2696
+ items: { type: 'string' },
2697
+ description: 'Array of task IDs to check. If empty or omitted, returns status of all child agents.',
2698
+ },
2699
+ },
2700
+ },
2701
+ },
2702
+ {
2703
+ name: 'list_agents',
2704
+ description: 'List all spawned child agents for the current task. Shows their status, model, title, and progress.',
2705
+ input_schema: {
2706
+ type: 'object',
2707
+ properties: {
2708
+ status_filter: {
2709
+ type: 'string',
2710
+ enum: ['all', 'running', 'completed', 'failed'],
2711
+ description: 'Filter agents by status. Default: "all"',
2712
+ },
2713
+ },
2714
+ },
2715
+ },
2716
+ ];
2717
+ }
2718
+ }
2719
+ exports.ToolRegistry = ToolRegistry;