agent-relay 2.3.13 → 2.4.0

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 (1421) hide show
  1. package/README.md +42 -176
  2. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  3. package/bin/agent-relay-broker-darwin-x64 +0 -0
  4. package/bin/agent-relay-broker-linux-arm64 +0 -0
  5. package/bin/agent-relay-broker-linux-x64 +0 -0
  6. package/dist/index.cjs +50288 -63371
  7. package/dist/src/cli/bootstrap.d.ts +6 -0
  8. package/dist/src/cli/bootstrap.d.ts.map +1 -0
  9. package/dist/src/cli/bootstrap.js +109 -0
  10. package/dist/src/cli/bootstrap.js.map +1 -0
  11. package/dist/src/cli/commands/agent-management.d.ts +51 -0
  12. package/dist/src/cli/commands/agent-management.d.ts.map +1 -0
  13. package/dist/src/cli/commands/agent-management.js +290 -0
  14. package/dist/src/cli/commands/agent-management.js.map +1 -0
  15. package/dist/src/cli/commands/auth.d.ts +9 -0
  16. package/dist/src/cli/commands/auth.d.ts.map +1 -0
  17. package/dist/src/cli/commands/auth.js +33 -0
  18. package/dist/src/cli/commands/auth.js.map +1 -0
  19. package/dist/src/cli/commands/cloud.d.ts +18 -0
  20. package/dist/src/cli/commands/cloud.d.ts.map +1 -0
  21. package/dist/src/cli/commands/cloud.js +392 -0
  22. package/dist/src/cli/commands/cloud.js.map +1 -0
  23. package/dist/src/cli/commands/core.d.ts +107 -0
  24. package/dist/src/cli/commands/core.d.ts.map +1 -0
  25. package/dist/src/cli/commands/core.js +299 -0
  26. package/dist/src/cli/commands/core.js.map +1 -0
  27. package/dist/src/cli/commands/doctor.d.ts +1 -1
  28. package/dist/src/cli/commands/doctor.d.ts.map +1 -1
  29. package/dist/src/cli/commands/doctor.js +1 -485
  30. package/dist/src/cli/commands/doctor.js.map +1 -1
  31. package/dist/src/cli/commands/messaging.d.ts +61 -0
  32. package/dist/src/cli/commands/messaging.d.ts.map +1 -0
  33. package/dist/src/cli/commands/messaging.js +213 -0
  34. package/dist/src/cli/commands/messaging.js.map +1 -0
  35. package/dist/src/cli/commands/monitoring.d.ts +57 -0
  36. package/dist/src/cli/commands/monitoring.d.ts.map +1 -0
  37. package/dist/src/cli/commands/monitoring.js +350 -0
  38. package/dist/src/cli/commands/monitoring.js.map +1 -0
  39. package/dist/src/cli/commands/setup.d.ts +29 -0
  40. package/dist/src/cli/commands/setup.d.ts.map +1 -0
  41. package/dist/src/cli/commands/setup.js +324 -0
  42. package/dist/src/cli/commands/setup.js.map +1 -0
  43. package/dist/src/cli/commands/swarm.d.ts +3 -0
  44. package/dist/src/cli/commands/swarm.d.ts.map +1 -0
  45. package/dist/src/cli/commands/swarm.js +108 -0
  46. package/dist/src/cli/commands/swarm.js.map +1 -0
  47. package/dist/src/cli/index.d.ts +1 -28
  48. package/dist/src/cli/index.d.ts.map +1 -1
  49. package/dist/src/cli/index.js +4 -4936
  50. package/dist/src/cli/index.js.map +1 -1
  51. package/dist/src/cli/lib/agent-management-listing.d.ts +39 -0
  52. package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -0
  53. package/dist/src/cli/lib/agent-management-listing.js +253 -0
  54. package/dist/src/cli/lib/agent-management-listing.js.map +1 -0
  55. package/dist/src/cli/lib/auth-ssh.d.ts +48 -0
  56. package/dist/src/cli/lib/auth-ssh.d.ts.map +1 -0
  57. package/dist/src/cli/lib/auth-ssh.js +572 -0
  58. package/dist/src/cli/lib/auth-ssh.js.map +1 -0
  59. package/dist/src/cli/lib/bridge.d.ts +8 -0
  60. package/dist/src/cli/lib/bridge.d.ts.map +1 -0
  61. package/dist/src/cli/lib/bridge.js +100 -0
  62. package/dist/src/cli/lib/bridge.js.map +1 -0
  63. package/dist/src/cli/lib/broker-lifecycle.d.ts +20 -0
  64. package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -0
  65. package/dist/src/cli/lib/broker-lifecycle.js +843 -0
  66. package/dist/src/cli/lib/broker-lifecycle.js.map +1 -0
  67. package/dist/src/cli/lib/client-factory.d.ts +24 -0
  68. package/dist/src/cli/lib/client-factory.d.ts.map +1 -0
  69. package/dist/src/cli/lib/client-factory.js +20 -0
  70. package/dist/src/cli/lib/client-factory.js.map +1 -0
  71. package/dist/src/cli/lib/cloud-client.d.ts +39 -0
  72. package/dist/src/cli/lib/cloud-client.d.ts.map +1 -0
  73. package/dist/src/cli/lib/cloud-client.js +120 -0
  74. package/dist/src/cli/lib/cloud-client.js.map +1 -0
  75. package/dist/src/cli/lib/core-maintenance.d.ts +13 -0
  76. package/dist/src/cli/lib/core-maintenance.d.ts.map +1 -0
  77. package/dist/src/cli/lib/core-maintenance.js +250 -0
  78. package/dist/src/cli/lib/core-maintenance.js.map +1 -0
  79. package/dist/src/cli/lib/doctor.d.ts +2 -0
  80. package/dist/src/cli/lib/doctor.d.ts.map +1 -0
  81. package/dist/src/cli/lib/doctor.js +484 -0
  82. package/dist/src/cli/lib/doctor.js.map +1 -0
  83. package/dist/src/cli/lib/formatting.d.ts +8 -0
  84. package/dist/src/cli/lib/formatting.d.ts.map +1 -0
  85. package/dist/src/cli/lib/formatting.js +48 -0
  86. package/dist/src/cli/lib/formatting.js.map +1 -0
  87. package/dist/src/cli/lib/index.d.ts +5 -0
  88. package/dist/src/cli/lib/index.d.ts.map +1 -0
  89. package/dist/src/cli/lib/index.js +5 -0
  90. package/dist/src/cli/lib/index.js.map +1 -0
  91. package/dist/src/cli/lib/jsonc.d.ts +8 -0
  92. package/dist/src/cli/lib/jsonc.d.ts.map +1 -0
  93. package/dist/src/cli/lib/jsonc.js +88 -0
  94. package/dist/src/cli/lib/jsonc.js.map +1 -0
  95. package/dist/src/cli/lib/monitoring-health.d.ts +32 -0
  96. package/dist/src/cli/lib/monitoring-health.d.ts.map +1 -0
  97. package/dist/src/cli/lib/monitoring-health.js +2 -0
  98. package/dist/src/cli/lib/monitoring-health.js.map +1 -0
  99. package/dist/src/cli/lib/paths.d.ts +2 -0
  100. package/dist/src/cli/lib/paths.d.ts.map +1 -0
  101. package/dist/src/cli/lib/paths.js +5 -0
  102. package/dist/src/cli/lib/paths.js.map +1 -0
  103. package/dist/src/index.d.ts +1 -5
  104. package/dist/src/index.d.ts.map +1 -1
  105. package/dist/src/index.js +1 -5
  106. package/dist/src/index.js.map +1 -1
  107. package/install.sh +0 -30
  108. package/package.json +84 -98
  109. package/packages/acp-bridge/README.md +8 -8
  110. package/packages/acp-bridge/dist/acp-agent.d.ts +8 -7
  111. package/packages/acp-bridge/dist/acp-agent.d.ts.map +1 -1
  112. package/packages/acp-bridge/dist/acp-agent.js +118 -143
  113. package/packages/acp-bridge/dist/acp-agent.js.map +1 -1
  114. package/packages/acp-bridge/dist/cli.d.ts +1 -1
  115. package/packages/acp-bridge/dist/cli.js +3 -3
  116. package/packages/acp-bridge/dist/index.d.ts +2 -2
  117. package/packages/acp-bridge/dist/index.d.ts.map +1 -1
  118. package/packages/acp-bridge/dist/index.js +1 -1
  119. package/packages/acp-bridge/dist/index.js.map +1 -1
  120. package/packages/acp-bridge/dist/types.d.ts +3 -3
  121. package/packages/acp-bridge/package.json +3 -3
  122. package/packages/acp-bridge/src/acp-agent.ts +123 -160
  123. package/packages/acp-bridge/src/cli.ts +3 -3
  124. package/packages/acp-bridge/src/index.ts +2 -2
  125. package/packages/acp-bridge/src/types.ts +3 -3
  126. package/packages/config/dist/bridge-config.d.ts +5 -5
  127. package/packages/config/dist/bridge-config.d.ts.map +1 -1
  128. package/packages/config/dist/bridge-config.js +16 -9
  129. package/packages/config/dist/bridge-config.js.map +1 -1
  130. package/packages/config/dist/cli-auth-config.d.ts +1 -1
  131. package/packages/config/dist/cli-auth-config.js +1 -1
  132. package/packages/config/dist/cli-registry.generated.d.ts +340 -0
  133. package/packages/config/dist/cli-registry.generated.d.ts.map +1 -0
  134. package/packages/config/dist/cli-registry.generated.js +297 -0
  135. package/packages/config/dist/cli-registry.generated.js.map +1 -0
  136. package/packages/config/dist/index.d.ts +1 -0
  137. package/packages/config/dist/index.d.ts.map +1 -1
  138. package/packages/config/dist/index.js +1 -0
  139. package/packages/config/dist/index.js.map +1 -1
  140. package/packages/config/dist/project-namespace.d.ts +9 -9
  141. package/packages/config/dist/project-namespace.js +3 -3
  142. package/packages/config/dist/relay-config.d.ts +1 -1
  143. package/packages/config/dist/relay-config.js +1 -1
  144. package/packages/config/dist/schemas.js +1 -1
  145. package/packages/config/dist/shadow-config.d.ts +2 -1
  146. package/packages/config/dist/shadow-config.d.ts.map +1 -1
  147. package/packages/config/dist/shadow-config.js.map +1 -1
  148. package/packages/config/package.json +2 -3
  149. package/packages/config/src/bridge-config.test.ts +20 -6
  150. package/packages/config/src/bridge-config.ts +20 -10
  151. package/packages/config/src/cli-auth-config.ts +1 -1
  152. package/packages/config/src/cli-registry.generated.ts +328 -0
  153. package/packages/config/src/index.ts +1 -0
  154. package/packages/config/src/project-namespace.ts +9 -9
  155. package/packages/config/src/relay-config.ts +1 -1
  156. package/packages/config/src/schemas.ts +1 -1
  157. package/packages/config/src/shadow-config.ts +8 -1
  158. package/packages/contracts/fixtures/error-fixtures.json +42 -0
  159. package/packages/contracts/fixtures/event-fixtures.json +161 -0
  160. package/packages/contracts/fixtures/health-fixtures.json +35 -0
  161. package/packages/contracts/fixtures/identity-fixtures.json +58 -0
  162. package/packages/contracts/fixtures/replay-fixtures.json +33 -0
  163. package/packages/hooks/dist/inbox-check/types.d.ts +2 -2
  164. package/packages/hooks/dist/types.d.ts +9 -3
  165. package/packages/hooks/dist/types.d.ts.map +1 -1
  166. package/packages/hooks/dist/types.js +1 -1
  167. package/packages/hooks/dist/types.js.map +1 -1
  168. package/packages/hooks/package.json +5 -5
  169. package/packages/hooks/src/inbox-check/types.ts +2 -2
  170. package/packages/hooks/src/types.ts +11 -3
  171. package/packages/memory/package.json +2 -2
  172. package/packages/policy/package.json +2 -2
  173. package/packages/sdk/.mcp.json +14 -0
  174. package/packages/sdk/.trajectories/active/traj_1771875803391_84ca57b2.json +50 -0
  175. package/packages/sdk/.trajectories/active/traj_1771891934534_06504121.json +50 -0
  176. package/packages/sdk/.trajectories/active/traj_1771891957929_211afc4e.json +50 -0
  177. package/packages/sdk/.trajectories/active/traj_1771891982509_38c84638.json +50 -0
  178. package/packages/sdk/.trajectories/completed/traj_1771875803188_cd6d181c.json +80 -0
  179. package/packages/sdk/.trajectories/completed/traj_1771875803204_f2aeb8c8.json +80 -0
  180. package/packages/sdk/.trajectories/completed/traj_1771875803210_d65f3f1a.json +80 -0
  181. package/packages/sdk/.trajectories/completed/traj_1771875803218_e454a25d.json +80 -0
  182. package/packages/sdk/.trajectories/completed/traj_1771875803223_d7a64815.json +80 -0
  183. package/packages/sdk/.trajectories/completed/traj_1771875803227_7e56da5b.json +80 -0
  184. package/packages/sdk/.trajectories/completed/traj_1771875803235_4fbf93b4.json +80 -0
  185. package/packages/sdk/.trajectories/completed/traj_1771875803243_47931c71.json +80 -0
  186. package/packages/sdk/.trajectories/completed/traj_1771875803258_3816f3fe.json +80 -0
  187. package/packages/sdk/.trajectories/completed/traj_1771875803268_8061140e.json +80 -0
  188. package/packages/sdk/.trajectories/completed/traj_1771875803326_ae6f9c78.json +80 -0
  189. package/packages/sdk/.trajectories/completed/traj_1771875808396_cbde0a6c.json +91 -0
  190. package/packages/sdk/.trajectories/completed/traj_1771875812026_aa2442bb.json +91 -0
  191. package/packages/sdk/.trajectories/completed/traj_1771875815431_c2c656c5.json +91 -0
  192. package/packages/sdk/.trajectories/completed/traj_1771875818645_3a4dbf02.json +91 -0
  193. package/packages/sdk/.trajectories/completed/traj_1771891934403_24923c03.json +80 -0
  194. package/packages/sdk/.trajectories/completed/traj_1771891934421_dca16e24.json +80 -0
  195. package/packages/sdk/.trajectories/completed/traj_1771891934430_057706f7.json +80 -0
  196. package/packages/sdk/.trajectories/completed/traj_1771891934442_faf97382.json +80 -0
  197. package/packages/sdk/.trajectories/completed/traj_1771891934454_5542ecd5.json +80 -0
  198. package/packages/sdk/.trajectories/completed/traj_1771891934464_12202a08.json +80 -0
  199. package/packages/sdk/.trajectories/completed/traj_1771891934487_94378275.json +80 -0
  200. package/packages/sdk/.trajectories/completed/traj_1771891934503_ca728c13.json +80 -0
  201. package/packages/sdk/.trajectories/completed/traj_1771891934519_100af69a.json +80 -0
  202. package/packages/sdk/.trajectories/completed/traj_1771891934536_62ad39d9.json +80 -0
  203. package/packages/sdk/.trajectories/completed/traj_1771891934553_d6798a52.json +80 -0
  204. package/packages/sdk/.trajectories/completed/traj_1771891939537_541c8096.json +91 -0
  205. package/packages/sdk/.trajectories/completed/traj_1771891942985_36ab9a4d.json +91 -0
  206. package/packages/sdk/.trajectories/completed/traj_1771891946453_e8a6e05f.json +91 -0
  207. package/packages/sdk/.trajectories/completed/traj_1771891949838_5de0de84.json +91 -0
  208. package/packages/sdk/.trajectories/completed/traj_1771891957807_0ecfb4f4.json +80 -0
  209. package/packages/sdk/.trajectories/completed/traj_1771891957827_c4539239.json +80 -0
  210. package/packages/sdk/.trajectories/completed/traj_1771891957836_91168b48.json +80 -0
  211. package/packages/sdk/.trajectories/completed/traj_1771891957848_8c5cad0b.json +80 -0
  212. package/packages/sdk/.trajectories/completed/traj_1771891957857_0986b293.json +80 -0
  213. package/packages/sdk/.trajectories/completed/traj_1771891957872_8a3113af.json +80 -0
  214. package/packages/sdk/.trajectories/completed/traj_1771891957884_0bb85208.json +80 -0
  215. package/packages/sdk/.trajectories/completed/traj_1771891957892_86c75e2e.json +80 -0
  216. package/packages/sdk/.trajectories/completed/traj_1771891957907_98ca0e6f.json +80 -0
  217. package/packages/sdk/.trajectories/completed/traj_1771891957918_d9091231.json +80 -0
  218. package/packages/sdk/.trajectories/completed/traj_1771891957931_dcaf77ed.json +80 -0
  219. package/packages/sdk/.trajectories/completed/traj_1771891962931_eb1fdee2.json +91 -0
  220. package/packages/sdk/.trajectories/completed/traj_1771891966262_9061a93f.json +91 -0
  221. package/packages/sdk/.trajectories/completed/traj_1771891969915_1adaba19.json +91 -0
  222. package/packages/sdk/.trajectories/completed/traj_1771891973588_f08b79e9.json +91 -0
  223. package/packages/sdk/.trajectories/completed/traj_1771891982421_f1985bce.json +80 -0
  224. package/packages/sdk/.trajectories/completed/traj_1771891982432_e7a84163.json +80 -0
  225. package/packages/sdk/.trajectories/completed/traj_1771891982447_369b842a.json +80 -0
  226. package/packages/sdk/.trajectories/completed/traj_1771891982469_5fc45199.json +80 -0
  227. package/packages/sdk/.trajectories/completed/traj_1771891982495_454c7cb3.json +80 -0
  228. package/packages/sdk/.trajectories/completed/traj_1771891982514_08098e03.json +80 -0
  229. package/packages/sdk/.trajectories/completed/traj_1771891982526_b351d778.json +80 -0
  230. package/packages/sdk/.trajectories/completed/traj_1771891982533_fa542d83.json +80 -0
  231. package/packages/sdk/.trajectories/completed/traj_1771891982540_18ab24dc.json +80 -0
  232. package/packages/sdk/.trajectories/completed/traj_1771891982544_5b4fa163.json +80 -0
  233. package/packages/sdk/.trajectories/completed/traj_1771891982548_c13f089a.json +80 -0
  234. package/packages/sdk/.trajectories/completed/traj_1771891987510_23f6da1f.json +91 -0
  235. package/packages/sdk/.trajectories/completed/traj_1771891991466_912c2e04.json +91 -0
  236. package/packages/sdk/.trajectories/completed/traj_1771891994891_60604be2.json +91 -0
  237. package/packages/sdk/.trajectories/completed/traj_1771891998370_cfaf9b8b.json +91 -0
  238. package/packages/sdk/README.md +68 -838
  239. package/packages/sdk/bin/agent-relay-broker +0 -0
  240. package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +2 -0
  241. package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +1 -0
  242. package/packages/sdk/dist/__tests__/contract-fixtures.test.js +85 -0
  243. package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +1 -0
  244. package/packages/sdk/dist/__tests__/facade.test.js +305 -0
  245. package/packages/sdk/dist/__tests__/facade.test.js.map +1 -0
  246. package/packages/sdk/dist/__tests__/integration.test.js +169 -0
  247. package/packages/sdk/dist/__tests__/integration.test.js.map +1 -0
  248. package/packages/sdk/dist/__tests__/pty.test.d.ts +2 -0
  249. package/packages/sdk/dist/__tests__/pty.test.d.ts.map +1 -0
  250. package/packages/sdk/dist/__tests__/pty.test.js +20 -0
  251. package/packages/sdk/dist/__tests__/pty.test.js.map +1 -0
  252. package/packages/sdk/dist/__tests__/quickstart.test.js +176 -0
  253. package/packages/sdk/dist/__tests__/quickstart.test.js.map +1 -0
  254. package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +2 -0
  255. package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +1 -0
  256. package/packages/sdk/dist/__tests__/spawn-from-env.test.js +206 -0
  257. package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +1 -0
  258. package/packages/sdk/dist/__tests__/unit.test.js +347 -0
  259. package/packages/sdk/dist/__tests__/unit.test.js.map +1 -0
  260. package/packages/sdk/dist/client.d.ts +140 -526
  261. package/packages/sdk/dist/client.d.ts.map +1 -1
  262. package/packages/sdk/dist/client.js +416 -1509
  263. package/packages/sdk/dist/client.js.map +1 -1
  264. package/packages/sdk/dist/examples/workflow-superiority.d.ts +32 -0
  265. package/packages/sdk/dist/examples/workflow-superiority.d.ts.map +1 -0
  266. package/packages/sdk/dist/examples/workflow-superiority.js +1421 -0
  267. package/packages/sdk/dist/examples/workflow-superiority.js.map +1 -0
  268. package/packages/sdk/dist/index.d.ts +13 -20
  269. package/packages/sdk/dist/index.d.ts.map +1 -1
  270. package/packages/sdk/dist/index.js +12 -26
  271. package/packages/sdk/dist/index.js.map +1 -1
  272. package/packages/sdk/dist/logs.d.ts +70 -25
  273. package/packages/sdk/dist/logs.d.ts.map +1 -1
  274. package/packages/sdk/dist/logs.js +238 -42
  275. package/packages/sdk/dist/logs.js.map +1 -1
  276. package/packages/sdk/dist/models.d.ts +9 -0
  277. package/packages/sdk/dist/models.d.ts.map +1 -0
  278. package/packages/sdk/dist/models.js +17 -0
  279. package/packages/sdk/dist/models.js.map +1 -0
  280. package/packages/sdk/dist/protocol.d.ts +366 -0
  281. package/packages/sdk/dist/protocol.d.ts.map +1 -0
  282. package/packages/sdk/dist/pty.d.ts.map +1 -0
  283. package/packages/sdk/dist/pty.js +26 -0
  284. package/packages/sdk/dist/pty.js.map +1 -0
  285. package/packages/sdk/dist/relay-adapter.d.ts +139 -0
  286. package/packages/sdk/dist/relay-adapter.d.ts.map +1 -0
  287. package/packages/sdk/dist/relay-adapter.js +210 -0
  288. package/packages/sdk/dist/relay-adapter.js.map +1 -0
  289. package/packages/sdk/dist/relay.d.ts +304 -0
  290. package/packages/sdk/dist/relay.d.ts.map +1 -0
  291. package/packages/sdk/dist/relay.js +910 -0
  292. package/packages/sdk/dist/relay.js.map +1 -0
  293. package/packages/sdk/dist/shadow.d.ts +101 -0
  294. package/packages/sdk/dist/shadow.d.ts.map +1 -0
  295. package/packages/sdk/dist/shadow.js.map +1 -0
  296. package/packages/sdk/dist/spawn-from-env.d.ts +77 -0
  297. package/packages/sdk/dist/spawn-from-env.d.ts.map +1 -0
  298. package/packages/sdk/dist/spawn-from-env.js +172 -0
  299. package/packages/sdk/dist/spawn-from-env.js.map +1 -0
  300. package/packages/sdk/dist/workflows/builder.d.ts +114 -0
  301. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -0
  302. package/packages/sdk/dist/workflows/builder.js +201 -0
  303. package/packages/sdk/dist/workflows/builder.js.map +1 -0
  304. package/packages/sdk/dist/workflows/cli.d.ts +11 -0
  305. package/packages/sdk/dist/workflows/cli.d.ts.map +1 -0
  306. package/packages/sdk/dist/workflows/cli.js +144 -0
  307. package/packages/sdk/dist/workflows/cli.js.map +1 -0
  308. package/packages/sdk/dist/workflows/coordinator.d.ts +73 -0
  309. package/packages/sdk/dist/workflows/coordinator.d.ts.map +1 -0
  310. package/packages/sdk/dist/workflows/coordinator.js +647 -0
  311. package/packages/sdk/dist/workflows/coordinator.js.map +1 -0
  312. package/packages/sdk/dist/workflows/custom-steps.d.ts +73 -0
  313. package/packages/sdk/dist/workflows/custom-steps.d.ts.map +1 -0
  314. package/packages/sdk/dist/workflows/custom-steps.js +321 -0
  315. package/packages/sdk/dist/workflows/custom-steps.js.map +1 -0
  316. package/packages/sdk/dist/workflows/dry-run-format.d.ts +6 -0
  317. package/packages/sdk/dist/workflows/dry-run-format.d.ts.map +1 -0
  318. package/packages/sdk/dist/workflows/dry-run-format.js +68 -0
  319. package/packages/sdk/dist/workflows/dry-run-format.js.map +1 -0
  320. package/packages/sdk/dist/workflows/file-db.d.ts +33 -0
  321. package/packages/sdk/dist/workflows/file-db.d.ts.map +1 -0
  322. package/packages/sdk/dist/workflows/file-db.js +108 -0
  323. package/packages/sdk/dist/workflows/file-db.js.map +1 -0
  324. package/packages/sdk/dist/workflows/index.d.ts +15 -0
  325. package/packages/sdk/dist/workflows/index.d.ts.map +1 -0
  326. package/packages/sdk/dist/workflows/index.js +15 -0
  327. package/packages/sdk/dist/workflows/index.js.map +1 -0
  328. package/packages/sdk/dist/workflows/run.d.ts +38 -0
  329. package/packages/sdk/dist/workflows/run.d.ts.map +1 -0
  330. package/packages/sdk/dist/workflows/run.js +25 -0
  331. package/packages/sdk/dist/workflows/run.js.map +1 -0
  332. package/packages/sdk/dist/workflows/runner.d.ts +320 -0
  333. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -0
  334. package/packages/sdk/dist/workflows/runner.js +2821 -0
  335. package/packages/sdk/dist/workflows/runner.js.map +1 -0
  336. package/packages/sdk/dist/workflows/templates.d.ts +47 -0
  337. package/packages/sdk/dist/workflows/templates.d.ts.map +1 -0
  338. package/packages/sdk/dist/workflows/templates.js +405 -0
  339. package/packages/sdk/dist/workflows/templates.js.map +1 -0
  340. package/packages/sdk/dist/workflows/trajectory.d.ts +87 -0
  341. package/packages/sdk/dist/workflows/trajectory.d.ts.map +1 -0
  342. package/packages/sdk/dist/workflows/trajectory.js +441 -0
  343. package/packages/sdk/dist/workflows/trajectory.js.map +1 -0
  344. package/packages/sdk/dist/workflows/types.d.ts +306 -0
  345. package/packages/sdk/dist/workflows/types.d.ts.map +1 -0
  346. package/packages/sdk/dist/workflows/types.js +23 -0
  347. package/packages/sdk/dist/workflows/types.js.map +1 -0
  348. package/packages/sdk/dist/workflows/validator.d.ts +11 -0
  349. package/packages/sdk/dist/workflows/validator.d.ts.map +1 -0
  350. package/packages/sdk/dist/workflows/validator.js +128 -0
  351. package/packages/sdk/dist/workflows/validator.js.map +1 -0
  352. package/packages/sdk/package.json +59 -53
  353. package/packages/sdk/scripts/bundle-agent-relay.mjs +53 -0
  354. package/packages/sdk/src/__tests__/contract-fixtures.test.ts +122 -0
  355. package/packages/sdk/src/__tests__/error-scenarios.test.ts +682 -0
  356. package/packages/sdk/src/__tests__/facade.test.ts +364 -0
  357. package/packages/sdk/src/__tests__/idle-nudge.test.ts +438 -0
  358. package/packages/sdk/src/__tests__/integration.test.ts +204 -0
  359. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +797 -0
  360. package/packages/sdk/src/__tests__/pty.test.ts +24 -0
  361. package/packages/sdk/src/__tests__/quickstart.test.ts +198 -0
  362. package/packages/sdk/src/__tests__/spawn-from-env.test.ts +282 -0
  363. package/packages/sdk/src/__tests__/swarm-coordinator.test.ts +909 -0
  364. package/packages/sdk/src/__tests__/unit.test.ts +435 -0
  365. package/packages/sdk/src/__tests__/workflow-runner.test.ts +489 -0
  366. package/packages/sdk/src/__tests__/yaml-validation.test.ts +890 -0
  367. package/packages/sdk/src/client.ts +514 -1912
  368. package/packages/sdk/src/examples/workflow-superiority.ts +1485 -0
  369. package/packages/sdk/src/examples/workflows/README.md +156 -0
  370. package/packages/sdk/src/examples/workflows/ralph-overnight.yaml +421 -0
  371. package/packages/sdk/src/examples/workflows/ralph-swarm.yaml +411 -0
  372. package/packages/sdk/src/examples/workflows/ralph-tdd.yaml +259 -0
  373. package/packages/sdk/src/index.ts +13 -116
  374. package/packages/sdk/src/logs.ts +282 -54
  375. package/packages/sdk/src/models.ts +36 -0
  376. package/packages/sdk/src/protocol.ts +385 -0
  377. package/packages/sdk/src/pty.ts +35 -0
  378. package/packages/sdk/src/relay-adapter.ts +316 -0
  379. package/packages/sdk/src/relay.ts +1147 -0
  380. package/packages/sdk/src/shadow.ts +228 -0
  381. package/packages/sdk/src/spawn-from-env.ts +245 -0
  382. package/packages/sdk/src/workflows/README.md +656 -0
  383. package/packages/sdk/src/workflows/builder.ts +278 -0
  384. package/packages/sdk/src/workflows/builtin-templates/bug-fix.yaml +135 -0
  385. package/packages/sdk/src/workflows/builtin-templates/code-review.yaml +133 -0
  386. package/packages/sdk/src/workflows/builtin-templates/competitive.yaml +103 -0
  387. package/packages/sdk/src/workflows/builtin-templates/documentation.yaml +120 -0
  388. package/packages/sdk/src/workflows/builtin-templates/feature-dev.yaml +142 -0
  389. package/packages/sdk/src/workflows/builtin-templates/refactor.yaml +141 -0
  390. package/packages/sdk/src/workflows/builtin-templates/review-loop.yaml +223 -0
  391. package/packages/sdk/src/workflows/builtin-templates/security-audit.yaml +129 -0
  392. package/packages/sdk/src/workflows/cli.ts +162 -0
  393. package/packages/sdk/src/workflows/coordinator.ts +842 -0
  394. package/packages/sdk/src/workflows/custom-steps.ts +450 -0
  395. package/packages/sdk/src/workflows/dry-run-format.ts +75 -0
  396. package/packages/sdk/src/workflows/file-db.ts +117 -0
  397. package/packages/sdk/src/workflows/index.ts +24 -0
  398. package/packages/sdk/src/workflows/run.ts +72 -0
  399. package/packages/sdk/src/workflows/runner.ts +3409 -0
  400. package/packages/sdk/src/workflows/schema.json +651 -0
  401. package/packages/sdk/src/workflows/templates.ts +552 -0
  402. package/packages/sdk/src/workflows/trajectory.ts +631 -0
  403. package/packages/sdk/src/workflows/types.ts +389 -0
  404. package/packages/sdk/src/workflows/validator.ts +151 -0
  405. package/packages/sdk/tsconfig.build.json +25 -0
  406. package/packages/sdk/tsconfig.json +17 -18
  407. package/packages/sdk/vitest.config.ts +1 -1
  408. package/packages/sdk-py/README.md +106 -21
  409. package/packages/sdk-py/agent_relay/__init__.py +21 -0
  410. package/packages/sdk-py/agent_relay/models.py +206 -0
  411. package/packages/sdk-py/pyproject.toml +2 -2
  412. package/packages/sdk-py/src/agent_relay/__init__.py +76 -0
  413. package/packages/sdk-py/src/agent_relay/builder.py +430 -109
  414. package/packages/sdk-py/src/agent_relay/templates.py +197 -0
  415. package/packages/sdk-py/src/agent_relay/types.py +489 -15
  416. package/packages/sdk-py/tests/test_builder.py +115 -1
  417. package/packages/sdk-py/tests/test_workflow_templates.py +450 -0
  418. package/packages/shared/cli-registry.yaml +193 -0
  419. package/packages/shared/codegen-py.mjs +215 -0
  420. package/packages/shared/codegen-ts.mjs +227 -0
  421. package/packages/telemetry/dist/events.d.ts +8 -8
  422. package/packages/telemetry/dist/index.d.ts +1 -1
  423. package/packages/telemetry/package.json +2 -2
  424. package/packages/telemetry/src/events.ts +9 -9
  425. package/packages/telemetry/src/index.ts +2 -2
  426. package/packages/trajectory/package.json +2 -2
  427. package/packages/user-directory/dist/user-directory.js +1 -1
  428. package/packages/user-directory/dist/user-directory.js.map +1 -1
  429. package/packages/user-directory/package.json +2 -2
  430. package/packages/user-directory/src/user-directory.ts +1 -1
  431. package/packages/utils/dist/cjs/client-helpers.js +4 -4
  432. package/packages/utils/dist/cjs/discovery.js +9 -6
  433. package/packages/utils/dist/cjs/errors.js +5 -5
  434. package/packages/utils/dist/cjs/legacy-protocol.js +70 -0
  435. package/packages/utils/dist/cjs/logger.js +3 -3
  436. package/packages/utils/dist/cjs/precompiled-patterns.js +33 -2
  437. package/packages/utils/dist/cjs/relay-pty-path.js +0 -6
  438. package/packages/utils/dist/client-helpers.d.ts +1 -1
  439. package/packages/utils/dist/client-helpers.d.ts.map +1 -1
  440. package/packages/utils/dist/client-helpers.js +1 -1
  441. package/packages/utils/dist/client-helpers.js.map +1 -1
  442. package/packages/utils/dist/discovery.d.ts +7 -7
  443. package/packages/utils/dist/discovery.d.ts.map +1 -1
  444. package/packages/utils/dist/discovery.js +20 -17
  445. package/packages/utils/dist/discovery.js.map +1 -1
  446. package/packages/utils/dist/errors.d.ts +1 -1
  447. package/packages/utils/dist/errors.js +3 -3
  448. package/packages/utils/dist/legacy-protocol.d.ts +46 -0
  449. package/packages/utils/dist/legacy-protocol.d.ts.map +1 -0
  450. package/packages/utils/dist/legacy-protocol.js +47 -0
  451. package/packages/utils/dist/legacy-protocol.js.map +1 -0
  452. package/packages/utils/dist/logger.d.ts +2 -2
  453. package/packages/utils/dist/logger.js +2 -2
  454. package/packages/utils/dist/precompiled-patterns.d.ts.map +1 -1
  455. package/packages/utils/dist/precompiled-patterns.js +28 -2
  456. package/packages/utils/dist/precompiled-patterns.js.map +1 -1
  457. package/packages/utils/dist/relay-pty-path.d.ts.map +1 -1
  458. package/packages/utils/dist/relay-pty-path.js +1 -10
  459. package/packages/utils/dist/relay-pty-path.js.map +1 -1
  460. package/packages/utils/package.json +2 -3
  461. package/packages/utils/src/client-helpers.ts +1 -1
  462. package/packages/utils/src/consolidation.test.ts +3 -3
  463. package/packages/utils/src/discovery.test.ts +3 -3
  464. package/packages/utils/src/discovery.ts +21 -18
  465. package/packages/utils/src/errors.test.ts +6 -11
  466. package/packages/utils/src/errors.ts +3 -3
  467. package/packages/utils/src/legacy-protocol.ts +151 -0
  468. package/packages/utils/src/logger.ts +2 -2
  469. package/packages/utils/src/precompiled-patterns.test.ts +8 -0
  470. package/packages/utils/src/precompiled-patterns.ts +40 -2
  471. package/packages/utils/src/relay-pty-path.test.ts +23 -34
  472. package/packages/utils/src/relay-pty-path.ts +1 -11
  473. package/relay-snippets/agent-relay-protocol.md +6 -43
  474. package/relay-snippets/agent-relay-snippet.md +59 -203
  475. package/scripts/postinstall.js +44 -171
  476. package/bin/relay-pty-darwin-arm64 +0 -0
  477. package/bin/relay-pty-darwin-x64 +0 -0
  478. package/bin/relay-pty-linux-arm64 +0 -0
  479. package/bin/relay-pty-linux-x64 +0 -0
  480. package/dist/src/bridge/index.d.ts +0 -8
  481. package/dist/src/bridge/index.d.ts.map +0 -1
  482. package/dist/src/bridge/index.js +0 -8
  483. package/dist/src/bridge/index.js.map +0 -1
  484. package/dist/src/continuity/index.d.ts +0 -5
  485. package/dist/src/continuity/index.d.ts.map +0 -1
  486. package/dist/src/continuity/index.js +0 -5
  487. package/dist/src/continuity/index.js.map +0 -1
  488. package/dist/src/daemon/index.d.ts +0 -8
  489. package/dist/src/daemon/index.d.ts.map +0 -1
  490. package/dist/src/daemon/index.js +0 -9
  491. package/dist/src/daemon/index.js.map +0 -1
  492. package/dist/src/protocol/index.d.ts +0 -8
  493. package/dist/src/protocol/index.d.ts.map +0 -1
  494. package/dist/src/protocol/index.js +0 -8
  495. package/dist/src/protocol/index.js.map +0 -1
  496. package/dist/src/resiliency/index.d.ts +0 -5
  497. package/dist/src/resiliency/index.d.ts.map +0 -1
  498. package/dist/src/resiliency/index.js +0 -5
  499. package/dist/src/resiliency/index.js.map +0 -1
  500. package/dist/src/state/index.d.ts +0 -5
  501. package/dist/src/state/index.d.ts.map +0 -1
  502. package/dist/src/state/index.js +0 -5
  503. package/dist/src/state/index.js.map +0 -1
  504. package/dist/src/storage/index.d.ts +0 -8
  505. package/dist/src/storage/index.d.ts.map +0 -1
  506. package/dist/src/storage/index.js +0 -8
  507. package/dist/src/storage/index.js.map +0 -1
  508. package/dist/src/wrapper/index.d.ts +0 -8
  509. package/dist/src/wrapper/index.d.ts.map +0 -1
  510. package/dist/src/wrapper/index.js +0 -11
  511. package/dist/src/wrapper/index.js.map +0 -1
  512. package/packages/bridge/dist/cli-resolution.d.ts +0 -32
  513. package/packages/bridge/dist/cli-resolution.d.ts.map +0 -1
  514. package/packages/bridge/dist/cli-resolution.js +0 -88
  515. package/packages/bridge/dist/cli-resolution.js.map +0 -1
  516. package/packages/bridge/dist/index.d.ts +0 -9
  517. package/packages/bridge/dist/index.d.ts.map +0 -1
  518. package/packages/bridge/dist/index.js +0 -11
  519. package/packages/bridge/dist/index.js.map +0 -1
  520. package/packages/bridge/dist/multi-project-client.d.ts +0 -99
  521. package/packages/bridge/dist/multi-project-client.d.ts.map +0 -1
  522. package/packages/bridge/dist/multi-project-client.js +0 -389
  523. package/packages/bridge/dist/multi-project-client.js.map +0 -1
  524. package/packages/bridge/dist/shadow-cli.d.ts +0 -17
  525. package/packages/bridge/dist/shadow-cli.d.ts.map +0 -1
  526. package/packages/bridge/dist/shadow-cli.js +0 -75
  527. package/packages/bridge/dist/shadow-cli.js.map +0 -1
  528. package/packages/bridge/dist/spawner.d.ts +0 -263
  529. package/packages/bridge/dist/spawner.d.ts.map +0 -1
  530. package/packages/bridge/dist/spawner.js +0 -1758
  531. package/packages/bridge/dist/spawner.js.map +0 -1
  532. package/packages/bridge/dist/types.d.ts +0 -141
  533. package/packages/bridge/dist/types.d.ts.map +0 -1
  534. package/packages/bridge/dist/types.js +0 -6
  535. package/packages/bridge/dist/types.js.map +0 -1
  536. package/packages/bridge/dist/utils.d.ts +0 -39
  537. package/packages/bridge/dist/utils.d.ts.map +0 -1
  538. package/packages/bridge/dist/utils.js +0 -98
  539. package/packages/bridge/dist/utils.js.map +0 -1
  540. package/packages/bridge/package.json +0 -45
  541. package/packages/bridge/src/cli-resolution.test.ts +0 -225
  542. package/packages/bridge/src/cli-resolution.ts +0 -100
  543. package/packages/bridge/src/index.ts +0 -34
  544. package/packages/bridge/src/multi-project-client.test.ts +0 -340
  545. package/packages/bridge/src/multi-project-client.ts +0 -469
  546. package/packages/bridge/src/shadow-cli.ts +0 -95
  547. package/packages/bridge/src/spawner-mcp.test.ts +0 -505
  548. package/packages/bridge/src/spawner.ts +0 -2067
  549. package/packages/bridge/src/types.ts +0 -153
  550. package/packages/bridge/src/utils.test.ts +0 -235
  551. package/packages/bridge/src/utils.ts +0 -113
  552. package/packages/bridge/tsconfig.json +0 -29
  553. package/packages/bridge/vitest.config.ts +0 -9
  554. package/packages/broker-sdk/README.md +0 -97
  555. package/packages/broker-sdk/dist/__tests__/facade.test.js +0 -257
  556. package/packages/broker-sdk/dist/__tests__/facade.test.js.map +0 -1
  557. package/packages/broker-sdk/dist/__tests__/integration.test.js +0 -139
  558. package/packages/broker-sdk/dist/__tests__/integration.test.js.map +0 -1
  559. package/packages/broker-sdk/dist/__tests__/quickstart.test.js +0 -176
  560. package/packages/broker-sdk/dist/__tests__/quickstart.test.js.map +0 -1
  561. package/packages/broker-sdk/dist/__tests__/unit.test.js +0 -192
  562. package/packages/broker-sdk/dist/__tests__/unit.test.js.map +0 -1
  563. package/packages/broker-sdk/dist/client.d.ts +0 -95
  564. package/packages/broker-sdk/dist/client.d.ts.map +0 -1
  565. package/packages/broker-sdk/dist/client.js +0 -372
  566. package/packages/broker-sdk/dist/client.js.map +0 -1
  567. package/packages/broker-sdk/dist/index.d.ts +0 -10
  568. package/packages/broker-sdk/dist/index.d.ts.map +0 -1
  569. package/packages/broker-sdk/dist/index.js +0 -10
  570. package/packages/broker-sdk/dist/index.js.map +0 -1
  571. package/packages/broker-sdk/dist/logs.d.ts +0 -47
  572. package/packages/broker-sdk/dist/logs.d.ts.map +0 -1
  573. package/packages/broker-sdk/dist/logs.js +0 -137
  574. package/packages/broker-sdk/dist/logs.js.map +0 -1
  575. package/packages/broker-sdk/dist/protocol.d.ts +0 -254
  576. package/packages/broker-sdk/dist/protocol.d.ts.map +0 -1
  577. package/packages/broker-sdk/dist/pty.d.ts.map +0 -1
  578. package/packages/broker-sdk/dist/pty.js +0 -14
  579. package/packages/broker-sdk/dist/pty.js.map +0 -1
  580. package/packages/broker-sdk/dist/relay.d.ts +0 -172
  581. package/packages/broker-sdk/dist/relay.d.ts.map +0 -1
  582. package/packages/broker-sdk/dist/relay.js +0 -486
  583. package/packages/broker-sdk/dist/relay.js.map +0 -1
  584. package/packages/broker-sdk/dist/relaycast.d.ts +0 -67
  585. package/packages/broker-sdk/dist/relaycast.d.ts.map +0 -1
  586. package/packages/broker-sdk/dist/relaycast.js +0 -150
  587. package/packages/broker-sdk/dist/relaycast.js.map +0 -1
  588. package/packages/broker-sdk/dist/shadow.d.ts +0 -100
  589. package/packages/broker-sdk/dist/shadow.d.ts.map +0 -1
  590. package/packages/broker-sdk/dist/shadow.js.map +0 -1
  591. package/packages/broker-sdk/dist/workflows/builder.d.ts +0 -101
  592. package/packages/broker-sdk/dist/workflows/builder.d.ts.map +0 -1
  593. package/packages/broker-sdk/dist/workflows/builder.js +0 -179
  594. package/packages/broker-sdk/dist/workflows/builder.js.map +0 -1
  595. package/packages/broker-sdk/dist/workflows/cli.d.ts +0 -10
  596. package/packages/broker-sdk/dist/workflows/cli.d.ts.map +0 -1
  597. package/packages/broker-sdk/dist/workflows/cli.js +0 -82
  598. package/packages/broker-sdk/dist/workflows/cli.js.map +0 -1
  599. package/packages/broker-sdk/dist/workflows/coordinator.d.ts +0 -69
  600. package/packages/broker-sdk/dist/workflows/coordinator.d.ts.map +0 -1
  601. package/packages/broker-sdk/dist/workflows/coordinator.js +0 -585
  602. package/packages/broker-sdk/dist/workflows/coordinator.js.map +0 -1
  603. package/packages/broker-sdk/dist/workflows/index.d.ts +0 -11
  604. package/packages/broker-sdk/dist/workflows/index.d.ts.map +0 -1
  605. package/packages/broker-sdk/dist/workflows/index.js +0 -11
  606. package/packages/broker-sdk/dist/workflows/index.js.map +0 -1
  607. package/packages/broker-sdk/dist/workflows/run.d.ts +0 -33
  608. package/packages/broker-sdk/dist/workflows/run.d.ts.map +0 -1
  609. package/packages/broker-sdk/dist/workflows/run.js +0 -28
  610. package/packages/broker-sdk/dist/workflows/run.js.map +0 -1
  611. package/packages/broker-sdk/dist/workflows/runner.d.ts +0 -136
  612. package/packages/broker-sdk/dist/workflows/runner.d.ts.map +0 -1
  613. package/packages/broker-sdk/dist/workflows/runner.js +0 -900
  614. package/packages/broker-sdk/dist/workflows/runner.js.map +0 -1
  615. package/packages/broker-sdk/dist/workflows/templates.d.ts +0 -47
  616. package/packages/broker-sdk/dist/workflows/templates.d.ts.map +0 -1
  617. package/packages/broker-sdk/dist/workflows/templates.js +0 -395
  618. package/packages/broker-sdk/dist/workflows/templates.js.map +0 -1
  619. package/packages/broker-sdk/dist/workflows/trajectory.d.ts +0 -80
  620. package/packages/broker-sdk/dist/workflows/trajectory.d.ts.map +0 -1
  621. package/packages/broker-sdk/dist/workflows/trajectory.js +0 -362
  622. package/packages/broker-sdk/dist/workflows/trajectory.js.map +0 -1
  623. package/packages/broker-sdk/dist/workflows/types.d.ts +0 -140
  624. package/packages/broker-sdk/dist/workflows/types.d.ts.map +0 -1
  625. package/packages/broker-sdk/dist/workflows/types.js +0 -8
  626. package/packages/broker-sdk/dist/workflows/types.js.map +0 -1
  627. package/packages/broker-sdk/package.json +0 -81
  628. package/packages/broker-sdk/scripts/bundle-agent-relay.mjs +0 -53
  629. package/packages/broker-sdk/src/__tests__/error-scenarios.test.ts +0 -682
  630. package/packages/broker-sdk/src/__tests__/facade.test.ts +0 -296
  631. package/packages/broker-sdk/src/__tests__/integration.test.ts +0 -170
  632. package/packages/broker-sdk/src/__tests__/quickstart.test.ts +0 -198
  633. package/packages/broker-sdk/src/__tests__/swarm-coordinator.test.ts +0 -772
  634. package/packages/broker-sdk/src/__tests__/unit.test.ts +0 -243
  635. package/packages/broker-sdk/src/__tests__/workflow-runner.test.ts +0 -333
  636. package/packages/broker-sdk/src/client.ts +0 -510
  637. package/packages/broker-sdk/src/index.ts +0 -9
  638. package/packages/broker-sdk/src/logs.ts +0 -163
  639. package/packages/broker-sdk/src/protocol.ts +0 -271
  640. package/packages/broker-sdk/src/pty.ts +0 -16
  641. package/packages/broker-sdk/src/relay.ts +0 -614
  642. package/packages/broker-sdk/src/relaycast.ts +0 -185
  643. package/packages/broker-sdk/src/shadow.ts +0 -230
  644. package/packages/broker-sdk/src/workflows/README.md +0 -514
  645. package/packages/broker-sdk/src/workflows/builder.ts +0 -241
  646. package/packages/broker-sdk/src/workflows/builtin-templates/bug-fix.yaml +0 -75
  647. package/packages/broker-sdk/src/workflows/builtin-templates/code-review.yaml +0 -82
  648. package/packages/broker-sdk/src/workflows/builtin-templates/documentation.yaml +0 -70
  649. package/packages/broker-sdk/src/workflows/builtin-templates/feature-dev.yaml +0 -76
  650. package/packages/broker-sdk/src/workflows/builtin-templates/refactor.yaml +0 -82
  651. package/packages/broker-sdk/src/workflows/builtin-templates/security-audit.yaml +0 -84
  652. package/packages/broker-sdk/src/workflows/cli.ts +0 -93
  653. package/packages/broker-sdk/src/workflows/coordinator.ts +0 -758
  654. package/packages/broker-sdk/src/workflows/index.ts +0 -10
  655. package/packages/broker-sdk/src/workflows/run.ts +0 -55
  656. package/packages/broker-sdk/src/workflows/runner.ts +0 -1184
  657. package/packages/broker-sdk/src/workflows/schema.json +0 -333
  658. package/packages/broker-sdk/src/workflows/templates.ts +0 -544
  659. package/packages/broker-sdk/src/workflows/trajectory.ts +0 -507
  660. package/packages/broker-sdk/src/workflows/types.ts +0 -208
  661. package/packages/broker-sdk/tsconfig.json +0 -22
  662. package/packages/broker-sdk/vitest.config.ts +0 -9
  663. package/packages/continuity/dist/formatter.d.ts +0 -57
  664. package/packages/continuity/dist/formatter.d.ts.map +0 -1
  665. package/packages/continuity/dist/formatter.js +0 -448
  666. package/packages/continuity/dist/formatter.js.map +0 -1
  667. package/packages/continuity/dist/handoff-store.d.ts +0 -67
  668. package/packages/continuity/dist/handoff-store.d.ts.map +0 -1
  669. package/packages/continuity/dist/handoff-store.js +0 -472
  670. package/packages/continuity/dist/handoff-store.js.map +0 -1
  671. package/packages/continuity/dist/index.d.ts +0 -10
  672. package/packages/continuity/dist/index.d.ts.map +0 -1
  673. package/packages/continuity/dist/index.js +0 -11
  674. package/packages/continuity/dist/index.js.map +0 -1
  675. package/packages/continuity/dist/ledger-store.d.ts +0 -110
  676. package/packages/continuity/dist/ledger-store.d.ts.map +0 -1
  677. package/packages/continuity/dist/ledger-store.js +0 -500
  678. package/packages/continuity/dist/ledger-store.js.map +0 -1
  679. package/packages/continuity/dist/manager.d.ts +0 -183
  680. package/packages/continuity/dist/manager.d.ts.map +0 -1
  681. package/packages/continuity/dist/manager.js +0 -616
  682. package/packages/continuity/dist/manager.js.map +0 -1
  683. package/packages/continuity/dist/parser.d.ts +0 -76
  684. package/packages/continuity/dist/parser.d.ts.map +0 -1
  685. package/packages/continuity/dist/parser.js +0 -579
  686. package/packages/continuity/dist/parser.js.map +0 -1
  687. package/packages/continuity/dist/types.d.ts +0 -180
  688. package/packages/continuity/dist/types.d.ts.map +0 -1
  689. package/packages/continuity/dist/types.js +0 -2
  690. package/packages/continuity/dist/types.js.map +0 -1
  691. package/packages/continuity/package.json +0 -40
  692. package/packages/continuity/src/formatter.ts +0 -536
  693. package/packages/continuity/src/handoff-store.ts +0 -523
  694. package/packages/continuity/src/index.ts +0 -12
  695. package/packages/continuity/src/ledger-store.ts +0 -594
  696. package/packages/continuity/src/manager.test.ts +0 -291
  697. package/packages/continuity/src/manager.ts +0 -774
  698. package/packages/continuity/src/parser.test.ts +0 -292
  699. package/packages/continuity/src/parser.ts +0 -680
  700. package/packages/continuity/src/types.ts +0 -211
  701. package/packages/continuity/tsconfig.json +0 -21
  702. package/packages/continuity/vitest.config.ts +0 -9
  703. package/packages/daemon/dist/agent-manager.d.ts +0 -134
  704. package/packages/daemon/dist/agent-manager.d.ts.map +0 -1
  705. package/packages/daemon/dist/agent-manager.js +0 -578
  706. package/packages/daemon/dist/agent-manager.js.map +0 -1
  707. package/packages/daemon/dist/agent-registry.d.ts +0 -99
  708. package/packages/daemon/dist/agent-registry.d.ts.map +0 -1
  709. package/packages/daemon/dist/agent-registry.js +0 -213
  710. package/packages/daemon/dist/agent-registry.js.map +0 -1
  711. package/packages/daemon/dist/agent-signing.d.ts +0 -158
  712. package/packages/daemon/dist/agent-signing.d.ts.map +0 -1
  713. package/packages/daemon/dist/agent-signing.js +0 -523
  714. package/packages/daemon/dist/agent-signing.js.map +0 -1
  715. package/packages/daemon/dist/api.d.ts +0 -106
  716. package/packages/daemon/dist/api.d.ts.map +0 -1
  717. package/packages/daemon/dist/api.js +0 -895
  718. package/packages/daemon/dist/api.js.map +0 -1
  719. package/packages/daemon/dist/auth.d.ts +0 -94
  720. package/packages/daemon/dist/auth.d.ts.map +0 -1
  721. package/packages/daemon/dist/auth.js +0 -197
  722. package/packages/daemon/dist/auth.js.map +0 -1
  723. package/packages/daemon/dist/channel-membership-store.d.ts +0 -55
  724. package/packages/daemon/dist/channel-membership-store.d.ts.map +0 -1
  725. package/packages/daemon/dist/channel-membership-store.js +0 -176
  726. package/packages/daemon/dist/channel-membership-store.js.map +0 -1
  727. package/packages/daemon/dist/cli-auth.d.ts +0 -97
  728. package/packages/daemon/dist/cli-auth.d.ts.map +0 -1
  729. package/packages/daemon/dist/cli-auth.js +0 -808
  730. package/packages/daemon/dist/cli-auth.js.map +0 -1
  731. package/packages/daemon/dist/cloud-sync.d.ts +0 -263
  732. package/packages/daemon/dist/cloud-sync.d.ts.map +0 -1
  733. package/packages/daemon/dist/cloud-sync.js +0 -820
  734. package/packages/daemon/dist/cloud-sync.js.map +0 -1
  735. package/packages/daemon/dist/connection.d.ts +0 -137
  736. package/packages/daemon/dist/connection.d.ts.map +0 -1
  737. package/packages/daemon/dist/connection.js +0 -465
  738. package/packages/daemon/dist/connection.js.map +0 -1
  739. package/packages/daemon/dist/consensus-integration.d.ts +0 -168
  740. package/packages/daemon/dist/consensus-integration.d.ts.map +0 -1
  741. package/packages/daemon/dist/consensus-integration.js +0 -371
  742. package/packages/daemon/dist/consensus-integration.js.map +0 -1
  743. package/packages/daemon/dist/consensus.d.ts +0 -269
  744. package/packages/daemon/dist/consensus.d.ts.map +0 -1
  745. package/packages/daemon/dist/consensus.js +0 -632
  746. package/packages/daemon/dist/consensus.js.map +0 -1
  747. package/packages/daemon/dist/delivery-tracker.d.ts +0 -34
  748. package/packages/daemon/dist/delivery-tracker.d.ts.map +0 -1
  749. package/packages/daemon/dist/delivery-tracker.js +0 -104
  750. package/packages/daemon/dist/delivery-tracker.js.map +0 -1
  751. package/packages/daemon/dist/enhanced-features.d.ts +0 -118
  752. package/packages/daemon/dist/enhanced-features.d.ts.map +0 -1
  753. package/packages/daemon/dist/enhanced-features.js +0 -177
  754. package/packages/daemon/dist/enhanced-features.js.map +0 -1
  755. package/packages/daemon/dist/index.d.ts +0 -29
  756. package/packages/daemon/dist/index.d.ts.map +0 -1
  757. package/packages/daemon/dist/index.js +0 -34
  758. package/packages/daemon/dist/index.js.map +0 -1
  759. package/packages/daemon/dist/orchestrator.d.ts +0 -217
  760. package/packages/daemon/dist/orchestrator.d.ts.map +0 -1
  761. package/packages/daemon/dist/orchestrator.js +0 -1172
  762. package/packages/daemon/dist/orchestrator.js.map +0 -1
  763. package/packages/daemon/dist/rate-limiter.d.ts +0 -68
  764. package/packages/daemon/dist/rate-limiter.d.ts.map +0 -1
  765. package/packages/daemon/dist/rate-limiter.js +0 -130
  766. package/packages/daemon/dist/rate-limiter.js.map +0 -1
  767. package/packages/daemon/dist/registry.d.ts +0 -9
  768. package/packages/daemon/dist/registry.d.ts.map +0 -1
  769. package/packages/daemon/dist/registry.js +0 -9
  770. package/packages/daemon/dist/registry.js.map +0 -1
  771. package/packages/daemon/dist/repo-manager.d.ts +0 -116
  772. package/packages/daemon/dist/repo-manager.d.ts.map +0 -1
  773. package/packages/daemon/dist/repo-manager.js +0 -384
  774. package/packages/daemon/dist/repo-manager.js.map +0 -1
  775. package/packages/daemon/dist/router.d.ts +0 -389
  776. package/packages/daemon/dist/router.d.ts.map +0 -1
  777. package/packages/daemon/dist/router.js +0 -1607
  778. package/packages/daemon/dist/router.js.map +0 -1
  779. package/packages/daemon/dist/server.d.ts +0 -201
  780. package/packages/daemon/dist/server.d.ts.map +0 -1
  781. package/packages/daemon/dist/server.js +0 -1791
  782. package/packages/daemon/dist/server.js.map +0 -1
  783. package/packages/daemon/dist/spawn-manager.d.ts +0 -119
  784. package/packages/daemon/dist/spawn-manager.d.ts.map +0 -1
  785. package/packages/daemon/dist/spawn-manager.js +0 -319
  786. package/packages/daemon/dist/spawn-manager.js.map +0 -1
  787. package/packages/daemon/dist/sync-queue.d.ts +0 -116
  788. package/packages/daemon/dist/sync-queue.d.ts.map +0 -1
  789. package/packages/daemon/dist/sync-queue.js +0 -361
  790. package/packages/daemon/dist/sync-queue.js.map +0 -1
  791. package/packages/daemon/dist/types.d.ts +0 -133
  792. package/packages/daemon/dist/types.d.ts.map +0 -1
  793. package/packages/daemon/dist/types.js +0 -6
  794. package/packages/daemon/dist/types.js.map +0 -1
  795. package/packages/daemon/dist/workspace-manager.d.ts +0 -80
  796. package/packages/daemon/dist/workspace-manager.d.ts.map +0 -1
  797. package/packages/daemon/dist/workspace-manager.js +0 -314
  798. package/packages/daemon/dist/workspace-manager.js.map +0 -1
  799. package/packages/daemon/package.json +0 -56
  800. package/packages/daemon/src/agent-manager.ts +0 -679
  801. package/packages/daemon/src/agent-registry.ts +0 -284
  802. package/packages/daemon/src/agent-signing.ts +0 -707
  803. package/packages/daemon/src/api.ts +0 -1034
  804. package/packages/daemon/src/auth.ts +0 -276
  805. package/packages/daemon/src/channel-membership-store.ts +0 -217
  806. package/packages/daemon/src/cli-auth.ts +0 -945
  807. package/packages/daemon/src/cloud-sync.ts +0 -1100
  808. package/packages/daemon/src/connection.ts +0 -561
  809. package/packages/daemon/src/consensus-integration.ts +0 -510
  810. package/packages/daemon/src/consensus.ts +0 -848
  811. package/packages/daemon/src/delivery-tracker.ts +0 -145
  812. package/packages/daemon/src/enhanced-features.ts +0 -390
  813. package/packages/daemon/src/index.ts +0 -48
  814. package/packages/daemon/src/orchestrator.test.ts +0 -231
  815. package/packages/daemon/src/orchestrator.ts +0 -1376
  816. package/packages/daemon/src/rate-limiter.ts +0 -172
  817. package/packages/daemon/src/registry.ts +0 -8
  818. package/packages/daemon/src/repo-manager.ts +0 -468
  819. package/packages/daemon/src/router.test.ts +0 -181
  820. package/packages/daemon/src/router.ts +0 -1925
  821. package/packages/daemon/src/server.ts +0 -2051
  822. package/packages/daemon/src/spawn-manager-set-model.test.ts +0 -144
  823. package/packages/daemon/src/spawn-manager.ts +0 -415
  824. package/packages/daemon/src/sync-queue.ts +0 -477
  825. package/packages/daemon/src/types.ts +0 -158
  826. package/packages/daemon/src/workspace-manager.ts +0 -371
  827. package/packages/daemon/tsconfig.json +0 -21
  828. package/packages/daemon/vitest.config.ts +0 -9
  829. package/packages/mcp/CHANGELOG.md +0 -28
  830. package/packages/mcp/LICENSE +0 -190
  831. package/packages/mcp/README.md +0 -266
  832. package/packages/mcp/dist/bin.d.ts +0 -12
  833. package/packages/mcp/dist/bin.d.ts.map +0 -1
  834. package/packages/mcp/dist/bin.js +0 -179
  835. package/packages/mcp/dist/bin.js.map +0 -1
  836. package/packages/mcp/dist/client-adapter.d.ts +0 -164
  837. package/packages/mcp/dist/client-adapter.d.ts.map +0 -1
  838. package/packages/mcp/dist/client-adapter.js +0 -231
  839. package/packages/mcp/dist/client-adapter.js.map +0 -1
  840. package/packages/mcp/dist/cloud.d.ts +0 -13
  841. package/packages/mcp/dist/cloud.d.ts.map +0 -1
  842. package/packages/mcp/dist/cloud.js +0 -25
  843. package/packages/mcp/dist/cloud.js.map +0 -1
  844. package/packages/mcp/dist/errors.d.ts +0 -9
  845. package/packages/mcp/dist/errors.d.ts.map +0 -1
  846. package/packages/mcp/dist/errors.js +0 -9
  847. package/packages/mcp/dist/errors.js.map +0 -1
  848. package/packages/mcp/dist/file-transport.d.ts +0 -103
  849. package/packages/mcp/dist/file-transport.d.ts.map +0 -1
  850. package/packages/mcp/dist/file-transport.js +0 -204
  851. package/packages/mcp/dist/file-transport.js.map +0 -1
  852. package/packages/mcp/dist/hybrid-client.d.ts +0 -5
  853. package/packages/mcp/dist/hybrid-client.d.ts.map +0 -1
  854. package/packages/mcp/dist/hybrid-client.js +0 -23
  855. package/packages/mcp/dist/hybrid-client.js.map +0 -1
  856. package/packages/mcp/dist/index.d.ts +0 -11
  857. package/packages/mcp/dist/index.d.ts.map +0 -1
  858. package/packages/mcp/dist/index.js +0 -25
  859. package/packages/mcp/dist/index.js.map +0 -1
  860. package/packages/mcp/dist/install-cli.d.ts +0 -35
  861. package/packages/mcp/dist/install-cli.d.ts.map +0 -1
  862. package/packages/mcp/dist/install-cli.js +0 -157
  863. package/packages/mcp/dist/install-cli.js.map +0 -1
  864. package/packages/mcp/dist/install.d.ts +0 -123
  865. package/packages/mcp/dist/install.d.ts.map +0 -1
  866. package/packages/mcp/dist/install.js +0 -661
  867. package/packages/mcp/dist/install.js.map +0 -1
  868. package/packages/mcp/dist/prompts/index.d.ts +0 -2
  869. package/packages/mcp/dist/prompts/index.d.ts.map +0 -1
  870. package/packages/mcp/dist/prompts/index.js +0 -2
  871. package/packages/mcp/dist/prompts/index.js.map +0 -1
  872. package/packages/mcp/dist/prompts/protocol.d.ts +0 -11
  873. package/packages/mcp/dist/prompts/protocol.d.ts.map +0 -1
  874. package/packages/mcp/dist/prompts/protocol.js +0 -160
  875. package/packages/mcp/dist/prompts/protocol.js.map +0 -1
  876. package/packages/mcp/dist/resources/agents.d.ts +0 -11
  877. package/packages/mcp/dist/resources/agents.d.ts.map +0 -1
  878. package/packages/mcp/dist/resources/agents.js +0 -17
  879. package/packages/mcp/dist/resources/agents.js.map +0 -1
  880. package/packages/mcp/dist/resources/inbox.d.ts +0 -11
  881. package/packages/mcp/dist/resources/inbox.d.ts.map +0 -1
  882. package/packages/mcp/dist/resources/inbox.js +0 -17
  883. package/packages/mcp/dist/resources/inbox.js.map +0 -1
  884. package/packages/mcp/dist/resources/index.d.ts +0 -4
  885. package/packages/mcp/dist/resources/index.d.ts.map +0 -1
  886. package/packages/mcp/dist/resources/index.js +0 -4
  887. package/packages/mcp/dist/resources/index.js.map +0 -1
  888. package/packages/mcp/dist/resources/project.d.ts +0 -11
  889. package/packages/mcp/dist/resources/project.d.ts.map +0 -1
  890. package/packages/mcp/dist/resources/project.js +0 -21
  891. package/packages/mcp/dist/resources/project.js.map +0 -1
  892. package/packages/mcp/dist/server.d.ts +0 -23
  893. package/packages/mcp/dist/server.d.ts.map +0 -1
  894. package/packages/mcp/dist/server.js +0 -317
  895. package/packages/mcp/dist/server.js.map +0 -1
  896. package/packages/mcp/dist/simple.d.ts +0 -170
  897. package/packages/mcp/dist/simple.d.ts.map +0 -1
  898. package/packages/mcp/dist/simple.js +0 -120
  899. package/packages/mcp/dist/simple.js.map +0 -1
  900. package/packages/mcp/dist/tools/index.d.ts +0 -20
  901. package/packages/mcp/dist/tools/index.d.ts.map +0 -1
  902. package/packages/mcp/dist/tools/index.js +0 -20
  903. package/packages/mcp/dist/tools/index.js.map +0 -1
  904. package/packages/mcp/dist/tools/relay-broadcast.d.ts +0 -20
  905. package/packages/mcp/dist/tools/relay-broadcast.d.ts.map +0 -1
  906. package/packages/mcp/dist/tools/relay-broadcast.js +0 -25
  907. package/packages/mcp/dist/tools/relay-broadcast.js.map +0 -1
  908. package/packages/mcp/dist/tools/relay-channel.d.ts +0 -75
  909. package/packages/mcp/dist/tools/relay-channel.d.ts.map +0 -1
  910. package/packages/mcp/dist/tools/relay-channel.js +0 -124
  911. package/packages/mcp/dist/tools/relay-channel.js.map +0 -1
  912. package/packages/mcp/dist/tools/relay-connected.d.ts +0 -17
  913. package/packages/mcp/dist/tools/relay-connected.d.ts.map +0 -1
  914. package/packages/mcp/dist/tools/relay-connected.js +0 -54
  915. package/packages/mcp/dist/tools/relay-connected.js.map +0 -1
  916. package/packages/mcp/dist/tools/relay-consensus.d.ts +0 -45
  917. package/packages/mcp/dist/tools/relay-consensus.d.ts.map +0 -1
  918. package/packages/mcp/dist/tools/relay-consensus.js +0 -80
  919. package/packages/mcp/dist/tools/relay-consensus.js.map +0 -1
  920. package/packages/mcp/dist/tools/relay-continuity.d.ts +0 -35
  921. package/packages/mcp/dist/tools/relay-continuity.d.ts.map +0 -1
  922. package/packages/mcp/dist/tools/relay-continuity.js +0 -101
  923. package/packages/mcp/dist/tools/relay-continuity.js.map +0 -1
  924. package/packages/mcp/dist/tools/relay-health.d.ts +0 -20
  925. package/packages/mcp/dist/tools/relay-health.d.ts.map +0 -1
  926. package/packages/mcp/dist/tools/relay-health.js +0 -130
  927. package/packages/mcp/dist/tools/relay-health.js.map +0 -1
  928. package/packages/mcp/dist/tools/relay-inbox.d.ts +0 -26
  929. package/packages/mcp/dist/tools/relay-inbox.d.ts.map +0 -1
  930. package/packages/mcp/dist/tools/relay-inbox.js +0 -58
  931. package/packages/mcp/dist/tools/relay-inbox.js.map +0 -1
  932. package/packages/mcp/dist/tools/relay-logs.d.ts +0 -20
  933. package/packages/mcp/dist/tools/relay-logs.d.ts.map +0 -1
  934. package/packages/mcp/dist/tools/relay-logs.js +0 -90
  935. package/packages/mcp/dist/tools/relay-logs.js.map +0 -1
  936. package/packages/mcp/dist/tools/relay-messages.d.ts +0 -32
  937. package/packages/mcp/dist/tools/relay-messages.d.ts.map +0 -1
  938. package/packages/mcp/dist/tools/relay-messages.js +0 -61
  939. package/packages/mcp/dist/tools/relay-messages.js.map +0 -1
  940. package/packages/mcp/dist/tools/relay-metrics.d.ts +0 -17
  941. package/packages/mcp/dist/tools/relay-metrics.d.ts.map +0 -1
  942. package/packages/mcp/dist/tools/relay-metrics.js +0 -124
  943. package/packages/mcp/dist/tools/relay-metrics.js.map +0 -1
  944. package/packages/mcp/dist/tools/relay-release.d.ts +0 -20
  945. package/packages/mcp/dist/tools/relay-release.d.ts.map +0 -1
  946. package/packages/mcp/dist/tools/relay-release.js +0 -44
  947. package/packages/mcp/dist/tools/relay-release.js.map +0 -1
  948. package/packages/mcp/dist/tools/relay-remove-agent.d.ts +0 -20
  949. package/packages/mcp/dist/tools/relay-remove-agent.d.ts.map +0 -1
  950. package/packages/mcp/dist/tools/relay-remove-agent.js +0 -50
  951. package/packages/mcp/dist/tools/relay-remove-agent.js.map +0 -1
  952. package/packages/mcp/dist/tools/relay-send.d.ts +0 -29
  953. package/packages/mcp/dist/tools/relay-send.d.ts.map +0 -1
  954. package/packages/mcp/dist/tools/relay-send.js +0 -73
  955. package/packages/mcp/dist/tools/relay-send.js.map +0 -1
  956. package/packages/mcp/dist/tools/relay-set-model.d.ts +0 -23
  957. package/packages/mcp/dist/tools/relay-set-model.d.ts.map +0 -1
  958. package/packages/mcp/dist/tools/relay-set-model.js +0 -52
  959. package/packages/mcp/dist/tools/relay-set-model.js.map +0 -1
  960. package/packages/mcp/dist/tools/relay-shadow.d.ts +0 -30
  961. package/packages/mcp/dist/tools/relay-shadow.d.ts.map +0 -1
  962. package/packages/mcp/dist/tools/relay-shadow.js +0 -55
  963. package/packages/mcp/dist/tools/relay-shadow.js.map +0 -1
  964. package/packages/mcp/dist/tools/relay-spawn.d.ts +0 -36
  965. package/packages/mcp/dist/tools/relay-spawn.d.ts.map +0 -1
  966. package/packages/mcp/dist/tools/relay-spawn.js +0 -73
  967. package/packages/mcp/dist/tools/relay-spawn.js.map +0 -1
  968. package/packages/mcp/dist/tools/relay-status.d.ts +0 -11
  969. package/packages/mcp/dist/tools/relay-status.d.ts.map +0 -1
  970. package/packages/mcp/dist/tools/relay-status.js +0 -43
  971. package/packages/mcp/dist/tools/relay-status.js.map +0 -1
  972. package/packages/mcp/dist/tools/relay-subscribe.d.ts +0 -27
  973. package/packages/mcp/dist/tools/relay-subscribe.d.ts.map +0 -1
  974. package/packages/mcp/dist/tools/relay-subscribe.js +0 -49
  975. package/packages/mcp/dist/tools/relay-subscribe.js.map +0 -1
  976. package/packages/mcp/dist/tools/relay-who.d.ts +0 -20
  977. package/packages/mcp/dist/tools/relay-who.d.ts.map +0 -1
  978. package/packages/mcp/dist/tools/relay-who.js +0 -62
  979. package/packages/mcp/dist/tools/relay-who.js.map +0 -1
  980. package/packages/mcp/package.json +0 -82
  981. package/packages/mcp/src/bin.ts +0 -200
  982. package/packages/mcp/src/client-adapter.ts +0 -358
  983. package/packages/mcp/src/cloud.ts +0 -41
  984. package/packages/mcp/src/errors.ts +0 -17
  985. package/packages/mcp/src/file-transport.ts +0 -275
  986. package/packages/mcp/src/hybrid-client.ts +0 -25
  987. package/packages/mcp/src/index.ts +0 -143
  988. package/packages/mcp/src/install-cli.ts +0 -210
  989. package/packages/mcp/src/install.ts +0 -820
  990. package/packages/mcp/src/prompts/index.ts +0 -1
  991. package/packages/mcp/src/prompts/protocol.ts +0 -164
  992. package/packages/mcp/src/resources/agents.ts +0 -21
  993. package/packages/mcp/src/resources/inbox.ts +0 -21
  994. package/packages/mcp/src/resources/index.ts +0 -3
  995. package/packages/mcp/src/resources/project.ts +0 -29
  996. package/packages/mcp/src/server.ts +0 -475
  997. package/packages/mcp/src/simple.ts +0 -214
  998. package/packages/mcp/src/tools/index.ts +0 -155
  999. package/packages/mcp/src/tools/relay-broadcast.ts +0 -32
  1000. package/packages/mcp/src/tools/relay-channel.ts +0 -151
  1001. package/packages/mcp/src/tools/relay-connected.ts +0 -67
  1002. package/packages/mcp/src/tools/relay-consensus.ts +0 -92
  1003. package/packages/mcp/src/tools/relay-continuity.ts +0 -127
  1004. package/packages/mcp/src/tools/relay-health.ts +0 -148
  1005. package/packages/mcp/src/tools/relay-inbox.ts +0 -70
  1006. package/packages/mcp/src/tools/relay-logs.ts +0 -106
  1007. package/packages/mcp/src/tools/relay-messages.ts +0 -66
  1008. package/packages/mcp/src/tools/relay-metrics.ts +0 -142
  1009. package/packages/mcp/src/tools/relay-release.ts +0 -54
  1010. package/packages/mcp/src/tools/relay-remove-agent.ts +0 -58
  1011. package/packages/mcp/src/tools/relay-send.ts +0 -84
  1012. package/packages/mcp/src/tools/relay-set-model.ts +0 -62
  1013. package/packages/mcp/src/tools/relay-shadow.ts +0 -67
  1014. package/packages/mcp/src/tools/relay-spawn.ts +0 -87
  1015. package/packages/mcp/src/tools/relay-status.ts +0 -57
  1016. package/packages/mcp/src/tools/relay-subscribe.ts +0 -61
  1017. package/packages/mcp/src/tools/relay-who.ts +0 -75
  1018. package/packages/mcp/tests/client.test.ts +0 -451
  1019. package/packages/mcp/tests/discover.test.ts +0 -256
  1020. package/packages/mcp/tests/install.test.ts +0 -123
  1021. package/packages/mcp/tests/prompts.test.ts +0 -12
  1022. package/packages/mcp/tests/resources.test.ts +0 -53
  1023. package/packages/mcp/tests/tools.test.ts +0 -1516
  1024. package/packages/mcp/tsconfig.json +0 -22
  1025. package/packages/mcp/vitest.config.ts +0 -9
  1026. package/packages/protocol/dist/channels.d.ts +0 -137
  1027. package/packages/protocol/dist/channels.d.ts.map +0 -1
  1028. package/packages/protocol/dist/channels.js +0 -154
  1029. package/packages/protocol/dist/channels.js.map +0 -1
  1030. package/packages/protocol/dist/framing.d.ts +0 -80
  1031. package/packages/protocol/dist/framing.d.ts.map +0 -1
  1032. package/packages/protocol/dist/framing.js +0 -206
  1033. package/packages/protocol/dist/framing.js.map +0 -1
  1034. package/packages/protocol/dist/id-generator.d.ts +0 -35
  1035. package/packages/protocol/dist/id-generator.d.ts.map +0 -1
  1036. package/packages/protocol/dist/id-generator.js +0 -60
  1037. package/packages/protocol/dist/id-generator.js.map +0 -1
  1038. package/packages/protocol/dist/index.d.ts +0 -5
  1039. package/packages/protocol/dist/index.d.ts.map +0 -1
  1040. package/packages/protocol/dist/index.js +0 -5
  1041. package/packages/protocol/dist/index.js.map +0 -1
  1042. package/packages/protocol/dist/relay-pty-schemas.d.ts +0 -340
  1043. package/packages/protocol/dist/relay-pty-schemas.d.ts.map +0 -1
  1044. package/packages/protocol/dist/relay-pty-schemas.js +0 -60
  1045. package/packages/protocol/dist/relay-pty-schemas.js.map +0 -1
  1046. package/packages/protocol/dist/types.d.ts +0 -793
  1047. package/packages/protocol/dist/types.d.ts.map +0 -1
  1048. package/packages/protocol/dist/types.js +0 -8
  1049. package/packages/protocol/dist/types.js.map +0 -1
  1050. package/packages/protocol/package.json +0 -61
  1051. package/packages/protocol/src/channels.test.ts +0 -330
  1052. package/packages/protocol/src/channels.ts +0 -270
  1053. package/packages/protocol/src/framing.test.ts +0 -164
  1054. package/packages/protocol/src/framing.ts +0 -242
  1055. package/packages/protocol/src/id-generator.ts +0 -69
  1056. package/packages/protocol/src/index.ts +0 -4
  1057. package/packages/protocol/src/relay-pty-schemas.ts +0 -400
  1058. package/packages/protocol/src/types.test.ts +0 -271
  1059. package/packages/protocol/src/types.ts +0 -988
  1060. package/packages/protocol/tsconfig.json +0 -21
  1061. package/packages/protocol/vitest.config.ts +0 -9
  1062. package/packages/resiliency/dist/cgroup-manager.d.ts +0 -152
  1063. package/packages/resiliency/dist/cgroup-manager.d.ts.map +0 -1
  1064. package/packages/resiliency/dist/cgroup-manager.js +0 -394
  1065. package/packages/resiliency/dist/cgroup-manager.js.map +0 -1
  1066. package/packages/resiliency/dist/context-persistence.d.ts +0 -140
  1067. package/packages/resiliency/dist/context-persistence.d.ts.map +0 -1
  1068. package/packages/resiliency/dist/context-persistence.js +0 -397
  1069. package/packages/resiliency/dist/context-persistence.js.map +0 -1
  1070. package/packages/resiliency/dist/crash-insights.d.ts +0 -156
  1071. package/packages/resiliency/dist/crash-insights.d.ts.map +0 -1
  1072. package/packages/resiliency/dist/crash-insights.js +0 -492
  1073. package/packages/resiliency/dist/crash-insights.js.map +0 -1
  1074. package/packages/resiliency/dist/gossip-health.d.ts +0 -137
  1075. package/packages/resiliency/dist/gossip-health.d.ts.map +0 -1
  1076. package/packages/resiliency/dist/gossip-health.js +0 -241
  1077. package/packages/resiliency/dist/gossip-health.js.map +0 -1
  1078. package/packages/resiliency/dist/health-monitor.d.ts +0 -97
  1079. package/packages/resiliency/dist/health-monitor.d.ts.map +0 -1
  1080. package/packages/resiliency/dist/health-monitor.js +0 -291
  1081. package/packages/resiliency/dist/health-monitor.js.map +0 -1
  1082. package/packages/resiliency/dist/index.d.ts +0 -69
  1083. package/packages/resiliency/dist/index.d.ts.map +0 -1
  1084. package/packages/resiliency/dist/index.js +0 -69
  1085. package/packages/resiliency/dist/index.js.map +0 -1
  1086. package/packages/resiliency/dist/leader-watchdog.d.ts +0 -109
  1087. package/packages/resiliency/dist/leader-watchdog.d.ts.map +0 -1
  1088. package/packages/resiliency/dist/leader-watchdog.js +0 -189
  1089. package/packages/resiliency/dist/leader-watchdog.js.map +0 -1
  1090. package/packages/resiliency/dist/logger.d.ts +0 -114
  1091. package/packages/resiliency/dist/logger.d.ts.map +0 -1
  1092. package/packages/resiliency/dist/logger.js +0 -250
  1093. package/packages/resiliency/dist/logger.js.map +0 -1
  1094. package/packages/resiliency/dist/memory-monitor.d.ts +0 -172
  1095. package/packages/resiliency/dist/memory-monitor.d.ts.map +0 -1
  1096. package/packages/resiliency/dist/memory-monitor.js +0 -599
  1097. package/packages/resiliency/dist/memory-monitor.js.map +0 -1
  1098. package/packages/resiliency/dist/metrics.d.ts +0 -115
  1099. package/packages/resiliency/dist/metrics.d.ts.map +0 -1
  1100. package/packages/resiliency/dist/metrics.js +0 -239
  1101. package/packages/resiliency/dist/metrics.js.map +0 -1
  1102. package/packages/resiliency/dist/provider-context.d.ts +0 -100
  1103. package/packages/resiliency/dist/provider-context.d.ts.map +0 -1
  1104. package/packages/resiliency/dist/provider-context.js +0 -362
  1105. package/packages/resiliency/dist/provider-context.js.map +0 -1
  1106. package/packages/resiliency/dist/stateless-lead.d.ts +0 -149
  1107. package/packages/resiliency/dist/stateless-lead.d.ts.map +0 -1
  1108. package/packages/resiliency/dist/stateless-lead.js +0 -308
  1109. package/packages/resiliency/dist/stateless-lead.js.map +0 -1
  1110. package/packages/resiliency/dist/supervisor.d.ts +0 -147
  1111. package/packages/resiliency/dist/supervisor.d.ts.map +0 -1
  1112. package/packages/resiliency/dist/supervisor.js +0 -459
  1113. package/packages/resiliency/dist/supervisor.js.map +0 -1
  1114. package/packages/resiliency/package.json +0 -38
  1115. package/packages/resiliency/src/cgroup-manager.ts +0 -468
  1116. package/packages/resiliency/src/context-persistence.ts +0 -538
  1117. package/packages/resiliency/src/crash-insights.test.ts +0 -620
  1118. package/packages/resiliency/src/crash-insights.ts +0 -660
  1119. package/packages/resiliency/src/gossip-health.ts +0 -333
  1120. package/packages/resiliency/src/health-monitor.ts +0 -371
  1121. package/packages/resiliency/src/index.ts +0 -157
  1122. package/packages/resiliency/src/leader-watchdog.ts +0 -260
  1123. package/packages/resiliency/src/logger.ts +0 -320
  1124. package/packages/resiliency/src/memory-monitor.test.ts +0 -637
  1125. package/packages/resiliency/src/memory-monitor.ts +0 -740
  1126. package/packages/resiliency/src/metrics.ts +0 -311
  1127. package/packages/resiliency/src/provider-context.ts +0 -452
  1128. package/packages/resiliency/src/stateless-lead.ts +0 -408
  1129. package/packages/resiliency/src/supervisor.ts +0 -578
  1130. package/packages/resiliency/tsconfig.json +0 -21
  1131. package/packages/resiliency/vitest.config.ts +0 -9
  1132. package/packages/sdk/dist/discovery.d.ts +0 -10
  1133. package/packages/sdk/dist/discovery.d.ts.map +0 -1
  1134. package/packages/sdk/dist/discovery.js +0 -22
  1135. package/packages/sdk/dist/discovery.js.map +0 -1
  1136. package/packages/sdk/dist/errors.d.ts +0 -9
  1137. package/packages/sdk/dist/errors.d.ts.map +0 -1
  1138. package/packages/sdk/dist/errors.js +0 -9
  1139. package/packages/sdk/dist/errors.js.map +0 -1
  1140. package/packages/sdk/dist/protocol/index.d.ts +0 -8
  1141. package/packages/sdk/dist/protocol/index.d.ts.map +0 -1
  1142. package/packages/sdk/dist/protocol/index.js +0 -8
  1143. package/packages/sdk/dist/protocol/index.js.map +0 -1
  1144. package/packages/sdk/examples/SWARM_CAPABILITIES.md +0 -498
  1145. package/packages/sdk/examples/SWARM_PATTERNS.md +0 -541
  1146. package/packages/sdk/src/client.test.ts +0 -1041
  1147. package/packages/sdk/src/discovery.ts +0 -38
  1148. package/packages/sdk/src/errors.ts +0 -17
  1149. package/packages/sdk/src/logs.test.ts +0 -98
  1150. package/packages/sdk/src/protocol/framing.test.ts +0 -164
  1151. package/packages/sdk/src/protocol/index.ts +0 -8
  1152. package/packages/spawner/.trajectories/index.json +0 -5
  1153. package/packages/spawner/API.md +0 -256
  1154. package/packages/spawner/dist/index.d.ts +0 -8
  1155. package/packages/spawner/dist/index.d.ts.map +0 -1
  1156. package/packages/spawner/dist/index.js +0 -8
  1157. package/packages/spawner/dist/index.js.map +0 -1
  1158. package/packages/spawner/dist/types.d.ts +0 -552
  1159. package/packages/spawner/dist/types.d.ts.map +0 -1
  1160. package/packages/spawner/dist/types.js +0 -193
  1161. package/packages/spawner/dist/types.js.map +0 -1
  1162. package/packages/spawner/package.json +0 -47
  1163. package/packages/spawner/src/index.ts +0 -8
  1164. package/packages/spawner/src/types.test.ts +0 -385
  1165. package/packages/spawner/src/types.ts +0 -228
  1166. package/packages/spawner/tsconfig.json +0 -19
  1167. package/packages/spawner/vitest.config.ts +0 -9
  1168. package/packages/state/dist/agent-state.d.ts +0 -40
  1169. package/packages/state/dist/agent-state.d.ts.map +0 -1
  1170. package/packages/state/dist/agent-state.js +0 -120
  1171. package/packages/state/dist/agent-state.js.map +0 -1
  1172. package/packages/state/dist/index.d.ts +0 -8
  1173. package/packages/state/dist/index.d.ts.map +0 -1
  1174. package/packages/state/dist/index.js +0 -8
  1175. package/packages/state/dist/index.js.map +0 -1
  1176. package/packages/state/package.json +0 -37
  1177. package/packages/state/src/agent-state.test.ts +0 -335
  1178. package/packages/state/src/agent-state.ts +0 -153
  1179. package/packages/state/src/index.ts +0 -12
  1180. package/packages/state/tsconfig.json +0 -21
  1181. package/packages/state/vitest.config.ts +0 -9
  1182. package/packages/storage/dist/adapter.d.ts +0 -189
  1183. package/packages/storage/dist/adapter.d.ts.map +0 -1
  1184. package/packages/storage/dist/adapter.js +0 -267
  1185. package/packages/storage/dist/adapter.js.map +0 -1
  1186. package/packages/storage/dist/batched-sqlite-adapter.d.ts +0 -75
  1187. package/packages/storage/dist/batched-sqlite-adapter.d.ts.map +0 -1
  1188. package/packages/storage/dist/batched-sqlite-adapter.js +0 -189
  1189. package/packages/storage/dist/batched-sqlite-adapter.js.map +0 -1
  1190. package/packages/storage/dist/dead-letter-queue.d.ts +0 -196
  1191. package/packages/storage/dist/dead-letter-queue.d.ts.map +0 -1
  1192. package/packages/storage/dist/dead-letter-queue.js +0 -427
  1193. package/packages/storage/dist/dead-letter-queue.js.map +0 -1
  1194. package/packages/storage/dist/dlq-adapter.d.ts +0 -195
  1195. package/packages/storage/dist/dlq-adapter.d.ts.map +0 -1
  1196. package/packages/storage/dist/dlq-adapter.js +0 -664
  1197. package/packages/storage/dist/dlq-adapter.js.map +0 -1
  1198. package/packages/storage/dist/index.d.ts +0 -6
  1199. package/packages/storage/dist/index.d.ts.map +0 -1
  1200. package/packages/storage/dist/index.js +0 -7
  1201. package/packages/storage/dist/index.js.map +0 -1
  1202. package/packages/storage/dist/jsonl-adapter.d.ts +0 -91
  1203. package/packages/storage/dist/jsonl-adapter.d.ts.map +0 -1
  1204. package/packages/storage/dist/jsonl-adapter.js +0 -580
  1205. package/packages/storage/dist/jsonl-adapter.js.map +0 -1
  1206. package/packages/storage/dist/sqlite-adapter.d.ts +0 -131
  1207. package/packages/storage/dist/sqlite-adapter.d.ts.map +0 -1
  1208. package/packages/storage/dist/sqlite-adapter.js +0 -865
  1209. package/packages/storage/dist/sqlite-adapter.js.map +0 -1
  1210. package/packages/storage/package.json +0 -74
  1211. package/packages/storage/src/adapter.ts +0 -446
  1212. package/packages/storage/src/batched-sqlite-adapter.test.ts +0 -256
  1213. package/packages/storage/src/batched-sqlite-adapter.ts +0 -239
  1214. package/packages/storage/src/dead-letter-queue.ts +0 -643
  1215. package/packages/storage/src/dlq-adapter.test.ts +0 -509
  1216. package/packages/storage/src/dlq-adapter.ts +0 -954
  1217. package/packages/storage/src/index.ts +0 -6
  1218. package/packages/storage/src/jsonl-adapter.test.ts +0 -239
  1219. package/packages/storage/src/jsonl-adapter.ts +0 -704
  1220. package/packages/storage/src/memory-adapter.test.ts +0 -36
  1221. package/packages/storage/src/sqlite-adapter.test.ts +0 -580
  1222. package/packages/storage/src/sqlite-adapter.ts +0 -1099
  1223. package/packages/storage/tsconfig.json +0 -21
  1224. package/packages/storage/vitest.config.ts +0 -9
  1225. package/packages/wrapper/dist/__fixtures__/claude-outputs.d.ts +0 -49
  1226. package/packages/wrapper/dist/__fixtures__/claude-outputs.d.ts.map +0 -1
  1227. package/packages/wrapper/dist/__fixtures__/claude-outputs.js +0 -443
  1228. package/packages/wrapper/dist/__fixtures__/claude-outputs.js.map +0 -1
  1229. package/packages/wrapper/dist/__fixtures__/codex-outputs.d.ts +0 -9
  1230. package/packages/wrapper/dist/__fixtures__/codex-outputs.d.ts.map +0 -1
  1231. package/packages/wrapper/dist/__fixtures__/codex-outputs.js +0 -94
  1232. package/packages/wrapper/dist/__fixtures__/codex-outputs.js.map +0 -1
  1233. package/packages/wrapper/dist/__fixtures__/gemini-outputs.d.ts +0 -19
  1234. package/packages/wrapper/dist/__fixtures__/gemini-outputs.d.ts.map +0 -1
  1235. package/packages/wrapper/dist/__fixtures__/gemini-outputs.js +0 -144
  1236. package/packages/wrapper/dist/__fixtures__/gemini-outputs.js.map +0 -1
  1237. package/packages/wrapper/dist/__fixtures__/index.d.ts +0 -68
  1238. package/packages/wrapper/dist/__fixtures__/index.d.ts.map +0 -1
  1239. package/packages/wrapper/dist/__fixtures__/index.js +0 -44
  1240. package/packages/wrapper/dist/__fixtures__/index.js.map +0 -1
  1241. package/packages/wrapper/dist/auth-detection.d.ts +0 -49
  1242. package/packages/wrapper/dist/auth-detection.d.ts.map +0 -1
  1243. package/packages/wrapper/dist/auth-detection.js +0 -199
  1244. package/packages/wrapper/dist/auth-detection.js.map +0 -1
  1245. package/packages/wrapper/dist/base-wrapper.d.ts +0 -254
  1246. package/packages/wrapper/dist/base-wrapper.d.ts.map +0 -1
  1247. package/packages/wrapper/dist/base-wrapper.js +0 -664
  1248. package/packages/wrapper/dist/base-wrapper.js.map +0 -1
  1249. package/packages/wrapper/dist/client.d.ts +0 -291
  1250. package/packages/wrapper/dist/client.d.ts.map +0 -1
  1251. package/packages/wrapper/dist/client.js +0 -926
  1252. package/packages/wrapper/dist/client.js.map +0 -1
  1253. package/packages/wrapper/dist/id-generator.d.ts +0 -35
  1254. package/packages/wrapper/dist/id-generator.d.ts.map +0 -1
  1255. package/packages/wrapper/dist/id-generator.js +0 -60
  1256. package/packages/wrapper/dist/id-generator.js.map +0 -1
  1257. package/packages/wrapper/dist/idle-detector.d.ts +0 -114
  1258. package/packages/wrapper/dist/idle-detector.d.ts.map +0 -1
  1259. package/packages/wrapper/dist/idle-detector.js +0 -317
  1260. package/packages/wrapper/dist/idle-detector.js.map +0 -1
  1261. package/packages/wrapper/dist/inbox.d.ts +0 -37
  1262. package/packages/wrapper/dist/inbox.d.ts.map +0 -1
  1263. package/packages/wrapper/dist/inbox.js +0 -73
  1264. package/packages/wrapper/dist/inbox.js.map +0 -1
  1265. package/packages/wrapper/dist/index.d.ts +0 -40
  1266. package/packages/wrapper/dist/index.d.ts.map +0 -1
  1267. package/packages/wrapper/dist/index.js +0 -53
  1268. package/packages/wrapper/dist/index.js.map +0 -1
  1269. package/packages/wrapper/dist/opencode-api.d.ts +0 -106
  1270. package/packages/wrapper/dist/opencode-api.d.ts.map +0 -1
  1271. package/packages/wrapper/dist/opencode-api.js +0 -219
  1272. package/packages/wrapper/dist/opencode-api.js.map +0 -1
  1273. package/packages/wrapper/dist/opencode-wrapper.d.ts +0 -161
  1274. package/packages/wrapper/dist/opencode-wrapper.d.ts.map +0 -1
  1275. package/packages/wrapper/dist/opencode-wrapper.js +0 -438
  1276. package/packages/wrapper/dist/opencode-wrapper.js.map +0 -1
  1277. package/packages/wrapper/dist/parser.d.ts +0 -236
  1278. package/packages/wrapper/dist/parser.d.ts.map +0 -1
  1279. package/packages/wrapper/dist/parser.js +0 -1238
  1280. package/packages/wrapper/dist/parser.js.map +0 -1
  1281. package/packages/wrapper/dist/prompt-composer.d.ts +0 -67
  1282. package/packages/wrapper/dist/prompt-composer.d.ts.map +0 -1
  1283. package/packages/wrapper/dist/prompt-composer.js +0 -168
  1284. package/packages/wrapper/dist/prompt-composer.js.map +0 -1
  1285. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +0 -486
  1286. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts.map +0 -1
  1287. package/packages/wrapper/dist/relay-pty-orchestrator.js +0 -2550
  1288. package/packages/wrapper/dist/relay-pty-orchestrator.js.map +0 -1
  1289. package/packages/wrapper/dist/shared.d.ts +0 -262
  1290. package/packages/wrapper/dist/shared.d.ts.map +0 -1
  1291. package/packages/wrapper/dist/shared.js +0 -507
  1292. package/packages/wrapper/dist/shared.js.map +0 -1
  1293. package/packages/wrapper/dist/stuck-detector.d.ts +0 -161
  1294. package/packages/wrapper/dist/stuck-detector.d.ts.map +0 -1
  1295. package/packages/wrapper/dist/stuck-detector.js +0 -402
  1296. package/packages/wrapper/dist/stuck-detector.js.map +0 -1
  1297. package/packages/wrapper/dist/tmux-resolver.d.ts +0 -55
  1298. package/packages/wrapper/dist/tmux-resolver.d.ts.map +0 -1
  1299. package/packages/wrapper/dist/tmux-resolver.js +0 -175
  1300. package/packages/wrapper/dist/tmux-resolver.js.map +0 -1
  1301. package/packages/wrapper/dist/tmux-wrapper.d.ts +0 -352
  1302. package/packages/wrapper/dist/tmux-wrapper.d.ts.map +0 -1
  1303. package/packages/wrapper/dist/tmux-wrapper.js +0 -1816
  1304. package/packages/wrapper/dist/tmux-wrapper.js.map +0 -1
  1305. package/packages/wrapper/dist/trajectory-integration.d.ts +0 -292
  1306. package/packages/wrapper/dist/trajectory-integration.d.ts.map +0 -1
  1307. package/packages/wrapper/dist/trajectory-integration.js +0 -979
  1308. package/packages/wrapper/dist/trajectory-integration.js.map +0 -1
  1309. package/packages/wrapper/dist/wrapper-events.d.ts +0 -489
  1310. package/packages/wrapper/dist/wrapper-events.d.ts.map +0 -1
  1311. package/packages/wrapper/dist/wrapper-events.js +0 -252
  1312. package/packages/wrapper/dist/wrapper-events.js.map +0 -1
  1313. package/packages/wrapper/dist/wrapper-types.d.ts +0 -41
  1314. package/packages/wrapper/dist/wrapper-types.d.ts.map +0 -1
  1315. package/packages/wrapper/dist/wrapper-types.js +0 -7
  1316. package/packages/wrapper/dist/wrapper-types.js.map +0 -1
  1317. package/packages/wrapper/package.json +0 -60
  1318. package/packages/wrapper/src/__fixtures__/claude-outputs.ts +0 -471
  1319. package/packages/wrapper/src/__fixtures__/codex-outputs.ts +0 -99
  1320. package/packages/wrapper/src/__fixtures__/gemini-outputs.ts +0 -151
  1321. package/packages/wrapper/src/__fixtures__/index.ts +0 -47
  1322. package/packages/wrapper/src/auth-detection.ts +0 -244
  1323. package/packages/wrapper/src/base-wrapper.test.ts +0 -589
  1324. package/packages/wrapper/src/base-wrapper.ts +0 -841
  1325. package/packages/wrapper/src/client.test.ts +0 -351
  1326. package/packages/wrapper/src/client.ts +0 -1166
  1327. package/packages/wrapper/src/id-generator.test.ts +0 -71
  1328. package/packages/wrapper/src/id-generator.ts +0 -69
  1329. package/packages/wrapper/src/idle-detector.test.ts +0 -418
  1330. package/packages/wrapper/src/idle-detector.ts +0 -384
  1331. package/packages/wrapper/src/inbox.test.ts +0 -233
  1332. package/packages/wrapper/src/inbox.ts +0 -89
  1333. package/packages/wrapper/src/index.ts +0 -199
  1334. package/packages/wrapper/src/opencode-api.test.ts +0 -292
  1335. package/packages/wrapper/src/opencode-api.ts +0 -285
  1336. package/packages/wrapper/src/opencode-wrapper.ts +0 -541
  1337. package/packages/wrapper/src/parser.regression.test.ts +0 -251
  1338. package/packages/wrapper/src/parser.test.ts +0 -1359
  1339. package/packages/wrapper/src/parser.ts +0 -1477
  1340. package/packages/wrapper/src/prompt-composer.test.ts +0 -219
  1341. package/packages/wrapper/src/prompt-composer.ts +0 -231
  1342. package/packages/wrapper/src/relay-pty-orchestrator.test.ts +0 -1386
  1343. package/packages/wrapper/src/relay-pty-orchestrator.ts +0 -3041
  1344. package/packages/wrapper/src/shared.test.ts +0 -467
  1345. package/packages/wrapper/src/shared.ts +0 -652
  1346. package/packages/wrapper/src/stuck-detector.test.ts +0 -303
  1347. package/packages/wrapper/src/stuck-detector.ts +0 -511
  1348. package/packages/wrapper/src/tmux-resolver.test.ts +0 -104
  1349. package/packages/wrapper/src/tmux-resolver.ts +0 -207
  1350. package/packages/wrapper/src/tmux-wrapper.test.ts +0 -316
  1351. package/packages/wrapper/src/tmux-wrapper.ts +0 -2095
  1352. package/packages/wrapper/src/trajectory-detection.test.ts +0 -151
  1353. package/packages/wrapper/src/trajectory-integration.ts +0 -1261
  1354. package/packages/wrapper/src/wrapper-events.ts +0 -395
  1355. package/packages/wrapper/src/wrapper-types.ts +0 -45
  1356. package/packages/wrapper/tsconfig.json +0 -19
  1357. package/packages/wrapper/vitest.config.ts +0 -9
  1358. /package/packages/{broker-sdk → sdk}/dist/__tests__/facade.test.d.ts +0 -0
  1359. /package/packages/{broker-sdk → sdk}/dist/__tests__/facade.test.d.ts.map +0 -0
  1360. /package/packages/{broker-sdk → sdk}/dist/__tests__/integration.test.d.ts +0 -0
  1361. /package/packages/{broker-sdk → sdk}/dist/__tests__/integration.test.d.ts.map +0 -0
  1362. /package/packages/{broker-sdk → sdk}/dist/__tests__/quickstart.test.d.ts +0 -0
  1363. /package/packages/{broker-sdk → sdk}/dist/__tests__/quickstart.test.d.ts.map +0 -0
  1364. /package/packages/{broker-sdk → sdk}/dist/__tests__/unit.test.d.ts +0 -0
  1365. /package/packages/{broker-sdk → sdk}/dist/__tests__/unit.test.d.ts.map +0 -0
  1366. /package/packages/{broker-sdk → sdk}/dist/browser.d.ts +0 -0
  1367. /package/packages/{broker-sdk → sdk}/dist/browser.d.ts.map +0 -0
  1368. /package/packages/{broker-sdk → sdk}/dist/browser.js +0 -0
  1369. /package/packages/{broker-sdk → sdk}/dist/browser.js.map +0 -0
  1370. /package/packages/{broker-sdk → sdk}/dist/consensus-helpers.d.ts +0 -0
  1371. /package/packages/{broker-sdk → sdk}/dist/consensus-helpers.d.ts.map +0 -0
  1372. /package/packages/{broker-sdk → sdk}/dist/consensus-helpers.js +0 -0
  1373. /package/packages/{broker-sdk → sdk}/dist/consensus-helpers.js.map +0 -0
  1374. /package/packages/{broker-sdk → sdk}/dist/consensus.d.ts +0 -0
  1375. /package/packages/{broker-sdk → sdk}/dist/consensus.d.ts.map +0 -0
  1376. /package/packages/{broker-sdk → sdk}/dist/consensus.js +0 -0
  1377. /package/packages/{broker-sdk → sdk}/dist/consensus.js.map +0 -0
  1378. /package/packages/{broker-sdk → sdk}/dist/examples/demo.d.ts +0 -0
  1379. /package/packages/{broker-sdk → sdk}/dist/examples/demo.d.ts.map +0 -0
  1380. /package/packages/{broker-sdk → sdk}/dist/examples/demo.js +0 -0
  1381. /package/packages/{broker-sdk → sdk}/dist/examples/demo.js.map +0 -0
  1382. /package/packages/{broker-sdk → sdk}/dist/examples/example.d.ts +0 -0
  1383. /package/packages/{broker-sdk → sdk}/dist/examples/example.d.ts.map +0 -0
  1384. /package/packages/{broker-sdk → sdk}/dist/examples/example.js +0 -0
  1385. /package/packages/{broker-sdk → sdk}/dist/examples/example.js.map +0 -0
  1386. /package/packages/{broker-sdk → sdk}/dist/examples/quickstart.d.ts +0 -0
  1387. /package/packages/{broker-sdk → sdk}/dist/examples/quickstart.d.ts.map +0 -0
  1388. /package/packages/{broker-sdk → sdk}/dist/examples/quickstart.js +0 -0
  1389. /package/packages/{broker-sdk → sdk}/dist/examples/quickstart.js.map +0 -0
  1390. /package/packages/{broker-sdk → sdk}/dist/examples/ralph-loop.d.ts +0 -0
  1391. /package/packages/{broker-sdk → sdk}/dist/examples/ralph-loop.d.ts.map +0 -0
  1392. /package/packages/{broker-sdk → sdk}/dist/examples/ralph-loop.js +0 -0
  1393. /package/packages/{broker-sdk → sdk}/dist/examples/ralph-loop.js.map +0 -0
  1394. /package/packages/{broker-sdk → sdk}/dist/protocol.js +0 -0
  1395. /package/packages/{broker-sdk → sdk}/dist/protocol.js.map +0 -0
  1396. /package/packages/{broker-sdk → sdk}/dist/pty.d.ts +0 -0
  1397. /package/packages/{broker-sdk → sdk}/dist/shadow.js +0 -0
  1398. /package/packages/{broker-sdk → sdk}/dist/workflows/barrier.d.ts +0 -0
  1399. /package/packages/{broker-sdk → sdk}/dist/workflows/barrier.d.ts.map +0 -0
  1400. /package/packages/{broker-sdk → sdk}/dist/workflows/barrier.js +0 -0
  1401. /package/packages/{broker-sdk → sdk}/dist/workflows/barrier.js.map +0 -0
  1402. /package/packages/{broker-sdk → sdk}/dist/workflows/memory-db.d.ts +0 -0
  1403. /package/packages/{broker-sdk → sdk}/dist/workflows/memory-db.d.ts.map +0 -0
  1404. /package/packages/{broker-sdk → sdk}/dist/workflows/memory-db.js +0 -0
  1405. /package/packages/{broker-sdk → sdk}/dist/workflows/memory-db.js.map +0 -0
  1406. /package/packages/{broker-sdk → sdk}/dist/workflows/state.d.ts +0 -0
  1407. /package/packages/{broker-sdk → sdk}/dist/workflows/state.d.ts.map +0 -0
  1408. /package/packages/{broker-sdk → sdk}/dist/workflows/state.js +0 -0
  1409. /package/packages/{broker-sdk → sdk}/dist/workflows/state.js.map +0 -0
  1410. /package/packages/{broker-sdk → sdk}/src/__tests__/workflow-trajectory.test.ts +0 -0
  1411. /package/packages/{broker-sdk → sdk}/src/browser.ts +0 -0
  1412. /package/packages/{broker-sdk → sdk}/src/consensus-helpers.ts +0 -0
  1413. /package/packages/{broker-sdk → sdk}/src/consensus.ts +0 -0
  1414. /package/packages/{broker-sdk → sdk}/src/examples/demo.ts +0 -0
  1415. /package/packages/{broker-sdk → sdk}/src/examples/example.ts +0 -0
  1416. /package/packages/{broker-sdk → sdk}/src/examples/quickstart.ts +0 -0
  1417. /package/packages/{broker-sdk → sdk}/src/examples/ralph-loop.ts +0 -0
  1418. /package/packages/{broker-sdk → sdk}/src/examples/sample-prd.json +0 -0
  1419. /package/packages/{broker-sdk → sdk}/src/workflows/barrier.ts +0 -0
  1420. /package/packages/{broker-sdk → sdk}/src/workflows/memory-db.ts +0 -0
  1421. /package/packages/{broker-sdk → sdk}/src/workflows/state.ts +0 -0
