@ebowwa/coder 0.7.63 → 0.7.65

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 (364) hide show
  1. package/dist/core/__tests__/permissions.test.d.ts +12 -0
  2. package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
  3. package/dist/core/__tests__/permissions.test.js +851 -0
  4. package/dist/core/agent-loop/__tests__/compaction.test.d.ts +5 -0
  5. package/dist/core/agent-loop/__tests__/compaction.test.d.ts.map +1 -0
  6. package/dist/core/agent-loop/__tests__/compaction.test.js +209 -0
  7. package/dist/core/agent-loop/__tests__/formatters.test.d.ts +5 -0
  8. package/dist/core/agent-loop/__tests__/formatters.test.d.ts.map +1 -0
  9. package/dist/core/agent-loop/__tests__/formatters.test.js +195 -0
  10. package/dist/core/agent-loop/__tests__/index.test.d.ts +5 -0
  11. package/dist/core/agent-loop/__tests__/index.test.d.ts.map +1 -0
  12. package/dist/core/agent-loop/__tests__/index.test.js +121 -0
  13. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts +5 -0
  14. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts.map +1 -0
  15. package/dist/core/agent-loop/__tests__/loop-state.test.js +340 -0
  16. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts +5 -0
  17. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts.map +1 -0
  18. package/dist/core/agent-loop/__tests__/message-builder.test.js +178 -0
  19. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts +5 -0
  20. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts.map +1 -0
  21. package/dist/core/agent-loop/__tests__/tool-executor.test.js +331 -0
  22. package/dist/core/agent-loop/compaction.d.ts +39 -0
  23. package/dist/core/agent-loop/compaction.d.ts.map +1 -0
  24. package/dist/core/agent-loop/compaction.js +51 -0
  25. package/dist/core/agent-loop/formatters.d.ts +21 -0
  26. package/dist/core/agent-loop/formatters.d.ts.map +1 -0
  27. package/dist/core/agent-loop/formatters.js +42 -0
  28. package/dist/core/agent-loop/index.d.ts +25 -0
  29. package/dist/core/agent-loop/index.d.ts.map +1 -0
  30. package/dist/core/agent-loop/index.js +83 -0
  31. package/dist/core/agent-loop/loop-state.d.ts +74 -0
  32. package/dist/core/agent-loop/loop-state.d.ts.map +1 -0
  33. package/dist/core/agent-loop/loop-state.js +147 -0
  34. package/dist/core/agent-loop/message-builder.d.ts +13 -0
  35. package/dist/core/agent-loop/message-builder.d.ts.map +1 -0
  36. package/dist/core/agent-loop/message-builder.js +49 -0
  37. package/dist/core/agent-loop/tool-executor.d.ts +23 -0
  38. package/dist/core/agent-loop/tool-executor.d.ts.map +1 -0
  39. package/dist/core/agent-loop/tool-executor.js +152 -0
  40. package/dist/core/agent-loop/turn-executor.d.ts +57 -0
  41. package/dist/core/agent-loop/turn-executor.d.ts.map +1 -0
  42. package/dist/core/agent-loop/turn-executor.js +124 -0
  43. package/dist/core/agent-loop/types.d.ts +141 -0
  44. package/dist/core/agent-loop/types.d.ts.map +1 -0
  45. package/dist/core/agent-loop/types.js +4 -0
  46. package/dist/core/agent-loop.d.ts +17 -0
  47. package/dist/core/agent-loop.d.ts.map +1 -0
  48. package/dist/core/agent-loop.js +16 -0
  49. package/dist/core/api-client-impl.d.ts +62 -0
  50. package/dist/core/api-client-impl.d.ts.map +1 -0
  51. package/dist/core/api-client-impl.js +479 -0
  52. package/dist/core/api-client.d.ts +6 -0
  53. package/dist/core/api-client.d.ts.map +1 -0
  54. package/dist/core/api-client.js +5 -0
  55. package/dist/core/checkpoints.d.ts +128 -0
  56. package/dist/core/checkpoints.d.ts.map +1 -0
  57. package/dist/core/checkpoints.js +438 -0
  58. package/dist/core/claude-md.d.ts +71 -0
  59. package/dist/core/claude-md.d.ts.map +1 -0
  60. package/dist/core/claude-md.js +198 -0
  61. package/dist/core/cognitive-security/hooks.d.ts +138 -0
  62. package/dist/core/cognitive-security/hooks.d.ts.map +1 -0
  63. package/dist/core/cognitive-security/hooks.js +389 -0
  64. package/dist/core/cognitive-security/index.d.ts +751 -0
  65. package/dist/core/cognitive-security/index.d.ts.map +1 -0
  66. package/dist/core/cognitive-security/index.js +1123 -0
  67. package/dist/core/cognitive-security/middleware.d.ts +136 -0
  68. package/dist/core/cognitive-security/middleware.d.ts.map +1 -0
  69. package/dist/core/cognitive-security/middleware.js +376 -0
  70. package/dist/core/config-loader.d.ts +127 -0
  71. package/dist/core/config-loader.d.ts.map +1 -0
  72. package/dist/core/config-loader.js +219 -0
  73. package/dist/core/context-compaction.d.ts +87 -0
  74. package/dist/core/context-compaction.d.ts.map +1 -0
  75. package/dist/core/context-compaction.js +428 -0
  76. package/dist/core/git-status.d.ts +25 -0
  77. package/dist/core/git-status.d.ts.map +1 -0
  78. package/dist/core/git-status.js +204 -0
  79. package/dist/core/image.d.ts +69 -0
  80. package/dist/core/image.d.ts.map +1 -0
  81. package/dist/core/image.js +290 -0
  82. package/dist/core/image.test.d.ts +2 -0
  83. package/dist/core/image.test.d.ts.map +1 -0
  84. package/dist/core/image.test.js +149 -0
  85. package/dist/core/models.d.ts +123 -0
  86. package/dist/core/models.d.ts.map +1 -0
  87. package/dist/core/models.js +325 -0
  88. package/dist/core/permissions.d.ts +81 -0
  89. package/dist/core/permissions.d.ts.map +1 -0
  90. package/dist/core/permissions.js +327 -0
  91. package/dist/core/retry.d.ts +25 -0
  92. package/dist/core/retry.d.ts.map +1 -0
  93. package/dist/core/retry.js +121 -0
  94. package/dist/core/session-store.d.ts +9 -0
  95. package/dist/core/session-store.d.ts.map +1 -0
  96. package/dist/core/session-store.js +10 -0
  97. package/dist/core/sessions/export.d.ts +47 -0
  98. package/dist/core/sessions/export.d.ts.map +1 -0
  99. package/dist/core/sessions/export.js +256 -0
  100. package/dist/core/sessions/index.d.ts +132 -0
  101. package/dist/core/sessions/index.d.ts.map +1 -0
  102. package/dist/core/sessions/index.js +442 -0
  103. package/dist/core/sessions/metadata.d.ts +77 -0
  104. package/dist/core/sessions/metadata.d.ts.map +1 -0
  105. package/dist/core/sessions/metadata.js +233 -0
  106. package/dist/core/sessions/persistence.d.ts +72 -0
  107. package/dist/core/sessions/persistence.d.ts.map +1 -0
  108. package/dist/core/sessions/persistence.js +201 -0
  109. package/dist/core/sessions/types.d.ts +110 -0
  110. package/dist/core/sessions/types.d.ts.map +1 -0
  111. package/dist/core/sessions/types.js +4 -0
  112. package/dist/core/stream-highlighter.d.ts +18 -0
  113. package/dist/core/stream-highlighter.d.ts.map +1 -0
  114. package/dist/core/stream-highlighter.js +916 -0
  115. package/dist/core/system-reminders.d.ts +89 -0
  116. package/dist/core/system-reminders.d.ts.map +1 -0
  117. package/dist/core/system-reminders.js +285 -0
  118. package/dist/ecosystem/hooks/__tests__/index.test.d.ts +5 -0
  119. package/dist/ecosystem/hooks/__tests__/index.test.d.ts.map +1 -0
  120. package/dist/ecosystem/hooks/__tests__/index.test.js +458 -0
  121. package/dist/ecosystem/hooks/index.d.ts +59 -0
  122. package/dist/ecosystem/hooks/index.d.ts.map +1 -0
  123. package/dist/ecosystem/hooks/index.js +294 -0
  124. package/dist/ecosystem/hooks/prompt-evaluator.d.ts +32 -0
  125. package/dist/ecosystem/hooks/prompt-evaluator.d.ts.map +1 -0
  126. package/dist/ecosystem/hooks/prompt-evaluator.js +229 -0
  127. package/dist/ecosystem/skills/index.d.ts +55 -0
  128. package/dist/ecosystem/skills/index.d.ts.map +1 -0
  129. package/dist/ecosystem/skills/index.js +258 -0
  130. package/dist/ecosystem/tools/__tests__/index.test.d.ts +7 -0
  131. package/dist/ecosystem/tools/__tests__/index.test.d.ts.map +1 -0
  132. package/dist/ecosystem/tools/__tests__/index.test.js +856 -0
  133. package/dist/ecosystem/tools/index.d.ts +24 -0
  134. package/dist/ecosystem/tools/index.d.ts.map +1 -0
  135. package/dist/ecosystem/tools/index.js +1709 -0
  136. package/dist/index.d.ts +24 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +33688 -49712
  139. package/dist/interfaces/mcp/client.d.ts +40 -0
  140. package/dist/interfaces/mcp/client.d.ts.map +1 -0
  141. package/dist/interfaces/mcp/client.js +309 -0
  142. package/dist/interfaces/ui/index.d.ts +36 -0
  143. package/dist/interfaces/ui/index.d.ts.map +1 -0
  144. package/dist/interfaces/ui/index.js +61 -0
  145. package/dist/interfaces/ui/spinner.d.ts +140 -0
  146. package/dist/interfaces/ui/spinner.d.ts.map +1 -0
  147. package/dist/interfaces/ui/spinner.js +342 -0
  148. package/dist/interfaces/ui/terminal/cli/index.d.ts +12 -0
  149. package/dist/interfaces/ui/terminal/cli/index.d.ts.map +1 -0
  150. package/dist/interfaces/ui/terminal/cli/index.js +32012 -50526
  151. package/dist/interfaces/ui/terminal/native/README.md +53 -0
  152. package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
  153. package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
  154. package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
  155. package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
  156. package/dist/interfaces/ui/terminal/native/index.js +43 -0
  157. package/dist/interfaces/ui/terminal/native/index.node +0 -0
  158. package/dist/interfaces/ui/terminal/native/package.json +34 -0
  159. package/dist/interfaces/ui/terminal/shared/args.d.ts +39 -0
  160. package/dist/interfaces/ui/terminal/shared/args.d.ts.map +1 -0
  161. package/dist/interfaces/ui/terminal/shared/args.js +176 -0
  162. package/dist/interfaces/ui/terminal/shared/index.d.ts +11 -0
  163. package/dist/interfaces/ui/terminal/shared/index.d.ts.map +1 -0
  164. package/dist/interfaces/ui/terminal/shared/index.js +16 -0
  165. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts +124 -0
  166. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts.map +1 -0
  167. package/dist/interfaces/ui/terminal/shared/loading-state.js +246 -0
  168. package/dist/interfaces/ui/terminal/shared/query.d.ts +22 -0
  169. package/dist/interfaces/ui/terminal/shared/query.d.ts.map +1 -0
  170. package/dist/interfaces/ui/terminal/shared/query.js +100 -0
  171. package/dist/interfaces/ui/terminal/shared/setup.d.ts +33 -0
  172. package/dist/interfaces/ui/terminal/shared/setup.d.ts.map +1 -0
  173. package/dist/interfaces/ui/terminal/shared/setup.js +226 -0
  174. package/dist/interfaces/ui/terminal/shared/status-line.d.ts +117 -0
  175. package/dist/interfaces/ui/terminal/shared/status-line.d.ts.map +1 -0
  176. package/dist/interfaces/ui/terminal/shared/status-line.js +267 -0
  177. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts +38 -0
  178. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts.map +1 -0
  179. package/dist/interfaces/ui/terminal/shared/system-prompt.js +102 -0
  180. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts +39 -0
  181. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts.map +1 -0
  182. package/dist/interfaces/ui/terminal/tui/HelpPanel.js +215 -0
  183. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts +91 -0
  184. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts.map +1 -0
  185. package/dist/interfaces/ui/terminal/tui/InputContext.js +154 -0
  186. package/dist/interfaces/ui/terminal/tui/InputField.d.ts +18 -0
  187. package/dist/interfaces/ui/terminal/tui/InputField.d.ts.map +1 -0
  188. package/dist/interfaces/ui/terminal/tui/InputField.js +41 -0
  189. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts +16 -0
  190. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts.map +1 -0
  191. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.js +451 -0
  192. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts +10 -0
  193. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts.map +1 -0
  194. package/dist/interfaces/ui/terminal/tui/MessageArea.js +91 -0
  195. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts +48 -0
  196. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts.map +1 -0
  197. package/dist/interfaces/ui/terminal/tui/MessageStore.js +151 -0
  198. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts +9 -0
  199. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts.map +1 -0
  200. package/dist/interfaces/ui/terminal/tui/StatusBar.js +36 -0
  201. package/dist/interfaces/ui/terminal/tui/commands.d.ts +21 -0
  202. package/dist/interfaces/ui/terminal/tui/commands.d.ts.map +1 -0
  203. package/dist/interfaces/ui/terminal/tui/commands.js +359 -0
  204. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts +115 -0
  205. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts.map +1 -0
  206. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.js +306 -0
  207. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts +92 -0
  208. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts.map +1 -0
  209. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.js +399 -0
  210. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts +59 -0
  211. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts.map +1 -0
  212. package/dist/interfaces/ui/terminal/tui/components/PaneManager.js +139 -0
  213. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts +68 -0
  214. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts.map +1 -0
  215. package/dist/interfaces/ui/terminal/tui/components/Sidebar.js +340 -0
  216. package/dist/interfaces/ui/terminal/tui/components/index.d.ts +23 -0
  217. package/dist/interfaces/ui/terminal/tui/components/index.d.ts.map +1 -0
  218. package/dist/interfaces/ui/terminal/tui/components/index.js +51 -0
  219. package/dist/interfaces/ui/terminal/tui/console.d.ts +20 -0
  220. package/dist/interfaces/ui/terminal/tui/console.d.ts.map +1 -0
  221. package/dist/interfaces/ui/terminal/tui/console.js +46 -0
  222. package/dist/interfaces/ui/terminal/tui/index.d.ts +20 -0
  223. package/dist/interfaces/ui/terminal/tui/index.d.ts.map +1 -0
  224. package/dist/interfaces/ui/terminal/tui/index.js +28 -0
  225. package/dist/interfaces/ui/terminal/tui/run.d.ts +13 -0
  226. package/dist/interfaces/ui/terminal/tui/run.d.ts.map +1 -0
  227. package/dist/interfaces/ui/terminal/tui/run.js +31 -0
  228. package/dist/interfaces/ui/terminal/tui/spinner.d.ts +44 -0
  229. package/dist/interfaces/ui/terminal/tui/spinner.d.ts.map +1 -0
  230. package/dist/interfaces/ui/terminal/tui/spinner.js +59 -0
  231. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts +39 -0
  232. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts.map +1 -0
  233. package/dist/interfaces/ui/terminal/tui/tui-app.js +198 -0
  234. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts +167 -0
  235. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts.map +1 -0
  236. package/dist/interfaces/ui/terminal/tui/tui-footer.js +330 -0
  237. package/dist/interfaces/ui/terminal/tui/types.d.ts +165 -0
  238. package/dist/interfaces/ui/terminal/tui/types.d.ts.map +1 -0
  239. package/dist/interfaces/ui/terminal/tui/types.js +5 -0
  240. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts +23 -0
  241. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts.map +1 -0
  242. package/dist/interfaces/ui/terminal/tui/useInputHandler.js +72 -0
  243. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts +90 -0
  244. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts.map +1 -0
  245. package/dist/interfaces/ui/terminal/tui/useNativeInput.js +188 -0
  246. package/dist/native/README.md +53 -0
  247. package/dist/native/claude_code_native.darwin-x64.node +0 -0
  248. package/dist/native/claude_code_native.dylib +0 -0
  249. package/dist/native/index.d.ts +0 -0
  250. package/dist/native/index.d.ts.map +1 -0
  251. package/dist/native/index.darwin-arm64.node +0 -0
  252. package/dist/native/index.js +43 -0
  253. package/dist/native/index.node +0 -0
  254. package/dist/native/package.json +34 -0
  255. package/dist/teammates/index.d.ts +161 -0
  256. package/dist/teammates/index.d.ts.map +1 -0
  257. package/dist/teammates/index.js +827 -0
  258. package/dist/types/index.d.ts +482 -0
  259. package/dist/types/index.d.ts.map +1 -0
  260. package/dist/types/index.js +52 -0
  261. package/native/index.darwin-arm64.node +0 -0
  262. package/native/index.js +33 -19
  263. package/package.json +6 -3
  264. package/packages/src/core/__tests__/permissions.test.ts +1091 -0
  265. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +283 -0
  266. package/packages/src/core/agent-loop/__tests__/formatters.test.ts +234 -0
  267. package/packages/src/core/agent-loop/__tests__/index.test.ts +162 -0
  268. package/packages/src/core/agent-loop/__tests__/loop-state.test.ts +413 -0
  269. package/packages/src/core/agent-loop/__tests__/message-builder.test.ts +229 -0
  270. package/packages/src/core/agent-loop/__tests__/tool-executor.test.ts +457 -0
  271. package/packages/src/core/agent-loop/compaction.ts +92 -0
  272. package/packages/src/core/agent-loop/formatters.ts +50 -0
  273. package/packages/src/core/agent-loop/index.ts +137 -0
  274. package/packages/src/core/agent-loop/loop-state.ts +187 -0
  275. package/packages/src/core/agent-loop/message-builder.ts +62 -0
  276. package/packages/src/core/agent-loop/tool-executor.ts +211 -0
  277. package/packages/src/core/agent-loop/turn-executor.ts +226 -0
  278. package/packages/src/core/agent-loop/types.ts +152 -0
  279. package/packages/src/core/agent-loop.ts +18 -0
  280. package/packages/src/core/api-client-impl.ts +729 -0
  281. package/packages/src/core/api-client.ts +6 -0
  282. package/packages/src/core/checkpoints.ts +606 -0
  283. package/packages/src/core/claude-md.ts +272 -0
  284. package/packages/src/core/cognitive-security/hooks.ts +591 -0
  285. package/packages/src/core/cognitive-security/index.ts +2041 -0
  286. package/packages/src/core/cognitive-security/middleware.ts +536 -0
  287. package/packages/src/core/config/todo +7 -0
  288. package/packages/src/core/config-loader.ts +324 -0
  289. package/packages/src/core/context/__tests__/integration.test.ts +334 -0
  290. package/packages/src/core/context/compaction.ts +170 -0
  291. package/packages/src/core/context/constants.ts +58 -0
  292. package/packages/src/core/context/extraction.ts +85 -0
  293. package/packages/src/core/context/index.ts +66 -0
  294. package/packages/src/core/context/summarization.ts +251 -0
  295. package/packages/src/core/context/token-estimation.ts +98 -0
  296. package/packages/src/core/context/types.ts +59 -0
  297. package/packages/src/core/git-status.ts +262 -0
  298. package/packages/src/core/image.test.ts +180 -0
  299. package/packages/src/core/image.ts +350 -0
  300. package/packages/src/core/lmdb.db +0 -0
  301. package/packages/src/core/lmdb.db-lock +0 -0
  302. package/packages/src/core/models.ts +507 -0
  303. package/packages/src/core/normalizers/todo +8 -0
  304. package/packages/src/core/permissions.ts +431 -0
  305. package/packages/src/core/providers/README.md +230 -0
  306. package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
  307. package/packages/src/core/providers/index.ts +419 -0
  308. package/packages/src/core/providers/types.ts +132 -0
  309. package/packages/src/core/retry.ts +180 -0
  310. package/packages/src/core/session-store.ts +36 -0
  311. package/packages/src/core/sessions/export.ts +329 -0
  312. package/packages/src/core/sessions/index.ts +587 -0
  313. package/packages/src/core/sessions/metadata.ts +309 -0
  314. package/packages/src/core/sessions/persistence.ts +244 -0
  315. package/packages/src/core/sessions/types.ts +169 -0
  316. package/packages/src/core/stream-highlighter.ts +1123 -0
  317. package/packages/src/core/system-reminders.ts +402 -0
  318. package/packages/src/core/todo +8 -0
  319. package/packages/src/ecosystem/hooks/__tests__/index.test.ts +561 -0
  320. package/packages/src/ecosystem/hooks/index.ts +341 -0
  321. package/packages/src/ecosystem/hooks/prompt-evaluator.ts +300 -0
  322. package/packages/src/ecosystem/skills/index.ts +295 -0
  323. package/packages/src/ecosystem/tools/__tests__/index.test.ts +1335 -0
  324. package/packages/src/ecosystem/tools/index.ts +2051 -0
  325. package/packages/src/index.ts +141 -0
  326. package/packages/src/interfaces/mcp/client.ts +389 -0
  327. package/packages/src/interfaces/ui/index.ts +158 -0
  328. package/packages/src/interfaces/ui/lmdb.db +0 -0
  329. package/packages/src/interfaces/ui/lmdb.db-lock +0 -0
  330. package/packages/src/interfaces/ui/spinner.ts +451 -0
  331. package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
  332. package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
  333. package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
  334. package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
  335. package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
  336. package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
  337. package/packages/src/interfaces/ui/terminal/cli/index.ts +415 -0
  338. package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
  339. package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
  340. package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
  341. package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
  342. package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
  343. package/packages/src/interfaces/ui/terminal/lmdb.db +0 -0
  344. package/packages/src/interfaces/ui/terminal/lmdb.db-lock +0 -0
  345. package/packages/src/interfaces/ui/terminal/shared/args.ts +222 -0
  346. package/packages/src/interfaces/ui/terminal/shared/index.ts +84 -0
  347. package/packages/src/interfaces/ui/terminal/shared/loading-state.ts +322 -0
  348. package/packages/src/interfaces/ui/terminal/shared/query.ts +152 -0
  349. package/packages/src/interfaces/ui/terminal/shared/setup.ts +299 -0
  350. package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
  351. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +366 -0
  352. package/packages/src/interfaces/ui/terminal/shared/system-prompt.ts +146 -0
  353. package/packages/src/lmdb.db +0 -0
  354. package/packages/src/lmdb.db-lock +0 -0
  355. package/packages/src/native/index.ts +2722 -0
  356. package/packages/src/native/tui_v2_types.ts +39 -0
  357. package/packages/src/teammates/coordination.test.ts +279 -0
  358. package/packages/src/teammates/coordination.ts +646 -0
  359. package/packages/src/teammates/index.ts +1052 -0
  360. package/packages/src/teammates/integration.test.ts +272 -0
  361. package/packages/src/teammates/runner.test.ts +235 -0
  362. package/packages/src/teammates/runner.ts +750 -0
  363. package/packages/src/teammates/schemas.ts +673 -0
  364. package/packages/src/types/index.ts +723 -0
