macro-agent 0.1.0 → 0.1.1

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 (337) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -0,0 +1,995 @@
1
+ /**
2
+ * MCP Bridge Extensions (_macro/mcp/*)
3
+ *
4
+ * Server-side extension handlers that mirror each MCP tool.
5
+ * When MCP subprocesses run in thin-client mode, their tool calls
6
+ * are routed here via ephemeral MAP WebSocket connections.
7
+ *
8
+ * Each handler receives agent context via `params.context` since the
9
+ * MAP ExtensionContext only has participant info, not agent identity.
10
+ */
11
+
12
+ import type { MAPAdapter, ExtensionHandler, ExtensionContext } from "../interface.js";
13
+ import type { EventStore } from "../../../store/event-store.js";
14
+ import type { AgentManager } from "../../../agent/agent-manager.js";
15
+ import type { TaskManager } from "../../../task/task-manager.js";
16
+ import type { MessageRouter } from "../../../router/message-router.js";
17
+ import type { PeerManager } from "../../../peer/peer-manager.js";
18
+ import type { ActivityWatcher } from "../../../activity/watcher.js";
19
+ import type { RoleRegistry } from "../../../roles/types.js";
20
+ import type { TaskBackend } from "../../../task/backend/types.js";
21
+ import type { AgentId } from "../../../store/types/index.js";
22
+ import type { ToolContext } from "../../../mcp/types.js";
23
+ import type { Agent } from "../../../store/types/index.js";
24
+ import type { AgentStopReason } from "../../../agent/types.js";
25
+ import type { EventNotification, MAPEventType } from "../types.js";
26
+ import { RPCError } from "../rpc-handler.js";
27
+ import type { AgentTokenManager } from "../../../auth/token.js";
28
+ import { ulid } from "ulid";
29
+ import { createDoneHandler, type DoneToolDeps } from "../../../mcp/tools/done.js";
30
+ import { createInjectContextHandler } from "../../../mcp/tools/inject_context.js";
31
+ import {
32
+ createWaitForActivityHandler,
33
+ } from "../../../mcp/tools/wait_for_activity.js";
34
+ import { createClaimTaskHandler } from "../../../mcp/tools/claim_task.js";
35
+ import { createUnclaimTaskHandler } from "../../../mcp/tools/unclaim_task.js";
36
+ import { createListClaimableTasksHandler } from "../../../mcp/tools/list_claimable_tasks.js";
37
+
38
+ // =============================================================================
39
+ // Services
40
+ // =============================================================================
41
+
42
+ /**
43
+ * Services required for MCP bridge extensions.
44
+ */
45
+ export interface MCPBridgeServices {
46
+ eventStore: EventStore;
47
+ agentManager: AgentManager;
48
+ taskManager: TaskManager;
49
+ messageRouter: MessageRouter;
50
+ peerManager?: PeerManager;
51
+ activityWatcher?: ActivityWatcher;
52
+ roleRegistry?: RoleRegistry;
53
+ taskBackend?: TaskBackend;
54
+ taskToolProvider?: import("../../../task/backend/types.js").TaskToolProvider;
55
+ /** Mutable context holder for task tool provider agent_id injection */
56
+ taskToolContext?: { agent_id: string };
57
+ integrationStrategy?: import("../../../workspace/strategies/types.js").IntegrationStrategy;
58
+ /** Optional agent token manager for per-agent authentication */
59
+ agentTokenManager?: AgentTokenManager;
60
+ }
61
+
62
+ // =============================================================================
63
+ // Agent Token Validation (module-level, set by registerMCPBridgeExtensions)
64
+ // =============================================================================
65
+
66
+ let _agentTokenManager: AgentTokenManager | undefined;
67
+
68
+ // =============================================================================
69
+ // Agent Context Extraction
70
+ // =============================================================================
71
+
72
+ /**
73
+ * Agent context passed in params.context by thin-client MCP tools.
74
+ */
75
+ interface AgentContext {
76
+ agent_id: string;
77
+ session_id: string;
78
+ task_id?: string;
79
+ lineage: string[];
80
+ cwd: string;
81
+ agent_token?: string;
82
+ }
83
+
84
+ /**
85
+ * Extract and validate agent context from params.
86
+ */
87
+ function extractContext(params: unknown): { context: AgentContext; args: Record<string, unknown> } {
88
+ const p = (params ?? {}) as Record<string, unknown>;
89
+ const context = p.context as AgentContext | undefined;
90
+
91
+ if (!context?.agent_id) {
92
+ throw RPCError.invalidParams("params.context.agent_id is required");
93
+ }
94
+
95
+ // Validate per-agent token if token manager is configured
96
+ if (_agentTokenManager) {
97
+ if (!context.agent_token || !_agentTokenManager.verifyToken(context.agent_id, context.agent_token)) {
98
+ throw RPCError.invalidParams("Invalid or missing agent token");
99
+ }
100
+ }
101
+
102
+ // Return everything except context as args
103
+ const { context: _, ...args } = p;
104
+ return { context, args };
105
+ }
106
+
107
+ /**
108
+ * Build a ToolContext from the agent context.
109
+ */
110
+ function toToolContext(ctx: AgentContext): ToolContext {
111
+ return {
112
+ agent_id: ctx.agent_id,
113
+ session_id: ctx.session_id,
114
+ task_id: ctx.task_id,
115
+ lineage: ctx.lineage ?? [],
116
+ cwd: ctx.cwd ?? process.cwd(),
117
+ };
118
+ }
119
+
120
+ // =============================================================================
121
+ // Handler Implementations
122
+ // =============================================================================
123
+
124
+ function createSpawnAgentBridge(
125
+ services: MCPBridgeServices,
126
+ emitMAPEvent: (event: EventNotification) => void,
127
+ ): ExtensionHandler {
128
+ return async (_extCtx: ExtensionContext, params: unknown) => {
129
+ const { context, args } = extractContext(params);
130
+
131
+ const task = args.task as string;
132
+ const spawned = await services.agentManager.spawn({
133
+ task,
134
+ parent: context.agent_id,
135
+ subscribeParent: (args.subscribe_parent as boolean) ?? true,
136
+ topics: (args.topics as string[]) ?? [],
137
+ config: args.config as Record<string, unknown> | undefined,
138
+ cwd: (args.cwd as string) ?? context.cwd,
139
+ permissionMode: (args.permission_mode as string | undefined) as import("acp-factory").PermissionMode | undefined,
140
+ });
141
+
142
+ // Fire-and-forget initial prompt so the agent starts working on its task.
143
+ // Without this, the agent process is running but idle — waiting for a message.
144
+ if (task) {
145
+ (async () => {
146
+ // Accumulate assistant response parts for history persistence
147
+ const buffer: {
148
+ parts: Array<
149
+ | { type: "text"; text: string }
150
+ | ({ type: "tool" } & Record<string, unknown>)
151
+ >;
152
+ } = { parts: [] };
153
+ const toolInfoCache = new Map<
154
+ string,
155
+ { title?: string; name?: string; input?: unknown }
156
+ >();
157
+
158
+ // Emit user message event so TUI clients can show the task prompt
159
+ emitMAPEvent({
160
+ eventId: ulid(),
161
+ type: "session_user_message" as MAPEventType,
162
+ timestamp: Date.now(),
163
+ agentId: spawned.id as AgentId,
164
+ data: {
165
+ agentId: spawned.id,
166
+ sessionId: spawned.session_id,
167
+ content: task,
168
+ },
169
+ });
170
+
171
+ try {
172
+ for await (const update of services.agentManager.prompt(
173
+ spawned.id,
174
+ task,
175
+ )) {
176
+ // Accumulate content for turn recording (mirrors acp-over-map.ts handlePrompt)
177
+ const u = update as Record<string, unknown>;
178
+ const updateType = (u.sessionUpdate as string) ?? (u.type as string);
179
+
180
+ if (updateType === "agent_message_chunk") {
181
+ const content = u.content as { type?: string; text?: string } | undefined;
182
+ if (content?.text) {
183
+ const last = buffer.parts[buffer.parts.length - 1];
184
+ if (last && last.type === "text") {
185
+ last.text += content.text;
186
+ } else {
187
+ buffer.parts.push({ type: "text", text: content.text });
188
+ }
189
+ }
190
+ } else if (updateType === "tool_call" || updateType === "tool_call_update") {
191
+ const toolCallId = u.toolCallId as string | undefined;
192
+ const status = u.status as string | undefined;
193
+ const meta = u._meta as { claudeCode?: { toolName?: string } } | undefined;
194
+
195
+ if (updateType === "tool_call" && toolCallId) {
196
+ toolInfoCache.set(toolCallId, {
197
+ title: u.title as string | undefined,
198
+ name: meta?.claudeCode?.toolName,
199
+ input: u.rawInput,
200
+ });
201
+ }
202
+
203
+ if (status === "completed" || status === "failed") {
204
+ const cached = toolCallId ? toolInfoCache.get(toolCallId) : undefined;
205
+ const rawOutput = u.rawOutput;
206
+ let output: string | undefined;
207
+ if (typeof rawOutput === "string") output = rawOutput;
208
+ else if (Array.isArray(rawOutput)) {
209
+ output = rawOutput
210
+ .filter((item: any) => item.type === "text" && typeof item.text === "string")
211
+ .map((item: any) => item.text as string)
212
+ .join("\n") || undefined;
213
+ }
214
+ buffer.parts.push({
215
+ type: "tool",
216
+ toolCallId,
217
+ title: u.title ?? cached?.title,
218
+ name: meta?.claudeCode?.toolName ?? cached?.name,
219
+ status: u.status,
220
+ input: u.rawInput ?? cached?.input,
221
+ output,
222
+ });
223
+ }
224
+ }
225
+
226
+ // Emit each session update as a MAP event for live streaming
227
+ emitMAPEvent({
228
+ eventId: ulid(),
229
+ type: "session_update" as MAPEventType,
230
+ timestamp: Date.now(),
231
+ agentId: spawned.id as AgentId,
232
+ data: {
233
+ agentId: spawned.id,
234
+ sessionId: spawned.session_id,
235
+ update,
236
+ },
237
+ });
238
+ }
239
+
240
+ // Emit prompt done event
241
+ emitMAPEvent({
242
+ eventId: ulid(),
243
+ type: "session_prompt_done" as MAPEventType,
244
+ timestamp: Date.now(),
245
+ agentId: spawned.id as AgentId,
246
+ data: {
247
+ agentId: spawned.id,
248
+ sessionId: spawned.session_id,
249
+ stopReason: "end_turn",
250
+ },
251
+ });
252
+
253
+ // Record turns so history is available when TUI reconnects
254
+ const now = Date.now();
255
+ const conversationId = spawned.session_id;
256
+
257
+ // Record user turn (the initial task prompt)
258
+ services.eventStore.emit({
259
+ type: "turn",
260
+ source: { agent_id: spawned.id },
261
+ payload: {
262
+ action: "recorded",
263
+ turn_id: `turn_user_${now}_${Math.random().toString(36).slice(2, 8)}`,
264
+ conversation_id: conversationId,
265
+ participant: "user",
266
+ timestamp: now,
267
+ content_type: "user_prompt",
268
+ content: task,
269
+ source_type: "explicit",
270
+ },
271
+ });
272
+
273
+ // Record assistant turn with accumulated content
274
+ if (buffer.parts.length > 0) {
275
+ services.eventStore.emit({
276
+ type: "turn",
277
+ source: { agent_id: spawned.id },
278
+ payload: {
279
+ action: "recorded",
280
+ turn_id: `turn_asst_${now}_${Math.random().toString(36).slice(2, 8)}`,
281
+ conversation_id: conversationId,
282
+ participant: spawned.id,
283
+ timestamp: now + 1,
284
+ content_type: "assistant_response",
285
+ content: { parts: buffer.parts },
286
+ source_type: "explicit",
287
+ },
288
+ });
289
+ }
290
+ } catch (err) {
291
+ // Emit prompt done with error so TUI stops spinner
292
+ emitMAPEvent({
293
+ eventId: ulid(),
294
+ type: "session_prompt_done" as MAPEventType,
295
+ timestamp: Date.now(),
296
+ agentId: spawned.id as AgentId,
297
+ data: {
298
+ agentId: spawned.id,
299
+ sessionId: spawned.session_id,
300
+ stopReason: "error",
301
+ },
302
+ });
303
+ console.error(
304
+ `[MCP Bridge] Failed to send initial prompt to spawned agent ${spawned.id}:`,
305
+ err,
306
+ );
307
+ }
308
+ })();
309
+ }
310
+
311
+ return {
312
+ agent_id: spawned.id,
313
+ task_id: spawned.agent.task_id,
314
+ session_id: spawned.session_id,
315
+ };
316
+ };
317
+ }
318
+
319
+ function createEmitStatusBridge(
320
+ services: MCPBridgeServices,
321
+ emitMAPEvent: (event: EventNotification) => void,
322
+ ): ExtensionHandler {
323
+ return async (_extCtx: ExtensionContext, params: unknown) => {
324
+ const { context, args } = extractContext(params);
325
+
326
+ const event = services.eventStore.emit({
327
+ type: "status",
328
+ source: { agent_id: context.agent_id },
329
+ payload: {
330
+ status_type: args.status_type,
331
+ summary: args.summary,
332
+ details: args.details,
333
+ },
334
+ });
335
+
336
+ // Emit MAP event so TUI subscribers see the status update
337
+ const now = Date.now();
338
+ emitMAPEvent({
339
+ eventId: ulid(),
340
+ type: "status_emitted" as MAPEventType,
341
+ timestamp: now,
342
+ agentId: context.agent_id as AgentId,
343
+ data: {
344
+ id: event.id,
345
+ agentId: context.agent_id,
346
+ taskId: context.task_id,
347
+ statusType: args.status_type,
348
+ summary: args.summary,
349
+ details: args.details,
350
+ timestamp: now,
351
+ },
352
+ });
353
+
354
+ let taskUpdated = false;
355
+
356
+ if (args.complete_task && args.status_type === "completed" && context.task_id) {
357
+ try {
358
+ services.taskManager.updateStatus(context.task_id, "completed");
359
+ taskUpdated = true;
360
+ } catch { /* Task may not exist */ }
361
+ }
362
+
363
+ if (args.complete_task && args.status_type === "failed" && context.task_id) {
364
+ try {
365
+ services.taskManager.updateStatus(context.task_id, "failed");
366
+ taskUpdated = true;
367
+ } catch { /* Task may not exist */ }
368
+ }
369
+
370
+ return { event_id: event.id, task_updated: taskUpdated };
371
+ };
372
+ }
373
+
374
+ function createSendMessageBridge(services: MCPBridgeServices): ExtensionHandler {
375
+ return async (_extCtx: ExtensionContext, params: unknown) => {
376
+ const { context, args } = extractContext(params);
377
+ const to = args.to as { agent_id?: string; task_id?: string; topic?: string } | undefined;
378
+
379
+ if (!to) {
380
+ throw RPCError.invalidParams("params.to is required");
381
+ }
382
+
383
+ const address = to.agent_id
384
+ ? { agent: to.agent_id }
385
+ : to.task_id
386
+ ? { task: to.task_id }
387
+ : to.topic
388
+ ? { scope: to.topic }
389
+ : null;
390
+
391
+ if (!address) {
392
+ throw RPCError.invalidParams("Must specify one of: agent_id, task_id, or topic");
393
+ }
394
+
395
+ const result = await services.messageRouter.sendToAddress({
396
+ from: context.agent_id,
397
+ to: address,
398
+ content: args.content as string,
399
+ options: args.correlation_id ? { correlationId: args.correlation_id as string } : undefined,
400
+ });
401
+
402
+ return {
403
+ message_id: result.id,
404
+ delivered_to: result.delivered.length,
405
+ };
406
+ };
407
+ }
408
+
409
+ function createCheckMessagesBridge(services: MCPBridgeServices): ExtensionHandler {
410
+ return async (_extCtx: ExtensionContext, params: unknown) => {
411
+ const { context, args } = extractContext(params);
412
+ const limit = (args.limit as number) ?? 10;
413
+
414
+ const internalMessages = services.messageRouter.getMessages(context.agent_id, {
415
+ limit,
416
+ includeAcknowledged: (args.include_acknowledged as boolean) ?? false,
417
+ });
418
+
419
+ const formattedInternalMessages = internalMessages.map((msg) => ({
420
+ id: msg.id,
421
+ from: `agent:${msg.from.agent_id}`,
422
+ content: msg.content.length > 500 ? msg.content.substring(0, 500) : msg.content,
423
+ timestamp: msg.timestamp,
424
+ truncated: msg.truncated || msg.content.length > 500,
425
+ correlation_id: msg.correlation_id,
426
+ }));
427
+
428
+ // Get peer messages if peerManager is available
429
+ let formattedPeerMessages: Array<{
430
+ id: string;
431
+ from: string;
432
+ content: string;
433
+ timestamp: number;
434
+ truncated: boolean;
435
+ correlation_id?: string;
436
+ is_request?: boolean;
437
+ request_id?: string;
438
+ }> = [];
439
+
440
+ if (services.peerManager) {
441
+ const peerMessages = services.peerManager.getPeerMessages(context.agent_id);
442
+ formattedPeerMessages = peerMessages.map((msg) => ({
443
+ id: msg.id,
444
+ from: msg.from,
445
+ content: typeof msg.payload === "string"
446
+ ? msg.payload.length > 500 ? msg.payload.substring(0, 500) : msg.payload
447
+ : JSON.stringify(msg.payload).substring(0, 500),
448
+ timestamp: msg.timestamp,
449
+ truncated: typeof msg.payload === "string" ? msg.payload.length > 500 : false,
450
+ correlation_id: msg.correlationId,
451
+ is_request: msg.isRequest,
452
+ request_id: msg.requestId,
453
+ }));
454
+ }
455
+
456
+ const allFormattedMessages = [...formattedInternalMessages, ...formattedPeerMessages]
457
+ .sort((a, b) => a.timestamp - b.timestamp)
458
+ .slice(0, limit);
459
+
460
+ const allInternalMessages = services.messageRouter.getMessages(context.agent_id, {
461
+ limit: 1000,
462
+ includeAcknowledged: false,
463
+ });
464
+ const allPeerMessages = services.peerManager ? services.peerManager.getPeerMessages(context.agent_id) : [];
465
+
466
+ return {
467
+ messages: allFormattedMessages,
468
+ total_pending: allInternalMessages.length + allPeerMessages.length,
469
+ };
470
+ };
471
+ }
472
+
473
+ function createQueryIndexBridge(services: MCPBridgeServices): ExtensionHandler {
474
+ return async (_extCtx: ExtensionContext, params: unknown) => {
475
+ const { context: _context, args } = extractContext(params);
476
+
477
+ const entries: Array<{
478
+ type: "agent" | "task";
479
+ id: string;
480
+ summary: string;
481
+ state?: string;
482
+ status?: string;
483
+ }> = [];
484
+
485
+ const limit = (args.limit as number) ?? 20;
486
+ const offset = (args.offset as number) ?? 0;
487
+ const search = (args.search as string)?.toLowerCase();
488
+ const filter = args.filter as { state?: string; status?: string; parent?: string | null } | undefined;
489
+ const queryType = args.type as string;
490
+
491
+ if (queryType === "agents" || queryType === "all") {
492
+ let agents = services.agentManager.list();
493
+
494
+ if (filter?.state) {
495
+ agents = agents.filter((a) => a.state === filter!.state);
496
+ }
497
+ if (filter?.parent !== undefined) {
498
+ agents = agents.filter((a) => a.parent === filter!.parent);
499
+ }
500
+ if (search) {
501
+ agents = agents.filter(
502
+ (a) =>
503
+ a.id.toLowerCase().includes(search) ||
504
+ a.task?.toLowerCase().includes(search)
505
+ );
506
+ }
507
+
508
+ for (const agent of agents) {
509
+ entries.push({
510
+ type: "agent",
511
+ id: agent.id,
512
+ summary: agent.task ?? "No task description",
513
+ state: agent.state,
514
+ });
515
+ }
516
+ }
517
+
518
+ if (queryType === "tasks" || queryType === "all") {
519
+ let tasks = services.taskManager.list();
520
+
521
+ if (filter?.status) {
522
+ tasks = tasks.filter((t) => t.status === filter!.status);
523
+ }
524
+ if (search) {
525
+ tasks = tasks.filter((t) =>
526
+ t.id.toLowerCase().includes(search) ||
527
+ t.description.toLowerCase().includes(search)
528
+ );
529
+ }
530
+
531
+ for (const task of tasks) {
532
+ entries.push({
533
+ type: "task",
534
+ id: task.id,
535
+ summary: task.description,
536
+ status: task.status,
537
+ });
538
+ }
539
+ }
540
+
541
+ const paginatedEntries = entries.slice(offset, offset + limit);
542
+
543
+ return {
544
+ entries: paginatedEntries,
545
+ total: entries.length,
546
+ has_more: offset + limit < entries.length,
547
+ };
548
+ };
549
+ }
550
+
551
+ function createGetHierarchyBridge(services: MCPBridgeServices): ExtensionHandler {
552
+ return async (_extCtx: ExtensionContext, params: unknown) => {
553
+ const { context, args } = extractContext(params);
554
+
555
+ let rootId: AgentId;
556
+ if (args.root) {
557
+ rootId = args.root as string;
558
+ } else {
559
+ rootId = context.lineage.length > 0 ? context.lineage[0] : context.agent_id;
560
+ }
561
+
562
+ const hierarchy = services.agentManager.getHierarchy(rootId);
563
+ if (!hierarchy) {
564
+ throw RPCError.notFound("agent", rootId);
565
+ }
566
+
567
+ function buildNode(node: { agent: Agent; children: Array<{ agent: Agent; children: any[] }> }, currentDepth: number): {
568
+ agent_id: string;
569
+ task: string;
570
+ state: string;
571
+ children: any[];
572
+ } {
573
+ const shouldIncludeChildren = args.depth === undefined || currentDepth < (args.depth as number);
574
+ return {
575
+ agent_id: node.agent.id,
576
+ task: node.agent.task ?? "No task",
577
+ state: node.agent.state,
578
+ children: shouldIncludeChildren
579
+ ? node.children.map((c) => buildNode(c, currentDepth + 1))
580
+ : [],
581
+ };
582
+ }
583
+
584
+ const tree = buildNode(hierarchy.root, 1);
585
+
586
+ return {
587
+ tree,
588
+ depth: hierarchy.depth,
589
+ total_agents: hierarchy.totalAgents,
590
+ };
591
+ };
592
+ }
593
+
594
+ function createGetAgentSummaryBridge(services: MCPBridgeServices): ExtensionHandler {
595
+ return async (_extCtx: ExtensionContext, params: unknown) => {
596
+ const { context: _context, args } = extractContext(params);
597
+ const agentId = args.agent_id as string;
598
+
599
+ const agent = services.agentManager.get(agentId);
600
+ if (!agent) {
601
+ throw RPCError.notFound("agent", agentId);
602
+ }
603
+
604
+ const children = services.agentManager.getChildren(agentId);
605
+
606
+ const statusEvents = services.eventStore.query({
607
+ type: "status",
608
+ source_agent_id: agentId,
609
+ limit: 1,
610
+ });
611
+
612
+ const recentStatus = statusEvents.length > 0
613
+ ? {
614
+ type: statusEvents[0].payload.status_type as string,
615
+ summary: statusEvents[0].payload.summary as string,
616
+ timestamp: statusEvents[0].timestamp,
617
+ }
618
+ : undefined;
619
+
620
+ const lastActivity = agent.stopped_at ?? agent.started_at ?? agent.created_at;
621
+
622
+ return {
623
+ id: agent.id,
624
+ session_id: agent.session_id,
625
+ task: agent.task ?? "No task",
626
+ state: agent.state,
627
+ parent: agent.parent,
628
+ children_count: children.length,
629
+ last_activity: lastActivity,
630
+ recent_status: recentStatus,
631
+ };
632
+ };
633
+ }
634
+
635
+ function createStopAgentBridge(services: MCPBridgeServices): ExtensionHandler {
636
+ return async (_extCtx: ExtensionContext, params: unknown) => {
637
+ const { context, args } = extractContext(params);
638
+ const targetAgentId = args.agent_id as string;
639
+ const reason = ((args.reason as string) ?? "cancelled") as AgentStopReason;
640
+
641
+ const targetAgent = services.agentManager.get(targetAgentId);
642
+ if (!targetAgent) {
643
+ throw RPCError.notFound("agent", targetAgentId);
644
+ }
645
+
646
+ // Check subtree authorization
647
+ const isInSubtree =
648
+ targetAgentId === context.agent_id ||
649
+ targetAgent.lineage?.includes(context.agent_id);
650
+
651
+ if (!isInSubtree) {
652
+ throw RPCError.permissionDenied(
653
+ `Cannot stop agent outside your subtree: ${targetAgentId}`
654
+ );
655
+ }
656
+
657
+ const stoppedAgents: AgentId[] = [];
658
+
659
+ async function stopRecursive(agentId: AgentId): Promise<void> {
660
+ const agent = services.agentManager.get(agentId);
661
+ if (!agent || agent.state === "stopped") return;
662
+
663
+ const children = services.agentManager.getChildren(agentId);
664
+ for (const child of children) {
665
+ await stopRecursive(child.id);
666
+ }
667
+
668
+ await services.agentManager.terminate(agentId, reason);
669
+ stoppedAgents.push(agentId);
670
+ }
671
+
672
+ await stopRecursive(targetAgentId);
673
+
674
+ return {
675
+ success: true,
676
+ stopped_agents: stoppedAgents,
677
+ };
678
+ };
679
+ }
680
+
681
+ function createDoneBridge(services: MCPBridgeServices): ExtensionHandler {
682
+ return async (_extCtx: ExtensionContext, params: unknown) => {
683
+ const { context, args } = extractContext(params);
684
+ const toolContext = toToolContext(context);
685
+
686
+ const doneDeps: DoneToolDeps = {
687
+ eventStore: services.eventStore,
688
+ agentManager: services.agentManager,
689
+ messageRouter: services.messageRouter,
690
+ taskManager: services.taskManager,
691
+ roleRegistry: services.roleRegistry,
692
+ integrationStrategy: services.integrationStrategy,
693
+ };
694
+
695
+ const doneHandler = createDoneHandler(toolContext, doneDeps);
696
+ const result = await doneHandler(args as {
697
+ status: "completed" | "failed" | "blocked" | "deferred";
698
+ summary?: string;
699
+ details?: Record<string, unknown>;
700
+ task_id?: string;
701
+ });
702
+
703
+ // Handle termination if needed
704
+ if (result.shouldTerminate) {
705
+ setImmediate(async () => {
706
+ try {
707
+ await services.agentManager.terminate(context.agent_id, "completed");
708
+ } catch { /* ignore */ }
709
+ });
710
+ }
711
+
712
+ return result;
713
+ };
714
+ }
715
+
716
+ function createInjectContextBridge(services: MCPBridgeServices): ExtensionHandler {
717
+ return async (_extCtx: ExtensionContext, params: unknown) => {
718
+ const { context, args } = extractContext(params);
719
+
720
+ const handler = createInjectContextHandler(
721
+ { agentManager: services.agentManager, messageRouter: services.messageRouter },
722
+ context.agent_id
723
+ );
724
+
725
+ return handler(args as {
726
+ target_agent_id: string;
727
+ content: string;
728
+ urgent?: boolean;
729
+ reason?: string;
730
+ });
731
+ };
732
+ }
733
+
734
+ function createWaitForActivityBridge(services: MCPBridgeServices): ExtensionHandler {
735
+ return async (_extCtx: ExtensionContext, params: unknown) => {
736
+ const { context, args } = extractContext(params);
737
+
738
+ if (!services.activityWatcher) {
739
+ throw RPCError.internalError("ActivityWatcher not available");
740
+ }
741
+
742
+ const toolContext = toToolContext(context);
743
+ const handler = createWaitForActivityHandler(toolContext, {
744
+ activityWatcher: services.activityWatcher,
745
+ });
746
+
747
+ return handler(args as {
748
+ event_types?: string[];
749
+ timeout_ms?: number;
750
+ scope?: {
751
+ subtree?: string;
752
+ role?: string;
753
+ target_agent?: string;
754
+ };
755
+ });
756
+ };
757
+ }
758
+
759
+ function createClaimTaskBridge(services: MCPBridgeServices): ExtensionHandler {
760
+ return async (_extCtx: ExtensionContext, params: unknown) => {
761
+ const { context, args } = extractContext(params);
762
+
763
+ if (!services.taskBackend) {
764
+ throw RPCError.internalError("Task backend not available");
765
+ }
766
+
767
+ const toolContext = toToolContext(context);
768
+ const handler = createClaimTaskHandler(toolContext, {
769
+ taskBackend: services.taskBackend,
770
+ });
771
+
772
+ return handler(args as { tags?: string[]; root_tasks_only?: boolean });
773
+ };
774
+ }
775
+
776
+ function createUnclaimTaskBridge(services: MCPBridgeServices): ExtensionHandler {
777
+ return async (_extCtx: ExtensionContext, params: unknown) => {
778
+ const { context, args } = extractContext(params);
779
+
780
+ if (!services.taskBackend) {
781
+ throw RPCError.internalError("Task backend not available");
782
+ }
783
+
784
+ const toolContext = toToolContext(context);
785
+ const handler = createUnclaimTaskHandler(toolContext, {
786
+ taskBackend: services.taskBackend,
787
+ });
788
+
789
+ return handler(args as { task_id: string });
790
+ };
791
+ }
792
+
793
+ function createListClaimableTasksBridge(services: MCPBridgeServices): ExtensionHandler {
794
+ return async (_extCtx: ExtensionContext, params: unknown) => {
795
+ const { context, args } = extractContext(params);
796
+
797
+ if (!services.taskBackend) {
798
+ throw RPCError.internalError("Task backend not available");
799
+ }
800
+
801
+ const toolContext = toToolContext(context);
802
+ const handler = createListClaimableTasksHandler(toolContext, {
803
+ taskBackend: services.taskBackend,
804
+ });
805
+
806
+ return handler(args as { tags?: string[]; root_tasks_only?: boolean; limit?: number });
807
+ };
808
+ }
809
+
810
+ // Peer communication bridges
811
+
812
+ function createSendPeerMessageBridge(services: MCPBridgeServices): ExtensionHandler {
813
+ return async (_extCtx: ExtensionContext, params: unknown) => {
814
+ const { context, args } = extractContext(params);
815
+
816
+ if (!services.peerManager || !services.peerManager.hasTransport()) {
817
+ throw RPCError.internalError("Peer communication not available");
818
+ }
819
+
820
+ await services.peerManager.sendMessage(context.agent_id, args.to as string, {
821
+ type: args.type as string,
822
+ payload: args.payload,
823
+ metadata: args.correlation_id ? { correlationId: args.correlation_id as string } : undefined,
824
+ });
825
+
826
+ return { success: true, timestamp: Date.now() };
827
+ };
828
+ }
829
+
830
+ function createSendPeerRequestBridge(services: MCPBridgeServices): ExtensionHandler {
831
+ return async (_extCtx: ExtensionContext, params: unknown) => {
832
+ const { context, args } = extractContext(params);
833
+
834
+ if (!services.peerManager || !services.peerManager.hasTransport()) {
835
+ throw RPCError.internalError("Peer communication not available");
836
+ }
837
+
838
+ return services.peerManager.sendRequest(context.agent_id, args.to as string, {
839
+ method: args.method as string,
840
+ params: args.params,
841
+ timeout: args.timeout as number | undefined,
842
+ });
843
+ };
844
+ }
845
+
846
+ function createRespondToPeerRequestBridge(services: MCPBridgeServices): ExtensionHandler {
847
+ return async (_extCtx: ExtensionContext, params: unknown) => {
848
+ const { context, args } = extractContext(params);
849
+
850
+ if (!services.peerManager) {
851
+ throw RPCError.internalError("Peer communication not available");
852
+ }
853
+
854
+ services.peerManager.respondToRequest(context.agent_id, args.request_id as string, {
855
+ result: args.result,
856
+ error: args.error as { code: number; message: string; data?: unknown } | undefined,
857
+ });
858
+
859
+ return { success: true };
860
+ };
861
+ }
862
+
863
+ // =============================================================================
864
+ // Dynamic Task Tool Bridge
865
+ // =============================================================================
866
+
867
+ /**
868
+ * Creates a bridge handler for a dynamic task tool from the TaskToolProvider.
869
+ * The handler extracts agent context from params and delegates to the tool's
870
+ * handler. The TaskToolProvider's getContext() is backed by a mutable holder
871
+ * that we update here before each call (safe since Node.js is single-threaded).
872
+ */
873
+ function createTaskToolBridge(
874
+ tool: import("../../../task/backend/types.js").MCPToolDefinition,
875
+ contextHolder: { agent_id: string },
876
+ ): ExtensionHandler {
877
+ return async (_extCtx: ExtensionContext, params: unknown) => {
878
+ const { context, args } = extractContext(params);
879
+
880
+ // Set the calling agent's ID so the tool provider's getContext() returns it
881
+ contextHolder.agent_id = context.agent_id;
882
+
883
+ // The tool handler receives the params directly (or unwrapped from the
884
+ // `params` envelope that the thin-client MCP server wraps them in)
885
+ const toolParams = (args as Record<string, unknown>).params ?? args;
886
+
887
+ // The tool handler now calls backend.syncExternalTransition() internally
888
+ // after successful opentasks transitions, which updates the EventStore
889
+ // and triggers MAP events via onTaskChange.
890
+ return await tool.handler(toolParams);
891
+ };
892
+ }
893
+
894
+ // =============================================================================
895
+ // Registration
896
+ // =============================================================================
897
+
898
+ /**
899
+ * All MCP bridge extension method names.
900
+ */
901
+ export const MCP_BRIDGE_METHODS = [
902
+ "_macro/mcp/spawn_agent",
903
+ "_macro/mcp/emit_status",
904
+ "_macro/mcp/send_message",
905
+ "_macro/mcp/check_messages",
906
+ "_macro/mcp/query_index",
907
+ "_macro/mcp/get_hierarchy",
908
+ "_macro/mcp/get_agent_summary",
909
+ "_macro/mcp/stop_agent",
910
+ "_macro/mcp/done",
911
+ "_macro/mcp/inject_context",
912
+ "_macro/mcp/wait_for_activity",
913
+ "_macro/mcp/claim_task",
914
+ "_macro/mcp/unclaim_task",
915
+ "_macro/mcp/list_claimable_tasks",
916
+ "_macro/mcp/send_peer_message",
917
+ "_macro/mcp/send_peer_request",
918
+ "_macro/mcp/respond_to_peer_request",
919
+ "_macro/mcp/task_tools_list",
920
+ ] as const;
921
+
922
+ /**
923
+ * Register all MCP bridge extension methods with the MAP adapter.
924
+ *
925
+ * These handlers mirror each MCP tool, allowing thin-client MCP subprocesses
926
+ * to execute tool logic on the main server via ephemeral WebSocket calls.
927
+ */
928
+ export function registerMCPBridgeExtensions(
929
+ adapter: MAPAdapter,
930
+ services: MCPBridgeServices
931
+ ): void {
932
+ // Set module-level agent token manager for extractContext() validation
933
+ _agentTokenManager = services.agentTokenManager;
934
+
935
+ const emitMAPEvent = (event: EventNotification) => adapter.emitEvent(event);
936
+
937
+ adapter.registerExtension("_macro/mcp/spawn_agent", createSpawnAgentBridge(services, emitMAPEvent));
938
+ adapter.registerExtension("_macro/mcp/emit_status", createEmitStatusBridge(services, emitMAPEvent));
939
+ adapter.registerExtension("_macro/mcp/send_message", createSendMessageBridge(services));
940
+ adapter.registerExtension("_macro/mcp/check_messages", createCheckMessagesBridge(services));
941
+ adapter.registerExtension("_macro/mcp/query_index", createQueryIndexBridge(services));
942
+ adapter.registerExtension("_macro/mcp/get_hierarchy", createGetHierarchyBridge(services));
943
+ adapter.registerExtension("_macro/mcp/get_agent_summary", createGetAgentSummaryBridge(services));
944
+ adapter.registerExtension("_macro/mcp/stop_agent", createStopAgentBridge(services));
945
+ adapter.registerExtension("_macro/mcp/done", createDoneBridge(services));
946
+ adapter.registerExtension("_macro/mcp/inject_context", createInjectContextBridge(services));
947
+
948
+ if (services.activityWatcher) {
949
+ adapter.registerExtension("_macro/mcp/wait_for_activity", createWaitForActivityBridge(services));
950
+ }
951
+
952
+ if (services.taskBackend) {
953
+ adapter.registerExtension("_macro/mcp/claim_task", createClaimTaskBridge(services));
954
+ adapter.registerExtension("_macro/mcp/unclaim_task", createUnclaimTaskBridge(services));
955
+ adapter.registerExtension("_macro/mcp/list_claimable_tasks", createListClaimableTasksBridge(services));
956
+ }
957
+
958
+ if (services.peerManager) {
959
+ adapter.registerExtension("_macro/mcp/send_peer_message", createSendPeerMessageBridge(services));
960
+ adapter.registerExtension("_macro/mcp/send_peer_request", createSendPeerRequestBridge(services));
961
+ adapter.registerExtension("_macro/mcp/respond_to_peer_request", createRespondToPeerRequestBridge(services));
962
+ }
963
+
964
+ // Register dynamic task tool bridges from the TaskToolProvider
965
+ if (services.taskToolProvider) {
966
+ const contextHolder = services.taskToolContext ?? { agent_id: "" };
967
+ const tools = services.taskToolProvider.getTools();
968
+ for (const tool of tools) {
969
+ const method = `_macro/mcp/task_tool/${tool.name}`;
970
+ adapter.registerExtension(method, createTaskToolBridge(tool, contextHolder));
971
+ }
972
+
973
+ // Discovery endpoint: returns list of available task tool names so
974
+ // thin-client MCP subprocesses only register tools the server supports
975
+ adapter.registerExtension("_macro/mcp/task_tools_list", async () => {
976
+ return { tools: tools.map((t) => ({ name: t.name, description: t.description })) };
977
+ });
978
+ } else {
979
+ // No task tools available — return empty list
980
+ adapter.registerExtension("_macro/mcp/task_tools_list", async () => {
981
+ return { tools: [] };
982
+ });
983
+ }
984
+ }
985
+
986
+ /**
987
+ * Unregister all MCP bridge extension methods.
988
+ */
989
+ export function unregisterMCPBridgeExtensions(adapter: MAPAdapter): void {
990
+ for (const method of MCP_BRIDGE_METHODS) {
991
+ try {
992
+ adapter.unregisterExtension(method);
993
+ } catch { /* ignore */ }
994
+ }
995
+ }