@edwardlee5423/magi 0.1.0-alpha.0

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 (531) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -0
  3. package/dist/agent/headless-agent.d.ts +15 -0
  4. package/dist/agent/headless-agent.js +105 -0
  5. package/dist/agent/headless-agent.js.map +1 -0
  6. package/dist/agent/local-plan.d.ts +20 -0
  7. package/dist/agent/local-plan.js +30 -0
  8. package/dist/agent/local-plan.js.map +1 -0
  9. package/dist/agent/query-engine.d.ts +71 -0
  10. package/dist/agent/query-engine.js +938 -0
  11. package/dist/agent/query-engine.js.map +1 -0
  12. package/dist/agent/query.d.ts +128 -0
  13. package/dist/agent/query.js +674 -0
  14. package/dist/agent/query.js.map +1 -0
  15. package/dist/agent/system-prompt.d.ts +12 -0
  16. package/dist/agent/system-prompt.js +104 -0
  17. package/dist/agent/system-prompt.js.map +1 -0
  18. package/dist/agent/tools.d.ts +71 -0
  19. package/dist/agent/tools.js +37 -0
  20. package/dist/agent/tools.js.map +1 -0
  21. package/dist/agents/roles.d.ts +9 -0
  22. package/dist/agents/roles.js +18 -0
  23. package/dist/agents/roles.js.map +1 -0
  24. package/dist/agents/task-queue.d.ts +13 -0
  25. package/dist/agents/task-queue.js +50 -0
  26. package/dist/agents/task-queue.js.map +1 -0
  27. package/dist/cli.d.ts +7 -0
  28. package/dist/cli.js +1540 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/colors.d.ts +20 -0
  31. package/dist/colors.js +92 -0
  32. package/dist/colors.js.map +1 -0
  33. package/dist/commands/agents.d.ts +9 -0
  34. package/dist/commands/agents.js +37 -0
  35. package/dist/commands/agents.js.map +1 -0
  36. package/dist/commands/clear.d.ts +9 -0
  37. package/dist/commands/clear.js +20 -0
  38. package/dist/commands/clear.js.map +1 -0
  39. package/dist/commands/commit.d.ts +8 -0
  40. package/dist/commands/commit.js +35 -0
  41. package/dist/commands/commit.js.map +1 -0
  42. package/dist/commands/compact.d.ts +8 -0
  43. package/dist/commands/compact.js +29 -0
  44. package/dist/commands/compact.js.map +1 -0
  45. package/dist/commands/copy.d.ts +8 -0
  46. package/dist/commands/copy.js +49 -0
  47. package/dist/commands/copy.js.map +1 -0
  48. package/dist/commands/cost.d.ts +8 -0
  49. package/dist/commands/cost.js +94 -0
  50. package/dist/commands/cost.js.map +1 -0
  51. package/dist/commands/diff.d.ts +8 -0
  52. package/dist/commands/diff.js +38 -0
  53. package/dist/commands/diff.js.map +1 -0
  54. package/dist/commands/doctor.d.ts +8 -0
  55. package/dist/commands/doctor.js +23 -0
  56. package/dist/commands/doctor.js.map +1 -0
  57. package/dist/commands/export.d.ts +8 -0
  58. package/dist/commands/export.js +42 -0
  59. package/dist/commands/export.js.map +1 -0
  60. package/dist/commands/fork.d.ts +8 -0
  61. package/dist/commands/fork.js +20 -0
  62. package/dist/commands/fork.js.map +1 -0
  63. package/dist/commands/goal.d.ts +9 -0
  64. package/dist/commands/goal.js +54 -0
  65. package/dist/commands/goal.js.map +1 -0
  66. package/dist/commands/help.d.ts +8 -0
  67. package/dist/commands/help.js +65 -0
  68. package/dist/commands/help.js.map +1 -0
  69. package/dist/commands/hooks.d.ts +8 -0
  70. package/dist/commands/hooks.js +56 -0
  71. package/dist/commands/hooks.js.map +1 -0
  72. package/dist/commands/image.d.ts +17 -0
  73. package/dist/commands/image.js +74 -0
  74. package/dist/commands/image.js.map +1 -0
  75. package/dist/commands/init.d.ts +26 -0
  76. package/dist/commands/init.js +214 -0
  77. package/dist/commands/init.js.map +1 -0
  78. package/dist/commands/mcp.d.ts +8 -0
  79. package/dist/commands/mcp.js +217 -0
  80. package/dist/commands/mcp.js.map +1 -0
  81. package/dist/commands/memory.d.ts +8 -0
  82. package/dist/commands/memory.js +79 -0
  83. package/dist/commands/memory.js.map +1 -0
  84. package/dist/commands/model.d.ts +8 -0
  85. package/dist/commands/model.js +21 -0
  86. package/dist/commands/model.js.map +1 -0
  87. package/dist/commands/permissions.d.ts +9 -0
  88. package/dist/commands/permissions.js +30 -0
  89. package/dist/commands/permissions.js.map +1 -0
  90. package/dist/commands/proactive.d.ts +9 -0
  91. package/dist/commands/proactive.js +24 -0
  92. package/dist/commands/proactive.js.map +1 -0
  93. package/dist/commands/register-all.d.ts +1 -0
  94. package/dist/commands/register-all.js +68 -0
  95. package/dist/commands/register-all.js.map +1 -0
  96. package/dist/commands/registry.d.ts +64 -0
  97. package/dist/commands/registry.js +235 -0
  98. package/dist/commands/registry.js.map +1 -0
  99. package/dist/commands/rename.d.ts +8 -0
  100. package/dist/commands/rename.js +16 -0
  101. package/dist/commands/rename.js.map +1 -0
  102. package/dist/commands/review.d.ts +8 -0
  103. package/dist/commands/review.js +13 -0
  104. package/dist/commands/review.js.map +1 -0
  105. package/dist/commands/rewind.d.ts +8 -0
  106. package/dist/commands/rewind.js +45 -0
  107. package/dist/commands/rewind.js.map +1 -0
  108. package/dist/commands/route.d.ts +9 -0
  109. package/dist/commands/route.js +83 -0
  110. package/dist/commands/route.js.map +1 -0
  111. package/dist/commands/sessions.d.ts +9 -0
  112. package/dist/commands/sessions.js +25 -0
  113. package/dist/commands/sessions.js.map +1 -0
  114. package/dist/commands/skill.d.ts +9 -0
  115. package/dist/commands/skill.js +46 -0
  116. package/dist/commands/skill.js.map +1 -0
  117. package/dist/commands/status.d.ts +8 -0
  118. package/dist/commands/status.js +41 -0
  119. package/dist/commands/status.js.map +1 -0
  120. package/dist/commands/summary.d.ts +8 -0
  121. package/dist/commands/summary.js +39 -0
  122. package/dist/commands/summary.js.map +1 -0
  123. package/dist/commands/tasks.d.ts +9 -0
  124. package/dist/commands/tasks.js +70 -0
  125. package/dist/commands/tasks.js.map +1 -0
  126. package/dist/commands/todos.d.ts +9 -0
  127. package/dist/commands/todos.js +27 -0
  128. package/dist/commands/todos.js.map +1 -0
  129. package/dist/commands/tutorial.d.ts +8 -0
  130. package/dist/commands/tutorial.js +159 -0
  131. package/dist/commands/tutorial.js.map +1 -0
  132. package/dist/commands/upgrade.d.ts +9 -0
  133. package/dist/commands/upgrade.js +73 -0
  134. package/dist/commands/upgrade.js.map +1 -0
  135. package/dist/commands/usage.d.ts +8 -0
  136. package/dist/commands/usage.js +54 -0
  137. package/dist/commands/usage.js.map +1 -0
  138. package/dist/commands/vim.d.ts +10 -0
  139. package/dist/commands/vim.js +34 -0
  140. package/dist/commands/vim.js.map +1 -0
  141. package/dist/config.d.ts +104 -0
  142. package/dist/config.js +533 -0
  143. package/dist/config.js.map +1 -0
  144. package/dist/context/compaction.d.ts +66 -0
  145. package/dist/context/compaction.js +353 -0
  146. package/dist/context/compaction.js.map +1 -0
  147. package/dist/context/layers.d.ts +32 -0
  148. package/dist/context/layers.js +114 -0
  149. package/dist/context/layers.js.map +1 -0
  150. package/dist/context/token-budget.d.ts +21 -0
  151. package/dist/context/token-budget.js +70 -0
  152. package/dist/context/token-budget.js.map +1 -0
  153. package/dist/control/auth.d.ts +17 -0
  154. package/dist/control/auth.js +31 -0
  155. package/dist/control/auth.js.map +1 -0
  156. package/dist/control/daemon.d.ts +38 -0
  157. package/dist/control/daemon.js +135 -0
  158. package/dist/control/daemon.js.map +1 -0
  159. package/dist/control/mdns.d.ts +51 -0
  160. package/dist/control/mdns.js +344 -0
  161. package/dist/control/mdns.js.map +1 -0
  162. package/dist/control/peer-client.d.ts +49 -0
  163. package/dist/control/peer-client.js +170 -0
  164. package/dist/control/peer-client.js.map +1 -0
  165. package/dist/control/server.d.ts +30 -0
  166. package/dist/control/server.js +887 -0
  167. package/dist/control/server.js.map +1 -0
  168. package/dist/cost.d.ts +23 -0
  169. package/dist/cost.js +80 -0
  170. package/dist/cost.js.map +1 -0
  171. package/dist/doctor.d.ts +8 -0
  172. package/dist/doctor.js +22 -0
  173. package/dist/doctor.js.map +1 -0
  174. package/dist/env.d.ts +7 -0
  175. package/dist/env.js +70 -0
  176. package/dist/env.js.map +1 -0
  177. package/dist/errors.d.ts +6 -0
  178. package/dist/errors.js +13 -0
  179. package/dist/errors.js.map +1 -0
  180. package/dist/events.d.ts +19 -0
  181. package/dist/events.js +236 -0
  182. package/dist/events.js.map +1 -0
  183. package/dist/fs-utils.d.ts +21 -0
  184. package/dist/fs-utils.js +48 -0
  185. package/dist/fs-utils.js.map +1 -0
  186. package/dist/goal.d.ts +32 -0
  187. package/dist/goal.js +172 -0
  188. package/dist/goal.js.map +1 -0
  189. package/dist/headless.d.ts +38 -0
  190. package/dist/headless.js +547 -0
  191. package/dist/headless.js.map +1 -0
  192. package/dist/history.d.ts +8 -0
  193. package/dist/history.js +77 -0
  194. package/dist/history.js.map +1 -0
  195. package/dist/hooks/events.d.ts +14 -0
  196. package/dist/hooks/events.js +37 -0
  197. package/dist/hooks/events.js.map +1 -0
  198. package/dist/hooks/runner.d.ts +73 -0
  199. package/dist/hooks/runner.js +256 -0
  200. package/dist/hooks/runner.js.map +1 -0
  201. package/dist/hooks/trigger.d.ts +9 -0
  202. package/dist/hooks/trigger.js +18 -0
  203. package/dist/hooks/trigger.js.map +1 -0
  204. package/dist/index.d.ts +61 -0
  205. package/dist/index.js +62 -0
  206. package/dist/index.js.map +1 -0
  207. package/dist/interactions.d.ts +102 -0
  208. package/dist/interactions.js +250 -0
  209. package/dist/interactions.js.map +1 -0
  210. package/dist/logger.d.ts +32 -0
  211. package/dist/logger.js +115 -0
  212. package/dist/logger.js.map +1 -0
  213. package/dist/markdown.d.ts +18 -0
  214. package/dist/markdown.js +293 -0
  215. package/dist/markdown.js.map +1 -0
  216. package/dist/mcp/approval.d.ts +9 -0
  217. package/dist/mcp/approval.js +27 -0
  218. package/dist/mcp/approval.js.map +1 -0
  219. package/dist/mcp/client.d.ts +61 -0
  220. package/dist/mcp/client.js +276 -0
  221. package/dist/mcp/client.js.map +1 -0
  222. package/dist/mcp/connection-manager.d.ts +30 -0
  223. package/dist/mcp/connection-manager.js +126 -0
  224. package/dist/mcp/connection-manager.js.map +1 -0
  225. package/dist/mcp/oauth-callback.d.ts +23 -0
  226. package/dist/mcp/oauth-callback.js +108 -0
  227. package/dist/mcp/oauth-callback.js.map +1 -0
  228. package/dist/mcp/oauth-flow.d.ts +45 -0
  229. package/dist/mcp/oauth-flow.js +145 -0
  230. package/dist/mcp/oauth-flow.js.map +1 -0
  231. package/dist/mcp/oauth.d.ts +82 -0
  232. package/dist/mcp/oauth.js +169 -0
  233. package/dist/mcp/oauth.js.map +1 -0
  234. package/dist/mcp/tool-registry.d.ts +29 -0
  235. package/dist/mcp/tool-registry.js +207 -0
  236. package/dist/mcp/tool-registry.js.map +1 -0
  237. package/dist/mcp/transport.d.ts +33 -0
  238. package/dist/mcp/transport.js +493 -0
  239. package/dist/mcp/transport.js.map +1 -0
  240. package/dist/mcp/types.d.ts +57 -0
  241. package/dist/mcp/types.js +2 -0
  242. package/dist/mcp/types.js.map +1 -0
  243. package/dist/memdir.d.ts +58 -0
  244. package/dist/memdir.js +219 -0
  245. package/dist/memdir.js.map +1 -0
  246. package/dist/memory-selection.d.ts +30 -0
  247. package/dist/memory-selection.js +102 -0
  248. package/dist/memory-selection.js.map +1 -0
  249. package/dist/memory.d.ts +85 -0
  250. package/dist/memory.js +259 -0
  251. package/dist/memory.js.map +1 -0
  252. package/dist/paths.d.ts +25 -0
  253. package/dist/paths.js +99 -0
  254. package/dist/paths.js.map +1 -0
  255. package/dist/permissions.d.ts +17 -0
  256. package/dist/permissions.js +72 -0
  257. package/dist/permissions.js.map +1 -0
  258. package/dist/plugins/manifest.d.ts +17 -0
  259. package/dist/plugins/manifest.js +98 -0
  260. package/dist/plugins/manifest.js.map +1 -0
  261. package/dist/plugins/marketplace.d.ts +22 -0
  262. package/dist/plugins/marketplace.js +98 -0
  263. package/dist/plugins/marketplace.js.map +1 -0
  264. package/dist/proactive.d.ts +15 -0
  265. package/dist/proactive.js +49 -0
  266. package/dist/proactive.js.map +1 -0
  267. package/dist/providers/errors.d.ts +14 -0
  268. package/dist/providers/errors.js +66 -0
  269. package/dist/providers/errors.js.map +1 -0
  270. package/dist/providers/format-proxy.d.ts +100 -0
  271. package/dist/providers/format-proxy.js +235 -0
  272. package/dist/providers/format-proxy.js.map +1 -0
  273. package/dist/providers/http.d.ts +4 -0
  274. package/dist/providers/http.js +21 -0
  275. package/dist/providers/http.js.map +1 -0
  276. package/dist/providers/ir.d.ts +84 -0
  277. package/dist/providers/ir.js +76 -0
  278. package/dist/providers/ir.js.map +1 -0
  279. package/dist/providers/messages-compatible.d.ts +19 -0
  280. package/dist/providers/messages-compatible.js +592 -0
  281. package/dist/providers/messages-compatible.js.map +1 -0
  282. package/dist/providers/openai.d.ts +18 -0
  283. package/dist/providers/openai.js +430 -0
  284. package/dist/providers/openai.js.map +1 -0
  285. package/dist/providers/registry.d.ts +10 -0
  286. package/dist/providers/registry.js +16 -0
  287. package/dist/providers/registry.js.map +1 -0
  288. package/dist/providers/sse.d.ts +6 -0
  289. package/dist/providers/sse.js +104 -0
  290. package/dist/providers/sse.js.map +1 -0
  291. package/dist/routing/model-alias.d.ts +8 -0
  292. package/dist/routing/model-alias.js +23 -0
  293. package/dist/routing/model-alias.js.map +1 -0
  294. package/dist/routing/model-router.d.ts +55 -0
  295. package/dist/routing/model-router.js +233 -0
  296. package/dist/routing/model-router.js.map +1 -0
  297. package/dist/routing/router.d.ts +26 -0
  298. package/dist/routing/router.js +63 -0
  299. package/dist/routing/router.js.map +1 -0
  300. package/dist/rules/agents-loader.d.ts +12 -0
  301. package/dist/rules/agents-loader.js +61 -0
  302. package/dist/rules/agents-loader.js.map +1 -0
  303. package/dist/runner/client.d.ts +67 -0
  304. package/dist/runner/client.js +175 -0
  305. package/dist/runner/client.js.map +1 -0
  306. package/dist/session-store.d.ts +250 -0
  307. package/dist/session-store.js +629 -0
  308. package/dist/session-store.js.map +1 -0
  309. package/dist/skills/bundled.d.ts +14 -0
  310. package/dist/skills/bundled.js +148 -0
  311. package/dist/skills/bundled.js.map +1 -0
  312. package/dist/skills/loader.d.ts +11 -0
  313. package/dist/skills/loader.js +73 -0
  314. package/dist/skills/loader.js.map +1 -0
  315. package/dist/slash-menu.d.ts +14 -0
  316. package/dist/slash-menu.js +166 -0
  317. package/dist/slash-menu.js.map +1 -0
  318. package/dist/slash.d.ts +75 -0
  319. package/dist/slash.js +126 -0
  320. package/dist/slash.js.map +1 -0
  321. package/dist/spinner.d.ts +27 -0
  322. package/dist/spinner.js +68 -0
  323. package/dist/spinner.js.map +1 -0
  324. package/dist/ssh/exec.d.ts +20 -0
  325. package/dist/ssh/exec.js +39 -0
  326. package/dist/ssh/exec.js.map +1 -0
  327. package/dist/ssh/file.d.ts +32 -0
  328. package/dist/ssh/file.js +60 -0
  329. package/dist/ssh/file.js.map +1 -0
  330. package/dist/syntax-highlight.d.ts +19 -0
  331. package/dist/syntax-highlight.js +181 -0
  332. package/dist/syntax-highlight.js.map +1 -0
  333. package/dist/tools/agent-tool.d.ts +53 -0
  334. package/dist/tools/agent-tool.js +62 -0
  335. package/dist/tools/agent-tool.js.map +1 -0
  336. package/dist/tools/archive-create.d.ts +35 -0
  337. package/dist/tools/archive-create.js +37 -0
  338. package/dist/tools/archive-create.js.map +1 -0
  339. package/dist/tools/archive-extract.d.ts +28 -0
  340. package/dist/tools/archive-extract.js +36 -0
  341. package/dist/tools/archive-extract.js.map +1 -0
  342. package/dist/tools/base64.d.ts +27 -0
  343. package/dist/tools/base64.js +19 -0
  344. package/dist/tools/base64.js.map +1 -0
  345. package/dist/tools/browser.d.ts +125 -0
  346. package/dist/tools/browser.js +241 -0
  347. package/dist/tools/browser.js.map +1 -0
  348. package/dist/tools/config-tool.d.ts +31 -0
  349. package/dist/tools/config-tool.js +167 -0
  350. package/dist/tools/config-tool.js.map +1 -0
  351. package/dist/tools/cron.d.ts +110 -0
  352. package/dist/tools/cron.js +299 -0
  353. package/dist/tools/cron.js.map +1 -0
  354. package/dist/tools/date.d.ts +24 -0
  355. package/dist/tools/date.js +26 -0
  356. package/dist/tools/date.js.map +1 -0
  357. package/dist/tools/dir-create.d.ts +21 -0
  358. package/dist/tools/dir-create.js +19 -0
  359. package/dist/tools/dir-create.js.map +1 -0
  360. package/dist/tools/dir-list.d.ts +36 -0
  361. package/dist/tools/dir-list.js +47 -0
  362. package/dist/tools/dir-list.js.map +1 -0
  363. package/dist/tools/disk-usage.d.ts +30 -0
  364. package/dist/tools/disk-usage.js +22 -0
  365. package/dist/tools/disk-usage.js.map +1 -0
  366. package/dist/tools/download-file.d.ts +28 -0
  367. package/dist/tools/download-file.js +42 -0
  368. package/dist/tools/download-file.js.map +1 -0
  369. package/dist/tools/environment.d.ts +31 -0
  370. package/dist/tools/environment.js +33 -0
  371. package/dist/tools/environment.js.map +1 -0
  372. package/dist/tools/errors.d.ts +5 -0
  373. package/dist/tools/errors.js +9 -0
  374. package/dist/tools/errors.js.map +1 -0
  375. package/dist/tools/file-copy.d.ts +34 -0
  376. package/dist/tools/file-copy.js +45 -0
  377. package/dist/tools/file-copy.js.map +1 -0
  378. package/dist/tools/file-delete.d.ts +34 -0
  379. package/dist/tools/file-delete.js +42 -0
  380. package/dist/tools/file-delete.js.map +1 -0
  381. package/dist/tools/file-find.d.ts +46 -0
  382. package/dist/tools/file-find.js +33 -0
  383. package/dist/tools/file-find.js.map +1 -0
  384. package/dist/tools/file-move.d.ts +33 -0
  385. package/dist/tools/file-move.js +32 -0
  386. package/dist/tools/file-move.js.map +1 -0
  387. package/dist/tools/files.d.ts +38 -0
  388. package/dist/tools/files.js +123 -0
  389. package/dist/tools/files.js.map +1 -0
  390. package/dist/tools/git-branch-delete.d.ts +27 -0
  391. package/dist/tools/git-branch-delete.js +25 -0
  392. package/dist/tools/git-branch-delete.js.map +1 -0
  393. package/dist/tools/git-reset.d.ts +30 -0
  394. package/dist/tools/git-reset.js +28 -0
  395. package/dist/tools/git-reset.js.map +1 -0
  396. package/dist/tools/git-stash.d.ts +28 -0
  397. package/dist/tools/git-stash.js +24 -0
  398. package/dist/tools/git-stash.js.map +1 -0
  399. package/dist/tools/git.d.ts +77 -0
  400. package/dist/tools/git.js +300 -0
  401. package/dist/tools/git.js.map +1 -0
  402. package/dist/tools/github.d.ts +68 -0
  403. package/dist/tools/github.js +77 -0
  404. package/dist/tools/github.js.map +1 -0
  405. package/dist/tools/head-tail.d.ts +34 -0
  406. package/dist/tools/head-tail.js +23 -0
  407. package/dist/tools/head-tail.js.map +1 -0
  408. package/dist/tools/http-request.d.ts +44 -0
  409. package/dist/tools/http-request.js +57 -0
  410. package/dist/tools/http-request.js.map +1 -0
  411. package/dist/tools/json-query.d.ts +27 -0
  412. package/dist/tools/json-query.js +90 -0
  413. package/dist/tools/json-query.js.map +1 -0
  414. package/dist/tools/kill-process.d.ts +32 -0
  415. package/dist/tools/kill-process.js +51 -0
  416. package/dist/tools/kill-process.js.map +1 -0
  417. package/dist/tools/lsp.d.ts +98 -0
  418. package/dist/tools/lsp.js +398 -0
  419. package/dist/tools/lsp.js.map +1 -0
  420. package/dist/tools/monitor.d.ts +29 -0
  421. package/dist/tools/monitor.js +78 -0
  422. package/dist/tools/monitor.js.map +1 -0
  423. package/dist/tools/network-check.d.ts +34 -0
  424. package/dist/tools/network-check.js +57 -0
  425. package/dist/tools/network-check.js.map +1 -0
  426. package/dist/tools/notebook.d.ts +63 -0
  427. package/dist/tools/notebook.js +158 -0
  428. package/dist/tools/notebook.js.map +1 -0
  429. package/dist/tools/plan-mode.d.ts +48 -0
  430. package/dist/tools/plan-mode.js +57 -0
  431. package/dist/tools/plan-mode.js.map +1 -0
  432. package/dist/tools/process-list.d.ts +35 -0
  433. package/dist/tools/process-list.js +42 -0
  434. package/dist/tools/process-list.js.map +1 -0
  435. package/dist/tools/registry.d.ts +132 -0
  436. package/dist/tools/registry.js +2262 -0
  437. package/dist/tools/registry.js.map +1 -0
  438. package/dist/tools/search.d.ts +38 -0
  439. package/dist/tools/search.js +327 -0
  440. package/dist/tools/search.js.map +1 -0
  441. package/dist/tools/shell.d.ts +18 -0
  442. package/dist/tools/shell.js +249 -0
  443. package/dist/tools/shell.js.map +1 -0
  444. package/dist/tools/skill-tool.d.ts +22 -0
  445. package/dist/tools/skill-tool.js +66 -0
  446. package/dist/tools/skill-tool.js.map +1 -0
  447. package/dist/tools/sleep.d.ts +16 -0
  448. package/dist/tools/sleep.js +24 -0
  449. package/dist/tools/sleep.js.map +1 -0
  450. package/dist/tools/snip.d.ts +26 -0
  451. package/dist/tools/snip.js +95 -0
  452. package/dist/tools/snip.js.map +1 -0
  453. package/dist/tools/system-info.d.ts +17 -0
  454. package/dist/tools/system-info.js +33 -0
  455. package/dist/tools/system-info.js.map +1 -0
  456. package/dist/tools/tasks.d.ts +191 -0
  457. package/dist/tools/tasks.js +348 -0
  458. package/dist/tools/tasks.js.map +1 -0
  459. package/dist/tools/text-stats.d.ts +25 -0
  460. package/dist/tools/text-stats.js +25 -0
  461. package/dist/tools/text-stats.js.map +1 -0
  462. package/dist/tools/todo.d.ts +69 -0
  463. package/dist/tools/todo.js +197 -0
  464. package/dist/tools/todo.js.map +1 -0
  465. package/dist/tools/tool-search.d.ts +29 -0
  466. package/dist/tools/tool-search.js +114 -0
  467. package/dist/tools/tool-search.js.map +1 -0
  468. package/dist/tools/tree-view.d.ts +34 -0
  469. package/dist/tools/tree-view.js +39 -0
  470. package/dist/tools/tree-view.js.map +1 -0
  471. package/dist/tools/user-message.d.ts +39 -0
  472. package/dist/tools/user-message.js +70 -0
  473. package/dist/tools/user-message.js.map +1 -0
  474. package/dist/tools/user-question.d.ts +88 -0
  475. package/dist/tools/user-question.js +236 -0
  476. package/dist/tools/user-question.js.map +1 -0
  477. package/dist/tools/web-browser.d.ts +53 -0
  478. package/dist/tools/web-browser.js +373 -0
  479. package/dist/tools/web-browser.js.map +1 -0
  480. package/dist/tools/web-fetch.d.ts +22 -0
  481. package/dist/tools/web-fetch.js +146 -0
  482. package/dist/tools/web-fetch.js.map +1 -0
  483. package/dist/tools/web-search.d.ts +51 -0
  484. package/dist/tools/web-search.js +198 -0
  485. package/dist/tools/web-search.js.map +1 -0
  486. package/dist/tools/which.d.ts +22 -0
  487. package/dist/tools/which.js +21 -0
  488. package/dist/tools/which.js.map +1 -0
  489. package/dist/tools/whoami.d.ts +17 -0
  490. package/dist/tools/whoami.js +24 -0
  491. package/dist/tools/whoami.js.map +1 -0
  492. package/dist/tools/workspace-diagnostics.d.ts +62 -0
  493. package/dist/tools/workspace-diagnostics.js +421 -0
  494. package/dist/tools/workspace-diagnostics.js.map +1 -0
  495. package/dist/tools/workspace.d.ts +14 -0
  496. package/dist/tools/workspace.js +65 -0
  497. package/dist/tools/workspace.js.map +1 -0
  498. package/dist/tools/worktree.d.ts +70 -0
  499. package/dist/tools/worktree.js +150 -0
  500. package/dist/tools/worktree.js.map +1 -0
  501. package/dist/tui/interactions.d.ts +25 -0
  502. package/dist/tui/interactions.js +232 -0
  503. package/dist/tui/interactions.js.map +1 -0
  504. package/dist/tui/multiline-input.d.ts +14 -0
  505. package/dist/tui/multiline-input.js +550 -0
  506. package/dist/tui/multiline-input.js.map +1 -0
  507. package/dist/tui/paste.d.ts +40 -0
  508. package/dist/tui/paste.js +113 -0
  509. package/dist/tui/paste.js.map +1 -0
  510. package/dist/tui/prompt-reader.d.ts +26 -0
  511. package/dist/tui/prompt-reader.js +553 -0
  512. package/dist/tui/prompt-reader.js.map +1 -0
  513. package/dist/tui/transcript.d.ts +34 -0
  514. package/dist/tui/transcript.js +275 -0
  515. package/dist/tui/transcript.js.map +1 -0
  516. package/dist/tui.d.ts +53 -0
  517. package/dist/tui.js +669 -0
  518. package/dist/tui.js.map +1 -0
  519. package/dist/version.d.ts +1 -0
  520. package/dist/version.js +2 -0
  521. package/dist/version.js.map +1 -0
  522. package/dist/vim/lineEditor.d.ts +18 -0
  523. package/dist/vim/lineEditor.js +420 -0
  524. package/dist/vim/lineEditor.js.map +1 -0
  525. package/dist/web/panel-html.d.ts +1 -0
  526. package/dist/web/panel-html.js +532 -0
  527. package/dist/web/panel-html.js.map +1 -0
  528. package/dist/web/panel.d.ts +4 -0
  529. package/dist/web/panel.js +121 -0
  530. package/dist/web/panel.js.map +1 -0
  531. package/package.json +42 -0
