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
@@ -1,12 +1,16 @@
1
1
  /**
2
- * Project Configuration Loader
2
+ * Configuration Loader
3
3
  *
4
- * Reads .macro-agent/config.json for project-level settings.
4
+ * Layered configuration system with priority (highest to lowest):
5
+ * 1. Environment variables (MACRO_*, OPENTASKS_*)
6
+ * 2. Project config: .multiagent/config.json
7
+ * 3. Global config: ~/.multiagent/config.json
5
8
  *
6
9
  * @module config/project-config
7
10
  */
8
11
 
9
12
  import * as fs from "fs";
13
+ import * as os from "os";
10
14
  import * as path from "path";
11
15
 
12
16
  // =============================================================================
@@ -14,9 +18,49 @@ import * as path from "path";
14
18
  // =============================================================================
15
19
 
16
20
  /**
17
- * Project-level configuration schema.
21
+ * Typed configuration schema for multiagent.
18
22
  *
19
- * Loaded from .macro-agent/config.json in the project root.
23
+ * Loaded from .multiagent/config.json (project or global).
24
+ * Environment variables override file-based config.
25
+ */
26
+ export interface MultiagentConfig {
27
+ /** Team template name to load on startup */
28
+ team?: string;
29
+
30
+ /** Server port (default: 3001) */
31
+ port?: number;
32
+
33
+ /** Server host (default: "localhost") */
34
+ host?: string;
35
+
36
+ /** Authentication config */
37
+ auth?: {
38
+ /** Disable auth entirely */
39
+ disabled?: boolean;
40
+ /** Server secret token */
41
+ secret?: string;
42
+ };
43
+
44
+ /** Task backend config */
45
+ task?: {
46
+ /** Backend type: "memory" | "opentasks" */
47
+ backend?: string;
48
+ /** OpenTasks-specific config */
49
+ opentasks?: {
50
+ /** Path to OpenTasks daemon socket */
51
+ socket_path?: string;
52
+ /** Auto-start central daemon (default: true) */
53
+ auto_start?: boolean;
54
+ /** Central daemon location (default: ~/.multiagent/opentasks) */
55
+ central_path?: string;
56
+ /** Auto-connect project .opentasks/ on agent spawn (default: true) */
57
+ connect_on_spawn?: boolean;
58
+ };
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Legacy project config interface (backwards compatibility).
20
64
  */
21
65
  export interface ProjectConfig {
22
66
  /** Team template name to load on startup */
@@ -31,38 +75,21 @@ export interface ProjectConfig {
31
75
  // =============================================================================
32
76
 
33
77
  /** Config directory name */
34
- export const CONFIG_DIR = ".macro-agent";
78
+ export const CONFIG_DIR = ".multiagent";
35
79
 
36
80
  /** Config file name */
37
81
  export const CONFIG_FILE = "config.json";
38
82
 
39
83
  // =============================================================================
40
- // Loader
84
+ // JSON Loader (shared)
41
85
  // =============================================================================
42
86
 
43
87
  /**
44
- * Get the project config file path.
45
- *
46
- * @param projectPath - Project root directory (default: process.cwd())
47
- * @returns Absolute path to .macro-agent/config.json
48
- */
49
- export function getProjectConfigPath(projectPath?: string): string {
50
- const root = projectPath ?? process.cwd();
51
- return path.join(root, CONFIG_DIR, CONFIG_FILE);
52
- }
53
-
54
- /**
55
- * Load project configuration from .macro-agent/config.json.
56
- *
57
- * Returns empty config if the file doesn't exist.
88
+ * Load and parse a JSON config file.
89
+ * Returns empty object if file doesn't exist.
58
90
  * Throws on invalid JSON.
59
- *
60
- * @param projectPath - Project root directory (default: process.cwd())
61
- * @returns Parsed ProjectConfig
62
91
  */
63
- export function loadProjectConfig(projectPath?: string): ProjectConfig {
64
- const configPath = getProjectConfigPath(projectPath);
65
-
92
+ function loadJsonConfig(configPath: string): Record<string, unknown> {
66
93
  if (!fs.existsSync(configPath)) {
67
94
  return {};
68
95
  }
@@ -78,7 +105,7 @@ export function loadProjectConfig(projectPath?: string): ProjectConfig {
78
105
  configPath
79
106
  );
80
107
  }
81
- return parsed as ProjectConfig;
108
+ return parsed as Record<string, unknown>;
82
109
  } catch (error) {
83
110
  if (error instanceof ProjectConfigError) throw error;
84
111
  throw new ProjectConfigError(
@@ -89,6 +116,142 @@ export function loadProjectConfig(projectPath?: string): ProjectConfig {
89
116
  }
90
117
  }
91
118
 
119
+ // =============================================================================
120
+ // Path Resolution
121
+ // =============================================================================
122
+
123
+ /**
124
+ * Get the project config file path.
125
+ *
126
+ * @param projectPath - Project root directory (default: process.cwd())
127
+ * @returns Absolute path to .multiagent/config.json
128
+ */
129
+ export function getProjectConfigPath(projectPath?: string): string {
130
+ const root = projectPath ?? process.cwd();
131
+ return path.join(root, CONFIG_DIR, CONFIG_FILE);
132
+ }
133
+
134
+ /**
135
+ * Get the global config file path.
136
+ *
137
+ * @returns Absolute path to ~/.multiagent/config.json
138
+ */
139
+ export function getGlobalConfigPath(): string {
140
+ return path.join(os.homedir(), CONFIG_DIR, CONFIG_FILE);
141
+ }
142
+
143
+ // =============================================================================
144
+ // Individual Loaders
145
+ // =============================================================================
146
+
147
+ /**
148
+ * Load project configuration from .multiagent/config.json.
149
+ *
150
+ * Returns empty config if the file doesn't exist.
151
+ * Throws on invalid JSON.
152
+ *
153
+ * @param projectPath - Project root directory (default: process.cwd())
154
+ * @returns Parsed ProjectConfig
155
+ */
156
+ export function loadProjectConfig(projectPath?: string): ProjectConfig {
157
+ return loadJsonConfig(getProjectConfigPath(projectPath)) as ProjectConfig;
158
+ }
159
+
160
+ /**
161
+ * Load global configuration from ~/.multiagent/config.json.
162
+ *
163
+ * Returns empty config if the file doesn't exist.
164
+ * Throws on invalid JSON.
165
+ *
166
+ * @returns Parsed config
167
+ */
168
+ export function loadGlobalConfig(): MultiagentConfig {
169
+ return loadJsonConfig(getGlobalConfigPath()) as MultiagentConfig;
170
+ }
171
+
172
+ // =============================================================================
173
+ // Merged Config (layered)
174
+ // =============================================================================
175
+
176
+ /**
177
+ * Deep merge two objects. Source values override target values.
178
+ * Only merges plain objects recursively; arrays and primitives are replaced.
179
+ */
180
+ function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T {
181
+ const result = { ...target };
182
+
183
+ for (const key of Object.keys(source)) {
184
+ const sourceVal = source[key];
185
+ const targetVal = (result as Record<string, unknown>)[key];
186
+
187
+ if (
188
+ sourceVal !== undefined &&
189
+ sourceVal !== null &&
190
+ typeof sourceVal === "object" &&
191
+ !Array.isArray(sourceVal) &&
192
+ typeof targetVal === "object" &&
193
+ targetVal !== null &&
194
+ !Array.isArray(targetVal)
195
+ ) {
196
+ (result as Record<string, unknown>)[key] = deepMerge(
197
+ targetVal as Record<string, unknown>,
198
+ sourceVal as Record<string, unknown>
199
+ );
200
+ } else if (sourceVal !== undefined) {
201
+ (result as Record<string, unknown>)[key] = sourceVal;
202
+ }
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ /**
209
+ * Load merged configuration with layered priority:
210
+ * 1. Environment variables (highest)
211
+ * 2. Project .multiagent/config.json
212
+ * 3. Global ~/.multiagent/config.json (lowest)
213
+ *
214
+ * Only server-level settings are merged. Agent-level env vars
215
+ * (MACRO_AGENT_ID, MACRO_SERVER_URL, etc.) are internal wiring
216
+ * and not part of this config.
217
+ *
218
+ * @param projectPath - Project root directory (default: process.cwd())
219
+ * @returns Fully merged MultiagentConfig
220
+ */
221
+ export function loadMergedConfig(projectPath?: string): MultiagentConfig {
222
+ const globalConfig = loadGlobalConfig();
223
+ const projectConfig = loadProjectConfig(projectPath);
224
+
225
+ // Layer 1: global (lowest priority)
226
+ // Layer 2: project overrides global
227
+ const merged: MultiagentConfig = deepMerge(
228
+ globalConfig as Record<string, unknown>,
229
+ projectConfig as Record<string, unknown>,
230
+ ) as MultiagentConfig;
231
+
232
+ // Layer 3: env vars override everything (server-level only)
233
+ if (process.env.MACRO_TASK_BACKEND) {
234
+ merged.task = { ...(merged.task ?? {}), backend: process.env.MACRO_TASK_BACKEND };
235
+ }
236
+ if (process.env.OPENTASKS_SOCKET_PATH) {
237
+ merged.task = {
238
+ ...(merged.task ?? {}),
239
+ opentasks: {
240
+ ...(merged.task?.opentasks ?? {}),
241
+ socket_path: process.env.OPENTASKS_SOCKET_PATH,
242
+ },
243
+ };
244
+ }
245
+ if (process.env.MACRO_SERVER_SECRET) {
246
+ merged.auth = { ...(merged.auth ?? {}), secret: process.env.MACRO_SERVER_SECRET };
247
+ }
248
+ if (process.env.MACRO_NO_AUTH === "true") {
249
+ merged.auth = { ...(merged.auth ?? {}), disabled: true };
250
+ }
251
+
252
+ return merged;
253
+ }
254
+
92
255
  // =============================================================================
93
256
  // Errors
94
257
  // =============================================================================
@@ -74,7 +74,7 @@ function createCascadeAdapter(harness: TestHarness): CascadeAgentManager {
74
74
  }
75
75
  // Update agent state in EventStore
76
76
  harness.eventStore.emit({
77
- type: "terminate",
77
+ type: "stop",
78
78
  source: { agent_id: agentId },
79
79
  payload: { agent_id: agentId, reason },
80
80
  });
@@ -742,8 +742,26 @@ describe("MAPAdapter handleStopAgent (map/agents/stop)", () => {
742
742
  ).rejects.toThrow("Failed to stop agent: Agent not found");
743
743
  });
744
744
 
745
- it("should emit agent.state.changed event via emitEvent", async () => {
746
- await setupAdapter();
745
+ it("should emit agent.state.changed event via lifecycle listener", async () => {
746
+ // Capture lifecycle callback so we can fire it from the mock terminate
747
+ let lifecycleCallback: ((event: unknown) => void) | undefined;
748
+
749
+ await setupAdapter({
750
+ onLifecycleEvent: vi.fn().mockImplementation((cb: (event: unknown) => void) => {
751
+ lifecycleCallback = cb;
752
+ return () => {};
753
+ }),
754
+ terminate: vi.fn().mockImplementation(async () => {
755
+ // Simulate what real agentManager.terminate() does: fire lifecycle
756
+ if (lifecycleCallback) {
757
+ lifecycleCallback({
758
+ type: "stopped",
759
+ agent: { id: "agent-1", name: "test-agent", role: "worker", state: "stopped" },
760
+ reason: "user stopped",
761
+ });
762
+ }
763
+ }),
764
+ } as unknown as Partial<AgentManager>);
747
765
 
748
766
  const impl = adapter as unknown as {
749
767
  handleStopAgent: (
@@ -761,10 +779,10 @@ describe("MAPAdapter handleStopAgent (map/agents/stop)", () => {
761
779
  { agentId: "agent-1" as AgentId, reason: "user stopped" },
762
780
  );
763
781
 
764
- // Verify emitEvent was called with the right event shape
782
+ // Verify emitEvent was called with the right event shape via lifecycle
765
783
  expect(emitSpy).toHaveBeenCalledWith(
766
784
  expect.objectContaining({
767
- type: "agent.state.changed",
785
+ type: "agent_state_changed",
768
786
  agentId: "agent-1",
769
787
  data: expect.objectContaining({
770
788
  agentId: "agent-1",
@@ -0,0 +1,355 @@
1
+ /**
2
+ * ACP-over-MAP _macro/getModels Tests
3
+ *
4
+ * Verifies that the _macro/getModels extension method correctly returns
5
+ * model information from the agent's session via the ACP-over-MAP handler.
6
+ */
7
+
8
+ import { describe, it, expect, afterEach, vi } from "vitest";
9
+ import { ACPOverMAPHandler } from "../acp-over-map.js";
10
+ import type { ACPEnvelope } from "../acp-over-map.js";
11
+ import { createEventStore, type EventStore } from "../../../store/event-store.js";
12
+ import type { AgentManager } from "../../../agent/agent-manager.js";
13
+ import type { TaskManager } from "../../../task/task-manager.js";
14
+ import type { Agent, Task } from "../../../store/types/index.js";
15
+ import type { AgentId } from "../../../store/types/index.js";
16
+
17
+ // ─────────────────────────────────────────────────────────────────
18
+ // Helpers
19
+ // ─────────────────────────────────────────────────────────────────
20
+
21
+ function createMockAgent(overrides: Partial<Agent> = {}): Agent {
22
+ return {
23
+ id: "agent-1" as AgentId,
24
+ session_id: "session-1",
25
+ state: "running",
26
+ task: "Test task",
27
+ task_id: "task-1",
28
+ parent: null,
29
+ lineage: [],
30
+ config: {},
31
+ cwd: "/test/cwd",
32
+ plan: [],
33
+ created_at: Date.now(),
34
+ started_at: Date.now(),
35
+ ...overrides,
36
+ };
37
+ }
38
+
39
+ function createMockTask(overrides: Partial<Task> = {}): Task {
40
+ return {
41
+ id: "task-1",
42
+ description: "Test task",
43
+ status: "in_progress",
44
+ created_by: "agent-1",
45
+ created_at: Date.now(),
46
+ ...overrides,
47
+ };
48
+ }
49
+
50
+ function createMockAgentManager(
51
+ sessionOverride?: unknown,
52
+ ): AgentManager {
53
+ const mockAgent = createMockAgent();
54
+
55
+ return {
56
+ spawn: vi.fn().mockResolvedValue({
57
+ id: "agent-new",
58
+ session_id: "session-new",
59
+ agent: createMockAgent({ id: "agent-new" as AgentId, session_id: "session-new" }),
60
+ session: {},
61
+ }),
62
+ get: vi.fn().mockReturnValue(mockAgent),
63
+ list: vi.fn().mockReturnValue([mockAgent]),
64
+ listHeadManagers: vi.fn().mockReturnValue([mockAgent]),
65
+ getChildren: vi.fn().mockReturnValue([]),
66
+ getHierarchy: vi.fn().mockReturnValue({
67
+ root: { agent: mockAgent, children: [] },
68
+ depth: 1,
69
+ totalAgents: 1,
70
+ }),
71
+ getOrCreateHeadManager: vi.fn().mockResolvedValue({
72
+ id: "agent-1",
73
+ session_id: "session-1",
74
+ agent: mockAgent,
75
+ session: {},
76
+ }),
77
+ hasActiveSession: vi.fn().mockReturnValue(true),
78
+ resume: vi.fn().mockResolvedValue({
79
+ id: "agent-1",
80
+ session_id: "session-1",
81
+ agent: mockAgent,
82
+ session: {},
83
+ }),
84
+ terminate: vi.fn().mockResolvedValue(undefined),
85
+ prompt: vi.fn().mockReturnValue({
86
+ [Symbol.asyncIterator]: async function* () {},
87
+ }),
88
+ getSession: vi.fn().mockReturnValue(sessionOverride ?? null),
89
+ onLifecycleEvent: vi.fn().mockReturnValue(() => {}),
90
+ close: vi.fn().mockResolvedValue(undefined),
91
+ respondToPermission: vi.fn().mockReturnValue(true),
92
+ cancelPermission: vi.fn().mockReturnValue(true),
93
+ } as unknown as AgentManager;
94
+ }
95
+
96
+ function createMockTaskManager(): TaskManager {
97
+ return {
98
+ get: vi.fn().mockReturnValue(createMockTask()),
99
+ list: vi.fn().mockReturnValue([createMockTask()]),
100
+ create: vi.fn().mockReturnValue(createMockTask()),
101
+ } as unknown as TaskManager;
102
+ }
103
+
104
+ /** Build an ACP envelope for processRequest */
105
+ function envelope(
106
+ streamId: string,
107
+ method: string,
108
+ params?: unknown,
109
+ sessionId?: string,
110
+ ): ACPEnvelope {
111
+ return {
112
+ acp: {
113
+ jsonrpc: "2.0",
114
+ id: `${streamId}-${method}-${Date.now()}`,
115
+ method,
116
+ params,
117
+ },
118
+ acpContext: {
119
+ streamId,
120
+ sessionId,
121
+ direction: "client-to-agent",
122
+ },
123
+ };
124
+ }
125
+
126
+ // ─────────────────────────────────────────────────────────────────
127
+ // Tests
128
+ // ─────────────────────────────────────────────────────────────────
129
+
130
+ describe("ACP-over-MAP _macro/getModels", () => {
131
+ let eventStore: EventStore;
132
+ let handler: ACPOverMAPHandler;
133
+
134
+ afterEach(async () => {
135
+ await eventStore.close();
136
+ });
137
+
138
+ async function setup(sessionOverride?: unknown) {
139
+ eventStore = await createEventStore({ inMemory: true });
140
+ const agentManager = createMockAgentManager(sessionOverride);
141
+ const taskManager = createMockTaskManager();
142
+
143
+ handler = new ACPOverMAPHandler({
144
+ agentManager,
145
+ eventStore,
146
+ taskManager,
147
+ defaultCwd: "/test/cwd",
148
+ });
149
+
150
+ return { agentManager, taskManager };
151
+ }
152
+
153
+ /** Register an agent in the EventStore so session mapper can resolve it */
154
+ function registerAgent(agentId: string, sessionId: string): void {
155
+ eventStore.emit({
156
+ type: "spawn",
157
+ source: { agent_id: agentId },
158
+ payload: {
159
+ agent_id: agentId,
160
+ session_id: sessionId,
161
+ task: "Test task",
162
+ task_id: "task-1",
163
+ cwd: "/test/cwd",
164
+ },
165
+ });
166
+ eventStore.emit({
167
+ type: "lifecycle",
168
+ source: { agent_id: agentId },
169
+ payload: {
170
+ agent_id: agentId,
171
+ action: "started",
172
+ },
173
+ });
174
+ }
175
+
176
+ /** Initialize a stream and create a session, returning the sessionId */
177
+ async function initAndCreateSession(
178
+ streamId: string,
179
+ targetAgentId: AgentId = "agent-1" as AgentId,
180
+ ): Promise<string> {
181
+ await handler.processRequest(
182
+ targetAgentId,
183
+ envelope(streamId, "initialize", {
184
+ protocolVersion: 1,
185
+ capabilities: {},
186
+ clientInfo: { name: "test", version: "1.0" },
187
+ }),
188
+ );
189
+
190
+ const sessionResult = await handler.processRequest(
191
+ targetAgentId,
192
+ envelope(streamId, "session/new", {
193
+ cwd: "/test",
194
+ mcpServers: [],
195
+ }),
196
+ );
197
+
198
+ const sessionId = (sessionResult.acp.result as { sessionId?: string })?.sessionId;
199
+ if (!sessionId) throw new Error("session/new did not return sessionId");
200
+
201
+ registerAgent(targetAgentId, sessionId);
202
+
203
+ return sessionId;
204
+ }
205
+
206
+ it("should return empty models when session is not found", async () => {
207
+ // getSession returns null — no session available
208
+ await setup(null);
209
+
210
+ const streamId = "test-stream-1";
211
+ const agentId = "agent-1" as AgentId;
212
+ const sessionId = await initAndCreateSession(streamId, agentId);
213
+
214
+ const result = await handler.processRequest(
215
+ agentId,
216
+ envelope(streamId, "_macro/getModels", { sessionId }),
217
+ );
218
+
219
+ expect(result.acp.error).toBeUndefined();
220
+ const data = result.acp.result as {
221
+ currentModelId: string | null;
222
+ availableModels: Array<{ modelId: string; name: string }>;
223
+ };
224
+ expect(data.currentModelId).toBeNull();
225
+ expect(data.availableModels).toEqual([]);
226
+ });
227
+
228
+ it("should return models from session.models when available", async () => {
229
+ // Mock session with models array (like Claude Code returns)
230
+ const mockSession = {
231
+ id: "acp-session-123",
232
+ models: ["default", "sonnet"],
233
+ };
234
+ await setup(mockSession);
235
+
236
+ const streamId = "test-stream-2";
237
+ const agentId = "agent-1" as AgentId;
238
+ const sessionId = await initAndCreateSession(streamId, agentId);
239
+
240
+ const result = await handler.processRequest(
241
+ agentId,
242
+ envelope(streamId, "_macro/getModels", { sessionId }),
243
+ );
244
+
245
+ expect(result.acp.error).toBeUndefined();
246
+ const data = result.acp.result as {
247
+ currentModelId: string | null;
248
+ availableModels: Array<{ modelId: string; name: string }>;
249
+ };
250
+ expect(data.currentModelId).toBe("default");
251
+ expect(data.availableModels).toEqual([
252
+ { modelId: "default", name: "default" },
253
+ { modelId: "sonnet", name: "sonnet" },
254
+ ]);
255
+ });
256
+
257
+ it("should return models from clientHandler when available", async () => {
258
+ // Mock session with clientHandler.getSessionModelInfo
259
+ const mockSession = {
260
+ id: "acp-session-456",
261
+ models: ["fallback-model"],
262
+ clientHandler: {
263
+ getSessionModelInfo: (_id: string) => ({
264
+ currentModelId: "opus",
265
+ availableModels: [
266
+ { modelId: "opus", name: "Claude Opus 4" },
267
+ { modelId: "sonnet", name: "Claude Sonnet 4.5" },
268
+ ],
269
+ }),
270
+ },
271
+ };
272
+ await setup(mockSession);
273
+
274
+ const streamId = "test-stream-3";
275
+ const agentId = "agent-1" as AgentId;
276
+ const sessionId = await initAndCreateSession(streamId, agentId);
277
+
278
+ const result = await handler.processRequest(
279
+ agentId,
280
+ envelope(streamId, "_macro/getModels", { sessionId }),
281
+ );
282
+
283
+ expect(result.acp.error).toBeUndefined();
284
+ const data = result.acp.result as {
285
+ currentModelId: string | null;
286
+ availableModels: Array<{ modelId: string; name: string }>;
287
+ };
288
+ // clientHandler should take priority over session.models
289
+ expect(data.currentModelId).toBe("opus");
290
+ expect(data.availableModels).toEqual([
291
+ { modelId: "opus", name: "Claude Opus 4" },
292
+ { modelId: "sonnet", name: "Claude Sonnet 4.5" },
293
+ ]);
294
+ });
295
+
296
+ it("should fall back to session.models when clientHandler returns empty", async () => {
297
+ // clientHandler returns empty, should fall back to session.models
298
+ const mockSession = {
299
+ id: "acp-session-789",
300
+ models: ["haiku"],
301
+ clientHandler: {
302
+ getSessionModelInfo: () => ({
303
+ currentModelId: null,
304
+ availableModels: [],
305
+ }),
306
+ },
307
+ };
308
+ await setup(mockSession);
309
+
310
+ const streamId = "test-stream-4";
311
+ const agentId = "agent-1" as AgentId;
312
+ const sessionId = await initAndCreateSession(streamId, agentId);
313
+
314
+ const result = await handler.processRequest(
315
+ agentId,
316
+ envelope(streamId, "_macro/getModels", { sessionId }),
317
+ );
318
+
319
+ expect(result.acp.error).toBeUndefined();
320
+ const data = result.acp.result as {
321
+ currentModelId: string | null;
322
+ availableModels: Array<{ modelId: string; name: string }>;
323
+ };
324
+ expect(data.currentModelId).toBe("haiku");
325
+ expect(data.availableModels).toEqual([
326
+ { modelId: "haiku", name: "haiku" },
327
+ ]);
328
+ });
329
+
330
+ it("should return empty when session has no models and no clientHandler", async () => {
331
+ // Session exists but has no models
332
+ const mockSession = {
333
+ id: "acp-session-empty",
334
+ models: [],
335
+ };
336
+ await setup(mockSession);
337
+
338
+ const streamId = "test-stream-5";
339
+ const agentId = "agent-1" as AgentId;
340
+ const sessionId = await initAndCreateSession(streamId, agentId);
341
+
342
+ const result = await handler.processRequest(
343
+ agentId,
344
+ envelope(streamId, "_macro/getModels", { sessionId }),
345
+ );
346
+
347
+ expect(result.acp.error).toBeUndefined();
348
+ const data = result.acp.result as {
349
+ currentModelId: string | null;
350
+ availableModels: Array<{ modelId: string; name: string }>;
351
+ };
352
+ expect(data.currentModelId).toBeNull();
353
+ expect(data.availableModels).toEqual([]);
354
+ });
355
+ });