@@ -0,0 +1,3409 @@
1
+ /**
2
+ * WorkflowRunner — parses relay.yaml, validates config, resolves templates,
3
+ * executes steps (sequential/parallel/DAG), runs verification checks,
4
+ * persists state to DB, and supports pause/resume/abort with retries.
5
+ */
6
+
7
+ import { spawn as cpSpawn } from 'node:child_process';
8
+ import { randomBytes } from 'node:crypto';
9
+ import { createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
10
+ import type { WriteStream } from 'node:fs';
11
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+
14
+ import { parse as parseYaml } from 'yaml';
15
+ import { stripAnsi as stripAnsiFn } from '../pty.js';
16
+
17
+ import {
18
+ loadCustomSteps,
19
+ resolveAllCustomSteps,
20
+ validateCustomStepsUsage,
21
+ CustomStepsParseError,
22
+ CustomStepResolutionError,
23
+ } from './custom-steps.js';
24
+ import { InMemoryWorkflowDb } from './memory-db.js';
25
+ import type {
26
+ AgentCli,
27
+ AgentDefinition,
28
+ AgentPreset,
29
+ DryRunReport,
30
+ DryRunWave,
31
+ ErrorHandlingConfig,
32
+ IdleNudgeConfig,
33
+ PreflightCheck,
34
+ RelayYamlConfig,
35
+ SwarmPattern,
36
+ VerificationCheck,
37
+ WorkflowDefinition,
38
+ WorkflowRunRow,
39
+ WorkflowRunStatus,
40
+ WorkflowStep,
41
+ WorkflowStepRow,
42
+ WorkflowStepStatus,
43
+ } from './types.js';
44
+ import { WorkflowTrajectory, type StepOutcome } from './trajectory.js';
45
+
46
+ // ── AgentRelay SDK imports ──────────────────────────────────────────────────
47
+
48
+ // Import from sub-paths to avoid pulling in the full @relaycast/sdk dependency.
49
+ import { AgentRelay } from '../relay.js';
50
+ import type { Agent, AgentRelayOptions } from '../relay.js';
51
+ import { RelayCast, RelayError, type AgentClient } from '@relaycast/sdk';
52
+
53
+ // ── DB adapter interface ────────────────────────────────────────────────────
54
+
55
+ /** Minimal DB adapter so the runner is not coupled to a specific driver. */
56
+ export interface WorkflowDb {
57
+ insertRun(run: WorkflowRunRow): Promise<void>;
58
+ updateRun(id: string, patch: Partial<WorkflowRunRow>): Promise<void>;
59
+ getRun(id: string): Promise<WorkflowRunRow | null>;
60
+
61
+ insertStep(step: WorkflowStepRow): Promise<void>;
62
+ updateStep(id: string, patch: Partial<WorkflowStepRow>): Promise<void>;
63
+ getStepsByRunId(runId: string): Promise<WorkflowStepRow[]>;
64
+ }
65
+
66
+ // ── Events ──────────────────────────────────────────────────────────────────
67
+
68
+ export type WorkflowEvent =
69
+ | { type: 'run:started'; runId: string }
70
+ | { type: 'run:completed'; runId: string }
71
+ | { type: 'run:failed'; runId: string; error: string }
72
+ | { type: 'run:cancelled'; runId: string }
73
+ | { type: 'step:started'; runId: string; stepName: string }
74
+ | { type: 'step:completed'; runId: string; stepName: string; output?: string }
75
+ | { type: 'step:failed'; runId: string; stepName: string; error: string }
76
+ | { type: 'step:skipped'; runId: string; stepName: string }
77
+ | { type: 'step:retrying'; runId: string; stepName: string; attempt: number }
78
+ | { type: 'step:nudged'; runId: string; stepName: string; nudgeCount: number }
79
+ | { type: 'step:force-released'; runId: string; stepName: string };
80
+
81
+ export type WorkflowEventListener = (event: WorkflowEvent) => void;
82
+
83
+ // ── Runner options ──────────────────────────────────────────────────────────
84
+
85
+ export interface WorkflowRunnerOptions {
86
+ db?: WorkflowDb;
87
+ workspaceId?: string;
88
+ relay?: AgentRelayOptions;
89
+ cwd?: string;
90
+ summaryDir?: string;
91
+ }
92
+
93
+ // ── Variable context for template resolution ────────────────────────────────
94
+
95
+ export interface VariableContext {
96
+ [key: string]: string | number | boolean | undefined;
97
+ }
98
+
99
+ // ── Internal step state ─────────────────────────────────────────────────────
100
+
101
+ interface StepState {
102
+ row: WorkflowStepRow;
103
+ agent?: Agent;
104
+ }
105
+
106
+ // ── WorkflowRunner ──────────────────────────────────────────────────────────
107
+
108
+ export class WorkflowRunner {
109
+ private readonly db: WorkflowDb;
110
+ private readonly workspaceId: string;
111
+ private readonly relayOptions: AgentRelayOptions;
112
+ private readonly cwd: string;
113
+ private readonly summaryDir: string;
114
+
115
+ /** @internal exposed for CLI signal-handler shutdown only */
116
+ relay?: AgentRelay;
117
+ private relaycast?: RelayCast;
118
+ private relaycastAgent?: AgentClient;
119
+ private relayApiKey?: string;
120
+ private relayApiKeyAutoCreated = false;
121
+ private channel?: string;
122
+ private trajectory?: WorkflowTrajectory;
123
+ private abortController?: AbortController;
124
+ private paused = false;
125
+ private pauseResolver?: () => void;
126
+ private listeners: WorkflowEventListener[] = [];
127
+
128
+ /** Current config for the active run, so spawnAndWait can access swarm config. */
129
+ private currentConfig?: RelayYamlConfig;
130
+ /** Current run ID for event emission from spawnAndWait context. */
131
+ private currentRunId?: string;
132
+ /** Live Agent handles keyed by name, for hub-mediated nudging. */
133
+ private readonly activeAgentHandles = new Map<string, Agent>();
134
+
135
+ // PTY-based output capture: accumulate terminal output per-agent
136
+ private readonly ptyOutputBuffers = new Map<string, string[]>();
137
+ private readonly ptyListeners = new Map<string, (chunk: string) => void>();
138
+ private readonly ptyLogStreams = new Map<string, WriteStream>();
139
+ /** Path to workers.json so `agents:kill` can find workflow-spawned agents */
140
+ private readonly workersPath: string;
141
+ /** In-memory tracking of active workers to avoid race conditions on workers.json */
142
+ private readonly activeWorkers = new Map<
143
+ string,
144
+ { cli: string; task: string; spawnedAt: number; pid?: number; logFile: string }
145
+ >();
146
+ /** Mutex for serializing workers.json file access */
147
+ private workersFileLock: Promise<void> = Promise.resolve();
148
+ /** Timestamp when the current workflow run started, for elapsed-time logging. */
149
+ private runStartTime?: number;
150
+ /** Unsubscribe handle for broker stderr listener wired during a run. */
151
+ private unsubBrokerStderr?: () => void;
152
+ /** Tracks last idle log time per agent to debounce idle warnings (30s multiples). */
153
+ private readonly lastIdleLog = new Map<string, number>();
154
+ /** Tracks last logged activity type per agent to avoid duplicate status lines. */
155
+ private readonly lastActivity = new Map<string, string>();
156
+
157
+ constructor(options: WorkflowRunnerOptions = {}) {
158
+ this.db = options.db ?? new InMemoryWorkflowDb();
159
+ this.workspaceId = options.workspaceId ?? 'local';
160
+ this.relayOptions = options.relay ?? {};
161
+ this.cwd = options.cwd ?? process.cwd();
162
+ this.summaryDir = options.summaryDir ?? path.join(this.cwd, '.relay', 'summaries');
163
+ this.workersPath = path.join(this.cwd, '.agent-relay', 'team', 'workers.json');
164
+ }
165
+
166
+ // ── Progress logging ────────────────────────────────────────────────────
167
+
168
+ /** Log a progress message with elapsed time since run start. */
169
+ private log(msg: string): void {
170
+ const elapsed = this.runStartTime ? Math.round((Date.now() - this.runStartTime) / 1000) : 0;
171
+ const mins = Math.floor(elapsed / 60);
172
+ const secs = elapsed % 60;
173
+ const ts =
174
+ mins > 0
175
+ ? `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
176
+ : `00:${String(secs).padStart(2, '0')}`;
177
+ console.log(`[workflow ${ts}] ${msg}`);
178
+ }
179
+
180
+ // ── Relaycast auto-provisioning ────────────────────────────────────────
181
+
182
+ /**
183
+ * Ensure a Relaycast workspace API key is available for the broker.
184
+ * Resolution order:
185
+ * 1. RELAY_API_KEY environment variable (explicit override)
186
+ * 2. Auto-create a fresh workspace via the Relaycast API
187
+ *
188
+ * Each workflow run gets its own isolated workspace — no caching, no sharing.
189
+ */
190
+ private async ensureRelaycastApiKey(channel: string): Promise<void> {
191
+ if (this.relayApiKey) return;
192
+
193
+ // Explicit override from relayOptions or environment takes priority.
194
+ const envKey = this.relayOptions.env?.RELAY_API_KEY ?? process.env.RELAY_API_KEY;
195
+ if (envKey) {
196
+ this.relayApiKey = envKey;
197
+ return;
198
+ }
199
+
200
+ // Always create a fresh workspace — each run gets full isolation.
201
+ const workspaceName = `relay-${channel}-${randomBytes(4).toString('hex')}`;
202
+ const baseUrl =
203
+ this.relayOptions.env?.RELAYCAST_BASE_URL ??
204
+ process.env.RELAYCAST_BASE_URL ??
205
+ 'https://api.relaycast.dev';
206
+ const res = await fetch(`${baseUrl}/v1/workspaces`, {
207
+ method: 'POST',
208
+ headers: { 'content-type': 'application/json' },
209
+ body: JSON.stringify({ name: workspaceName }),
210
+ });
211
+
212
+ if (!res.ok) {
213
+ throw new Error(`Failed to auto-create Relaycast workspace: ${res.status} ${await res.text()}`);
214
+ }
215
+
216
+ const body = (await res.json()) as Record<string, any>;
217
+ const data = (body.data ?? body) as Record<string, any>;
218
+ const apiKey = data.api_key as string;
219
+
220
+ if (!apiKey) {
221
+ throw new Error('Relaycast workspace response missing api_key');
222
+ }
223
+
224
+ this.relayApiKey = apiKey;
225
+ this.relayApiKeyAutoCreated = true;
226
+ }
227
+
228
+ private getRelayEnv(): NodeJS.ProcessEnv | undefined {
229
+ if (!this.relayApiKey) {
230
+ return this.relayOptions.env;
231
+ }
232
+
233
+ return {
234
+ ...(this.relayOptions.env ?? process.env),
235
+ RELAY_API_KEY: this.relayApiKey,
236
+ };
237
+ }
238
+
239
+ private getRelaycastBaseUrl(): string {
240
+ return (
241
+ this.relayOptions.env?.RELAYCAST_BASE_URL ??
242
+ process.env.RELAYCAST_BASE_URL ??
243
+ 'https://api.relaycast.dev'
244
+ );
245
+ }
246
+
247
+ private getRelaycastClient(): RelayCast {
248
+ if (!this.relayApiKey) {
249
+ throw new Error('No Relaycast API key available');
250
+ }
251
+ if (!this.relaycast) {
252
+ this.relaycast = new RelayCast({
253
+ apiKey: this.relayApiKey,
254
+ baseUrl: this.getRelaycastBaseUrl(),
255
+ });
256
+ }
257
+ return this.relaycast;
258
+ }
259
+
260
+ private async ensureRelaycastRunnerAgent(): Promise<AgentClient> {
261
+ if (this.relaycastAgent) return this.relaycastAgent;
262
+
263
+ const rc = this.getRelaycastClient();
264
+ let registration;
265
+ try {
266
+ registration = await rc.agents.register({ name: 'WorkflowRunner', type: 'agent' });
267
+ } catch (err) {
268
+ if (err instanceof RelayError && err.code === 'name_conflict') {
269
+ registration = await rc.agents.register({
270
+ name: `WorkflowRunner-${randomBytes(4).toString('hex')}`,
271
+ type: 'agent',
272
+ });
273
+ } else {
274
+ throw err;
275
+ }
276
+ }
277
+
278
+ this.relaycastAgent = rc.as(registration.token);
279
+ return this.relaycastAgent;
280
+ }
281
+
282
+ private async createAndJoinRelaycastChannel(channel: string, topic?: string): Promise<void> {
283
+ const agent = await this.ensureRelaycastRunnerAgent();
284
+ try {
285
+ await agent.channels.create({ name: channel, ...(topic ? { topic } : {}) });
286
+ } catch (err) {
287
+ if (!(err instanceof RelayError && err.code === 'name_conflict')) {
288
+ throw err;
289
+ }
290
+ }
291
+ await agent.channels.join(channel);
292
+ }
293
+
294
+ private async registerRelaycastExternalAgent(name: string, persona?: string): Promise<AgentClient | null> {
295
+ const rc = this.getRelaycastClient();
296
+ try {
297
+ const registration = await rc.agents.register({
298
+ name,
299
+ type: 'agent',
300
+ ...(persona ? { persona } : {}),
301
+ });
302
+ return rc.as(registration.token);
303
+ } catch (err) {
304
+ if (err instanceof RelayError && err.code === 'name_conflict') {
305
+ return null;
306
+ }
307
+ throw err;
308
+ }
309
+ }
310
+
311
+ private startRelaycastHeartbeat(agent: AgentClient, intervalMs = 30_000): () => void {
312
+ const beat = () => {
313
+ agent.heartbeat().catch(() => {});
314
+ };
315
+ const timer = setInterval(beat, intervalMs);
316
+ timer.unref();
317
+ beat();
318
+ return () => clearInterval(timer);
319
+ }
320
+
321
+ // ── Event subscription ──────────────────────────────────────────────────
322
+
323
+ on(listener: WorkflowEventListener): () => void {
324
+ this.listeners.push(listener);
325
+ return () => {
326
+ this.listeners = this.listeners.filter((l) => l !== listener);
327
+ };
328
+ }
329
+
330
+ private emit(event: WorkflowEvent): void {
331
+ for (const listener of this.listeners) {
332
+ listener(event);
333
+ }
334
+ }
335
+
336
+ // ── Parsing & validation ────────────────────────────────────────────────
337
+
338
+ /** Parse a relay.yaml file from disk. */
339
+ async parseYamlFile(filePath: string): Promise<RelayYamlConfig> {
340
+ const absPath = path.resolve(this.cwd, filePath);
341
+ const raw = await readFile(absPath, 'utf-8');
342
+ return this.parseYamlString(raw, absPath);
343
+ }
344
+
345
+ /** Parse a relay.yaml string. */
346
+ parseYamlString(raw: string, source = '<string>'): RelayYamlConfig {
347
+ const parsed = parseYaml(raw);
348
+ this.validateConfig(parsed, source);
349
+ const config = parsed as RelayYamlConfig;
350
+ config.agents ??= [];
351
+ return config;
352
+ }
353
+
354
+ /** Validate a config object against the RelayYamlConfig shape. */
355
+ validateConfig(config: unknown, source = '<config>'): asserts config is RelayYamlConfig {
356
+ if (typeof config !== 'object' || config === null) {
357
+ throw new Error(`${source}: config must be a non-null object`);
358
+ }
359
+
360
+ const c = config as Record<string, unknown>;
361
+
362
+ if (typeof c.version !== 'string') {
363
+ throw new Error(`${source}: missing required field "version"`);
364
+ }
365
+ if (typeof c.name !== 'string') {
366
+ throw new Error(`${source}: missing required field "name"`);
367
+ }
368
+ if (typeof c.swarm !== 'object' || c.swarm === null) {
369
+ throw new Error(`${source}: missing required field "swarm"`);
370
+ }
371
+ const swarm = c.swarm as Record<string, unknown>;
372
+ if (typeof swarm.pattern !== 'string') {
373
+ throw new Error(`${source}: missing required field "swarm.pattern"`);
374
+ }
375
+ if (c.agents !== undefined && !Array.isArray(c.agents)) {
376
+ throw new Error(`${source}: "agents" must be an array when provided`);
377
+ }
378
+
379
+ for (const agent of c.agents ?? []) {
380
+ if (typeof agent !== 'object' || agent === null) {
381
+ throw new Error(`${source}: each agent must be an object`);
382
+ }
383
+ const a = agent as Record<string, unknown>;
384
+ if (typeof a.name !== 'string') {
385
+ throw new Error(`${source}: each agent must have a string "name"`);
386
+ }
387
+ if (typeof a.cli !== 'string') {
388
+ throw new Error(`${source}: each agent must have a string "cli"`);
389
+ }
390
+ }
391
+
392
+ if (c.workflows !== undefined) {
393
+ if (!Array.isArray(c.workflows)) {
394
+ throw new Error(`${source}: "workflows" must be an array`);
395
+ }
396
+ for (const wf of c.workflows) {
397
+ this.validateWorkflow(wf, (c.agents ?? []) as AgentDefinition[], source);
398
+ }
399
+ }
400
+ }
401
+
402
+ // ── Dry-run simulation ──────────────────────────────────────────────
403
+
404
+ /**
405
+ * Validate a workflow config and simulate execution waves without spawning agents.
406
+ * Returns a DryRunReport with DAG analysis, agent summary, and wave breakdown.
407
+ */
408
+ dryRun(config: RelayYamlConfig, workflowName?: string, vars?: VariableContext): DryRunReport {
409
+ const errors: string[] = [];
410
+ const warnings: string[] = [];
411
+
412
+ // 1. Validate config
413
+ let resolved: RelayYamlConfig;
414
+ try {
415
+ this.validateConfig(config);
416
+ resolved = vars ? this.resolveVariables(config, vars) : config;
417
+ } catch (err) {
418
+ errors.push(err instanceof Error ? err.message : String(err));
419
+ return {
420
+ valid: false,
421
+ errors,
422
+ warnings,
423
+ name: (config as any)?.name ?? '<unknown>',
424
+ pattern: (config as any)?.swarm?.pattern ?? '<unknown>',
425
+ agents: [],
426
+ waves: [],
427
+ totalSteps: 0,
428
+ estimatedWaves: 0,
429
+ };
430
+ }
431
+
432
+ // 2. Find target workflow
433
+ const workflows = resolved.workflows ?? [];
434
+ const workflow = workflowName ? workflows.find((w) => w.name === workflowName) : workflows[0];
435
+
436
+ if (!workflow) {
437
+ errors.push(
438
+ workflowName ? `Workflow "${workflowName}" not found in config` : 'No workflows defined in config'
439
+ );
440
+ return {
441
+ valid: false,
442
+ errors,
443
+ warnings,
444
+ name: resolved.name,
445
+ description: resolved.description,
446
+ pattern: resolved.swarm.pattern,
447
+ agents: [],
448
+ waves: [],
449
+ totalSteps: 0,
450
+ estimatedWaves: 0,
451
+ };
452
+ }
453
+
454
+ // 3. Load and validate custom steps
455
+ let customSteps = new Map<string, import('./types.js').CustomStepDefinition>();
456
+ try {
457
+ customSteps = loadCustomSteps(this.cwd);
458
+ } catch (err) {
459
+ if (err instanceof CustomStepsParseError) {
460
+ errors.push(`Custom steps file error: ${err.issue}\n${err.suggestion}`);
461
+ } else {
462
+ errors.push(`Failed to load custom steps: ${err instanceof Error ? err.message : String(err)}`);
463
+ }
464
+ }
465
+
466
+ // Validate custom step usage in workflow steps
467
+ const customStepValidation = validateCustomStepsUsage(workflow.steps, customSteps);
468
+ errors.push(...customStepValidation.errors);
469
+ warnings.push(...customStepValidation.warnings);
470
+
471
+ // Resolve custom steps for further validation
472
+ let resolvedSteps = workflow.steps;
473
+ if (customStepValidation.valid) {
474
+ try {
475
+ resolvedSteps = resolveAllCustomSteps(workflow.steps, customSteps);
476
+ } catch (err) {
477
+ if (err instanceof CustomStepResolutionError) {
478
+ errors.push(`${err.issue}\n${err.suggestion}`);
479
+ } else {
480
+ errors.push(`Failed to resolve custom steps: ${err instanceof Error ? err.message : String(err)}`);
481
+ }
482
+ }
483
+ }
484
+
485
+ // 4. Build agent map and validate step→agent references
486
+ const agentMap = new Map<string, AgentDefinition>();
487
+ for (const agent of resolved.agents) {
488
+ agentMap.set(agent.name, agent);
489
+ }
490
+
491
+ const stepAgentCounts = new Map<string, number>();
492
+ for (const step of resolvedSteps) {
493
+ // Only validate agent references for agent-type steps
494
+ if (step.agent) {
495
+ if (!agentMap.has(step.agent)) {
496
+ warnings.push(`Step "${step.name}" references unknown agent "${step.agent}"`);
497
+ }
498
+ stepAgentCounts.set(step.agent, (stepAgentCounts.get(step.agent) ?? 0) + 1);
499
+ }
500
+ }
501
+
502
+ // Validate cwd paths
503
+ for (const agent of resolved.agents) {
504
+ if (agent.cwd) {
505
+ const resolvedCwd = path.resolve(this.cwd, agent.cwd);
506
+ if (!existsSync(resolvedCwd)) {
507
+ warnings.push(
508
+ `Agent "${agent.name}" cwd "${agent.cwd}" resolves to "${resolvedCwd}" which does not exist`
509
+ );
510
+ }
511
+ }
512
+ if (agent.additionalPaths) {
513
+ for (const ap of agent.additionalPaths) {
514
+ const resolvedPath = path.resolve(this.cwd, ap);
515
+ if (!existsSync(resolvedPath)) {
516
+ warnings.push(
517
+ `Agent "${agent.name}" additionalPath "${ap}" resolves to "${resolvedPath}" which does not exist`
518
+ );
519
+ }
520
+ }
521
+ }
522
+ }
523
+
524
+ // Cycle detection via topological sort
525
+ const stepNames = new Set(resolvedSteps.map((s) => s.name));
526
+ const inDegree = new Map<string, number>();
527
+ const adjacency = new Map<string, string[]>();
528
+ for (const step of resolvedSteps) {
529
+ inDegree.set(step.name, 0);
530
+ adjacency.set(step.name, []);
531
+ }
532
+ for (const step of resolvedSteps) {
533
+ for (const dep of step.dependsOn ?? []) {
534
+ if (stepNames.has(dep)) {
535
+ adjacency.get(dep)!.push(step.name);
536
+ inDegree.set(step.name, (inDegree.get(step.name) ?? 0) + 1);
537
+ }
538
+ }
539
+ }
540
+ const topoQueue: string[] = [];
541
+ for (const [name, deg] of inDegree) {
542
+ if (deg === 0) topoQueue.push(name);
543
+ }
544
+ let visited = 0;
545
+ while (topoQueue.length > 0) {
546
+ const node = topoQueue.shift()!;
547
+ visited++;
548
+ for (const neighbor of adjacency.get(node) ?? []) {
549
+ const newDeg = (inDegree.get(neighbor) ?? 1) - 1;
550
+ inDegree.set(neighbor, newDeg);
551
+ if (newDeg === 0) topoQueue.push(neighbor);
552
+ }
553
+ }
554
+ if (visited < resolvedSteps.length) {
555
+ errors.push(
556
+ 'Dependency cycle detected in workflow steps. Check dependsOn references for circular dependencies.'
557
+ );
558
+ }
559
+
560
+ // Missing dependency references
561
+ for (const step of resolvedSteps) {
562
+ for (const dep of step.dependsOn ?? []) {
563
+ if (!stepNames.has(dep)) {
564
+ errors.push(`Step "${step.name}" depends on unknown step "${dep}"`);
565
+ }
566
+ }
567
+ }
568
+
569
+ // Unreachable steps (steps that are never depended on and aren't root steps)
570
+ const dependedOn = new Set<string>();
571
+ for (const step of resolvedSteps) {
572
+ for (const dep of step.dependsOn ?? []) {
573
+ dependedOn.add(dep);
574
+ }
575
+ }
576
+
577
+ // Timeout warnings
578
+ for (const step of resolvedSteps) {
579
+ if (!step.timeoutMs) {
580
+ const agentDef = step.agent ? agentMap.get(step.agent) : undefined;
581
+ if (!agentDef?.constraints?.timeoutMs && !resolved.swarm.timeoutMs) {
582
+ warnings.push(
583
+ `Step "${step.name}" has no timeout configured (no step, agent, or swarm-level timeout)`
584
+ );
585
+ }
586
+ }
587
+ }
588
+
589
+ // Large dependency fan-in warning (decomposition guidance)
590
+ for (const step of resolvedSteps) {
591
+ if ((step.dependsOn?.length ?? 0) >= 5) {
592
+ warnings.push(
593
+ `Step "${step.name}" depends on ${step.dependsOn!.length} upstream steps. ` +
594
+ `Consider decomposing into smaller verification steps to reduce context size.`
595
+ );
596
+ }
597
+ }
598
+
599
+ // 4. Build agent summary
600
+ const agents = resolved.agents.map((a) => ({
601
+ name: a.name,
602
+ cli: a.cli,
603
+ role: a.role,
604
+ cwd: a.cwd,
605
+ stepCount: stepAgentCounts.get(a.name) ?? 0,
606
+ }));
607
+
608
+ // 5. Simulate execution waves
609
+ const waves: DryRunWave[] = [];
610
+ const completed = new Set<string>();
611
+ const allSteps = [...resolvedSteps];
612
+ let waveNum = 0;
613
+
614
+ while (completed.size < allSteps.length) {
615
+ const ready = allSteps.filter((step) => {
616
+ if (completed.has(step.name)) return false;
617
+ const deps = step.dependsOn ?? [];
618
+ return deps.every((dep) => completed.has(dep));
619
+ });
620
+
621
+ if (ready.length === 0) {
622
+ // Remaining steps are blocked — likely a cycle or unresolvable deps
623
+ const blocked = allSteps.filter((s) => !completed.has(s.name)).map((s) => s.name);
624
+ errors.push(`Blocked steps with unresolvable dependencies: ${blocked.join(', ')}`);
625
+ break;
626
+ }
627
+
628
+ waveNum++;
629
+ waves.push({
630
+ wave: waveNum,
631
+ steps: ready.map((s) => ({
632
+ name: s.name,
633
+ agent: s.agent,
634
+ dependsOn: s.dependsOn ?? [],
635
+ })),
636
+ });
637
+
638
+ for (const step of ready) {
639
+ completed.add(step.name);
640
+ }
641
+ }
642
+
643
+ // 6. Resource estimation
644
+ const peakConcurrency = Math.max(...waves.map((w) => w.steps.length), 0);
645
+ const totalAgentSteps = resolvedSteps.filter(
646
+ (s) => s.type !== 'deterministic' && s.type !== 'worktree'
647
+ ).length;
648
+
649
+ // 7. Check maxConcurrency against wave widths
650
+ const maxConcurrency = resolved.swarm.maxConcurrency;
651
+ if (maxConcurrency !== undefined) {
652
+ for (const wave of waves) {
653
+ if (wave.steps.length > maxConcurrency) {
654
+ warnings.push(
655
+ `Wave ${wave.wave} has ${wave.steps.length} parallel steps but maxConcurrency is ${maxConcurrency}`
656
+ );
657
+ }
658
+ }
659
+ }
660
+
661
+ return {
662
+ valid: errors.length === 0,
663
+ errors,
664
+ warnings,
665
+ name: workflow.name,
666
+ description: workflow.description ?? resolved.description,
667
+ pattern: resolved.swarm.pattern,
668
+ agents,
669
+ waves,
670
+ totalSteps: workflow.steps.length,
671
+ maxConcurrency,
672
+ estimatedWaves: waves.length,
673
+ estimatedPeakConcurrency: peakConcurrency,
674
+ estimatedTotalAgentSteps: totalAgentSteps,
675
+ };
676
+ }
677
+
678
+ private validateWorkflow(wf: unknown, agents: AgentDefinition[], source: string): void {
679
+ if (typeof wf !== 'object' || wf === null) {
680
+ throw new Error(`${source}: each workflow must be an object`);
681
+ }
682
+ const w = wf as Record<string, unknown>;
683
+ if (typeof w.name !== 'string') {
684
+ throw new Error(`${source}: each workflow must have a string "name"`);
685
+ }
686
+ if (!Array.isArray(w.steps) || w.steps.length === 0) {
687
+ throw new Error(`${source}: workflow "${w.name}" must have a non-empty "steps" array`);
688
+ }
689
+ for (const step of w.steps) {
690
+ if (typeof step !== 'object' || step === null) {
691
+ throw new Error(`${source}: each step must be an object`);
692
+ }
693
+ const s = step as Record<string, unknown>;
694
+ if (typeof s.name !== 'string') {
695
+ throw new Error(`${source}: each step must have a string "name" field`);
696
+ }
697
+
698
+ // Deterministic steps require type and command
699
+ if (s.type === 'deterministic') {
700
+ if (typeof s.command !== 'string') {
701
+ throw new Error(`${source}: deterministic step "${s.name}" must have a "command" field`);
702
+ }
703
+ } else {
704
+ // Agent steps (type undefined or 'agent') require agent and task
705
+ if (typeof s.agent !== 'string' || typeof s.task !== 'string') {
706
+ throw new Error(`${source}: agent step "${s.name}" must have "agent" and "task" string fields`);
707
+ }
708
+ }
709
+ }
710
+
711
+ // Validate DAG: check for unknown dependencies and cycles
712
+ const stepNames = new Set((w.steps as WorkflowStep[]).map((s) => s.name));
713
+ for (const step of w.steps as WorkflowStep[]) {
714
+ if (step.dependsOn) {
715
+ for (const dep of step.dependsOn) {
716
+ if (!stepNames.has(dep)) {
717
+ throw new Error(`${source}: step "${step.name}" depends on unknown step "${dep}"`);
718
+ }
719
+ }
720
+ }
721
+ }
722
+ this.detectCycles(w.steps as WorkflowStep[], source, w.name as string);
723
+ this.detectLeadWorkerDeadlock(w.steps as WorkflowStep[], agents, source, w.name as string);
724
+
725
+ // Warn if non-interactive agent task is excessively large before interpolation
726
+ for (const step of w.steps as WorkflowStep[]) {
727
+ if (step.type === 'deterministic' || step.type === 'worktree') continue;
728
+ const agentDef = agents.find((a) => a.name === step.agent);
729
+ const isNonInteractive =
730
+ agentDef?.interactive === false || ['worker', 'reviewer', 'analyst'].includes(agentDef?.preset ?? '');
731
+ if (isNonInteractive && (step.task ?? '').length > 10_000) {
732
+ console.warn(
733
+ `[WorkflowRunner] Warning: non-interactive step "${step.name}" has a very large task (${step.task!.length} chars). ` +
734
+ `Consider pre-reading files in a deterministic step and injecting only the relevant excerpt.`
735
+ );
736
+ }
737
+ }
738
+ }
739
+
740
+ private detectCycles(steps: WorkflowStep[], source: string, workflowName: string): void {
741
+ const adj = new Map<string, string[]>();
742
+ for (const step of steps) {
743
+ adj.set(step.name, step.dependsOn ?? []);
744
+ }
745
+
746
+ const visited = new Set<string>();
747
+ const inStack = new Set<string>();
748
+
749
+ const dfs = (node: string): void => {
750
+ if (inStack.has(node)) {
751
+ throw new Error(
752
+ `${source}: workflow "${workflowName}" contains a dependency cycle involving "${node}"`
753
+ );
754
+ }
755
+ if (visited.has(node)) return;
756
+ inStack.add(node);
757
+ for (const dep of adj.get(node) ?? []) {
758
+ dfs(dep);
759
+ }
760
+ inStack.delete(node);
761
+ visited.add(node);
762
+ };
763
+
764
+ for (const step of steps) {
765
+ dfs(step.name);
766
+ }
767
+ }
768
+
769
+ private detectLeadWorkerDeadlock(
770
+ steps: WorkflowStep[],
771
+ agents: AgentDefinition[],
772
+ source: string,
773
+ workflowName: string
774
+ ): void {
775
+ // Build a map of step name → steps that depend on it
776
+ const downstreamOf = new Map<string, string[]>();
777
+ for (const step of steps) {
778
+ for (const dep of step.dependsOn ?? []) {
779
+ if (!downstreamOf.has(dep)) downstreamOf.set(dep, []);
780
+ downstreamOf.get(dep)!.push(step.name);
781
+ }
782
+ }
783
+
784
+ for (const step of steps) {
785
+ // Only check interactive agent steps (leads)
786
+ if (step.type === 'deterministic' || step.type === 'worktree') continue;
787
+ const agentDef = agents.find((a) => a.name === step.agent);
788
+ // Skip non-interactive agents — they can't wait for channel signals
789
+ if (
790
+ agentDef?.interactive === false ||
791
+ agentDef?.preset === 'worker' ||
792
+ agentDef?.preset === 'reviewer' ||
793
+ agentDef?.preset === 'analyst'
794
+ )
795
+ continue;
796
+
797
+ const downstream = downstreamOf.get(step.name) ?? [];
798
+ if (downstream.length === 0) continue;
799
+
800
+ // Check if the task mentions downstream step names in a "waiting" context
801
+ const task = step.task ?? '';
802
+ const waitingKeywords = /\b(wait|waiting|monitor|check inbox|check.*channel|DONE|_DONE|signal)\b/i;
803
+ if (!waitingKeywords.test(task)) continue;
804
+
805
+ // Check if any downstream step name appears in the task
806
+ const mentioned = downstream.filter((name) => task.includes(name));
807
+ if (mentioned.length > 0) {
808
+ throw new Error(
809
+ `${source}: workflow "${workflowName}" likely has a lead\u2194worker deadlock. ` +
810
+ `Step "${step.name}" (interactive lead) mentions downstream step(s) [${mentioned.join(', ')}] in its task ` +
811
+ `and appears to wait for their signals, but those steps can't start until "${step.name}" completes. ` +
812
+ `Fix: make workers depend on a shared upstream step (e.g. "context"), not on the lead step. ` +
813
+ `See tests/workflows/README.md rule #6.`
814
+ );
815
+ }
816
+ }
817
+ }
818
+
819
+ // ── Template variable resolution ────────────────────────────────────────
820
+
821
+ /** Resolve {{variable}} placeholders in all task strings. */
822
+ resolveVariables(config: RelayYamlConfig, vars: VariableContext): RelayYamlConfig {
823
+ const resolved = structuredClone(config);
824
+
825
+ for (const agent of resolved.agents) {
826
+ if (agent.task) {
827
+ agent.task = this.interpolate(agent.task, vars);
828
+ }
829
+ }
830
+
831
+ if (resolved.workflows) {
832
+ for (const wf of resolved.workflows) {
833
+ for (const step of wf.steps) {
834
+ // Resolve variables in task (agent steps) and command (deterministic steps)
835
+ if (step.task) {
836
+ step.task = this.interpolate(step.task, vars);
837
+ }
838
+ if (step.command) {
839
+ step.command = this.interpolate(step.command, vars);
840
+ }
841
+ }
842
+ }
843
+ }
844
+
845
+ return resolved;
846
+ }
847
+
848
+ private interpolate(template: string, vars: VariableContext): string {
849
+ return template.replace(/\{\{([\w][\w.\-]*)\}\}/g, (_match, key: string) => {
850
+ // Skip step-output placeholders — they are resolved at execution time by interpolateStepTask()
851
+ if (key.startsWith('steps.')) {
852
+ return _match;
853
+ }
854
+
855
+ // Resolve dot-path variables like steps.plan.output
856
+ const value = this.resolveDotPath(key, vars);
857
+ if (value === undefined) {
858
+ throw new Error(`Unresolved variable: {{${key}}}`);
859
+ }
860
+ return String(value);
861
+ });
862
+ }
863
+
864
+ private resolveDotPath(key: string, vars: VariableContext): string | number | boolean | undefined {
865
+ // Simple key — direct lookup
866
+ if (!key.includes('.')) {
867
+ return vars[key];
868
+ }
869
+
870
+ // Dot-path — walk into nested context
871
+ const parts = key.split('.');
872
+ let current: unknown = vars;
873
+ for (const part of parts) {
874
+ if (current === null || current === undefined || typeof current !== 'object') {
875
+ return undefined;
876
+ }
877
+ current = (current as Record<string, unknown>)[part];
878
+ }
879
+
880
+ if (current === undefined || current === null) {
881
+ return undefined;
882
+ }
883
+ if (typeof current === 'string' || typeof current === 'number' || typeof current === 'boolean') {
884
+ return current;
885
+ }
886
+ return String(current);
887
+ }
888
+
889
+ /** Build a nested context from completed step outputs for {{steps.X.output}} resolution. */
890
+ private buildStepOutputContext(stepStates: Map<string, StepState>, runId?: string): VariableContext {
891
+ const steps: Record<string, { output: string }> = {};
892
+ for (const [name, state] of stepStates) {
893
+ if (state.row.status === 'completed' && state.row.output !== undefined) {
894
+ steps[name] = { output: state.row.output };
895
+ } else if (state.row.status === 'completed' && runId) {
896
+ // Recover from persisted output on disk (e.g., after restart)
897
+ const persisted = this.loadStepOutput(runId, name);
898
+ if (persisted) {
899
+ state.row.output = persisted;
900
+ steps[name] = { output: persisted };
901
+ }
902
+ }
903
+ }
904
+ return { steps } as unknown as VariableContext;
905
+ }
906
+
907
+ /** Interpolate step-output variables, silently skipping unresolved ones (they may be user vars). */
908
+ private interpolateStepTask(template: string, context: VariableContext): string {
909
+ return template.replace(/\{\{(steps\.[\w\-]+\.output)\}\}/g, (_match, key: string) => {
910
+ const value = this.resolveDotPath(key, context);
911
+ if (value === undefined) {
912
+ // Leave unresolved — may not be an error if the template doesn't depend on prior steps
913
+ return _match;
914
+ }
915
+ return String(value);
916
+ });
917
+ }
918
+
919
+ // ── Execution ───────────────────────────────────────────────────────────
920
+
921
+ /** Execute a named workflow from a validated config. */
922
+ async execute(
923
+ config: RelayYamlConfig,
924
+ workflowName?: string,
925
+ vars?: VariableContext
926
+ ): Promise<WorkflowRunRow> {
927
+ const resolved = vars ? this.resolveVariables(config, vars) : config;
928
+ const workflows = resolved.workflows ?? [];
929
+
930
+ const workflow = workflowName ? workflows.find((w) => w.name === workflowName) : workflows[0];
931
+
932
+ if (!workflow) {
933
+ throw new Error(
934
+ workflowName ? `Workflow "${workflowName}" not found in config` : 'No workflows defined in config'
935
+ );
936
+ }
937
+
938
+ // Load and resolve custom step definitions
939
+ const customSteps = loadCustomSteps(this.cwd);
940
+ const resolvedSteps = resolveAllCustomSteps(workflow.steps, customSteps);
941
+ const resolvedWorkflow = { ...workflow, steps: resolvedSteps };
942
+
943
+ const runId = this.generateId();
944
+ const now = new Date().toISOString();
945
+
946
+ const run: WorkflowRunRow = {
947
+ id: runId,
948
+ workspaceId: this.workspaceId,
949
+ workflowName: resolvedWorkflow.name,
950
+ pattern: resolved.swarm.pattern,
951
+ status: 'pending',
952
+ config: resolved,
953
+ startedAt: now,
954
+ createdAt: now,
955
+ updatedAt: now,
956
+ };
957
+
958
+ await this.db.insertRun(run);
959
+
960
+ // Build step rows
961
+ const stepStates = new Map<string, StepState>();
962
+ for (const step of resolvedWorkflow.steps) {
963
+ // Handle agent, deterministic, and worktree steps
964
+ const isNonAgent = step.type === 'deterministic' || step.type === 'worktree';
965
+
966
+ const stepRow: WorkflowStepRow = {
967
+ id: this.generateId(),
968
+ runId,
969
+ stepName: step.name,
970
+ agentName: isNonAgent ? null : (step.agent ?? null),
971
+ stepType: isNonAgent ? (step.type as 'deterministic' | 'worktree') : 'agent',
972
+ status: 'pending',
973
+ task:
974
+ step.type === 'deterministic'
975
+ ? (step.command ?? '')
976
+ : step.type === 'worktree'
977
+ ? (step.branch ?? '')
978
+ : (step.task ?? ''),
979
+ dependsOn: step.dependsOn ?? [],
980
+ retryCount: 0,
981
+ createdAt: now,
982
+ updatedAt: now,
983
+ };
984
+ await this.db.insertStep(stepRow);
985
+ stepStates.set(step.name, { row: stepRow });
986
+ }
987
+
988
+ return this.runWorkflowCore({
989
+ run,
990
+ workflow: resolvedWorkflow,
991
+ config: resolved,
992
+ stepStates,
993
+ isResume: false,
994
+ });
995
+ }
996
+
997
+ /** Resume a previously paused or partially completed run. */
998
+ async resume(runId: string, vars?: VariableContext): Promise<WorkflowRunRow> {
999
+ const run = await this.db.getRun(runId);
1000
+ if (!run) {
1001
+ throw new Error(`Run "${runId}" not found`);
1002
+ }
1003
+
1004
+ if (run.status !== 'running' && run.status !== 'failed') {
1005
+ throw new Error(`Run "${runId}" is in status "${run.status}" and cannot be resumed`);
1006
+ }
1007
+
1008
+ const config = vars ? this.resolveVariables(run.config, vars) : run.config;
1009
+ const workflows = config.workflows ?? [];
1010
+ const workflow = workflows.find((w) => w.name === run.workflowName);
1011
+ if (!workflow) {
1012
+ throw new Error(`Workflow "${run.workflowName}" not found in stored config`);
1013
+ }
1014
+
1015
+ const existingSteps = await this.db.getStepsByRunId(runId);
1016
+ const stepStates = new Map<string, StepState>();
1017
+ for (const stepRow of existingSteps) {
1018
+ stepStates.set(stepRow.stepName, { row: stepRow });
1019
+ }
1020
+
1021
+ // Reset failed steps to pending for retry
1022
+ for (const [, state] of stepStates) {
1023
+ if (state.row.status === 'failed') {
1024
+ state.row.status = 'pending';
1025
+ state.row.error = undefined;
1026
+ await this.db.updateStep(state.row.id, {
1027
+ status: 'pending',
1028
+ error: undefined,
1029
+ updatedAt: new Date().toISOString(),
1030
+ });
1031
+ }
1032
+ }
1033
+
1034
+ return this.runWorkflowCore({
1035
+ run,
1036
+ workflow,
1037
+ config,
1038
+ stepStates,
1039
+ isResume: true,
1040
+ });
1041
+ }
1042
+
1043
+ private async runWorkflowCore(input: {
1044
+ run: WorkflowRunRow;
1045
+ workflow: WorkflowDefinition;
1046
+ config: RelayYamlConfig;
1047
+ stepStates: Map<string, StepState>;
1048
+ isResume: boolean;
1049
+ }): Promise<WorkflowRunRow> {
1050
+ const { run, workflow, config, stepStates, isResume } = input;
1051
+ const runId = run.id;
1052
+
1053
+ // Start execution
1054
+ this.abortController = new AbortController();
1055
+ this.paused = false;
1056
+ this.currentConfig = config;
1057
+ this.currentRunId = runId;
1058
+ this.runStartTime = Date.now();
1059
+
1060
+ this.log(`Starting workflow "${workflow.name}" (${workflow.steps.length} steps)`);
1061
+
1062
+ // Initialize trajectory recording
1063
+ this.trajectory = new WorkflowTrajectory(config.trajectories, runId, this.cwd);
1064
+
1065
+ try {
1066
+ await this.updateRunStatus(runId, 'running');
1067
+ if (!isResume) {
1068
+ this.emit({ type: 'run:started', runId });
1069
+ }
1070
+
1071
+ const pendingCount = [...stepStates.values()].filter((s) => s.row.status === 'pending').length;
1072
+ if (isResume) {
1073
+ await this.trajectory.start(
1074
+ workflow.name,
1075
+ workflow.steps.length,
1076
+ `Resumed run: ${pendingCount} pending steps of ${workflow.steps.length} total`,
1077
+ config.description,
1078
+ config.swarm.pattern
1079
+ );
1080
+ } else {
1081
+ // Analyze DAG for trajectory context on first run
1082
+ const dagInfo = this.analyzeDAG(workflow.steps);
1083
+ await this.trajectory.start(
1084
+ workflow.name,
1085
+ workflow.steps.length,
1086
+ dagInfo,
1087
+ config.description,
1088
+ config.swarm.pattern
1089
+ );
1090
+ }
1091
+
1092
+ const channel =
1093
+ config.swarm.channel ??
1094
+ `wf-${this.sanitizeChannelName(config.name || run.workflowName)}-${this.generateShortId()}`;
1095
+ this.channel = channel;
1096
+ if (!config.swarm.channel) {
1097
+ config.swarm.channel = channel;
1098
+ await this.db.updateRun(runId, { config });
1099
+ }
1100
+ this.log('Resolving Relaycast API key...');
1101
+ await this.ensureRelaycastApiKey(channel);
1102
+ this.log('API key resolved');
1103
+ if (this.relayApiKeyAutoCreated && this.relayApiKey) {
1104
+ this.log(`Workspace created — follow this run in Relaycast:`);
1105
+ this.log(` RELAY_API_KEY=${this.relayApiKey}`);
1106
+ this.log(` Observer: https://observer.relaycast.dev (paste key above)`);
1107
+ this.log(` Channel: ${channel}`);
1108
+ }
1109
+
1110
+ this.log('Starting broker...');
1111
+ // Include a short run ID suffix in the broker name so each workflow execution
1112
+ // registers a unique identity in Relaycast. Without this, re-running in the same
1113
+ // workspace hits a 409 conflict because the previous run's agent is still registered.
1114
+ const brokerBaseName = path.basename(this.cwd) || 'workflow';
1115
+ const brokerName = `${brokerBaseName}-${runId.slice(0, 8)}`;
1116
+ this.relay = new AgentRelay({
1117
+ ...this.relayOptions,
1118
+ brokerName,
1119
+ channels: [channel],
1120
+ env: this.getRelayEnv(),
1121
+ // Workflows spawn agents across multiple waves; each spawn requires a PTY +
1122
+ // Relaycast registration. 60s is too tight when the broker is saturated with
1123
+ // long-running PTY processes from earlier steps. 120s gives room to breathe.
1124
+ requestTimeoutMs: this.relayOptions.requestTimeoutMs ?? 120_000,
1125
+ });
1126
+
1127
+ // Wire PTY output dispatcher — routes chunks to per-agent listeners + activity logging
1128
+ this.relay.onWorkerOutput = ({ name, chunk }) => {
1129
+ const listener = this.ptyListeners.get(name);
1130
+ if (listener) listener(chunk);
1131
+
1132
+ // Parse PTY output for high-signal activity
1133
+ const stripped = WorkflowRunner.stripAnsi(chunk);
1134
+ const shortName = name.replace(/-[a-f0-9]{6,}$/, '');
1135
+ let activity: string | undefined;
1136
+ if (/Read\(/.test(stripped)) {
1137
+ // Extract filename — path may be truncated at chunk boundary so require
1138
+ // at least a dir separator or 8+ chars to trust the basename.
1139
+ const m = stripped.match(/Read\(\s*~?([^\s)"']{8,})/);
1140
+ if (m) {
1141
+ const base = path.basename(m[1]);
1142
+ activity = base.length >= 3 ? `Reading ${base}` : 'Reading file...';
1143
+ } else {
1144
+ activity = 'Reading file...';
1145
+ }
1146
+ } else if (/Edit\(/.test(stripped)) {
1147
+ const m = stripped.match(/Edit\(\s*~?([^\s)"']{8,})/);
1148
+ if (m) {
1149
+ const base = path.basename(m[1]);
1150
+ activity = base.length >= 3 ? `Editing ${base}` : 'Editing file...';
1151
+ } else {
1152
+ activity = 'Editing file...';
1153
+ }
1154
+ } else if (/Bash\(/.test(stripped)) {
1155
+ // Extract a short preview of the command
1156
+ const m = stripped.match(/Bash\(\s*(.{1,40})/);
1157
+ activity = m ? `Running: ${m[1].trim()}...` : 'Running command...';
1158
+ } else if (/Explore\(/.test(stripped)) {
1159
+ const m = stripped.match(/Explore\(\s*(.{1,50})/);
1160
+ activity = m ? `Exploring: ${m[1].replace(/\).*/, '').trim()}` : 'Exploring codebase...';
1161
+ } else if (/Task\(/.test(stripped)) {
1162
+ activity = 'Running sub-agent...';
1163
+ } else if (/Sublimating|Thinking|Coalescing|Cultivating/.test(stripped)) {
1164
+ const m = stripped.match(/(\d+)s/);
1165
+ activity = m ? `Thinking... (${m[1]}s)` : 'Thinking...';
1166
+ }
1167
+ if (activity && this.lastActivity.get(name) !== activity) {
1168
+ this.lastActivity.set(name, activity);
1169
+ this.log(`[${shortName}] ${activity}`);
1170
+ }
1171
+ };
1172
+
1173
+ // Wire relay event hooks for rich console logging
1174
+ this.relay.onMessageReceived = (msg) => {
1175
+ const body = msg.text.length > 120 ? msg.text.slice(0, 117) + '...' : msg.text;
1176
+ const fromShort = msg.from.replace(/-[a-f0-9]{6,}$/, '');
1177
+ const toShort = msg.to.replace(/-[a-f0-9]{6,}$/, '');
1178
+ this.log(`[msg] ${fromShort} → ${toShort}: ${body}`);
1179
+ };
1180
+
1181
+ this.relay.onAgentSpawned = (agent) => {
1182
+ // Skip agents already managed by step execution
1183
+ if (!this.activeAgentHandles.has(agent.name)) {
1184
+ this.log(`[spawned] ${agent.name} (${agent.runtime})`);
1185
+ }
1186
+ };
1187
+
1188
+ this.relay.onAgentExited = (agent) => {
1189
+ this.lastActivity.delete(agent.name);
1190
+ this.lastIdleLog.delete(agent.name);
1191
+ if (!this.activeAgentHandles.has(agent.name)) {
1192
+ this.log(`[exited] ${agent.name} (code: ${agent.exitCode ?? '?'})`);
1193
+ }
1194
+ };
1195
+
1196
+ this.relay.onAgentIdle = ({ name, idleSecs }) => {
1197
+ // Only log at 30s multiples to avoid watchdog spam
1198
+ const bucket = Math.floor(idleSecs / 30) * 30;
1199
+ if (bucket >= 30 && this.lastIdleLog.get(name) !== bucket) {
1200
+ this.lastIdleLog.set(name, bucket);
1201
+ const shortName = name.replace(/-[a-f0-9]{6,}$/, '');
1202
+ this.log(`[idle] ${shortName} silent for ${bucket}s`);
1203
+ }
1204
+ };
1205
+
1206
+ this.relaycast = undefined;
1207
+ this.relaycastAgent = undefined;
1208
+
1209
+ // Wire broker stderr to console for observability
1210
+ this.unsubBrokerStderr = this.relay.onBrokerStderr((line: string) => {
1211
+ console.log(`[broker] ${line}`);
1212
+ });
1213
+
1214
+ this.log(`Creating channel: ${channel}...`);
1215
+ if (isResume) {
1216
+ await this.createAndJoinRelaycastChannel(channel);
1217
+ } else {
1218
+ await this.createAndJoinRelaycastChannel(channel, workflow.description);
1219
+ }
1220
+ this.log('Channel ready');
1221
+
1222
+ if (isResume) {
1223
+ this.postToChannel(`Workflow **${workflow.name}** resumed — ${pendingCount} pending steps`);
1224
+ } else {
1225
+ this.postToChannel(
1226
+ `Workflow **${workflow.name}** started — ${workflow.steps.length} steps, pattern: ${config.swarm.pattern}`
1227
+ );
1228
+ }
1229
+
1230
+ const agentMap = new Map<string, AgentDefinition>();
1231
+ for (const agent of config.agents) {
1232
+ agentMap.set(agent.name, agent);
1233
+ }
1234
+
1235
+ // Run preflight checks before any steps (skip on resume)
1236
+ if (!isResume && workflow.preflight?.length) {
1237
+ await this.runPreflightChecks(workflow.preflight, runId);
1238
+ }
1239
+
1240
+ // Pre-register all interactive agent steps with Relaycast before execution.
1241
+ // This warms the broker's token cache so spawn_agent calls are instant cache
1242
+ // hits rather than blocking on individual HTTP registrations per spawn.
1243
+ // Agent names use the run ID prefix (deterministic) so we can predict them.
1244
+ if (this.relay && !isResume) {
1245
+ const agentPreflight = workflow.steps
1246
+ .filter((s) => s.type !== 'deterministic' && s.type !== 'worktree' && s.agent)
1247
+ .map((s) => {
1248
+ const agentDef = agentMap.get(s.agent!);
1249
+ return agentDef && agentDef.interactive !== false
1250
+ ? { name: `${s.name}-${runId.slice(0, 8)}`, cli: agentDef.cli }
1251
+ : null;
1252
+ })
1253
+ .filter((e): e is { name: string; cli: AgentCli } => e !== null);
1254
+
1255
+ if (agentPreflight.length > 0) {
1256
+ this.log(`Pre-registering ${agentPreflight.length} agents with Relaycast...`);
1257
+ await this.relay.preflightAgents(agentPreflight).catch((err: Error) => {
1258
+ this.log(`[preflight-agents] warning: ${err.message} — continuing without pre-registration`);
1259
+ });
1260
+ this.log('Agent pre-registration complete');
1261
+ }
1262
+ }
1263
+
1264
+ this.log(`Executing ${workflow.steps.length} steps (pattern: ${config.swarm.pattern})`);
1265
+ await this.executeSteps(workflow, stepStates, agentMap, config.errorHandling, runId);
1266
+
1267
+ const allCompleted = [...stepStates.values()].every(
1268
+ (s) => s.row.status === 'completed' || s.row.status === 'skipped'
1269
+ );
1270
+
1271
+ if (allCompleted) {
1272
+ this.log('Workflow completed successfully');
1273
+ await this.updateRunStatus(runId, 'completed');
1274
+ this.emit({ type: 'run:completed', runId });
1275
+
1276
+ const outcomes = this.collectOutcomes(stepStates, workflow.steps);
1277
+ const summary = this.trajectory.buildRunSummary(outcomes);
1278
+ const confidence = this.trajectory.computeConfidence(outcomes);
1279
+ await this.trajectory.complete(summary, confidence, {
1280
+ learnings: this.trajectory.extractLearnings(outcomes),
1281
+ challenges: this.trajectory.extractChallenges(outcomes),
1282
+ });
1283
+
1284
+ this.postCompletionReport(workflow.name, outcomes, summary, confidence);
1285
+ this.logRunSummary(workflow.name, outcomes, runId);
1286
+ } else {
1287
+ const failedStep = [...stepStates.values()].find((s) => s.row.status === 'failed');
1288
+ const errorMsg = failedStep?.row.error ?? 'One or more steps failed';
1289
+ await this.updateRunStatus(runId, 'failed', errorMsg);
1290
+ this.emit({ type: 'run:failed', runId, error: errorMsg });
1291
+
1292
+ const outcomes = this.collectOutcomes(stepStates, workflow.steps);
1293
+ this.postFailureReport(workflow.name, outcomes, errorMsg);
1294
+ this.logRunSummary(workflow.name, outcomes, runId);
1295
+ await this.trajectory.abandon(errorMsg);
1296
+ }
1297
+ } catch (err) {
1298
+ const errorMsg = err instanceof Error ? err.message : String(err);
1299
+ const status: WorkflowRunStatus =
1300
+ !isResume && this.abortController?.signal.aborted ? 'cancelled' : 'failed';
1301
+ await this.updateRunStatus(runId, status, errorMsg);
1302
+
1303
+ if (status === 'cancelled') {
1304
+ this.emit({ type: 'run:cancelled', runId });
1305
+ this.postToChannel(`Workflow **${workflow.name}** cancelled`);
1306
+ await this.trajectory.abandon('Cancelled by user');
1307
+ } else {
1308
+ this.emit({ type: 'run:failed', runId, error: errorMsg });
1309
+ this.postToChannel(`Workflow failed: ${errorMsg}`);
1310
+ await this.trajectory.abandon(errorMsg);
1311
+ }
1312
+ } finally {
1313
+ for (const stream of this.ptyLogStreams.values()) stream.end();
1314
+ this.ptyLogStreams.clear();
1315
+ this.ptyOutputBuffers.clear();
1316
+ this.ptyListeners.clear();
1317
+
1318
+ this.unsubBrokerStderr?.();
1319
+ this.unsubBrokerStderr = undefined;
1320
+
1321
+ // Null out relay event hooks to prevent leaks
1322
+ if (this.relay) {
1323
+ this.relay.onMessageReceived = null;
1324
+ this.relay.onAgentSpawned = null;
1325
+ this.relay.onAgentExited = null;
1326
+ this.relay.onAgentIdle = null;
1327
+ this.relay.onWorkerOutput = null;
1328
+ }
1329
+ this.lastIdleLog.clear();
1330
+ this.lastActivity.clear();
1331
+
1332
+ this.log('Shutting down broker...');
1333
+ await this.relay?.shutdown();
1334
+ this.relay = undefined;
1335
+ this.runStartTime = undefined;
1336
+ this.relaycast = undefined;
1337
+ this.relaycastAgent = undefined;
1338
+ this.channel = undefined;
1339
+ this.trajectory = undefined;
1340
+ this.abortController = undefined;
1341
+ this.currentConfig = undefined;
1342
+ this.currentRunId = undefined;
1343
+ this.activeAgentHandles.clear();
1344
+ }
1345
+
1346
+ const finalRun = await this.db.getRun(runId);
1347
+ return finalRun ?? run;
1348
+ }
1349
+
1350
+ /** Pause execution. Currently-running steps will finish but no new steps start. */
1351
+ pause(): void {
1352
+ this.paused = true;
1353
+ }
1354
+
1355
+ /** Resume after a pause(). */
1356
+ unpause(): void {
1357
+ this.paused = false;
1358
+ this.pauseResolver?.();
1359
+ this.pauseResolver = undefined;
1360
+ }
1361
+
1362
+ /** Abort the current run. Running agents are released. */
1363
+ abort(): void {
1364
+ // Unblock waitIfPaused() so the run loop can exit
1365
+ this.pauseResolver?.();
1366
+ this.pauseResolver = undefined;
1367
+ this.abortController?.abort();
1368
+ }
1369
+
1370
+ // ── Step execution engine ─────────────────────────────────────────────
1371
+
1372
+ private async executeSteps(
1373
+ workflow: WorkflowDefinition,
1374
+ stepStates: Map<string, StepState>,
1375
+ agentMap: Map<string, AgentDefinition>,
1376
+ errorHandling: ErrorHandlingConfig | undefined,
1377
+ runId: string
1378
+ ): Promise<void> {
1379
+ const rawStrategy = errorHandling?.strategy ?? workflow.onError ?? 'fail-fast';
1380
+ // Map shorthand onError values to canonical strategy names.
1381
+ // 'retry' maps to 'fail-fast' so downstream steps are properly skipped after retries exhaust.
1382
+ const strategy =
1383
+ rawStrategy === 'fail'
1384
+ ? 'fail-fast'
1385
+ : rawStrategy === 'skip'
1386
+ ? 'continue'
1387
+ : rawStrategy === 'retry'
1388
+ ? 'fail-fast'
1389
+ : rawStrategy;
1390
+
1391
+ // DAG-based execution: repeatedly find ready steps and run them in parallel
1392
+ while (true) {
1393
+ this.checkAborted();
1394
+ await this.waitIfPaused();
1395
+
1396
+ const readySteps = this.findReadySteps(workflow.steps, stepStates);
1397
+ if (readySteps.length === 0) {
1398
+ // No steps ready — either all done or blocked
1399
+ break;
1400
+ }
1401
+
1402
+ // Begin a track chapter if multiple parallel steps are starting
1403
+ if (readySteps.length > 1 && this.trajectory) {
1404
+ const trackNames = readySteps.map((s) => s.name).join(', ');
1405
+ await this.trajectory.beginTrack(trackNames);
1406
+ }
1407
+
1408
+ // Stagger spawns when many steps are ready simultaneously.
1409
+ // All agents still run concurrently once spawned — this only delays when
1410
+ // each step's executeStep() begins, preventing Relaycast from receiving
1411
+ // N simultaneous registration requests which causes spawn timeouts.
1412
+ const STAGGER_THRESHOLD = 3;
1413
+ const STAGGER_DELAY_MS = 2_000;
1414
+ const results = await Promise.allSettled(
1415
+ readySteps.map((step, i) => {
1416
+ const delay = readySteps.length > STAGGER_THRESHOLD ? i * STAGGER_DELAY_MS : 0;
1417
+ if (delay === 0) {
1418
+ return this.executeStep(step, stepStates, agentMap, errorHandling, runId);
1419
+ }
1420
+ return new Promise<void>((resolve) => setTimeout(resolve, delay)).then(() =>
1421
+ this.executeStep(step, stepStates, agentMap, errorHandling, runId)
1422
+ );
1423
+ })
1424
+ );
1425
+
1426
+ // Collect outcomes from this batch for convergence reflection
1427
+ const batchOutcomes: StepOutcome[] = [];
1428
+
1429
+ for (let i = 0; i < results.length; i++) {
1430
+ const result = results[i];
1431
+ const step = readySteps[i];
1432
+ const state = stepStates.get(step.name);
1433
+
1434
+ if (result.status === 'rejected') {
1435
+ const error = result.reason instanceof Error ? result.reason.message : String(result.reason);
1436
+ if (state && state.row.status !== 'failed') {
1437
+ await this.markStepFailed(state, error, runId);
1438
+ }
1439
+
1440
+ batchOutcomes.push({
1441
+ name: step.name,
1442
+ agent: step.agent ?? 'deterministic',
1443
+ status: 'failed',
1444
+ attempts: (state?.row.retryCount ?? 0) + 1,
1445
+ error,
1446
+ });
1447
+
1448
+ if (strategy === 'fail-fast') {
1449
+ // Mark all pending downstream steps as skipped
1450
+ await this.markDownstreamSkipped(step.name, workflow.steps, stepStates, runId);
1451
+ throw new Error(`Step "${step.name}" failed: ${error}`);
1452
+ }
1453
+
1454
+ if (strategy === 'continue') {
1455
+ await this.markDownstreamSkipped(step.name, workflow.steps, stepStates, runId);
1456
+ }
1457
+ } else {
1458
+ batchOutcomes.push({
1459
+ name: step.name,
1460
+ agent: step.agent ?? 'deterministic',
1461
+ status: state?.row.status === 'completed' ? 'completed' : 'failed',
1462
+ attempts: (state?.row.retryCount ?? 0) + 1,
1463
+ output: state?.row.output,
1464
+ verificationPassed: state?.row.status === 'completed' && step.verification !== undefined,
1465
+ });
1466
+ }
1467
+ }
1468
+
1469
+ // Reflect at convergence when a parallel batch completes
1470
+ if (readySteps.length > 1 && this.trajectory?.shouldReflectOnConverge()) {
1471
+ const label = readySteps.map((s) => s.name).join(' + ');
1472
+ // Find steps that this batch unblocks
1473
+ const completedNames = new Set(
1474
+ batchOutcomes.filter((o) => o.status === 'completed').map((o) => o.name)
1475
+ );
1476
+ const unblocked = workflow.steps
1477
+ .filter((s) => s.dependsOn?.some((dep) => completedNames.has(dep)))
1478
+ .filter((s) => {
1479
+ const st = stepStates.get(s.name);
1480
+ return st && st.row.status === 'pending';
1481
+ })
1482
+ .map((s) => s.name);
1483
+
1484
+ await this.trajectory.synthesizeAndReflect(
1485
+ label,
1486
+ batchOutcomes,
1487
+ unblocked.length > 0 ? unblocked : undefined
1488
+ );
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ private findReadySteps(steps: WorkflowStep[], stepStates: Map<string, StepState>): WorkflowStep[] {
1494
+ return steps.filter((step) => {
1495
+ const state = stepStates.get(step.name);
1496
+ if (!state || state.row.status !== 'pending') return false;
1497
+
1498
+ const deps = step.dependsOn ?? [];
1499
+ return deps.every((dep) => {
1500
+ const depState = stepStates.get(dep);
1501
+ return depState && (depState.row.status === 'completed' || depState.row.status === 'skipped');
1502
+ });
1503
+ });
1504
+ }
1505
+
1506
+ /**
1507
+ * Execute preflight checks before any workflow steps.
1508
+ * All checks must pass or the workflow fails immediately.
1509
+ */
1510
+ private async runPreflightChecks(checks: PreflightCheck[], runId: string): Promise<void> {
1511
+ this.postToChannel(`Running ${checks.length} preflight check(s)...`);
1512
+
1513
+ for (const check of checks) {
1514
+ this.checkAborted();
1515
+
1516
+ const description = check.description ?? check.command.slice(0, 50);
1517
+ this.postToChannel(`**[preflight]** ${description}`);
1518
+
1519
+ try {
1520
+ const output = await new Promise<string>((resolve, reject) => {
1521
+ const child = cpSpawn('sh', ['-c', check.command], {
1522
+ stdio: 'pipe',
1523
+ cwd: this.cwd,
1524
+ env: { ...process.env },
1525
+ });
1526
+
1527
+ const stdoutChunks: string[] = [];
1528
+ const stderrChunks: string[] = [];
1529
+
1530
+ // Wire abort signal
1531
+ const abortSignal = this.abortController?.signal;
1532
+ let abortHandler: (() => void) | undefined;
1533
+ if (abortSignal && !abortSignal.aborted) {
1534
+ abortHandler = () => {
1535
+ child.kill('SIGTERM');
1536
+ };
1537
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
1538
+ }
1539
+
1540
+ // 30s timeout for preflight checks
1541
+ const timer = setTimeout(() => {
1542
+ child.kill('SIGTERM');
1543
+ reject(new Error(`Preflight check timed out: ${description}`));
1544
+ }, 30_000);
1545
+
1546
+ child.stdout?.on('data', (chunk: Buffer) => {
1547
+ stdoutChunks.push(chunk.toString());
1548
+ });
1549
+
1550
+ child.stderr?.on('data', (chunk: Buffer) => {
1551
+ stderrChunks.push(chunk.toString());
1552
+ });
1553
+
1554
+ child.on('close', (code) => {
1555
+ clearTimeout(timer);
1556
+ if (abortHandler && abortSignal) {
1557
+ abortSignal.removeEventListener('abort', abortHandler);
1558
+ }
1559
+
1560
+ if (abortSignal?.aborted) {
1561
+ reject(new Error('Preflight check aborted'));
1562
+ return;
1563
+ }
1564
+
1565
+ // Non-zero exit code is a failure
1566
+ if (code !== 0 && code !== null) {
1567
+ const stderr = stderrChunks.join('');
1568
+ reject(
1569
+ new Error(`Preflight check failed (exit ${code})${stderr ? `: ${stderr.slice(0, 200)}` : ''}`)
1570
+ );
1571
+ return;
1572
+ }
1573
+
1574
+ resolve(stdoutChunks.join(''));
1575
+ });
1576
+
1577
+ child.on('error', (err) => {
1578
+ clearTimeout(timer);
1579
+ if (abortHandler && abortSignal) {
1580
+ abortSignal.removeEventListener('abort', abortHandler);
1581
+ }
1582
+ reject(new Error(`Preflight check error: ${err.message}`));
1583
+ });
1584
+ });
1585
+
1586
+ // Check failIf condition
1587
+ if (check.failIf) {
1588
+ const trimmedOutput = output.trim();
1589
+ if (check.failIf === 'non-empty' && trimmedOutput.length > 0) {
1590
+ throw new Error(`Preflight failed: output is non-empty\n${trimmedOutput.slice(0, 200)}`);
1591
+ }
1592
+ if (check.failIf === 'empty' && trimmedOutput.length === 0) {
1593
+ throw new Error('Preflight failed: output is empty');
1594
+ }
1595
+ // Treat as regex pattern
1596
+ if (check.failIf !== 'non-empty' && check.failIf !== 'empty') {
1597
+ const regex = new RegExp(check.failIf);
1598
+ if (regex.test(output)) {
1599
+ throw new Error(`Preflight failed: output matches pattern "${check.failIf}"`);
1600
+ }
1601
+ }
1602
+ }
1603
+
1604
+ // Check successIf condition
1605
+ if (check.successIf) {
1606
+ const regex = new RegExp(check.successIf);
1607
+ if (!regex.test(output)) {
1608
+ throw new Error(`Preflight failed: output does not match required pattern "${check.successIf}"`);
1609
+ }
1610
+ }
1611
+
1612
+ this.postToChannel(`**[preflight]** ${description} — passed`);
1613
+ } catch (err) {
1614
+ const errorMsg = err instanceof Error ? err.message : String(err);
1615
+ this.postToChannel(`**[preflight]** ${description} — FAILED: ${errorMsg}`);
1616
+ throw new Error(`Preflight check failed: ${errorMsg}`);
1617
+ }
1618
+ }
1619
+
1620
+ this.postToChannel('All preflight checks passed');
1621
+ }
1622
+
1623
+ /** Check if a step is deterministic (shell command) vs agent (LLM-powered). */
1624
+ private isDeterministicStep(step: WorkflowStep): boolean {
1625
+ return step.type === 'deterministic';
1626
+ }
1627
+
1628
+ /** Check if a step is a worktree (git worktree setup) step. */
1629
+ private isWorktreeStep(step: WorkflowStep): boolean {
1630
+ return step.type === 'worktree';
1631
+ }
1632
+
1633
+ private async executeStep(
1634
+ step: WorkflowStep,
1635
+ stepStates: Map<string, StepState>,
1636
+ agentMap: Map<string, AgentDefinition>,
1637
+ errorHandling: ErrorHandlingConfig | undefined,
1638
+ runId: string
1639
+ ): Promise<void> {
1640
+ // Branch: deterministic steps execute shell commands
1641
+ if (this.isDeterministicStep(step)) {
1642
+ return this.executeDeterministicStep(step, stepStates, runId);
1643
+ }
1644
+
1645
+ // Branch: worktree steps set up git worktrees
1646
+ if (this.isWorktreeStep(step)) {
1647
+ return this.executeWorktreeStep(step, stepStates, runId);
1648
+ }
1649
+
1650
+ // Agent step execution
1651
+ return this.executeAgentStep(step, stepStates, agentMap, errorHandling, runId);
1652
+ }
1653
+
1654
+ /**
1655
+ * Execute a deterministic step (shell command).
1656
+ * Fast, reliable, $0 LLM cost.
1657
+ */
1658
+ private async executeDeterministicStep(
1659
+ step: WorkflowStep,
1660
+ stepStates: Map<string, StepState>,
1661
+ runId: string
1662
+ ): Promise<void> {
1663
+ const state = stepStates.get(step.name);
1664
+ if (!state) throw new Error(`Step state not found: ${step.name}`);
1665
+
1666
+ this.checkAborted();
1667
+
1668
+ // Mark step as running
1669
+ state.row.status = 'running';
1670
+ state.row.startedAt = new Date().toISOString();
1671
+ await this.db.updateStep(state.row.id, {
1672
+ status: 'running',
1673
+ startedAt: state.row.startedAt,
1674
+ updatedAt: new Date().toISOString(),
1675
+ });
1676
+ this.emit({ type: 'step:started', runId, stepName: step.name });
1677
+ this.postToChannel(`**[${step.name}]** Started (deterministic)`);
1678
+
1679
+ // Resolve variables in the command (e.g., {{steps.plan.output}}, {{branch-name}})
1680
+ const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
1681
+ let resolvedCommand = this.interpolateStepTask(step.command ?? '', stepOutputContext);
1682
+
1683
+ // Also resolve simple {{variable}} placeholders (already resolved in top-level config but safe to re-run)
1684
+ resolvedCommand = resolvedCommand.replace(/\{\{([\w][\w.\-]*)\}\}/g, (_match, key: string) => {
1685
+ if (key.startsWith('steps.')) return _match; // Already handled above
1686
+ const value = this.resolveDotPath(key, stepOutputContext);
1687
+ return value !== undefined ? String(value) : _match;
1688
+ });
1689
+
1690
+ try {
1691
+ const output = await new Promise<string>((resolve, reject) => {
1692
+ const child = cpSpawn('sh', ['-c', resolvedCommand], {
1693
+ stdio: 'pipe',
1694
+ cwd: this.cwd,
1695
+ env: { ...process.env },
1696
+ });
1697
+
1698
+ const stdoutChunks: string[] = [];
1699
+ const stderrChunks: string[] = [];
1700
+
1701
+ // Wire abort signal
1702
+ const abortSignal = this.abortController?.signal;
1703
+ let abortHandler: (() => void) | undefined;
1704
+ if (abortSignal && !abortSignal.aborted) {
1705
+ abortHandler = () => {
1706
+ child.kill('SIGTERM');
1707
+ setTimeout(() => child.kill('SIGKILL'), 5000);
1708
+ };
1709
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
1710
+ }
1711
+
1712
+ // Handle timeout
1713
+ let timedOut = false;
1714
+ let timer: ReturnType<typeof setTimeout> | undefined;
1715
+ if (step.timeoutMs) {
1716
+ timer = setTimeout(() => {
1717
+ timedOut = true;
1718
+ child.kill('SIGTERM');
1719
+ setTimeout(() => child.kill('SIGKILL'), 5000);
1720
+ }, step.timeoutMs);
1721
+ }
1722
+
1723
+ child.stdout?.on('data', (chunk: Buffer) => {
1724
+ stdoutChunks.push(chunk.toString());
1725
+ });
1726
+
1727
+ child.stderr?.on('data', (chunk: Buffer) => {
1728
+ stderrChunks.push(chunk.toString());
1729
+ });
1730
+
1731
+ child.on('close', (code) => {
1732
+ if (timer) clearTimeout(timer);
1733
+ if (abortHandler && abortSignal) {
1734
+ abortSignal.removeEventListener('abort', abortHandler);
1735
+ }
1736
+
1737
+ if (abortSignal?.aborted) {
1738
+ reject(new Error(`Step "${step.name}" aborted`));
1739
+ return;
1740
+ }
1741
+
1742
+ if (timedOut) {
1743
+ reject(
1744
+ new Error(`Step "${step.name}" timed out (no step timeout set, check global swarm.timeoutMs)`)
1745
+ );
1746
+ return;
1747
+ }
1748
+
1749
+ const stdout = stdoutChunks.join('');
1750
+ const stderr = stderrChunks.join('');
1751
+
1752
+ // Check exit code unless failOnError is explicitly false
1753
+ const failOnError = step.failOnError !== false;
1754
+ if (failOnError && code !== 0 && code !== null) {
1755
+ reject(
1756
+ new Error(`Command failed with exit code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ''}`)
1757
+ );
1758
+ return;
1759
+ }
1760
+
1761
+ resolve(step.captureOutput !== false ? stdout : `Command completed (exit code ${code ?? 0})`);
1762
+ });
1763
+
1764
+ child.on('error', (err) => {
1765
+ if (timer) clearTimeout(timer);
1766
+ if (abortHandler && abortSignal) {
1767
+ abortSignal.removeEventListener('abort', abortHandler);
1768
+ }
1769
+ reject(new Error(`Failed to execute command: ${err.message}`));
1770
+ });
1771
+ });
1772
+
1773
+ // Mark completed
1774
+ state.row.status = 'completed';
1775
+ state.row.output = output;
1776
+ state.row.completedAt = new Date().toISOString();
1777
+ await this.db.updateStep(state.row.id, {
1778
+ status: 'completed',
1779
+ output,
1780
+ completedAt: state.row.completedAt,
1781
+ updatedAt: new Date().toISOString(),
1782
+ });
1783
+
1784
+ // Persist step output
1785
+ await this.persistStepOutput(runId, step.name, output);
1786
+
1787
+ this.emit({ type: 'step:completed', runId, stepName: step.name, output });
1788
+ this.postToChannel(
1789
+ `**[${step.name}]** Completed (deterministic)\n${output.slice(0, 500)}${output.length > 500 ? '\n...(truncated)' : ''}`
1790
+ );
1791
+ } catch (err) {
1792
+ const errorMsg = err instanceof Error ? err.message : String(err);
1793
+ this.postToChannel(`**[${step.name}]** Failed: ${errorMsg}`);
1794
+ await this.markStepFailed(state, errorMsg, runId);
1795
+ throw new Error(`Step "${step.name}" failed: ${errorMsg}`);
1796
+ }
1797
+ }
1798
+
1799
+ /**
1800
+ * Execute a worktree step (git worktree setup).
1801
+ * Fast, reliable, $0 LLM cost.
1802
+ * Outputs the worktree path for downstream steps to use.
1803
+ */
1804
+ private async executeWorktreeStep(
1805
+ step: WorkflowStep,
1806
+ stepStates: Map<string, StepState>,
1807
+ runId: string
1808
+ ): Promise<void> {
1809
+ const state = stepStates.get(step.name);
1810
+ if (!state) throw new Error(`Step state not found: ${step.name}`);
1811
+
1812
+ this.checkAborted();
1813
+
1814
+ // Mark step as running
1815
+ state.row.status = 'running';
1816
+ state.row.startedAt = new Date().toISOString();
1817
+ await this.db.updateStep(state.row.id, {
1818
+ status: 'running',
1819
+ startedAt: state.row.startedAt,
1820
+ updatedAt: new Date().toISOString(),
1821
+ });
1822
+ this.emit({ type: 'step:started', runId, stepName: step.name });
1823
+ this.postToChannel(`**[${step.name}]** Started (worktree setup)`);
1824
+
1825
+ // Resolve variables in branch name and path
1826
+ const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
1827
+ const branch = this.interpolateStepTask(step.branch ?? '', stepOutputContext);
1828
+ const baseBranch = step.baseBranch
1829
+ ? this.interpolateStepTask(step.baseBranch, stepOutputContext)
1830
+ : 'HEAD';
1831
+ const worktreePath = step.path
1832
+ ? this.interpolateStepTask(step.path, stepOutputContext)
1833
+ : path.join('.worktrees', step.name);
1834
+ const createBranch = step.createBranch !== false;
1835
+
1836
+ if (!branch) {
1837
+ const errorMsg = 'Worktree step missing required "branch" field';
1838
+ await this.markStepFailed(state, errorMsg, runId);
1839
+ throw new Error(`Step "${step.name}" failed: ${errorMsg}`);
1840
+ }
1841
+
1842
+ try {
1843
+ // Build the git worktree command
1844
+ // If createBranch is true and branch doesn't exist, use -b flag
1845
+ const absoluteWorktreePath = path.resolve(this.cwd, worktreePath);
1846
+
1847
+ // First, check if the branch already exists
1848
+ const checkBranchCmd = `git rev-parse --verify --quiet ${branch} 2>/dev/null`;
1849
+ let branchExists = false;
1850
+
1851
+ await new Promise<void>((resolve) => {
1852
+ const checkChild = cpSpawn('sh', ['-c', checkBranchCmd], {
1853
+ stdio: 'pipe',
1854
+ cwd: this.cwd,
1855
+ env: { ...process.env },
1856
+ });
1857
+ checkChild.on('close', (code) => {
1858
+ branchExists = code === 0;
1859
+ resolve();
1860
+ });
1861
+ checkChild.on('error', () => resolve());
1862
+ });
1863
+
1864
+ // Build appropriate worktree add command
1865
+ let worktreeCmd: string;
1866
+ if (branchExists) {
1867
+ // Branch exists, just checkout into worktree
1868
+ worktreeCmd = `git worktree add "${absoluteWorktreePath}" ${branch}`;
1869
+ } else if (createBranch) {
1870
+ // Create new branch from baseBranch
1871
+ worktreeCmd = `git worktree add -b ${branch} "${absoluteWorktreePath}" ${baseBranch}`;
1872
+ } else {
1873
+ // Branch doesn't exist and we're not creating it
1874
+ const errorMsg = `Branch "${branch}" does not exist and createBranch is false`;
1875
+ await this.markStepFailed(state, errorMsg, runId);
1876
+ throw new Error(`Step "${step.name}" failed: ${errorMsg}`);
1877
+ }
1878
+
1879
+ const output = await new Promise<string>((resolve, reject) => {
1880
+ const child = cpSpawn('sh', ['-c', worktreeCmd], {
1881
+ stdio: 'pipe',
1882
+ cwd: this.cwd,
1883
+ env: { ...process.env },
1884
+ });
1885
+
1886
+ const stdoutChunks: string[] = [];
1887
+ const stderrChunks: string[] = [];
1888
+
1889
+ // Wire abort signal
1890
+ const abortSignal = this.abortController?.signal;
1891
+ let abortHandler: (() => void) | undefined;
1892
+ if (abortSignal && !abortSignal.aborted) {
1893
+ abortHandler = () => {
1894
+ child.kill('SIGTERM');
1895
+ setTimeout(() => child.kill('SIGKILL'), 5000);
1896
+ };
1897
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
1898
+ }
1899
+
1900
+ // Handle timeout
1901
+ let timedOut = false;
1902
+ let timer: ReturnType<typeof setTimeout> | undefined;
1903
+ if (step.timeoutMs) {
1904
+ timer = setTimeout(() => {
1905
+ timedOut = true;
1906
+ child.kill('SIGTERM');
1907
+ setTimeout(() => child.kill('SIGKILL'), 5000);
1908
+ }, step.timeoutMs);
1909
+ }
1910
+
1911
+ child.stdout?.on('data', (chunk: Buffer) => {
1912
+ stdoutChunks.push(chunk.toString());
1913
+ });
1914
+
1915
+ child.stderr?.on('data', (chunk: Buffer) => {
1916
+ stderrChunks.push(chunk.toString());
1917
+ });
1918
+
1919
+ child.on('close', (code) => {
1920
+ if (timer) clearTimeout(timer);
1921
+ if (abortHandler && abortSignal) {
1922
+ abortSignal.removeEventListener('abort', abortHandler);
1923
+ }
1924
+
1925
+ if (abortSignal?.aborted) {
1926
+ reject(new Error(`Step "${step.name}" aborted`));
1927
+ return;
1928
+ }
1929
+
1930
+ if (timedOut) {
1931
+ reject(
1932
+ new Error(`Step "${step.name}" timed out (no step timeout set, check global swarm.timeoutMs)`)
1933
+ );
1934
+ return;
1935
+ }
1936
+
1937
+ const stderr = stderrChunks.join('');
1938
+
1939
+ if (code !== 0 && code !== null) {
1940
+ reject(
1941
+ new Error(
1942
+ `git worktree add failed with exit code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ''}`
1943
+ )
1944
+ );
1945
+ return;
1946
+ }
1947
+
1948
+ // Output the worktree path for downstream steps
1949
+ resolve(absoluteWorktreePath);
1950
+ });
1951
+
1952
+ child.on('error', (err) => {
1953
+ if (timer) clearTimeout(timer);
1954
+ if (abortHandler && abortSignal) {
1955
+ abortSignal.removeEventListener('abort', abortHandler);
1956
+ }
1957
+ reject(new Error(`Failed to execute git worktree command: ${err.message}`));
1958
+ });
1959
+ });
1960
+
1961
+ // Mark completed
1962
+ state.row.status = 'completed';
1963
+ state.row.output = output;
1964
+ state.row.completedAt = new Date().toISOString();
1965
+ await this.db.updateStep(state.row.id, {
1966
+ status: 'completed',
1967
+ output,
1968
+ completedAt: state.row.completedAt,
1969
+ updatedAt: new Date().toISOString(),
1970
+ });
1971
+
1972
+ // Persist step output
1973
+ await this.persistStepOutput(runId, step.name, output);
1974
+
1975
+ this.emit({ type: 'step:completed', runId, stepName: step.name, output });
1976
+ this.postToChannel(
1977
+ `**[${step.name}]** Worktree created at: ${output}\n Branch: ${branch}${!branchExists && createBranch ? ' (created)' : ''}`
1978
+ );
1979
+ } catch (err) {
1980
+ const errorMsg = err instanceof Error ? err.message : String(err);
1981
+ this.postToChannel(`**[${step.name}]** Failed: ${errorMsg}`);
1982
+ await this.markStepFailed(state, errorMsg, runId);
1983
+ throw new Error(`Step "${step.name}" failed: ${errorMsg}`);
1984
+ }
1985
+ }
1986
+
1987
+ /**
1988
+ * Execute an agent step (LLM-powered).
1989
+ */
1990
+ private async executeAgentStep(
1991
+ step: WorkflowStep,
1992
+ stepStates: Map<string, StepState>,
1993
+ agentMap: Map<string, AgentDefinition>,
1994
+ errorHandling: ErrorHandlingConfig | undefined,
1995
+ runId: string
1996
+ ): Promise<void> {
1997
+ const state = stepStates.get(step.name);
1998
+ if (!state) throw new Error(`Step state not found: ${step.name}`);
1999
+
2000
+ const agentName = step.agent;
2001
+ if (!agentName) {
2002
+ throw new Error(`Step "${step.name}" is missing required "agent" field`);
2003
+ }
2004
+ const rawAgentDef = agentMap.get(agentName);
2005
+ if (!rawAgentDef) {
2006
+ throw new Error(`Agent "${agentName}" not found in config`);
2007
+ }
2008
+ const agentDef = WorkflowRunner.resolveAgentDef(rawAgentDef);
2009
+
2010
+ const maxRetries = step.retries ?? agentDef.constraints?.retries ?? errorHandling?.maxRetries ?? 0;
2011
+ const retryDelay = errorHandling?.retryDelayMs ?? 1000;
2012
+ const timeoutMs =
2013
+ step.timeoutMs ?? agentDef.constraints?.timeoutMs ?? this.currentConfig?.swarm?.timeoutMs;
2014
+
2015
+ let lastError: string | undefined;
2016
+
2017
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2018
+ this.checkAborted();
2019
+
2020
+ if (attempt > 0) {
2021
+ this.emit({ type: 'step:retrying', runId, stepName: step.name, attempt });
2022
+ this.postToChannel(`**[${step.name}]** Retrying (attempt ${attempt + 1}/${maxRetries + 1})`);
2023
+ state.row.retryCount = attempt;
2024
+ await this.db.updateStep(state.row.id, {
2025
+ retryCount: attempt,
2026
+ updatedAt: new Date().toISOString(),
2027
+ });
2028
+ await this.trajectory?.stepRetrying(step, attempt, maxRetries);
2029
+ await this.delay(retryDelay);
2030
+ }
2031
+
2032
+ try {
2033
+ // Mark step as running
2034
+ state.row.status = 'running';
2035
+ state.row.startedAt = new Date().toISOString();
2036
+ await this.db.updateStep(state.row.id, {
2037
+ status: 'running',
2038
+ startedAt: state.row.startedAt,
2039
+ updatedAt: new Date().toISOString(),
2040
+ });
2041
+ this.emit({ type: 'step:started', runId, stepName: step.name });
2042
+ this.postToChannel(`**[${step.name}]** Started (agent: ${agentDef.name})`);
2043
+ await this.trajectory?.stepStarted(step, agentDef.name);
2044
+
2045
+ // Resolve step-output variables (e.g. {{steps.plan.output}}) at execution time
2046
+ const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
2047
+ let resolvedTask = this.interpolateStepTask(step.task ?? '', stepOutputContext);
2048
+
2049
+ // If this is an interactive agent, append awareness of non-interactive workers
2050
+ // so the lead knows not to message them and to use step output chaining instead
2051
+ if (agentDef.interactive !== false) {
2052
+ const nonInteractiveInfo = this.buildNonInteractiveAwareness(agentMap, stepStates);
2053
+ if (nonInteractiveInfo) {
2054
+ resolvedTask += nonInteractiveInfo;
2055
+ }
2056
+ }
2057
+
2058
+ // Spawn agent via AgentRelay
2059
+ this.log(`[${step.name}] Spawning agent "${agentDef.name}" (cli: ${agentDef.cli})`);
2060
+ const resolvedStep = { ...step, task: resolvedTask };
2061
+ const output = await this.spawnAndWait(agentDef, resolvedStep, timeoutMs);
2062
+ this.log(`[${step.name}] Agent "${agentDef.name}" exited`);
2063
+
2064
+ // Run verification if configured
2065
+ if (step.verification) {
2066
+ this.runVerification(step.verification, output, step.name, resolvedTask);
2067
+ }
2068
+
2069
+ // Mark completed
2070
+ state.row.status = 'completed';
2071
+ state.row.output = output;
2072
+ state.row.completedAt = new Date().toISOString();
2073
+ await this.db.updateStep(state.row.id, {
2074
+ status: 'completed',
2075
+ output,
2076
+ completedAt: state.row.completedAt,
2077
+ updatedAt: new Date().toISOString(),
2078
+ });
2079
+
2080
+ // Persist step output to disk so it survives restarts and is inspectable
2081
+ await this.persistStepOutput(runId, step.name, output);
2082
+
2083
+ this.emit({ type: 'step:completed', runId, stepName: step.name, output });
2084
+ this.postToChannel(
2085
+ `**[${step.name}]** Completed\n${output.slice(0, 500)}${output.length > 500 ? '\n...(truncated)' : ''}`
2086
+ );
2087
+ await this.trajectory?.stepCompleted(step, output, attempt + 1);
2088
+ return;
2089
+ } catch (err) {
2090
+ lastError = err instanceof Error ? err.message : String(err);
2091
+ }
2092
+ }
2093
+
2094
+ // All retries exhausted — record root-cause diagnosis and mark failed
2095
+ const nonInteractive =
2096
+ agentDef.interactive === false || ['worker', 'reviewer', 'analyst'].includes(agentDef.preset ?? '');
2097
+ const verificationValue =
2098
+ typeof step.verification === 'object' && 'value' in step.verification
2099
+ ? String(step.verification.value)
2100
+ : undefined;
2101
+ await this.trajectory?.stepFailed(step, lastError ?? 'Unknown error', maxRetries + 1, maxRetries, {
2102
+ agent: agentName,
2103
+ nonInteractive,
2104
+ verificationValue,
2105
+ });
2106
+ this.postToChannel(`**[${step.name}]** Failed: ${lastError ?? 'Unknown error'}`);
2107
+ await this.markStepFailed(state, lastError ?? 'Unknown error', runId);
2108
+ throw new Error(
2109
+ `Step "${step.name}" failed after ${maxRetries} retries: ${lastError ?? 'Unknown error'}`
2110
+ );
2111
+ }
2112
+
2113
+ /**
2114
+ * Build the CLI command and arguments for a non-interactive agent execution.
2115
+ * Each CLI has a specific flag for one-shot prompt mode.
2116
+ */
2117
+ static buildNonInteractiveCommand(
2118
+ cli: AgentCli,
2119
+ task: string,
2120
+ extraArgs: string[] = []
2121
+ ): { cmd: string; args: string[] } {
2122
+ switch (cli) {
2123
+ case 'claude':
2124
+ // --dangerously-skip-permissions prevents any tool-use permission prompt
2125
+ // from blocking the process when stdio is piped (no TTY available).
2126
+ return { cmd: 'claude', args: ['-p', '--dangerously-skip-permissions', task, ...extraArgs] };
2127
+ case 'codex':
2128
+ return { cmd: 'codex', args: ['exec', task, ...extraArgs] };
2129
+ case 'gemini':
2130
+ return { cmd: 'gemini', args: ['-p', task, ...extraArgs] };
2131
+ case 'opencode':
2132
+ return { cmd: 'opencode', args: ['--prompt', task, ...extraArgs] };
2133
+ case 'droid':
2134
+ return { cmd: 'droid', args: ['exec', task, ...extraArgs] };
2135
+ case 'aider':
2136
+ return { cmd: 'aider', args: ['--message', task, '--yes-always', '--no-git', ...extraArgs] };
2137
+ case 'goose':
2138
+ return { cmd: 'goose', args: ['run', '--text', task, '--no-session', ...extraArgs] };
2139
+ }
2140
+ }
2141
+
2142
+ /**
2143
+ * Apply preset defaults to an agent definition.
2144
+ * Explicit fields on the definition always win over preset-inferred defaults.
2145
+ */
2146
+ private static resolveAgentDef(def: AgentDefinition): AgentDefinition {
2147
+ if (!def.preset) return def;
2148
+ const nonInteractivePresets: AgentPreset[] = ['worker', 'reviewer', 'analyst'];
2149
+ const defaults: Partial<AgentDefinition> = nonInteractivePresets.includes(def.preset)
2150
+ ? { interactive: false }
2151
+ : {};
2152
+ // Explicit fields on the def always win
2153
+ return { ...defaults, ...def } as AgentDefinition;
2154
+ }
2155
+
2156
+ /**
2157
+ * Returns a preset-specific prefix that is prepended to the non-interactive
2158
+ * enforcement block in execNonInteractive.
2159
+ */
2160
+ /**
2161
+ * Returns a prefix injected into the task prompt for non-interactive agents.
2162
+ * Lead agents are always interactive (PTY), so they never reach execNonInteractive
2163
+ * and there is no 'lead' case here.
2164
+ */
2165
+ private buildPresetInjection(preset: AgentPreset | undefined): string {
2166
+ switch (preset) {
2167
+ case 'worker':
2168
+ return (
2169
+ 'You are a non-interactive worker agent. Produce clean, structured output to stdout.\n' +
2170
+ 'Do NOT use relay_spawn, add_agent, or any MCP tool to spawn sub-agents.\n' +
2171
+ 'Do NOT use relay_send or any Relaycast messaging tools — you have no relay connection.\n\n'
2172
+ );
2173
+ case 'reviewer':
2174
+ return (
2175
+ 'You are a non-interactive reviewer agent. Read the specified files/artifacts and produce a clear verdict.\n' +
2176
+ 'Do NOT spawn sub-agents or use any Relaycast messaging tools.\n\n'
2177
+ );
2178
+ case 'analyst':
2179
+ return (
2180
+ 'You are a non-interactive analyst agent. Read the specified code/files and write your findings.\n' +
2181
+ 'Do NOT spawn sub-agents or use any Relaycast messaging tools.\n\n'
2182
+ );
2183
+ default:
2184
+ return '';
2185
+ }
2186
+ }
2187
+
2188
+ /**
2189
+ * Execute an agent as a non-interactive subprocess.
2190
+ * No PTY, no relay messaging, no /exit injection. The process receives its task
2191
+ * as a CLI argument and stdout is captured as the step output.
2192
+ */
2193
+ private async execNonInteractive(
2194
+ agentDef: AgentDefinition,
2195
+ step: WorkflowStep,
2196
+ timeoutMs?: number
2197
+ ): Promise<string> {
2198
+ const agentName = `${step.name}-${this.generateShortId()}`;
2199
+ const modelArgs = agentDef.constraints?.model ? ['--model', agentDef.constraints.model] : [];
2200
+
2201
+ // Append strict deliverable enforcement — non-interactive agents MUST produce
2202
+ // clear, structured output since there's no opportunity for follow-up or clarification.
2203
+ const presetPrefix = this.buildPresetInjection(agentDef.preset);
2204
+ const taskWithDeliverable =
2205
+ presetPrefix +
2206
+ step.task +
2207
+ '\n\n---\n' +
2208
+ 'IMPORTANT: You are running as a non-interactive subprocess. ' +
2209
+ 'Do NOT call relay_spawn, add_agent, or any MCP tool to spawn or manage other agents.\n\n' +
2210
+ 'CRITICAL REQUIREMENT — YOU MUST FOLLOW THIS EXACTLY:\n' +
2211
+ 'You are running in non-interactive mode. There is NO opportunity for follow-up, ' +
2212
+ 'clarification, or additional input. Your stdout output is your ONLY deliverable.\n\n' +
2213
+ 'You MUST:\n' +
2214
+ '1. Complete the ENTIRE task in a single pass — no partial work, no "I\'ll continue later"\n' +
2215
+ '2. Print your COMPLETE deliverable to stdout — this is the ONLY output that will be captured\n' +
2216
+ '3. Be thorough and self-contained — another agent will consume your output with zero context about your process\n' +
2217
+ '4. End with a clear summary of what was accomplished and any artifacts produced\n\n' +
2218
+ 'DO NOT:\n' +
2219
+ '- Ask questions or request clarification (there is no one to answer)\n' +
2220
+ '- Output partial results expecting a follow-up (there will be none)\n' +
2221
+ '- Skip steps or leave work incomplete\n' +
2222
+ '- Output only status messages without the actual deliverable content';
2223
+
2224
+ const { cmd, args } = WorkflowRunner.buildNonInteractiveCommand(
2225
+ agentDef.cli,
2226
+ taskWithDeliverable,
2227
+ modelArgs
2228
+ );
2229
+
2230
+ // Open a log file for dashboard observability
2231
+ const logsDir = this.getWorkerLogsDir();
2232
+ const logPath = path.join(logsDir, `${agentName}.log`);
2233
+ const logStream = createWriteStream(logPath, { flags: 'a' });
2234
+
2235
+ // Register in workers.json with interactive: false metadata
2236
+ this.registerWorker(agentName, agentDef.cli, step.task ?? '', undefined, false);
2237
+
2238
+ // Register agent in Relaycast for observability
2239
+ let stopHeartbeat: (() => void) | undefined;
2240
+ if (this.relayApiKey) {
2241
+ const agentClient = await this.registerRelaycastExternalAgent(
2242
+ agentName,
2243
+ `Non-interactive workflow agent for step "${step.name}" (${agentDef.cli})`
2244
+ ).catch((err) => {
2245
+ console.warn(`[WorkflowRunner] Failed to register ${agentName} in Relaycast:`, err?.message ?? err);
2246
+ return null;
2247
+ });
2248
+ if (agentClient) {
2249
+ stopHeartbeat = this.startRelaycastHeartbeat(agentClient);
2250
+ }
2251
+ }
2252
+
2253
+ // Post assignment notification (no task content — task arrives via direct broker injection)
2254
+ this.postToChannel(`**[${step.name}]** Assigned to \`${agentName}\` (non-interactive)`);
2255
+
2256
+ const stdoutChunks: string[] = [];
2257
+ const stderrChunks: string[] = [];
2258
+
2259
+ try {
2260
+ const output = await new Promise<string>((resolve, reject) => {
2261
+ const child = cpSpawn(cmd, args, {
2262
+ stdio: ['ignore', 'pipe', 'pipe'],
2263
+ cwd: agentDef.cwd ? path.resolve(this.cwd, agentDef.cwd) : this.cwd,
2264
+ env: this.getRelayEnv() ?? { ...process.env },
2265
+ });
2266
+
2267
+ // Update workers.json with PID now that we have it
2268
+ this.registerWorker(agentName, agentDef.cli, step.task ?? '', child.pid, false);
2269
+
2270
+ // Wire abort signal so runner.abort() kills the child process
2271
+ const abortSignal = this.abortController?.signal;
2272
+ let abortHandler: (() => void) | undefined;
2273
+ if (abortSignal && !abortSignal.aborted) {
2274
+ abortHandler = () => {
2275
+ child.kill('SIGTERM');
2276
+ setTimeout(() => child.kill('SIGKILL'), 5000);
2277
+ };
2278
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
2279
+ }
2280
+
2281
+ // Heartbeat so a slow non-interactive agent doesn't look frozen.
2282
+ // Each tick shows the last substantive line received — gives insight
2283
+ // without flooding the log with raw model output.
2284
+ const startedAt = Date.now();
2285
+ let lastHeartbeatLine = '';
2286
+ const heartbeat = setInterval(() => {
2287
+ const elapsed = Math.round((Date.now() - startedAt) / 1000);
2288
+ const suffix = lastHeartbeatLine ? ` — ${lastHeartbeatLine.slice(0, 80)}` : '';
2289
+ this.log(`[${step.name}] still running (${elapsed}s)${suffix}`);
2290
+ lastHeartbeatLine = '';
2291
+ }, 30_000);
2292
+
2293
+ child.stdout?.on('data', (chunk: Buffer) => {
2294
+ const text = chunk.toString();
2295
+ stdoutChunks.push(text);
2296
+ logStream.write(text);
2297
+ // Track last substantive line for the next heartbeat
2298
+ const line =
2299
+ text
2300
+ .split('\n')
2301
+ .map((l) => l.trim())
2302
+ .filter(Boolean)
2303
+ .at(-1) ?? '';
2304
+ if (line) lastHeartbeatLine = line;
2305
+ });
2306
+
2307
+ child.stderr?.on('data', (chunk: Buffer) => {
2308
+ const text = chunk.toString();
2309
+ stderrChunks.push(text);
2310
+ logStream.write(`[stderr] ${text}`);
2311
+ });
2312
+
2313
+ // Handle timeout
2314
+ let timedOut = false;
2315
+ let timer: ReturnType<typeof setTimeout> | undefined;
2316
+ if (timeoutMs) {
2317
+ timer = setTimeout(() => {
2318
+ timedOut = true;
2319
+ child.kill('SIGTERM');
2320
+ // Give process time to clean up, then force kill
2321
+ setTimeout(() => child.kill('SIGKILL'), 5000);
2322
+ }, timeoutMs);
2323
+ }
2324
+
2325
+ child.on('close', (code) => {
2326
+ clearInterval(heartbeat);
2327
+ if (timer) clearTimeout(timer);
2328
+ if (abortHandler && abortSignal) {
2329
+ abortSignal.removeEventListener('abort', abortHandler);
2330
+ }
2331
+ const stdout = stdoutChunks.join('');
2332
+
2333
+ if (abortSignal?.aborted) {
2334
+ reject(new Error(`Step "${step.name}" aborted`));
2335
+ return;
2336
+ }
2337
+
2338
+ if (timedOut) {
2339
+ reject(new Error(`Step "${step.name}" timed out after ${timeoutMs ?? 'unknown'}ms`));
2340
+ return;
2341
+ }
2342
+
2343
+ if (code !== 0 && code !== null) {
2344
+ const stderr = stderrChunks.join('');
2345
+ reject(
2346
+ new Error(
2347
+ `Step "${step.name}" exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ''}`
2348
+ )
2349
+ );
2350
+ return;
2351
+ }
2352
+
2353
+ resolve(stdout);
2354
+ });
2355
+
2356
+ child.on('error', (err) => {
2357
+ clearInterval(heartbeat);
2358
+ if (timer) clearTimeout(timer);
2359
+ if (abortHandler && abortSignal) {
2360
+ abortSignal.removeEventListener('abort', abortHandler);
2361
+ }
2362
+ reject(new Error(`Failed to spawn ${cmd}: ${err.message}`));
2363
+ });
2364
+ });
2365
+
2366
+ return output;
2367
+ } finally {
2368
+ stopHeartbeat?.();
2369
+ logStream.end();
2370
+ this.unregisterWorker(agentName);
2371
+ }
2372
+ }
2373
+
2374
+ private async spawnAndWait(
2375
+ agentDef: AgentDefinition,
2376
+ step: WorkflowStep,
2377
+ timeoutMs?: number
2378
+ ): Promise<string> {
2379
+ // Branch: non-interactive agents run as simple subprocesses
2380
+ if (agentDef.interactive === false) {
2381
+ return this.execNonInteractive(agentDef, step, timeoutMs);
2382
+ }
2383
+
2384
+ if (!this.relay) {
2385
+ throw new Error('AgentRelay not initialized');
2386
+ }
2387
+
2388
+ // Deterministic name: step name + first 8 chars of run ID.
2389
+ // This matches the names pre-registered in preflightAgents(), so the broker
2390
+ // hits its token cache instantly instead of making a fresh Relaycast HTTP call.
2391
+ // On retry the broker may suffix a UUID (409 conflict) — that's fine, the agent
2392
+ // still works, just without the cache benefit.
2393
+ let agentName = `${step.name}-${(this.currentRunId ?? this.generateShortId()).slice(0, 8)}`;
2394
+
2395
+ // Only inject delegation guidance for lead/coordinator agents, not spokes/workers.
2396
+ // In non-hub patterns (pipeline, dag, etc.) every agent is autonomous so they all get it.
2397
+ const role = agentDef.role?.toLowerCase() ?? '';
2398
+ const nameLC = agentDef.name.toLowerCase();
2399
+ const isHub =
2400
+ WorkflowRunner.HUB_ROLES.has(nameLC) || [...WorkflowRunner.HUB_ROLES].some((r) => role.includes(r));
2401
+ const pattern = this.currentConfig?.swarm.pattern;
2402
+ const isHubPattern = pattern && WorkflowRunner.HUB_PATTERNS.has(pattern);
2403
+ const delegationGuidance =
2404
+ isHub || !isHubPattern ? this.buildDelegationGuidance(agentDef.cli, timeoutMs) : '';
2405
+
2406
+ // Non-claude CLIs (codex, gemini, etc.) don't auto-register with Relaycast
2407
+ // via the MCP system prompt the way claude does. Inject an explicit preamble
2408
+ // so they call register() before any other relay tool.
2409
+ const relayRegistrationNote = this.buildRelayRegistrationNote(agentDef.cli, agentName);
2410
+
2411
+ const taskWithExit =
2412
+ step.task +
2413
+ (relayRegistrationNote ? '\n\n' + relayRegistrationNote : '') +
2414
+ (delegationGuidance ? '\n\n' + delegationGuidance + '\n' : '') +
2415
+ '\n\n---\n' +
2416
+ 'IMPORTANT: When you have fully completed this task, you MUST self-terminate by either: ' +
2417
+ '(a) calling remove_agent(name: "<your-agent-name>", reason: "task completed") — preferred, or ' +
2418
+ '(b) outputting the exact text "/exit" on its own line as a fallback. ' +
2419
+ 'Do not wait for further input — terminate immediately after finishing. ' +
2420
+ 'Do NOT spawn sub-agents unless the task explicitly requires it.';
2421
+
2422
+ // Register PTY output listener before spawning so we capture everything
2423
+ this.ptyOutputBuffers.set(agentName, []);
2424
+
2425
+ // Open a log file so `agents:logs <name>` works for workflow-spawned agents
2426
+ const logsDir = this.getWorkerLogsDir();
2427
+ const logStream = createWriteStream(path.join(logsDir, `${agentName}.log`), { flags: 'a' });
2428
+ this.ptyLogStreams.set(agentName, logStream);
2429
+
2430
+ this.ptyListeners.set(agentName, (chunk: string) => {
2431
+ const stripped = WorkflowRunner.stripAnsi(chunk);
2432
+ this.ptyOutputBuffers.get(agentName)?.push(stripped);
2433
+ // Write raw output (with ANSI codes) to log file so dashboard's
2434
+ // XTermLogViewer can render colors/formatting natively via xterm.js
2435
+ logStream.write(chunk);
2436
+ });
2437
+
2438
+ const agentChannels = this.channel ? [this.channel] : agentDef.channels;
2439
+
2440
+ let agent: Awaited<ReturnType<typeof this.relay.spawnPty>>;
2441
+ let exitResult: string = 'unknown';
2442
+ let stopHeartbeat: (() => void) | undefined;
2443
+ let ptyChunks: string[] = [];
2444
+
2445
+ try {
2446
+ agent = await this.relay.spawnPty({
2447
+ name: agentName,
2448
+ cli: agentDef.cli,
2449
+ model: agentDef.constraints?.model,
2450
+ args: [],
2451
+ channels: agentChannels,
2452
+ task: taskWithExit,
2453
+ idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
2454
+ cwd: agentDef.cwd ? path.resolve(this.cwd, agentDef.cwd) : undefined,
2455
+ });
2456
+
2457
+ // Re-key PTY maps if broker assigned a different name than requested
2458
+ if (agent.name !== agentName) {
2459
+ const oldName = agentName;
2460
+ this.ptyOutputBuffers.set(agent.name, this.ptyOutputBuffers.get(oldName) ?? []);
2461
+ this.ptyOutputBuffers.delete(oldName);
2462
+
2463
+ // Close old log stream and rename the file to match the new agent name
2464
+ const oldLogPath = path.join(logsDir, `${oldName}.log`);
2465
+ const newLogPath = path.join(logsDir, `${agent.name}.log`);
2466
+ const oldLogStream = this.ptyLogStreams.get(oldName);
2467
+ if (oldLogStream) {
2468
+ oldLogStream.end();
2469
+ this.ptyLogStreams.delete(oldName);
2470
+ try {
2471
+ renameSync(oldLogPath, newLogPath);
2472
+ } catch {
2473
+ // File may not exist yet if no output was written
2474
+ }
2475
+ }
2476
+
2477
+ // Open new log stream with the correct name
2478
+ const newLogStream = createWriteStream(newLogPath, { flags: 'a' });
2479
+ this.ptyLogStreams.set(agent.name, newLogStream);
2480
+
2481
+ // Update listener to use the new log stream
2482
+ const oldListener = this.ptyListeners.get(oldName);
2483
+ if (oldListener) {
2484
+ this.ptyListeners.delete(oldName);
2485
+ this.ptyListeners.set(agent.name, (chunk: string) => {
2486
+ const stripped = WorkflowRunner.stripAnsi(chunk);
2487
+ this.ptyOutputBuffers.get(agent.name)?.push(stripped);
2488
+ newLogStream.write(chunk);
2489
+ });
2490
+ }
2491
+
2492
+ agentName = agent.name;
2493
+ }
2494
+
2495
+ // Register in workers.json so `agents:kill` can find this agent
2496
+ let workerPid: number | undefined;
2497
+ try {
2498
+ const rawAgents = await this.relay!.listAgentsRaw();
2499
+ workerPid = rawAgents.find((a) => a.name === agentName)?.pid ?? undefined;
2500
+ } catch {
2501
+ // Best-effort PID lookup
2502
+ }
2503
+ this.registerWorker(agentName, agentDef.cli, step.task ?? '', workerPid);
2504
+
2505
+ // Register the spawned agent in Relaycast for observability + start heartbeat
2506
+ if (this.relayApiKey) {
2507
+ const agentClient = await this.registerRelaycastExternalAgent(
2508
+ agent.name,
2509
+ `Workflow agent for step "${step.name}" (${agentDef.cli})`
2510
+ ).catch((err) => {
2511
+ console.warn(
2512
+ `[WorkflowRunner] Failed to register ${agent.name} in Relaycast:`,
2513
+ err?.message ?? err
2514
+ );
2515
+ return null;
2516
+ });
2517
+
2518
+ // Keep the agent online in the dashboard while it's working
2519
+ if (agentClient) {
2520
+ stopHeartbeat = this.startRelaycastHeartbeat(agentClient);
2521
+ }
2522
+ }
2523
+
2524
+ // Invite the spawned agent to the workflow channel
2525
+ if (this.channel && this.relayApiKey) {
2526
+ const channelAgent = await this.ensureRelaycastRunnerAgent().catch(() => null);
2527
+ await channelAgent?.channels.invite(this.channel, agent.name).catch(() => {});
2528
+ }
2529
+
2530
+ // Post assignment notification (no task content — task arrives via direct broker injection)
2531
+ this.postToChannel(`**[${step.name}]** Assigned to \`${agent.name}\``);
2532
+
2533
+ // Register agent handle for hub-mediated nudging
2534
+ this.activeAgentHandles.set(agentName, agent);
2535
+
2536
+ // Wait for agent to exit, with idle nudging if configured
2537
+ exitResult = await this.waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs);
2538
+
2539
+ // Stop heartbeat now that agent has exited
2540
+ stopHeartbeat?.();
2541
+
2542
+ if (exitResult === 'timeout') {
2543
+ // Safety net: check if the verification file exists before giving up.
2544
+ // The agent may have completed work but failed to /exit.
2545
+ if (step.verification?.type === 'file_exists') {
2546
+ const verifyPath = path.resolve(this.cwd, step.verification.value);
2547
+ if (existsSync(verifyPath)) {
2548
+ this.postToChannel(`**[${step.name}]** Agent idle after completing work — releasing`);
2549
+ await agent.release();
2550
+ // Fall through to read output below
2551
+ } else {
2552
+ await agent.release();
2553
+ throw new Error(`Step "${step.name}" timed out after ${timeoutMs ?? 'unknown'}ms`);
2554
+ }
2555
+ } else {
2556
+ await agent.release();
2557
+ throw new Error(`Step "${step.name}" timed out after ${timeoutMs ?? 'unknown'}ms`);
2558
+ }
2559
+ }
2560
+ } finally {
2561
+ // Snapshot PTY chunks before cleanup — we need them for output reading below
2562
+ ptyChunks = this.ptyOutputBuffers.get(agentName) ?? [];
2563
+
2564
+ // Always clean up PTY resources — prevents fd leaks if spawnPty or waitForExit throws
2565
+ stopHeartbeat?.();
2566
+ this.activeAgentHandles.delete(agentName);
2567
+ this.ptyOutputBuffers.delete(agentName);
2568
+ this.ptyListeners.delete(agentName);
2569
+ const stream = this.ptyLogStreams.get(agentName);
2570
+ if (stream) {
2571
+ stream.end();
2572
+ this.ptyLogStreams.delete(agentName);
2573
+ }
2574
+ this.unregisterWorker(agentName);
2575
+ }
2576
+
2577
+ let output: string;
2578
+ if (ptyChunks.length > 0) {
2579
+ output = ptyChunks.join('');
2580
+ } else {
2581
+ // Legacy fallback: summary file
2582
+ const summaryPath = path.join(this.summaryDir, `${step.name}.md`);
2583
+ output = existsSync(summaryPath)
2584
+ ? await readFile(summaryPath, 'utf-8')
2585
+ : exitResult === 'timeout'
2586
+ ? 'Agent completed (released after idle timeout)'
2587
+ : exitResult === 'released'
2588
+ ? 'Agent completed (force-released after idle nudging)'
2589
+ : `Agent exited (${exitResult})`;
2590
+ }
2591
+
2592
+ return output;
2593
+ }
2594
+
2595
+ // ── Idle nudging ────────────────────────────────────────────────────────
2596
+
2597
+ /** Patterns where a hub agent coordinates spoke agents. */
2598
+ private static readonly HUB_PATTERNS = new Set<string>([
2599
+ 'fan-out',
2600
+ 'hub-spoke',
2601
+ 'hierarchical',
2602
+ 'map-reduce',
2603
+ 'scatter-gather',
2604
+ 'supervisor',
2605
+ 'saga',
2606
+ 'auction',
2607
+ ]);
2608
+
2609
+ /** Roles that indicate a coordinator/lead agent (eligible for delegation guidance). */
2610
+ private static readonly HUB_ROLES = new Set([
2611
+ 'lead',
2612
+ 'hub',
2613
+ 'coordinator',
2614
+ 'supervisor',
2615
+ 'orchestrator',
2616
+ 'auctioneer',
2617
+ ]);
2618
+
2619
+ /**
2620
+ * Wait for agent exit with idle detection and nudging.
2621
+ * If no idle nudge config is set, falls through to simple waitForExit.
2622
+ */
2623
+ private async waitForExitWithIdleNudging(
2624
+ agent: Agent,
2625
+ agentDef: AgentDefinition,
2626
+ step: WorkflowStep,
2627
+ timeoutMs?: number
2628
+ ): Promise<'exited' | 'timeout' | 'released'> {
2629
+ const nudgeConfig = this.currentConfig?.swarm.idleNudge;
2630
+ if (!nudgeConfig) {
2631
+ // No nudge config — backward compatible simple wait
2632
+ return agent.waitForExit(timeoutMs);
2633
+ }
2634
+
2635
+ const nudgeAfterMs = nudgeConfig.nudgeAfterMs ?? 120_000;
2636
+ const escalateAfterMs = nudgeConfig.escalateAfterMs ?? 120_000;
2637
+ const maxNudges = nudgeConfig.maxNudges ?? 1;
2638
+
2639
+ let nudgeCount = 0;
2640
+ const startTime = Date.now();
2641
+
2642
+ while (true) {
2643
+ // Calculate remaining time from overall timeout
2644
+ const elapsed = Date.now() - startTime;
2645
+ const remaining = timeoutMs ? timeoutMs - elapsed : undefined;
2646
+ if (remaining !== undefined && remaining <= 0) {
2647
+ return 'timeout';
2648
+ }
2649
+
2650
+ // nudgeAfterMs = how long to wait before nudging (first interval).
2651
+ // escalateAfterMs = how long to wait between subsequent nudges.
2652
+ //
2653
+ // We wait for exit, not for idle. The broker's idle_threshold_secs is
2654
+ // only 30s by default, so racing waitForExit vs waitForIdle would nudge
2655
+ // after 30s of PTY silence regardless of nudgeAfterMs. Instead, we give
2656
+ // the agent the full nudgeAfterMs window to finish before nudging.
2657
+ const windowMs = nudgeCount === 0 ? nudgeAfterMs : escalateAfterMs;
2658
+ const waitMs = remaining !== undefined ? Math.min(windowMs, remaining) : windowMs;
2659
+
2660
+ const exitResult = await agent.waitForExit(waitMs);
2661
+
2662
+ if (exitResult !== 'timeout') {
2663
+ // Agent actually exited or was released — done
2664
+ return exitResult;
2665
+ }
2666
+
2667
+ // Agent is still running after the window expired.
2668
+ if (remaining !== undefined && Date.now() - startTime >= remaining) {
2669
+ return 'timeout';
2670
+ }
2671
+
2672
+ // Nudge if we haven't exhausted the limit
2673
+ if (nudgeCount < maxNudges) {
2674
+ await this.nudgeIdleAgent(agent, agentDef, step);
2675
+ nudgeCount++;
2676
+ this.postToChannel(`**[${step.name}]** Agent \`${agent.name}\` idle — nudge #${nudgeCount} sent`);
2677
+ this.emit({ type: 'step:nudged', runId: this.currentRunId ?? '', stepName: step.name, nudgeCount });
2678
+ continue;
2679
+ }
2680
+
2681
+ // Exhausted nudges — force-release
2682
+ this.postToChannel(
2683
+ `**[${step.name}]** Agent \`${agent.name}\` still idle after ${nudgeCount} nudge(s) — force-releasing`
2684
+ );
2685
+ this.emit({ type: 'step:force-released', runId: this.currentRunId ?? '', stepName: step.name });
2686
+ await agent.release();
2687
+ return 'released';
2688
+ }
2689
+ }
2690
+
2691
+ /**
2692
+ * Send a nudge to an idle agent. Uses hub-mediated nudge for hub patterns,
2693
+ * or direct system injection otherwise.
2694
+ */
2695
+ private async nudgeIdleAgent(agent: Agent, agentDef: AgentDefinition, step: WorkflowStep): Promise<void> {
2696
+ const hubAgent = this.resolveHubForNudge(agentDef);
2697
+
2698
+ if (hubAgent) {
2699
+ // Hub-mediated: tell the hub to check on the idle agent
2700
+ try {
2701
+ await hubAgent.sendMessage({
2702
+ to: agent.name,
2703
+ text: `Agent ${agent.name} appears idle on step "${step.name}". Check on them and remind them to /exit when done.`,
2704
+ });
2705
+ return; // Hub nudge succeeded
2706
+ } catch {
2707
+ // Fall through to direct nudge
2708
+ }
2709
+ }
2710
+
2711
+ // Direct system injection via human handle
2712
+ if (this.relay) {
2713
+ const human = this.relay.human({ name: 'workflow-runner' });
2714
+ await human
2715
+ .sendMessage({
2716
+ to: agent.name,
2717
+ text: "You appear idle. If you've completed your task, output /exit. If still working, continue.",
2718
+ })
2719
+ .catch(() => {
2720
+ // Non-critical — don't break workflow
2721
+ });
2722
+ }
2723
+ }
2724
+
2725
+ /**
2726
+ * Find the hub agent for hub-mediated nudging.
2727
+ * Returns the hub's live Agent handle if this is a hub pattern and the idle agent is not the hub.
2728
+ */
2729
+ private resolveHubForNudge(idleAgentDef: AgentDefinition): Agent | undefined {
2730
+ const pattern = this.currentConfig?.swarm.pattern;
2731
+ if (!pattern || !WorkflowRunner.HUB_PATTERNS.has(pattern)) {
2732
+ return undefined;
2733
+ }
2734
+
2735
+ // Find an interactive agent with a hub-like role
2736
+ const agents = this.currentConfig?.agents ?? [];
2737
+
2738
+ for (const agentDef of agents) {
2739
+ // Skip non-interactive and the idle agent itself
2740
+ if (agentDef.interactive === false) continue;
2741
+ if (agentDef.name === idleAgentDef.name) continue;
2742
+
2743
+ const role = agentDef.role?.toLowerCase() ?? '';
2744
+ const nameLC = agentDef.name.toLowerCase();
2745
+
2746
+ if (
2747
+ WorkflowRunner.HUB_ROLES.has(nameLC) ||
2748
+ [...WorkflowRunner.HUB_ROLES].some((r) => role.includes(r))
2749
+ ) {
2750
+ // Found a hub candidate — check if we have a live handle
2751
+ const handle = this.activeAgentHandles.get(agentDef.name);
2752
+ if (handle) return handle;
2753
+ }
2754
+ }
2755
+
2756
+ return undefined;
2757
+ }
2758
+
2759
+ // ── Verification ────────────────────────────────────────────────────────
2760
+
2761
+ private runVerification(
2762
+ check: VerificationCheck,
2763
+ output: string,
2764
+ stepName: string,
2765
+ injectedTaskText?: string
2766
+ ): void {
2767
+ switch (check.type) {
2768
+ case 'output_contains': {
2769
+ // Guard against false positives: the PTY captures the injected task text
2770
+ // verbatim, so if the verification token appears in the task itself the
2771
+ // check would pass immediately without the agent doing any real work.
2772
+ // When the task contains the token, require a SECOND occurrence — one
2773
+ // from the task injection and one from the agent's actual response.
2774
+ const token = check.value;
2775
+ const taskHasToken = injectedTaskText ? injectedTaskText.includes(token) : false;
2776
+ if (taskHasToken) {
2777
+ const first = output.indexOf(token);
2778
+ const hasSecond = first !== -1 && output.includes(token, first + token.length);
2779
+ if (!hasSecond) {
2780
+ throw new Error(
2781
+ `Verification failed for "${stepName}": output does not contain "${token}" ` +
2782
+ `(token found only in task injection — agent must output it explicitly)`
2783
+ );
2784
+ }
2785
+ } else if (!output.includes(token)) {
2786
+ throw new Error(`Verification failed for "${stepName}": output does not contain "${token}"`);
2787
+ }
2788
+ break;
2789
+ }
2790
+
2791
+ case 'exit_code':
2792
+ // exit_code verification is implicitly satisfied if the agent exited successfully
2793
+ break;
2794
+
2795
+ case 'file_exists':
2796
+ if (!existsSync(path.resolve(this.cwd, check.value))) {
2797
+ throw new Error(`Verification failed for "${stepName}": file "${check.value}" does not exist`);
2798
+ }
2799
+ break;
2800
+
2801
+ case 'custom':
2802
+ // Custom verifications are evaluated by callers; no-op here
2803
+ break;
2804
+ }
2805
+ }
2806
+
2807
+ // ── State helpers ─────────────────────────────────────────────────────
2808
+
2809
+ private async updateRunStatus(runId: string, status: WorkflowRunStatus, error?: string): Promise<void> {
2810
+ const patch: Partial<WorkflowRunRow> = {
2811
+ status,
2812
+ updatedAt: new Date().toISOString(),
2813
+ };
2814
+ if (status === 'completed' || status === 'failed' || status === 'cancelled') {
2815
+ patch.completedAt = new Date().toISOString();
2816
+ }
2817
+ if (error) {
2818
+ patch.error = error;
2819
+ }
2820
+ await this.db.updateRun(runId, patch);
2821
+ }
2822
+
2823
+ private async markStepFailed(state: StepState, error: string, runId: string): Promise<void> {
2824
+ state.row.status = 'failed';
2825
+ state.row.error = error;
2826
+ state.row.completedAt = new Date().toISOString();
2827
+ await this.db.updateStep(state.row.id, {
2828
+ status: 'failed',
2829
+ error,
2830
+ completedAt: state.row.completedAt,
2831
+ updatedAt: new Date().toISOString(),
2832
+ });
2833
+ this.emit({ type: 'step:failed', runId, stepName: state.row.stepName, error });
2834
+ }
2835
+
2836
+ private async markDownstreamSkipped(
2837
+ failedStepName: string,
2838
+ allSteps: WorkflowStep[],
2839
+ stepStates: Map<string, StepState>,
2840
+ runId: string
2841
+ ): Promise<void> {
2842
+ const queue = [failedStepName];
2843
+ const visited = new Set<string>();
2844
+
2845
+ while (queue.length > 0) {
2846
+ const current = queue.shift()!;
2847
+ if (visited.has(current)) continue;
2848
+ visited.add(current);
2849
+
2850
+ for (const step of allSteps) {
2851
+ if (step.dependsOn?.includes(current)) {
2852
+ const state = stepStates.get(step.name);
2853
+ if (state && state.row.status === 'pending') {
2854
+ state.row.status = 'skipped';
2855
+ await this.db.updateStep(state.row.id, {
2856
+ status: 'skipped',
2857
+ updatedAt: new Date().toISOString(),
2858
+ });
2859
+ this.emit({ type: 'step:skipped', runId, stepName: step.name });
2860
+ this.postToChannel(`**[${step.name}]** Skipped — upstream dependency "${current}" failed`);
2861
+ await this.trajectory?.stepSkipped(step, `Upstream dependency "${current}" failed`);
2862
+ await this.trajectory?.decide(
2863
+ `Whether to skip ${step.name}`,
2864
+ 'skip',
2865
+ `Upstream dependency "${current}" failed`
2866
+ );
2867
+ queue.push(step.name);
2868
+ }
2869
+ }
2870
+ }
2871
+ }
2872
+ }
2873
+
2874
+ // ── Control flow helpers ──────────────────────────────────────────────
2875
+
2876
+ private checkAborted(): void {
2877
+ if (this.abortController?.signal.aborted) {
2878
+ throw new Error('Workflow aborted');
2879
+ }
2880
+ }
2881
+
2882
+ private async waitIfPaused(): Promise<void> {
2883
+ if (!this.paused) return;
2884
+ await new Promise<void>((resolve) => {
2885
+ this.pauseResolver = resolve;
2886
+ });
2887
+ }
2888
+
2889
+ private delay(ms: number): Promise<void> {
2890
+ return new Promise((resolve) => setTimeout(resolve, ms));
2891
+ }
2892
+
2893
+ // ── Channel messaging ──────────────────────────────────────────────────
2894
+
2895
+ /**
2896
+ * Build a metadata note about non-interactive workers for inclusion in interactive agent tasks.
2897
+ * Returns undefined if there are no non-interactive agents.
2898
+ */
2899
+ private buildNonInteractiveAwareness(
2900
+ agentMap: Map<string, AgentDefinition>,
2901
+ stepStates: Map<string, StepState>
2902
+ ): string | undefined {
2903
+ const nonInteractive = [...agentMap.values()].filter((a) => a.interactive === false);
2904
+ if (nonInteractive.length === 0) return undefined;
2905
+
2906
+ // Map agent names to their step names so the lead knows exact {{steps.X.output}} references
2907
+ const agentToSteps = new Map<string, string[]>();
2908
+ for (const [stepName, state] of stepStates) {
2909
+ const agentName = state.row.agentName;
2910
+ if (!agentName) continue; // Skip deterministic steps
2911
+ if (!agentToSteps.has(agentName)) agentToSteps.set(agentName, []);
2912
+ agentToSteps.get(agentName)!.push(stepName);
2913
+ }
2914
+
2915
+ const lines = nonInteractive.map((a) => {
2916
+ const steps = agentToSteps.get(a.name) ?? [];
2917
+ const stepRefs = steps.map((s) => `{{steps.${s}.output}}`).join(', ');
2918
+ return `- ${a.name} (${a.cli}) — will return output when complete${stepRefs ? `. Access via: ${stepRefs}` : ''}`;
2919
+ });
2920
+ return (
2921
+ '\n\n---\n' +
2922
+ 'Note: The following agents are non-interactive workers and cannot receive messages:\n' +
2923
+ lines.join('\n') +
2924
+ '\n' +
2925
+ 'Do NOT attempt to message these agents. Use the {{steps.<name>.output}} references above to access their results.'
2926
+ );
2927
+ }
2928
+
2929
+ /**
2930
+ * Build guidance that encourages agents to autonomously delegate subtasks
2931
+ * to helper agents when work is too complex for a single pass.
2932
+ */
2933
+ /**
2934
+ * Returns a relay registration preamble for CLIs that don't auto-call
2935
+ * `register` via the MCP system prompt (everyone except claude).
2936
+ *
2937
+ * Claude reads the Relaycast system prompt and registers on its own.
2938
+ * Codex, gemini, etc. have the MCP server configured with the workspace
2939
+ * key, but they won't call `register` unless explicitly told to.
2940
+ */
2941
+ private buildRelayRegistrationNote(cli: string, agentName: string): string {
2942
+ if (cli === 'claude') return '';
2943
+ return (
2944
+ '---\n' +
2945
+ 'RELAY SETUP — do this FIRST before any other relay tool:\n' +
2946
+ `1. Call: register(name="${agentName}")\n` +
2947
+ ' This authenticates you in the Relaycast workspace.\n' +
2948
+ ' ALL relay tools (relay_send, relay_inbox, post_message, etc.) require\n' +
2949
+ ' registration first — they will fail with "Not registered" otherwise.\n' +
2950
+ `2. Your agent name is "${agentName}" — use this exact name when registering.`
2951
+ );
2952
+ }
2953
+
2954
+ private buildDelegationGuidance(cli: string, timeoutMs?: number): string {
2955
+ const timeoutNote = timeoutMs
2956
+ ? `You have approximately ${Math.round(timeoutMs / 60000)} minutes before this step times out. ` +
2957
+ 'Plan accordingly — delegate early if the work is substantial.\n\n'
2958
+ : '';
2959
+
2960
+ // Option 2 (sub-agents via Task tool) is only available in Claude
2961
+ const subAgentOption =
2962
+ cli === 'claude'
2963
+ ? 'Option 2 — Use built-in sub-agents (Task tool) for research or scoped work:\n' +
2964
+ ' - Good for exploring code, reading files, or making targeted changes\n' +
2965
+ ' - Can run multiple sub-agents in parallel\n\n'
2966
+ : '';
2967
+
2968
+ return (
2969
+ '---\n' +
2970
+ 'AUTONOMOUS DELEGATION — READ THIS BEFORE STARTING:\n' +
2971
+ timeoutNote +
2972
+ 'Before diving in, assess whether this task is too large or complex for a single agent. ' +
2973
+ 'If it involves multiple independent subtasks, touches many files, or could take a long time, ' +
2974
+ 'you should break it down and delegate to helper agents to avoid timeouts.\n\n' +
2975
+ 'Option 1 — Spawn relay agents (for real parallel coding work):\n' +
2976
+ ' - relay_spawn(name="helper-1", cli="claude", task="Specific subtask description")\n' +
2977
+ ' - Coordinate via relay_send(to="helper-1", message="...")\n' +
2978
+ ' - Check on them with relay_inbox()\n' +
2979
+ ' - Clean up when done: relay_release(name="helper-1")\n\n' +
2980
+ subAgentOption +
2981
+ 'Guidelines:\n' +
2982
+ '- You are the lead — delegate but stay in control, track progress, integrate results\n' +
2983
+ '- Give each helper a clear, self-contained task with enough context to work independently\n' +
2984
+ "- For simple or quick work, just do it yourself — don't over-delegate\n" +
2985
+ '- Always release spawned relay agents when their work is complete\n' +
2986
+ '- When spawning non-claude agents (codex, gemini, etc.), prepend to their task:\n' +
2987
+ ' "RELAY SETUP: First call register(name=\'<exact-agent-name>\') before any other relay tool."'
2988
+ );
2989
+ }
2990
+
2991
+ /** Post a message to the workflow channel. Fire-and-forget — never throws or blocks. */
2992
+ private postToChannel(text: string): void {
2993
+ if (!this.relayApiKey || !this.channel) return;
2994
+ this.ensureRelaycastRunnerAgent()
2995
+ .then((agent) => agent.send(this.channel!, text))
2996
+ .catch(() => {
2997
+ // Non-critical — don't break workflow execution
2998
+ });
2999
+ }
3000
+
3001
+ /** Post a rich completion report to the channel. */
3002
+ private postCompletionReport(
3003
+ workflowName: string,
3004
+ outcomes: StepOutcome[],
3005
+ summary: string,
3006
+ confidence: number
3007
+ ): void {
3008
+ const completed = outcomes.filter((o) => o.status === 'completed');
3009
+ const skipped = outcomes.filter((o) => o.status === 'skipped');
3010
+ const retried = outcomes.filter((o) => o.attempts > 1);
3011
+
3012
+ const lines: string[] = [
3013
+ `## Workflow **${workflowName}** — Complete`,
3014
+ '',
3015
+ summary,
3016
+ `Confidence: ${Math.round(confidence * 100)}%`,
3017
+ '',
3018
+ '### Steps',
3019
+ ...completed.map(
3020
+ (o) =>
3021
+ `- **${o.name}** (${o.agent}) — passed${o.verificationPassed ? ' (verified)' : ''}${o.attempts > 1 ? ` after ${o.attempts} attempts` : ''}`
3022
+ ),
3023
+ ...skipped.map((o) => `- **${o.name}** — skipped`),
3024
+ ];
3025
+
3026
+ if (retried.length > 0) {
3027
+ lines.push('', '### Retries');
3028
+ for (const o of retried) {
3029
+ lines.push(`- ${o.name}: ${o.attempts} attempts`);
3030
+ }
3031
+ }
3032
+
3033
+ this.postToChannel(lines.join('\n'));
3034
+ }
3035
+
3036
+ /** Post a failure report to the channel. */
3037
+ private postFailureReport(workflowName: string, outcomes: StepOutcome[], errorMsg: string): void {
3038
+ const completed = outcomes.filter((o) => o.status === 'completed');
3039
+ const failed = outcomes.filter((o) => o.status === 'failed');
3040
+ const skipped = outcomes.filter((o) => o.status === 'skipped');
3041
+
3042
+ const lines: string[] = [
3043
+ `## Workflow **${workflowName}** — Failed`,
3044
+ '',
3045
+ `${completed.length}/${outcomes.length} steps passed. Error: ${errorMsg}`,
3046
+ '',
3047
+ '### Steps',
3048
+ ...completed.map((o) => `- **${o.name}** (${o.agent}) — passed`),
3049
+ ...failed.map((o) => `- **${o.name}** (${o.agent}) — FAILED: ${o.error ?? 'unknown'}`),
3050
+ ...skipped.map((o) => `- **${o.name}** — skipped`),
3051
+ ];
3052
+
3053
+ this.postToChannel(lines.join('\n'));
3054
+ }
3055
+
3056
+ /**
3057
+ * Log a human-readable run summary to the console after completion or failure.
3058
+ * Extracts the last meaningful lines from each step's raw PTY output.
3059
+ */
3060
+ private logRunSummary(workflowName: string, outcomes: StepOutcome[], runId: string): void {
3061
+ const completed = outcomes.filter((o) => o.status === 'completed');
3062
+ const failed = outcomes.filter((o) => o.status === 'failed');
3063
+ const skipped = outcomes.filter((o) => o.status === 'skipped');
3064
+
3065
+ console.log('');
3066
+ console.log('━'.repeat(70));
3067
+ console.log(` Workflow "${workflowName}" — ${failed.length === 0 ? 'COMPLETED' : 'FAILED'}`);
3068
+ console.log(` ${completed.length} passed, ${failed.length} failed, ${skipped.length} skipped`);
3069
+ console.log('━'.repeat(70));
3070
+
3071
+ for (const outcome of outcomes) {
3072
+ const icon = outcome.status === 'completed' ? '✓' : outcome.status === 'failed' ? '✗' : '⊘';
3073
+ const retryNote = outcome.attempts > 1 ? ` (${outcome.attempts} attempts)` : '';
3074
+ console.log(` ${icon} ${outcome.name} [${outcome.agent}]${retryNote}`);
3075
+
3076
+ if (outcome.error) {
3077
+ console.log(` Error: ${outcome.error}`);
3078
+ }
3079
+
3080
+ // Extract last meaningful lines from raw PTY output
3081
+ if (outcome.output) {
3082
+ const excerpt = this.extractOutputExcerpt(outcome.output);
3083
+ if (excerpt) {
3084
+ for (const line of excerpt.split('\n')) {
3085
+ console.log(` ${line}`);
3086
+ }
3087
+ }
3088
+ }
3089
+ }
3090
+
3091
+ // Point to detailed output files
3092
+ const outputDir = this.getStepOutputDir(runId);
3093
+ const logsDir = path.join(this.cwd, '.agent-relay', 'team', 'worker-logs');
3094
+ console.log('');
3095
+ console.log(` Step output: ${outputDir}`);
3096
+ console.log(` Agent logs: ${logsDir}`);
3097
+ console.log('━'.repeat(70));
3098
+ console.log('');
3099
+ }
3100
+
3101
+ /**
3102
+ * Extract a useful excerpt from raw PTY output.
3103
+ * Looks for the agent's final text output (ignoring ANSI, system prompts, tool calls).
3104
+ */
3105
+ private extractOutputExcerpt(rawOutput: string): string {
3106
+ const stripped = WorkflowRunner.stripAnsi(rawOutput);
3107
+
3108
+ // Split into lines, filter out noise
3109
+ const lines = stripped.split('\n').filter((line) => {
3110
+ const trimmed = line.trim();
3111
+ if (!trimmed) return false;
3112
+ // Skip system/UI chrome
3113
+ if (trimmed.startsWith('╭') || trimmed.startsWith('╰') || trimmed.startsWith('│')) return false;
3114
+ if (trimmed.startsWith('─')) return false;
3115
+ if (trimmed.startsWith('❯') || trimmed.startsWith('⏵')) return false;
3116
+ if (trimmed.startsWith('<system-reminder>') || trimmed.startsWith('</system-reminder>')) return false;
3117
+ if (/^\[?workflow\s/.test(trimmed)) return false;
3118
+ // Skip tool invocations
3119
+ if (/^(Read|Edit|Bash|Glob|Grep|Task|Explore|Write)\(/.test(trimmed)) return false;
3120
+ // Skip thinking indicators
3121
+ if (/^[·✳✻✽⏺]?\s*Sublimating/.test(trimmed)) return false;
3122
+ // Skip very short lines (likely UI fragments)
3123
+ if (trimmed.length < 10) return false;
3124
+ return true;
3125
+ });
3126
+
3127
+ if (lines.length === 0) return '';
3128
+
3129
+ // Take the last few meaningful lines (agent's final words)
3130
+ const tail = lines.slice(-5);
3131
+ const excerpt = tail.map((l) => l.trim().slice(0, 120)).join('\n');
3132
+ return excerpt.length > 0 ? `...\n${excerpt}` : '';
3133
+ }
3134
+
3135
+ // ── Trajectory helpers ────────────────────────────────────────────────
3136
+
3137
+ /** Analyze DAG structure for trajectory context. */
3138
+ private analyzeDAG(steps: WorkflowStep[]): string {
3139
+ const roots = steps.filter((s) => !s.dependsOn?.length);
3140
+ const withDeps = steps.filter((s) => s.dependsOn?.length);
3141
+
3142
+ const parts = [`Parsed ${steps.length} steps`];
3143
+ if (roots.length > 1) {
3144
+ parts.push(`${roots.length} parallel tracks`);
3145
+ }
3146
+ if (withDeps.length > 0) {
3147
+ parts.push(`${withDeps.length} dependent steps`);
3148
+ }
3149
+ parts.push('DAG validated, no cycles');
3150
+ return parts.join(', ');
3151
+ }
3152
+
3153
+ /** Collect step outcomes for trajectory synthesis. */
3154
+ private collectOutcomes(stepStates: Map<string, StepState>, steps?: WorkflowStep[]): StepOutcome[] {
3155
+ const stepsWithVerification = new Set(steps?.filter((s) => s.verification).map((s) => s.name) ?? []);
3156
+ const outcomes: StepOutcome[] = [];
3157
+ for (const [name, state] of stepStates) {
3158
+ outcomes.push({
3159
+ name,
3160
+ agent: state.row.agentName ?? 'deterministic',
3161
+ status:
3162
+ state.row.status === 'completed'
3163
+ ? 'completed'
3164
+ : state.row.status === 'skipped'
3165
+ ? 'skipped'
3166
+ : 'failed',
3167
+ attempts: state.row.retryCount + 1,
3168
+ output: state.row.output,
3169
+ error: state.row.error,
3170
+ verificationPassed: state.row.status === 'completed' && stepsWithVerification.has(name),
3171
+ });
3172
+ }
3173
+ return outcomes;
3174
+ }
3175
+
3176
+ // ── ID generation ─────────────────────────────────────────────────────
3177
+
3178
+ private generateId(): string {
3179
+ return randomBytes(12).toString('hex');
3180
+ }
3181
+
3182
+ private generateShortId(): string {
3183
+ return randomBytes(4).toString('hex');
3184
+ }
3185
+
3186
+ /** Strip ANSI escape codes from terminal output — delegates to pty.ts canonical regex. */
3187
+ private static stripAnsi(text: string): string {
3188
+ return stripAnsiFn(text);
3189
+ }
3190
+
3191
+ /**
3192
+ * Strip TUI chrome from PTY-captured output before posting to a channel.
3193
+ * Removes: ANSI codes, unicode spinner/thinking characters, cursor-movement
3194
+ * artifacts, and collapses runs of blank lines to a single blank line.
3195
+ * The raw (ANSI-stripped) output is still written to disk for step chaining.
3196
+ */
3197
+ private static scrubForChannel(text: string): string {
3198
+ // Strip system-reminder blocks (closed or unclosed)
3199
+ const withoutSystemReminders = text
3200
+ .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/giu, '')
3201
+ .replace(/<system-reminder>[\s\S]*/giu, '');
3202
+
3203
+ // Normalize CRLF and bare \r before stripping ANSI — PTY output often
3204
+ // contains \r\r\n which leaves stray \r after stripping that confuse line splitting.
3205
+ const normalized = withoutSystemReminders.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
3206
+ const ansiStripped = stripAnsiFn(normalized);
3207
+
3208
+ // Unicode spinner / ornament characters used by Claude TUI animations.
3209
+ // Includes block-element chars (▗▖▘▝) used in the Claude Code header bar.
3210
+ const SPINNER =
3211
+ '\\u2756\\u2738\\u2739\\u273a\\u273b\\u273c\\u273d\\u2731\\u2732\\u2733\\u2734\\u2735\\u2736\\u2737\\u2743\\u2745\\u2746\\u25d6\\u25d7\\u25d8\\u25d9\\u2022\\u25cf\\u25cb\\u25a0\\u25a1\\u25b6\\u25c0\\u23f5\\u23f6\\u23f7\\u23f8\\u23f9\\u25e2\\u25e3\\u25e4\\u25e5\\u2597\\u2596\\u2598\\u259d\\u2bc8\\u2bc7\\u2bc5\\u2bc6\\u00b7' +
3212
+ '\\u2590\\u258c\\u2588\\u2584\\u2580\\u259a\\u259e'; // additional block elements
3213
+ const spinnerRe = new RegExp(`[${SPINNER}]`, 'gu');
3214
+ const spinnerClassRe = new RegExp(`^[\\s${SPINNER}]*$`, 'u');
3215
+
3216
+ // Line-level filters
3217
+ const boxDrawingOnlyRe = /^[\s\u2500-\u257f\u2580-\u259f\u25a0-\u25ff\-_=~]{3,}$/u;
3218
+ // Broker internal log lines: "2026-02-26T12:45:12.123Z INFO agent_relay_broker::..."
3219
+ const brokerLogRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z\s+(?:INFO|WARN|ERROR|DEBUG)\s/u;
3220
+ const claudeHeaderRe =
3221
+ /^(?:[\s\u2580-\u259f✢*·▗▖▘▝]+\s*)?(?:Claude\s+Code(?:\s+v?[\d.]+)?|(?:Sonnet|Haiku|Opus)\s*[\d.]+|claude-(?:sonnet|haiku|opus)-[\w.-]+|Running\s+on\s+claude)/iu;
3222
+ // TUI directory breadcrumb lines (e.g. " ~/Projects/agent-workforce/relay-...")
3223
+ const dirBreadcrumbRe = /^\s*~[\\/]/u;
3224
+ const uiHintRe =
3225
+ /\b(?:Press\s+up\s+to\s+edit|tab\s+to\s+queue|bypass\s+permissions|esc\s+to\s+interrupt)\b/iu;
3226
+ // Any spinner-prefixed word ending in … — catches all Claude thinking animations
3227
+ // regardless of the specific word used (Thinking, Cascading, Flibbertigibbeting, etc.)
3228
+ const thinkingLineRe = new RegExp(`^[\\s${SPINNER}]*\\s*\\w[\\w\\s]*\\u2026\\s*$`, 'u');
3229
+ const cursorOnlyRe = /^[\s❯⎿›»◀▶←→↑↓⟨⟩⟪⟫·]+$/u;
3230
+ const slashCommandRe = /^\/\w+\s*$/u;
3231
+ const mcpJsonKvRe =
3232
+ /^\s*"(?:type|method|params|result|id|jsonrpc|tool|name|arguments|content|role|metadata)"\s*:/u;
3233
+ const meaningfulContentRe = /[a-zA-Z0-9]/u;
3234
+
3235
+ const countJsonDepth = (line: string): number => {
3236
+ let depth = 0;
3237
+ for (const ch of line) {
3238
+ if (ch === '{' || ch === '[') depth += 1;
3239
+ if (ch === '}' || ch === ']') depth -= 1;
3240
+ }
3241
+ return depth;
3242
+ };
3243
+
3244
+ const lines = ansiStripped.split('\n');
3245
+ const meaningful: string[] = [];
3246
+ let jsonDepth = 0;
3247
+
3248
+ for (const line of lines) {
3249
+ const trimmed = line.trim();
3250
+
3251
+ if (jsonDepth > 0) {
3252
+ jsonDepth += countJsonDepth(line);
3253
+ if (jsonDepth <= 0) jsonDepth = 0;
3254
+ continue;
3255
+ }
3256
+
3257
+ if (trimmed.length === 0) continue;
3258
+
3259
+ if (trimmed.startsWith('{') || /^\[\s*\{/.test(trimmed)) {
3260
+ jsonDepth = Math.max(countJsonDepth(line), 0);
3261
+ continue;
3262
+ }
3263
+
3264
+ if (mcpJsonKvRe.test(line)) continue;
3265
+ if (spinnerClassRe.test(trimmed)) continue;
3266
+ if (boxDrawingOnlyRe.test(trimmed)) continue;
3267
+ if (brokerLogRe.test(trimmed)) continue;
3268
+ if (claudeHeaderRe.test(trimmed)) continue;
3269
+ if (dirBreadcrumbRe.test(trimmed)) continue;
3270
+ if (uiHintRe.test(trimmed)) continue;
3271
+ if (thinkingLineRe.test(trimmed)) continue;
3272
+ if (cursorOnlyRe.test(trimmed)) continue;
3273
+ if (slashCommandRe.test(trimmed)) continue;
3274
+ if (!meaningfulContentRe.test(trimmed)) continue;
3275
+
3276
+ // Drop TUI animation frame fragments: lines where stripping spinners and
3277
+ // whitespace leaves ≤ 3 alphanumeric characters (e.g. "F", "l b", "i g").
3278
+ const alphanum = trimmed.replace(spinnerRe, '').replace(/\s+/g, '');
3279
+ if (alphanum.replace(/[^a-zA-Z0-9]/g, '').length <= 3) continue;
3280
+
3281
+ meaningful.push(line);
3282
+ }
3283
+
3284
+ return meaningful
3285
+ .join('\n')
3286
+ .replace(/\n{3,}/g, '\n\n')
3287
+ .trim();
3288
+ }
3289
+
3290
+ /** Sanitize a workflow name into a valid channel name. */
3291
+ private sanitizeChannelName(name: string): string {
3292
+ return name
3293
+ .toLowerCase()
3294
+ .replace(/[^a-z0-9-]/g, '-')
3295
+ .replace(/-+/g, '-')
3296
+ .slice(0, 32);
3297
+ }
3298
+
3299
+ /** Directory for persisted step outputs: .agent-relay/step-outputs/{runId}/ */
3300
+ private getStepOutputDir(runId: string): string {
3301
+ return path.join(this.cwd, '.agent-relay', 'step-outputs', runId);
3302
+ }
3303
+
3304
+ /** Persist step output to disk and post full output as a channel message. */
3305
+ private async persistStepOutput(runId: string, stepName: string, output: string): Promise<void> {
3306
+ // 1. Write to disk
3307
+ try {
3308
+ const dir = this.getStepOutputDir(runId);
3309
+ mkdirSync(dir, { recursive: true });
3310
+ const cleaned = WorkflowRunner.stripAnsi(output);
3311
+ await writeFile(path.join(dir, `${stepName}.md`), cleaned);
3312
+ } catch {
3313
+ // Non-critical
3314
+ }
3315
+
3316
+ // 2. Post scrubbed output as a single channel message (most recent tail only)
3317
+ const scrubbed = WorkflowRunner.scrubForChannel(output);
3318
+ if (scrubbed.length === 0) {
3319
+ this.postToChannel(`**[${stepName}]** Step completed — output written to disk`);
3320
+ return;
3321
+ }
3322
+
3323
+ const maxMsg = 2000;
3324
+ const preview = scrubbed.length > maxMsg ? scrubbed.slice(-maxMsg) : scrubbed;
3325
+ this.postToChannel(`**[${stepName}] Output:**\n\`\`\`\n${preview}\n\`\`\``);
3326
+ }
3327
+
3328
+ /** Load persisted step output from disk. */
3329
+ private loadStepOutput(runId: string, stepName: string): string | undefined {
3330
+ try {
3331
+ const filePath = path.join(this.getStepOutputDir(runId), `${stepName}.md`);
3332
+ if (!existsSync(filePath)) return undefined;
3333
+ return readFileSync(filePath, 'utf-8');
3334
+ } catch {
3335
+ return undefined;
3336
+ }
3337
+ }
3338
+
3339
+ /** Get or create the worker logs directory (.agent-relay/team/worker-logs) */
3340
+ private getWorkerLogsDir(): string {
3341
+ const logsDir = path.join(this.cwd, '.agent-relay', 'team', 'worker-logs');
3342
+ mkdirSync(logsDir, { recursive: true });
3343
+ return logsDir;
3344
+ }
3345
+
3346
+ /** Register a spawned agent in workers.json so `agents:kill` can find it. */
3347
+ private registerWorker(
3348
+ agentName: string,
3349
+ cli: string,
3350
+ task: string,
3351
+ pid?: number,
3352
+ interactive = true
3353
+ ): void {
3354
+ // Track in memory first (no race condition)
3355
+ const workerEntry = {
3356
+ cli,
3357
+ task: task.slice(0, 500),
3358
+ spawnedAt: Date.now(),
3359
+ pid,
3360
+ interactive,
3361
+ logFile: path.join(this.getWorkerLogsDir(), `${agentName}.log`),
3362
+ };
3363
+ this.activeWorkers.set(agentName, workerEntry);
3364
+
3365
+ // Serialize file writes with mutex to prevent race conditions
3366
+ this.workersFileLock = this.workersFileLock.then(() => {
3367
+ try {
3368
+ mkdirSync(path.dirname(this.workersPath), { recursive: true });
3369
+ // Filter out any existing entry with the same name before adding
3370
+ const existing = this.readWorkers().filter((w) => w.name !== agentName);
3371
+ existing.push({ name: agentName, ...workerEntry });
3372
+ this.writeWorkers(existing);
3373
+ } catch {
3374
+ // Non-critical — don't fail the workflow if workers.json can't be written
3375
+ }
3376
+ });
3377
+ }
3378
+
3379
+ /** Remove a spawned agent from workers.json after it exits. */
3380
+ private unregisterWorker(agentName: string): void {
3381
+ // Remove from in-memory tracking first
3382
+ this.activeWorkers.delete(agentName);
3383
+
3384
+ // Serialize file writes with mutex to prevent race conditions
3385
+ this.workersFileLock = this.workersFileLock.then(() => {
3386
+ try {
3387
+ const existing = this.readWorkers();
3388
+ const filtered = existing.filter((w) => w.name !== agentName);
3389
+ this.writeWorkers(filtered);
3390
+ } catch {
3391
+ // Non-critical
3392
+ }
3393
+ });
3394
+ }
3395
+
3396
+ private readWorkers(): Array<Record<string, unknown>> {
3397
+ try {
3398
+ if (!existsSync(this.workersPath)) return [];
3399
+ const raw = JSON.parse(readFileSync(this.workersPath, 'utf-8'));
3400
+ return Array.isArray(raw?.workers) ? raw.workers : [];
3401
+ } catch {
3402
+ return [];
3403
+ }
3404
+ }
3405
+
3406
+ private writeWorkers(workers: Array<Record<string, unknown>>): void {
3407
+ writeFileSync(this.workersPath, JSON.stringify({ workers }, null, 2));
3408
+ }
3409
+ }