macro-agent 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/.macro-agent/teams/self-driving/prompts/grinder.md +27 -0
  2. package/.macro-agent/teams/self-driving/prompts/judge.md +27 -0
  3. package/.macro-agent/teams/self-driving/prompts/planner.md +33 -0
  4. package/.macro-agent/teams/self-driving/roles/grinder.yaml +17 -0
  5. package/.macro-agent/teams/self-driving/roles/judge.yaml +24 -0
  6. package/.macro-agent/teams/self-driving/roles/planner.yaml +18 -0
  7. package/.macro-agent/teams/self-driving/team.yaml +103 -0
  8. package/.macro-agent/teams/structured/prompts/developer.md +26 -0
  9. package/.macro-agent/teams/structured/prompts/lead.md +25 -0
  10. package/.macro-agent/teams/structured/prompts/reviewer.md +24 -0
  11. package/.macro-agent/teams/structured/roles/developer.yaml +12 -0
  12. package/.macro-agent/teams/structured/roles/lead.yaml +11 -0
  13. package/.macro-agent/teams/structured/roles/reviewer.yaml +19 -0
  14. package/.macro-agent/teams/structured/team.yaml +89 -0
  15. package/.sudocode/issues.jsonl +56 -51
  16. package/.sudocode/specs.jsonl +8 -1
  17. package/CLAUDE.md +121 -30
  18. package/README.md +60 -3
  19. package/dist/acp/macro-agent.d.ts +4 -0
  20. package/dist/acp/macro-agent.d.ts.map +1 -1
  21. package/dist/acp/macro-agent.js +50 -4
  22. package/dist/acp/macro-agent.js.map +1 -1
  23. package/dist/acp/session-mapper.d.ts +20 -1
  24. package/dist/acp/session-mapper.d.ts.map +1 -1
  25. package/dist/acp/session-mapper.js +90 -1
  26. package/dist/acp/session-mapper.js.map +1 -1
  27. package/dist/acp/types.d.ts +24 -1
  28. package/dist/acp/types.d.ts.map +1 -1
  29. package/dist/acp/types.js.map +1 -1
  30. package/dist/agent/agent-manager.d.ts +40 -1
  31. package/dist/agent/agent-manager.d.ts.map +1 -1
  32. package/dist/agent/agent-manager.js +172 -8
  33. package/dist/agent/agent-manager.js.map +1 -1
  34. package/dist/agent/types.d.ts +22 -0
  35. package/dist/agent/types.d.ts.map +1 -1
  36. package/dist/agent/types.js.map +1 -1
  37. package/dist/agent/wake.d.ts +15 -0
  38. package/dist/agent/wake.d.ts.map +1 -1
  39. package/dist/agent/wake.js +15 -0
  40. package/dist/agent/wake.js.map +1 -1
  41. package/dist/agent-detection/command-builder.d.ts +30 -0
  42. package/dist/agent-detection/command-builder.d.ts.map +1 -0
  43. package/dist/agent-detection/command-builder.js +71 -0
  44. package/dist/agent-detection/command-builder.js.map +1 -0
  45. package/dist/agent-detection/detector.d.ts +84 -0
  46. package/dist/agent-detection/detector.d.ts.map +1 -0
  47. package/dist/agent-detection/detector.js +240 -0
  48. package/dist/agent-detection/detector.js.map +1 -0
  49. package/dist/agent-detection/index.d.ts +12 -0
  50. package/dist/agent-detection/index.d.ts.map +1 -0
  51. package/dist/agent-detection/index.js +14 -0
  52. package/dist/agent-detection/index.js.map +1 -0
  53. package/dist/agent-detection/registry.d.ts +53 -0
  54. package/dist/agent-detection/registry.d.ts.map +1 -0
  55. package/dist/agent-detection/registry.js +177 -0
  56. package/dist/agent-detection/registry.js.map +1 -0
  57. package/dist/agent-detection/types.d.ts +121 -0
  58. package/dist/agent-detection/types.d.ts.map +1 -0
  59. package/dist/agent-detection/types.js +20 -0
  60. package/dist/agent-detection/types.js.map +1 -0
  61. package/dist/api/server.d.ts +5 -1
  62. package/dist/api/server.d.ts.map +1 -1
  63. package/dist/api/server.js +362 -0
  64. package/dist/api/server.js.map +1 -1
  65. package/dist/api/types.d.ts +50 -1
  66. package/dist/api/types.d.ts.map +1 -1
  67. package/dist/cli/acp.d.ts +2 -0
  68. package/dist/cli/acp.d.ts.map +1 -1
  69. package/dist/cli/acp.js +8 -1
  70. package/dist/cli/acp.js.map +1 -1
  71. package/dist/cli/index.js +29 -0
  72. package/dist/cli/index.js.map +1 -1
  73. package/dist/cli/mcp.js +38 -0
  74. package/dist/cli/mcp.js.map +1 -1
  75. package/dist/config/index.d.ts +2 -0
  76. package/dist/config/index.d.ts.map +1 -0
  77. package/dist/config/index.js +2 -0
  78. package/dist/config/index.js.map +1 -0
  79. package/dist/config/project-config.d.ts +46 -0
  80. package/dist/config/project-config.d.ts.map +1 -0
  81. package/dist/config/project-config.js +68 -0
  82. package/dist/config/project-config.js.map +1 -0
  83. package/dist/lifecycle/cascade.d.ts +1 -1
  84. package/dist/lifecycle/cascade.d.ts.map +1 -1
  85. package/dist/lifecycle/handlers/index.d.ts +4 -0
  86. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  87. package/dist/lifecycle/handlers/index.js +2 -0
  88. package/dist/lifecycle/handlers/index.js.map +1 -1
  89. package/dist/lifecycle/handlers/worker.d.ts +4 -0
  90. package/dist/lifecycle/handlers/worker.d.ts.map +1 -1
  91. package/dist/lifecycle/handlers/worker.js +35 -3
  92. package/dist/lifecycle/handlers/worker.js.map +1 -1
  93. package/dist/mail/conversation-map.d.ts +33 -0
  94. package/dist/mail/conversation-map.d.ts.map +1 -0
  95. package/dist/mail/conversation-map.js +61 -0
  96. package/dist/mail/conversation-map.js.map +1 -0
  97. package/dist/mail/index.d.ts +11 -0
  98. package/dist/mail/index.d.ts.map +1 -0
  99. package/dist/mail/index.js +11 -0
  100. package/dist/mail/index.js.map +1 -0
  101. package/dist/mail/mail-service.d.ts +85 -0
  102. package/dist/mail/mail-service.d.ts.map +1 -0
  103. package/dist/mail/mail-service.js +121 -0
  104. package/dist/mail/mail-service.js.map +1 -0
  105. package/dist/mail/stores/eventstore-conversation-store.d.ts +40 -0
  106. package/dist/mail/stores/eventstore-conversation-store.d.ts.map +1 -0
  107. package/dist/mail/stores/eventstore-conversation-store.js +131 -0
  108. package/dist/mail/stores/eventstore-conversation-store.js.map +1 -0
  109. package/dist/mail/stores/eventstore-participant-store.d.ts +43 -0
  110. package/dist/mail/stores/eventstore-participant-store.d.ts.map +1 -0
  111. package/dist/mail/stores/eventstore-participant-store.js +145 -0
  112. package/dist/mail/stores/eventstore-participant-store.js.map +1 -0
  113. package/dist/mail/stores/eventstore-thread-store.d.ts +46 -0
  114. package/dist/mail/stores/eventstore-thread-store.d.ts.map +1 -0
  115. package/dist/mail/stores/eventstore-thread-store.js +118 -0
  116. package/dist/mail/stores/eventstore-thread-store.js.map +1 -0
  117. package/dist/mail/stores/eventstore-turn-store.d.ts +47 -0
  118. package/dist/mail/stores/eventstore-turn-store.d.ts.map +1 -0
  119. package/dist/mail/stores/eventstore-turn-store.js +153 -0
  120. package/dist/mail/stores/eventstore-turn-store.js.map +1 -0
  121. package/dist/mail/stores/index.d.ts +12 -0
  122. package/dist/mail/stores/index.d.ts.map +1 -0
  123. package/dist/mail/stores/index.js +12 -0
  124. package/dist/mail/stores/index.js.map +1 -0
  125. package/dist/mail/stores/types.d.ts +146 -0
  126. package/dist/mail/stores/types.d.ts.map +1 -0
  127. package/dist/mail/stores/types.js +13 -0
  128. package/dist/mail/stores/types.js.map +1 -0
  129. package/dist/mail/turn-recorder.d.ts +30 -0
  130. package/dist/mail/turn-recorder.d.ts.map +1 -0
  131. package/dist/mail/turn-recorder.js +98 -0
  132. package/dist/mail/turn-recorder.js.map +1 -0
  133. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  134. package/dist/map/adapter/acp-over-map.js +32 -2
  135. package/dist/map/adapter/acp-over-map.js.map +1 -1
  136. package/dist/map/adapter/event-translator.d.ts.map +1 -1
  137. package/dist/map/adapter/event-translator.js +4 -0
  138. package/dist/map/adapter/event-translator.js.map +1 -1
  139. package/dist/map/adapter/extensions/agent-detection.d.ts +49 -0
  140. package/dist/map/adapter/extensions/agent-detection.d.ts.map +1 -0
  141. package/dist/map/adapter/extensions/agent-detection.js +91 -0
  142. package/dist/map/adapter/extensions/agent-detection.js.map +1 -0
  143. package/dist/map/adapter/extensions/index.d.ts +10 -1
  144. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  145. package/dist/map/adapter/extensions/index.js +39 -0
  146. package/dist/map/adapter/extensions/index.js.map +1 -1
  147. package/dist/map/adapter/extensions/resume.d.ts +47 -0
  148. package/dist/map/adapter/extensions/resume.d.ts.map +1 -0
  149. package/dist/map/adapter/extensions/resume.js +59 -0
  150. package/dist/map/adapter/extensions/resume.js.map +1 -0
  151. package/dist/map/adapter/extensions/workspace-files.d.ts +42 -0
  152. package/dist/map/adapter/extensions/workspace-files.d.ts.map +1 -0
  153. package/dist/map/adapter/extensions/workspace-files.js +338 -0
  154. package/dist/map/adapter/extensions/workspace-files.js.map +1 -0
  155. package/dist/map/adapter/mail-handler-adapter.d.ts +27 -0
  156. package/dist/map/adapter/mail-handler-adapter.d.ts.map +1 -0
  157. package/dist/map/adapter/mail-handler-adapter.js +292 -0
  158. package/dist/map/adapter/mail-handler-adapter.js.map +1 -0
  159. package/dist/map/adapter/map-adapter.d.ts +34 -10
  160. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  161. package/dist/map/adapter/map-adapter.js +110 -14
  162. package/dist/map/adapter/map-adapter.js.map +1 -1
  163. package/dist/map/adapter/rpc-handler.d.ts +4 -1
  164. package/dist/map/adapter/rpc-handler.d.ts.map +1 -1
  165. package/dist/map/adapter/rpc-handler.js +6 -0
  166. package/dist/map/adapter/rpc-handler.js.map +1 -1
  167. package/dist/map/index.d.ts +1 -0
  168. package/dist/map/index.d.ts.map +1 -1
  169. package/dist/map/index.js +2 -0
  170. package/dist/map/index.js.map +1 -1
  171. package/dist/map/types.d.ts +3 -1
  172. package/dist/map/types.d.ts.map +1 -1
  173. package/dist/map/types.js.map +1 -1
  174. package/dist/mcp/mcp-server.d.ts +6 -0
  175. package/dist/mcp/mcp-server.d.ts.map +1 -1
  176. package/dist/mcp/mcp-server.js +45 -0
  177. package/dist/mcp/mcp-server.js.map +1 -1
  178. package/dist/mcp/tools/claim_task.d.ts +35 -0
  179. package/dist/mcp/tools/claim_task.d.ts.map +1 -0
  180. package/dist/mcp/tools/claim_task.js +58 -0
  181. package/dist/mcp/tools/claim_task.js.map +1 -0
  182. package/dist/mcp/tools/done.d.ts +15 -2
  183. package/dist/mcp/tools/done.d.ts.map +1 -1
  184. package/dist/mcp/tools/done.js +45 -10
  185. package/dist/mcp/tools/done.js.map +1 -1
  186. package/dist/mcp/tools/list_claimable_tasks.d.ts +38 -0
  187. package/dist/mcp/tools/list_claimable_tasks.d.ts.map +1 -0
  188. package/dist/mcp/tools/list_claimable_tasks.js +63 -0
  189. package/dist/mcp/tools/list_claimable_tasks.js.map +1 -0
  190. package/dist/mcp/tools/unclaim_task.d.ts +31 -0
  191. package/dist/mcp/tools/unclaim_task.d.ts.map +1 -0
  192. package/dist/mcp/tools/unclaim_task.js +47 -0
  193. package/dist/mcp/tools/unclaim_task.js.map +1 -0
  194. package/dist/metrics/index.d.ts +2 -0
  195. package/dist/metrics/index.d.ts.map +1 -0
  196. package/dist/metrics/index.js +2 -0
  197. package/dist/metrics/index.js.map +1 -0
  198. package/dist/metrics/metrics.d.ts +79 -0
  199. package/dist/metrics/metrics.d.ts.map +1 -0
  200. package/dist/metrics/metrics.js +166 -0
  201. package/dist/metrics/metrics.js.map +1 -0
  202. package/dist/roles/capabilities.d.ts +1 -0
  203. package/dist/roles/capabilities.d.ts.map +1 -1
  204. package/dist/roles/capabilities.js +3 -0
  205. package/dist/roles/capabilities.js.map +1 -1
  206. package/dist/roles/types.d.ts +1 -1
  207. package/dist/roles/types.d.ts.map +1 -1
  208. package/dist/router/channels.d.ts +2 -4
  209. package/dist/router/channels.d.ts.map +1 -1
  210. package/dist/router/channels.js.map +1 -1
  211. package/dist/router/message-router.d.ts +85 -9
  212. package/dist/router/message-router.d.ts.map +1 -1
  213. package/dist/router/message-router.js +203 -14
  214. package/dist/router/message-router.js.map +1 -1
  215. package/dist/router/role-resolver.d.ts +10 -1
  216. package/dist/router/role-resolver.d.ts.map +1 -1
  217. package/dist/router/role-resolver.js +15 -1
  218. package/dist/router/role-resolver.js.map +1 -1
  219. package/dist/router/types.d.ts +30 -1
  220. package/dist/router/types.d.ts.map +1 -1
  221. package/dist/router/types.js.map +1 -1
  222. package/dist/server/combined-server.d.ts +6 -0
  223. package/dist/server/combined-server.d.ts.map +1 -1
  224. package/dist/server/combined-server.js +24 -2
  225. package/dist/server/combined-server.js.map +1 -1
  226. package/dist/store/event-store.d.ts +14 -1
  227. package/dist/store/event-store.d.ts.map +1 -1
  228. package/dist/store/event-store.js +456 -4
  229. package/dist/store/event-store.js.map +1 -1
  230. package/dist/store/types/agents.d.ts +1 -1
  231. package/dist/store/types/agents.d.ts.map +1 -1
  232. package/dist/store/types/conversations.d.ts +91 -0
  233. package/dist/store/types/conversations.d.ts.map +1 -0
  234. package/dist/store/types/conversations.js +8 -0
  235. package/dist/store/types/conversations.js.map +1 -0
  236. package/dist/store/types/events.d.ts +1 -1
  237. package/dist/store/types/events.d.ts.map +1 -1
  238. package/dist/store/types/events.js.map +1 -1
  239. package/dist/store/types/index.d.ts +2 -0
  240. package/dist/store/types/index.d.ts.map +1 -1
  241. package/dist/store/types/index.js +2 -0
  242. package/dist/store/types/index.js.map +1 -1
  243. package/dist/store/types/sessions.d.ts +44 -0
  244. package/dist/store/types/sessions.d.ts.map +1 -0
  245. package/dist/store/types/sessions.js +9 -0
  246. package/dist/store/types/sessions.js.map +1 -0
  247. package/dist/store/types/tasks.d.ts +2 -0
  248. package/dist/store/types/tasks.d.ts.map +1 -1
  249. package/dist/task/backend/memory.d.ts +4 -1
  250. package/dist/task/backend/memory.d.ts.map +1 -1
  251. package/dist/task/backend/memory.js +81 -0
  252. package/dist/task/backend/memory.js.map +1 -1
  253. package/dist/task/backend/types.d.ts +30 -0
  254. package/dist/task/backend/types.d.ts.map +1 -1
  255. package/dist/task/backend/types.js.map +1 -1
  256. package/dist/teams/index.d.ts +4 -0
  257. package/dist/teams/index.d.ts.map +1 -0
  258. package/dist/teams/index.js +4 -0
  259. package/dist/teams/index.js.map +1 -0
  260. package/dist/teams/team-loader.d.ts +20 -0
  261. package/dist/teams/team-loader.d.ts.map +1 -0
  262. package/dist/teams/team-loader.js +293 -0
  263. package/dist/teams/team-loader.js.map +1 -0
  264. package/dist/teams/team-runtime.d.ts +139 -0
  265. package/dist/teams/team-runtime.d.ts.map +1 -0
  266. package/dist/teams/team-runtime.js +613 -0
  267. package/dist/teams/team-runtime.js.map +1 -0
  268. package/dist/teams/types.d.ts +266 -0
  269. package/dist/teams/types.d.ts.map +1 -0
  270. package/dist/teams/types.js +20 -0
  271. package/dist/teams/types.js.map +1 -0
  272. package/dist/trigger/router/trigger-router.d.ts +30 -3
  273. package/dist/trigger/router/trigger-router.d.ts.map +1 -1
  274. package/dist/trigger/router/trigger-router.js +30 -3
  275. package/dist/trigger/router/trigger-router.js.map +1 -1
  276. package/dist/trigger/wake/types.d.ts +31 -5
  277. package/dist/trigger/wake/types.d.ts.map +1 -1
  278. package/dist/trigger/wake/types.js +19 -0
  279. package/dist/trigger/wake/types.js.map +1 -1
  280. package/dist/workspace/dataplane-adapter.d.ts +1 -1
  281. package/dist/workspace/dataplane-adapter.d.ts.map +1 -1
  282. package/dist/workspace/dataplane-adapter.js +1 -1
  283. package/dist/workspace/dataplane-adapter.js.map +1 -1
  284. package/dist/workspace/index.d.ts +1 -1
  285. package/dist/workspace/index.d.ts.map +1 -1
  286. package/dist/workspace/strategies/index.d.ts +6 -0
  287. package/dist/workspace/strategies/index.d.ts.map +1 -0
  288. package/dist/workspace/strategies/index.js +5 -0
  289. package/dist/workspace/strategies/index.js.map +1 -0
  290. package/dist/workspace/strategies/optimistic.d.ts +26 -0
  291. package/dist/workspace/strategies/optimistic.d.ts.map +1 -0
  292. package/dist/workspace/strategies/optimistic.js +121 -0
  293. package/dist/workspace/strategies/optimistic.js.map +1 -0
  294. package/dist/workspace/strategies/queue.d.ts +26 -0
  295. package/dist/workspace/strategies/queue.d.ts.map +1 -0
  296. package/dist/workspace/strategies/queue.js +67 -0
  297. package/dist/workspace/strategies/queue.js.map +1 -0
  298. package/dist/workspace/strategies/registry.d.ts +37 -0
  299. package/dist/workspace/strategies/registry.d.ts.map +1 -0
  300. package/dist/workspace/strategies/registry.js +63 -0
  301. package/dist/workspace/strategies/registry.js.map +1 -0
  302. package/dist/workspace/strategies/trunk.d.ts +20 -0
  303. package/dist/workspace/strategies/trunk.d.ts.map +1 -0
  304. package/dist/workspace/strategies/trunk.js +108 -0
  305. package/dist/workspace/strategies/trunk.js.map +1 -0
  306. package/dist/workspace/strategies/types.d.ts +104 -0
  307. package/dist/workspace/strategies/types.d.ts.map +1 -0
  308. package/dist/workspace/strategies/types.js +11 -0
  309. package/dist/workspace/strategies/types.js.map +1 -0
  310. package/dist/workspace/types.d.ts +1 -1
  311. package/dist/workspace/types.d.ts.map +1 -1
  312. package/dist/workspace/workspace-manager.d.ts +1 -1
  313. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  314. package/docs/implementation-details.md +1127 -0
  315. package/docs/implementation-summary.md +448 -0
  316. package/docs/mail-integration.md +608 -0
  317. package/docs/plan-self-driving-support.md +433 -0
  318. package/docs/spec-self-driving-support.md +462 -0
  319. package/docs/team-templates.md +860 -0
  320. package/docs/teams.md +233 -0
  321. package/package.json +5 -3
  322. package/src/acp/__tests__/integration.test.ts +161 -1
  323. package/src/acp/__tests__/macro-agent.test.ts +95 -0
  324. package/src/acp/__tests__/session-persistence.test.ts +276 -0
  325. package/src/acp/macro-agent.ts +79 -7
  326. package/src/acp/session-mapper.ts +108 -1
  327. package/src/acp/types.ts +33 -1
  328. package/src/agent/agent-manager.ts +278 -6
  329. package/src/agent/types.ts +27 -0
  330. package/src/agent/wake.ts +15 -0
  331. package/src/agent-detection/__tests__/command-builder.test.ts +336 -0
  332. package/src/agent-detection/__tests__/detector.test.ts +768 -0
  333. package/src/agent-detection/__tests__/registry.test.ts +254 -0
  334. package/src/agent-detection/command-builder.ts +90 -0
  335. package/src/agent-detection/detector.ts +307 -0
  336. package/src/agent-detection/index.ts +36 -0
  337. package/src/agent-detection/registry.ts +200 -0
  338. package/src/agent-detection/types.ts +184 -0
  339. package/src/api/__tests__/conversation-api.test.ts +468 -0
  340. package/src/api/server.ts +425 -1
  341. package/src/api/types.ts +64 -1
  342. package/src/cli/acp.ts +9 -1
  343. package/src/cli/index.ts +44 -0
  344. package/src/cli/mcp.ts +47 -0
  345. package/src/config/index.ts +9 -0
  346. package/src/config/project-config.ts +107 -0
  347. package/src/lifecycle/cascade.ts +1 -1
  348. package/src/lifecycle/handlers/index.ts +8 -0
  349. package/src/lifecycle/handlers/worker.ts +48 -3
  350. package/src/mail/__tests__/conversation-lifecycle.test.ts +409 -0
  351. package/src/mail/__tests__/eventstore-stores.test.ts +1073 -0
  352. package/src/mail/__tests__/mail-full-agent.e2e.test.ts +575 -0
  353. package/src/mail/__tests__/mail-integration.test.ts +759 -0
  354. package/src/mail/__tests__/mail-map-protocol.e2e.test.ts +1068 -0
  355. package/src/mail/__tests__/mail-service.test.ts +506 -0
  356. package/src/mail/__tests__/turn-recorder.test.ts +328 -0
  357. package/src/mail/conversation-map.ts +107 -0
  358. package/src/mail/index.ts +25 -0
  359. package/src/mail/mail-service.ts +257 -0
  360. package/src/mail/stores/eventstore-conversation-store.ts +146 -0
  361. package/src/mail/stores/eventstore-participant-store.ts +172 -0
  362. package/src/mail/stores/eventstore-thread-store.ts +129 -0
  363. package/src/mail/stores/eventstore-turn-store.ts +173 -0
  364. package/src/mail/stores/index.ts +12 -0
  365. package/src/mail/stores/types.ts +160 -0
  366. package/src/mail/turn-recorder.ts +124 -0
  367. package/src/map/README.md +79 -0
  368. package/src/map/adapter/__tests__/extensions.test.ts +359 -0
  369. package/src/map/adapter/__tests__/map-adapter.test.ts +90 -0
  370. package/src/map/adapter/__tests__/workspace-files.test.ts +673 -0
  371. package/src/map/adapter/acp-over-map.ts +45 -2
  372. package/src/map/adapter/event-translator.ts +4 -0
  373. package/src/map/adapter/extensions/agent-detection.ts +201 -0
  374. package/src/map/adapter/extensions/index.ts +63 -0
  375. package/src/map/adapter/extensions/resume.ts +114 -0
  376. package/src/map/adapter/extensions/workspace-files.ts +449 -0
  377. package/src/map/adapter/mail-handler-adapter.ts +429 -0
  378. package/src/map/adapter/map-adapter.ts +173 -27
  379. package/src/map/adapter/rpc-handler.ts +8 -1
  380. package/src/map/index.ts +3 -0
  381. package/src/map/types.ts +3 -1
  382. package/src/mcp/mcp-server.ts +67 -0
  383. package/src/mcp/tools/claim_task.ts +86 -0
  384. package/src/mcp/tools/done.ts +59 -10
  385. package/src/mcp/tools/list_claimable_tasks.ts +93 -0
  386. package/src/mcp/tools/unclaim_task.ts +71 -0
  387. package/src/metrics/index.ts +9 -0
  388. package/src/metrics/metrics.ts +280 -0
  389. package/src/roles/capabilities.ts +3 -0
  390. package/src/roles/types.ts +2 -1
  391. package/src/router/README.md +120 -0
  392. package/src/router/__tests__/message-router.test.ts +561 -0
  393. package/src/router/channels.ts +3 -4
  394. package/src/router/message-router.ts +308 -22
  395. package/src/router/role-resolver.ts +22 -1
  396. package/src/router/types.ts +36 -1
  397. package/src/server/combined-server.ts +36 -2
  398. package/src/store/README.md +134 -0
  399. package/src/store/event-store.ts +546 -3
  400. package/src/store/types/agents.ts +1 -1
  401. package/src/store/types/conversations.ts +129 -0
  402. package/src/store/types/events.ts +5 -1
  403. package/src/store/types/index.ts +2 -0
  404. package/src/store/types/sessions.ts +53 -0
  405. package/src/store/types/tasks.ts +3 -0
  406. package/src/task/backend/memory.ts +116 -0
  407. package/src/task/backend/types.ts +43 -0
  408. package/src/teams/__tests__/cross-subsystem.integration.test.ts +983 -0
  409. package/src/teams/__tests__/e2e/team-runtime.e2e.test.ts +553 -0
  410. package/src/teams/__tests__/team-system.test.ts +1280 -0
  411. package/src/teams/index.ts +13 -0
  412. package/src/teams/team-loader.ts +434 -0
  413. package/src/teams/team-runtime.ts +727 -0
  414. package/src/teams/types.ts +377 -0
  415. package/src/trigger/router/trigger-router.ts +30 -3
  416. package/src/trigger/wake/types.ts +32 -5
  417. package/src/trigger/wake/wake-manager.ts +2 -2
  418. package/src/workspace/dataplane-adapter.ts +1 -1
  419. package/src/workspace/index.ts +1 -1
  420. package/src/workspace/strategies/index.ts +18 -0
  421. package/src/workspace/strategies/optimistic.ts +136 -0
  422. package/src/workspace/strategies/queue.ts +81 -0
  423. package/src/workspace/strategies/registry.ts +89 -0
  424. package/src/workspace/strategies/trunk.ts +123 -0
  425. package/src/workspace/strategies/types.ts +145 -0
  426. package/src/workspace/types.ts +1 -1
  427. package/src/workspace/workspace-manager.ts +1 -1
  428. package/.claude/settings.local.json +0 -59
  429. package/dist/map/utils/address-translation.d.ts +0 -99
  430. package/dist/map/utils/address-translation.d.ts.map +0 -1
  431. package/dist/map/utils/address-translation.js +0 -285
  432. package/dist/map/utils/address-translation.js.map +0 -1
  433. package/dist/map/utils/index.d.ts +0 -7
  434. package/dist/map/utils/index.d.ts.map +0 -1
  435. package/dist/map/utils/index.js +0 -7
  436. package/dist/map/utils/index.js.map +0 -1
  437. package/openspec/AGENTS.md +0 -456
  438. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/design.md +0 -128
  439. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/proposal.md +0 -49
  440. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/agent-manager/spec.md +0 -150
  441. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/cli-api/spec.md +0 -258
  442. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/event-store/spec.md +0 -160
  443. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/mcp-tools/spec.md +0 -224
  444. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/message-router/spec.md +0 -153
  445. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/task-manager/spec.md +0 -136
  446. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/tasks.md +0 -147
  447. package/openspec/project.md +0 -31
  448. package/openspec/specs/agent-manager/spec.md +0 -154
  449. package/openspec/specs/cli-api/spec.md +0 -262
  450. package/openspec/specs/event-store/spec.md +0 -164
  451. package/openspec/specs/mcp-tools/spec.md +0 -228
  452. package/openspec/specs/message-router/spec.md +0 -157
  453. package/openspec/specs/task-manager/spec.md +0 -140
  454. package/references/acp-factory-ref/CHANGELOG.md +0 -33
  455. package/references/acp-factory-ref/LICENSE +0 -21
  456. package/references/acp-factory-ref/README.md +0 -341
  457. package/references/acp-factory-ref/package-lock.json +0 -3102
  458. package/references/acp-factory-ref/package.json +0 -96
  459. package/references/acp-factory-ref/python/CHANGELOG.md +0 -33
  460. package/references/acp-factory-ref/python/LICENSE +0 -21
  461. package/references/acp-factory-ref/python/Makefile +0 -57
  462. package/references/acp-factory-ref/python/README.md +0 -253
  463. package/references/acp-factory-ref/python/pyproject.toml +0 -73
  464. package/references/acp-factory-ref/python/tests/__init__.py +0 -0
  465. package/references/acp-factory-ref/python/tests/e2e/__init__.py +0 -1
  466. package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +0 -349
  467. package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +0 -165
  468. package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +0 -296
  469. package/references/acp-factory-ref/python/tests/test_client_handler.py +0 -543
  470. package/references/acp-factory-ref/python/tests/test_pushable.py +0 -199
  471. package/references/claude-code-acp/.github/workflows/ci.yml +0 -45
  472. package/references/claude-code-acp/.github/workflows/publish.yml +0 -34
  473. package/references/claude-code-acp/.prettierrc.json +0 -4
  474. package/references/claude-code-acp/CHANGELOG.md +0 -249
  475. package/references/claude-code-acp/LICENSE +0 -222
  476. package/references/claude-code-acp/README.md +0 -53
  477. package/references/claude-code-acp/docs/RELEASES.md +0 -24
  478. package/references/claude-code-acp/eslint.config.js +0 -48
  479. package/references/claude-code-acp/package-lock.json +0 -4570
  480. package/references/claude-code-acp/package.json +0 -88
  481. package/references/claude-code-acp/scripts/release.sh +0 -119
  482. package/references/claude-code-acp/src/acp-agent.ts +0 -2065
  483. package/references/claude-code-acp/src/index.ts +0 -26
  484. package/references/claude-code-acp/src/lib.ts +0 -38
  485. package/references/claude-code-acp/src/mcp-server.ts +0 -911
  486. package/references/claude-code-acp/src/settings.ts +0 -522
  487. package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +0 -5
  488. package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +0 -6
  489. package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +0 -479
  490. package/references/claude-code-acp/src/tests/acp-agent.test.ts +0 -1502
  491. package/references/claude-code-acp/src/tests/extract-lines.test.ts +0 -103
  492. package/references/claude-code-acp/src/tests/fork-session.test.ts +0 -335
  493. package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +0 -334
  494. package/references/claude-code-acp/src/tests/settings.test.ts +0 -617
  495. package/references/claude-code-acp/src/tests/skills-options.test.ts +0 -187
  496. package/references/claude-code-acp/src/tests/tools.test.ts +0 -318
  497. package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +0 -558
  498. package/references/claude-code-acp/src/tools.ts +0 -819
  499. package/references/claude-code-acp/src/utils.ts +0 -171
  500. package/references/claude-code-acp/tsconfig.json +0 -18
  501. package/references/claude-code-acp/vitest.config.ts +0 -19
  502. package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -82
  503. package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -9
  504. package/references/multi-agent-protocol/LICENSE +0 -21
  505. package/references/multi-agent-protocol/README.md +0 -113
  506. package/references/multi-agent-protocol/docs/00-design-specification.md +0 -460
  507. package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
  508. package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
  509. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
  510. package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
  511. package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
  512. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
  513. package/references/multi-agent-protocol/docs/07-federation.md +0 -259
  514. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
  515. package/references/multi-agent-protocol/package-lock.json +0 -3239
  516. package/references/multi-agent-protocol/package.json +0 -56
  517. package/references/multi-agent-protocol/schema/meta.json +0 -337
  518. package/references/multi-agent-protocol/schema/schema.json +0 -1828
