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
@@ -380,4 +380,6 @@ export type AgentManagerErrorCode =
380
380
  | "NOT_RUNNING"
381
381
  | "INVALID_STATE"
382
382
  | "PERMISSION_DENIED"
383
- | "CAPABILITY_DENIED";
383
+ | "CAPABILITY_DENIED"
384
+ | "SHUTDOWN_IN_PROGRESS"
385
+ | "FORK_NOT_SUPPORTED";
package/src/api/server.ts CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  type InjectionDeps,
57
57
  } from "../steering/index.js";
58
58
  import type { AgentId } from "../store/types/index.js";
59
+ import { secureCompare } from "../auth/token.js";
59
60
 
60
61
  // ─────────────────────────────────────────────────────────────────
61
62
  // Server Configuration
@@ -73,6 +74,9 @@ export interface APIServerConfig {
73
74
 
74
75
  /** Grace period in milliseconds for in-flight work during shutdown (default: 5000) */
75
76
  shutdownGracePeriodMs?: number;
77
+
78
+ /** Server token for Bearer auth on API routes. When set, all routes except /health require auth. */
79
+ serverToken?: string;
76
80
  }
77
81
 
78
82
  export interface APIServices {
@@ -351,7 +355,7 @@ export function createAPIServer(
351
355
  services: APIServices,
352
356
  config: APIServerConfig = {}
353
357
  ): APIServer {
354
- const { port = 3000, host = "localhost", cors = true, shutdownGracePeriodMs = 5000 } = config;
358
+ const { port = 3000, host = "localhost", cors = true, shutdownGracePeriodMs = 5000, serverToken } = config;
355
359
  const { eventStore, agentManager, taskManager } = services;
356
360
 
357
361
  // Server state
@@ -374,12 +378,27 @@ export function createAPIServer(
374
378
  if (cors) {
375
379
  app.use((_req: Request, res: Response, next: NextFunction) => {
376
380
  res.header("Access-Control-Allow-Origin", "*");
377
- res.header("Access-Control-Allow-Headers", "Content-Type");
381
+ res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
378
382
  res.header("Access-Control-Allow-Methods", "GET, POST, DELETE");
379
383
  next();
380
384
  });
381
385
  }
382
386
 
387
+ // Bearer token auth middleware (skip /health)
388
+ if (serverToken) {
389
+ app.use((req: Request, res: Response, next: NextFunction) => {
390
+ if (req.path === "/health") return next();
391
+ const authHeader = req.headers.authorization;
392
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : undefined;
393
+ if (!token || !secureCompare(token, serverToken)) {
394
+ const error: APIError = { error: "Unauthorized", code: "AUTH_REQUIRED" };
395
+ res.status(401).json(error);
396
+ return;
397
+ }
398
+ next();
399
+ });
400
+ }
401
+
383
402
  // ─────────────────────────────────────────────────────────────────
384
403
  // Helper Functions
385
404
  // ─────────────────────────────────────────────────────────────────
@@ -438,7 +457,7 @@ export function createAPIServer(
438
457
  case "spawn":
439
458
  summary = `Agent ${event.payload.agent_id} spawned`;
440
459
  break;
441
- case "terminate":
460
+ case "stop":
442
461
  summary = `Agent terminated: ${event.payload.reason}`;
443
462
  break;
444
463
  case "status":
@@ -1241,9 +1260,9 @@ export function createAPIServer(
1241
1260
  */
1242
1261
  export function createAPIApp(
1243
1262
  services: Pick<APIServices, "eventStore" | "agentManager" | "taskManager" | "messageRouter"> & Pick<Partial<APIServices>, "mailService" | "conversationMap">,
1244
- config: { cors?: boolean } = {}
1263
+ config: { cors?: boolean; serverToken?: string } = {}
1245
1264
  ): Express {
1246
- const { cors = true } = config;
1265
+ const { cors = true, serverToken } = config;
1247
1266
  const { agentManager, taskManager, messageRouter } = services;
1248
1267
 
1249
1268
  // Create shared state
@@ -1262,12 +1281,27 @@ export function createAPIApp(
1262
1281
  if (cors) {
1263
1282
  app.use((_req: Request, res: Response, next: NextFunction) => {
1264
1283
  res.header("Access-Control-Allow-Origin", "*");
1265
- res.header("Access-Control-Allow-Headers", "Content-Type");
1284
+ res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
1266
1285
  res.header("Access-Control-Allow-Methods", "GET, POST, DELETE");
1267
1286
  next();
1268
1287
  });
1269
1288
  }
1270
1289
 
1290
+ // Bearer token auth middleware (skip /health)
1291
+ if (serverToken) {
1292
+ app.use((req: Request, res: Response, next: NextFunction) => {
1293
+ if (req.path === "/health") return next();
1294
+ const authHeader = req.headers.authorization;
1295
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : undefined;
1296
+ if (!token || !secureCompare(token, serverToken)) {
1297
+ const error: APIError = { error: "Unauthorized", code: "AUTH_REQUIRED" };
1298
+ res.status(401).json(error);
1299
+ return;
1300
+ }
1301
+ next();
1302
+ });
1303
+ }
1304
+
1271
1305
  // ─────────────────────────────────────────────────────────────────
1272
1306
  // Helper Functions
1273
1307
  // ─────────────────────────────────────────────────────────────────
@@ -1326,7 +1360,7 @@ export function createAPIApp(
1326
1360
  case "spawn":
1327
1361
  summary = `Agent ${event.payload.agent_id} spawned`;
1328
1362
  break;
1329
- case "terminate":
1363
+ case "stop":
1330
1364
  summary = `Agent terminated: ${event.payload.reason}`;
1331
1365
  break;
1332
1366
  case "status":
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateToken, secureCompare, AgentTokenManager } from "../token.js";
3
+
4
+ describe("generateToken", () => {
5
+ it("generates a hex string of expected length", () => {
6
+ const token = generateToken();
7
+ // 32 bytes = 64 hex chars
8
+ expect(token).toHaveLength(64);
9
+ expect(token).toMatch(/^[0-9a-f]+$/);
10
+ });
11
+
12
+ it("generates different tokens each time", () => {
13
+ const a = generateToken();
14
+ const b = generateToken();
15
+ expect(a).not.toBe(b);
16
+ });
17
+
18
+ it("respects custom byte length", () => {
19
+ const token = generateToken(16);
20
+ expect(token).toHaveLength(32); // 16 bytes = 32 hex chars
21
+ });
22
+ });
23
+
24
+ describe("secureCompare", () => {
25
+ it("returns true for identical strings", () => {
26
+ expect(secureCompare("abc123", "abc123")).toBe(true);
27
+ });
28
+
29
+ it("returns false for different strings of same length", () => {
30
+ expect(secureCompare("abc123", "xyz789")).toBe(false);
31
+ });
32
+
33
+ it("returns false for different length strings", () => {
34
+ expect(secureCompare("short", "longer-string")).toBe(false);
35
+ });
36
+
37
+ it("returns true for empty strings", () => {
38
+ expect(secureCompare("", "")).toBe(true);
39
+ });
40
+ });
41
+
42
+ describe("AgentTokenManager", () => {
43
+ it("creates and verifies a token", () => {
44
+ const mgr = new AgentTokenManager();
45
+ const token = mgr.createToken("agent-1");
46
+ expect(token).toHaveLength(64);
47
+ expect(mgr.verifyToken("agent-1", token)).toBe(true);
48
+ });
49
+
50
+ it("rejects wrong token", () => {
51
+ const mgr = new AgentTokenManager();
52
+ mgr.createToken("agent-1");
53
+ expect(mgr.verifyToken("agent-1", "wrong-token")).toBe(false);
54
+ });
55
+
56
+ it("rejects unknown agent", () => {
57
+ const mgr = new AgentTokenManager();
58
+ expect(mgr.verifyToken("unknown", "any-token")).toBe(false);
59
+ });
60
+
61
+ it("revokes a token", () => {
62
+ const mgr = new AgentTokenManager();
63
+ const token = mgr.createToken("agent-1");
64
+ expect(mgr.revokeToken("agent-1")).toBe(true);
65
+ expect(mgr.verifyToken("agent-1", token)).toBe(false);
66
+ expect(mgr.hasToken("agent-1")).toBe(false);
67
+ });
68
+
69
+ it("revoke returns false for unknown agent", () => {
70
+ const mgr = new AgentTokenManager();
71
+ expect(mgr.revokeToken("unknown")).toBe(false);
72
+ });
73
+
74
+ it("hasToken returns correct state", () => {
75
+ const mgr = new AgentTokenManager();
76
+ expect(mgr.hasToken("agent-1")).toBe(false);
77
+ mgr.createToken("agent-1");
78
+ expect(mgr.hasToken("agent-1")).toBe(true);
79
+ });
80
+
81
+ it("createToken overwrites previous token", () => {
82
+ const mgr = new AgentTokenManager();
83
+ const token1 = mgr.createToken("agent-1");
84
+ const token2 = mgr.createToken("agent-1");
85
+ expect(token1).not.toBe(token2);
86
+ expect(mgr.verifyToken("agent-1", token1)).toBe(false);
87
+ expect(mgr.verifyToken("agent-1", token2)).toBe(true);
88
+ });
89
+
90
+ it("manages multiple agents independently", () => {
91
+ const mgr = new AgentTokenManager();
92
+ const t1 = mgr.createToken("agent-1");
93
+ const t2 = mgr.createToken("agent-2");
94
+ expect(mgr.verifyToken("agent-1", t1)).toBe(true);
95
+ expect(mgr.verifyToken("agent-2", t2)).toBe(true);
96
+ // Cross-validation fails
97
+ expect(mgr.verifyToken("agent-1", t2)).toBe(false);
98
+ expect(mgr.verifyToken("agent-2", t1)).toBe(false);
99
+ });
100
+ });
@@ -0,0 +1 @@
1
+ export { generateToken, secureCompare, AgentTokenManager } from "./token.js";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Authentication Token Utilities
3
+ *
4
+ * Provides token generation, secure comparison, and per-agent
5
+ * token management for the macro-agent server.
6
+ */
7
+
8
+ import * as crypto from "crypto";
9
+
10
+ // =============================================================================
11
+ // Token Generation
12
+ // =============================================================================
13
+
14
+ /**
15
+ * Generate a cryptographically random hex token.
16
+ */
17
+ export function generateToken(bytes = 32): string {
18
+ return crypto.randomBytes(bytes).toString("hex");
19
+ }
20
+
21
+ // =============================================================================
22
+ // Secure Comparison
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Constant-time string comparison to prevent timing attacks.
27
+ */
28
+ export function secureCompare(a: string, b: string): boolean {
29
+ if (a.length !== b.length) {
30
+ return false;
31
+ }
32
+ const bufA = Buffer.from(a);
33
+ const bufB = Buffer.from(b);
34
+ return crypto.timingSafeEqual(bufA, bufB);
35
+ }
36
+
37
+ // =============================================================================
38
+ // Agent Token Manager
39
+ // =============================================================================
40
+
41
+ /**
42
+ * Manages per-agent authentication tokens.
43
+ *
44
+ * Each spawned agent gets a unique token at spawn time. The token is
45
+ * passed to the subprocess via environment variable and validated on
46
+ * every MCP bridge RPC call.
47
+ */
48
+ export class AgentTokenManager {
49
+ private tokens = new Map<string, string>();
50
+
51
+ /**
52
+ * Create and store a token for an agent. Returns the generated token.
53
+ */
54
+ createToken(agentId: string): string {
55
+ const token = generateToken();
56
+ this.tokens.set(agentId, token);
57
+ return token;
58
+ }
59
+
60
+ /**
61
+ * Verify that a token matches the one stored for an agent.
62
+ */
63
+ verifyToken(agentId: string, token: string): boolean {
64
+ const stored = this.tokens.get(agentId);
65
+ if (!stored) return false;
66
+ return secureCompare(stored, token);
67
+ }
68
+
69
+ /**
70
+ * Revoke an agent's token (e.g., on terminate).
71
+ */
72
+ revokeToken(agentId: string): boolean {
73
+ return this.tokens.delete(agentId);
74
+ }
75
+
76
+ /**
77
+ * Check if an agent has a registered token.
78
+ */
79
+ hasToken(agentId: string): boolean {
80
+ return this.tokens.has(agentId);
81
+ }
82
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect } from "vitest";
9
- import { parseArgs, type ACPServerOptions } from "../acp.js";
9
+ import { parseArgs, type ACPServerOptions } from "../parse-args.js";
10
10
 
11
11
  // ─────────────────────────────────────────────────────────────────
12
12
  // parseArgs Tests
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { describe, it, expect } from "vitest";
9
9
  import { resolve } from "node:path";
10
- import { getStableInstanceId } from "../acp.js";
10
+ import { getStableInstanceId } from "../stable-instance-id.js";
11
11
 
12
12
  describe("getStableInstanceId", () => {
13
13
  it("should return the same ID for the same path", () => {
package/src/cli/acp.ts CHANGED
@@ -38,11 +38,7 @@
38
38
  * });
39
39
  */
40
40
 
41
- import { readFileSync } from "node:fs";
42
- import { createHash } from "node:crypto";
43
- import { dirname, join, resolve } from "node:path";
44
41
  import { Readable } from "node:stream";
45
- import { fileURLToPath } from "node:url";
46
42
  import {
47
43
  AgentSideConnection,
48
44
  ndJsonStream,
@@ -73,63 +69,13 @@ import type { AgentId, EventId } from "../store/types/index.js";
73
69
  // Configuration
74
70
  // ─────────────────────────────────────────────────────────────────
75
71
 
76
- export interface ACPServerOptions {
77
- /** Working directory for agents */
78
- cwd?: string;
79
- /** Stdio ACP-only mode (for embedded use with acp-factory) */
80
- acp?: boolean;
81
- /** Port for server (default: 3001) */
82
- port?: number;
83
- /** Host for server (default: localhost) */
84
- host?: string;
85
- /** Instance ID to reuse an existing event store (omit for new instance) */
86
- instanceId?: string;
87
- }
88
-
89
- /**
90
- * Parse command line arguments.
91
- * @param argv Optional array of arguments (defaults to process.argv.slice(2))
92
- */
93
- export function parseArgs(argv?: string[]): ACPServerOptions {
94
- const args = argv ?? process.argv.slice(2);
95
- const options: ACPServerOptions = {};
96
-
97
- for (let i = 0; i < args.length; i++) {
98
- if (args[i] === "--version" || args[i] === "-v") {
99
- const __dirname = dirname(fileURLToPath(import.meta.url));
100
- const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8"));
101
- console.log(pkg.version);
102
- process.exit(0);
103
- } else if (args[i] === "--cwd" && args[i + 1]) {
104
- options.cwd = args[i + 1];
105
- i++;
106
- } else if (args[i] === "--acp") {
107
- options.acp = true;
108
- } else if (args[i] === "--port" && args[i + 1]) {
109
- options.port = parseInt(args[i + 1], 10);
110
- i++;
111
- } else if (args[i] === "--host" && args[i + 1]) {
112
- options.host = args[i + 1];
113
- i++;
114
- } else if (args[i] === "--instance-id" && args[i + 1]) {
115
- options.instanceId = args[i + 1];
116
- i++;
117
- }
118
- }
119
-
120
- return options;
121
- }
72
+ import { parseArgs } from "./parse-args.js";
73
+ import { getStableInstanceId } from "./stable-instance-id.js";
74
+ import { loadMergedConfig } from "../config/project-config.js";
122
75
 
123
- /**
124
- * Generate a stable instance ID from the working directory.
125
- * This ensures the same project always uses the same EventStore,
126
- * so agents and sessions persist across server restarts.
127
- */
128
- export function getStableInstanceId(cwd: string): string {
129
- const normalizedPath = resolve(cwd);
130
- const hash = createHash("sha256").update(normalizedPath).digest("hex").slice(0, 12);
131
- return `inst_${hash}`;
132
- }
76
+ // Re-export utilities for backwards compatibility
77
+ export { parseArgs, type ACPServerOptions } from "./parse-args.js";
78
+ export { getStableInstanceId } from "./stable-instance-id.js";
133
79
 
134
80
  // ─────────────────────────────────────────────────────────────────
135
81
  // Stream Setup
@@ -272,10 +218,92 @@ async function main() {
272
218
  wakeHandler: routerWakeHandler,
273
219
  });
274
220
 
221
+ // Load merged config (global → project → env vars)
222
+ const mergedConfig = loadMergedConfig(defaultCwd);
223
+
224
+ // Compute server URL for thin-client MCP mode (only in server mode, not stdio ACP)
225
+ const serverUrl = options.acp
226
+ ? undefined
227
+ : `http://${options.host ?? mergedConfig.host ?? "localhost"}:${options.port ?? mergedConfig.port ?? 3001}`;
228
+
229
+ // Set up authentication tokens from merged config
230
+ const noAuth = options.noAuth || mergedConfig.auth?.disabled === true;
231
+ let serverToken: string | undefined;
232
+ let agentTokenManager: import("../auth/token.js").AgentTokenManager | undefined;
233
+
234
+ if (!noAuth && mergedConfig.auth?.secret) {
235
+ const { AgentTokenManager } = await import("../auth/token.js");
236
+ serverToken = mergedConfig.auth.secret;
237
+ agentTokenManager = new AgentTokenManager();
238
+ }
239
+
275
240
  // Now create the agentManager with the real router
276
- agentManager = createAgentManager(eventStore, messageRouter);
241
+ agentManager = createAgentManager(eventStore, messageRouter, {
242
+ serverUrl,
243
+ serverToken: serverUrl ? serverToken : undefined,
244
+ agentTokenManager: serverUrl ? agentTokenManager : undefined,
245
+ taskBackend: mergedConfig.task?.backend,
246
+ openTasksSocketPath: mergedConfig.task?.opentasks?.socket_path,
247
+ });
277
248
  const taskManager = createTaskManager(eventStore);
278
249
 
250
+ // Create task backend for dynamic task tools (create_task, get_task, etc.)
251
+ const { createTaskBackend, loadTaskConfigFromMerged } = await import("../task/backend/index.js");
252
+ const { UnifiedTaskToolProvider } = await import("../task/backend/unified-tool-provider.js");
253
+
254
+ let taskBackend: import("../task/backend/types.js").TaskBackend | undefined;
255
+ let taskToolProvider: import("../task/backend/types.js").TaskToolProvider | undefined;
256
+ let taskBackendShutdown: (() => Promise<void>) | undefined;
257
+
258
+ // Mutable context holder for the task tool provider. Bridge handlers set
259
+ // this before each tool call so the provider uses the calling agent's ID.
260
+ // Safe because Node.js is single-threaded.
261
+ const taskToolContext = { agent_id: "" as string };
262
+
263
+ // Connect-on-spawn callback: auto-connect project .opentasks/ dirs to central daemon
264
+ let connectProject: ((projectPath: string) => Promise<void>) | undefined;
265
+ let getConnectedProjects: (() => string[]) | undefined;
266
+
267
+ try {
268
+ const taskConfig = loadTaskConfigFromMerged(mergedConfig);
269
+ const result = await createTaskBackend(taskConfig, eventStore);
270
+ taskBackend = result.backend;
271
+ taskBackendShutdown = result.shutdown;
272
+ connectProject = result.connectProject;
273
+ getConnectedProjects = result.getConnectedProjects;
274
+
275
+ taskToolProvider = new UnifiedTaskToolProvider(
276
+ taskBackend,
277
+ () => taskToolContext,
278
+ result.openTasksClient
279
+ );
280
+ console.error(`[acp] Task backend created: ${taskConfig.backend.type}`);
281
+
282
+ // Propagate runtime socket path to child agents so they skip daemon discovery
283
+ if (result.socketPath) {
284
+ agentManager.setOpenTasksSocketPath(result.socketPath);
285
+ }
286
+
287
+ // Auto-connect the server's own project directory on startup
288
+ if (connectProject) {
289
+ connectProject(defaultCwd).catch(() => {});
290
+ }
291
+ } catch (err) {
292
+ // Fall back to in-memory backend if opentasks connection fails
293
+ console.error(`[acp] Failed to create task backend (${err}), falling back to memory`);
294
+ try {
295
+ const fallbackResult = await createTaskBackend({ backend: { type: "memory" } }, eventStore);
296
+ taskBackend = fallbackResult.backend;
297
+ taskToolProvider = new UnifiedTaskToolProvider(
298
+ taskBackend,
299
+ () => taskToolContext,
300
+ );
301
+ console.error(`[acp] Task backend created: memory (fallback)`);
302
+ } catch (fallbackErr) {
303
+ console.error(`[acp] Memory fallback also failed: ${fallbackErr}. Task tools will be unavailable.`);
304
+ }
305
+ }
306
+
279
307
  // Create ActivityWatcher for event-driven agent waking
280
308
  const sessionProvider = createSessionProviderFromAgentManager(agentManager);
281
309
  const wakeHandler = createWakeHandler(sessionProvider, agentManager);
@@ -332,9 +360,11 @@ async function main() {
332
360
  activityWatcher.start();
333
361
 
334
362
  // Auto-subscribe Monitor agents to health events when they spawn
363
+ // and auto-connect project .opentasks/ directories to central daemon
335
364
  agentManager.onLifecycleEvent((event) => {
336
365
  if (event.type === "spawned") {
337
366
  const agent = event.agent;
367
+
338
368
  // Check if this is a Monitor agent
339
369
  if (agent.role === "monitor" || agent.role?.startsWith("monitor.")) {
340
370
  subscribeAgentToEvents(
@@ -346,23 +376,44 @@ async function main() {
346
376
  );
347
377
  console.error(`[acp] Auto-subscribed Monitor ${agent.id} to health events`);
348
378
  }
379
+
380
+ // Connect-on-spawn: auto-connect project .opentasks/ to central daemon
381
+ if (connectProject && agent.cwd) {
382
+ connectProject(agent.cwd).catch(() => {
383
+ // Non-fatal — logged inside connectProject
384
+ });
385
+ }
349
386
  }
350
387
  });
351
388
 
352
389
  // Combined server (when --ws is enabled)
353
390
  let combinedServer: CombinedServer | undefined;
354
391
 
355
- // Cleanup function
392
+ // Cleanup function — best-effort, each step isolated
356
393
  const cleanup = async () => {
357
- // Stop ActivityWatcher
358
- activityWatcher.stop();
394
+ try { activityWatcher.stop(); } catch (err) {
395
+ console.error(`[cleanup] ActivityWatcher stop failed: ${err}`);
396
+ }
359
397
 
360
- // Stop combined server if running
361
398
  if (combinedServer) {
362
- await combinedServer.stop();
399
+ try { await combinedServer.stop(); } catch (err) {
400
+ console.error(`[cleanup] Combined server stop failed: ${err}`);
401
+ }
402
+ }
403
+
404
+ try { await agentManager.close(); } catch (err) {
405
+ console.error(`[cleanup] AgentManager close failed: ${err}`);
406
+ }
407
+
408
+ if (taskBackendShutdown) {
409
+ try { await taskBackendShutdown(); } catch (err) {
410
+ console.error(`[cleanup] Task backend shutdown failed: ${err}`);
411
+ }
412
+ }
413
+
414
+ try { await eventStore.close(); } catch (err) {
415
+ console.error(`[cleanup] EventStore close failed: ${err}`);
363
416
  }
364
- await agentManager.close();
365
- await eventStore.close();
366
417
  };
367
418
 
368
419
  try {
@@ -401,15 +452,22 @@ async function main() {
401
452
  await cleanup();
402
453
  } else {
403
454
  // Full server mode (default): WebSocket ACP + MAP + REST API
404
- const host = options.host ?? "localhost";
405
- const port = options.port ?? 3001;
455
+ const host = options.host ?? mergedConfig.host ?? "localhost";
456
+ const port = options.port ?? mergedConfig.port ?? 3001;
406
457
 
407
458
  combinedServer = createCombinedServer(
408
- { eventStore, agentManager, taskManager, messageRouter, activityWatcher },
409
- { port, host, defaultCwd }
459
+ { eventStore, agentManager, taskManager, messageRouter, activityWatcher, taskBackend, taskToolProvider, taskToolContext, agentTokenManager, getConnectedProjects },
460
+ { port, host, defaultCwd, serverToken, noAuth }
410
461
  );
411
462
 
412
463
  await combinedServer.start();
464
+ if (serverToken) {
465
+ console.error(`[acp] Server token: ${serverToken.substring(0, 8)}...`);
466
+ } else if (noAuth) {
467
+ console.error(`[acp] Auth: explicitly disabled`);
468
+ } else {
469
+ console.error(`[acp] Auth: off (set auth.secret in config or MACRO_SERVER_SECRET to enable)`);
470
+ }
413
471
 
414
472
  // Keep process alive - will exit via SIGINT/SIGTERM handlers
415
473
  await new Promise<void>(() => {