@@ -0,0 +1,2051 @@
1
+ /**
2
+ * Built-in Tools
3
+ */
4
+
5
+ import type { ToolDefinition, ToolResult, ToolContext, ImageBlock } from "../../types/index.js";
6
+ import { glob as globAsync } from "glob";
7
+ import { spawn } from "child_process";
8
+ import { apply_multi_edits, validate_multi_edits, preview_multi_edits, type MultiEditEntry } from "../../native/index.js";
9
+ import {
10
+ isImageExtension,
11
+ isBinaryExclusion,
12
+ readImageFile,
13
+ toImageBlock,
14
+ formatImageResult,
15
+ } from "../../core/image.js";
16
+ import { MODEL_ALIASES, resolveModelAlias } from "../../core/models.js";
17
+ import * as path from "path";
18
+
19
+ // ============================================
20
+ // READ TOOL
21
+ // ============================================
22
+
23
+ export const ReadTool: ToolDefinition = {
24
+ name: "Read",
25
+ description:
26
+ "Reads a file from the local filesystem. You can access any file directly by using this tool.\n\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid.\n\nThis tool allows Coder to read images (PNG, JPG, JPEG, GIF, WEBP) and PDF files.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify line offset and limit (especially handy for long files)\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n\nThis tool can read images (PNG, JPG, JPEG, GIF, WEBP). When reading images, the tool displays them visually.\n\nFor PDF files:\n- Get the pages parameter to read specific page ranges (e.g., pages: \"1-5\")\n- Maximum 20 pages per request\n- For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges.\n\nTry to read the whole file by default, but for particularly large files, you should consider reading the file in chunks.\n\nIf you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.",
27
+ input_schema: {
28
+ type: "object",
29
+ properties: {
30
+ file_path: {
31
+ type: "string",
32
+ description: "The absolute path to the file to read",
33
+ },
34
+ offset: {
35
+ type: "number",
36
+ description: "The line number to start reading from (1-based)",
37
+ },
38
+ limit: {
39
+ type: "number",
40
+ description: "The number of lines to read",
41
+ },
42
+ pages: {
43
+ type: "string",
44
+ description: "Page range for PDF files (e.g., \"1-5\")",
45
+ },
46
+ },
47
+ required: ["file_path"],
48
+ },
49
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
50
+ const filePath = args.file_path as string;
51
+ const offset = (args.offset as number) || 1;
52
+ const limit = (args.limit as number) || 2000;
53
+
54
+ // Validate required parameters
55
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
56
+ return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
57
+ }
58
+
59
+ try {
60
+ // Get file extension to check for image files
61
+ const ext = path.extname(filePath).toLowerCase().slice(1);
62
+
63
+ // Check if this is an image file (GI8 set in binary)
64
+ if (isImageExtension(ext)) {
65
+ return await handleImageRead(filePath, context.abortSignal);
66
+ }
67
+
68
+ // Check for binary exclusions (CI8 set in binary)
69
+ if (isBinaryExclusion(ext)) {
70
+ return {
71
+ content: `Binary file detected: ${filePath}\nThis file type (${ext}) is not supported for text reading.`,
72
+ is_error: true,
73
+ };
74
+ }
75
+
76
+ // Default text file reading
77
+ const file = Bun.file(filePath);
78
+
79
+ // Check if file exists
80
+ if (!(await file.exists())) {
81
+ return { content: `Error: File not found: ${filePath}`, is_error: true };
82
+ }
83
+
84
+ const text = await file.text();
85
+ const lines = text.split("\n");
86
+
87
+ // Apply offset and limit (1-based offset)
88
+ const startLine = Math.max(0, offset - 1);
89
+ const endLine = Math.min(lines.length, startLine + limit);
90
+ const selectedLines = lines.slice(startLine, endLine);
91
+
92
+ // Check for truncation
93
+ const wasTruncated = endLine < lines.length;
94
+
95
+ // Format with line numbers
96
+ const formatted = selectedLines
97
+ .map((line, i) => `${startLine + i + 1}\t${line}`)
98
+ .join("\n");
99
+
100
+ // Add truncation notice if applicable
101
+ let result = formatted;
102
+ if (wasTruncated) {
103
+ result += `\n\n> WARNING: ${filePath} is ${lines.length} lines (limit: ${limit}). Only the first ${limit} lines were loaded.`;
104
+ }
105
+
106
+ return { content: result };
107
+ } catch (error) {
108
+ const errorMessage = error instanceof Error ? error.message : String(error);
109
+ return { content: `Error reading file: ${errorMessage}`, is_error: true };
110
+ }
111
+ },
112
+ };
113
+
114
+ /**
115
+ * Handle reading image files (bwA function in binary)
116
+ */
117
+ async function handleImageRead(filePath: string, signal?: AbortSignal): Promise<ToolResult> {
118
+ try {
119
+ const result = await readImageFile(filePath, 25000, signal);
120
+ const imageBlock = toImageBlock(result);
121
+
122
+ // Return as ContentBlock array for proper image handling
123
+ // TODO: Return image block when using native Anthropic API (proxies like Z.AI don't support images in tool_result)
124
+ // For now, return text description only
125
+ // When proxy support is detected, return: [{ type: "text", text: formatImageResult(result) }, imageBlock]
126
+ return {
127
+ content: `${formatImageResult(result)}
128
+
129
+ Note: Image content was read but cannot be displayed through this API proxy. When using native Anthropic API, the image would be included for visual analysis.`,
130
+ };
131
+ } catch (error) {
132
+ const errorMessage = error instanceof Error ? error.message : String(error);
133
+ return { content: `Error reading image: ${errorMessage}`, is_error: true };
134
+ }
135
+ }
136
+
137
+ // ============================================
138
+ // WRITE TOOL
139
+ // ============================================
140
+
141
+ export const WriteTool: ToolDefinition = {
142
+ name: "Write",
143
+ description:
144
+ "Writes a file to the local filesystem. This tool will overwrite the existing file if there is one at the provided path.",
145
+ input_schema: {
146
+ type: "object",
147
+ properties: {
148
+ file_path: {
149
+ type: "string",
150
+ description: "The absolute path to the file to write",
151
+ },
152
+ content: {
153
+ type: "string",
154
+ description: "The content to write to the file",
155
+ },
156
+ },
157
+ required: ["file_path", "content"],
158
+ },
159
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
160
+ const filePath = args.file_path as string;
161
+ const content = args.content as string;
162
+
163
+ // Validate required parameters
164
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
165
+ return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
166
+ }
167
+ if (content === undefined || content === null) {
168
+ return { content: "Error: content parameter is required", is_error: true };
169
+ }
170
+
171
+ try {
172
+ await Bun.write(filePath, content);
173
+ return { content: `Successfully wrote to ${filePath}` };
174
+ } catch (error) {
175
+ const errorMessage = error instanceof Error ? error.message : String(error);
176
+ return { content: `Error writing file: ${errorMessage}`, is_error: true };
177
+ }
178
+ },
179
+ };
180
+
181
+ // ============================================
182
+ // EDIT TOOL
183
+ // ============================================
184
+
185
+ export const EditTool: ToolDefinition = {
186
+ name: "Edit",
187
+ description:
188
+ "Performs exact string replacements in files. Use this tool to modify existing files.",
189
+ input_schema: {
190
+ type: "object",
191
+ properties: {
192
+ file_path: {
193
+ type: "string",
194
+ description: "The absolute path to the file to modify",
195
+ },
196
+ old_string: {
197
+ type: "string",
198
+ description: "The text to replace",
199
+ },
200
+ new_string: {
201
+ type: "string",
202
+ description: "The text to replace it with",
203
+ },
204
+ replace_all: {
205
+ type: "boolean",
206
+ description: "Replace all occurrences (default false)",
207
+ },
208
+ },
209
+ required: ["file_path", "old_string", "new_string"],
210
+ },
211
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
212
+ const filePath = args.file_path as string;
213
+ const oldString = args.old_string as string;
214
+ const newString = args.new_string as string;
215
+ const replaceAll = (args.replace_all as boolean) || false;
216
+
217
+ // Validate required parameters
218
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
219
+ return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
220
+ }
221
+ if (oldString === undefined || oldString === null) {
222
+ return { content: "Error: old_string parameter is required", is_error: true };
223
+ }
224
+ if (newString === undefined || newString === null) {
225
+ return { content: "Error: new_string parameter is required", is_error: true };
226
+ }
227
+
228
+ try {
229
+ const file = Bun.file(filePath);
230
+ let content = await file.text();
231
+
232
+ if (replaceAll) {
233
+ const originalContent = content;
234
+ content = content.split(oldString).join(newString);
235
+ const count = (originalContent.match(new RegExp(escapeRegex(oldString), "g")) || []).length;
236
+ if (count === 0) {
237
+ return { content: `Error: String not found in file`, is_error: true };
238
+ }
239
+ await Bun.write(filePath, content);
240
+ return { content: `Successfully replaced ${count} occurrences` };
241
+ } else {
242
+ const index = content.indexOf(oldString);
243
+ if (index === -1) {
244
+ return { content: `Error: String not found in file`, is_error: true };
245
+ }
246
+ // Check for uniqueness
247
+ const secondIndex = content.indexOf(oldString, index + 1);
248
+ if (secondIndex !== -1) {
249
+ return {
250
+ content: `Error: String appears multiple times in file. Use replace_all or provide more context.`,
251
+ is_error: true,
252
+ };
253
+ }
254
+ content = content.replace(oldString, newString);
255
+ await Bun.write(filePath, content);
256
+ return { content: `Successfully edited ${filePath}` };
257
+ }
258
+ } catch (error) {
259
+ const errorMessage = error instanceof Error ? error.message : String(error);
260
+ return { content: `Error editing file: ${errorMessage}`, is_error: true };
261
+ }
262
+ },
263
+ };
264
+
265
+ function escapeRegex(string: string): string {
266
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
267
+ }
268
+
269
+ // ============================================
270
+ // BASH TOOL
271
+ // ============================================
272
+
273
+ export const BashTool: ToolDefinition = {
274
+ name: "Bash",
275
+ description:
276
+ "Executes a given bash command with optional timeout. Working directory persists between commands.",
277
+ input_schema: {
278
+ type: "object",
279
+ properties: {
280
+ command: {
281
+ type: "string",
282
+ description: "The command to execute",
283
+ },
284
+ timeout: {
285
+ type: "number",
286
+ description: "Optional timeout in milliseconds (max 600000)",
287
+ },
288
+ description: {
289
+ type: "string",
290
+ description: "Clear, concise description of what this command does",
291
+ },
292
+ },
293
+ required: ["command"],
294
+ },
295
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
296
+ const command = args.command as string;
297
+ const timeout = (args.timeout as number) || 120000;
298
+
299
+ // Validate required parameters
300
+ if (!command || typeof command !== 'string' || command.trim() === '') {
301
+ return { content: "Error: command parameter is required and must be a non-empty string", is_error: true };
302
+ }
303
+
304
+ try {
305
+ const result = Bun.spawnSync(["sh", "-c", command], {
306
+ cwd: context.workingDirectory,
307
+ timeout,
308
+ maxBuffer: 1024 * 1024 * 30, // 30MB
309
+ });
310
+
311
+ const stdout = result.stdout?.toString() || "";
312
+ const stderr = result.stderr?.toString() || "";
313
+
314
+ if (result.exitCode !== 0) {
315
+ return {
316
+ content: `Exit code: ${result.exitCode}\n${stdout}\n${stderr}`.trim(),
317
+ is_error: true,
318
+ };
319
+ }
320
+
321
+ return { content: stdout || "(no output)" };
322
+ } catch (error) {
323
+ const errorMessage = error instanceof Error ? error.message : String(error);
324
+ return { content: `Error executing command: ${errorMessage}`, is_error: true };
325
+ }
326
+ },
327
+ };
328
+
329
+ // ============================================
330
+ // GLOB TOOL
331
+ // ============================================
332
+
333
+ export const GlobTool: ToolDefinition = {
334
+ name: "Glob",
335
+ description:
336
+ "Fast file pattern matching tool that works with any codebase size. Supports glob patterns.",
337
+ input_schema: {
338
+ type: "object",
339
+ properties: {
340
+ pattern: {
341
+ type: "string",
342
+ description: "The glob pattern to match files",
343
+ },
344
+ path: {
345
+ type: "string",
346
+ description: "The directory to search (default: current directory)",
347
+ },
348
+ },
349
+ required: ["pattern"],
350
+ },
351
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
352
+ const pattern = args.pattern as string;
353
+ const searchPath = (args.path as string) || context.workingDirectory;
354
+
355
+ // Validate required parameters
356
+ if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
357
+ return { content: "Error: pattern parameter is required and must be a non-empty string", is_error: true };
358
+ }
359
+
360
+ try {
361
+ const files = await globAsync(pattern, {
362
+ cwd: searchPath,
363
+ absolute: true,
364
+ nodir: true,
365
+ });
366
+
367
+ if (files.length === 0) {
368
+ return { content: "No files found matching pattern" };
369
+ }
370
+
371
+ return { content: files.sort().join("\n") };
372
+ } catch (error) {
373
+ const errorMessage = error instanceof Error ? error.message : String(error);
374
+ return { content: `Error searching files: ${errorMessage}`, is_error: true };
375
+ }
376
+ },
377
+ };
378
+
379
+ // ============================================
380
+ // GREP TOOL
381
+ // ============================================
382
+
383
+ export const GrepTool: ToolDefinition = {
384
+ name: "Grep",
385
+ description:
386
+ "A powerful search tool built on ripgrep. Supports full regex syntax.",
387
+ input_schema: {
388
+ type: "object",
389
+ properties: {
390
+ pattern: {
391
+ type: "string",
392
+ description: "The regular expression pattern to search for",
393
+ },
394
+ path: {
395
+ type: "string",
396
+ description: "File or directory to search",
397
+ },
398
+ glob: {
399
+ type: "string",
400
+ description: "Glob pattern to filter files",
401
+ },
402
+ output_mode: {
403
+ type: "string",
404
+ enum: ["content", "files_with_matches", "count"],
405
+ description: "Output mode (default: content)",
406
+ },
407
+ "-i:": {
408
+ type: "boolean",
409
+ description: "Case insensitive search",
410
+ },
411
+ "-C:": {
412
+ type: "number",
413
+ description: "Context lines around match",
414
+ },
415
+ head_limit: {
416
+ type: "number",
417
+ description: "Maximum number of results",
418
+ },
419
+ },
420
+ required: ["pattern"],
421
+ },
422
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
423
+ const pattern = args.pattern as string;
424
+ const searchPath = (args.path as string) || context.workingDirectory;
425
+ const glob = args.glob as string | undefined;
426
+ const outputMode = (args.output_mode as string) || "content";
427
+ const caseInsensitive = args["-i:"] as boolean;
428
+ const contextLines = args["-C:"] as number;
429
+ const headLimit = args.head_limit as number;
430
+
431
+ // Validate required parameters
432
+ if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
433
+ return { content: "Error: pattern parameter is required and must be a non-empty string", is_error: true };
434
+ }
435
+
436
+ try {
437
+ // Build ripgrep arguments
438
+ const rgArgs = ["--json"];
439
+ if (caseInsensitive) rgArgs.push("-i");
440
+ if (contextLines) rgArgs.push("-C", String(contextLines));
441
+ if (glob) rgArgs.push("--glob", glob);
442
+ if (outputMode === "files_with_matches") rgArgs.push("--files-with-matches");
443
+ if (outputMode === "count") rgArgs.push("--count");
444
+
445
+ rgArgs.push(pattern, searchPath);
446
+
447
+ const result = Bun.spawnSync(["rg", ...rgArgs], {
448
+ cwd: searchPath,
449
+ maxBuffer: 1024 * 1024 * 10, // 10MB
450
+ });
451
+
452
+ const stdout = result.stdout?.toString() || "";
453
+
454
+ if (!stdout.trim()) {
455
+ return { content: "No matches found" };
456
+ }
457
+
458
+ // Parse JSON output for content mode
459
+ if (outputMode === "content") {
460
+ const lines = stdout.trim().split("\n");
461
+ const matches: string[] = [];
462
+
463
+ for (const line of lines.slice(0, headLimit || 100)) {
464
+ try {
465
+ const parsed = JSON.parse(line);
466
+ if (parsed.type === "match") {
467
+ const filePath = parsed.data?.path?.text || "";
468
+ const lineNum = parsed.data?.line_number || 0;
469
+ const text = parsed.data?.lines?.text || "";
470
+ matches.push(`${filePath}:${lineNum}:${text.trim()}`);
471
+ }
472
+ } catch {
473
+ // Not JSON, use raw line
474
+ matches.push(line);
475
+ }
476
+ }
477
+
478
+ return { content: matches.join("\n") || "No matches found" };
479
+ }
480
+
481
+ return { content: stdout.trim() };
482
+ } catch (error) {
483
+ const errorMessage = error instanceof Error ? error.message : String(error);
484
+ return { content: `Error searching: ${errorMessage}`, is_error: true };
485
+ }
486
+ },
487
+ };
488
+
489
+ // ============================================
490
+ // TASK TOOL (Subagents)
491
+ // ============================================
492
+
493
+ export const TaskTool: ToolDefinition = {
494
+ name: "Task",
495
+ description: `Launch a new agent to handle complex, multi-step tasks autonomously.
496
+
497
+ The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
498
+
499
+ Available agent types and their tools:
500
+ - Bash: Command execution specialist for running bash commands. Use for git operations, command execution, and other terminal tasks.
501
+ - general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks.
502
+ - Explore: Fast agent specialized for exploring codebases. Use to quickly find files by patterns, search code for keywords, or answer questions about the codebase.
503
+ - Plan: Software architect agent for designing implementation plans. Returns step-by-step plans, identifies critical files and considers architectural trade-offs.
504
+
505
+ When using the Task tool, you must specify a subagent_type parameter to select the agent type.
506
+
507
+ Usage notes:
508
+ - Always include a short description (3-5 words) summarizing what the agent will do
509
+ - Launch multiple agents concurrently whenever possible to maximize performance
510
+ - Agents can be resumed using the "resume" parameter by passing the agent ID from a previous invocation.`,
511
+ input_schema: {
512
+ type: "object",
513
+ properties: {
514
+ subagent_type: {
515
+ type: "string",
516
+ enum: ["Bash", "general-purpose", "Explore", "Plan"],
517
+ description: "The agent type to launch",
518
+ },
519
+ description: {
520
+ type: "string",
521
+ description: "A short (3-5 word) description of what the agent will do",
522
+ },
523
+ prompt: {
524
+ type: "string",
525
+ description: "The task for the agent to perform",
526
+ },
527
+ resume: {
528
+ type: "string",
529
+ description: "Resume a previous agent by its ID",
530
+ },
531
+ model: {
532
+ type: "string",
533
+ enum: ["sonnet", "opus", "haiku"],
534
+ description: "Model for the subagent (default: haiku for quick tasks)",
535
+ },
536
+ run_in_background: {
537
+ type: "boolean",
538
+ description: "Run the agent in the background",
539
+ },
540
+ },
541
+ required: ["subagent_type", "prompt"],
542
+ },
543
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
544
+ const subagentType = args.subagent_type as string;
545
+ const prompt = args.prompt as string;
546
+ const description = args.description as string | undefined;
547
+ const model = (args.model as string) || "haiku";
548
+ const resume = args.resume as string | undefined;
549
+ const runInBackground = args.run_in_background as boolean;
550
+
551
+ // Generate a unique agent ID
552
+ const agentId = resume || `${subagentType.toLowerCase()}-${Date.now().toString(36)}`;
553
+
554
+ try {
555
+ // Get API key from environment (check multiple env var names)
556
+ const apiKey = process.env.ANTHROPIC_API_KEY ||
557
+ process.env.CLAUDE_API_KEY ||
558
+ process.env.ANTHROPIC_AUTH_TOKEN ||
559
+ process.env.Z_AI_API_KEY || "";
560
+ if (!apiKey) {
561
+ return { content: "Error: No API key available for subagent. Set ANTHROPIC_API_KEY, CLAUDE_API_KEY, ANTHROPIC_AUTH_TOKEN, or Z_AI_API_KEY environment variable.", is_error: true };
562
+ }
563
+
564
+ // Map model names using centralized aliases
565
+ const fullModel = resolveModelAlias(model) || MODEL_ALIASES.haiku!;
566
+
567
+ // Find the CLI - check multiple locations
568
+ const possiblePaths = [
569
+ import.meta.dir + "/../../dist/cli.js", // Built CLI
570
+ import.meta.dir + "/../interfaces/ui/terminal/cli/index.ts", // Source CLI
571
+ process.cwd() + "/dist/cli.js", // Built CLI in cwd
572
+ process.cwd() + "/src/interfaces/ui/terminal/cli/index.ts", // Source CLI in cwd
573
+ ];
574
+
575
+ let cliPath: string | null = null;
576
+ for (const path of possiblePaths) {
577
+ try {
578
+ const file = Bun.file(path);
579
+ if (await file.exists()) {
580
+ cliPath = path;
581
+ break;
582
+ }
583
+ } catch {
584
+ // Continue to next path
585
+ }
586
+ }
587
+
588
+ if (!cliPath) {
589
+ return {
590
+ content: `Error: Could not find CLI. Tried:\n${possiblePaths.join("\n")}`,
591
+ is_error: true,
592
+ };
593
+ }
594
+
595
+ // Build command arguments
596
+ const cmdArgs = [
597
+ "run",
598
+ cliPath,
599
+ "-m", fullModel,
600
+ "-p", context.permissionMode,
601
+ "-q", prompt,
602
+ ];
603
+
604
+ if (runInBackground) {
605
+ // Spawn in background with proper env
606
+ const child = Bun.spawn(["bun", ...cmdArgs], {
607
+ cwd: context.workingDirectory,
608
+ detached: true,
609
+ stdio: ["ignore", "pipe", "pipe"],
610
+ env: {
611
+ ...process.env,
612
+ ANTHROPIC_API_KEY: apiKey,
613
+ },
614
+ });
615
+
616
+ // Don't wait for background tasks
617
+ child.unref();
618
+
619
+ return {
620
+ content: JSON.stringify({
621
+ agentId,
622
+ status: "running",
623
+ message: `Agent started in background. Use TaskOutput tool with task_id: "${agentId}" to check results.`,
624
+ description: description || "Background task",
625
+ }),
626
+ };
627
+ }
628
+
629
+ // Run synchronously with timeout
630
+ const result = Bun.spawnSync(["bun", ...cmdArgs], {
631
+ cwd: context.workingDirectory,
632
+ timeout: 300000, // 5 minutes max
633
+ maxBuffer: 1024 * 1024 * 10, // 10MB
634
+ env: {
635
+ ...process.env,
636
+ ANTHROPIC_API_KEY: apiKey,
637
+ },
638
+ });
639
+
640
+ const stdout = result.stdout?.toString() || "";
641
+ const stderr = result.stderr?.toString() || "";
642
+
643
+ if (result.exitCode !== 0) {
644
+ return {
645
+ content: `Agent failed with exit code ${result.exitCode}\n${stderr}\n${stdout}`.trim(),
646
+ is_error: true,
647
+ };
648
+ }
649
+
650
+ return {
651
+ content: JSON.stringify({
652
+ agentId,
653
+ status: "completed",
654
+ output: stdout,
655
+ description: description || "Task completed",
656
+ }),
657
+ };
658
+ } catch (error) {
659
+ const errorMessage = error instanceof Error ? error.message : String(error);
660
+ return { content: `Error running subagent: ${errorMessage}`, is_error: true };
661
+ }
662
+ },
663
+ };
664
+
665
+ // ============================================
666
+ // TASK OUTPUT TOOL
667
+ // ============================================
668
+
669
+ export const TaskOutputTool: ToolDefinition = {
670
+ name: "TaskOutput",
671
+ description: `Retrieves output from a running or completed task (background shell, agent, or remote session).
672
+
673
+ Takes a task_id parameter identifying the task.
674
+ Returns the task output along with status information.
675
+ Use block=true (default) to wait for task completion.
676
+ Use block=false for non-blocking check of current status.
677
+
678
+ Task IDs can be found using the /tasks command
679
+ Works with all task types: background shells, async agents, and remote sessions`,
680
+ input_schema: {
681
+ type: "object",
682
+ properties: {
683
+ task_id: {
684
+ type: "string",
685
+ description: "The task ID to get output from",
686
+ },
687
+ block: {
688
+ type: "boolean",
689
+ description: "Whether to wait for completion (default: true)",
690
+ default: true,
691
+ },
692
+ timeout: {
693
+ type: "number",
694
+ description: "Max wait time in ms (default: 30000, max: 600000)",
695
+ default: 30000,
696
+ minimum: 0,
697
+ maximum: 600000,
698
+ },
699
+ },
700
+ required: ["task_id"],
701
+ },
702
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
703
+ const taskId = args.task_id as string;
704
+ const block = (args.block as boolean) ?? true;
705
+ const timeout = (args.timeout as number) ?? 30000;
706
+
707
+ try {
708
+ // In a real implementation, this would check a task registry
709
+ // For now, we'll check for background task output files
710
+ const taskFile = `${context.workingDirectory}/.claude/tasks/${taskId}.json`;
711
+
712
+ const file = Bun.file(taskFile);
713
+ if (!(await file.exists())) {
714
+ return {
715
+ content: `Task not found: ${taskId}. Use /tasks to list available tasks.`,
716
+ is_error: true,
717
+ };
718
+ }
719
+
720
+ const taskData = await file.json() as {
721
+ status: string;
722
+ output?: string;
723
+ error?: string;
724
+ startTime: number;
725
+ endTime?: number;
726
+ };
727
+
728
+ if (block && taskData.status === "running") {
729
+ // Wait for task completion with timeout
730
+ const startTime = Date.now();
731
+ while (Date.now() - startTime < timeout) {
732
+ await new Promise((resolve) => setTimeout(resolve, 1000));
733
+ const updatedFile = Bun.file(taskFile);
734
+ if (await updatedFile.exists()) {
735
+ const updatedData = await updatedFile.json() as typeof taskData;
736
+ if (updatedData.status !== "running") {
737
+ return {
738
+ content: JSON.stringify({
739
+ task_id: taskId,
740
+ status: updatedData.status,
741
+ output: updatedData.output,
742
+ error: updatedData.error,
743
+ duration: updatedData.endTime
744
+ ? updatedData.endTime - updatedData.startTime
745
+ : null,
746
+ }, null, 2),
747
+ };
748
+ }
749
+ }
750
+ }
751
+ return {
752
+ content: JSON.stringify({
753
+ task_id: taskId,
754
+ status: "timeout",
755
+ message: `Task still running after ${timeout}ms`,
756
+ }, null, 2),
757
+ };
758
+ }
759
+
760
+ return {
761
+ content: JSON.stringify({
762
+ task_id: taskId,
763
+ status: taskData.status,
764
+ output: taskData.output,
765
+ error: taskData.error,
766
+ duration: taskData.endTime
767
+ ? taskData.endTime - taskData.startTime
768
+ : null,
769
+ }, null, 2),
770
+ };
771
+ } catch (error) {
772
+ const errorMessage = error instanceof Error ? error.message : String(error);
773
+ return { content: `Error getting task output: ${errorMessage}`, is_error: true };
774
+ }
775
+ },
776
+ };
777
+
778
+ // ============================================
779
+ // ASK USER QUESTION TOOL
780
+ // ============================================
781
+
782
+ export const AskUserQuestionTool: ToolDefinition = {
783
+ name: "AskUserQuestion",
784
+ description: `Use this tool when you need to ask the user questions during execution.
785
+
786
+ This allows you to:
787
+ 1. Gather user preferences or requirements
788
+ 2. Clarify ambiguous instructions
789
+ 3. Get decisions on implementation choices
790
+ 4. Offer choices to the user about what direction to take
791
+
792
+ Plan mode note: In plan mode, use this tool to clarify requirements or choose between approaches BEFORE finalizing your plan. Do NOT use this tool if your plan is ready - that's what ExitPlanMode is for.
793
+
794
+ The options array should have 2-4 options. Each option should be a distinct, mutually exclusive choice.
795
+ The preview feature allows showing markdown content in a side-by-side layout.
796
+
797
+ User can always select "Other" to provide custom text input.`,
798
+ input_schema: {
799
+ type: "object",
800
+ properties: {
801
+ questions: {
802
+ type: "array",
803
+ description: "Questions to ask the user (1-4 questions)",
804
+ items: {
805
+ type: "object",
806
+ properties: {
807
+ question: {
808
+ type: "string",
809
+ description: "The complete question to ask the user",
810
+ },
811
+ header: {
812
+ type: "string",
813
+ description: "Very short label displayed as a chip/tag (max 12 chars)",
814
+ },
815
+ options: {
816
+ type: "array",
817
+ description: "The available choices (2-4 options)",
818
+ items: {
819
+ type: "object",
820
+ properties: {
821
+ label: {
822
+ type: "string",
823
+ description: "The display text for this option (5 words max)",
824
+ },
825
+ description: {
826
+ type: "string",
827
+ description: "Explanation of what this option means",
828
+ },
829
+ markdown: {
830
+ type: "string",
831
+ description: "Optional preview content shown in a monospace box",
832
+ },
833
+ },
834
+ required: ["label", "description"],
835
+ },
836
+ minItems: 2,
837
+ maxItems: 4,
838
+ },
839
+ multiSelect: {
840
+ type: "boolean",
841
+ description: "Allow selecting multiple options (default: false)",
842
+ default: false,
843
+ },
844
+ },
845
+ required: ["question", "header", "options"],
846
+ },
847
+ minItems: 1,
848
+ maxItems: 4,
849
+ },
850
+ },
851
+ required: ["questions"],
852
+ },
853
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
854
+ const questions = args.questions as Array<{
855
+ question: string;
856
+ header: string;
857
+ options: Array<{ label: string; description: string; markdown?: string }>;
858
+ multiSelect?: boolean;
859
+ }>;
860
+
861
+ try {
862
+ // Format questions for display
863
+ const formattedQuestions = questions.map((q, i) => {
864
+ const options = q.options
865
+ .map((opt, j) => {
866
+ let optionText = ` ${j + 1}. ${opt.label}`;
867
+ if (opt.description) {
868
+ optionText += ` - ${opt.description}`;
869
+ }
870
+ return optionText;
871
+ })
872
+ .join("\n");
873
+
874
+ return `## Question ${i + 1}: [${q.header}]\n${q.question}\n\nOptions:\n${options}${q.multiSelect ? "\n(multi-select enabled)" : ""}`;
875
+ }).join("\n\n---\n\n");
876
+
877
+ // In interactive mode, this would prompt the user
878
+ // For now, return the formatted questions
879
+ return {
880
+ content: JSON.stringify({
881
+ type: "user_question",
882
+ questions: questions,
883
+ formatted: formattedQuestions,
884
+ message: "Questions prepared for user response",
885
+ }, null, 2),
886
+ };
887
+ } catch (error) {
888
+ const errorMessage = error instanceof Error ? error.message : String(error);
889
+ return { content: `Error preparing questions: ${errorMessage}`, is_error: true };
890
+ }
891
+ },
892
+ };
893
+
894
+ // ============================================
895
+ // ENTER PLAN MODE TOOL
896
+ // ============================================
897
+
898
+ export const EnterPlanModeTool: ToolDefinition = {
899
+ name: "EnterPlanMode",
900
+ description: `Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
901
+
902
+ How This Tool Works:
903
+ - You should have already written your plan to the plan file specified in the plan mode system message
904
+ - This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote
905
+ - This tool simply signals that you're done planning and ready for the user to review and approve
906
+
907
+ When to Use This Tool:
908
+ IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code. For research tasks where you're gathering information, searching files, reading files or in general trying to understand the codebase - do NOT use this tool.
909
+
910
+ Plan mode note: In plan mode, use this tool to clarify requirements or choose between approaches BEFORE finalizing your plan. Do NOT use this tool if your plan is ready - that's what ExitPlanMode is for.
911
+
912
+ Examples:
913
+ - "Search for and understand the implementation of vim mode" - Do NOT use this tool
914
+ - "Help me implement yank mode for vim" - Use EnterPlanMode
915
+
916
+ Important notes:
917
+ - NEVER run additional commands to read or explore code, besides git bash commands
918
+ - NEVER use the TodoWrite or Task tools
919
+ - DO NOT commit files that likely contain secrets (.env, credentials.json, etc.)
920
+ - If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting`,
921
+ input_schema: {
922
+ type: "object",
923
+ properties: {
924
+ allowedPrompts: {
925
+ type: "array",
926
+ description: "Prompt-based permissions needed to implement the plan",
927
+ items: {
928
+ type: "object",
929
+ properties: {
930
+ tool: {
931
+ type: "string",
932
+ description: "The tool this prompt applies to (e.g., 'Bash')",
933
+ },
934
+ prompt: {
935
+ type: "string",
936
+ description: "Semantic description of the action (e.g., 'run tests', 'install dependencies')",
937
+ },
938
+ },
939
+ required: ["tool", "prompt"],
940
+ },
941
+ },
942
+ },
943
+ required: [],
944
+ },
945
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
946
+ const allowedPrompts = args.allowedPrompts as Array<{ tool: string; prompt: string }> | undefined;
947
+
948
+ try {
949
+ // Read the plan file
950
+ const planFile = `${context.workingDirectory}/.claude/plan.md`;
951
+ const file = Bun.file(planFile);
952
+
953
+ if (!(await file.exists())) {
954
+ return {
955
+ content: "Error: No plan file found. Please write your plan to .claude/plan.md first.",
956
+ is_error: true,
957
+ };
958
+ }
959
+
960
+ const planContent = await file.text();
961
+
962
+ return {
963
+ content: JSON.stringify({
964
+ type: "plan_ready",
965
+ planFile: planFile,
966
+ planLength: planContent.length,
967
+ allowedPrompts: allowedPrompts || [],
968
+ message: "Plan is ready for user review. ExitPlanMode will request user approval.",
969
+ }, null, 2),
970
+ };
971
+ } catch (error) {
972
+ const errorMessage = error instanceof Error ? error.message : String(error);
973
+ return { content: `Error entering plan mode: ${errorMessage}`, is_error: true };
974
+ }
975
+ },
976
+ };
977
+
978
+ // ============================================
979
+ // EXIT PLAN MODE TOOL
980
+ // ============================================
981
+
982
+ export const ExitPlanModeTool: ToolDefinition = {
983
+ name: "ExitPlanMode",
984
+ description: `Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
985
+
986
+ This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote.
987
+ This tool simply signals that you're done planning and ready for the user to review and approve.
988
+
989
+ IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code.
990
+ ExitPlanMode inherently requests user approval of the plan.`,
991
+ input_schema: {
992
+ type: "object",
993
+ properties: {
994
+ allowedPrompts: {
995
+ type: "array",
996
+ description: "Prompt-based permissions needed to implement the plan",
997
+ items: {
998
+ type: "object",
999
+ properties: {
1000
+ tool: {
1001
+ type: "string",
1002
+ description: "The tool this prompt applies to",
1003
+ },
1004
+ prompt: {
1005
+ type: "string",
1006
+ description: "Semantic description of the action",
1007
+ },
1008
+ },
1009
+ required: ["tool", "prompt"],
1010
+ },
1011
+ },
1012
+ },
1013
+ required: [],
1014
+ },
1015
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1016
+ const allowedPrompts = args.allowedPrompts as Array<{ tool: string; prompt: string }> | undefined;
1017
+
1018
+ try {
1019
+ // Read the plan file
1020
+ const planFile = `${context.workingDirectory}/.claude/plan.md`;
1021
+ const file = Bun.file(planFile);
1022
+
1023
+ if (!(await file.exists())) {
1024
+ return {
1025
+ content: "Error: No plan file found at .claude/plan.md",
1026
+ is_error: true,
1027
+ };
1028
+ }
1029
+
1030
+ const planContent = await file.text();
1031
+
1032
+ return {
1033
+ content: JSON.stringify({
1034
+ type: "exit_plan_mode",
1035
+ status: "awaiting_approval",
1036
+ planFile: planFile,
1037
+ planPreview: planContent.slice(0, 500) + (planContent.length > 500 ? "..." : ""),
1038
+ allowedPrompts: allowedPrompts || [],
1039
+ message: "Plan submitted for user approval.",
1040
+ }, null, 2),
1041
+ };
1042
+ } catch (error) {
1043
+ const errorMessage = error instanceof Error ? error.message : String(error);
1044
+ return { content: `Error exiting plan mode: ${errorMessage}`, is_error: true };
1045
+ }
1046
+ },
1047
+ };
1048
+
1049
+ // ============================================
1050
+ // SKILL TOOL
1051
+ // ============================================
1052
+
1053
+ export const SkillTool: ToolDefinition = {
1054
+ name: "Skill",
1055
+ description: `Execute a skill within the main conversation.
1056
+
1057
+ When users ask you to perform tasks, check if any of the available skills match. Skills provide specialized capabilities and domain knowledge.
1058
+
1059
+ When users reference a "slash command" or "/<something>" (e.g., "/commit", "/review-pr"), they are referring to a skill. Use this tool to invoke it.
1060
+
1061
+ How to invoke:
1062
+ - Use this tool with the skill name and optional arguments
1063
+ - Examples:
1064
+ - skill: "commit" - invoke the commit skill
1065
+ - skill: "review-pr", args: "123" - invoke with arguments
1066
+ - Use fully qualified name for namespaced skills: skill: "ms-office-suite:pdf"
1067
+
1068
+ Available skills are listed in system-reminder messages in the conversation.
1069
+ When a skill matches the user's request, this is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task.
1070
+
1071
+ Important:
1072
+ - NEVER mention a skill without actually calling this tool
1073
+ - Do not invoke a skill that is already running
1074
+ - Do not use this tool for built-in CLI commands (like /help, /clear)`,
1075
+ input_schema: {
1076
+ type: "object",
1077
+ properties: {
1078
+ skill: {
1079
+ type: "string",
1080
+ description: "The skill name (e.g., 'commit', 'review-pr', or fully qualified 'namespace:skill')",
1081
+ },
1082
+ args: {
1083
+ type: "string",
1084
+ description: "Optional arguments for the skill",
1085
+ },
1086
+ },
1087
+ required: ["skill"],
1088
+ },
1089
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1090
+ const skillName = args.skill as string;
1091
+ const skillArgs = args.args as string | undefined;
1092
+
1093
+ try {
1094
+ // Load available skills
1095
+ const skillsDir = `${context.workingDirectory}/.claude/skills`;
1096
+ const globalSkillsDir = `${process.env.HOME || ""}/.claude/skills`;
1097
+
1098
+ // Check for skill file in multiple locations
1099
+ const possiblePaths = [
1100
+ `${skillsDir}/${skillName}.md`,
1101
+ `${skillsDir}/${skillName}/skill.md`,
1102
+ `${globalSkillsDir}/${skillName}.md`,
1103
+ `${globalSkillsDir}/${skillName}/skill.md`,
1104
+ ];
1105
+
1106
+ let skillFile: string | null = null;
1107
+ for (const path of possiblePaths) {
1108
+ const file = Bun.file(path);
1109
+ if (await file.exists()) {
1110
+ skillFile = path;
1111
+ break;
1112
+ }
1113
+ }
1114
+
1115
+ if (!skillFile) {
1116
+ return {
1117
+ content: `Skill not found: ${skillName}. Available skills can be listed with /help.`,
1118
+ is_error: true,
1119
+ };
1120
+ }
1121
+
1122
+ // Read and return the skill content
1123
+ const file = Bun.file(skillFile);
1124
+ const skillContent = await file.text();
1125
+
1126
+ return {
1127
+ content: JSON.stringify({
1128
+ type: "skill_invocation",
1129
+ skill: skillName,
1130
+ args: skillArgs,
1131
+ skillFile: skillFile,
1132
+ content: skillContent,
1133
+ message: `Skill "${skillName}" loaded. Follow the instructions in the skill content.`,
1134
+ }, null, 2),
1135
+ };
1136
+ } catch (error) {
1137
+ const errorMessage = error instanceof Error ? error.message : String(error);
1138
+ return { content: `Error invoking skill: ${errorMessage}`, is_error: true };
1139
+ }
1140
+ },
1141
+ };
1142
+
1143
+ // ============================================
1144
+ // TASK STOP TOOL
1145
+ // ============================================
1146
+
1147
+ export const TaskStopTool: ToolDefinition = {
1148
+ name: "TaskStop",
1149
+ description: `Stops a running background task by its ID.
1150
+ Takes a task_id parameter identifying the task to stop.
1151
+ Returns a success or failure status.
1152
+ Use this tool to terminate a long-running task.`,
1153
+ input_schema: {
1154
+ type: "object",
1155
+ properties: {
1156
+ task_id: {
1157
+ type: "string",
1158
+ description: "The ID of the background task to stop",
1159
+ },
1160
+ shell_id: {
1161
+ type: "string",
1162
+ description: "Deprecated: use task_id instead",
1163
+ },
1164
+ },
1165
+ required: ["task_id"],
1166
+ },
1167
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1168
+ const taskId = args.task_id as string;
1169
+
1170
+ try {
1171
+ const taskFile = `${context.workingDirectory}/.claude/tasks/${taskId}.json`;
1172
+ const file = Bun.file(taskFile);
1173
+
1174
+ if (!(await file.exists())) {
1175
+ return {
1176
+ content: `Task not found: ${taskId}`,
1177
+ is_error: true,
1178
+ };
1179
+ }
1180
+
1181
+ const taskData = await file.json() as {
1182
+ status: string;
1183
+ pid?: number;
1184
+ };
1185
+
1186
+ if (taskData.status !== "running") {
1187
+ return {
1188
+ content: JSON.stringify({
1189
+ task_id: taskId,
1190
+ status: taskData.status,
1191
+ message: `Task is already ${taskData.status}`,
1192
+ }, null, 2),
1193
+ };
1194
+ }
1195
+
1196
+ // In a real implementation, this would kill the process
1197
+ // For now, just update the status
1198
+ taskData.status = "stopped";
1199
+ await Bun.write(taskFile, JSON.stringify(taskData, null, 2));
1200
+
1201
+ return {
1202
+ content: JSON.stringify({
1203
+ task_id: taskId,
1204
+ status: "stopped",
1205
+ message: "Task stopped successfully",
1206
+ }, null, 2),
1207
+ };
1208
+ } catch (error) {
1209
+ const errorMessage = error instanceof Error ? error.message : String(error);
1210
+ return { content: `Error stopping task: ${errorMessage}`, is_error: true };
1211
+ }
1212
+ },
1213
+ };
1214
+
1215
+ // ============================================
1216
+ // MULTI-EDIT TOOL (Atomic Multi-File Editing)
1217
+ // ============================================
1218
+
1219
+ export const MultiEditTool: ToolDefinition = {
1220
+ name: "MultiEdit",
1221
+ description: `Performs atomic multi-file editing with rollback on failure.
1222
+
1223
+ This tool allows you to edit multiple files simultaneously in a single atomic operation.
1224
+ If any edit fails, all changes are automatically rolled back to maintain consistency.
1225
+
1226
+ Key features:
1227
+ - Validates all edits before applying (files exist, strings found)
1228
+ - Creates automatic backups before editing
1229
+ - Applies all edits atomically (all-or-nothing)
1230
+ - Rolls back on any failure
1231
+
1232
+ Use this when you need to make coordinated changes across multiple files and want
1233
+ to ensure either all changes succeed or none are applied.
1234
+
1235
+ IMPORTANT: You MUST read the files first before using this tool. Only edit files you have already read.`,
1236
+ input_schema: {
1237
+ type: "object",
1238
+ properties: {
1239
+ edits: {
1240
+ type: "array",
1241
+ description: "Array of edit operations to apply atomically",
1242
+ items: {
1243
+ type: "object",
1244
+ properties: {
1245
+ file_path: {
1246
+ type: "string",
1247
+ description: "The absolute path to the file to edit",
1248
+ },
1249
+ old_string: {
1250
+ type: "string",
1251
+ description: "The text to find and replace",
1252
+ },
1253
+ new_string: {
1254
+ type: "string",
1255
+ description: "The text to replace it with",
1256
+ },
1257
+ replace_all: {
1258
+ type: "boolean",
1259
+ description: "Replace all occurrences (default: false)",
1260
+ },
1261
+ },
1262
+ required: ["file_path", "old_string", "new_string"],
1263
+ },
1264
+ },
1265
+ dry_run: {
1266
+ type: "boolean",
1267
+ description: "Preview changes without applying them (default: false)",
1268
+ },
1269
+ },
1270
+ required: ["edits"],
1271
+ },
1272
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1273
+ const rawEdits = args.edits as Array<{
1274
+ file_path?: string;
1275
+ old_string?: string;
1276
+ new_string?: string;
1277
+ replace_all?: boolean;
1278
+ }>;
1279
+ const dryRun = (args.dry_run as boolean) || false;
1280
+
1281
+ try {
1282
+ // Validate inputs
1283
+ if (!Array.isArray(rawEdits) || rawEdits.length === 0) {
1284
+ return { content: "Error: edits must be a non-empty array", is_error: true };
1285
+ }
1286
+
1287
+ // Validate each edit has required fields and convert to native format
1288
+ const edits: MultiEditEntry[] = [];
1289
+ for (let i = 0; i < rawEdits.length; i++) {
1290
+ const rawEdit = rawEdits[i];
1291
+ if (!rawEdit || !rawEdit.file_path || !rawEdit.old_string || rawEdit.new_string === undefined) {
1292
+ return {
1293
+ content: `Error: Edit at index ${i} is missing required fields (file_path, old_string, new_string)`,
1294
+ is_error: true,
1295
+ };
1296
+ }
1297
+ edits.push({
1298
+ filePath: rawEdit.file_path,
1299
+ oldString: rawEdit.old_string,
1300
+ newString: rawEdit.new_string,
1301
+ replaceAll: rawEdit.replace_all || false,
1302
+ });
1303
+ }
1304
+
1305
+ // If dry run, just preview
1306
+ if (dryRun) {
1307
+ const preview = preview_multi_edits(edits);
1308
+ const errors = validate_multi_edits(edits);
1309
+
1310
+ if (errors.length > 0) {
1311
+ return {
1312
+ content: JSON.stringify({
1313
+ valid: false,
1314
+ errors,
1315
+ preview: preview,
1316
+ }, null, 2),
1317
+ is_error: true,
1318
+ };
1319
+ }
1320
+
1321
+ return {
1322
+ content: JSON.stringify({
1323
+ valid: true,
1324
+ preview: preview,
1325
+ total_files: preview.length,
1326
+ total_replacements: preview.reduce((sum, p) => sum + p.replacementCount, 0),
1327
+ message: "Dry run successful - no changes applied",
1328
+ }, null, 2),
1329
+ };
1330
+ }
1331
+
1332
+ // Validate all edits first
1333
+ const errors = validate_multi_edits(edits);
1334
+ if (errors.length > 0) {
1335
+ return {
1336
+ content: `Validation failed:\n${errors.join("\n")}`,
1337
+ is_error: true,
1338
+ };
1339
+ }
1340
+
1341
+ // Apply edits atomically
1342
+ const result = apply_multi_edits(edits);
1343
+
1344
+ if (result.success) {
1345
+ return {
1346
+ content: JSON.stringify({
1347
+ success: true,
1348
+ files_modified: result.filesModified,
1349
+ total_replacements: result.totalReplacements,
1350
+ message: `Successfully applied ${result.totalReplacements} replacement(s) across ${result.filesModified.length} file(s)`,
1351
+ }, null, 2),
1352
+ };
1353
+ } else {
1354
+ return {
1355
+ content: JSON.stringify({
1356
+ success: false,
1357
+ error: result.error,
1358
+ rolled_back: result.rolledBack,
1359
+ files_modified: result.filesModified,
1360
+ }, null, 2),
1361
+ is_error: true,
1362
+ };
1363
+ }
1364
+ } catch (error) {
1365
+ const errorMessage = error instanceof Error ? error.message : String(error);
1366
+ return { content: `Error applying multi-edit: ${errorMessage}`, is_error: true };
1367
+ }
1368
+ },
1369
+ };
1370
+
1371
+ // ============================================
1372
+ // NOTEBOOK EDIT TOOL
1373
+ // ============================================
1374
+
1375
+ export const NotebookEditTool: ToolDefinition = {
1376
+ name: "NotebookEdit",
1377
+ description: `Completely replaces the contents of a specific cell in a Jupyter notebook (.ipynb file) with new source.
1378
+
1379
+ Jupyter notebooks are interactive documents that combine code, text, and visualizations. The notebook_path parameter must be an absolute path, not a relative path. The cell_number is 0-indexed. Use edit_mode=insert to add a new cell at the index specified by cell_number. Use edit_mode=delete to delete the cell at the index specified by cell_number.`,
1380
+ input_schema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ notebook_path: {
1384
+ type: "string",
1385
+ description: "The absolute path to the Jupyter notebook file to edit",
1386
+ },
1387
+ cell_id: {
1388
+ type: "string",
1389
+ description: "The ID of the cell to edit (optional, alternative to cell_number)",
1390
+ },
1391
+ cell_number: {
1392
+ type: "number",
1393
+ description: "The index of the cell to edit (0-indexed)",
1394
+ },
1395
+ new_source: {
1396
+ type: "string",
1397
+ description: "The new source for the cell",
1398
+ },
1399
+ cell_type: {
1400
+ type: "string",
1401
+ enum: ["code", "markdown"],
1402
+ description: "The type of the cell (code or markdown). Defaults to code.",
1403
+ },
1404
+ edit_mode: {
1405
+ type: "string",
1406
+ enum: ["replace", "insert", "delete"],
1407
+ description: "The type of edit to perform (replace, insert, delete)",
1408
+ },
1409
+ },
1410
+ required: ["notebook_path"],
1411
+ },
1412
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1413
+ const notebookPath = args.notebook_path as string;
1414
+ const cellId = args.cell_id as string | undefined;
1415
+ const cellNumber = args.cell_number as number | undefined;
1416
+ const newSource = args.new_source as string | undefined;
1417
+ const cellType = (args.cell_type as string) || "code";
1418
+ const editMode = (args.edit_mode as string) || "replace";
1419
+
1420
+ try {
1421
+ // Read the notebook
1422
+ const file = Bun.file(notebookPath);
1423
+ if (!await file.exists()) {
1424
+ return { content: `Error: Notebook not found: ${notebookPath}`, is_error: true };
1425
+ }
1426
+
1427
+ const notebook = await file.json() as {
1428
+ cells: Array<{
1429
+ id?: string;
1430
+ cell_type: string;
1431
+ source: string | string[];
1432
+ outputs?: unknown[];
1433
+ metadata?: Record<string, unknown>;
1434
+ execution_count?: number | null;
1435
+ }>;
1436
+ metadata: Record<string, unknown>;
1437
+ nbformat: number;
1438
+ nbformat_minor: number;
1439
+ };
1440
+
1441
+ // Validate notebook structure
1442
+ if (!notebook.cells || !Array.isArray(notebook.cells)) {
1443
+ return { content: "Error: Invalid notebook format - no cells array", is_error: true };
1444
+ }
1445
+
1446
+ // Find the cell to edit
1447
+ let targetIndex: number;
1448
+
1449
+ if (cellId) {
1450
+ // Find by cell ID
1451
+ targetIndex = notebook.cells.findIndex(c => c.id === cellId);
1452
+ if (targetIndex === -1) {
1453
+ return { content: `Error: Cell with ID "${cellId}" not found`, is_error: true };
1454
+ }
1455
+ } else if (cellNumber !== undefined) {
1456
+ targetIndex = cellNumber;
1457
+ if (targetIndex < 0 || targetIndex >= notebook.cells.length) {
1458
+ if (editMode === "insert") {
1459
+ // Allow inserting at the end
1460
+ targetIndex = notebook.cells.length;
1461
+ } else {
1462
+ return { content: `Error: Cell number ${targetIndex} out of range (0-${notebook.cells.length - 1})`, is_error: true };
1463
+ }
1464
+ }
1465
+ } else if (editMode !== "insert") {
1466
+ return { content: "Error: Must specify either cell_id or cell_number", is_error: true };
1467
+ } else {
1468
+ targetIndex = notebook.cells.length;
1469
+ }
1470
+
1471
+ // Perform the edit
1472
+ switch (editMode) {
1473
+ case "delete": {
1474
+ notebook.cells.splice(targetIndex, 1);
1475
+ break;
1476
+ }
1477
+
1478
+ case "insert": {
1479
+ const newCell = {
1480
+ id: `cell-${Date.now()}`,
1481
+ cell_type: cellType,
1482
+ source: newSource || "",
1483
+ metadata: {},
1484
+ ...(cellType === "code" ? { outputs: [] as unknown[], execution_count: null } : {}),
1485
+ };
1486
+ notebook.cells.splice(targetIndex, 0, newCell);
1487
+ break;
1488
+ }
1489
+
1490
+ case "replace":
1491
+ default: {
1492
+ if (newSource === undefined) {
1493
+ return { content: "Error: new_source is required for replace mode", is_error: true };
1494
+ }
1495
+ const existingCell = notebook.cells[targetIndex];
1496
+ if (!existingCell) {
1497
+ return { content: `Error: Cell at index ${targetIndex} not found`, is_error: true };
1498
+ }
1499
+ notebook.cells[targetIndex] = {
1500
+ ...existingCell,
1501
+ source: newSource,
1502
+ cell_type: cellType,
1503
+ ...(cellType === "code" ? { execution_count: null } : {}),
1504
+ };
1505
+ break;
1506
+ }
1507
+ }
1508
+
1509
+ // Write the notebook back
1510
+ await Bun.write(notebookPath, JSON.stringify(notebook, null, 1));
1511
+
1512
+ return {
1513
+ content: JSON.stringify({
1514
+ success: true,
1515
+ message: `Successfully ${editMode}d cell in ${notebookPath}`,
1516
+ cellCount: notebook.cells.length,
1517
+ }),
1518
+ };
1519
+ } catch (error) {
1520
+ const errorMessage = error instanceof Error ? error.message : String(error);
1521
+ return { content: `Error editing notebook: ${errorMessage}`, is_error: true };
1522
+ }
1523
+ },
1524
+ };
1525
+
1526
+ // ============================================
1527
+ // ALL BUILT-IN TOOLS
1528
+ // ============================================
1529
+
1530
+ // ============================================
1531
+ // TEMPGlmVision TOOL (Simple Vision via GLM)
1532
+ // ============================================
1533
+
1534
+ export const TempGlmVisionTool: ToolDefinition = {
1535
+ name: "tempglmvision",
1536
+ description: `Analyze images using GLM-4.6V vision model. Use this tool when you need to analyze, describe, or extract information from images. Supports PNG, JPG, JPEG, GIF, and WEBP formats. Accepts both local file paths and remote URLs.`,
1537
+ input_schema: {
1538
+ type: "object",
1539
+ properties: {
1540
+ imageSource: {
1541
+ type: "string",
1542
+ description: "Local file path or remote URL to the image (supports PNG, JPG, JPEG, GIF, WEBP)",
1543
+ },
1544
+ prompt: {
1545
+ type: "string",
1546
+ description: "Detailed text prompt describing what to analyze, extract, or understand from the image.",
1547
+ },
1548
+ },
1549
+ required: ["imageSource", "prompt"],
1550
+ },
1551
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1552
+ const imageSource = args.imageSource as string;
1553
+ const prompt = args.prompt as string;
1554
+
1555
+ try {
1556
+ // Get API credentials from environment
1557
+ const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
1558
+ if (!apiKey) {
1559
+ return {
1560
+ content: "Error: No API key found. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.",
1561
+ is_error: true,
1562
+ };
1563
+ }
1564
+
1565
+ const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
1566
+
1567
+ // Read and encode the image
1568
+ let imageData: string;
1569
+ let mediaType: string;
1570
+
1571
+ if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
1572
+ // Fetch remote image
1573
+ const response = await fetch(imageSource);
1574
+ if (!response.ok) {
1575
+ return {
1576
+ content: `Error fetching image: ${response.status} ${response.statusText}`,
1577
+ is_error: true,
1578
+ };
1579
+ }
1580
+ const buffer = Buffer.from(await response.arrayBuffer());
1581
+ imageData = buffer.toString("base64");
1582
+
1583
+ // Detect media type from content-type header or extension
1584
+ const contentType = response.headers.get("content-type");
1585
+ if (contentType?.includes("image/png")) {
1586
+ mediaType = "image/png";
1587
+ } else if (contentType?.includes("image/gif")) {
1588
+ mediaType = "image/gif";
1589
+ } else if (contentType?.includes("image/webp")) {
1590
+ mediaType = "image/webp";
1591
+ } else {
1592
+ mediaType = "image/jpeg"; // Default to JPEG
1593
+ }
1594
+ } else {
1595
+ // Local file path
1596
+ const file = Bun.file(imageSource);
1597
+ if (!(await file.exists())) {
1598
+ return {
1599
+ content: `Error: Image file not found: ${imageSource}`,
1600
+ is_error: true,
1601
+ };
1602
+ }
1603
+ const buffer = Buffer.from(await file.arrayBuffer());
1604
+ imageData = buffer.toString("base64");
1605
+
1606
+ // Detect media type from extension
1607
+ const ext = path.extname(imageSource).toLowerCase();
1608
+ if (ext === ".png") {
1609
+ mediaType = "image/png";
1610
+ } else if (ext === ".gif") {
1611
+ mediaType = "image/gif";
1612
+ } else if (ext === ".webp") {
1613
+ mediaType = "image/webp";
1614
+ } else {
1615
+ mediaType = "image/jpeg";
1616
+ }
1617
+ }
1618
+
1619
+ // Build request for vision model
1620
+ // Use GLM-5 for vision (supportsVision: true in models.ts)
1621
+ const request = {
1622
+ model: "glm-5",
1623
+ max_tokens: 4096,
1624
+ messages: [
1625
+ {
1626
+ role: "user",
1627
+ content: [
1628
+ {
1629
+ type: "image",
1630
+ source: {
1631
+ type: "base64",
1632
+ media_type: mediaType,
1633
+ data: imageData,
1634
+ },
1635
+ },
1636
+ {
1637
+ type: "text",
1638
+ text: prompt,
1639
+ },
1640
+ ],
1641
+ },
1642
+ ],
1643
+ };
1644
+
1645
+ // Make API request
1646
+ const response = await fetch(`${baseUrl}/v1/messages`, {
1647
+ method: "POST",
1648
+ headers: {
1649
+ "Content-Type": "application/json",
1650
+ "x-api-key": apiKey,
1651
+ "anthropic-version": "2023-06-01",
1652
+ },
1653
+ body: JSON.stringify(request),
1654
+ signal: context.abortSignal,
1655
+ });
1656
+
1657
+ if (!response.ok) {
1658
+ const errorText = await response.text();
1659
+ return {
1660
+ content: `Vision API error: ${response.status} - ${errorText}`,
1661
+ is_error: true,
1662
+ };
1663
+ }
1664
+
1665
+ const result = (await response.json()) as {
1666
+ content?: Array<{ type: string; text?: string }>;
1667
+ error?: { message: string };
1668
+ };
1669
+
1670
+ if (result.error) {
1671
+ return {
1672
+ content: `Vision API error: ${result.error.message}`,
1673
+ is_error: true,
1674
+ };
1675
+ }
1676
+
1677
+ // Extract text from response
1678
+ const textContent = result.content
1679
+ ?.filter((block) => block.type === "text")
1680
+ .map((block) => block.text)
1681
+ .join("\n");
1682
+
1683
+ return {
1684
+ content: textContent || "No text content in vision response",
1685
+ };
1686
+ } catch (error) {
1687
+ const errorMessage = error instanceof Error ? error.message : String(error);
1688
+ return { content: `Error analyzing image: ${errorMessage}`, is_error: true };
1689
+ }
1690
+ },
1691
+ };
1692
+
1693
+ // ============================================
1694
+ // ANALYZE IMAGE TOOL (Vision via GLM)
1695
+ // ============================================
1696
+
1697
+ export const AnalyzeImageTool: ToolDefinition = {
1698
+ name: "mcp__4_5v_mcp__analyze_image",
1699
+ description: `Analyze an image using advanced AI vision models with comprehensive understanding capabilities. Supports PNG, JPG, JPEG, GIF, and WEBP formats. Accepts both local file paths and remote URLs.`,
1700
+ input_schema: {
1701
+ type: "object",
1702
+ properties: {
1703
+ imageSource: {
1704
+ type: "string",
1705
+ description: "Local file path or remote URL to the image (supports PNG, JPG, JPEG, GIF, WEBP)",
1706
+ },
1707
+ prompt: {
1708
+ type: "string",
1709
+ description: "Detailed text prompt describing what to analyze, extract, or understand from the image. For front-end code replication, describe layout structure, color style, main components, and interactive elements.",
1710
+ },
1711
+ },
1712
+ required: ["imageSource", "prompt"],
1713
+ },
1714
+ handler: async (args, context: ToolContext): Promise<ToolResult> => {
1715
+ const imageSource = args.imageSource as string;
1716
+ const prompt = args.prompt as string;
1717
+
1718
+ try {
1719
+ // Get API credentials from environment
1720
+ const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
1721
+ if (!apiKey) {
1722
+ return {
1723
+ content: "Error: No API key found. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.",
1724
+ is_error: true,
1725
+ };
1726
+ }
1727
+
1728
+ const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
1729
+
1730
+ // Read and encode the image
1731
+ let imageData: string;
1732
+ let mediaType: string;
1733
+
1734
+ if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
1735
+ // Fetch remote image
1736
+ const response = await fetch(imageSource);
1737
+ if (!response.ok) {
1738
+ return {
1739
+ content: `Error fetching image: ${response.status} ${response.statusText}`,
1740
+ is_error: true,
1741
+ };
1742
+ }
1743
+ const buffer = Buffer.from(await response.arrayBuffer());
1744
+ imageData = buffer.toString("base64");
1745
+
1746
+ // Detect media type from content-type header or extension
1747
+ const contentType = response.headers.get("content-type");
1748
+ if (contentType?.includes("image/png")) {
1749
+ mediaType = "image/png";
1750
+ } else if (contentType?.includes("image/gif")) {
1751
+ mediaType = "image/gif";
1752
+ } else if (contentType?.includes("image/webp")) {
1753
+ mediaType = "image/webp";
1754
+ } else {
1755
+ mediaType = "image/jpeg"; // Default to JPEG
1756
+ }
1757
+ } else {
1758
+ // Local file path
1759
+ const file = Bun.file(imageSource);
1760
+ if (!(await file.exists())) {
1761
+ return {
1762
+ content: `Error: Image file not found: ${imageSource}`,
1763
+ is_error: true,
1764
+ };
1765
+ }
1766
+ const buffer = Buffer.from(await file.arrayBuffer());
1767
+ imageData = buffer.toString("base64");
1768
+
1769
+ // Detect media type from extension
1770
+ const ext = path.extname(imageSource).toLowerCase();
1771
+ if (ext === ".png") {
1772
+ mediaType = "image/png";
1773
+ } else if (ext === ".gif") {
1774
+ mediaType = "image/gif";
1775
+ } else if (ext === ".webp") {
1776
+ mediaType = "image/webp";
1777
+ } else {
1778
+ mediaType = "image/jpeg";
1779
+ }
1780
+ }
1781
+
1782
+ // Build request for vision model
1783
+ const request = {
1784
+ model: "glm-5", // Vision-capable model
1785
+ max_tokens: 4096,
1786
+ messages: [
1787
+ {
1788
+ role: "user",
1789
+ content: [
1790
+ {
1791
+ type: "image",
1792
+ source: {
1793
+ type: "base64",
1794
+ media_type: mediaType,
1795
+ data: imageData,
1796
+ },
1797
+ },
1798
+ {
1799
+ type: "text",
1800
+ text: prompt,
1801
+ },
1802
+ ],
1803
+ },
1804
+ ],
1805
+ };
1806
+
1807
+ // Make API request
1808
+ const response = await fetch(`${baseUrl}/v1/messages`, {
1809
+ method: "POST",
1810
+ headers: {
1811
+ "Content-Type": "application/json",
1812
+ "x-api-key": apiKey,
1813
+ "anthropic-version": "2023-06-01",
1814
+ },
1815
+ body: JSON.stringify(request),
1816
+ signal: context.abortSignal,
1817
+ });
1818
+
1819
+ if (!response.ok) {
1820
+ const errorText = await response.text();
1821
+ return {
1822
+ content: `Vision API error: ${response.status} - ${errorText}`,
1823
+ is_error: true,
1824
+ };
1825
+ }
1826
+
1827
+ const result = (await response.json()) as {
1828
+ content?: Array<{ type: string; text?: string }>;
1829
+ error?: { message: string };
1830
+ };
1831
+
1832
+ if (result.error) {
1833
+ return {
1834
+ content: `Vision API error: ${result.error.message}`,
1835
+ is_error: true,
1836
+ };
1837
+ }
1838
+
1839
+ // Extract text from response
1840
+ const textContent = result.content
1841
+ ?.filter((block) => block.type === "text")
1842
+ .map((block) => block.text)
1843
+ .join("\n");
1844
+
1845
+ return {
1846
+ content: textContent || "No text content in vision response",
1847
+ };
1848
+ } catch (error) {
1849
+ const errorMessage = error instanceof Error ? error.message : String(error);
1850
+ return { content: `Error analyzing image: ${errorMessage}`, is_error: true };
1851
+ }
1852
+ },
1853
+ };
1854
+
1855
+ // ============================================
1856
+ // TEAMMATE COORDINATION TOOLS
1857
+ // ============================================
1858
+
1859
+ /**
1860
+ * Check for new messages from teammates
1861
+ */
1862
+ const TeammateCheckMessagesTool: ToolDefinition = {
1863
+ name: "teammate_check_messages",
1864
+ description:
1865
+ "Check for new messages from other teammates in your team. Returns pending messages and clears the inbox. Use this periodically when in teammate mode to receive tasks and coordination messages.",
1866
+ input_schema: {
1867
+ type: "object",
1868
+ properties: {},
1869
+ },
1870
+ handler: async (_args, _context: ToolContext): Promise<ToolResult> => {
1871
+ const { getTeammateRunner } = await import("../../teammates/runner.js");
1872
+ const runner = getTeammateRunner();
1873
+ if (!runner) {
1874
+ return {
1875
+ content: "Teammate mode is not active. Start with --teammate-mode flag.",
1876
+ is_error: true,
1877
+ };
1878
+ }
1879
+
1880
+ const messages = runner.getPendingMessages();
1881
+ if (messages.length === 0) {
1882
+ return { content: "No pending messages." };
1883
+ }
1884
+
1885
+ const formatted = messages
1886
+ .map((m) => `[${m.type}] From: ${m.from}\n${m.content}`)
1887
+ .join("\n\n---\n\n");
1888
+
1889
+ return { content: `Received ${messages.length} message(s):\n\n${formatted}` };
1890
+ },
1891
+ };
1892
+
1893
+ /**
1894
+ * Send a direct message to another teammate
1895
+ */
1896
+ const TeammateSendMessageTool: ToolDefinition = {
1897
+ name: "teammate_send_message",
1898
+ description:
1899
+ "Send a direct message to a specific teammate or broadcast to all teammates. Use this for coordination, sharing results, or requesting help.",
1900
+ input_schema: {
1901
+ type: "object",
1902
+ properties: {
1903
+ to: {
1904
+ type: "string",
1905
+ description:
1906
+ "Teammate ID to send to, or 'broadcast' to send to all teammates",
1907
+ },
1908
+ message: {
1909
+ type: "string",
1910
+ description: "The message content to send",
1911
+ },
1912
+ },
1913
+ required: ["to", "message"],
1914
+ },
1915
+ handler: async (args, _context: ToolContext): Promise<ToolResult> => {
1916
+ const { getTeammateRunner } = await import("../../teammates/runner.js");
1917
+ const runner = getTeammateRunner();
1918
+ if (!runner) {
1919
+ return {
1920
+ content: "Teammate mode is not active. Start with --teammate-mode flag.",
1921
+ is_error: true,
1922
+ };
1923
+ }
1924
+
1925
+ const to = args.to as string;
1926
+ const message = args.message as string;
1927
+
1928
+ if (to === "broadcast") {
1929
+ runner.broadcast(message);
1930
+ return { content: `Broadcast message sent to team.` };
1931
+ } else {
1932
+ runner.sendDirectMessage(to, message);
1933
+ return { content: `Direct message sent to ${to}.` };
1934
+ }
1935
+ },
1936
+ };
1937
+
1938
+ /**
1939
+ * Report idle status to team
1940
+ */
1941
+ const TeammateReportIdleTool: ToolDefinition = {
1942
+ name: "teammate_report_idle",
1943
+ description:
1944
+ "Report that you are now idle and ready for new tasks. Use this when you complete your current work and are available for assignment.",
1945
+ input_schema: {
1946
+ type: "object",
1947
+ properties: {
1948
+ message: {
1949
+ type: "string",
1950
+ description: "Optional status message",
1951
+ },
1952
+ },
1953
+ },
1954
+ handler: async (args, _context: ToolContext): Promise<ToolResult> => {
1955
+ const { getTeammateRunner } = await import("../../teammates/runner.js");
1956
+ const runner = getTeammateRunner();
1957
+ if (!runner) {
1958
+ return {
1959
+ content: "Teammate mode is not active. Start with --teammate-mode flag.",
1960
+ is_error: true,
1961
+ };
1962
+ }
1963
+
1964
+ const message = args.message as string | undefined;
1965
+ runner.updateStatus("idle");
1966
+ runner.broadcast(message || "Now idle and ready for tasks.");
1967
+ return { content: "Status updated to idle. Team notified." };
1968
+ },
1969
+ };
1970
+
1971
+ /**
1972
+ * Get teammate status information
1973
+ */
1974
+ const TeammateGetStatusTool: ToolDefinition = {
1975
+ name: "teammate_get_status",
1976
+ description:
1977
+ "Get current teammate mode status including your info, team members, and inbox stats. Use this to check your state and team coordination.",
1978
+ input_schema: {
1979
+ type: "object",
1980
+ properties: {},
1981
+ },
1982
+ handler: async (_args, _context: ToolContext): Promise<ToolResult> => {
1983
+ const { getTeammateRunner } = await import("../../teammates/runner.js");
1984
+ const runner = getTeammateRunner();
1985
+ if (!runner) {
1986
+ return {
1987
+ content: "Teammate mode is not active. Start with --teammate-mode flag.",
1988
+ is_error: true,
1989
+ };
1990
+ }
1991
+
1992
+ const teammate = runner.getTeammate();
1993
+ const team = runner.getTeam();
1994
+ const status = runner.getStatus();
1995
+ const inbox = runner.getInboxStats();
1996
+
1997
+ const info = {
1998
+ teammate: teammate
1999
+ ? {
2000
+ id: teammate.teammateId,
2001
+ name: teammate.name,
2002
+ color: teammate.color,
2003
+ }
2004
+ : null,
2005
+ team: team
2006
+ ? {
2007
+ name: team.name,
2008
+ memberCount: team.teammates.length,
2009
+ members: team.teammates.map((t) => ({
2010
+ id: t.teammateId,
2011
+ name: t.name,
2012
+ status: t.status,
2013
+ })),
2014
+ }
2015
+ : null,
2016
+ status,
2017
+ inbox,
2018
+ };
2019
+
2020
+ return { content: JSON.stringify(info, null, 2) };
2021
+ },
2022
+ };
2023
+
2024
+ export const builtInTools: ToolDefinition[] = [
2025
+ ReadTool,
2026
+ WriteTool,
2027
+ EditTool,
2028
+ MultiEditTool,
2029
+ BashTool,
2030
+ GlobTool,
2031
+ GrepTool,
2032
+ TaskTool,
2033
+ TaskOutputTool,
2034
+ TaskStopTool,
2035
+ AskUserQuestionTool,
2036
+ EnterPlanModeTool,
2037
+ ExitPlanModeTool,
2038
+ SkillTool,
2039
+ NotebookEditTool,
2040
+ AnalyzeImageTool,
2041
+ TempGlmVisionTool,
2042
+ // Teammate coordination tools
2043
+ TeammateCheckMessagesTool,
2044
+ TeammateSendMessageTool,
2045
+ TeammateReportIdleTool,
2046
+ TeammateGetStatusTool,
2047
+ ];
2048
+
2049
+ export function getToolByName(name: string): ToolDefinition | undefined {
2050
+ return builtInTools.find((t) => t.name === name);
2051
+ }