@@ -0,0 +1,2262 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { executeConfigTool, ConfigToolInputSchema, parseConfigToolInput } from "./config-tool.js";
5
+ import { addCronJob, applyCronUpdate, CRON_CREATE_SCHEMA, CRON_DELETE_SCHEMA, CRON_LIST_SCHEMA, CRON_UPDATE_SCHEMA, cronStorePathFromRoot, deleteCronJob, formatCronJob, formatCronList, listCronJobs } from "./cron.js";
6
+ import { createUnifiedDiff, editWorkspaceFile, readWorkspaceFile, writeWorkspaceFile } from "./files.js";
7
+ import { checkoutGitBranch, createGitBranch, getGitDiff, getGitLog, getGitShow, getGitStatus, getGitSummary, listGitBranches, stageGitPaths } from "./git.js";
8
+ import { executeLspRequest, LSP_SCHEMA, parseLspRequest } from "./lsp.js";
9
+ import { formatSearchMatches, globWorkspace, searchWorkspace } from "./search.js";
10
+ import { runShellCommand } from "./shell.js";
11
+ import { formatMonitorResult, getMonitorData, MonitorInputSchema, parseMonitorInput } from "./monitor.js";
12
+ import { executeSleep, parseSleepInput, SleepInputSchema } from "./sleep.js";
13
+ import { executeFileCopy, formatFileCopyResult, FileCopyInputSchema, parseFileCopyInput } from "./file-copy.js";
14
+ import { executeFileMove, formatFileMoveResult, FileMoveInputSchema, parseFileMoveInput } from "./file-move.js";
15
+ import { executeFileDelete, FileDeleteInputSchema, formatFileDeleteResult, parseFileDeleteInput } from "./file-delete.js";
16
+ import { DirCreateInputSchema, executeDirCreate, formatDirCreateResult, parseDirCreateInput } from "./dir-create.js";
17
+ import { DirListInputSchema, executeDirList, formatDirListResult, parseDirListInput } from "./dir-list.js";
18
+ import { executeProcessList, formatProcessListResult, parseProcessListInput, ProcessListInputSchema } from "./process-list.js";
19
+ import { executeKillProcess, formatKillProcessResult, KillProcessInputSchema, parseKillProcessInput } from "./kill-process.js";
20
+ import { EnvironmentInputSchema, executeEnvironment, formatEnvironmentResult, parseEnvironmentInput } from "./environment.js";
21
+ import { DiskUsageInputSchema, executeDiskUsage, formatDiskUsageResult, parseDiskUsageInput } from "./disk-usage.js";
22
+ import { executeSystemInfo, formatSystemInfoResult, SystemInfoInputSchema } from "./system-info.js";
23
+ import { executeHttpRequest, formatHttpRequestResult, HttpRequestInputSchema, parseHttpRequestInput } from "./http-request.js";
24
+ import { executeJsonQuery, formatJsonQueryResult, JsonQueryInputSchema, parseJsonQueryInput } from "./json-query.js";
25
+ import { ArchiveCreateInputSchema, executeArchiveCreate, formatArchiveCreateResult, parseArchiveCreateInput } from "./archive-create.js";
26
+ import { ArchiveExtractInputSchema, executeArchiveExtract, formatArchiveExtractResult, parseArchiveExtractInput } from "./archive-extract.js";
27
+ import { executeGitBranchDelete, formatGitBranchDeleteResult, GitBranchDeleteInputSchema, parseGitBranchDeleteInput } from "./git-branch-delete.js";
28
+ import { executeGitStash, formatGitStashResult, GitStashInputSchema, parseGitStashInput } from "./git-stash.js";
29
+ import { executeGitReset, formatGitResetResult, GitResetInputSchema, parseGitResetInput } from "./git-reset.js";
30
+ import { executeFileFind, FileFindInputSchema, formatFileFindResult, parseFileFindInput } from "./file-find.js";
31
+ import { executeHeadTail, formatHeadTailResult, HeadTailInputSchema, parseHeadTailInput } from "./head-tail.js";
32
+ import { executeTextStats, formatTextStatsResult, parseTextStatsInput, TextStatsInputSchema } from "./text-stats.js";
33
+ import { executeTreeView, formatTreeViewResult, parseTreeViewInput, TreeViewInputSchema } from "./tree-view.js";
34
+ import { executeWhoAmI, formatWhoAmIResult, WhoAmIInputSchema } from "./whoami.js";
35
+ import { executeNetworkCheck, formatNetworkCheckResult, NetworkCheckInputSchema, parseNetworkCheckInput } from "./network-check.js";
36
+ import { Base64InputSchema, executeBase64, formatBase64Result, parseBase64Input } from "./base64.js";
37
+ import { executeWhich, formatWhichResult, parseWhichInput, WhichInputSchema } from "./which.js";
38
+ import { DateInputSchema, executeDate, formatDateResult } from "./date.js";
39
+ import { sshExec } from "../ssh/exec.js";
40
+ import { sshFileRead, sshFileWrite } from "../ssh/file.js";
41
+ import { executeSnip, formatSnipResult, parseSnipInput, SnipInputSchema } from "./snip.js";
42
+ import { executeSkillTool, parseSkillToolInput, SkillToolInputSchema } from "./skill-tool.js";
43
+ import { writeMemdirEntry } from "../memdir.js";
44
+ import { formatTodoWriteResult, parseTodoWriteInput, replaceTodoList, TodoWriteInputSchema } from "./todo.js";
45
+ import { executeToolSearch, parseToolSearchInput, ToolSearchInputSchema } from "./tool-search.js";
46
+ import { executeTaskCreate, executeTaskGet, executeTaskList, executeTaskOutput, executeTaskStop, executeTaskUpdate, formatTaskCreateResult, formatTaskGetResult, formatTaskListResult, formatTaskOutputResult, formatTaskStopResult, formatTaskUpdateResult, parseTaskCreateInput, parseTaskUpdateInput, TaskCreateInputSchema, TaskGetInputSchema, TaskListInputSchema, TaskOutputInputSchema, TaskStopInputSchema, TaskUpdateInputSchema } from "./tasks.js";
47
+ import { EnterPlanModeInputSchema, ExitPlanModeInputSchema, formatEnterPlanModeResult, formatExitPlanModeResult, parseEnterPlanModeInput, parseExitPlanModeInput } from "./plan-mode.js";
48
+ import { EnterWorktreeInputSchema, ExitWorktreeInputSchema, executeEnterWorktree, executeExitWorktree, formatEnterWorktreeResult, formatExitWorktreeResult, parseEnterWorktreeInput, parseExitWorktreeInput } from "./worktree.js";
49
+ import { ghIssueView, ghPRDiff, ghPRList, ghPRView, GitHubIssueViewInputSchema, GitHubPRDiffInputSchema, GitHubPRListInputSchema, GitHubPRViewInputSchema } from "./github.js";
50
+ import { AgentToolInputSchema, formatAgentToolResult, parseAgentToolInput } from "./agent-tool.js";
51
+ import { executeNotebookEdit, executeNotebookRead, NotebookEditInputSchema, NotebookReadInputSchema, parseNotebookEditInput, parseNotebookReadInput } from "./notebook.js";
52
+ import { defaultUserMessageSink, formatSendUserMessageResult, parseSendUserMessageInput, SEND_USER_MESSAGE_SCHEMA } from "./user-message.js";
53
+ import { ASK_USER_QUESTION_SCHEMA, formatAskUserQuestionAnswer, normalizeAskUserQuestionAnswer, parseAskUserQuestionInput } from "./user-question.js";
54
+ import { readWebFetchAllowlist, webFetch, webFetchHostAllowed } from "./web-fetch.js";
55
+ import { BrowserActionInputSchema, executeBrowserAction, formatBrowserActionResult } from "./browser.js";
56
+ import { executeWebBrowser, formatWebBrowserResult, parseWebBrowserInput, WebBrowserInputSchema } from "./web-browser.js";
57
+ import { formatWebSearchResult, parseWebSearchInput, webSearch, WebSearchInputSchema } from "./web-search.js";
58
+ import { formatWorkspaceDiagnostics, parseWorkspaceDiagnosticsInput, runWorkspaceDiagnostics, WorkspaceDiagnosticsInputSchema } from "./workspace-diagnostics.js";
59
+ import { resolveWorkspacePath } from "./workspace.js";
60
+ export function getBuiltinToolRegistry() {
61
+ return new Map(BUILTIN_TOOLS.map((tool) => [tool.name, tool]));
62
+ }
63
+ export function getBuiltinToolDefinitions() {
64
+ return BUILTIN_TOOLS.map((tool) => ({
65
+ name: tool.name,
66
+ description: tool.description,
67
+ inputSchema: tool.inputSchema
68
+ }));
69
+ }
70
+ export async function executeRegisteredTool(input) {
71
+ const registry = getBuiltinToolRegistry();
72
+ const tool = registry.get(input.toolUse.name);
73
+ if (!tool) {
74
+ return errorResult(input.toolUse, `Unknown tool: ${input.toolUse.name}`);
75
+ }
76
+ try {
77
+ const context = {
78
+ cwd: input.cwd,
79
+ env: input.env,
80
+ permissionMode: input.permissionMode ?? "default",
81
+ rules: input.rules,
82
+ outputRoot: input.outputRoot,
83
+ stateRoot: input.stateRoot,
84
+ sessionId: input.sessionId,
85
+ webSearchConfig: input.webSearchConfig,
86
+ promptModel: input.promptModel,
87
+ userQuestionResolver: input.userQuestionResolver,
88
+ userMessageSink: input.userMessageSink,
89
+ toolUse: input.toolUse,
90
+ spawnSubAgent: input.spawnSubAgent,
91
+ signal: input.signal
92
+ };
93
+ const permission = checkToolPermission({
94
+ toolUse: input.toolUse,
95
+ mode: context.permissionMode,
96
+ rules: context.rules,
97
+ tool,
98
+ env: context.env
99
+ });
100
+ // Generate diff preview for FileWrite/FileEdit when approval is needed
101
+ if (permission.decision === "ask" && (input.toolUse.name === "FileWrite" || input.toolUse.name === "FileEdit")) {
102
+ try {
103
+ const filePath = readString(input.toolUse.input, "file_path");
104
+ const resolved = resolveWorkspacePath(input.cwd, filePath);
105
+ const before = existsSync(resolved.absolutePath) ? readFileSync(resolved.absolutePath, "utf8") : "";
106
+ let after;
107
+ if (input.toolUse.name === "FileWrite") {
108
+ after = readString(input.toolUse.input, "content");
109
+ }
110
+ else {
111
+ const oldString = readString(input.toolUse.input, "old_string");
112
+ const newString = readString(input.toolUse.input, "new_string");
113
+ const replaceAll = Boolean(input.toolUse.input.replace_all);
114
+ after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
115
+ }
116
+ permission.diff = createUnifiedDiff(resolved.relativePath, before, after);
117
+ }
118
+ catch {
119
+ // Diff preview is best-effort
120
+ }
121
+ }
122
+ const approvalPermission = permission.decision === "ask" ? permission : undefined;
123
+ if (permission.decision === "ask") {
124
+ const approved = await input.approvalResolver?.({ toolUse: input.toolUse, permission });
125
+ if (!approved) {
126
+ return errorResult(input.toolUse, `Permission ask: ${permission.reason}`, permission);
127
+ }
128
+ }
129
+ else if (permission.decision !== "allow") {
130
+ return errorResult(input.toolUse, `Permission ${permission.decision}: ${permission.reason}`);
131
+ }
132
+ const raw = await tool.call(input.toolUse.input, context);
133
+ return {
134
+ toolCallId: input.toolUse.id,
135
+ toolName: input.toolUse.name,
136
+ content: formatToolResult({
137
+ content: raw,
138
+ outputRoot: context.outputRoot,
139
+ maxChars: 30_000,
140
+ previewChars: 2_000
141
+ }),
142
+ permission: approvalPermission
143
+ };
144
+ }
145
+ catch (error) {
146
+ if (shouldRethrowToolExecutionError(error)) {
147
+ throw error;
148
+ }
149
+ return errorResult(input.toolUse, error instanceof Error ? error.message : String(error));
150
+ }
151
+ }
152
+ function shouldRethrowToolExecutionError(error) {
153
+ if (error instanceof DOMException && error.name === "AbortError") {
154
+ return true;
155
+ }
156
+ if (!(error instanceof Error)) {
157
+ return false;
158
+ }
159
+ return error.name === "AbortError"
160
+ || error.name === "ActiveInteractionCancelledError"
161
+ || error.name === "ActiveInteractionTimeoutError";
162
+ }
163
+ export async function executeRegisteredTools(input) {
164
+ const registry = getBuiltinToolRegistry();
165
+ const results = new Array(input.toolUses.length);
166
+ const concurrent = [];
167
+ const sequential = [];
168
+ input.toolUses.forEach((toolUse, index) => {
169
+ const tool = registry.get(toolUse.name);
170
+ if (tool?.isConcurrencySafe(toolUse.input)) {
171
+ concurrent.push({ index, toolUse });
172
+ }
173
+ else {
174
+ sequential.push({ index, toolUse });
175
+ }
176
+ });
177
+ await Promise.all(concurrent.map(async ({ index, toolUse }) => {
178
+ results[index] = await executeRegisteredTool({ ...input, toolUse });
179
+ }));
180
+ for (const { index, toolUse } of sequential) {
181
+ results[index] = await executeRegisteredTool({ ...input, toolUse });
182
+ }
183
+ return results;
184
+ }
185
+ export function checkToolPermission(input) {
186
+ const tool = input.tool ?? getBuiltinToolRegistry().get(input.toolUse.name);
187
+ if (!tool) {
188
+ return { decision: "deny", reason: `Unknown tool: ${input.toolUse.name}` };
189
+ }
190
+ const context = {
191
+ cwd: ".",
192
+ permissionMode: input.mode,
193
+ rules: input.rules,
194
+ env: input.env
195
+ };
196
+ const custom = tool.checkPermissions?.(input.toolUse.input, context);
197
+ if (custom) {
198
+ return custom;
199
+ }
200
+ const ruleDecision = matchRules(input.toolUse, input.rules);
201
+ if (ruleDecision) {
202
+ return ruleDecision;
203
+ }
204
+ if (input.mode === "bypassPermissions" || input.mode === "acceptEdits") {
205
+ return { decision: "allow", reason: `${input.mode} mode` };
206
+ }
207
+ if (input.mode === "plan" && !tool.isReadOnly(input.toolUse.input)) {
208
+ return { decision: "deny", reason: `${input.toolUse.name} is not allowed in plan mode` };
209
+ }
210
+ if (input.mode === "default" && !tool.isReadOnly(input.toolUse.input)) {
211
+ return { decision: "ask", reason: `${input.toolUse.name} requires approval` };
212
+ }
213
+ return { decision: "allow", reason: "read-only tool" };
214
+ }
215
+ export function formatToolResult(input) {
216
+ const maxChars = input.maxChars ?? 30_000;
217
+ if (input.content.length <= maxChars) {
218
+ return input.content;
219
+ }
220
+ if (!input.outputRoot) {
221
+ return `${input.content.slice(0, input.previewChars ?? 2_000)}\n...[truncated]...`;
222
+ }
223
+ mkdirSync(input.outputRoot, { recursive: true });
224
+ const file = path.join(input.outputRoot, `${randomUUID()}.txt`);
225
+ writeFileSync(file, input.content, "utf8");
226
+ return `${input.content.slice(0, input.previewChars ?? 2_000)}\n...[truncated]...\n\nFull output saved to: ${file}`;
227
+ }
228
+ const BUILTIN_TOOLS = [
229
+ {
230
+ name: "FileRead",
231
+ description: "Read a UTF-8 text file inside the current workspace.",
232
+ category: "files",
233
+ tags: ["file", "read", "workspace"],
234
+ inputSchema: objectSchema({
235
+ file_path: { type: "string" },
236
+ max_bytes: { type: "number" }
237
+ }, ["file_path"]),
238
+ call: (input, context) => {
239
+ const result = readWorkspaceFile({
240
+ cwd: context.cwd,
241
+ filePath: readString(input, "file_path"),
242
+ maxBytes: readOptionalNumber(input, "max_bytes")
243
+ });
244
+ return `Read ${result.path} (${result.sizeBytes} bytes)\n${result.content}`;
245
+ },
246
+ isReadOnly: () => true,
247
+ isDestructive: () => false,
248
+ isConcurrencySafe: () => true
249
+ },
250
+ {
251
+ name: "FileWrite",
252
+ description: "Create or overwrite a UTF-8 text file inside the current workspace.",
253
+ category: "files",
254
+ tags: ["file", "write", "workspace"],
255
+ inputSchema: objectSchema({
256
+ file_path: { type: "string" },
257
+ content: { type: "string" }
258
+ }, ["file_path", "content"]),
259
+ call: (input, context) => {
260
+ const result = writeWorkspaceFile({
261
+ cwd: context.cwd,
262
+ filePath: readString(input, "file_path"),
263
+ content: readString(input, "content"),
264
+ approved: true
265
+ });
266
+ return `Wrote ${result.path}\n${result.diff}`;
267
+ },
268
+ isReadOnly: () => false,
269
+ isDestructive: () => true,
270
+ isConcurrencySafe: () => false
271
+ },
272
+ {
273
+ name: "FileEdit",
274
+ description: "Replace text in a UTF-8 file inside the current workspace.",
275
+ category: "files",
276
+ tags: ["file", "edit", "workspace"],
277
+ inputSchema: objectSchema({
278
+ file_path: { type: "string" },
279
+ old_string: { type: "string" },
280
+ new_string: { type: "string" },
281
+ replace_all: { type: "boolean" }
282
+ }, ["file_path", "old_string", "new_string"]),
283
+ call: (input, context) => {
284
+ const result = editWorkspaceFile({
285
+ cwd: context.cwd,
286
+ filePath: readString(input, "file_path"),
287
+ oldString: readString(input, "old_string"),
288
+ newString: readString(input, "new_string"),
289
+ replaceAll: readOptionalBoolean(input, "replace_all"),
290
+ approved: true
291
+ });
292
+ return `Wrote ${result.path}\n${result.diff}`;
293
+ },
294
+ isReadOnly: () => false,
295
+ isDestructive: () => false,
296
+ isConcurrencySafe: () => false
297
+ },
298
+ {
299
+ name: "NotebookEdit",
300
+ description: "Edit a Jupyter notebook (.ipynb) cell: replace, insert, or delete.",
301
+ category: "files",
302
+ tags: ["notebook", "jupyter", "ipynb", "edit"],
303
+ inputSchema: NotebookEditInputSchema,
304
+ call: (input, context) => {
305
+ const parsed = parseNotebookEditInput(input);
306
+ return executeNotebookEdit(context.cwd, parsed);
307
+ },
308
+ isReadOnly: () => false,
309
+ isDestructive: (input) => input.edit_mode === "delete",
310
+ isConcurrencySafe: () => false
311
+ },
312
+ {
313
+ name: "NotebookRead",
314
+ description: "Read a Jupyter notebook (.ipynb) and display cell contents.",
315
+ category: "files",
316
+ tags: ["notebook", "jupyter", "ipynb", "read"],
317
+ inputSchema: NotebookReadInputSchema,
318
+ call: (input, context) => {
319
+ const parsed = parseNotebookReadInput(input);
320
+ return executeNotebookRead(context.cwd, parsed);
321
+ },
322
+ isReadOnly: () => true,
323
+ isDestructive: () => false,
324
+ isConcurrencySafe: () => true
325
+ },
326
+ {
327
+ name: "Glob",
328
+ description: "Find files by glob pattern inside the current workspace.",
329
+ category: "search",
330
+ tags: ["glob", "file", "workspace"],
331
+ inputSchema: objectSchema({
332
+ pattern: { type: "string" },
333
+ path: { type: "string" },
334
+ max_matches: { type: "number" }
335
+ }, ["pattern"]),
336
+ call: (input, context) => {
337
+ const matches = globWorkspace({
338
+ cwd: context.cwd,
339
+ pattern: readString(input, "pattern"),
340
+ basePath: readOptionalString(input, "path"),
341
+ maxMatches: readOptionalNumber(input, "max_matches")
342
+ });
343
+ return matches.length === 0 ? "No matches" : matches.join("\n");
344
+ },
345
+ isReadOnly: () => true,
346
+ isDestructive: () => false,
347
+ isConcurrencySafe: () => true
348
+ },
349
+ {
350
+ name: "Grep",
351
+ description: "Search text in the current workspace with ripgrep-compatible options.",
352
+ category: "search",
353
+ tags: ["grep", "search", "ripgrep", "workspace"],
354
+ inputSchema: objectSchema({
355
+ pattern: { type: "string" },
356
+ path: { type: "string" },
357
+ glob: { type: "string" },
358
+ type: { type: "string" },
359
+ output_mode: { type: "string" },
360
+ max_matches: { type: "number" },
361
+ head_limit: { type: "number" },
362
+ ignore_case: { type: "boolean" },
363
+ fixed_strings: { type: "boolean" },
364
+ line_numbers: { type: "boolean" },
365
+ before_context: { type: "number" },
366
+ after_context: { type: "number" },
367
+ context: { type: "number" }
368
+ }, ["pattern"]),
369
+ call: (input, context) => {
370
+ const outputMode = readOutputMode(input, "output_mode");
371
+ const contextLines = readOptionalNumber(input, "context");
372
+ const matches = searchWorkspace({
373
+ cwd: context.cwd,
374
+ query: readString(input, "pattern"),
375
+ basePath: readOptionalString(input, "path"),
376
+ glob: readOptionalString(input, "glob"),
377
+ type: readOptionalString(input, "type"),
378
+ maxMatches: readOptionalNumber(input, "max_matches"),
379
+ headLimit: readOptionalNumber(input, "head_limit"),
380
+ ignoreCase: readOptionalBoolean(input, "ignore_case"),
381
+ fixedStrings: readOptionalBoolean(input, "fixed_strings"),
382
+ beforeContext: readOptionalNumber(input, "before_context") ?? contextLines,
383
+ afterContext: readOptionalNumber(input, "after_context") ?? contextLines
384
+ });
385
+ return formatSearchMatches(matches, {
386
+ outputMode,
387
+ pattern: readString(input, "pattern"),
388
+ lineNumbers: readOptionalBoolean(input, "line_numbers") ?? true
389
+ });
390
+ },
391
+ isReadOnly: () => true,
392
+ isDestructive: () => false,
393
+ isConcurrencySafe: () => true
394
+ },
395
+ {
396
+ name: "Bash",
397
+ description: "Run a shell command in the current workspace.",
398
+ category: "shell",
399
+ tags: ["bash", "command", "terminal"],
400
+ inputSchema: objectSchema({
401
+ command: { type: "string" },
402
+ timeout_ms: { type: "number" }
403
+ }, ["command"]),
404
+ call: async (input, context) => {
405
+ const result = await runShellCommand({
406
+ cwd: context.cwd,
407
+ command: readString(input, "command"),
408
+ timeoutMs: readOptionalNumber(input, "timeout_ms"),
409
+ approveDangerous: context.env?.MAGI_APPROVE_DANGEROUS_COMMANDS === "1",
410
+ signal: context.signal
411
+ });
412
+ return [
413
+ `Command exited ${result.exitCode}`,
414
+ result.stdout ? `stdout:\n${result.stdout.trimEnd()}` : undefined,
415
+ result.stderr ? `stderr:\n${result.stderr.trimEnd()}` : undefined
416
+ ].filter((line) => Boolean(line)).join("\n");
417
+ },
418
+ isReadOnly: () => false,
419
+ isDestructive: () => false,
420
+ isConcurrencySafe: () => false
421
+ },
422
+ {
423
+ name: "GitSummary",
424
+ description: "Return git branch, status, and diffstat for the current workspace.",
425
+ category: "git",
426
+ tags: ["git", "status", "diff"],
427
+ inputSchema: objectSchema({}, []),
428
+ call: (_input, context) => {
429
+ const git = getGitSummary(context.cwd);
430
+ if (!git.gitAvailable || !git.isRepository) {
431
+ return git.reason ?? "Git summary is unavailable";
432
+ }
433
+ return [
434
+ `branch: ${git.branch}`,
435
+ git.status ? `status:\n${git.status}` : "status: clean",
436
+ git.diffStat ? `diffStat:\n${git.diffStat}` : "diffStat: none"
437
+ ].join("\n");
438
+ },
439
+ isReadOnly: () => true,
440
+ isDestructive: () => false,
441
+ isConcurrencySafe: () => true
442
+ },
443
+ {
444
+ name: "GitStatus",
445
+ description: "Return git working tree status for the current workspace.",
446
+ category: "git",
447
+ tags: ["git", "status", "workspace"],
448
+ inputSchema: objectSchema({
449
+ path: { type: "string" },
450
+ branch: { type: "boolean" },
451
+ porcelain: { type: "boolean" },
452
+ untracked: { type: "string" }
453
+ }, []),
454
+ call: (input, context) => getGitStatus(context.cwd, {
455
+ path: readOptionalString(input, "path"),
456
+ branch: readOptionalBoolean(input, "branch"),
457
+ porcelain: readOptionalBoolean(input, "porcelain"),
458
+ untracked: readGitUntracked(input, "untracked")
459
+ }),
460
+ isReadOnly: () => true,
461
+ isDestructive: () => false,
462
+ isConcurrencySafe: () => true
463
+ },
464
+ {
465
+ name: "GitDiff",
466
+ description: "Return git diff output for unstaged or staged workspace changes.",
467
+ category: "git",
468
+ tags: ["git", "diff", "workspace"],
469
+ inputSchema: objectSchema({
470
+ path: { type: "string" },
471
+ staged: { type: "boolean" },
472
+ stat: { type: "boolean" },
473
+ name_only: { type: "boolean" },
474
+ context: { type: "number" }
475
+ }, []),
476
+ call: (input, context) => getGitDiff(context.cwd, {
477
+ path: readOptionalString(input, "path"),
478
+ staged: readOptionalBoolean(input, "staged"),
479
+ stat: readOptionalBoolean(input, "stat"),
480
+ nameOnly: readOptionalBoolean(input, "name_only"),
481
+ context: readOptionalNumber(input, "context")
482
+ }),
483
+ isReadOnly: () => true,
484
+ isDestructive: () => false,
485
+ isConcurrencySafe: () => true
486
+ },
487
+ {
488
+ name: "GitLog",
489
+ description: "Return recent git commits for the current workspace.",
490
+ category: "git",
491
+ tags: ["git", "log", "commits"],
492
+ inputSchema: objectSchema({
493
+ path: { type: "string" },
494
+ max_count: { type: "number" },
495
+ oneline: { type: "boolean" }
496
+ }, []),
497
+ call: (input, context) => getGitLog(context.cwd, {
498
+ path: readOptionalString(input, "path"),
499
+ maxCount: readOptionalNumber(input, "max_count"),
500
+ oneline: readOptionalBoolean(input, "oneline")
501
+ }),
502
+ isReadOnly: () => true,
503
+ isDestructive: () => false,
504
+ isConcurrencySafe: () => true
505
+ },
506
+ {
507
+ name: "GitShow",
508
+ description: "Return a git object or commit using a simple revision name, tag, or commit id.",
509
+ category: "git",
510
+ tags: ["git", "show", "commit"],
511
+ inputSchema: objectSchema({
512
+ rev: { type: "string" },
513
+ stat: { type: "boolean" },
514
+ name_only: { type: "boolean" },
515
+ max_bytes: { type: "number" }
516
+ }, []),
517
+ call: (input, context) => getGitShow(context.cwd, {
518
+ rev: readOptionalString(input, "rev"),
519
+ stat: readOptionalBoolean(input, "stat"),
520
+ nameOnly: readOptionalBoolean(input, "name_only"),
521
+ maxBytes: readOptionalNumber(input, "max_bytes")
522
+ }),
523
+ isReadOnly: () => true,
524
+ isDestructive: () => false,
525
+ isConcurrencySafe: () => true
526
+ },
527
+ {
528
+ name: "GitBranchList",
529
+ description: "List local or all branches for the current git workspace.",
530
+ category: "git",
531
+ tags: ["git", "branch", "list"],
532
+ inputSchema: objectSchema({
533
+ all: { type: "boolean" }
534
+ }, []),
535
+ call: (input, context) => listGitBranches(context.cwd, {
536
+ all: readOptionalBoolean(input, "all")
537
+ }),
538
+ isReadOnly: () => true,
539
+ isDestructive: () => false,
540
+ isConcurrencySafe: () => true
541
+ },
542
+ {
543
+ name: "GitBranchCreate",
544
+ description: "Create a new git branch, optionally checking it out.",
545
+ category: "git",
546
+ tags: ["git", "branch", "create", "mutation"],
547
+ inputSchema: objectSchema({
548
+ name: { type: "string" },
549
+ start_point: { type: "string" },
550
+ checkout: { type: "boolean" }
551
+ }, ["name"]),
552
+ call: (input, context) => createGitBranch(context.cwd, {
553
+ name: readString(input, "name"),
554
+ startPoint: readOptionalString(input, "start_point"),
555
+ checkout: readOptionalBoolean(input, "checkout")
556
+ }),
557
+ isReadOnly: () => false,
558
+ isDestructive: () => false,
559
+ isConcurrencySafe: () => false
560
+ },
561
+ {
562
+ name: "GitCheckout",
563
+ description: "Check out an existing git branch, or create and check out a new branch.",
564
+ category: "git",
565
+ tags: ["git", "checkout", "branch", "mutation"],
566
+ inputSchema: objectSchema({
567
+ branch: { type: "string" },
568
+ create: { type: "boolean" },
569
+ start_point: { type: "string" }
570
+ }, ["branch"]),
571
+ call: (input, context) => checkoutGitBranch(context.cwd, {
572
+ branch: readString(input, "branch"),
573
+ create: readOptionalBoolean(input, "create"),
574
+ startPoint: readOptionalString(input, "start_point")
575
+ }),
576
+ isReadOnly: () => false,
577
+ isDestructive: () => false,
578
+ isConcurrencySafe: () => false
579
+ },
580
+ {
581
+ name: "GitStage",
582
+ description: "Stage or unstage specific workspace paths.",
583
+ category: "git",
584
+ tags: ["git", "stage", "index", "mutation"],
585
+ inputSchema: objectSchema({
586
+ paths: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 100 },
587
+ mode: { type: "string" }
588
+ }, ["paths"]),
589
+ call: (input, context) => stageGitPaths(context.cwd, {
590
+ paths: readStringArray(input, "paths"),
591
+ mode: readGitStageMode(input, "mode")
592
+ }),
593
+ isReadOnly: () => false,
594
+ isDestructive: (input) => input.mode === "unstage",
595
+ isConcurrencySafe: () => false
596
+ },
597
+ {
598
+ name: "WebFetch",
599
+ description: "Fetch a web page and use the active model to extract information from it.",
600
+ category: "web",
601
+ tags: ["web", "fetch", "http", "summarize"],
602
+ inputSchema: objectSchema({
603
+ url: { type: "string" },
604
+ prompt: { type: "string" },
605
+ max_bytes: { type: "number" }
606
+ }, ["url", "prompt"]),
607
+ call: async (input, context) => {
608
+ if (!context.promptModel) {
609
+ throw new Error("WebFetch requires an active model route");
610
+ }
611
+ const result = await webFetch({
612
+ url: readString(input, "url"),
613
+ prompt: readString(input, "prompt"),
614
+ maxBytes: readOptionalNumber(input, "max_bytes"),
615
+ promptModel: context.promptModel
616
+ });
617
+ return [
618
+ `Title: ${result.title}`,
619
+ `URL: ${result.url}`,
620
+ `Fetched bytes: ${result.fetchedBytes}`,
621
+ "",
622
+ result.summary
623
+ ].join("\n");
624
+ },
625
+ isReadOnly: () => true,
626
+ isDestructive: () => false,
627
+ isConcurrencySafe: () => false,
628
+ checkPermissions: (input, context) => {
629
+ const url = readString(input, "url");
630
+ const allowed = webFetchHostAllowed(url, readWebFetchAllowlist(context.env));
631
+ if (allowed) {
632
+ return { decision: "allow", reason: "WebFetch URL is allowlisted" };
633
+ }
634
+ if (context.permissionMode === "bypassPermissions") {
635
+ return { decision: "allow", reason: "bypassPermissions mode" };
636
+ }
637
+ return { decision: "ask", reason: `WebFetch requires approval for ${new URL(url).hostname}` };
638
+ }
639
+ },
640
+ {
641
+ name: "WebSearch",
642
+ description: "Search the web through the configured Magi Next HTTP JSON search provider.",
643
+ category: "web",
644
+ tags: ["web", "search", "http", "sources"],
645
+ inputSchema: WebSearchInputSchema,
646
+ call: async (input, context) => {
647
+ const request = parseWebSearchInput(input);
648
+ // Fallback: if no endpoint configured, silently use WebBrowser (Bing HTML)
649
+ if (!context.webSearchConfig || !context.webSearchConfig.endpoint) {
650
+ const browserResult = await executeWebBrowser({
651
+ action: "search",
652
+ query: request.query,
653
+ maxResults: request.maxResults
654
+ });
655
+ return formatWebBrowserResult(browserResult);
656
+ }
657
+ const result = await webSearch({
658
+ request,
659
+ config: context.webSearchConfig,
660
+ env: context.env
661
+ });
662
+ return formatWebSearchResult(result);
663
+ },
664
+ isReadOnly: () => true,
665
+ isDestructive: () => false,
666
+ isConcurrencySafe: () => false
667
+ },
668
+ {
669
+ name: "WebBrowser",
670
+ description: "Browse the web: search via DuckDuckGo (no API key needed) or fetch a URL and extract readable text.",
671
+ category: "web",
672
+ tags: ["web", "browser", "search", "fetch", "duckduckgo"],
673
+ inputSchema: WebBrowserInputSchema,
674
+ call: async (input) => {
675
+ const parsed = parseWebBrowserInput(input);
676
+ const result = await executeWebBrowser(parsed);
677
+ return formatWebBrowserResult(result);
678
+ },
679
+ isReadOnly: () => true,
680
+ isDestructive: () => false,
681
+ isConcurrencySafe: () => false
682
+ },
683
+ {
684
+ name: "AskUserQuestion",
685
+ description: "Ask the user 1 to 4 structured multiple-choice questions and return their selections.",
686
+ category: "communication",
687
+ tags: ["user", "question", "approval", "choice"],
688
+ inputSchema: ASK_USER_QUESTION_SCHEMA,
689
+ call: async (input, context) => {
690
+ if (!context.userQuestionResolver) {
691
+ throw new Error("AskUserQuestion requires an interactive user question resolver");
692
+ }
693
+ const request = parseAskUserQuestionInput(input);
694
+ const rawAnswer = await context.userQuestionResolver({
695
+ toolUse: context.toolUse ?? { type: "tool-use", id: "AskUserQuestion", name: "AskUserQuestion", input },
696
+ question: request
697
+ });
698
+ return formatAskUserQuestionAnswer(normalizeAskUserQuestionAnswer(request, rawAnswer));
699
+ },
700
+ isReadOnly: () => true,
701
+ isDestructive: () => false,
702
+ isConcurrencySafe: () => false
703
+ },
704
+ {
705
+ name: "SendUserMessage",
706
+ description: "Send a markdown message from the agent to the user, optionally with attachments.",
707
+ category: "communication",
708
+ tags: ["user", "message", "notification"],
709
+ inputSchema: SEND_USER_MESSAGE_SCHEMA,
710
+ call: async (input, context) => {
711
+ const request = parseSendUserMessageInput(input);
712
+ const result = await (context.userMessageSink ?? defaultUserMessageSink)({
713
+ toolUse: context.toolUse ?? { type: "tool-use", id: "SendUserMessage", name: "SendUserMessage", input },
714
+ message: request
715
+ });
716
+ return formatSendUserMessageResult(request, result);
717
+ },
718
+ isReadOnly: () => true,
719
+ isDestructive: () => false,
720
+ isConcurrencySafe: () => false
721
+ },
722
+ {
723
+ name: "Brief",
724
+ description: "Alias of SendUserMessage for concise agent-to-user updates.",
725
+ category: "communication",
726
+ tags: ["user", "message", "brief"],
727
+ inputSchema: SEND_USER_MESSAGE_SCHEMA,
728
+ call: async (input, context) => {
729
+ const request = parseSendUserMessageInput(input);
730
+ const result = await (context.userMessageSink ?? defaultUserMessageSink)({
731
+ toolUse: context.toolUse ?? { type: "tool-use", id: "Brief", name: "Brief", input },
732
+ message: request
733
+ });
734
+ return formatSendUserMessageResult(request, result);
735
+ },
736
+ isReadOnly: () => true,
737
+ isDestructive: () => false,
738
+ isConcurrencySafe: () => false
739
+ },
740
+ {
741
+ name: "CronCreate",
742
+ description: "Create a 5-field local-time cron job that queues a prompt for future execution.",
743
+ category: "schedule",
744
+ tags: ["cron", "schedule", "job"],
745
+ inputSchema: CRON_CREATE_SCHEMA,
746
+ call: (input, context) => {
747
+ const job = addCronJob(requireStateFile(context), {
748
+ cron: readString(input, "cron"),
749
+ prompt: readString(input, "prompt"),
750
+ recurring: readOptionalBoolean(input, "recurring"),
751
+ durable: readOptionalBoolean(input, "durable")
752
+ });
753
+ return `Created cron job\n${formatCronJob(job)}`;
754
+ },
755
+ isReadOnly: () => false,
756
+ isDestructive: () => false,
757
+ isConcurrencySafe: () => false
758
+ },
759
+ {
760
+ name: "CronUpdate",
761
+ description: "Update an existing cron job by id.",
762
+ category: "schedule",
763
+ tags: ["cron", "schedule", "update"],
764
+ inputSchema: CRON_UPDATE_SCHEMA,
765
+ call: (input, context) => {
766
+ const job = applyCronUpdate(requireStateFile(context), {
767
+ id: readString(input, "id"),
768
+ cron: readOptionalString(input, "cron"),
769
+ prompt: readOptionalString(input, "prompt"),
770
+ recurring: readOptionalBoolean(input, "recurring"),
771
+ durable: readOptionalBoolean(input, "durable"),
772
+ enabled: readOptionalBoolean(input, "enabled")
773
+ });
774
+ return `Updated cron job\n${formatCronJob(job)}`;
775
+ },
776
+ isReadOnly: () => false,
777
+ isDestructive: () => false,
778
+ isConcurrencySafe: () => false
779
+ },
780
+ {
781
+ name: "CronDelete",
782
+ description: "Delete an existing cron job by id.",
783
+ category: "schedule",
784
+ tags: ["cron", "schedule", "delete"],
785
+ inputSchema: CRON_DELETE_SCHEMA,
786
+ call: (input, context) => {
787
+ const job = deleteCronJob(requireStateFile(context), readString(input, "id"));
788
+ return `Deleted cron job\n${formatCronJob(job)}`;
789
+ },
790
+ isReadOnly: () => false,
791
+ isDestructive: () => true,
792
+ isConcurrencySafe: () => false
793
+ },
794
+ {
795
+ name: "CronList",
796
+ description: "List all configured cron jobs.",
797
+ category: "schedule",
798
+ tags: ["cron", "schedule", "list"],
799
+ inputSchema: CRON_LIST_SCHEMA,
800
+ call: (_input, context) => formatCronList(listCronJobs(requireStateFile(context))),
801
+ isReadOnly: () => true,
802
+ isDestructive: () => false,
803
+ isConcurrencySafe: () => false
804
+ },
805
+ {
806
+ name: "TodoWrite",
807
+ description: "Replace the current session todo list with a validated complete list.",
808
+ category: "state",
809
+ tags: ["todo", "plan", "state"],
810
+ inputSchema: TodoWriteInputSchema,
811
+ call: async (input, context) => {
812
+ const todoContext = requireTodoContext(context);
813
+ return formatTodoWriteResult(await replaceTodoList({
814
+ stateRoot: todoContext.stateRoot,
815
+ sessionId: todoContext.sessionId,
816
+ todos: parseTodoWriteInput(input)
817
+ }));
818
+ },
819
+ isReadOnly: () => false,
820
+ isDestructive: () => true,
821
+ isConcurrencySafe: () => false
822
+ },
823
+ {
824
+ name: "TaskCreate",
825
+ description: "Create a new task to track progress on multi-step work.",
826
+ category: "state",
827
+ tags: ["task", "create", "progress"],
828
+ inputSchema: TaskCreateInputSchema,
829
+ call: (input, context) => {
830
+ const todoContext = requireTodoContext(context);
831
+ return formatTaskCreateResult(executeTaskCreate({
832
+ stateRoot: todoContext.stateRoot,
833
+ sessionId: todoContext.sessionId,
834
+ task: parseTaskCreateInput(input)
835
+ }));
836
+ },
837
+ isReadOnly: () => false,
838
+ isDestructive: () => false,
839
+ isConcurrencySafe: () => false
840
+ },
841
+ {
842
+ name: "TaskUpdate",
843
+ description: "Update a task's status, subject, or priority. Set status to 'deleted' to remove.",
844
+ category: "state",
845
+ tags: ["task", "update", "progress"],
846
+ inputSchema: TaskUpdateInputSchema,
847
+ call: (input, context) => {
848
+ const todoContext = requireTodoContext(context);
849
+ return formatTaskUpdateResult(executeTaskUpdate({
850
+ stateRoot: todoContext.stateRoot,
851
+ sessionId: todoContext.sessionId,
852
+ update: parseTaskUpdateInput(input)
853
+ }));
854
+ },
855
+ isReadOnly: () => false,
856
+ isDestructive: (input) => input.status === "deleted",
857
+ isConcurrencySafe: () => false
858
+ },
859
+ {
860
+ name: "TaskList",
861
+ description: "List all tasks in the current session.",
862
+ category: "state",
863
+ tags: ["task", "list", "progress"],
864
+ inputSchema: TaskListInputSchema,
865
+ call: (_input, context) => {
866
+ const todoContext = requireTodoContext(context);
867
+ return formatTaskListResult(executeTaskList({
868
+ stateRoot: todoContext.stateRoot,
869
+ sessionId: todoContext.sessionId
870
+ }));
871
+ },
872
+ isReadOnly: () => true,
873
+ isDestructive: () => false,
874
+ isConcurrencySafe: () => true
875
+ },
876
+ {
877
+ name: "TaskGet",
878
+ description: "Retrieve a task by ID with full details including description and status.",
879
+ category: "state",
880
+ tags: ["task", "get", "detail"],
881
+ inputSchema: TaskGetInputSchema,
882
+ call: (input, context) => {
883
+ const todoContext = requireTodoContext(context);
884
+ return formatTaskGetResult(executeTaskGet({
885
+ stateRoot: todoContext.stateRoot,
886
+ sessionId: todoContext.sessionId,
887
+ taskId: readString(input, "taskId")
888
+ }));
889
+ },
890
+ isReadOnly: () => true,
891
+ isDestructive: () => false,
892
+ isConcurrencySafe: () => true
893
+ },
894
+ {
895
+ name: "TaskOutput",
896
+ description: "Retrieve output from a running or completed background task.",
897
+ category: "state",
898
+ tags: ["task", "output", "background"],
899
+ inputSchema: TaskOutputInputSchema,
900
+ call: async (input, context) => {
901
+ const todoContext = requireTodoContext(context);
902
+ return formatTaskOutputResult(await executeTaskOutput({
903
+ stateRoot: todoContext.stateRoot,
904
+ sessionId: todoContext.sessionId,
905
+ taskId: readString(input, "taskId")
906
+ }));
907
+ },
908
+ isReadOnly: () => true,
909
+ isDestructive: () => false,
910
+ isConcurrencySafe: () => true
911
+ },
912
+ {
913
+ name: "TaskStop",
914
+ description: "Stop a running background task by its ID.",
915
+ category: "state",
916
+ tags: ["task", "stop", "cancel"],
917
+ inputSchema: TaskStopInputSchema,
918
+ call: async (input, context) => {
919
+ const todoContext = requireTodoContext(context);
920
+ return formatTaskStopResult(await executeTaskStop({
921
+ stateRoot: todoContext.stateRoot,
922
+ sessionId: todoContext.sessionId,
923
+ taskId: readString(input, "taskId")
924
+ }));
925
+ },
926
+ isReadOnly: () => false,
927
+ isDestructive: () => false,
928
+ isConcurrencySafe: () => false
929
+ },
930
+ {
931
+ name: "ToolSearch",
932
+ description: "Search built-in tool documentation or select one tool by name for its full schema.",
933
+ category: "tools",
934
+ tags: ["tool", "search", "schema", "docs"],
935
+ inputSchema: ToolSearchInputSchema,
936
+ call: (input) => executeToolSearch(parseToolSearchInput(input), BUILTIN_TOOLS),
937
+ isReadOnly: () => true,
938
+ isDestructive: () => false,
939
+ isConcurrencySafe: () => true
940
+ },
941
+ {
942
+ name: "WorkspaceDiagnostics",
943
+ description: "Inspect the current workspace for manifests, languages, package scripts, suggested commands, and git status without executing project commands.",
944
+ category: "workspace",
945
+ tags: ["workspace", "diagnostics", "scripts", "setup"],
946
+ inputSchema: WorkspaceDiagnosticsInputSchema,
947
+ call: (input, context) => {
948
+ const request = parseWorkspaceDiagnosticsInput(input);
949
+ return formatWorkspaceDiagnostics(runWorkspaceDiagnostics({
950
+ cwd: context.cwd,
951
+ request
952
+ }), request.format);
953
+ },
954
+ isReadOnly: () => true,
955
+ isDestructive: () => false,
956
+ isConcurrencySafe: () => true
957
+ },
958
+ {
959
+ name: "Config",
960
+ description: "Read or update allowlisted Magi Next configuration settings.",
961
+ category: "config",
962
+ tags: ["config", "settings", "magi"],
963
+ inputSchema: ConfigToolInputSchema,
964
+ call: (input, context) => executeConfigTool({
965
+ request: parseConfigToolInput(input),
966
+ configFile: requireConfigFile(context),
967
+ env: context.env
968
+ }),
969
+ isReadOnly: (input) => input.value === undefined,
970
+ isDestructive: () => false,
971
+ isConcurrencySafe: (input) => input.value === undefined
972
+ },
973
+ {
974
+ name: "Memorize",
975
+ description: "Save a durable memory (memdir entry) for future conversations. Use sparingly for genuine user preferences, project facts, corrections, or reference pointers — not for ephemeral conversation state.",
976
+ category: "memory",
977
+ tags: ["memory", "memdir", "persist"],
978
+ inputSchema: {
979
+ type: "object",
980
+ additionalProperties: false,
981
+ properties: {
982
+ type: {
983
+ type: "string",
984
+ enum: ["user", "feedback", "project", "reference"],
985
+ description: "Memory type. user=facts about the user, feedback=corrections/preferences, project=ongoing work facts, reference=pointers to external systems."
986
+ },
987
+ name: { type: "string", minLength: 1, maxLength: 80, description: "Short title (e.g. 'User role', 'Prefers tabs over spaces')." },
988
+ description: { type: "string", minLength: 1, maxLength: 200, description: "One-line description used to decide relevance in future conversations." },
989
+ body: { type: "string", minLength: 1, maxLength: 4000, description: "Memory content. For feedback/project entries, include why and how to apply." }
990
+ },
991
+ required: ["type", "name", "description", "body"]
992
+ },
993
+ call: (input, context) => {
994
+ const type = String(input.type ?? "");
995
+ if (!isValidMemdirType(type)) {
996
+ throw new Error(`Invalid memory type: ${type}`);
997
+ }
998
+ const name = String(input.name ?? "").trim();
999
+ const description = String(input.description ?? "").trim();
1000
+ const body = String(input.body ?? "").trim();
1001
+ if (!name || !description || !body) {
1002
+ throw new Error("Memorize requires name, description, and body");
1003
+ }
1004
+ if (!context.stateRoot) {
1005
+ throw new Error("Memorize requires Magi stateRoot");
1006
+ }
1007
+ const root = path.dirname(context.stateRoot);
1008
+ const entry = writeMemdirEntry({
1009
+ paths: { root },
1010
+ type,
1011
+ name,
1012
+ description,
1013
+ body
1014
+ });
1015
+ return `Saved memory: ${entry.filename} (type=${entry.type})`;
1016
+ },
1017
+ isReadOnly: () => false,
1018
+ isDestructive: () => false,
1019
+ isConcurrencySafe: () => false
1020
+ },
1021
+ {
1022
+ name: "Skill",
1023
+ description: "List installed Magi Next skills or load one skill's instructions by name.",
1024
+ category: "skills",
1025
+ tags: ["skill", "instructions", "workflow"],
1026
+ inputSchema: SkillToolInputSchema,
1027
+ call: (input, context) => executeSkillTool({
1028
+ request: parseSkillToolInput(input),
1029
+ skillsRoot: requireSkillsRoot(context)
1030
+ }),
1031
+ isReadOnly: () => true,
1032
+ isDestructive: () => false,
1033
+ isConcurrencySafe: () => true
1034
+ },
1035
+ {
1036
+ name: "LSP",
1037
+ description: "Run TypeScript/JavaScript workspace symbol, definition, reference, and hover queries.",
1038
+ category: "lsp",
1039
+ tags: ["lsp", "typescript", "symbols", "references"],
1040
+ inputSchema: LSP_SCHEMA,
1041
+ call: (input, context) => executeLspRequest({
1042
+ cwd: context.cwd,
1043
+ request: parseLspRequest(input)
1044
+ }),
1045
+ isReadOnly: () => true,
1046
+ isDestructive: () => false,
1047
+ isConcurrencySafe: () => true
1048
+ },
1049
+ {
1050
+ name: "EnterPlanMode",
1051
+ description: "Switch to plan mode for non-trivial tasks. Blocks mutations until the plan is approved by the user.",
1052
+ category: "planning",
1053
+ tags: ["plan", "mode", "architecture"],
1054
+ inputSchema: EnterPlanModeInputSchema,
1055
+ call: (input) => formatEnterPlanModeResult(parseEnterPlanModeInput(input)),
1056
+ isReadOnly: () => true,
1057
+ isDestructive: () => false,
1058
+ isConcurrencySafe: () => true
1059
+ },
1060
+ {
1061
+ name: "ExitPlanMode",
1062
+ description: "Submit the implementation plan for user approval and exit plan mode.",
1063
+ category: "planning",
1064
+ tags: ["plan", "mode", "approval"],
1065
+ inputSchema: ExitPlanModeInputSchema,
1066
+ call: async (input, context) => {
1067
+ const parsed = parseExitPlanModeInput(input);
1068
+ // Ask the user to approve or reject the plan
1069
+ if (context.userQuestionResolver && context.toolUse) {
1070
+ try {
1071
+ const answer = await context.userQuestionResolver({
1072
+ toolUse: context.toolUse,
1073
+ question: {
1074
+ questions: [
1075
+ {
1076
+ question: "Do you approve this plan and want me to proceed with implementation?",
1077
+ header: "Plan review",
1078
+ options: [
1079
+ { label: "Yes, proceed", description: "Approve the plan and start implementing" },
1080
+ { label: "No, revise", description: "I want to give feedback or change the approach" }
1081
+ ],
1082
+ multiSelect: false
1083
+ }
1084
+ ]
1085
+ }
1086
+ });
1087
+ const selection = answer.answers?.[0];
1088
+ const approved = selection?.selectedLabels?.includes("Yes, proceed") ?? false;
1089
+ if (approved) {
1090
+ return [
1091
+ "Plan approved. Proceeding with implementation.",
1092
+ "",
1093
+ "---",
1094
+ parsed.plan,
1095
+ "---"
1096
+ ].join("\n");
1097
+ }
1098
+ const feedback = selection?.selectedLabels?.join(", ") ?? "User wants to revise the plan.";
1099
+ return [
1100
+ `Plan not approved. User response: ${feedback}`,
1101
+ "",
1102
+ "Stay in plan mode. Revise the approach based on the feedback above and call ExitPlanMode again with an updated plan."
1103
+ ].join("\n");
1104
+ }
1105
+ catch {
1106
+ return formatExitPlanModeResult(parsed);
1107
+ }
1108
+ }
1109
+ return formatExitPlanModeResult(parsed);
1110
+ },
1111
+ isReadOnly: () => true,
1112
+ isDestructive: () => false,
1113
+ isConcurrencySafe: () => true
1114
+ },
1115
+ {
1116
+ name: "EnterWorktree",
1117
+ description: "Create an isolated git worktree for safe agent execution. Changes do not affect the main working tree.",
1118
+ category: "git",
1119
+ tags: ["git", "worktree", "isolation", "mutation"],
1120
+ inputSchema: EnterWorktreeInputSchema,
1121
+ call: async (input, context) => {
1122
+ const parsed = parseEnterWorktreeInput(input);
1123
+ const state = executeEnterWorktree({ cwd: context.cwd, name: parsed.name });
1124
+ // Persist worktree state in the session so ExitWorktree can find it
1125
+ if (context.sessionId && context.stateRoot) {
1126
+ try {
1127
+ const dbPath = path.join(context.stateRoot, "sessions.sqlite");
1128
+ if (existsSync(dbPath)) {
1129
+ const mod = await import("../session-store.js");
1130
+ const store = new mod.SessionStore(dbPath);
1131
+ try {
1132
+ store.updateSessionMetadata(context.sessionId, { worktree: state });
1133
+ }
1134
+ finally {
1135
+ store.close();
1136
+ }
1137
+ }
1138
+ }
1139
+ catch {
1140
+ // Best effort — the worktree itself is created, state just isn't persisted
1141
+ }
1142
+ }
1143
+ return formatEnterWorktreeResult(state);
1144
+ },
1145
+ isReadOnly: () => false,
1146
+ isDestructive: () => false,
1147
+ isConcurrencySafe: () => false
1148
+ },
1149
+ {
1150
+ name: "ExitWorktree",
1151
+ description: "Exit the current worktree session. Use action 'keep' to preserve or 'remove' to delete.",
1152
+ category: "git",
1153
+ tags: ["git", "worktree", "cleanup"],
1154
+ inputSchema: ExitWorktreeInputSchema,
1155
+ call: async (input, context) => {
1156
+ const parsed = parseExitWorktreeInput(input);
1157
+ // Try to find the worktree state for this session
1158
+ let state;
1159
+ if (context.sessionId && context.stateRoot) {
1160
+ try {
1161
+ const dbPath = path.join(context.stateRoot, "sessions.sqlite");
1162
+ if (existsSync(dbPath)) {
1163
+ const mod = await import("../session-store.js");
1164
+ const store = new mod.SessionStore(dbPath);
1165
+ try {
1166
+ const session = store.getSession(context.sessionId);
1167
+ const meta = session?.metadata;
1168
+ if (meta && meta.worktree && typeof meta.worktree === "object") {
1169
+ state = meta.worktree;
1170
+ }
1171
+ if (parsed.action === "remove" && state) {
1172
+ // Clear from metadata
1173
+ store.updateSessionMetadata(context.sessionId, { worktree: undefined });
1174
+ }
1175
+ }
1176
+ finally {
1177
+ store.close();
1178
+ }
1179
+ }
1180
+ }
1181
+ catch {
1182
+ // Best effort
1183
+ }
1184
+ }
1185
+ if (!state) {
1186
+ return [
1187
+ "No active worktree session found.",
1188
+ "If you created a worktree manually with `git worktree add`, this tool only operates on worktrees created by EnterWorktree."
1189
+ ].join("\n");
1190
+ }
1191
+ const result = executeExitWorktree({
1192
+ cwd: context.cwd,
1193
+ state,
1194
+ action: parsed.action,
1195
+ discardChanges: parsed.discardChanges
1196
+ });
1197
+ return formatExitWorktreeResult(result);
1198
+ },
1199
+ isReadOnly: () => false,
1200
+ isDestructive: (input) => input.action === "remove",
1201
+ isConcurrencySafe: () => false
1202
+ },
1203
+ {
1204
+ name: "GitHubIssueView",
1205
+ description: "View a GitHub issue by number or URL using the gh CLI.",
1206
+ category: "github",
1207
+ tags: ["github", "issue", "read"],
1208
+ inputSchema: GitHubIssueViewInputSchema,
1209
+ call: (input, context) => ghIssueView(context.cwd, readString(input, "issue")),
1210
+ isReadOnly: () => true,
1211
+ isDestructive: () => false,
1212
+ isConcurrencySafe: () => true
1213
+ },
1214
+ {
1215
+ name: "GitHubPRView",
1216
+ description: "View a GitHub pull request by number or URL using the gh CLI.",
1217
+ category: "github",
1218
+ tags: ["github", "pr", "read"],
1219
+ inputSchema: GitHubPRViewInputSchema,
1220
+ call: (input, context) => ghPRView(context.cwd, readString(input, "pr")),
1221
+ isReadOnly: () => true,
1222
+ isDestructive: () => false,
1223
+ isConcurrencySafe: () => true
1224
+ },
1225
+ {
1226
+ name: "GitHubPRList",
1227
+ description: "List GitHub pull requests for the current repository.",
1228
+ category: "github",
1229
+ tags: ["github", "pr", "list"],
1230
+ inputSchema: GitHubPRListInputSchema,
1231
+ call: (input, context) => ghPRList(context.cwd, {
1232
+ state: readOptionalString(input, "state"),
1233
+ limit: readOptionalNumber(input, "limit"),
1234
+ author: readOptionalString(input, "author"),
1235
+ label: readOptionalString(input, "label")
1236
+ }),
1237
+ isReadOnly: () => true,
1238
+ isDestructive: () => false,
1239
+ isConcurrencySafe: () => true
1240
+ },
1241
+ {
1242
+ name: "GitHubPRDiff",
1243
+ description: "View the diff of a GitHub pull request.",
1244
+ category: "github",
1245
+ tags: ["github", "pr", "diff"],
1246
+ inputSchema: GitHubPRDiffInputSchema,
1247
+ call: (input, context) => ghPRDiff(context.cwd, readString(input, "pr")),
1248
+ isReadOnly: () => true,
1249
+ isDestructive: () => false,
1250
+ isConcurrencySafe: () => true
1251
+ },
1252
+ {
1253
+ name: "Snip",
1254
+ description: "Take a screenshot of the current screen and save it to a file.",
1255
+ category: "system",
1256
+ tags: ["screenshot", "snip", "image"],
1257
+ inputSchema: SnipInputSchema,
1258
+ call: async (input, context) => {
1259
+ const parsed = parseSnipInput(input);
1260
+ const result = await executeSnip({
1261
+ format: parsed.format,
1262
+ cwd: context.cwd
1263
+ });
1264
+ return formatSnipResult(result);
1265
+ },
1266
+ isReadOnly: () => true,
1267
+ isDestructive: () => false,
1268
+ isConcurrencySafe: () => true
1269
+ },
1270
+ {
1271
+ name: "SshExec",
1272
+ description: "Run a shell command on a remote host via SSH.",
1273
+ category: "ssh",
1274
+ tags: ["ssh", "remote", "shell"],
1275
+ inputSchema: objectSchema({
1276
+ host: { type: "string" },
1277
+ command: { type: "string" },
1278
+ user: { type: "string" },
1279
+ port: { type: "number" },
1280
+ timeoutMs: { type: "number" }
1281
+ }, ["host", "command"]),
1282
+ call: async (input, context) => {
1283
+ const result = await sshExec({
1284
+ host: readString(input, "host"),
1285
+ command: readString(input, "command"),
1286
+ user: readOptionalString(input, "user"),
1287
+ port: readOptionalNumber(input, "port"),
1288
+ timeoutMs: readOptionalNumber(input, "timeoutMs")
1289
+ });
1290
+ return [
1291
+ `Host: ${result.host}`,
1292
+ `Command: ${result.command}`,
1293
+ `Exit code: ${result.exitCode}`,
1294
+ result.stdout ? `\n${result.stdout}` : "",
1295
+ result.stderr ? `\nstderr:\n${result.stderr}` : ""
1296
+ ].filter(Boolean).join("\n");
1297
+ },
1298
+ isReadOnly: () => false,
1299
+ isDestructive: () => false,
1300
+ isConcurrencySafe: () => false
1301
+ },
1302
+ {
1303
+ name: "SshFileRead",
1304
+ description: "Read a file from a remote host via SSH.",
1305
+ category: "ssh",
1306
+ tags: ["ssh", "remote", "file", "read"],
1307
+ inputSchema: objectSchema({
1308
+ host: { type: "string" },
1309
+ path: { type: "string" },
1310
+ user: { type: "string" },
1311
+ port: { type: "number" }
1312
+ }, ["host", "path"]),
1313
+ call: async (input, context) => {
1314
+ const result = await sshFileRead({
1315
+ host: readString(input, "host"),
1316
+ path: readString(input, "path"),
1317
+ user: readOptionalString(input, "user"),
1318
+ port: readOptionalNumber(input, "port")
1319
+ });
1320
+ return `Read ${result.path} on ${result.host} (${result.sizeBytes} bytes)\n${result.content}`;
1321
+ },
1322
+ isReadOnly: () => true,
1323
+ isDestructive: () => false,
1324
+ isConcurrencySafe: () => false
1325
+ },
1326
+ {
1327
+ name: "SshFileWrite",
1328
+ description: "Write a file to a remote host via SSH.",
1329
+ category: "ssh",
1330
+ tags: ["ssh", "remote", "file", "write"],
1331
+ inputSchema: objectSchema({
1332
+ host: { type: "string" },
1333
+ path: { type: "string" },
1334
+ content: { type: "string" },
1335
+ user: { type: "string" },
1336
+ port: { type: "number" }
1337
+ }, ["host", "path", "content"]),
1338
+ call: async (input, context) => {
1339
+ const result = await sshFileWrite({
1340
+ host: readString(input, "host"),
1341
+ path: readString(input, "path"),
1342
+ content: readString(input, "content"),
1343
+ user: readOptionalString(input, "user"),
1344
+ port: readOptionalNumber(input, "port")
1345
+ });
1346
+ return `Wrote ${result.path} on ${result.host} (${result.sizeBytes} bytes)`;
1347
+ },
1348
+ isReadOnly: () => false,
1349
+ isDestructive: () => true,
1350
+ isConcurrencySafe: () => false
1351
+ },
1352
+ {
1353
+ name: "Sleep",
1354
+ description: "Pause execution for a specified number of milliseconds.",
1355
+ category: "system",
1356
+ tags: ["sleep", "wait", "delay"],
1357
+ inputSchema: SleepInputSchema,
1358
+ call: async (input) => {
1359
+ const parsed = parseSleepInput(input);
1360
+ return executeSleep(parsed);
1361
+ },
1362
+ isReadOnly: () => true,
1363
+ isDestructive: () => false,
1364
+ isConcurrencySafe: () => true
1365
+ },
1366
+ {
1367
+ name: "Monitor",
1368
+ description: "Show system resource usage (CPU, memory, disk).",
1369
+ category: "system",
1370
+ tags: ["monitor", "system", "resources"],
1371
+ inputSchema: MonitorInputSchema,
1372
+ call: (input) => {
1373
+ const parsed = parseMonitorInput(input);
1374
+ const data = getMonitorData();
1375
+ return formatMonitorResult(data, parsed.scope ?? "quick");
1376
+ },
1377
+ isReadOnly: () => true,
1378
+ isDestructive: () => false,
1379
+ isConcurrencySafe: () => true
1380
+ },
1381
+ {
1382
+ name: "FileCopy",
1383
+ description: "Copy a file with safety checks (refuses overwrite without overwrite flag).",
1384
+ category: "files",
1385
+ tags: ["file", "copy"],
1386
+ inputSchema: FileCopyInputSchema,
1387
+ call: (input, context) => {
1388
+ const parsed = parseFileCopyInput(input);
1389
+ const result = executeFileCopy({ ...parsed, cwd: context.cwd });
1390
+ return formatFileCopyResult(result);
1391
+ },
1392
+ isReadOnly: () => false,
1393
+ isDestructive: () => true,
1394
+ isConcurrencySafe: () => false
1395
+ },
1396
+ {
1397
+ name: "FileMove",
1398
+ description: "Move or rename a file with safety checks.",
1399
+ category: "files",
1400
+ tags: ["file", "move", "rename"],
1401
+ inputSchema: FileMoveInputSchema,
1402
+ call: (input, context) => {
1403
+ const parsed = parseFileMoveInput(input);
1404
+ const result = executeFileMove({ ...parsed, cwd: context.cwd });
1405
+ return formatFileMoveResult(result);
1406
+ },
1407
+ isReadOnly: () => false,
1408
+ isDestructive: () => true,
1409
+ isConcurrencySafe: () => false
1410
+ },
1411
+ {
1412
+ name: "FileDelete",
1413
+ description: "Delete a file or directory with path safety checks.",
1414
+ category: "files",
1415
+ tags: ["file", "delete", "remove"],
1416
+ inputSchema: FileDeleteInputSchema,
1417
+ call: (input, context) => {
1418
+ const parsed = parseFileDeleteInput(input);
1419
+ const result = executeFileDelete({ ...parsed, cwd: context.cwd });
1420
+ return formatFileDeleteResult(result);
1421
+ },
1422
+ isReadOnly: () => false,
1423
+ isDestructive: () => true,
1424
+ isConcurrencySafe: () => false
1425
+ },
1426
+ {
1427
+ name: "DirCreate",
1428
+ description: "Create a directory (recursive).",
1429
+ category: "files",
1430
+ tags: ["file", "directory", "create", "mkdir"],
1431
+ inputSchema: DirCreateInputSchema,
1432
+ call: (input, context) => {
1433
+ const parsed = parseDirCreateInput(input);
1434
+ const result = executeDirCreate({ ...parsed, cwd: context.cwd });
1435
+ return formatDirCreateResult(result);
1436
+ },
1437
+ isReadOnly: () => false,
1438
+ isDestructive: () => true,
1439
+ isConcurrencySafe: () => false
1440
+ },
1441
+ {
1442
+ name: "DirList",
1443
+ description: "List directory contents with file sizes and modification dates.",
1444
+ category: "files",
1445
+ tags: ["file", "directory", "list", "ls"],
1446
+ inputSchema: DirListInputSchema,
1447
+ call: (input, context) => {
1448
+ const parsed = parseDirListInput(input);
1449
+ const result = executeDirList({ ...parsed, cwd: context.cwd });
1450
+ return formatDirListResult(result);
1451
+ },
1452
+ isReadOnly: () => true,
1453
+ isDestructive: () => false,
1454
+ isConcurrencySafe: () => true
1455
+ },
1456
+ {
1457
+ name: "ProcessList",
1458
+ description: "List running processes sorted by CPU or memory usage.",
1459
+ category: "system",
1460
+ tags: ["process", "ps", "system"],
1461
+ inputSchema: ProcessListInputSchema,
1462
+ call: (input) => {
1463
+ const parsed = parseProcessListInput(input);
1464
+ const result = executeProcessList(parsed);
1465
+ return formatProcessListResult(result);
1466
+ },
1467
+ isReadOnly: () => true,
1468
+ isDestructive: () => false,
1469
+ isConcurrencySafe: () => true
1470
+ },
1471
+ {
1472
+ name: "KillProcess",
1473
+ description: "Kill a process by PID or process name.",
1474
+ category: "system",
1475
+ tags: ["process", "kill", "signal"],
1476
+ inputSchema: KillProcessInputSchema,
1477
+ call: (input) => {
1478
+ const parsed = parseKillProcessInput(input);
1479
+ const result = executeKillProcess(parsed);
1480
+ return formatKillProcessResult(result);
1481
+ },
1482
+ isReadOnly: () => false,
1483
+ isDestructive: () => true,
1484
+ isConcurrencySafe: () => false
1485
+ },
1486
+ {
1487
+ name: "Environment",
1488
+ description: "Show environment variables with optional filtering.",
1489
+ category: "system",
1490
+ tags: ["env", "environment"],
1491
+ inputSchema: EnvironmentInputSchema,
1492
+ call: (input) => {
1493
+ const parsed = parseEnvironmentInput(input);
1494
+ const result = executeEnvironment(parsed);
1495
+ return formatEnvironmentResult(result);
1496
+ },
1497
+ isReadOnly: () => true,
1498
+ isDestructive: () => false,
1499
+ isConcurrencySafe: () => true
1500
+ },
1501
+ {
1502
+ name: "DiskUsage",
1503
+ description: "Show disk usage for a path.",
1504
+ category: "system",
1505
+ tags: ["disk", "df", "usage"],
1506
+ inputSchema: DiskUsageInputSchema,
1507
+ call: (input) => {
1508
+ const parsed = parseDiskUsageInput(input);
1509
+ const result = executeDiskUsage(parsed);
1510
+ return formatDiskUsageResult(result);
1511
+ },
1512
+ isReadOnly: () => true,
1513
+ isDestructive: () => false,
1514
+ isConcurrencySafe: () => true
1515
+ },
1516
+ {
1517
+ name: "SystemInfo",
1518
+ description: "Show system information: hostname, OS, uptime, load, users, processes.",
1519
+ category: "system",
1520
+ tags: ["system", "uptime", "info"],
1521
+ inputSchema: SystemInfoInputSchema,
1522
+ call: () => {
1523
+ const result = executeSystemInfo();
1524
+ return formatSystemInfoResult(result);
1525
+ },
1526
+ isReadOnly: () => true,
1527
+ isDestructive: () => false,
1528
+ isConcurrencySafe: () => true
1529
+ },
1530
+ {
1531
+ name: "HttpRequest",
1532
+ description: "Send an HTTP request (GET/POST/PUT/DELETE) with custom headers and body.",
1533
+ category: "web",
1534
+ tags: ["http", "api", "request"],
1535
+ inputSchema: HttpRequestInputSchema,
1536
+ call: async (input) => {
1537
+ const parsed = parseHttpRequestInput(input);
1538
+ const result = await executeHttpRequest(parsed);
1539
+ return formatHttpRequestResult(result);
1540
+ },
1541
+ isReadOnly: () => false,
1542
+ isDestructive: () => true,
1543
+ isConcurrencySafe: () => false
1544
+ },
1545
+ {
1546
+ name: "JsonQuery",
1547
+ description: "Query/filter JSON data using simple path expressions (e.g., items[0].name).",
1548
+ category: "data",
1549
+ tags: ["json", "query", "parse"],
1550
+ inputSchema: JsonQueryInputSchema,
1551
+ call: (input) => {
1552
+ const parsed = parseJsonQueryInput(input);
1553
+ const result = executeJsonQuery(parsed);
1554
+ return formatJsonQueryResult(result);
1555
+ },
1556
+ isReadOnly: () => true,
1557
+ isDestructive: () => false,
1558
+ isConcurrencySafe: () => true
1559
+ },
1560
+ {
1561
+ name: "ArchiveCreate",
1562
+ description: "Create a tar.gz or zip archive from files or directories.",
1563
+ category: "files",
1564
+ tags: ["archive", "tar", "zip", "compress"],
1565
+ inputSchema: ArchiveCreateInputSchema,
1566
+ call: (input, context) => {
1567
+ const parsed = parseArchiveCreateInput(input);
1568
+ const result = executeArchiveCreate({ ...parsed, cwd: context.cwd });
1569
+ return formatArchiveCreateResult(result);
1570
+ },
1571
+ isReadOnly: () => false,
1572
+ isDestructive: () => true,
1573
+ isConcurrencySafe: () => false
1574
+ },
1575
+ {
1576
+ name: "ArchiveExtract",
1577
+ description: "Extract a tar.gz or zip archive to a directory.",
1578
+ category: "files",
1579
+ tags: ["archive", "tar", "zip", "extract"],
1580
+ inputSchema: ArchiveExtractInputSchema,
1581
+ call: (input, context) => {
1582
+ const parsed = parseArchiveExtractInput(input);
1583
+ const result = executeArchiveExtract({ ...parsed, cwd: context.cwd });
1584
+ return formatArchiveExtractResult(result);
1585
+ },
1586
+ isReadOnly: () => false,
1587
+ isDestructive: () => true,
1588
+ isConcurrencySafe: () => false
1589
+ },
1590
+ {
1591
+ name: "GitBranchDelete",
1592
+ description: "Delete a git branch (refuses if it is the current branch).",
1593
+ category: "git",
1594
+ tags: ["git", "branch", "delete"],
1595
+ inputSchema: GitBranchDeleteInputSchema,
1596
+ call: (input, context) => {
1597
+ const parsed = parseGitBranchDeleteInput(input);
1598
+ const result = executeGitBranchDelete({ ...parsed, cwd: context.cwd });
1599
+ return formatGitBranchDeleteResult(result);
1600
+ },
1601
+ isReadOnly: () => false,
1602
+ isDestructive: () => true,
1603
+ isConcurrencySafe: () => false
1604
+ },
1605
+ {
1606
+ name: "GitStash",
1607
+ description: "Stash, pop, list, drop, or apply git stashes.",
1608
+ category: "git",
1609
+ tags: ["git", "stash"],
1610
+ inputSchema: GitStashInputSchema,
1611
+ call: (input, context) => {
1612
+ const parsed = parseGitStashInput(input);
1613
+ const result = executeGitStash({ ...parsed, cwd: context.cwd });
1614
+ return formatGitStashResult(result);
1615
+ },
1616
+ isReadOnly: (input) => input.action === "list",
1617
+ isDestructive: (input) => input.action !== "list",
1618
+ isConcurrencySafe: () => false
1619
+ },
1620
+ {
1621
+ name: "GitReset",
1622
+ description: "Unstage files or hard-reset the working tree.",
1623
+ category: "git",
1624
+ tags: ["git", "reset", "unstage"],
1625
+ inputSchema: GitResetInputSchema,
1626
+ call: (input, context) => {
1627
+ const parsed = parseGitResetInput(input);
1628
+ const result = executeGitReset({ ...parsed, cwd: context.cwd });
1629
+ return formatGitResetResult(result);
1630
+ },
1631
+ isReadOnly: () => false,
1632
+ isDestructive: (input) => input.hard === true,
1633
+ isConcurrencySafe: () => false
1634
+ },
1635
+ {
1636
+ name: "FileFind",
1637
+ description: "Find files by name pattern, size range, or modification time.",
1638
+ category: "search",
1639
+ tags: ["find", "file", "search"],
1640
+ inputSchema: FileFindInputSchema,
1641
+ call: (input, context) => {
1642
+ const parsed = parseFileFindInput(input);
1643
+ const result = executeFileFind({ ...parsed, cwd: context.cwd });
1644
+ return formatFileFindResult(result);
1645
+ },
1646
+ isReadOnly: () => true,
1647
+ isDestructive: () => false,
1648
+ isConcurrencySafe: () => true
1649
+ },
1650
+ {
1651
+ name: "HeadTail",
1652
+ description: "Read the first or last N lines of a file.",
1653
+ category: "files",
1654
+ tags: ["file", "head", "tail", "read"],
1655
+ inputSchema: HeadTailInputSchema,
1656
+ call: (input, context) => {
1657
+ const parsed = parseHeadTailInput(input);
1658
+ const result = executeHeadTail({ ...parsed, cwd: context.cwd });
1659
+ return formatHeadTailResult(result);
1660
+ },
1661
+ isReadOnly: () => true,
1662
+ isDestructive: () => false,
1663
+ isConcurrencySafe: () => true
1664
+ },
1665
+ {
1666
+ name: "TextStats",
1667
+ description: "Count lines, words, characters, and bytes in a file.",
1668
+ category: "files",
1669
+ tags: ["file", "count", "wc", "stats"],
1670
+ inputSchema: TextStatsInputSchema,
1671
+ call: (input, context) => {
1672
+ const parsed = parseTextStatsInput(input);
1673
+ const result = executeTextStats({ ...parsed, cwd: context.cwd });
1674
+ return formatTextStatsResult(result);
1675
+ },
1676
+ isReadOnly: () => true,
1677
+ isDestructive: () => false,
1678
+ isConcurrencySafe: () => true
1679
+ },
1680
+ {
1681
+ name: "TreeView",
1682
+ description: "Show a directory tree of the workspace (depth-limited).",
1683
+ category: "search",
1684
+ tags: ["tree", "directory", "ls"],
1685
+ inputSchema: TreeViewInputSchema,
1686
+ call: (input, context) => {
1687
+ const parsed = parseTreeViewInput(input);
1688
+ const result = executeTreeView({ ...parsed, cwd: context.cwd });
1689
+ return formatTreeViewResult(result);
1690
+ },
1691
+ isReadOnly: () => true,
1692
+ isDestructive: () => false,
1693
+ isConcurrencySafe: () => true
1694
+ },
1695
+ {
1696
+ name: "WhoAmI",
1697
+ description: "Show current user information (username, home, shell, groups).",
1698
+ category: "system",
1699
+ tags: ["user", "whoami"],
1700
+ inputSchema: WhoAmIInputSchema,
1701
+ call: () => {
1702
+ const result = executeWhoAmI();
1703
+ return formatWhoAmIResult(result);
1704
+ },
1705
+ isReadOnly: () => true,
1706
+ isDestructive: () => false,
1707
+ isConcurrencySafe: () => true
1708
+ },
1709
+ {
1710
+ name: "NetworkCheck",
1711
+ description: "Check if a host is reachable via ping or TCP port check.",
1712
+ category: "system",
1713
+ tags: ["network", "ping", "connectivity"],
1714
+ inputSchema: NetworkCheckInputSchema,
1715
+ call: async (input) => {
1716
+ const parsed = parseNetworkCheckInput(input);
1717
+ const result = await executeNetworkCheck(parsed);
1718
+ return formatNetworkCheckResult(result);
1719
+ },
1720
+ isReadOnly: () => true,
1721
+ isDestructive: () => false,
1722
+ isConcurrencySafe: () => true
1723
+ },
1724
+ {
1725
+ name: "Base64",
1726
+ description: "Encode or decode base64 strings.",
1727
+ category: "data",
1728
+ tags: ["base64", "encode", "decode"],
1729
+ inputSchema: Base64InputSchema,
1730
+ call: (input) => {
1731
+ const parsed = parseBase64Input(input);
1732
+ const result = executeBase64(parsed);
1733
+ return formatBase64Result(result);
1734
+ },
1735
+ isReadOnly: () => true,
1736
+ isDestructive: () => false,
1737
+ isConcurrencySafe: () => true
1738
+ },
1739
+ {
1740
+ name: "Which",
1741
+ description: "Locate an executable in the system PATH.",
1742
+ category: "system",
1743
+ tags: ["which", "path", "executable"],
1744
+ inputSchema: WhichInputSchema,
1745
+ call: (input) => {
1746
+ const parsed = parseWhichInput(input);
1747
+ const result = executeWhich(parsed);
1748
+ return formatWhichResult(result);
1749
+ },
1750
+ isReadOnly: () => true,
1751
+ isDestructive: () => false,
1752
+ isConcurrencySafe: () => true
1753
+ },
1754
+ {
1755
+ name: "Date",
1756
+ description: "Show current date/time in ISO, Unix, UTC, and local formats.",
1757
+ category: "system",
1758
+ tags: ["date", "time"],
1759
+ inputSchema: DateInputSchema,
1760
+ call: () => {
1761
+ const result = executeDate();
1762
+ return formatDateResult(result);
1763
+ },
1764
+ isReadOnly: () => true,
1765
+ isDestructive: () => false,
1766
+ isConcurrencySafe: () => true
1767
+ },
1768
+ {
1769
+ name: "Agent",
1770
+ description: "Spawn a sub-agent to handle complex tasks autonomously, preserving the main context window.",
1771
+ category: "agent",
1772
+ tags: ["agent", "subagent", "parallel", "research"],
1773
+ inputSchema: AgentToolInputSchema,
1774
+ call: async (input, context) => {
1775
+ const parsed = parseAgentToolInput(input);
1776
+ if (!context.spawnSubAgent) {
1777
+ throw new Error("Agent tool requires a spawnSubAgent executor (not available in this context)");
1778
+ }
1779
+ const result = await context.spawnSubAgent({
1780
+ prompt: parsed.prompt,
1781
+ description: parsed.description,
1782
+ subagentType: parsed.subagentType ?? "general",
1783
+ runInBackground: parsed.runInBackground ?? false,
1784
+ target: parsed.target
1785
+ });
1786
+ return formatAgentToolResult({
1787
+ agentId: result.agentId,
1788
+ type: (parsed.subagentType ?? "general"),
1789
+ status: result.status,
1790
+ result: result.result,
1791
+ error: result.error
1792
+ });
1793
+ },
1794
+ isReadOnly: () => true,
1795
+ isDestructive: () => false,
1796
+ isConcurrencySafe: () => true
1797
+ },
1798
+ {
1799
+ name: "ListPeers",
1800
+ description: "List all Magi peers reachable on the local network. Use this to discover targets for the Agent tool's `target` parameter when distributing work across multiple machines. Returns peer name, address, and status.",
1801
+ category: "agent",
1802
+ tags: ["agent", "peers", "swarm", "discovery"],
1803
+ inputSchema: {
1804
+ type: "object",
1805
+ additionalProperties: false,
1806
+ properties: {
1807
+ timeoutMs: {
1808
+ type: "integer",
1809
+ minimum: 500,
1810
+ maximum: 10000,
1811
+ description: "How long to wait for mDNS responses (default 2500ms)"
1812
+ }
1813
+ }
1814
+ },
1815
+ call: async (input) => {
1816
+ const timeoutMs = typeof input.timeoutMs === "number"
1817
+ ? input.timeoutMs
1818
+ : 2500;
1819
+ // Discover via mDNS
1820
+ const { browseMdns } = await import("../control/mdns.js");
1821
+ const browser = browseMdns({});
1822
+ await new Promise(resolve => setTimeout(resolve, timeoutMs));
1823
+ const peers = browser.peers();
1824
+ browser.stop();
1825
+ // Also include saved peers (manually configured with credentials)
1826
+ // Note: we read from a stand-alone SessionStore; saved peers are stored as
1827
+ // mcp_oauth_tokens with serverName starting with "peer:".
1828
+ let savedPeers = [];
1829
+ try {
1830
+ const path = await import("node:path");
1831
+ const { existsSync } = await import("node:fs");
1832
+ const stateDir = process.env.MAGI_HOME
1833
+ ? path.join(process.env.MAGI_HOME, "state")
1834
+ : path.join(process.env.HOME ?? "/tmp", ".magi-next", "state");
1835
+ const dbPath = path.join(stateDir, "sessions.sqlite");
1836
+ if (existsSync(dbPath)) {
1837
+ const mod = await import("../session-store.js");
1838
+ const store = new mod.SessionStore(dbPath);
1839
+ savedPeers = store.listMcpOAuthTokens()
1840
+ .filter(t => t.serverName.startsWith("peer:"))
1841
+ .map(t => ({
1842
+ name: t.serverName.replace(/^peer:/, ""),
1843
+ url: t.metadata?.peerUrl ?? t.authServerUrl ?? "?"
1844
+ }));
1845
+ store.close();
1846
+ }
1847
+ }
1848
+ catch {
1849
+ // best effort
1850
+ }
1851
+ const lines = [];
1852
+ if (peers.length === 0 && savedPeers.length === 0) {
1853
+ return [
1854
+ "No peers found.",
1855
+ "",
1856
+ "To add a peer:",
1857
+ " 1. On the remote machine: `magi daemon start` and `magi pair`",
1858
+ " 2. On this machine: `magi peers add <name> <url> <device-id> <token>`",
1859
+ " 3. Use Agent({ target: <name>, ... }) to dispatch to it."
1860
+ ].join("\n");
1861
+ }
1862
+ if (peers.length > 0) {
1863
+ lines.push(`Discovered ${peers.length} peer(s) via mDNS:`);
1864
+ for (const p of peers) {
1865
+ lines.push(` ${p.instanceName.padEnd(28)} ${p.address}:${p.port} ${p.hostname}`);
1866
+ if (p.txt && Object.keys(p.txt).length > 0) {
1867
+ lines.push(` info: ${Object.entries(p.txt).map(([k, v]) => `${k}=${v}`).join(", ")}`);
1868
+ }
1869
+ }
1870
+ }
1871
+ if (savedPeers.length > 0) {
1872
+ if (lines.length > 0)
1873
+ lines.push("");
1874
+ lines.push(`${savedPeers.length} saved peer(s) with credentials (use these as Agent target):`);
1875
+ for (const p of savedPeers) {
1876
+ lines.push(` ${p.name.padEnd(28)} ${p.url}`);
1877
+ }
1878
+ }
1879
+ lines.push("");
1880
+ lines.push("To dispatch a sub-agent to a peer, call:");
1881
+ lines.push(" Agent({ target: <peer-name>, subagent_type: <type>, prompt: <task>, description: <short> })");
1882
+ lines.push("Multiple Agent calls in the same response run in parallel — split work across peers for speed.");
1883
+ return lines.join("\n");
1884
+ },
1885
+ isReadOnly: () => true,
1886
+ isDestructive: () => false,
1887
+ isConcurrencySafe: () => true
1888
+ },
1889
+ {
1890
+ name: "DiscoverSkills",
1891
+ description: "List user-installed skills with their descriptions. Skills are reusable workflows; invoke them by replying with their name as a slash command (e.g. `/verify`) or calling the Skill tool with the skill name.",
1892
+ category: "agent",
1893
+ tags: ["skills", "discovery"],
1894
+ inputSchema: {
1895
+ type: "object",
1896
+ additionalProperties: false,
1897
+ properties: {}
1898
+ },
1899
+ call: async (_input, context) => {
1900
+ if (!context.stateRoot) {
1901
+ return "Skills require a configured Magi state root.";
1902
+ }
1903
+ const skillsRoot = path.join(path.dirname(context.stateRoot), "skills");
1904
+ const { listSkills } = await import("../skills/loader.js");
1905
+ const fakePaths = { skillsRoot };
1906
+ const skills = listSkills(fakePaths);
1907
+ if (skills.length === 0) {
1908
+ return [
1909
+ "No skills installed.",
1910
+ `Skills directory: ${skillsRoot}`,
1911
+ "Bundled skills (verify, debug, stuck) are auto-installed on first run."
1912
+ ].join("\n");
1913
+ }
1914
+ const lines = ["Installed skills:", ""];
1915
+ for (const s of skills) {
1916
+ lines.push(` /${s.name.padEnd(20)} ${s.summary}`);
1917
+ }
1918
+ lines.push("");
1919
+ lines.push("To run a skill: reply with its name as a slash command, or call Skill({name: \"...\"}).");
1920
+ return lines.join("\n");
1921
+ },
1922
+ isReadOnly: () => true,
1923
+ isDestructive: () => false,
1924
+ isConcurrencySafe: () => true
1925
+ },
1926
+ {
1927
+ name: "CtxInspect",
1928
+ description: "Show the current session's context size: message count, approximate token usage, and the most recent message titles. Use to decide whether to compact the session.",
1929
+ category: "agent",
1930
+ tags: ["context", "introspection"],
1931
+ inputSchema: {
1932
+ type: "object",
1933
+ additionalProperties: false,
1934
+ properties: {}
1935
+ },
1936
+ call: async (_input, context) => {
1937
+ if (!context.sessionId || !context.stateRoot) {
1938
+ return "CtxInspect requires an active session.";
1939
+ }
1940
+ const dbPath = path.join(context.stateRoot, "sessions.sqlite");
1941
+ const { existsSync } = await import("node:fs");
1942
+ if (!existsSync(dbPath))
1943
+ return "No session database yet.";
1944
+ const mod = await import("../session-store.js");
1945
+ const store = new mod.SessionStore(dbPath);
1946
+ try {
1947
+ const session = store.getSession(context.sessionId);
1948
+ if (!session)
1949
+ return `Session not found: ${context.sessionId}`;
1950
+ let totalChars = 0;
1951
+ const counts = { user: 0, assistant: 0, tool: 0, system: 0 };
1952
+ for (const m of session.messages) {
1953
+ totalChars += m.content.length;
1954
+ counts[m.role] = (counts[m.role] ?? 0) + 1;
1955
+ }
1956
+ const tokens = Math.ceil(totalChars / 4);
1957
+ const lines = [
1958
+ `Session: ${session.id}`,
1959
+ `Title: ${session.title ?? "(untitled)"}`,
1960
+ `Messages: ${session.messages.length}`,
1961
+ ` user=${counts.user ?? 0} assistant=${counts.assistant ?? 0} tool=${counts.tool ?? 0} system=${counts.system ?? 0}`,
1962
+ `Approx tokens: ~${tokens.toLocaleString()} (chars/4 estimate)`,
1963
+ ""
1964
+ ];
1965
+ const recent = session.messages.slice(-5);
1966
+ if (recent.length > 0) {
1967
+ lines.push("Most recent:");
1968
+ for (const m of recent) {
1969
+ const preview = m.content.replace(/\s+/g, " ").trim().slice(0, 80);
1970
+ lines.push(` [${m.role}] ${preview}${m.content.length > 80 ? "..." : ""}`);
1971
+ }
1972
+ }
1973
+ if (tokens > 100_000) {
1974
+ lines.push("");
1975
+ lines.push("Context is large. Consider /compact to summarize older messages.");
1976
+ }
1977
+ return lines.join("\n");
1978
+ }
1979
+ finally {
1980
+ store.close();
1981
+ }
1982
+ },
1983
+ isReadOnly: () => true,
1984
+ isDestructive: () => false,
1985
+ isConcurrencySafe: () => true
1986
+ },
1987
+ {
1988
+ name: "VerifyPlanExecution",
1989
+ description: "Run the project's build and test commands and return a PASS/FAIL/PARTIAL verdict with evidence. Use after implementation work that touches multiple files. Detects npm/pnpm/yarn, cargo, go, mvn/gradle automatically.",
1990
+ category: "verification",
1991
+ tags: ["verify", "test", "build"],
1992
+ inputSchema: {
1993
+ type: "object",
1994
+ additionalProperties: false,
1995
+ properties: {
1996
+ skipTests: { type: "boolean", description: "Skip test step (build only). Default false." },
1997
+ skipLint: { type: "boolean", description: "Skip lint step. Default false." }
1998
+ }
1999
+ },
2000
+ call: async (input, context) => {
2001
+ const { spawnSync } = await import("node:child_process");
2002
+ const { existsSync } = await import("node:fs");
2003
+ const cwd = context.cwd;
2004
+ const opts = input;
2005
+ const skipTests = opts.skipTests === true;
2006
+ const skipLint = opts.skipLint === true;
2007
+ function detectStack() {
2008
+ if (existsSync(path.join(cwd, "package.json"))) {
2009
+ const pm = existsSync(path.join(cwd, "pnpm-lock.yaml")) ? "pnpm"
2010
+ : existsSync(path.join(cwd, "yarn.lock")) ? "yarn"
2011
+ : "npm";
2012
+ return {
2013
+ build: [pm, "run", "build"],
2014
+ test: [pm, "test", "--", "--run"],
2015
+ lint: [pm, "run", "lint"]
2016
+ };
2017
+ }
2018
+ if (existsSync(path.join(cwd, "Cargo.toml")))
2019
+ return { build: ["cargo", "build"], test: ["cargo", "test"], lint: ["cargo", "clippy"] };
2020
+ if (existsSync(path.join(cwd, "go.mod")))
2021
+ return { build: ["go", "build", "./..."], test: ["go", "test", "./..."], lint: ["go", "vet", "./..."] };
2022
+ if (existsSync(path.join(cwd, "pom.xml")))
2023
+ return { build: ["mvn", "compile"], test: ["mvn", "test"] };
2024
+ return undefined;
2025
+ }
2026
+ const stack = detectStack();
2027
+ if (!stack) {
2028
+ return [
2029
+ "VERDICT: PARTIAL",
2030
+ "EVIDENCE: no build system detected",
2031
+ ` Looked in: ${cwd}`,
2032
+ ` Did not find: package.json, Cargo.toml, go.mod, pom.xml`,
2033
+ "Run the relevant build/test commands manually to verify."
2034
+ ].join("\n");
2035
+ }
2036
+ function run(label, argv) {
2037
+ const r = spawnSync(argv[0], argv.slice(1), { cwd, encoding: "utf8", env: { ...process.env, CI: "1" }, timeout: 5 * 60 * 1000 });
2038
+ const stdout = (r.stdout ?? "").toString();
2039
+ const stderr = (r.stderr ?? "").toString();
2040
+ const tail = (stdout + stderr).split("\n").slice(-15).join("\n");
2041
+ return { ok: r.status === 0, output: `${label}: ${argv.join(" ")}\n${tail}` };
2042
+ }
2043
+ const evidence = [];
2044
+ const issues = [];
2045
+ let allPassed = true;
2046
+ let anyRun = false;
2047
+ const buildResult = run("BUILD", stack.build);
2048
+ anyRun = true;
2049
+ evidence.push(buildResult.output);
2050
+ if (!buildResult.ok) {
2051
+ allPassed = false;
2052
+ issues.push("build failed");
2053
+ }
2054
+ if (!skipTests) {
2055
+ const testResult = run("TEST", stack.test);
2056
+ anyRun = true;
2057
+ evidence.push(testResult.output);
2058
+ if (!testResult.ok) {
2059
+ allPassed = false;
2060
+ issues.push("tests failed");
2061
+ }
2062
+ }
2063
+ if (!skipLint && stack.lint) {
2064
+ const lintResult = run("LINT", stack.lint);
2065
+ anyRun = true;
2066
+ evidence.push(lintResult.output);
2067
+ if (!lintResult.ok) {
2068
+ allPassed = false;
2069
+ issues.push("lint failed");
2070
+ }
2071
+ }
2072
+ const verdict = !anyRun ? "PARTIAL" : allPassed ? "PASS" : "FAIL";
2073
+ const lines = [`VERDICT: ${verdict}`, "", "EVIDENCE:"];
2074
+ for (const e of evidence) {
2075
+ lines.push("---");
2076
+ lines.push(e);
2077
+ }
2078
+ if (issues.length > 0) {
2079
+ lines.push("");
2080
+ lines.push("ISSUES:");
2081
+ for (const i of issues)
2082
+ lines.push(` - ${i}`);
2083
+ }
2084
+ return lines.join("\n");
2085
+ },
2086
+ isReadOnly: () => false,
2087
+ isDestructive: () => false,
2088
+ isConcurrencySafe: () => false
2089
+ },
2090
+ {
2091
+ name: "Browser",
2092
+ description: "Control a real Chromium browser. Actions: navigate, click, type, scroll, screenshot, extract_text, wait, evaluate, close. Use navigate to open URLs, click to interact, screenshot to see the page (vision), evaluate to run JS. Browser stays open between calls so you can do multiple actions in sequence.",
2093
+ category: "web",
2094
+ tags: ["browser", "web", "automation", "playwright"],
2095
+ inputSchema: BrowserActionInputSchema,
2096
+ call: async (input) => {
2097
+ const result = await executeBrowserAction(input);
2098
+ return formatBrowserActionResult(result);
2099
+ },
2100
+ // Read-only actions (navigate, scroll, screenshot, extract_text, wait,
2101
+ // close) don't modify any third-party state. Write actions (click, type,
2102
+ // evaluate) might submit forms / post comments / run arbitrary JS — those
2103
+ // need approval.
2104
+ isReadOnly: (input) => {
2105
+ const action = input.action;
2106
+ return action === "navigate" || action === "scroll" || action === "screenshot"
2107
+ || action === "extract_text" || action === "wait" || action === "close";
2108
+ },
2109
+ isDestructive: () => false,
2110
+ isConcurrencySafe: () => false
2111
+ }
2112
+ ];
2113
+ function matchRules(toolUse, rules) {
2114
+ for (const [decision, list] of [
2115
+ ["deny", rules?.deny ?? []],
2116
+ ["ask", rules?.ask ?? []],
2117
+ ["allow", rules?.allow ?? []]
2118
+ ]) {
2119
+ const rule = list.find((item) => ruleMatches(item, toolUse));
2120
+ if (rule) {
2121
+ return { decision, reason: `matched rule ${rule}` };
2122
+ }
2123
+ }
2124
+ return undefined;
2125
+ }
2126
+ function ruleMatches(rule, toolUse) {
2127
+ const parsed = /^([A-Za-z0-9_]+)\((.*)\)$/.exec(rule.trim());
2128
+ if (!parsed || parsed[1] !== toolUse.name) {
2129
+ return false;
2130
+ }
2131
+ const selector = parsed[2];
2132
+ if (selector === "*") {
2133
+ return true;
2134
+ }
2135
+ const haystack = String(toolUse.input.command
2136
+ ?? toolUse.input.file_path
2137
+ ?? toolUse.input.pattern
2138
+ ?? toolUse.input.url
2139
+ ?? "");
2140
+ return globPattern(selector, haystack);
2141
+ }
2142
+ function globPattern(pattern, value) {
2143
+ const regex = new RegExp(`^${pattern.split("*").map(escapeRegExp).join(".*")}$`);
2144
+ return regex.test(value);
2145
+ }
2146
+ function objectSchema(properties, required) {
2147
+ return { type: "object", properties, required, additionalProperties: false };
2148
+ }
2149
+ function errorResult(toolUse, content, permission) {
2150
+ return { toolCallId: toolUse.id, toolName: toolUse.name, content, isError: true, permission };
2151
+ }
2152
+ function readString(input, name) {
2153
+ const value = input[name];
2154
+ if (typeof value !== "string") {
2155
+ throw new Error(`Tool input ${name} must be a string`);
2156
+ }
2157
+ return value;
2158
+ }
2159
+ function readOptionalString(input, name) {
2160
+ const value = input[name];
2161
+ if (value === undefined)
2162
+ return undefined;
2163
+ if (typeof value !== "string")
2164
+ throw new Error(`Tool input ${name} must be a string`);
2165
+ return value;
2166
+ }
2167
+ function readOptionalNumber(input, name) {
2168
+ const value = input[name];
2169
+ if (value === undefined)
2170
+ return undefined;
2171
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2172
+ throw new Error(`Tool input ${name} must be a number`);
2173
+ }
2174
+ return value;
2175
+ }
2176
+ function readOptionalBoolean(input, name) {
2177
+ const value = input[name];
2178
+ if (value === undefined)
2179
+ return undefined;
2180
+ if (typeof value !== "boolean")
2181
+ throw new Error(`Tool input ${name} must be a boolean`);
2182
+ return value;
2183
+ }
2184
+ function readStringArray(input, name) {
2185
+ const value = input[name];
2186
+ if (!Array.isArray(value)) {
2187
+ throw new Error(`Tool input ${name} must be an array`);
2188
+ }
2189
+ if (value.length < 1 || value.length > 100) {
2190
+ throw new Error(`Tool input ${name} must contain 1 to 100 strings`);
2191
+ }
2192
+ return value.map((item, index) => {
2193
+ if (typeof item !== "string") {
2194
+ throw new Error(`Tool input ${name}.${index} must be a string`);
2195
+ }
2196
+ return item;
2197
+ });
2198
+ }
2199
+ function readGitUntracked(input, name) {
2200
+ const value = input[name];
2201
+ if (value === undefined)
2202
+ return undefined;
2203
+ if (value === "all" || value === "normal" || value === "none") {
2204
+ return value;
2205
+ }
2206
+ throw new Error(`Tool input ${name} must be all, normal, or none`);
2207
+ }
2208
+ function readGitStageMode(input, name) {
2209
+ const value = input[name];
2210
+ if (value === undefined)
2211
+ return undefined;
2212
+ if (value === "stage" || value === "unstage") {
2213
+ return value;
2214
+ }
2215
+ throw new Error(`Tool input ${name} must be stage or unstage`);
2216
+ }
2217
+ function readOutputMode(input, name) {
2218
+ const value = input[name];
2219
+ if (value === undefined)
2220
+ return "content";
2221
+ if (value === "content" || value === "files_with_matches" || value === "count") {
2222
+ return value;
2223
+ }
2224
+ throw new Error(`Tool input ${name} must be content, files_with_matches, or count`);
2225
+ }
2226
+ function escapeRegExp(value) {
2227
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
2228
+ }
2229
+ function requireStateFile(context) {
2230
+ if (!context.stateRoot) {
2231
+ throw new Error("Cron tools require Magi stateRoot");
2232
+ }
2233
+ return cronStorePathFromRoot(context.stateRoot);
2234
+ }
2235
+ function requireTodoContext(context) {
2236
+ if (!context.stateRoot) {
2237
+ throw new Error("TodoWrite requires Magi stateRoot");
2238
+ }
2239
+ if (!context.sessionId) {
2240
+ throw new Error("TodoWrite requires a Magi sessionId");
2241
+ }
2242
+ return {
2243
+ stateRoot: context.stateRoot,
2244
+ sessionId: context.sessionId
2245
+ };
2246
+ }
2247
+ function requireConfigFile(context) {
2248
+ if (!context.stateRoot) {
2249
+ throw new Error("Config requires Magi stateRoot");
2250
+ }
2251
+ return path.join(path.dirname(context.stateRoot), "config.yaml");
2252
+ }
2253
+ function requireSkillsRoot(context) {
2254
+ if (!context.stateRoot) {
2255
+ throw new Error("Skill requires Magi stateRoot");
2256
+ }
2257
+ return path.join(path.dirname(context.stateRoot), "skills");
2258
+ }
2259
+ function isValidMemdirType(value) {
2260
+ return value === "user" || value === "feedback" || value === "project" || value === "reference";
2261
+ }
2262
+ //# sourceMappingURL=registry.js.map