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