macro-agent 0.0.11 → 0.0.12

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 (408) hide show
  1. package/.macro-agent/teams/self-driving/prompts/grinder.md +27 -0
  2. package/.macro-agent/teams/self-driving/prompts/judge.md +27 -0
  3. package/.macro-agent/teams/self-driving/prompts/planner.md +33 -0
  4. package/.macro-agent/teams/self-driving/roles/grinder.yaml +17 -0
  5. package/.macro-agent/teams/self-driving/roles/judge.yaml +24 -0
  6. package/.macro-agent/teams/self-driving/roles/planner.yaml +18 -0
  7. package/.macro-agent/teams/self-driving/team.yaml +103 -0
  8. package/.macro-agent/teams/structured/prompts/developer.md +26 -0
  9. package/.macro-agent/teams/structured/prompts/lead.md +25 -0
  10. package/.macro-agent/teams/structured/prompts/reviewer.md +24 -0
  11. package/.macro-agent/teams/structured/roles/developer.yaml +12 -0
  12. package/.macro-agent/teams/structured/roles/lead.yaml +11 -0
  13. package/.macro-agent/teams/structured/roles/reviewer.yaml +19 -0
  14. package/.macro-agent/teams/structured/team.yaml +89 -0
  15. package/.sudocode/issues.jsonl +6 -0
  16. package/.sudocode/specs.jsonl +7 -0
  17. package/CLAUDE.md +110 -30
  18. package/README.md +60 -3
  19. package/dist/acp/macro-agent.d.ts +4 -0
  20. package/dist/acp/macro-agent.d.ts.map +1 -1
  21. package/dist/acp/macro-agent.js +50 -4
  22. package/dist/acp/macro-agent.js.map +1 -1
  23. package/dist/acp/session-mapper.d.ts +20 -1
  24. package/dist/acp/session-mapper.d.ts.map +1 -1
  25. package/dist/acp/session-mapper.js +90 -1
  26. package/dist/acp/session-mapper.js.map +1 -1
  27. package/dist/acp/types.d.ts +24 -1
  28. package/dist/acp/types.d.ts.map +1 -1
  29. package/dist/acp/types.js.map +1 -1
  30. package/dist/agent/agent-manager.d.ts +25 -1
  31. package/dist/agent/agent-manager.d.ts.map +1 -1
  32. package/dist/agent/agent-manager.js +93 -7
  33. package/dist/agent/agent-manager.js.map +1 -1
  34. package/dist/agent/types.d.ts +22 -0
  35. package/dist/agent/types.d.ts.map +1 -1
  36. package/dist/agent/types.js.map +1 -1
  37. package/dist/agent-detection/command-builder.d.ts +30 -0
  38. package/dist/agent-detection/command-builder.d.ts.map +1 -0
  39. package/dist/agent-detection/command-builder.js +71 -0
  40. package/dist/agent-detection/command-builder.js.map +1 -0
  41. package/dist/agent-detection/detector.d.ts +84 -0
  42. package/dist/agent-detection/detector.d.ts.map +1 -0
  43. package/dist/agent-detection/detector.js +240 -0
  44. package/dist/agent-detection/detector.js.map +1 -0
  45. package/dist/agent-detection/index.d.ts +12 -0
  46. package/dist/agent-detection/index.d.ts.map +1 -0
  47. package/dist/agent-detection/index.js +14 -0
  48. package/dist/agent-detection/index.js.map +1 -0
  49. package/dist/agent-detection/registry.d.ts +53 -0
  50. package/dist/agent-detection/registry.d.ts.map +1 -0
  51. package/dist/agent-detection/registry.js +177 -0
  52. package/dist/agent-detection/registry.js.map +1 -0
  53. package/dist/agent-detection/types.d.ts +121 -0
  54. package/dist/agent-detection/types.d.ts.map +1 -0
  55. package/dist/agent-detection/types.js +20 -0
  56. package/dist/agent-detection/types.js.map +1 -0
  57. package/dist/api/server.d.ts.map +1 -1
  58. package/dist/api/server.js +95 -0
  59. package/dist/api/server.js.map +1 -1
  60. package/dist/cli/index.js +29 -0
  61. package/dist/cli/index.js.map +1 -1
  62. package/dist/cli/mcp.js +38 -0
  63. package/dist/cli/mcp.js.map +1 -1
  64. package/dist/config/index.d.ts +2 -0
  65. package/dist/config/index.d.ts.map +1 -0
  66. package/dist/config/index.js +2 -0
  67. package/dist/config/index.js.map +1 -0
  68. package/dist/config/project-config.d.ts +46 -0
  69. package/dist/config/project-config.d.ts.map +1 -0
  70. package/dist/config/project-config.js +68 -0
  71. package/dist/config/project-config.js.map +1 -0
  72. package/dist/lifecycle/cascade.d.ts +1 -1
  73. package/dist/lifecycle/cascade.d.ts.map +1 -1
  74. package/dist/lifecycle/handlers/index.d.ts +4 -0
  75. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  76. package/dist/lifecycle/handlers/index.js +2 -0
  77. package/dist/lifecycle/handlers/index.js.map +1 -1
  78. package/dist/lifecycle/handlers/worker.d.ts +4 -0
  79. package/dist/lifecycle/handlers/worker.d.ts.map +1 -1
  80. package/dist/lifecycle/handlers/worker.js +35 -3
  81. package/dist/lifecycle/handlers/worker.js.map +1 -1
  82. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  83. package/dist/map/adapter/acp-over-map.js +32 -2
  84. package/dist/map/adapter/acp-over-map.js.map +1 -1
  85. package/dist/map/adapter/event-translator.d.ts.map +1 -1
  86. package/dist/map/adapter/event-translator.js +1 -0
  87. package/dist/map/adapter/event-translator.js.map +1 -1
  88. package/dist/map/adapter/extensions/agent-detection.d.ts +49 -0
  89. package/dist/map/adapter/extensions/agent-detection.d.ts.map +1 -0
  90. package/dist/map/adapter/extensions/agent-detection.js +91 -0
  91. package/dist/map/adapter/extensions/agent-detection.js.map +1 -0
  92. package/dist/map/adapter/extensions/index.d.ts +10 -1
  93. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  94. package/dist/map/adapter/extensions/index.js +39 -0
  95. package/dist/map/adapter/extensions/index.js.map +1 -1
  96. package/dist/map/adapter/extensions/resume.d.ts +47 -0
  97. package/dist/map/adapter/extensions/resume.d.ts.map +1 -0
  98. package/dist/map/adapter/extensions/resume.js +59 -0
  99. package/dist/map/adapter/extensions/resume.js.map +1 -0
  100. package/dist/map/adapter/extensions/workspace-files.d.ts +42 -0
  101. package/dist/map/adapter/extensions/workspace-files.d.ts.map +1 -0
  102. package/dist/map/adapter/extensions/workspace-files.js +338 -0
  103. package/dist/map/adapter/extensions/workspace-files.js.map +1 -0
  104. package/dist/mcp/mcp-server.d.ts +6 -0
  105. package/dist/mcp/mcp-server.d.ts.map +1 -1
  106. package/dist/mcp/mcp-server.js +45 -0
  107. package/dist/mcp/mcp-server.js.map +1 -1
  108. package/dist/mcp/tools/claim_task.d.ts +35 -0
  109. package/dist/mcp/tools/claim_task.d.ts.map +1 -0
  110. package/dist/mcp/tools/claim_task.js +58 -0
  111. package/dist/mcp/tools/claim_task.js.map +1 -0
  112. package/dist/mcp/tools/done.d.ts +11 -2
  113. package/dist/mcp/tools/done.d.ts.map +1 -1
  114. package/dist/mcp/tools/done.js +15 -10
  115. package/dist/mcp/tools/done.js.map +1 -1
  116. package/dist/mcp/tools/list_claimable_tasks.d.ts +38 -0
  117. package/dist/mcp/tools/list_claimable_tasks.d.ts.map +1 -0
  118. package/dist/mcp/tools/list_claimable_tasks.js +63 -0
  119. package/dist/mcp/tools/list_claimable_tasks.js.map +1 -0
  120. package/dist/mcp/tools/unclaim_task.d.ts +31 -0
  121. package/dist/mcp/tools/unclaim_task.d.ts.map +1 -0
  122. package/dist/mcp/tools/unclaim_task.js +47 -0
  123. package/dist/mcp/tools/unclaim_task.js.map +1 -0
  124. package/dist/metrics/index.d.ts +2 -0
  125. package/dist/metrics/index.d.ts.map +1 -0
  126. package/dist/metrics/index.js +2 -0
  127. package/dist/metrics/index.js.map +1 -0
  128. package/dist/metrics/metrics.d.ts +79 -0
  129. package/dist/metrics/metrics.d.ts.map +1 -0
  130. package/dist/metrics/metrics.js +166 -0
  131. package/dist/metrics/metrics.js.map +1 -0
  132. package/dist/roles/capabilities.d.ts +1 -0
  133. package/dist/roles/capabilities.d.ts.map +1 -1
  134. package/dist/roles/capabilities.js +3 -0
  135. package/dist/roles/capabilities.js.map +1 -1
  136. package/dist/roles/types.d.ts +1 -1
  137. package/dist/roles/types.d.ts.map +1 -1
  138. package/dist/router/message-router.d.ts +41 -0
  139. package/dist/router/message-router.d.ts.map +1 -1
  140. package/dist/router/message-router.js +136 -5
  141. package/dist/router/message-router.js.map +1 -1
  142. package/dist/store/event-store.d.ts +8 -1
  143. package/dist/store/event-store.d.ts.map +1 -1
  144. package/dist/store/event-store.js +120 -4
  145. package/dist/store/event-store.js.map +1 -1
  146. package/dist/store/types/agents.d.ts +1 -1
  147. package/dist/store/types/agents.d.ts.map +1 -1
  148. package/dist/store/types/events.d.ts +1 -1
  149. package/dist/store/types/events.d.ts.map +1 -1
  150. package/dist/store/types/events.js.map +1 -1
  151. package/dist/store/types/index.d.ts +1 -0
  152. package/dist/store/types/index.d.ts.map +1 -1
  153. package/dist/store/types/index.js +1 -0
  154. package/dist/store/types/index.js.map +1 -1
  155. package/dist/store/types/sessions.d.ts +44 -0
  156. package/dist/store/types/sessions.d.ts.map +1 -0
  157. package/dist/store/types/sessions.js +9 -0
  158. package/dist/store/types/sessions.js.map +1 -0
  159. package/dist/store/types/tasks.d.ts +2 -0
  160. package/dist/store/types/tasks.d.ts.map +1 -1
  161. package/dist/task/backend/memory.d.ts +4 -1
  162. package/dist/task/backend/memory.d.ts.map +1 -1
  163. package/dist/task/backend/memory.js +81 -0
  164. package/dist/task/backend/memory.js.map +1 -1
  165. package/dist/task/backend/types.d.ts +30 -0
  166. package/dist/task/backend/types.d.ts.map +1 -1
  167. package/dist/task/backend/types.js.map +1 -1
  168. package/dist/teams/index.d.ts +4 -0
  169. package/dist/teams/index.d.ts.map +1 -0
  170. package/dist/teams/index.js +4 -0
  171. package/dist/teams/index.js.map +1 -0
  172. package/dist/teams/team-loader.d.ts +20 -0
  173. package/dist/teams/team-loader.d.ts.map +1 -0
  174. package/dist/teams/team-loader.js +293 -0
  175. package/dist/teams/team-loader.js.map +1 -0
  176. package/dist/teams/team-runtime.d.ts +139 -0
  177. package/dist/teams/team-runtime.d.ts.map +1 -0
  178. package/dist/teams/team-runtime.js +613 -0
  179. package/dist/teams/team-runtime.js.map +1 -0
  180. package/dist/teams/types.d.ts +266 -0
  181. package/dist/teams/types.d.ts.map +1 -0
  182. package/dist/teams/types.js +20 -0
  183. package/dist/teams/types.js.map +1 -0
  184. package/dist/workspace/dataplane-adapter.d.ts +1 -1
  185. package/dist/workspace/dataplane-adapter.d.ts.map +1 -1
  186. package/dist/workspace/dataplane-adapter.js +1 -1
  187. package/dist/workspace/dataplane-adapter.js.map +1 -1
  188. package/dist/workspace/index.d.ts +1 -1
  189. package/dist/workspace/index.d.ts.map +1 -1
  190. package/dist/workspace/strategies/index.d.ts +6 -0
  191. package/dist/workspace/strategies/index.d.ts.map +1 -0
  192. package/dist/workspace/strategies/index.js +5 -0
  193. package/dist/workspace/strategies/index.js.map +1 -0
  194. package/dist/workspace/strategies/optimistic.d.ts +26 -0
  195. package/dist/workspace/strategies/optimistic.d.ts.map +1 -0
  196. package/dist/workspace/strategies/optimistic.js +121 -0
  197. package/dist/workspace/strategies/optimistic.js.map +1 -0
  198. package/dist/workspace/strategies/queue.d.ts +26 -0
  199. package/dist/workspace/strategies/queue.d.ts.map +1 -0
  200. package/dist/workspace/strategies/queue.js +67 -0
  201. package/dist/workspace/strategies/queue.js.map +1 -0
  202. package/dist/workspace/strategies/registry.d.ts +37 -0
  203. package/dist/workspace/strategies/registry.d.ts.map +1 -0
  204. package/dist/workspace/strategies/registry.js +63 -0
  205. package/dist/workspace/strategies/registry.js.map +1 -0
  206. package/dist/workspace/strategies/trunk.d.ts +20 -0
  207. package/dist/workspace/strategies/trunk.d.ts.map +1 -0
  208. package/dist/workspace/strategies/trunk.js +108 -0
  209. package/dist/workspace/strategies/trunk.js.map +1 -0
  210. package/dist/workspace/strategies/types.d.ts +104 -0
  211. package/dist/workspace/strategies/types.d.ts.map +1 -0
  212. package/dist/workspace/strategies/types.js +11 -0
  213. package/dist/workspace/strategies/types.js.map +1 -0
  214. package/dist/workspace/types.d.ts +1 -1
  215. package/dist/workspace/types.d.ts.map +1 -1
  216. package/dist/workspace/workspace-manager.d.ts +1 -1
  217. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  218. package/docs/implementation-details.md +1127 -0
  219. package/docs/implementation-summary.md +448 -0
  220. package/docs/plan-self-driving-support.md +433 -0
  221. package/docs/spec-self-driving-support.md +462 -0
  222. package/docs/team-templates.md +860 -0
  223. package/docs/teams.md +233 -0
  224. package/package.json +4 -2
  225. package/src/acp/__tests__/integration.test.ts +161 -1
  226. package/src/acp/__tests__/macro-agent.test.ts +95 -0
  227. package/src/acp/__tests__/session-persistence.test.ts +276 -0
  228. package/src/acp/macro-agent.ts +79 -7
  229. package/src/acp/session-mapper.ts +108 -1
  230. package/src/acp/types.ts +33 -1
  231. package/src/agent/agent-manager.ts +158 -6
  232. package/src/agent/types.ts +27 -0
  233. package/src/agent-detection/__tests__/command-builder.test.ts +336 -0
  234. package/src/agent-detection/__tests__/detector.test.ts +768 -0
  235. package/src/agent-detection/__tests__/registry.test.ts +254 -0
  236. package/src/agent-detection/command-builder.ts +90 -0
  237. package/src/agent-detection/detector.ts +307 -0
  238. package/src/agent-detection/index.ts +36 -0
  239. package/src/agent-detection/registry.ts +200 -0
  240. package/src/agent-detection/types.ts +184 -0
  241. package/src/api/server.ts +110 -0
  242. package/src/cli/index.ts +44 -0
  243. package/src/cli/mcp.ts +47 -0
  244. package/src/config/index.ts +9 -0
  245. package/src/config/project-config.ts +107 -0
  246. package/src/lifecycle/cascade.ts +1 -1
  247. package/src/lifecycle/handlers/index.ts +8 -0
  248. package/src/lifecycle/handlers/worker.ts +48 -3
  249. package/src/map/adapter/__tests__/extensions.test.ts +359 -0
  250. package/src/map/adapter/__tests__/workspace-files.test.ts +673 -0
  251. package/src/map/adapter/acp-over-map.ts +45 -2
  252. package/src/map/adapter/event-translator.ts +1 -0
  253. package/src/map/adapter/extensions/agent-detection.ts +201 -0
  254. package/src/map/adapter/extensions/index.ts +63 -0
  255. package/src/map/adapter/extensions/resume.ts +114 -0
  256. package/src/map/adapter/extensions/workspace-files.ts +449 -0
  257. package/src/mcp/mcp-server.ts +67 -0
  258. package/src/mcp/tools/claim_task.ts +86 -0
  259. package/src/mcp/tools/done.ts +24 -10
  260. package/src/mcp/tools/list_claimable_tasks.ts +93 -0
  261. package/src/mcp/tools/unclaim_task.ts +71 -0
  262. package/src/metrics/index.ts +9 -0
  263. package/src/metrics/metrics.ts +280 -0
  264. package/src/roles/capabilities.ts +3 -0
  265. package/src/roles/types.ts +2 -1
  266. package/src/router/__tests__/message-router.test.ts +561 -0
  267. package/src/router/message-router.ts +223 -6
  268. package/src/store/event-store.ts +151 -3
  269. package/src/store/types/agents.ts +1 -1
  270. package/src/store/types/events.ts +2 -1
  271. package/src/store/types/index.ts +1 -0
  272. package/src/store/types/sessions.ts +53 -0
  273. package/src/store/types/tasks.ts +3 -0
  274. package/src/task/backend/memory.ts +116 -0
  275. package/src/task/backend/types.ts +43 -0
  276. package/src/teams/__tests__/cross-subsystem.integration.test.ts +983 -0
  277. package/src/teams/__tests__/e2e/team-runtime.e2e.test.ts +553 -0
  278. package/src/teams/__tests__/team-system.test.ts +1280 -0
  279. package/src/teams/index.ts +13 -0
  280. package/src/teams/team-loader.ts +434 -0
  281. package/src/teams/team-runtime.ts +727 -0
  282. package/src/teams/types.ts +377 -0
  283. package/src/workspace/dataplane-adapter.ts +1 -1
  284. package/src/workspace/index.ts +1 -1
  285. package/src/workspace/strategies/index.ts +18 -0
  286. package/src/workspace/strategies/optimistic.ts +136 -0
  287. package/src/workspace/strategies/queue.ts +81 -0
  288. package/src/workspace/strategies/registry.ts +89 -0
  289. package/src/workspace/strategies/trunk.ts +123 -0
  290. package/src/workspace/strategies/types.ts +145 -0
  291. package/src/workspace/types.ts +1 -1
  292. package/src/workspace/workspace-manager.ts +1 -1
  293. package/.claude/settings.local.json +0 -59
  294. package/dist/map/utils/address-translation.d.ts +0 -99
  295. package/dist/map/utils/address-translation.d.ts.map +0 -1
  296. package/dist/map/utils/address-translation.js +0 -285
  297. package/dist/map/utils/address-translation.js.map +0 -1
  298. package/dist/map/utils/index.d.ts +0 -7
  299. package/dist/map/utils/index.d.ts.map +0 -1
  300. package/dist/map/utils/index.js +0 -7
  301. package/dist/map/utils/index.js.map +0 -1
  302. package/references/acp-factory-ref/CHANGELOG.md +0 -33
  303. package/references/acp-factory-ref/LICENSE +0 -21
  304. package/references/acp-factory-ref/README.md +0 -341
  305. package/references/acp-factory-ref/package-lock.json +0 -3102
  306. package/references/acp-factory-ref/package.json +0 -96
  307. package/references/acp-factory-ref/python/CHANGELOG.md +0 -33
  308. package/references/acp-factory-ref/python/LICENSE +0 -21
  309. package/references/acp-factory-ref/python/Makefile +0 -57
  310. package/references/acp-factory-ref/python/README.md +0 -253
  311. package/references/acp-factory-ref/python/pyproject.toml +0 -73
  312. package/references/acp-factory-ref/python/tests/__init__.py +0 -0
  313. package/references/acp-factory-ref/python/tests/e2e/__init__.py +0 -1
  314. package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +0 -349
  315. package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +0 -165
  316. package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +0 -296
  317. package/references/acp-factory-ref/python/tests/test_client_handler.py +0 -543
  318. package/references/acp-factory-ref/python/tests/test_pushable.py +0 -199
  319. package/references/claude-code-acp/.github/workflows/ci.yml +0 -45
  320. package/references/claude-code-acp/.github/workflows/publish.yml +0 -34
  321. package/references/claude-code-acp/.prettierrc.json +0 -4
  322. package/references/claude-code-acp/CHANGELOG.md +0 -249
  323. package/references/claude-code-acp/LICENSE +0 -222
  324. package/references/claude-code-acp/README.md +0 -53
  325. package/references/claude-code-acp/docs/RELEASES.md +0 -24
  326. package/references/claude-code-acp/eslint.config.js +0 -48
  327. package/references/claude-code-acp/package-lock.json +0 -4570
  328. package/references/claude-code-acp/package.json +0 -88
  329. package/references/claude-code-acp/scripts/release.sh +0 -119
  330. package/references/claude-code-acp/src/acp-agent.ts +0 -2065
  331. package/references/claude-code-acp/src/index.ts +0 -26
  332. package/references/claude-code-acp/src/lib.ts +0 -38
  333. package/references/claude-code-acp/src/mcp-server.ts +0 -911
  334. package/references/claude-code-acp/src/settings.ts +0 -522
  335. package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +0 -5
  336. package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +0 -6
  337. package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +0 -479
  338. package/references/claude-code-acp/src/tests/acp-agent.test.ts +0 -1502
  339. package/references/claude-code-acp/src/tests/extract-lines.test.ts +0 -103
  340. package/references/claude-code-acp/src/tests/fork-session.test.ts +0 -335
  341. package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +0 -334
  342. package/references/claude-code-acp/src/tests/settings.test.ts +0 -617
  343. package/references/claude-code-acp/src/tests/skills-options.test.ts +0 -187
  344. package/references/claude-code-acp/src/tests/tools.test.ts +0 -318
  345. package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +0 -558
  346. package/references/claude-code-acp/src/tools.ts +0 -819
  347. package/references/claude-code-acp/src/utils.ts +0 -171
  348. package/references/claude-code-acp/tsconfig.json +0 -18
  349. package/references/claude-code-acp/vitest.config.ts +0 -19
  350. package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -111
  351. package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -13
  352. package/references/multi-agent-protocol/LICENSE +0 -21
  353. package/references/multi-agent-protocol/README.md +0 -113
  354. package/references/multi-agent-protocol/docs/00-design-specification.md +0 -496
  355. package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
  356. package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
  357. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
  358. package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
  359. package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
  360. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
  361. package/references/multi-agent-protocol/docs/07-federation.md +0 -259
  362. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
  363. package/references/multi-agent-protocol/docs/09-authentication.md +0 -680
  364. package/references/multi-agent-protocol/docs/10-mail-protocol.md +0 -553
  365. package/references/multi-agent-protocol/docs/agent-iam-integration.md +0 -877
  366. package/references/multi-agent-protocol/docs/agentic-mesh-integration-draft.md +0 -459
  367. package/references/multi-agent-protocol/docs/git-transport-draft.md +0 -251
  368. package/references/multi-agent-protocol/docs-site/Gemfile +0 -22
  369. package/references/multi-agent-protocol/docs-site/README.md +0 -82
  370. package/references/multi-agent-protocol/docs-site/_config.yml +0 -91
  371. package/references/multi-agent-protocol/docs-site/_includes/head_custom.html +0 -20
  372. package/references/multi-agent-protocol/docs-site/_sass/color_schemes/map.scss +0 -42
  373. package/references/multi-agent-protocol/docs-site/_sass/custom/custom.scss +0 -34
  374. package/references/multi-agent-protocol/docs-site/examples/full-integration.md +0 -510
  375. package/references/multi-agent-protocol/docs-site/examples/index.md +0 -138
  376. package/references/multi-agent-protocol/docs-site/examples/simple-chat.md +0 -282
  377. package/references/multi-agent-protocol/docs-site/examples/task-queue.md +0 -399
  378. package/references/multi-agent-protocol/docs-site/getting-started/index.md +0 -98
  379. package/references/multi-agent-protocol/docs-site/getting-started/installation.md +0 -219
  380. package/references/multi-agent-protocol/docs-site/getting-started/overview.md +0 -172
  381. package/references/multi-agent-protocol/docs-site/getting-started/quickstart.md +0 -237
  382. package/references/multi-agent-protocol/docs-site/index.md +0 -136
  383. package/references/multi-agent-protocol/docs-site/protocol/authentication.md +0 -391
  384. package/references/multi-agent-protocol/docs-site/protocol/connection-model.md +0 -376
  385. package/references/multi-agent-protocol/docs-site/protocol/design.md +0 -284
  386. package/references/multi-agent-protocol/docs-site/protocol/error-handling.md +0 -312
  387. package/references/multi-agent-protocol/docs-site/protocol/federation.md +0 -449
  388. package/references/multi-agent-protocol/docs-site/protocol/index.md +0 -129
  389. package/references/multi-agent-protocol/docs-site/protocol/permissions.md +0 -398
  390. package/references/multi-agent-protocol/docs-site/protocol/streaming.md +0 -353
  391. package/references/multi-agent-protocol/docs-site/protocol/wire-protocol.md +0 -369
  392. package/references/multi-agent-protocol/docs-site/sdk/api/agent.md +0 -357
  393. package/references/multi-agent-protocol/docs-site/sdk/api/client.md +0 -380
  394. package/references/multi-agent-protocol/docs-site/sdk/api/index.md +0 -62
  395. package/references/multi-agent-protocol/docs-site/sdk/api/server.md +0 -453
  396. package/references/multi-agent-protocol/docs-site/sdk/api/types.md +0 -468
  397. package/references/multi-agent-protocol/docs-site/sdk/guides/agent.md +0 -375
  398. package/references/multi-agent-protocol/docs-site/sdk/guides/authentication.md +0 -405
  399. package/references/multi-agent-protocol/docs-site/sdk/guides/client.md +0 -352
  400. package/references/multi-agent-protocol/docs-site/sdk/guides/index.md +0 -89
  401. package/references/multi-agent-protocol/docs-site/sdk/guides/server.md +0 -360
  402. package/references/multi-agent-protocol/docs-site/sdk/guides/testing.md +0 -446
  403. package/references/multi-agent-protocol/docs-site/sdk/guides/transports.md +0 -363
  404. package/references/multi-agent-protocol/docs-site/sdk/index.md +0 -206
  405. package/references/multi-agent-protocol/package-lock.json +0 -3886
  406. package/references/multi-agent-protocol/package.json +0 -56
  407. package/references/multi-agent-protocol/schema/meta.json +0 -467
  408. package/references/multi-agent-protocol/schema/schema.json +0 -2558