@@ -0,0 +1,983 @@
1
+ /**
2
+ * Cross-Subsystem Integration Tests
3
+ *
4
+ * Tests interactions between subsystems:
5
+ * - Strategy ↔ Worker Handler
6
+ * - Task Backend claim/unclaim cycle
7
+ * - Team Config → Spawn Interceptor → Worker Done pipeline
8
+ * - Metrics ↔ EventStore realistic events
9
+ * - Pull mode lifecycle ↔ handler dispatch
10
+ * - Role resolution ↔ capability gating
11
+ */
12
+
13
+ import { describe, it, expect, vi, beforeEach } from "vitest";
14
+ import * as path from "path";
15
+ import { loadTeam } from "../team-loader.js";
16
+ import { TeamRuntime, type TeamServices } from "../team-runtime.js";
17
+ import { DefaultRoleRegistry } from "../../roles/registry.js";
18
+ import {
19
+ handleWorkerDone,
20
+ type WorkerHandlerDeps,
21
+ } from "../../lifecycle/handlers/worker.js";
22
+ import {
23
+ dispatchDone,
24
+ createHandlerRegistry,
25
+ type AllHandlerDeps,
26
+ } from "../../lifecycle/handlers/index.js";
27
+ import type {
28
+ LifecycleContext,
29
+ DoneArgs,
30
+ CleanupStatus,
31
+ } from "../../lifecycle/types.js";
32
+ import type {
33
+ IntegrationStrategy,
34
+ LandRequest,
35
+ LandResult,
36
+ } from "../../workspace/strategies/types.js";
37
+ import { defaultStrategyRegistry } from "../../workspace/strategies/registry.js";
38
+ import {
39
+ createClaimTaskHandler,
40
+ type ClaimTaskDeps,
41
+ } from "../../mcp/tools/claim_task.js";
42
+ import {
43
+ createUnclaimTaskHandler,
44
+ type UnclaimTaskDeps,
45
+ } from "../../mcp/tools/unclaim_task.js";
46
+ import {
47
+ createListClaimableTasksHandler,
48
+ } from "../../mcp/tools/list_claimable_tasks.js";
49
+ import type { TaskBackend, ClaimFilter, ExtendedTask } from "../../task/backend/types.js";
50
+ import type { ToolContext } from "../../mcp/types.js";
51
+ import {
52
+ getThroughputMetrics,
53
+ getUtilizationMetrics,
54
+ getErrorMetrics,
55
+ } from "../../metrics/index.js";
56
+ import {
57
+ TASK_CAPABILITIES,
58
+ CAPABILITY_TOOL_MAP,
59
+ } from "../../roles/capabilities.js";
60
+ import type { AgentManager, SpawnInterceptor } from "../../agent/agent-manager.js";
61
+ import type { MessageRouter } from "../../router/message-router.js";
62
+ import type { EventStore } from "../../store/event-store.js";
63
+ import type { SpawnAgentOptions } from "../../agent/types.js";
64
+ import type { Event, Agent } from "../../store/types/index.js";
65
+
66
+ // =============================================================================
67
+ // Shared Helpers
68
+ // =============================================================================
69
+
70
+ const PROJECT_ROOT = path.resolve(import.meta.dirname, "../../..");
71
+
72
+ function createMockEventStore(events: Event[] = []): EventStore & { _events: Event[] } {
73
+ return {
74
+ emit: vi.fn((input: Record<string, unknown>) => {
75
+ const event = {
76
+ id: `evt_${events.length}`,
77
+ type: input.type,
78
+ timestamp: Date.now(),
79
+ source: input.source,
80
+ target: input.target,
81
+ payload: input.payload,
82
+ } as unknown as Event;
83
+ events.push(event);
84
+ return event;
85
+ }),
86
+ persist: vi.fn().mockResolvedValue(undefined),
87
+ close: vi.fn().mockResolvedValue(undefined),
88
+ query: vi.fn((filter: { type?: string; after?: number }) => {
89
+ return events.filter((e) => {
90
+ if (filter.type && e.type !== filter.type) return false;
91
+ if (filter.after && e.timestamp < filter.after) return false;
92
+ return true;
93
+ });
94
+ }),
95
+ getAgent: vi.fn().mockReturnValue(null),
96
+ getTask: vi.fn().mockReturnValue(null),
97
+ listAgents: vi.fn().mockReturnValue([]),
98
+ onAgentChange: vi.fn(),
99
+ onTaskChange: vi.fn(),
100
+ instanceId: "test-instance",
101
+ _events: events,
102
+ } as unknown as EventStore & { _events: Event[] };
103
+ }
104
+
105
+ function createMockMessageRouter(): MessageRouter {
106
+ return {
107
+ sendToAddress: vi.fn().mockResolvedValue({ delivered: true }),
108
+ emitStatus: vi.fn(),
109
+ getMessages: vi.fn().mockReturnValue([]),
110
+ subscribe: vi.fn(),
111
+ unsubscribe: vi.fn(),
112
+ getSubscriptions: vi.fn().mockReturnValue([]),
113
+ setupDefaultSubscriptions: vi.fn(),
114
+ } as unknown as MessageRouter;
115
+ }
116
+
117
+ function createMockStrategy(
118
+ name: string,
119
+ landResult: LandResult = { status: "landed", commitHash: "abc123" }
120
+ ): IntegrationStrategy & { land: ReturnType<typeof vi.fn> } {
121
+ return {
122
+ name,
123
+ land: vi.fn().mockResolvedValue(landResult),
124
+ };
125
+ }
126
+
127
+ function createWorkerContext(overrides: Partial<LifecycleContext> = {}): LifecycleContext {
128
+ return {
129
+ agentId: "worker-1",
130
+ role: "worker",
131
+ taskId: "task-1",
132
+ workspacePath: "/tmp/test-workspace",
133
+ branch: "feature/test",
134
+ integrationBranch: "integration",
135
+ streamId: "stream-1",
136
+ ...overrides,
137
+ };
138
+ }
139
+
140
+ function createCleanupStatus(): CleanupStatus {
141
+ return { ready: true };
142
+ }
143
+
144
+ // =============================================================================
145
+ // Tests: Strategy ↔ Worker Handler Integration
146
+ // =============================================================================
147
+
148
+ describe("Strategy ↔ Worker Handler", () => {
149
+ let messageRouter: MessageRouter;
150
+ let agentManager: AgentManager;
151
+
152
+ beforeEach(() => {
153
+ messageRouter = createMockMessageRouter();
154
+ agentManager = {
155
+ getChildren: vi.fn().mockReturnValue([]),
156
+ } as unknown as AgentManager;
157
+ });
158
+
159
+ it("dispatches to integration strategy when configured", async () => {
160
+ const strategy = createMockStrategy("trunk");
161
+ const deps: WorkerHandlerDeps = {
162
+ messageRouter,
163
+ agentManager,
164
+ integrationStrategy: strategy,
165
+ };
166
+
167
+ const context = createWorkerContext();
168
+ const args: DoneArgs = { status: "completed", summary: "Done" };
169
+
170
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
171
+
172
+ expect(strategy.land).toHaveBeenCalledTimes(1);
173
+ expect(strategy.land).toHaveBeenCalledWith(
174
+ expect.objectContaining({
175
+ sourceBranch: "feature/test",
176
+ targetBranch: "integration",
177
+ workspacePath: "/tmp/test-workspace",
178
+ agentId: "worker-1",
179
+ taskId: "task-1",
180
+ streamId: "stream-1",
181
+ })
182
+ );
183
+ expect(result.signalsEmitted).toContain("WORKER_INTEGRATED");
184
+ expect(result.shouldTerminate).toBe(true);
185
+ });
186
+
187
+ it("falls back to merge queue when no strategy configured", async () => {
188
+ const mergeQueue = {
189
+ submit: vi.fn().mockReturnValue("mr-1"),
190
+ };
191
+ const deps: WorkerHandlerDeps = {
192
+ messageRouter,
193
+ agentManager,
194
+ mergeQueue: mergeQueue as any,
195
+ // No integrationStrategy
196
+ };
197
+
198
+ const context = createWorkerContext();
199
+ const args: DoneArgs = { status: "completed" };
200
+
201
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
202
+
203
+ expect(mergeQueue.submit).toHaveBeenCalledTimes(1);
204
+ expect(result.signalsEmitted).toContain("MERGE_REQUEST");
205
+ });
206
+
207
+ it("strategy takes priority over merge queue", async () => {
208
+ const strategy = createMockStrategy("trunk");
209
+ const mergeQueue = { submit: vi.fn() };
210
+ const deps: WorkerHandlerDeps = {
211
+ messageRouter,
212
+ agentManager,
213
+ integrationStrategy: strategy,
214
+ mergeQueue: mergeQueue as any,
215
+ };
216
+
217
+ const context = createWorkerContext();
218
+ const args: DoneArgs = { status: "completed" };
219
+
220
+ await handleWorkerDone(context, args, createCleanupStatus(), deps);
221
+
222
+ expect(strategy.land).toHaveBeenCalledTimes(1);
223
+ expect(mergeQueue.submit).not.toHaveBeenCalled();
224
+ });
225
+
226
+ it("handles strategy conflict result gracefully", async () => {
227
+ const strategy = createMockStrategy("trunk", {
228
+ status: "conflict",
229
+ conflictFiles: ["src/foo.ts"],
230
+ error: "Rebase conflict after 3 retries",
231
+ });
232
+ const deps: WorkerHandlerDeps = {
233
+ messageRouter,
234
+ agentManager,
235
+ integrationStrategy: strategy,
236
+ };
237
+
238
+ const context = createWorkerContext();
239
+ const args: DoneArgs = { status: "completed" };
240
+
241
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
242
+
243
+ expect(result.warnings).toBeDefined();
244
+ expect(result.warnings!.some((w) => w.includes("conflict"))).toBe(true);
245
+ expect(result.signalsEmitted).not.toContain("WORKER_INTEGRATED");
246
+ });
247
+
248
+ it("handles strategy failure result gracefully", async () => {
249
+ const strategy = createMockStrategy("trunk", {
250
+ status: "failed",
251
+ error: "Push rejected",
252
+ });
253
+ const deps: WorkerHandlerDeps = {
254
+ messageRouter,
255
+ agentManager,
256
+ integrationStrategy: strategy,
257
+ };
258
+
259
+ const context = createWorkerContext();
260
+ const args: DoneArgs = { status: "completed" };
261
+
262
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
263
+
264
+ expect(result.warnings).toBeDefined();
265
+ expect(result.warnings!.some((w) => w.includes("failed"))).toBe(true);
266
+ });
267
+
268
+ it("handles strategy exception gracefully", async () => {
269
+ const strategy = createMockStrategy("trunk");
270
+ strategy.land.mockRejectedValue(new Error("Git process crashed"));
271
+ const deps: WorkerHandlerDeps = {
272
+ messageRouter,
273
+ agentManager,
274
+ integrationStrategy: strategy,
275
+ };
276
+
277
+ const context = createWorkerContext();
278
+ const args: DoneArgs = { status: "completed" };
279
+
280
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
281
+
282
+ // Should not throw — graceful degradation
283
+ expect(result.warnings).toBeDefined();
284
+ expect(result.warnings!.some((w) => w.includes("Git process crashed"))).toBe(true);
285
+ });
286
+
287
+ it("skips strategy when status is not completed", async () => {
288
+ const strategy = createMockStrategy("trunk");
289
+ const deps: WorkerHandlerDeps = {
290
+ messageRouter,
291
+ agentManager,
292
+ integrationStrategy: strategy,
293
+ };
294
+
295
+ const context = createWorkerContext();
296
+ const args: DoneArgs = { status: "failed", summary: "Build failed" };
297
+
298
+ await handleWorkerDone(context, args, createCleanupStatus(), deps);
299
+
300
+ expect(strategy.land).not.toHaveBeenCalled();
301
+ });
302
+ });
303
+
304
+ // =============================================================================
305
+ // Tests: Pull Mode Lifecycle
306
+ // =============================================================================
307
+
308
+ describe("Pull Mode ↔ Worker Handler", () => {
309
+ let messageRouter: MessageRouter;
310
+ let agentManager: AgentManager;
311
+
312
+ beforeEach(() => {
313
+ messageRouter = createMockMessageRouter();
314
+ agentManager = {
315
+ getChildren: vi.fn().mockReturnValue([]),
316
+ } as unknown as AgentManager;
317
+ });
318
+
319
+ it("pull mode workers stay alive after completion", async () => {
320
+ const strategy = createMockStrategy("trunk");
321
+ const deps: WorkerHandlerDeps = {
322
+ messageRouter,
323
+ agentManager,
324
+ integrationStrategy: strategy,
325
+ taskMode: "pull",
326
+ };
327
+
328
+ const context = createWorkerContext();
329
+ const args: DoneArgs = { status: "completed" };
330
+
331
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
332
+
333
+ expect(result.shouldTerminate).toBe(false);
334
+ });
335
+
336
+ it("pull mode workers still terminate on failure", async () => {
337
+ const strategy = createMockStrategy("trunk");
338
+ const deps: WorkerHandlerDeps = {
339
+ messageRouter,
340
+ agentManager,
341
+ integrationStrategy: strategy,
342
+ taskMode: "pull",
343
+ };
344
+
345
+ const context = createWorkerContext();
346
+ const args: DoneArgs = { status: "failed", summary: "Error" };
347
+
348
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
349
+
350
+ expect(result.shouldTerminate).toBe(true);
351
+ });
352
+
353
+ it("push mode workers always terminate", async () => {
354
+ const strategy = createMockStrategy("trunk");
355
+ const deps: WorkerHandlerDeps = {
356
+ messageRouter,
357
+ agentManager,
358
+ integrationStrategy: strategy,
359
+ taskMode: "push",
360
+ };
361
+
362
+ const context = createWorkerContext();
363
+ const args: DoneArgs = { status: "completed" };
364
+
365
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
366
+
367
+ expect(result.shouldTerminate).toBe(true);
368
+ });
369
+
370
+ it("undefined taskMode defaults to terminate (push behavior)", async () => {
371
+ const strategy = createMockStrategy("trunk");
372
+ const deps: WorkerHandlerDeps = {
373
+ messageRouter,
374
+ agentManager,
375
+ integrationStrategy: strategy,
376
+ // taskMode undefined
377
+ };
378
+
379
+ const context = createWorkerContext();
380
+ const args: DoneArgs = { status: "completed" };
381
+
382
+ const result = await handleWorkerDone(context, args, createCleanupStatus(), deps);
383
+
384
+ expect(result.shouldTerminate).toBe(true);
385
+ });
386
+ });
387
+
388
+ // =============================================================================
389
+ // Tests: Handler Registry ↔ Team Roles
390
+ // =============================================================================
391
+
392
+ describe("Handler Registry ↔ Team Roles", () => {
393
+ it("team-defined roles dispatch to correct base handler", async () => {
394
+ const messageRouter = createMockMessageRouter();
395
+ const agentManager = {
396
+ getChildren: vi.fn().mockReturnValue([]),
397
+ } as unknown as AgentManager;
398
+
399
+ const strategy = createMockStrategy("trunk");
400
+ const deps: AllHandlerDeps = {
401
+ messageRouter,
402
+ agentManager,
403
+ integrationStrategy: strategy,
404
+ taskMode: "pull",
405
+ };
406
+
407
+ // "grinder" extends "worker" — should get the worker handler
408
+ const context = createWorkerContext({ role: "worker", agentId: "grinder-1" });
409
+ const args: DoneArgs = { status: "completed" };
410
+
411
+ const result = await dispatchDone(context, args, createCleanupStatus(), deps);
412
+
413
+ expect(strategy.land).toHaveBeenCalledTimes(1);
414
+ expect(result.shouldTerminate).toBe(false); // pull mode
415
+ });
416
+
417
+ it("monitor roles do not trigger strategy dispatch", async () => {
418
+ const messageRouter = createMockMessageRouter();
419
+ const agentManager = {
420
+ getChildren: vi.fn().mockReturnValue([]),
421
+ } as unknown as AgentManager;
422
+
423
+ const strategy = createMockStrategy("trunk");
424
+ const deps: AllHandlerDeps = {
425
+ messageRouter,
426
+ agentManager,
427
+ integrationStrategy: strategy,
428
+ };
429
+
430
+ const context: LifecycleContext = {
431
+ agentId: "judge-1",
432
+ role: "monitor",
433
+ };
434
+ const args: DoneArgs = { status: "completed", summary: "Health OK" };
435
+
436
+ const result = await dispatchDone(context, args, createCleanupStatus(), deps);
437
+
438
+ expect(strategy.land).not.toHaveBeenCalled();
439
+ expect(result.shouldTerminate).toBe(true);
440
+ });
441
+ });
442
+
443
+ // =============================================================================
444
+ // Tests: Task Backend Claim/Unclaim Cycle
445
+ // =============================================================================
446
+
447
+ describe("Task Claim/Unclaim Cycle via MCP Tools", () => {
448
+ function createMockTaskBackend(): TaskBackend {
449
+ const tasks: ExtendedTask[] = [
450
+ {
451
+ id: "task-1",
452
+ description: "Fix auth bug",
453
+ status: "pending",
454
+ created_at: Date.now() - 5000,
455
+ tags: ["bugfix"],
456
+ isBlocked: false,
457
+ } as ExtendedTask,
458
+ {
459
+ id: "task-2",
460
+ description: "Add feature X",
461
+ status: "pending",
462
+ created_at: Date.now() - 3000,
463
+ tags: ["feature"],
464
+ isBlocked: false,
465
+ } as ExtendedTask,
466
+ {
467
+ id: "task-3",
468
+ description: "Blocked task",
469
+ status: "pending",
470
+ created_at: Date.now() - 1000,
471
+ tags: ["bugfix"],
472
+ isBlocked: true,
473
+ } as ExtendedTask,
474
+ ];
475
+
476
+ return {
477
+ claim: vi.fn(async (agentId: string, filter?: ClaimFilter) => {
478
+ const candidates = tasks.filter((t) => {
479
+ if (t.status !== "pending" || t.isBlocked) return false;
480
+ if (t.assigned_agent) return false;
481
+ if (filter?.tags) {
482
+ const taskTags = t.tags ?? [];
483
+ if (!filter.tags.some((ft) => taskTags.includes(ft))) return false;
484
+ }
485
+ return true;
486
+ });
487
+ if (candidates.length === 0) return null;
488
+ const claimed = candidates[0];
489
+ claimed.status = "in_progress";
490
+ claimed.assigned_agent = agentId;
491
+ return claimed;
492
+ }),
493
+ unclaim: vi.fn(async (taskId: string) => {
494
+ const task = tasks.find((t) => t.id === taskId);
495
+ if (task) {
496
+ task.status = "pending";
497
+ task.assigned_agent = undefined;
498
+ }
499
+ }),
500
+ listClaimable: vi.fn(async (filter?: ClaimFilter) => {
501
+ return tasks.filter((t) => {
502
+ if (t.status !== "pending" || t.isBlocked) return false;
503
+ if (t.assigned_agent) return false;
504
+ if (filter?.tags) {
505
+ const taskTags = t.tags ?? [];
506
+ if (!filter.tags.some((ft) => taskTags.includes(ft))) return false;
507
+ }
508
+ return true;
509
+ });
510
+ }),
511
+ } as unknown as TaskBackend;
512
+ }
513
+
514
+ const toolContext: ToolContext = {
515
+ agent_id: "grinder-1",
516
+ session_id: "session-1",
517
+ } as ToolContext;
518
+
519
+ it("claim_task claims the first available task", async () => {
520
+ const backend = createMockTaskBackend();
521
+ const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
522
+
523
+ const result = await handler({});
524
+
525
+ expect(result.claimed).toBe(true);
526
+ expect(result.task).toBeDefined();
527
+ expect(result.task!.id).toBe("task-1");
528
+ expect(backend.claim).toHaveBeenCalledWith("grinder-1", {});
529
+ });
530
+
531
+ it("claim_task filters by tags", async () => {
532
+ const backend = createMockTaskBackend();
533
+ const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
534
+
535
+ const result = await handler({ tags: ["feature"] });
536
+
537
+ expect(result.claimed).toBe(true);
538
+ expect(result.task!.id).toBe("task-2");
539
+ });
540
+
541
+ it("claim_task returns not claimed when no tasks available", async () => {
542
+ const backend = createMockTaskBackend();
543
+ (backend.claim as ReturnType<typeof vi.fn>).mockResolvedValue(null);
544
+
545
+ const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
546
+ const result = await handler({});
547
+
548
+ expect(result.claimed).toBe(false);
549
+ expect(result.task).toBeUndefined();
550
+ });
551
+
552
+ it("unclaim_task returns task to pending pool", async () => {
553
+ const backend = createMockTaskBackend();
554
+ const unclaimHandler = createUnclaimTaskHandler(toolContext, {
555
+ taskBackend: backend,
556
+ });
557
+
558
+ const result = await unclaimHandler({ task_id: "task-1" });
559
+
560
+ expect(result.success).toBe(true);
561
+ expect(backend.unclaim).toHaveBeenCalledWith("task-1");
562
+ });
563
+
564
+ it("list_claimable_tasks returns only claimable tasks", async () => {
565
+ const backend = createMockTaskBackend();
566
+ const listHandler = createListClaimableTasksHandler(toolContext, {
567
+ taskBackend: backend,
568
+ });
569
+
570
+ const result = await listHandler({});
571
+
572
+ expect(result.tasks.length).toBe(2); // task-3 is blocked
573
+ expect(result.tasks.every((t: ExtendedTask) => !t.isBlocked)).toBe(true);
574
+ });
575
+
576
+ it("claim → unclaim → re-claim cycle works", async () => {
577
+ const backend = createMockTaskBackend();
578
+ const claimHandler = createClaimTaskHandler(toolContext, {
579
+ taskBackend: backend,
580
+ });
581
+ const unclaimHandler = createUnclaimTaskHandler(toolContext, {
582
+ taskBackend: backend,
583
+ });
584
+
585
+ // Claim task-1
586
+ const claim1 = await claimHandler({});
587
+ expect(claim1.claimed).toBe(true);
588
+ expect(claim1.task!.id).toBe("task-1");
589
+
590
+ // Unclaim task-1
591
+ await unclaimHandler({ task_id: "task-1" });
592
+
593
+ // Re-claim should get task-1 again (it's back to pending)
594
+ const claim2 = await claimHandler({});
595
+ expect(claim2.claimed).toBe(true);
596
+ expect(claim2.task!.id).toBe("task-1");
597
+ });
598
+
599
+ it("claim_task fails gracefully when backend lacks claim support", async () => {
600
+ const backend = {} as TaskBackend; // No claim method
601
+ const handler = createClaimTaskHandler(toolContext, { taskBackend: backend });
602
+
603
+ const result = await handler({});
604
+
605
+ expect(result.claimed).toBe(false);
606
+ expect(result.message).toContain("does not support");
607
+ });
608
+ });
609
+
610
+ // =============================================================================
611
+ // Tests: Metrics ↔ Realistic EventStore Events
612
+ // =============================================================================
613
+
614
+ describe("Metrics ↔ EventStore Integration", () => {
615
+ it("throughput metrics count task events correctly", () => {
616
+ const events: Event[] = [];
617
+ const now = Date.now();
618
+
619
+ // Simulate task events
620
+ events.push({
621
+ id: "e1", type: "task", timestamp: now - 1000,
622
+ source: { agent_id: "planner-1" },
623
+ payload: { action: "created", task_id: "t1" },
624
+ } as unknown as Event);
625
+ events.push({
626
+ id: "e2", type: "task", timestamp: now - 800,
627
+ source: { agent_id: "planner-1" },
628
+ payload: { action: "created", task_id: "t2" },
629
+ } as unknown as Event);
630
+ events.push({
631
+ id: "e3", type: "task", timestamp: now - 500,
632
+ source: { agent_id: "grinder-1" },
633
+ payload: { action: "completed", task_id: "t1" },
634
+ } as unknown as Event);
635
+ events.push({
636
+ id: "e4", type: "task", timestamp: now - 200,
637
+ source: { agent_id: "grinder-2" },
638
+ payload: { action: "failed", task_id: "t2" },
639
+ } as unknown as Event);
640
+
641
+ const store = createMockEventStore(events);
642
+
643
+ const metrics = getThroughputMetrics(store, 60000);
644
+
645
+ expect(metrics.tasksCreated).toBe(2);
646
+ expect(metrics.tasksCompleted).toBe(1);
647
+ expect(metrics.tasksFailed).toBe(1);
648
+ });
649
+
650
+ it("utilization metrics reflect active agents", () => {
651
+ const events: Event[] = [];
652
+ const now = Date.now();
653
+
654
+ events.push({
655
+ id: "e1", type: "spawn", timestamp: now - 5000,
656
+ source: { agent_id: "system" },
657
+ payload: { agent_id: "planner-1" },
658
+ } as unknown as Event);
659
+ events.push({
660
+ id: "e2", type: "spawn", timestamp: now - 3000,
661
+ source: { agent_id: "planner-1" },
662
+ payload: { agent_id: "grinder-1" },
663
+ } as unknown as Event);
664
+ events.push({
665
+ id: "e3", type: "terminate", timestamp: now - 1000,
666
+ source: { agent_id: "grinder-1" },
667
+ payload: { reason: "completed" },
668
+ } as unknown as Event);
669
+
670
+ const agents: Agent[] = [
671
+ { id: "planner-1", state: "running", role: "planner" } as unknown as Agent,
672
+ { id: "grinder-1", state: "stopped", role: "grinder" } as unknown as Agent,
673
+ ];
674
+
675
+ const store = createMockEventStore(events);
676
+ vi.mocked(store.listAgents).mockReturnValue(agents);
677
+
678
+ const metrics = getUtilizationMetrics(store, 60000);
679
+
680
+ expect(metrics.activeAgents).toBe(1);
681
+ expect(metrics.totalSpawned).toBe(2);
682
+ expect(metrics.totalStopped).toBe(1);
683
+ expect(metrics.agentsByRole).toEqual({ planner: 1 });
684
+ expect(metrics.agentsByState).toEqual({ running: 1, stopped: 1 });
685
+ });
686
+
687
+ it("error metrics capture both status and task failures", () => {
688
+ const events: Event[] = [];
689
+ const now = Date.now();
690
+
691
+ events.push({
692
+ id: "e1", type: "status", timestamp: now - 2000,
693
+ source: { agent_id: "grinder-1" },
694
+ payload: { status_type: "failed", summary: "OOM killed", details: { signal: "SIGKILL" } },
695
+ } as unknown as Event);
696
+ events.push({
697
+ id: "e2", type: "status", timestamp: now - 1500,
698
+ source: { agent_id: "grinder-2" },
699
+ payload: { status_type: "completed", summary: "Done" }, // Not an error
700
+ } as unknown as Event);
701
+ events.push({
702
+ id: "e3", type: "task", timestamp: now - 1000,
703
+ source: { agent_id: "grinder-3" },
704
+ payload: { action: "failed", task_id: "t5" },
705
+ } as unknown as Event);
706
+
707
+ const store = createMockEventStore(events);
708
+
709
+ const metrics = getErrorMetrics(store, 60000, 10);
710
+
711
+ expect(metrics.totalErrors).toBe(2);
712
+ expect(metrics.errorsByType["SIGKILL"]).toBe(1);
713
+ expect(metrics.errorsByType["task_failed"]).toBe(1);
714
+ expect(metrics.recentErrors).toHaveLength(2);
715
+ // Most recent first
716
+ expect(metrics.recentErrors[0].type).toBe("task_failed");
717
+ expect(metrics.recentErrors[1].type).toBe("SIGKILL");
718
+ });
719
+
720
+ it("metrics respect time window boundaries", () => {
721
+ const events: Event[] = [];
722
+ const now = Date.now();
723
+
724
+ // Event inside 10-second window
725
+ events.push({
726
+ id: "e1", type: "task", timestamp: now - 5000,
727
+ source: { agent_id: "a1" },
728
+ payload: { action: "completed", task_id: "t1" },
729
+ } as unknown as Event);
730
+ // Event outside 10-second window
731
+ events.push({
732
+ id: "e2", type: "task", timestamp: now - 30000,
733
+ source: { agent_id: "a1" },
734
+ payload: { action: "completed", task_id: "t2" },
735
+ } as unknown as Event);
736
+
737
+ const store = createMockEventStore(events);
738
+
739
+ const metrics = getThroughputMetrics(store, 10000);
740
+
741
+ expect(metrics.tasksCompleted).toBe(1); // Only the recent one
742
+ });
743
+ });
744
+
745
+ // =============================================================================
746
+ // Tests: Strategy Registry ↔ Team Config
747
+ // =============================================================================
748
+
749
+ describe("Strategy Registry ↔ Team Config", () => {
750
+ it("strategy registry can instantiate all strategies referenced by templates", async () => {
751
+ const roleRegistry = new DefaultRoleRegistry();
752
+
753
+ // Load self-driving template
754
+ const selfDriving = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
755
+ const sdStrategy = selfDriving.macro_agent.integration!.strategy;
756
+ expect(defaultStrategyRegistry.has(sdStrategy)).toBe(true);
757
+
758
+ // Load structured template
759
+ const structured = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
760
+ const stStrategy = structured.macro_agent.integration!.strategy;
761
+ expect(defaultStrategyRegistry.has(stStrategy)).toBe(true);
762
+
763
+ // Instantiate each
764
+ const trunkStrategy = defaultStrategyRegistry.get(sdStrategy, selfDriving.macro_agent.integration!.config);
765
+ expect(trunkStrategy.name).toBe("trunk");
766
+
767
+ const queueStrategy = defaultStrategyRegistry.get(stStrategy, structured.macro_agent.integration!.config);
768
+ expect(queueStrategy.name).toBe("queue");
769
+ });
770
+
771
+ it("custom strategy can be registered and resolved", () => {
772
+ defaultStrategyRegistry.register("custom-ci", (config) => ({
773
+ name: "custom-ci",
774
+ async land(request: LandRequest): Promise<LandResult> {
775
+ return { status: "landed", commitHash: "custom-hash" };
776
+ },
777
+ }));
778
+
779
+ expect(defaultStrategyRegistry.has("custom-ci")).toBe(true);
780
+ const strategy = defaultStrategyRegistry.get("custom-ci");
781
+ expect(strategy.name).toBe("custom-ci");
782
+ });
783
+ });
784
+
785
+ // =============================================================================
786
+ // Tests: Role Capability ↔ Tool Gating
787
+ // =============================================================================
788
+
789
+ describe("Role Capability ↔ Tool Gating", () => {
790
+ it("task.claim capability maps to all claim-related tools", () => {
791
+ const tools = CAPABILITY_TOOL_MAP[TASK_CAPABILITIES.CLAIM as keyof typeof CAPABILITY_TOOL_MAP];
792
+ expect(tools).toBeDefined();
793
+ expect(tools).toContain("claim_task");
794
+ expect(tools).toContain("unclaim_task");
795
+ expect(tools).toContain("list_claimable_tasks");
796
+ });
797
+
798
+ it("self-driving grinder role has task.claim capability", async () => {
799
+ const roleRegistry = new DefaultRoleRegistry();
800
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
801
+
802
+ const grinder = manifest._resolvedRoles.get("grinder");
803
+ expect(grinder!.capabilities).toContain("task.claim");
804
+
805
+ // Grinder inherits from worker — should still have worker capabilities
806
+ expect(grinder!.capabilities).toContain("file.read");
807
+ expect(grinder!.capabilities).toContain("file.write");
808
+ });
809
+
810
+ it("structured developer role does NOT have task.claim capability", async () => {
811
+ const roleRegistry = new DefaultRoleRegistry();
812
+ const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
813
+
814
+ const developer = manifest._resolvedRoles.get("developer");
815
+ expect(developer!.capabilities).not.toContain("task.claim");
816
+ });
817
+
818
+ it("registered team roles are resolvable from RoleRegistry", async () => {
819
+ const roleRegistry = new DefaultRoleRegistry();
820
+ const eventStore = createMockEventStore();
821
+ const messageRouter = createMockMessageRouter();
822
+ let capturedInterceptor: SpawnInterceptor | null = null;
823
+ const agentManager = {
824
+ spawn: vi.fn().mockResolvedValue({ id: "agent_0" }),
825
+ getRoleRegistry: () => roleRegistry,
826
+ setSpawnInterceptor: vi.fn((i: SpawnInterceptor | null) => { capturedInterceptor = i; }),
827
+ onLifecycleEvent: vi.fn(() => () => {}),
828
+ getChildren: vi.fn().mockReturnValue([]),
829
+ } as unknown as AgentManager;
830
+
831
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
832
+ const runtime = new TeamRuntime(manifest, {
833
+ agentManager,
834
+ messageRouter,
835
+ eventStore,
836
+ });
837
+
838
+ await runtime.initialize();
839
+
840
+ // After initialization, team roles should be in the registry
841
+ const planner = roleRegistry.resolveRole("planner");
842
+ expect(planner).toBeDefined();
843
+ expect(planner.capabilities).toContain("task.claim");
844
+
845
+ const grinder = roleRegistry.resolveRole("grinder");
846
+ expect(grinder).toBeDefined();
847
+ expect(grinder.capabilities).toContain("task.claim");
848
+ expect(grinder.capabilities).toContain("git.push");
849
+ });
850
+ });
851
+
852
+ // =============================================================================
853
+ // Tests: Team Config → Worker Done Pipeline (end-to-end wiring)
854
+ // =============================================================================
855
+
856
+ describe("Team Config → Worker Done Pipeline", () => {
857
+ it("full pipeline: team config flows through handler registry to strategy", async () => {
858
+ const messageRouter = createMockMessageRouter();
859
+ const agentManager = {
860
+ getChildren: vi.fn().mockReturnValue([]),
861
+ } as unknown as AgentManager;
862
+ const strategy = createMockStrategy("trunk");
863
+
864
+ // Build deps as they would be wired from MCPServices → DoneToolDeps → AllHandlerDeps
865
+ const allDeps: AllHandlerDeps = {
866
+ messageRouter,
867
+ agentManager,
868
+ integrationStrategy: strategy,
869
+ taskMode: "pull",
870
+ };
871
+
872
+ // Create registry (this is what createDoneHandler does internally)
873
+ const registry = createHandlerRegistry(allDeps);
874
+
875
+ // Dispatch as a "grinder" (extends worker)
876
+ const context: LifecycleContext = {
877
+ agentId: "grinder-42",
878
+ role: "worker", // resolved base role
879
+ taskId: "task-99",
880
+ workspacePath: "/workspace/grinder-42",
881
+ branch: "feature/task-99",
882
+ integrationBranch: "main",
883
+ streamId: "stream-main",
884
+ };
885
+ const args: DoneArgs = { status: "completed", summary: "Implemented task-99" };
886
+
887
+ const result = await dispatchDone(
888
+ context,
889
+ args,
890
+ createCleanupStatus(),
891
+ allDeps,
892
+ registry
893
+ );
894
+
895
+ // Strategy should have been called
896
+ expect(strategy.land).toHaveBeenCalledWith(
897
+ expect.objectContaining({
898
+ agentId: "grinder-42",
899
+ taskId: "task-99",
900
+ sourceBranch: "feature/task-99",
901
+ targetBranch: "main",
902
+ })
903
+ );
904
+
905
+ // Pull mode: should not terminate
906
+ expect(result.shouldTerminate).toBe(false);
907
+
908
+ // Integration signal emitted
909
+ expect(result.signalsEmitted).toContain("WORKER_DONE");
910
+ expect(result.signalsEmitted).toContain("WORKER_INTEGRATED");
911
+ });
912
+
913
+ it("no strategy and no queue: emits signal but warns about missing queue", async () => {
914
+ const messageRouter = createMockMessageRouter();
915
+ const agentManager = {
916
+ getChildren: vi.fn().mockReturnValue([]),
917
+ } as unknown as AgentManager;
918
+
919
+ const allDeps: AllHandlerDeps = {
920
+ messageRouter,
921
+ agentManager,
922
+ // No strategy, no mergeQueue
923
+ };
924
+
925
+ const context = createWorkerContext();
926
+ const args: DoneArgs = { status: "completed" };
927
+
928
+ const result = await dispatchDone(
929
+ context,
930
+ args,
931
+ createCleanupStatus(),
932
+ allDeps
933
+ );
934
+
935
+ expect(result.signalsEmitted).toContain("WORKER_DONE");
936
+ expect(result.signalsEmitted).toContain("MERGE_REQUEST");
937
+ // Should mention "no queue configured" in cleanup actions
938
+ expect(result.cleanupActions?.some((a) => a.includes("no queue configured"))).toBe(true);
939
+ });
940
+ });
941
+
942
+ // =============================================================================
943
+ // Tests: Communication Topology Validation
944
+ // =============================================================================
945
+
946
+ describe("Communication Topology Validation", () => {
947
+ it("rejects templates with unknown role in subscriptions", async () => {
948
+ // This tests the loader's validation — not a runtime test
949
+ const roleRegistry = new DefaultRoleRegistry();
950
+
951
+ // self-driving template is valid — should load without error
952
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
953
+
954
+ // Verify all subscription roles exist in the roles list
955
+ for (const roleName of Object.keys(manifest.communication.subscriptions ?? {})) {
956
+ expect(manifest.roles).toContain(roleName);
957
+ }
958
+
959
+ // Verify all emission roles exist in the roles list
960
+ for (const roleName of Object.keys(manifest.communication.emissions ?? {})) {
961
+ expect(manifest.roles).toContain(roleName);
962
+ }
963
+
964
+ // Verify all peer routing roles exist
965
+ for (const peer of manifest.communication.routing?.peers ?? []) {
966
+ expect(manifest.roles).toContain(peer.from);
967
+ expect(manifest.roles).toContain(peer.to);
968
+ }
969
+ });
970
+
971
+ it("all subscribed channels exist in channel definitions", async () => {
972
+ const roleRegistry = new DefaultRoleRegistry();
973
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
974
+
975
+ const channelNames = new Set(Object.keys(manifest.communication.channels ?? {}));
976
+
977
+ for (const [, subs] of Object.entries(manifest.communication.subscriptions ?? {})) {
978
+ for (const sub of subs) {
979
+ expect(channelNames.has(sub.channel)).toBe(true);
980
+ }
981
+ }
982
+ });
983
+ });