@@ -0,0 +1,768 @@
1
+ /**
2
+ * Agent Detector Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
6
+ import { execFile } from "node:child_process";
7
+
8
+ import { AgentDetector, createAgentDetector, parseVersion } from "../detector.js";
9
+ import { AgentDetectionError } from "../types.js";
10
+
11
+ // Mock child_process.execFile
12
+ vi.mock("node:child_process", () => ({
13
+ execFile: vi.fn(),
14
+ }));
15
+
16
+ // Mock node:util to return our mock
17
+ vi.mock("node:util", () => ({
18
+ promisify: (fn: unknown) => {
19
+ // Return a function that calls the mock and wraps in a promise
20
+ return (...args: unknown[]) => {
21
+ return new Promise((resolve, reject) => {
22
+ (fn as Function)(...args, (err: Error | null, result: unknown) => {
23
+ if (err) reject(err);
24
+ else resolve(result);
25
+ });
26
+ });
27
+ };
28
+ },
29
+ }));
30
+
31
+ const mockExecFile = vi.mocked(execFile);
32
+
33
+ function setupExecFileMock(responses: Record<string, { stdout?: string; stderr?: string; error?: Error }>) {
34
+ mockExecFile.mockImplementation(((
35
+ command: string,
36
+ args: string[],
37
+ _options: unknown,
38
+ callback: (err: Error | null, result: { stdout: string; stderr: string }) => void
39
+ ) => {
40
+ // Build a key from the command
41
+ let key: string;
42
+ if (command === "which") {
43
+ key = `which:${args[0]}`;
44
+ } else if (command === "/bin/sh") {
45
+ key = `command-v:${args[1]?.replace("command -v ", "")}`;
46
+ } else {
47
+ key = `version:${command}`;
48
+ }
49
+
50
+ const response = responses[key];
51
+ if (response?.error) {
52
+ callback(response.error, { stdout: "", stderr: "" });
53
+ } else if (response) {
54
+ callback(null, { stdout: response.stdout ?? "", stderr: response.stderr ?? "" });
55
+ } else {
56
+ callback(new Error(`Command not found: ${key}`), { stdout: "", stderr: "" });
57
+ }
58
+ }) as unknown as typeof execFile);
59
+ }
60
+
61
+ describe("AgentDetector", () => {
62
+ beforeEach(() => {
63
+ vi.clearAllMocks();
64
+ });
65
+
66
+ afterEach(() => {
67
+ vi.restoreAllMocks();
68
+ });
69
+
70
+ // ===========================================================================
71
+ // parseVersion()
72
+ // ===========================================================================
73
+
74
+ describe("parseVersion()", () => {
75
+ it("parses standard semver version", () => {
76
+ expect(parseVersion("1.2.3")).toBe("1.2.3");
77
+ });
78
+
79
+ it("parses version from verbose output", () => {
80
+ expect(parseVersion("claude v1.0.30 (build abc123)")).toBe("1.0.30");
81
+ });
82
+
83
+ it("parses version with only major.minor", () => {
84
+ expect(parseVersion("version 0.82")).toBe("0.82");
85
+ });
86
+
87
+ it("parses version with four parts", () => {
88
+ expect(parseVersion("aider 0.82.1.2")).toBe("0.82.1.2");
89
+ });
90
+
91
+ it("parses version from multiline output", () => {
92
+ expect(parseVersion("Tool name\nVersion: 2.3.4\nBuild: xyz")).toBe("2.3.4");
93
+ });
94
+
95
+ it("returns null for output with no version", () => {
96
+ expect(parseVersion("no version here")).toBeNull();
97
+ });
98
+
99
+ it("returns null for empty output", () => {
100
+ expect(parseVersion("")).toBeNull();
101
+ });
102
+ });
103
+
104
+ // ===========================================================================
105
+ // Constructor & Configuration
106
+ // ===========================================================================
107
+
108
+ describe("constructor", () => {
109
+ it("creates detector with default configuration", () => {
110
+ const detector = new AgentDetector();
111
+ expect(detector.getRegistry().size).toBe(6);
112
+ });
113
+
114
+ it("creates detector with custom agents", () => {
115
+ const detector = new AgentDetector({
116
+ additionalAgents: [
117
+ {
118
+ id: "custom",
119
+ name: "Custom",
120
+ description: "Custom agent",
121
+ binary: "custom",
122
+ versionArgs: ["--version"],
123
+ headless: { promptFlag: "--prompt" },
124
+ vendor: "Custom",
125
+ },
126
+ ],
127
+ });
128
+ expect(detector.getRegistry().size).toBe(7);
129
+ });
130
+
131
+ it("creates detector with disabled agents", () => {
132
+ const detector = new AgentDetector({
133
+ disabledAgents: ["goose", "aider"],
134
+ });
135
+ // Registry still has all agents, but detection will skip disabled ones
136
+ expect(detector.getRegistry().size).toBe(6);
137
+ });
138
+ });
139
+
140
+ // ===========================================================================
141
+ // Detection
142
+ // ===========================================================================
143
+
144
+ describe("detect()", () => {
145
+ it("detects installed agents", async () => {
146
+ setupExecFileMock({
147
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
148
+ "version:claude": { stdout: "claude v1.0.30\n" },
149
+ "which:codex": { error: new Error("not found") },
150
+ "command-v:codex": { error: new Error("not found") },
151
+ "which:gemini": { error: new Error("not found") },
152
+ "command-v:gemini": { error: new Error("not found") },
153
+ "which:opencode": { error: new Error("not found") },
154
+ "command-v:opencode": { error: new Error("not found") },
155
+ "which:aider": { stdout: "/home/user/.local/bin/aider\n" },
156
+ "version:aider": { stdout: "aider v0.82.1\n" },
157
+ "which:goose": { error: new Error("not found") },
158
+ "command-v:goose": { error: new Error("not found") },
159
+ });
160
+
161
+ const detector = createAgentDetector();
162
+ const result = await detector.detect();
163
+
164
+ expect(result.scanned).toBe(6);
165
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
166
+
167
+ const claude = result.agents.find((a) => a.id === "claude-code");
168
+ expect(claude).toBeDefined();
169
+ expect(claude!.installed).toBe(true);
170
+ expect(claude!.version).toBe("1.0.30");
171
+ expect(claude!.path).toBe("/usr/local/bin/claude");
172
+
173
+ const aider = result.agents.find((a) => a.id === "aider");
174
+ expect(aider).toBeDefined();
175
+ expect(aider!.installed).toBe(true);
176
+ expect(aider!.version).toBe("0.82.1");
177
+
178
+ const codex = result.agents.find((a) => a.id === "codex");
179
+ expect(codex).toBeDefined();
180
+ expect(codex!.installed).toBe(false);
181
+ });
182
+
183
+ it("handles version output on stderr", async () => {
184
+ setupExecFileMock({
185
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
186
+ "version:claude": { stdout: "", stderr: "claude version 2.0.0\n" },
187
+ "which:codex": { error: new Error("not found") },
188
+ "command-v:codex": { error: new Error("not found") },
189
+ "which:gemini": { error: new Error("not found") },
190
+ "command-v:gemini": { error: new Error("not found") },
191
+ "which:opencode": { error: new Error("not found") },
192
+ "command-v:opencode": { error: new Error("not found") },
193
+ "which:aider": { error: new Error("not found") },
194
+ "command-v:aider": { error: new Error("not found") },
195
+ "which:goose": { error: new Error("not found") },
196
+ "command-v:goose": { error: new Error("not found") },
197
+ });
198
+
199
+ const detector = createAgentDetector();
200
+ const result = await detector.detect();
201
+
202
+ const claude = result.agents.find((a) => a.id === "claude-code");
203
+ expect(claude!.installed).toBe(true);
204
+ expect(claude!.version).toBe("2.0.0");
205
+ });
206
+
207
+ it("marks agent as installed even if version check fails", async () => {
208
+ setupExecFileMock({
209
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
210
+ "version:claude": { error: new Error("version failed") },
211
+ "which:codex": { error: new Error("not found") },
212
+ "command-v:codex": { error: new Error("not found") },
213
+ "which:gemini": { error: new Error("not found") },
214
+ "command-v:gemini": { error: new Error("not found") },
215
+ "which:opencode": { error: new Error("not found") },
216
+ "command-v:opencode": { error: new Error("not found") },
217
+ "which:aider": { error: new Error("not found") },
218
+ "command-v:aider": { error: new Error("not found") },
219
+ "which:goose": { error: new Error("not found") },
220
+ "command-v:goose": { error: new Error("not found") },
221
+ });
222
+
223
+ const detector = createAgentDetector();
224
+ const result = await detector.detect();
225
+
226
+ const claude = result.agents.find((a) => a.id === "claude-code");
227
+ expect(claude!.installed).toBe(true);
228
+ expect(claude!.version).toBeUndefined();
229
+ });
230
+
231
+ it("skips disabled agents", async () => {
232
+ setupExecFileMock({
233
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
234
+ "version:claude": { stdout: "1.0.0\n" },
235
+ "which:codex": { error: new Error("not found") },
236
+ "command-v:codex": { error: new Error("not found") },
237
+ "which:gemini": { error: new Error("not found") },
238
+ "command-v:gemini": { error: new Error("not found") },
239
+ "which:opencode": { error: new Error("not found") },
240
+ "command-v:opencode": { error: new Error("not found") },
241
+ });
242
+
243
+ const detector = createAgentDetector({
244
+ disabledAgents: ["aider", "goose"],
245
+ });
246
+ const result = await detector.detect();
247
+
248
+ expect(result.scanned).toBe(4);
249
+ expect(result.agents.find((a) => a.id === "aider")).toBeUndefined();
250
+ expect(result.agents.find((a) => a.id === "goose")).toBeUndefined();
251
+ });
252
+
253
+ it("falls back to command -v when which fails", async () => {
254
+ setupExecFileMock({
255
+ "which:claude": { error: new Error("which not found") },
256
+ "command-v:claude": { stdout: "/usr/local/bin/claude\n" },
257
+ "version:claude": { stdout: "1.0.0\n" },
258
+ "which:codex": { error: new Error("not found") },
259
+ "command-v:codex": { error: new Error("not found") },
260
+ "which:gemini": { error: new Error("not found") },
261
+ "command-v:gemini": { error: new Error("not found") },
262
+ "which:opencode": { error: new Error("not found") },
263
+ "command-v:opencode": { error: new Error("not found") },
264
+ "which:aider": { error: new Error("not found") },
265
+ "command-v:aider": { error: new Error("not found") },
266
+ "which:goose": { error: new Error("not found") },
267
+ "command-v:goose": { error: new Error("not found") },
268
+ });
269
+
270
+ const detector = createAgentDetector();
271
+ const result = await detector.detect();
272
+
273
+ const claude = result.agents.find((a) => a.id === "claude-code");
274
+ expect(claude!.installed).toBe(true);
275
+ expect(claude!.path).toBe("/usr/local/bin/claude");
276
+ });
277
+ });
278
+
279
+ // ===========================================================================
280
+ // Caching
281
+ // ===========================================================================
282
+
283
+ describe("caching", () => {
284
+ it("returns cached result on subsequent calls", async () => {
285
+ setupExecFileMock({
286
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
287
+ "version:claude": { stdout: "1.0.0\n" },
288
+ "which:codex": { error: new Error("not found") },
289
+ "command-v:codex": { error: new Error("not found") },
290
+ "which:gemini": { error: new Error("not found") },
291
+ "command-v:gemini": { error: new Error("not found") },
292
+ "which:opencode": { error: new Error("not found") },
293
+ "command-v:opencode": { error: new Error("not found") },
294
+ "which:aider": { error: new Error("not found") },
295
+ "command-v:aider": { error: new Error("not found") },
296
+ "which:goose": { error: new Error("not found") },
297
+ "command-v:goose": { error: new Error("not found") },
298
+ });
299
+
300
+ const detector = createAgentDetector();
301
+ const result1 = await detector.detect();
302
+ const callCount = mockExecFile.mock.calls.length;
303
+
304
+ const result2 = await detector.detect();
305
+ // No additional execFile calls — result was cached
306
+ expect(mockExecFile.mock.calls.length).toBe(callCount);
307
+ expect(result2).toEqual(result1);
308
+ });
309
+
310
+ it("bypasses cache when refresh is true", async () => {
311
+ setupExecFileMock({
312
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
313
+ "version:claude": { stdout: "1.0.0\n" },
314
+ "which:codex": { error: new Error("not found") },
315
+ "command-v:codex": { error: new Error("not found") },
316
+ "which:gemini": { error: new Error("not found") },
317
+ "command-v:gemini": { error: new Error("not found") },
318
+ "which:opencode": { error: new Error("not found") },
319
+ "command-v:opencode": { error: new Error("not found") },
320
+ "which:aider": { error: new Error("not found") },
321
+ "command-v:aider": { error: new Error("not found") },
322
+ "which:goose": { error: new Error("not found") },
323
+ "command-v:goose": { error: new Error("not found") },
324
+ });
325
+
326
+ const detector = createAgentDetector();
327
+ await detector.detect();
328
+ const callCountAfterFirst = mockExecFile.mock.calls.length;
329
+
330
+ await detector.detect({ refresh: true });
331
+ expect(mockExecFile.mock.calls.length).toBeGreaterThan(callCountAfterFirst);
332
+ });
333
+
334
+ it("invalidates cache manually", async () => {
335
+ setupExecFileMock({
336
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
337
+ "version:claude": { stdout: "1.0.0\n" },
338
+ "which:codex": { error: new Error("not found") },
339
+ "command-v:codex": { error: new Error("not found") },
340
+ "which:gemini": { error: new Error("not found") },
341
+ "command-v:gemini": { error: new Error("not found") },
342
+ "which:opencode": { error: new Error("not found") },
343
+ "command-v:opencode": { error: new Error("not found") },
344
+ "which:aider": { error: new Error("not found") },
345
+ "command-v:aider": { error: new Error("not found") },
346
+ "which:goose": { error: new Error("not found") },
347
+ "command-v:goose": { error: new Error("not found") },
348
+ });
349
+
350
+ const detector = createAgentDetector();
351
+ await detector.detect();
352
+ expect(detector.getCachedResult()).not.toBeNull();
353
+
354
+ detector.invalidateCache();
355
+ expect(detector.getCachedResult()).toBeNull();
356
+ });
357
+
358
+ it("expires cache after TTL", async () => {
359
+ setupExecFileMock({
360
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
361
+ "version:claude": { stdout: "1.0.0\n" },
362
+ "which:codex": { error: new Error("not found") },
363
+ "command-v:codex": { error: new Error("not found") },
364
+ "which:gemini": { error: new Error("not found") },
365
+ "command-v:gemini": { error: new Error("not found") },
366
+ "which:opencode": { error: new Error("not found") },
367
+ "command-v:opencode": { error: new Error("not found") },
368
+ "which:aider": { error: new Error("not found") },
369
+ "command-v:aider": { error: new Error("not found") },
370
+ "which:goose": { error: new Error("not found") },
371
+ "command-v:goose": { error: new Error("not found") },
372
+ });
373
+
374
+ const detector = createAgentDetector({ cacheTtlMs: 50 });
375
+ await detector.detect();
376
+ const callCountAfterFirst = mockExecFile.mock.calls.length;
377
+
378
+ // Wait for cache to expire
379
+ await new Promise((resolve) => setTimeout(resolve, 60));
380
+
381
+ await detector.detect();
382
+ expect(mockExecFile.mock.calls.length).toBeGreaterThan(callCountAfterFirst);
383
+ });
384
+ });
385
+
386
+ // ===========================================================================
387
+ // getAvailableAgents()
388
+ // ===========================================================================
389
+
390
+ describe("getAvailableAgents()", () => {
391
+ it("returns only installed agents by default", async () => {
392
+ setupExecFileMock({
393
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
394
+ "version:claude": { stdout: "1.0.0\n" },
395
+ "which:codex": { error: new Error("not found") },
396
+ "command-v:codex": { error: new Error("not found") },
397
+ "which:gemini": { error: new Error("not found") },
398
+ "command-v:gemini": { error: new Error("not found") },
399
+ "which:opencode": { error: new Error("not found") },
400
+ "command-v:opencode": { error: new Error("not found") },
401
+ "which:aider": { error: new Error("not found") },
402
+ "command-v:aider": { error: new Error("not found") },
403
+ "which:goose": { error: new Error("not found") },
404
+ "command-v:goose": { error: new Error("not found") },
405
+ });
406
+
407
+ const detector = createAgentDetector();
408
+ const result = await detector.getAvailableAgents();
409
+
410
+ expect(result.agents).toHaveLength(1);
411
+ expect(result.agents[0].id).toBe("claude-code");
412
+ });
413
+
414
+ it("includes not-installed agents when requested", async () => {
415
+ setupExecFileMock({
416
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
417
+ "version:claude": { stdout: "1.0.0\n" },
418
+ "which:codex": { error: new Error("not found") },
419
+ "command-v:codex": { error: new Error("not found") },
420
+ "which:gemini": { error: new Error("not found") },
421
+ "command-v:gemini": { error: new Error("not found") },
422
+ "which:opencode": { error: new Error("not found") },
423
+ "command-v:opencode": { error: new Error("not found") },
424
+ "which:aider": { error: new Error("not found") },
425
+ "command-v:aider": { error: new Error("not found") },
426
+ "which:goose": { error: new Error("not found") },
427
+ "command-v:goose": { error: new Error("not found") },
428
+ });
429
+
430
+ const detector = createAgentDetector();
431
+ const result = await detector.getAvailableAgents({ includeNotInstalled: true });
432
+
433
+ expect(result.agents).toHaveLength(6);
434
+ });
435
+ });
436
+
437
+ // ===========================================================================
438
+ // getDefinition()
439
+ // ===========================================================================
440
+
441
+ describe("getDefinition()", () => {
442
+ it("returns definition for known agent", () => {
443
+ const detector = createAgentDetector();
444
+ const def = detector.getDefinition("claude-code");
445
+ expect(def.id).toBe("claude-code");
446
+ expect(def.binary).toBe("claude");
447
+ });
448
+
449
+ it("throws AgentDetectionError with UNKNOWN_AGENT code for unknown agent", () => {
450
+ const detector = createAgentDetector();
451
+ try {
452
+ detector.getDefinition("foo-agent");
453
+ expect.fail("Expected AgentDetectionError to be thrown");
454
+ } catch (err) {
455
+ expect(err).toBeInstanceOf(AgentDetectionError);
456
+ const error = err as AgentDetectionError;
457
+ expect(error.code).toBe("UNKNOWN_AGENT");
458
+ expect(error.agentId).toBe("foo-agent");
459
+ expect(error.message).toBe("Unknown agent backend: foo-agent");
460
+ }
461
+ });
462
+ });
463
+
464
+ // ===========================================================================
465
+ // isInstalled()
466
+ // ===========================================================================
467
+
468
+ describe("isInstalled()", () => {
469
+ it("returns true for installed agent", async () => {
470
+ setupExecFileMock({
471
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
472
+ "version:claude": { stdout: "1.0.0\n" },
473
+ "which:codex": { error: new Error("not found") },
474
+ "command-v:codex": { error: new Error("not found") },
475
+ "which:gemini": { error: new Error("not found") },
476
+ "command-v:gemini": { error: new Error("not found") },
477
+ "which:opencode": { error: new Error("not found") },
478
+ "command-v:opencode": { error: new Error("not found") },
479
+ "which:aider": { error: new Error("not found") },
480
+ "command-v:aider": { error: new Error("not found") },
481
+ "which:goose": { error: new Error("not found") },
482
+ "command-v:goose": { error: new Error("not found") },
483
+ });
484
+
485
+ const detector = createAgentDetector();
486
+ expect(await detector.isInstalled("claude-code")).toBe(true);
487
+ });
488
+
489
+ it("returns false for non-installed agent", async () => {
490
+ setupExecFileMock({
491
+ "which:claude": { error: new Error("not found") },
492
+ "command-v:claude": { error: new Error("not found") },
493
+ "which:codex": { error: new Error("not found") },
494
+ "command-v:codex": { error: new Error("not found") },
495
+ "which:gemini": { error: new Error("not found") },
496
+ "command-v:gemini": { error: new Error("not found") },
497
+ "which:opencode": { error: new Error("not found") },
498
+ "command-v:opencode": { error: new Error("not found") },
499
+ "which:aider": { error: new Error("not found") },
500
+ "command-v:aider": { error: new Error("not found") },
501
+ "which:goose": { error: new Error("not found") },
502
+ "command-v:goose": { error: new Error("not found") },
503
+ });
504
+
505
+ const detector = createAgentDetector();
506
+ expect(await detector.isInstalled("codex")).toBe(false);
507
+ });
508
+ });
509
+
510
+ // ===========================================================================
511
+ // getAgent()
512
+ // ===========================================================================
513
+
514
+ describe("getAgent()", () => {
515
+ it("returns a specific detected agent by ID", async () => {
516
+ setupExecFileMock({
517
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
518
+ "version:claude": { stdout: "1.0.0\n" },
519
+ "which:codex": { error: new Error("not found") },
520
+ "command-v:codex": { error: new Error("not found") },
521
+ "which:gemini": { error: new Error("not found") },
522
+ "command-v:gemini": { error: new Error("not found") },
523
+ "which:opencode": { error: new Error("not found") },
524
+ "command-v:opencode": { error: new Error("not found") },
525
+ "which:aider": { error: new Error("not found") },
526
+ "command-v:aider": { error: new Error("not found") },
527
+ "which:goose": { error: new Error("not found") },
528
+ "command-v:goose": { error: new Error("not found") },
529
+ });
530
+
531
+ const detector = createAgentDetector();
532
+ const agent = await detector.getAgent("claude-code");
533
+ expect(agent).toBeDefined();
534
+ expect(agent!.id).toBe("claude-code");
535
+ expect(agent!.installed).toBe(true);
536
+ expect(agent!.version).toBe("1.0.0");
537
+ });
538
+
539
+ it("returns undefined for unknown agent ID", async () => {
540
+ setupExecFileMock({
541
+ "which:claude": { error: new Error("not found") },
542
+ "command-v:claude": { error: new Error("not found") },
543
+ "which:codex": { error: new Error("not found") },
544
+ "command-v:codex": { error: new Error("not found") },
545
+ "which:gemini": { error: new Error("not found") },
546
+ "command-v:gemini": { error: new Error("not found") },
547
+ "which:opencode": { error: new Error("not found") },
548
+ "command-v:opencode": { error: new Error("not found") },
549
+ "which:aider": { error: new Error("not found") },
550
+ "command-v:aider": { error: new Error("not found") },
551
+ "which:goose": { error: new Error("not found") },
552
+ "command-v:goose": { error: new Error("not found") },
553
+ });
554
+
555
+ const detector = createAgentDetector();
556
+ const agent = await detector.getAgent("nonexistent");
557
+ expect(agent).toBeUndefined();
558
+ });
559
+
560
+ it("returns not-installed agent entry by ID", async () => {
561
+ setupExecFileMock({
562
+ "which:claude": { error: new Error("not found") },
563
+ "command-v:claude": { error: new Error("not found") },
564
+ "which:codex": { error: new Error("not found") },
565
+ "command-v:codex": { error: new Error("not found") },
566
+ "which:gemini": { error: new Error("not found") },
567
+ "command-v:gemini": { error: new Error("not found") },
568
+ "which:opencode": { error: new Error("not found") },
569
+ "command-v:opencode": { error: new Error("not found") },
570
+ "which:aider": { error: new Error("not found") },
571
+ "command-v:aider": { error: new Error("not found") },
572
+ "which:goose": { error: new Error("not found") },
573
+ "command-v:goose": { error: new Error("not found") },
574
+ });
575
+
576
+ const detector = createAgentDetector();
577
+ const agent = await detector.getAgent("codex");
578
+ expect(agent).toBeDefined();
579
+ expect(agent!.id).toBe("codex");
580
+ expect(agent!.installed).toBe(false);
581
+ expect(agent!.version).toBeUndefined();
582
+ expect(agent!.path).toBeUndefined();
583
+ });
584
+ });
585
+
586
+ // ===========================================================================
587
+ // All agents not found (no crash)
588
+ // ===========================================================================
589
+
590
+ describe("detection with no agents installed", () => {
591
+ it("handles all agents not found without crashing", async () => {
592
+ setupExecFileMock({
593
+ "which:claude": { error: new Error("not found") },
594
+ "command-v:claude": { error: new Error("not found") },
595
+ "which:codex": { error: new Error("not found") },
596
+ "command-v:codex": { error: new Error("not found") },
597
+ "which:gemini": { error: new Error("not found") },
598
+ "command-v:gemini": { error: new Error("not found") },
599
+ "which:opencode": { error: new Error("not found") },
600
+ "command-v:opencode": { error: new Error("not found") },
601
+ "which:aider": { error: new Error("not found") },
602
+ "command-v:aider": { error: new Error("not found") },
603
+ "which:goose": { error: new Error("not found") },
604
+ "command-v:goose": { error: new Error("not found") },
605
+ });
606
+
607
+ const detector = createAgentDetector();
608
+ const result = await detector.detect();
609
+
610
+ expect(result.scanned).toBe(6);
611
+ expect(result.agents).toHaveLength(6);
612
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
613
+
614
+ // All agents should be marked as not installed
615
+ for (const agent of result.agents) {
616
+ expect(agent.installed).toBe(false);
617
+ expect(agent.version).toBeUndefined();
618
+ expect(agent.path).toBeUndefined();
619
+ expect(agent.definition).toBeDefined();
620
+ expect(agent.detectedAt).toBeGreaterThan(0);
621
+ }
622
+ });
623
+
624
+ it("getAvailableAgents returns empty list when nothing installed", async () => {
625
+ setupExecFileMock({
626
+ "which:claude": { error: new Error("not found") },
627
+ "command-v:claude": { error: new Error("not found") },
628
+ "which:codex": { error: new Error("not found") },
629
+ "command-v:codex": { error: new Error("not found") },
630
+ "which:gemini": { error: new Error("not found") },
631
+ "command-v:gemini": { error: new Error("not found") },
632
+ "which:opencode": { error: new Error("not found") },
633
+ "command-v:opencode": { error: new Error("not found") },
634
+ "which:aider": { error: new Error("not found") },
635
+ "command-v:aider": { error: new Error("not found") },
636
+ "which:goose": { error: new Error("not found") },
637
+ "command-v:goose": { error: new Error("not found") },
638
+ });
639
+
640
+ const detector = createAgentDetector();
641
+ const result = await detector.getAvailableAgents();
642
+ expect(result.agents).toHaveLength(0);
643
+ });
644
+ });
645
+
646
+ // ===========================================================================
647
+ // Concurrent detection deduplication
648
+ // ===========================================================================
649
+
650
+ describe("concurrent detection deduplication", () => {
651
+ it("deduplicates concurrent detect() calls into a single scan", async () => {
652
+ setupExecFileMock({
653
+ "which:claude": { stdout: "/usr/local/bin/claude\n" },
654
+ "version:claude": { stdout: "1.0.0\n" },
655
+ "which:codex": { error: new Error("not found") },
656
+ "command-v:codex": { error: new Error("not found") },
657
+ "which:gemini": { error: new Error("not found") },
658
+ "command-v:gemini": { error: new Error("not found") },
659
+ "which:opencode": { error: new Error("not found") },
660
+ "command-v:opencode": { error: new Error("not found") },
661
+ "which:aider": { error: new Error("not found") },
662
+ "command-v:aider": { error: new Error("not found") },
663
+ "which:goose": { error: new Error("not found") },
664
+ "command-v:goose": { error: new Error("not found") },
665
+ });
666
+
667
+ const detector = createAgentDetector();
668
+
669
+ // Fire two detect() calls concurrently (no await)
670
+ const promise1 = detector.detect({ refresh: true });
671
+ const promise2 = detector.detect({ refresh: true });
672
+
673
+ const [result1, result2] = await Promise.all([promise1, promise2]);
674
+
675
+ // Both should return the same result object
676
+ expect(result1).toEqual(result2);
677
+
678
+ // execFile should only be called once per agent (not doubled),
679
+ // meaning only one scan ran. Each agent needs 1-2 calls (which + maybe version).
680
+ // With 6 agents, a single scan calls which for each (6 calls) plus
681
+ // version for installed ones. Two scans would roughly double the call count.
682
+ // We check that the total is consistent with a single scan.
683
+ const totalCalls = mockExecFile.mock.calls.length;
684
+ // A single scan: 6 which calls + some fallback/version calls.
685
+ // Should be well under 20 for a single scan (two scans would be ~24+).
686
+ expect(totalCalls).toBeLessThanOrEqual(14);
687
+ });
688
+
689
+ it("isDetecting() returns true during active detection", async () => {
690
+ let resolveWhich: ((value: unknown) => void) | undefined;
691
+ const whichPromise = new Promise((resolve) => {
692
+ resolveWhich = resolve;
693
+ });
694
+
695
+ // Make the which call hang so we can check isDetecting()
696
+ mockExecFile.mockImplementation(((
697
+ _command: string,
698
+ _args: string[],
699
+ _options: unknown,
700
+ callback: (err: Error | null, result: { stdout: string; stderr: string }) => void
701
+ ) => {
702
+ whichPromise.then(() => {
703
+ callback(new Error("not found"), { stdout: "", stderr: "" });
704
+ });
705
+ }) as unknown as typeof execFile);
706
+
707
+ const detector = createAgentDetector();
708
+ const detectPromise = detector.detect();
709
+
710
+ // While the mock is still pending, isDetecting should be true
711
+ expect(detector.isDetecting()).toBe(true);
712
+
713
+ // Resolve the hanging mock
714
+ resolveWhich!(undefined);
715
+ await detectPromise;
716
+
717
+ expect(detector.isDetecting()).toBe(false);
718
+ });
719
+ });
720
+
721
+ // ===========================================================================
722
+ // AgentDetectionError
723
+ // ===========================================================================
724
+
725
+ describe("AgentDetectionError", () => {
726
+ it("has correct name property", () => {
727
+ const error = new AgentDetectionError("test", "UNKNOWN_AGENT", "foo");
728
+ expect(error.name).toBe("AgentDetectionError");
729
+ });
730
+
731
+ it("has correct code property", () => {
732
+ const error = new AgentDetectionError("test", "AGENT_NOT_INSTALLED", "bar");
733
+ expect(error.code).toBe("AGENT_NOT_INSTALLED");
734
+ });
735
+
736
+ it("has correct agentId property", () => {
737
+ const error = new AgentDetectionError("test", "UNKNOWN_AGENT", "my-agent");
738
+ expect(error.agentId).toBe("my-agent");
739
+ });
740
+
741
+ it("has correct message", () => {
742
+ const error = new AgentDetectionError("Something went wrong", "DETECTION_FAILED");
743
+ expect(error.message).toBe("Something went wrong");
744
+ });
745
+
746
+ it("is an instance of Error", () => {
747
+ const error = new AgentDetectionError("test", "UNKNOWN_AGENT");
748
+ expect(error).toBeInstanceOf(Error);
749
+ });
750
+
751
+ it("works without agentId", () => {
752
+ const error = new AgentDetectionError("test", "DETECTION_TIMEOUT");
753
+ expect(error.agentId).toBeUndefined();
754
+ expect(error.code).toBe("DETECTION_TIMEOUT");
755
+ });
756
+ });
757
+
758
+ // ===========================================================================
759
+ // isDetecting()
760
+ // ===========================================================================
761
+
762
+ describe("isDetecting()", () => {
763
+ it("returns false when not detecting", () => {
764
+ const detector = createAgentDetector();
765
+ expect(detector.isDetecting()).toBe(false);
766
+ });
767
+ });
768
+ });