agent-relay 1.0.22 → 1.2.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 (613) hide show
  1. package/README.md +1 -1
  2. package/dist/bridge/shadow-cli.d.ts +17 -0
  3. package/dist/bridge/shadow-cli.d.ts.map +1 -0
  4. package/dist/bridge/shadow-cli.js +75 -0
  5. package/dist/bridge/shadow-cli.js.map +1 -0
  6. package/dist/bridge/shadow-config.d.ts +87 -0
  7. package/dist/bridge/shadow-config.d.ts.map +1 -0
  8. package/dist/bridge/shadow-config.js +134 -0
  9. package/dist/bridge/shadow-config.js.map +1 -0
  10. package/dist/bridge/spawner.d.ts +68 -1
  11. package/dist/bridge/spawner.d.ts.map +1 -1
  12. package/dist/bridge/spawner.js +360 -16
  13. package/dist/bridge/spawner.js.map +1 -1
  14. package/dist/bridge/types.d.ts +67 -0
  15. package/dist/bridge/types.d.ts.map +1 -1
  16. package/dist/cli/index.js +1196 -15
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cloud/api/auth.d.ts +20 -0
  19. package/dist/cloud/api/auth.d.ts.map +1 -0
  20. package/dist/cloud/api/auth.js +128 -0
  21. package/dist/cloud/api/auth.js.map +1 -0
  22. package/dist/cloud/api/billing.d.ts +17 -0
  23. package/dist/cloud/api/billing.d.ts.map +1 -0
  24. package/dist/cloud/api/billing.js +353 -0
  25. package/dist/cloud/api/billing.js.map +1 -0
  26. package/dist/cloud/api/cli-pty-runner.d.ts +54 -0
  27. package/dist/cloud/api/cli-pty-runner.d.ts.map +1 -0
  28. package/dist/cloud/api/cli-pty-runner.js +119 -0
  29. package/dist/cloud/api/cli-pty-runner.js.map +1 -0
  30. package/dist/cloud/api/coordinators.d.ts +8 -0
  31. package/dist/cloud/api/coordinators.d.ts.map +1 -0
  32. package/dist/cloud/api/coordinators.js +347 -0
  33. package/dist/cloud/api/coordinators.js.map +1 -0
  34. package/dist/cloud/api/daemons.d.ts +12 -0
  35. package/dist/cloud/api/daemons.d.ts.map +1 -0
  36. package/dist/cloud/api/daemons.js +320 -0
  37. package/dist/cloud/api/daemons.js.map +1 -0
  38. package/dist/cloud/api/generic-webhooks.d.ts +8 -0
  39. package/dist/cloud/api/generic-webhooks.d.ts.map +1 -0
  40. package/dist/cloud/api/generic-webhooks.js +129 -0
  41. package/dist/cloud/api/generic-webhooks.js.map +1 -0
  42. package/dist/cloud/api/git.d.ts +8 -0
  43. package/dist/cloud/api/git.d.ts.map +1 -0
  44. package/dist/cloud/api/git.js +131 -0
  45. package/dist/cloud/api/git.js.map +1 -0
  46. package/dist/cloud/api/github-app.d.ts +11 -0
  47. package/dist/cloud/api/github-app.d.ts.map +1 -0
  48. package/dist/cloud/api/github-app.js +189 -0
  49. package/dist/cloud/api/github-app.js.map +1 -0
  50. package/dist/cloud/api/middleware/planLimits.d.ts +43 -0
  51. package/dist/cloud/api/middleware/planLimits.d.ts.map +1 -0
  52. package/dist/cloud/api/middleware/planLimits.js +202 -0
  53. package/dist/cloud/api/middleware/planLimits.js.map +1 -0
  54. package/dist/cloud/api/monitoring.d.ts +11 -0
  55. package/dist/cloud/api/monitoring.d.ts.map +1 -0
  56. package/dist/cloud/api/monitoring.js +578 -0
  57. package/dist/cloud/api/monitoring.js.map +1 -0
  58. package/dist/cloud/api/nango-auth.d.ts +9 -0
  59. package/dist/cloud/api/nango-auth.d.ts.map +1 -0
  60. package/dist/cloud/api/nango-auth.js +377 -0
  61. package/dist/cloud/api/nango-auth.js.map +1 -0
  62. package/dist/cloud/api/onboarding.d.ts +15 -0
  63. package/dist/cloud/api/onboarding.d.ts.map +1 -0
  64. package/dist/cloud/api/onboarding.js +588 -0
  65. package/dist/cloud/api/onboarding.js.map +1 -0
  66. package/dist/cloud/api/policy.d.ts +8 -0
  67. package/dist/cloud/api/policy.d.ts.map +1 -0
  68. package/dist/cloud/api/policy.js +229 -0
  69. package/dist/cloud/api/policy.js.map +1 -0
  70. package/dist/cloud/api/providers.d.ts +7 -0
  71. package/dist/cloud/api/providers.d.ts.map +1 -0
  72. package/dist/cloud/api/providers.js +507 -0
  73. package/dist/cloud/api/providers.js.map +1 -0
  74. package/dist/cloud/api/repos.d.ts +7 -0
  75. package/dist/cloud/api/repos.d.ts.map +1 -0
  76. package/dist/cloud/api/repos.js +314 -0
  77. package/dist/cloud/api/repos.js.map +1 -0
  78. package/dist/cloud/api/teams.d.ts +7 -0
  79. package/dist/cloud/api/teams.d.ts.map +1 -0
  80. package/dist/cloud/api/teams.js +279 -0
  81. package/dist/cloud/api/teams.js.map +1 -0
  82. package/dist/cloud/api/test-helpers.d.ts +10 -0
  83. package/dist/cloud/api/test-helpers.d.ts.map +1 -0
  84. package/dist/cloud/api/test-helpers.js +575 -0
  85. package/dist/cloud/api/test-helpers.js.map +1 -0
  86. package/dist/cloud/api/usage.d.ts +7 -0
  87. package/dist/cloud/api/usage.d.ts.map +1 -0
  88. package/dist/cloud/api/usage.js +98 -0
  89. package/dist/cloud/api/usage.js.map +1 -0
  90. package/dist/cloud/api/webhooks.d.ts +7 -0
  91. package/dist/cloud/api/webhooks.d.ts.map +1 -0
  92. package/dist/cloud/api/webhooks.js +496 -0
  93. package/dist/cloud/api/webhooks.js.map +1 -0
  94. package/dist/cloud/api/workspaces.d.ts +7 -0
  95. package/dist/cloud/api/workspaces.d.ts.map +1 -0
  96. package/dist/cloud/api/workspaces.js +727 -0
  97. package/dist/cloud/api/workspaces.js.map +1 -0
  98. package/dist/cloud/billing/index.d.ts +9 -0
  99. package/dist/cloud/billing/index.d.ts.map +1 -0
  100. package/dist/cloud/billing/index.js +9 -0
  101. package/dist/cloud/billing/index.js.map +1 -0
  102. package/dist/cloud/billing/plans.d.ts +39 -0
  103. package/dist/cloud/billing/plans.d.ts.map +1 -0
  104. package/dist/cloud/billing/plans.js +245 -0
  105. package/dist/cloud/billing/plans.js.map +1 -0
  106. package/dist/cloud/billing/service.d.ts +80 -0
  107. package/dist/cloud/billing/service.d.ts.map +1 -0
  108. package/dist/cloud/billing/service.js +388 -0
  109. package/dist/cloud/billing/service.js.map +1 -0
  110. package/dist/cloud/billing/types.d.ts +141 -0
  111. package/dist/cloud/billing/types.d.ts.map +1 -0
  112. package/dist/cloud/billing/types.js +7 -0
  113. package/dist/cloud/billing/types.js.map +1 -0
  114. package/dist/cloud/config.d.ts +66 -0
  115. package/dist/cloud/config.d.ts.map +1 -0
  116. package/dist/cloud/config.js +92 -0
  117. package/dist/cloud/config.js.map +1 -0
  118. package/dist/cloud/db/drizzle.d.ts +215 -0
  119. package/dist/cloud/db/drizzle.d.ts.map +1 -0
  120. package/dist/cloud/db/drizzle.js +1083 -0
  121. package/dist/cloud/db/drizzle.js.map +1 -0
  122. package/dist/cloud/db/index.d.ts +35 -0
  123. package/dist/cloud/db/index.d.ts.map +1 -0
  124. package/dist/cloud/db/index.js +52 -0
  125. package/dist/cloud/db/index.js.map +1 -0
  126. package/dist/cloud/db/schema.d.ts +4519 -0
  127. package/dist/cloud/db/schema.d.ts.map +1 -0
  128. package/dist/cloud/db/schema.js +547 -0
  129. package/dist/cloud/db/schema.js.map +1 -0
  130. package/dist/cloud/index.d.ts +12 -0
  131. package/dist/cloud/index.d.ts.map +1 -0
  132. package/dist/cloud/index.js +39 -0
  133. package/dist/cloud/index.js.map +1 -0
  134. package/dist/cloud/provisioner/index.d.ts +75 -0
  135. package/dist/cloud/provisioner/index.d.ts.map +1 -0
  136. package/dist/cloud/provisioner/index.js +977 -0
  137. package/dist/cloud/provisioner/index.js.map +1 -0
  138. package/dist/cloud/server.d.ts +17 -0
  139. package/dist/cloud/server.d.ts.map +1 -0
  140. package/dist/cloud/server.js +534 -0
  141. package/dist/cloud/server.js.map +1 -0
  142. package/dist/cloud/services/auto-scaler.d.ts +152 -0
  143. package/dist/cloud/services/auto-scaler.d.ts.map +1 -0
  144. package/dist/cloud/services/auto-scaler.js +439 -0
  145. package/dist/cloud/services/auto-scaler.js.map +1 -0
  146. package/dist/cloud/services/capacity-manager.d.ts +148 -0
  147. package/dist/cloud/services/capacity-manager.d.ts.map +1 -0
  148. package/dist/cloud/services/capacity-manager.js +449 -0
  149. package/dist/cloud/services/capacity-manager.js.map +1 -0
  150. package/dist/cloud/services/ci-agent-spawner.d.ts +49 -0
  151. package/dist/cloud/services/ci-agent-spawner.d.ts.map +1 -0
  152. package/dist/cloud/services/ci-agent-spawner.js +373 -0
  153. package/dist/cloud/services/ci-agent-spawner.js.map +1 -0
  154. package/dist/cloud/services/coordinator.d.ts +62 -0
  155. package/dist/cloud/services/coordinator.d.ts.map +1 -0
  156. package/dist/cloud/services/coordinator.js +389 -0
  157. package/dist/cloud/services/coordinator.js.map +1 -0
  158. package/dist/cloud/services/index.d.ts +12 -0
  159. package/dist/cloud/services/index.d.ts.map +1 -0
  160. package/dist/cloud/services/index.js +15 -0
  161. package/dist/cloud/services/index.js.map +1 -0
  162. package/dist/cloud/services/mention-handler.d.ts +65 -0
  163. package/dist/cloud/services/mention-handler.d.ts.map +1 -0
  164. package/dist/cloud/services/mention-handler.js +405 -0
  165. package/dist/cloud/services/mention-handler.js.map +1 -0
  166. package/dist/cloud/services/nango.d.ts +126 -0
  167. package/dist/cloud/services/nango.d.ts.map +1 -0
  168. package/dist/cloud/services/nango.js +191 -0
  169. package/dist/cloud/services/nango.js.map +1 -0
  170. package/dist/cloud/services/persistence.d.ts +131 -0
  171. package/dist/cloud/services/persistence.d.ts.map +1 -0
  172. package/dist/cloud/services/persistence.js +200 -0
  173. package/dist/cloud/services/persistence.js.map +1 -0
  174. package/dist/cloud/services/planLimits.d.ts +125 -0
  175. package/dist/cloud/services/planLimits.d.ts.map +1 -0
  176. package/dist/cloud/services/planLimits.js +282 -0
  177. package/dist/cloud/services/planLimits.js.map +1 -0
  178. package/dist/cloud/services/scaling-orchestrator.d.ts +159 -0
  179. package/dist/cloud/services/scaling-orchestrator.d.ts.map +1 -0
  180. package/dist/cloud/services/scaling-orchestrator.js +502 -0
  181. package/dist/cloud/services/scaling-orchestrator.js.map +1 -0
  182. package/dist/cloud/services/scaling-policy.d.ts +121 -0
  183. package/dist/cloud/services/scaling-policy.d.ts.map +1 -0
  184. package/dist/cloud/services/scaling-policy.js +415 -0
  185. package/dist/cloud/services/scaling-policy.js.map +1 -0
  186. package/dist/cloud/vault/index.d.ts +76 -0
  187. package/dist/cloud/vault/index.d.ts.map +1 -0
  188. package/dist/cloud/vault/index.js +219 -0
  189. package/dist/cloud/vault/index.js.map +1 -0
  190. package/dist/cloud/webhooks/index.d.ts +24 -0
  191. package/dist/cloud/webhooks/index.d.ts.map +1 -0
  192. package/dist/cloud/webhooks/index.js +29 -0
  193. package/dist/cloud/webhooks/index.js.map +1 -0
  194. package/dist/cloud/webhooks/parsers/github.d.ts +8 -0
  195. package/dist/cloud/webhooks/parsers/github.d.ts.map +1 -0
  196. package/dist/cloud/webhooks/parsers/github.js +234 -0
  197. package/dist/cloud/webhooks/parsers/github.js.map +1 -0
  198. package/dist/cloud/webhooks/parsers/index.d.ts +23 -0
  199. package/dist/cloud/webhooks/parsers/index.d.ts.map +1 -0
  200. package/dist/cloud/webhooks/parsers/index.js +30 -0
  201. package/dist/cloud/webhooks/parsers/index.js.map +1 -0
  202. package/dist/cloud/webhooks/parsers/linear.d.ts +9 -0
  203. package/dist/cloud/webhooks/parsers/linear.d.ts.map +1 -0
  204. package/dist/cloud/webhooks/parsers/linear.js +258 -0
  205. package/dist/cloud/webhooks/parsers/linear.js.map +1 -0
  206. package/dist/cloud/webhooks/parsers/slack.d.ts +9 -0
  207. package/dist/cloud/webhooks/parsers/slack.d.ts.map +1 -0
  208. package/dist/cloud/webhooks/parsers/slack.js +214 -0
  209. package/dist/cloud/webhooks/parsers/slack.js.map +1 -0
  210. package/dist/cloud/webhooks/responders/github.d.ts +8 -0
  211. package/dist/cloud/webhooks/responders/github.d.ts.map +1 -0
  212. package/dist/cloud/webhooks/responders/github.js +73 -0
  213. package/dist/cloud/webhooks/responders/github.js.map +1 -0
  214. package/dist/cloud/webhooks/responders/index.d.ts +23 -0
  215. package/dist/cloud/webhooks/responders/index.d.ts.map +1 -0
  216. package/dist/cloud/webhooks/responders/index.js +30 -0
  217. package/dist/cloud/webhooks/responders/index.js.map +1 -0
  218. package/dist/cloud/webhooks/responders/linear.d.ts +9 -0
  219. package/dist/cloud/webhooks/responders/linear.d.ts.map +1 -0
  220. package/dist/cloud/webhooks/responders/linear.js +149 -0
  221. package/dist/cloud/webhooks/responders/linear.js.map +1 -0
  222. package/dist/cloud/webhooks/responders/slack.d.ts +20 -0
  223. package/dist/cloud/webhooks/responders/slack.d.ts.map +1 -0
  224. package/dist/cloud/webhooks/responders/slack.js +178 -0
  225. package/dist/cloud/webhooks/responders/slack.js.map +1 -0
  226. package/dist/cloud/webhooks/router.d.ts +25 -0
  227. package/dist/cloud/webhooks/router.d.ts.map +1 -0
  228. package/dist/cloud/webhooks/router.js +504 -0
  229. package/dist/cloud/webhooks/router.js.map +1 -0
  230. package/dist/cloud/webhooks/rules-engine.d.ts +24 -0
  231. package/dist/cloud/webhooks/rules-engine.d.ts.map +1 -0
  232. package/dist/cloud/webhooks/rules-engine.js +287 -0
  233. package/dist/cloud/webhooks/rules-engine.js.map +1 -0
  234. package/dist/cloud/webhooks/types.d.ts +186 -0
  235. package/dist/cloud/webhooks/types.d.ts.map +1 -0
  236. package/dist/cloud/webhooks/types.js +8 -0
  237. package/dist/cloud/webhooks/types.js.map +1 -0
  238. package/dist/continuity/formatter.d.ts +51 -0
  239. package/dist/continuity/formatter.d.ts.map +1 -0
  240. package/dist/continuity/formatter.js +313 -0
  241. package/dist/continuity/formatter.js.map +1 -0
  242. package/dist/continuity/handoff-store.d.ts +67 -0
  243. package/dist/continuity/handoff-store.d.ts.map +1 -0
  244. package/dist/continuity/handoff-store.js +472 -0
  245. package/dist/continuity/handoff-store.js.map +1 -0
  246. package/dist/continuity/index.d.ts +45 -0
  247. package/dist/continuity/index.d.ts.map +1 -0
  248. package/dist/continuity/index.js +48 -0
  249. package/dist/continuity/index.js.map +1 -0
  250. package/dist/continuity/ledger-store.d.ts +110 -0
  251. package/dist/continuity/ledger-store.d.ts.map +1 -0
  252. package/dist/continuity/ledger-store.js +500 -0
  253. package/dist/continuity/ledger-store.js.map +1 -0
  254. package/dist/continuity/manager.d.ts +178 -0
  255. package/dist/continuity/manager.d.ts.map +1 -0
  256. package/dist/continuity/manager.js +562 -0
  257. package/dist/continuity/manager.js.map +1 -0
  258. package/dist/continuity/parser.d.ts +76 -0
  259. package/dist/continuity/parser.d.ts.map +1 -0
  260. package/dist/continuity/parser.js +579 -0
  261. package/dist/continuity/parser.js.map +1 -0
  262. package/dist/continuity/types.d.ts +180 -0
  263. package/dist/continuity/types.d.ts.map +1 -0
  264. package/dist/continuity/types.js +9 -0
  265. package/dist/continuity/types.js.map +1 -0
  266. package/dist/daemon/agent-manager.d.ts +114 -0
  267. package/dist/daemon/agent-manager.d.ts.map +1 -0
  268. package/dist/daemon/agent-manager.js +513 -0
  269. package/dist/daemon/agent-manager.js.map +1 -0
  270. package/dist/daemon/agent-registry.d.ts +34 -0
  271. package/dist/daemon/agent-registry.d.ts.map +1 -1
  272. package/dist/daemon/agent-registry.js +45 -2
  273. package/dist/daemon/agent-registry.js.map +1 -1
  274. package/dist/daemon/api.d.ts +81 -0
  275. package/dist/daemon/api.d.ts.map +1 -0
  276. package/dist/daemon/api.js +554 -0
  277. package/dist/daemon/api.js.map +1 -0
  278. package/dist/daemon/cli-auth.d.ts +67 -0
  279. package/dist/daemon/cli-auth.d.ts.map +1 -0
  280. package/dist/daemon/cli-auth.js +537 -0
  281. package/dist/daemon/cli-auth.js.map +1 -0
  282. package/dist/daemon/cloud-sync.d.ts +101 -0
  283. package/dist/daemon/cloud-sync.d.ts.map +1 -0
  284. package/dist/daemon/cloud-sync.js +263 -0
  285. package/dist/daemon/cloud-sync.js.map +1 -0
  286. package/dist/daemon/index.d.ts +4 -0
  287. package/dist/daemon/index.d.ts.map +1 -1
  288. package/dist/daemon/index.js +6 -0
  289. package/dist/daemon/index.js.map +1 -1
  290. package/dist/daemon/orchestrator.d.ts +155 -0
  291. package/dist/daemon/orchestrator.d.ts.map +1 -0
  292. package/dist/daemon/orchestrator.js +766 -0
  293. package/dist/daemon/orchestrator.js.map +1 -0
  294. package/dist/daemon/router.d.ts +29 -0
  295. package/dist/daemon/router.d.ts.map +1 -1
  296. package/dist/daemon/router.js +143 -21
  297. package/dist/daemon/router.js.map +1 -1
  298. package/dist/daemon/server.d.ts +42 -0
  299. package/dist/daemon/server.d.ts.map +1 -1
  300. package/dist/daemon/server.js +199 -16
  301. package/dist/daemon/server.js.map +1 -1
  302. package/dist/daemon/services/browser-testing.d.ts +88 -0
  303. package/dist/daemon/services/browser-testing.d.ts.map +1 -0
  304. package/dist/daemon/services/browser-testing.js +244 -0
  305. package/dist/daemon/services/browser-testing.js.map +1 -0
  306. package/dist/daemon/services/container-spawner.d.ts +135 -0
  307. package/dist/daemon/services/container-spawner.d.ts.map +1 -0
  308. package/dist/daemon/services/container-spawner.js +313 -0
  309. package/dist/daemon/services/container-spawner.js.map +1 -0
  310. package/dist/daemon/types.d.ts +131 -0
  311. package/dist/daemon/types.d.ts.map +1 -0
  312. package/dist/daemon/types.js +6 -0
  313. package/dist/daemon/types.js.map +1 -0
  314. package/dist/daemon/workspace-manager.d.ts +75 -0
  315. package/dist/daemon/workspace-manager.d.ts.map +1 -0
  316. package/dist/daemon/workspace-manager.js +289 -0
  317. package/dist/daemon/workspace-manager.js.map +1 -0
  318. package/dist/dashboard/out/404.html +1 -1
  319. package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +1 -0
  320. package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
  321. package/dist/dashboard/out/_next/static/chunks/480-2d4111711d4e473c.js +1 -0
  322. package/dist/dashboard/out/_next/static/chunks/724-73c1ee5f60abe860.js +9 -0
  323. package/dist/dashboard/out/_next/static/chunks/766-c3a14283c88d815b.js +1 -0
  324. package/dist/dashboard/out/_next/static/chunks/app/app/page-7120be68bea622f3.js +1 -0
  325. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-dc2e3a1a22478efc.js +1 -0
  326. package/dist/dashboard/out/_next/static/chunks/app/history/page-56a8b4616a90dc43.js +1 -0
  327. package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +1 -0
  328. package/dist/dashboard/out/_next/static/chunks/app/login/page-3eac37ea6f5dd153.js +1 -0
  329. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-1081dd190a331a91.js +1 -0
  330. package/dist/dashboard/out/_next/static/chunks/app/page-daf87e86f783f980.js +1 -0
  331. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-4d72d5a5d8a9b618.js +1 -0
  332. package/dist/dashboard/out/_next/static/chunks/app/providers/page-b68a681526eb145e.js +1 -0
  333. package/dist/dashboard/out/_next/static/chunks/app/signup/page-fee4ed1709070bcd.js +1 -0
  334. package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
  335. package/dist/dashboard/out/_next/static/chunks/{main-e0a1f53fe0617a63.js → main-97850e03d723ea8c.js} +1 -1
  336. package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +1 -0
  337. package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
  338. package/dist/dashboard/out/_next/static/css/29852f26181969a0.css +1 -0
  339. package/dist/dashboard/out/_next/static/css/411ce23ffeae9f76.css +1 -0
  340. package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
  341. package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
  342. package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
  343. package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
  344. package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
  345. package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
  346. package/dist/dashboard/out/alt-logos/logo.svg +38 -0
  347. package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
  348. package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
  349. package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
  350. package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
  351. package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
  352. package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
  353. package/dist/dashboard/out/app.html +1 -0
  354. package/dist/dashboard/out/app.txt +7 -0
  355. package/dist/dashboard/out/connect-repos.html +1 -0
  356. package/dist/dashboard/out/connect-repos.txt +7 -0
  357. package/dist/dashboard/out/history.html +1 -0
  358. package/dist/dashboard/out/history.txt +7 -0
  359. package/dist/dashboard/out/index.html +1 -1
  360. package/dist/dashboard/out/index.txt +2 -2
  361. package/dist/dashboard/out/login.html +6 -0
  362. package/dist/dashboard/out/login.txt +7 -0
  363. package/dist/dashboard/out/metrics.html +1 -515
  364. package/dist/dashboard/out/metrics.txt +2 -2
  365. package/dist/dashboard/out/pricing.html +13 -0
  366. package/dist/dashboard/out/pricing.txt +7 -0
  367. package/dist/dashboard/out/providers.html +1 -0
  368. package/dist/dashboard/out/providers.txt +7 -0
  369. package/dist/dashboard/out/signup.html +6 -0
  370. package/dist/dashboard/out/signup.txt +7 -0
  371. package/dist/dashboard-server/metrics.d.ts.map +1 -1
  372. package/dist/dashboard-server/metrics.js +3 -2
  373. package/dist/dashboard-server/metrics.js.map +1 -1
  374. package/dist/dashboard-server/server.d.ts.map +1 -1
  375. package/dist/dashboard-server/server.js +2653 -130
  376. package/dist/dashboard-server/server.js.map +1 -1
  377. package/dist/hooks/emitter.d.ts +40 -0
  378. package/dist/hooks/emitter.d.ts.map +1 -0
  379. package/dist/hooks/emitter.js +63 -0
  380. package/dist/hooks/emitter.js.map +1 -0
  381. package/dist/hooks/index.d.ts +3 -0
  382. package/dist/hooks/index.d.ts.map +1 -1
  383. package/dist/hooks/index.js +3 -0
  384. package/dist/hooks/index.js.map +1 -1
  385. package/dist/hooks/registry.d.ts +173 -0
  386. package/dist/hooks/registry.d.ts.map +1 -0
  387. package/dist/hooks/registry.js +476 -0
  388. package/dist/hooks/registry.js.map +1 -0
  389. package/dist/hooks/trajectory-hooks.d.ts +52 -0
  390. package/dist/hooks/trajectory-hooks.d.ts.map +1 -0
  391. package/dist/hooks/trajectory-hooks.js +183 -0
  392. package/dist/hooks/trajectory-hooks.js.map +1 -0
  393. package/dist/hooks/types.d.ts +141 -0
  394. package/dist/hooks/types.d.ts.map +1 -1
  395. package/dist/index.d.ts +2 -0
  396. package/dist/index.d.ts.map +1 -1
  397. package/dist/index.js +3 -0
  398. package/dist/index.js.map +1 -1
  399. package/dist/memory/adapters/index.d.ts +8 -0
  400. package/dist/memory/adapters/index.d.ts.map +1 -0
  401. package/dist/memory/adapters/index.js +8 -0
  402. package/dist/memory/adapters/index.js.map +1 -0
  403. package/dist/memory/adapters/inmemory.d.ts +59 -0
  404. package/dist/memory/adapters/inmemory.d.ts.map +1 -0
  405. package/dist/memory/adapters/inmemory.js +195 -0
  406. package/dist/memory/adapters/inmemory.js.map +1 -0
  407. package/dist/memory/adapters/supermemory.d.ts +71 -0
  408. package/dist/memory/adapters/supermemory.d.ts.map +1 -0
  409. package/dist/memory/adapters/supermemory.js +338 -0
  410. package/dist/memory/adapters/supermemory.js.map +1 -0
  411. package/dist/memory/factory.d.ts +48 -0
  412. package/dist/memory/factory.d.ts.map +1 -0
  413. package/dist/memory/factory.js +143 -0
  414. package/dist/memory/factory.js.map +1 -0
  415. package/dist/memory/index.d.ts +32 -0
  416. package/dist/memory/index.d.ts.map +1 -0
  417. package/dist/memory/index.js +32 -0
  418. package/dist/memory/index.js.map +1 -0
  419. package/dist/memory/memory-hooks.d.ts +60 -0
  420. package/dist/memory/memory-hooks.d.ts.map +1 -0
  421. package/dist/memory/memory-hooks.js +313 -0
  422. package/dist/memory/memory-hooks.js.map +1 -0
  423. package/dist/memory/service.d.ts +49 -0
  424. package/dist/memory/service.d.ts.map +1 -0
  425. package/dist/memory/service.js +146 -0
  426. package/dist/memory/service.js.map +1 -0
  427. package/dist/memory/types.d.ts +195 -0
  428. package/dist/memory/types.d.ts.map +1 -0
  429. package/dist/memory/types.js +8 -0
  430. package/dist/memory/types.js.map +1 -0
  431. package/dist/policy/agent-policy.d.ts +225 -0
  432. package/dist/policy/agent-policy.d.ts.map +1 -0
  433. package/dist/policy/agent-policy.js +665 -0
  434. package/dist/policy/agent-policy.js.map +1 -0
  435. package/dist/policy/cloud-policy-fetcher.d.ts +12 -0
  436. package/dist/policy/cloud-policy-fetcher.d.ts.map +1 -0
  437. package/dist/policy/cloud-policy-fetcher.js +64 -0
  438. package/dist/policy/cloud-policy-fetcher.js.map +1 -0
  439. package/dist/protocol/types.d.ts +10 -1
  440. package/dist/protocol/types.d.ts.map +1 -1
  441. package/dist/resiliency/context-persistence.d.ts +140 -0
  442. package/dist/resiliency/context-persistence.d.ts.map +1 -0
  443. package/dist/resiliency/context-persistence.js +397 -0
  444. package/dist/resiliency/context-persistence.js.map +1 -0
  445. package/dist/resiliency/crash-insights.d.ts +156 -0
  446. package/dist/resiliency/crash-insights.d.ts.map +1 -0
  447. package/dist/resiliency/crash-insights.js +492 -0
  448. package/dist/resiliency/crash-insights.js.map +1 -0
  449. package/dist/resiliency/gossip-health.d.ts +137 -0
  450. package/dist/resiliency/gossip-health.d.ts.map +1 -0
  451. package/dist/resiliency/gossip-health.js +241 -0
  452. package/dist/resiliency/gossip-health.js.map +1 -0
  453. package/dist/resiliency/health-monitor.d.ts +97 -0
  454. package/dist/resiliency/health-monitor.d.ts.map +1 -0
  455. package/dist/resiliency/health-monitor.js +291 -0
  456. package/dist/resiliency/health-monitor.js.map +1 -0
  457. package/dist/resiliency/index.d.ts +68 -0
  458. package/dist/resiliency/index.d.ts.map +1 -0
  459. package/dist/resiliency/index.js +68 -0
  460. package/dist/resiliency/index.js.map +1 -0
  461. package/dist/resiliency/leader-watchdog.d.ts +109 -0
  462. package/dist/resiliency/leader-watchdog.d.ts.map +1 -0
  463. package/dist/resiliency/leader-watchdog.js +189 -0
  464. package/dist/resiliency/leader-watchdog.js.map +1 -0
  465. package/dist/resiliency/logger.d.ts +114 -0
  466. package/dist/resiliency/logger.d.ts.map +1 -0
  467. package/dist/resiliency/logger.js +250 -0
  468. package/dist/resiliency/logger.js.map +1 -0
  469. package/dist/resiliency/memory-monitor.d.ts +172 -0
  470. package/dist/resiliency/memory-monitor.d.ts.map +1 -0
  471. package/dist/resiliency/memory-monitor.js +593 -0
  472. package/dist/resiliency/memory-monitor.js.map +1 -0
  473. package/dist/resiliency/metrics.d.ts +115 -0
  474. package/dist/resiliency/metrics.d.ts.map +1 -0
  475. package/dist/resiliency/metrics.js +239 -0
  476. package/dist/resiliency/metrics.js.map +1 -0
  477. package/dist/resiliency/provider-context.d.ts +100 -0
  478. package/dist/resiliency/provider-context.d.ts.map +1 -0
  479. package/dist/resiliency/provider-context.js +360 -0
  480. package/dist/resiliency/provider-context.js.map +1 -0
  481. package/dist/resiliency/stateless-lead.d.ts +149 -0
  482. package/dist/resiliency/stateless-lead.d.ts.map +1 -0
  483. package/dist/resiliency/stateless-lead.js +308 -0
  484. package/dist/resiliency/stateless-lead.js.map +1 -0
  485. package/dist/resiliency/supervisor.d.ts +147 -0
  486. package/dist/resiliency/supervisor.d.ts.map +1 -0
  487. package/dist/resiliency/supervisor.js +459 -0
  488. package/dist/resiliency/supervisor.js.map +1 -0
  489. package/dist/shared/cli-auth-config.d.ts +91 -0
  490. package/dist/shared/cli-auth-config.d.ts.map +1 -0
  491. package/dist/shared/cli-auth-config.js +264 -0
  492. package/dist/shared/cli-auth-config.js.map +1 -0
  493. package/dist/storage/adapter.d.ts +3 -1
  494. package/dist/storage/adapter.d.ts.map +1 -1
  495. package/dist/storage/adapter.js +12 -2
  496. package/dist/storage/adapter.js.map +1 -1
  497. package/dist/storage/sqlite-adapter.d.ts.map +1 -1
  498. package/dist/storage/sqlite-adapter.js +18 -14
  499. package/dist/storage/sqlite-adapter.js.map +1 -1
  500. package/dist/trajectory/config.d.ts +84 -0
  501. package/dist/trajectory/config.d.ts.map +1 -0
  502. package/dist/trajectory/config.js +163 -0
  503. package/dist/trajectory/config.js.map +1 -0
  504. package/dist/trajectory/index.d.ts +8 -0
  505. package/dist/trajectory/index.d.ts.map +1 -0
  506. package/dist/trajectory/index.js +8 -0
  507. package/dist/trajectory/index.js.map +1 -0
  508. package/dist/trajectory/integration.d.ts +292 -0
  509. package/dist/trajectory/integration.d.ts.map +1 -0
  510. package/dist/trajectory/integration.js +834 -0
  511. package/dist/trajectory/integration.js.map +1 -0
  512. package/dist/utils/index.d.ts +1 -0
  513. package/dist/utils/index.d.ts.map +1 -1
  514. package/dist/utils/index.js +1 -0
  515. package/dist/utils/index.js.map +1 -1
  516. package/dist/utils/logger.d.ts +40 -0
  517. package/dist/utils/logger.d.ts.map +1 -0
  518. package/dist/utils/logger.js +84 -0
  519. package/dist/utils/logger.js.map +1 -0
  520. package/dist/utils/project-namespace.d.ts +24 -0
  521. package/dist/utils/project-namespace.d.ts.map +1 -1
  522. package/dist/utils/project-namespace.js +84 -0
  523. package/dist/utils/project-namespace.js.map +1 -1
  524. package/dist/wrapper/client.d.ts +16 -1
  525. package/dist/wrapper/client.d.ts.map +1 -1
  526. package/dist/wrapper/client.js +32 -1
  527. package/dist/wrapper/client.js.map +1 -1
  528. package/dist/wrapper/parser.d.ts +13 -0
  529. package/dist/wrapper/parser.d.ts.map +1 -1
  530. package/dist/wrapper/parser.js +217 -47
  531. package/dist/wrapper/parser.js.map +1 -1
  532. package/dist/wrapper/pty-wrapper.d.ts +219 -17
  533. package/dist/wrapper/pty-wrapper.d.ts.map +1 -1
  534. package/dist/wrapper/pty-wrapper.js +1050 -104
  535. package/dist/wrapper/pty-wrapper.js.map +1 -1
  536. package/dist/wrapper/shared.d.ts +165 -0
  537. package/dist/wrapper/shared.d.ts.map +1 -0
  538. package/dist/wrapper/shared.js +270 -0
  539. package/dist/wrapper/shared.js.map +1 -0
  540. package/dist/wrapper/tmux-wrapper.d.ts +78 -11
  541. package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
  542. package/dist/wrapper/tmux-wrapper.js +567 -106
  543. package/dist/wrapper/tmux-wrapper.js.map +1 -1
  544. package/docs/CLOUD-ARCHITECTURE.md +804 -0
  545. package/docs/CLOUD-ONBOARDING-DESIGN.md +1983 -0
  546. package/docs/HOOKS_API.md +394 -0
  547. package/docs/WRAPPER_EVENTS.md +358 -0
  548. package/docs/agent-policy-snippet.md +40 -0
  549. package/docs/agent-relay-protocol.md +238 -0
  550. package/docs/agent-relay-snippet.md +115 -6
  551. package/docs/archive/EXECUTIVE_SUMMARY.md +358 -0
  552. package/docs/archive/ROADMAP.md +329 -0
  553. package/docs/archive/TESTING_PRESENCE_FEATURES.md +327 -0
  554. package/docs/competitive/GASTOWN.md +451 -0
  555. package/docs/{COMPETITIVE_ANALYSIS.md → competitive/OVERVIEW.md} +1 -0
  556. package/docs/competitive/README.md +34 -0
  557. package/docs/competitive/TMUX_ORCHESTRATOR.md +605 -0
  558. package/docs/dashboard.png +0 -0
  559. package/docs/design/ci-failure-webhooks.md +812 -0
  560. package/docs/design/comprehensive-integrations.md +238 -0
  561. package/docs/design/e2b-sandbox-integration.md +504 -0
  562. package/docs/design/github-app-permissions.md +264 -0
  563. package/docs/guides/CLOUD.md +236 -0
  564. package/docs/guides/LOCAL.md +535 -0
  565. package/docs/guides/SELF-HOSTED.md +494 -0
  566. package/docs/local-testing.md +428 -0
  567. package/docs/proposals/continuous-claude-integration.md +622 -0
  568. package/docs/proposals/custom-commands.md +368 -0
  569. package/docs/proposals/shadow-as-subagent.md +765 -0
  570. package/docs/proposals/slack-bot-integration.md +1457 -0
  571. package/docs/tasks/global-skills-system.tasks.md +230 -0
  572. package/docs/tasks/webhook-integrations.tasks.md +184 -0
  573. package/docs/tasks/workspace-capabilities.tasks.md +121 -0
  574. package/docs/testing/RESILIENCY-TEST-PLAN-2026-01-01.md +366 -0
  575. package/package.json +45 -7
  576. package/scripts/cloud-setup.sh +96 -0
  577. package/scripts/manual-qa.sh +293 -0
  578. package/scripts/postinstall.js +60 -0
  579. package/scripts/run-cloud-qa.sh +220 -0
  580. package/scripts/test-cli-auth/Dockerfile +44 -0
  581. package/scripts/test-cli-auth/Dockerfile.real +79 -0
  582. package/scripts/test-cli-auth/README.md +286 -0
  583. package/scripts/test-cli-auth/ci-test-real-clis.ts +251 -0
  584. package/scripts/test-cli-auth/ci-test-runner.ts +263 -0
  585. package/scripts/test-cli-auth/mock-cli.sh +147 -0
  586. package/scripts/test-cli-auth/package.json +14 -0
  587. package/scripts/test-cli-auth/test-oauth-flow.ts +220 -0
  588. package/scripts/test-pty-input-auto.js +222 -0
  589. package/scripts/test-pty-input.js +150 -0
  590. package/dist/dashboard/out/_next/static/chunks/app/layout-c9d8c5d95e48c6bf.js +0 -1
  591. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-8aa9936bc6c771ab.js +0 -1
  592. package/dist/dashboard/out/_next/static/chunks/app/page-4498be09a5157759.js +0 -1
  593. package/dist/dashboard/out/_next/static/chunks/main-app-bae2e535de00de50.js +0 -1
  594. package/dist/dashboard/out/_next/static/chunks/webpack-c81f7fd28659d64f.js +0 -1
  595. package/dist/dashboard/out/_next/static/css/50ed6996e3df7bdd.css +0 -1
  596. /package/dist/dashboard/out/_next/static/{DXFA-jj8wb3PcY5DX2xcU → H5aWG0udPB4iOUIl_gytz}/_buildManifest.js +0 -0
  597. /package/dist/dashboard/out/_next/static/{DXFA-jj8wb3PcY5DX2xcU → H5aWG0udPB4iOUIl_gytz}/_ssgManifest.js +0 -0
  598. /package/dist/dashboard/out/_next/static/chunks/{117-3bef7b19f3e60751.js → 117-b100311aff8d5c61.js} +0 -0
  599. /package/dist/dashboard/out/_next/static/chunks/{648-6cf686106c891ad3.js → 648-a13d3c2b1be45466.js} +0 -0
  600. /package/dist/dashboard/out/_next/static/chunks/app/_not-found/{page-8ff6572bc7c9bc61.js → page-a4973f3e3c82fb67.js} +0 -0
  601. /package/dist/dashboard/out/_next/static/chunks/{fd9d1056-26bd8d656b496dba.js → fd9d1056-bf46c09eb57e019c.js} +0 -0
  602. /package/docs/{CHANGELOG.md → archive/CHANGELOG.md} +0 -0
  603. /package/docs/{CLI-SIMPLIFICATION-COMPLETE.md → archive/CLI-SIMPLIFICATION-COMPLETE.md} +0 -0
  604. /package/docs/{DESIGN_BRIDGE_STAFFING.md → archive/DESIGN_BRIDGE_STAFFING.md} +0 -0
  605. /package/docs/{DESIGN_V2.md → archive/DESIGN_V2.md} +0 -0
  606. /package/docs/{MONETIZATION.md → archive/MONETIZATION.md} +0 -0
  607. /package/docs/{PROPOSAL-trajectories.md → archive/PROPOSAL-trajectories.md} +0 -0
  608. /package/docs/{SCALING_ANALYSIS.md → archive/SCALING_ANALYSIS.md} +0 -0
  609. /package/docs/{TMUX_IMPLEMENTATION_NOTES.md → archive/TMUX_IMPLEMENTATION_NOTES.md} +0 -0
  610. /package/docs/{TMUX_IMPROVEMENTS.md → archive/TMUX_IMPROVEMENTS.md} +0 -0
  611. /package/docs/{dashboard-v2-plan.md → archive/dashboard-v2-plan.md} +0 -0
  612. /package/docs/{removable-code-analysis.md → archive/removable-code-analysis.md} +0 -0
  613. /package/docs/{competitive-analysis-mcp-agent-mail.md → competitive/MCP_AGENT_MAIL.md} +0 -0
@@ -20,6 +20,11 @@ import { InboxManager } from './inbox.js';
20
20
  import { SqliteStorageAdapter } from '../storage/sqlite-adapter.js';
21
21
  import { getProjectPaths } from '../utils/project-namespace.js';
22
22
  import { getTmuxPath } from '../utils/tmux-resolver.js';
23
+ import { findAgentConfig } from '../utils/agent-config.js';
24
+ import { getTrajectoryIntegration, detectPhaseFromContent, detectToolCalls, detectErrors, getCompactTrailInstructions, getTrailEnvVars, } from '../trajectory/integration.js';
25
+ import { escapeForShell } from '../bridge/utils.js';
26
+ import { getContinuityManager, parseContinuityCommand, hasContinuityCommand, } from '../continuity/index.js';
27
+ import { stripAnsi, sleep, getDefaultRelayPrefix, createInjectionMetrics, buildInjectionString, injectWithRetry as sharedInjectWithRetry, INJECTION_CONSTANTS, CLI_QUIRKS, } from './shared.js';
23
28
  const execAsync = promisify(exec);
24
29
  const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
30
  // Constants for cursor stability detection in waitForClearInput
@@ -34,10 +39,10 @@ const RELAY_LOG_TRUNCATE_LENGTH = 50;
34
39
  /**
35
40
  * Get the default relay prefix for a given CLI type.
36
41
  * All agents now use '->relay:' as the unified prefix.
42
+ * @deprecated Use getDefaultRelayPrefix() from shared.js instead
37
43
  */
38
44
  export function getDefaultPrefix(_cliType) {
39
- // Unified prefix for all agent types
40
- return '->relay:';
45
+ return getDefaultRelayPrefix();
41
46
  }
42
47
  export class TmuxWrapper {
43
48
  config;
@@ -60,21 +65,32 @@ export class TmuxWrapper {
60
65
  isInjecting = false;
61
66
  // Track processed output to avoid re-parsing
62
67
  processedOutputLength = 0;
68
+ lastLoggedLength = 0; // Track length for incremental log streaming
63
69
  lastDebugLog = 0;
64
70
  cliType;
65
71
  relayPrefix;
66
72
  lastSummaryHash = ''; // Dedup summary saves
67
73
  lastSummaryRawContent = ''; // Dedup invalid JSON error logging
68
74
  sessionEndProcessed = false; // Track if we've already processed session end
75
+ sessionEndData; // Store SESSION_END data for handoff
69
76
  pendingRelayCommands = [];
70
77
  queuedMessageHashes = new Set(); // For offline queue dedup
71
78
  MAX_PENDING_RELAY_COMMANDS = 50;
72
79
  processedSpawnCommands = new Set(); // Dedup spawn commands
73
80
  processedReleaseCommands = new Set(); // Dedup release commands
81
+ pendingFencedSpawn = null; // Track multi-line spawn task
74
82
  receivedMessageIdSet = new Set();
75
83
  receivedMessageIdOrder = [];
76
84
  MAX_RECEIVED_MESSAGES = 2000;
77
85
  tmuxPath; // Resolved path to tmux binary (system or bundled)
86
+ trajectory; // Trajectory tracking via trail
87
+ lastDetectedPhase; // Track last auto-detected PDERO phase
88
+ seenToolCalls = new Set(); // Dedup tool call trajectory events
89
+ seenErrors = new Set(); // Dedup error trajectory events
90
+ continuity; // Session continuity management
91
+ processedContinuityCommands = new Set(); // Dedup continuity commands
92
+ agentId; // Unique agent ID for resume functionality
93
+ injectionMetrics = createInjectionMetrics(); // Track injection reliability
78
94
  constructor(config) {
79
95
  this.config = {
80
96
  cols: process.stdout.columns || 120,
@@ -88,6 +104,7 @@ export class TmuxWrapper {
88
104
  activityIdleThresholdMs: 30_000, // Consider idle after 30s with no output
89
105
  outputStabilityTimeoutMs: 2000,
90
106
  outputStabilityPollMs: 200,
107
+ streamLogs: true, // Stream output to daemon for dashboard
91
108
  ...config,
92
109
  };
93
110
  // Detect CLI type from command for special handling
@@ -107,6 +124,9 @@ export class TmuxWrapper {
107
124
  else if (cmdLower.includes('droid')) {
108
125
  this.cliType = 'droid';
109
126
  }
127
+ else if (cmdLower.includes('opencode')) {
128
+ this.cliType = 'opencode';
129
+ }
110
130
  else {
111
131
  this.cliType = 'other';
112
132
  }
@@ -116,13 +136,22 @@ export class TmuxWrapper {
116
136
  this.sessionName = `relay-${config.name}`;
117
137
  // Resolve tmux path early so we fail fast if tmux isn't available
118
138
  this.tmuxPath = getTmuxPath();
139
+ // Auto-detect agent role from .claude/agents/ or .openagents/ if task not provided
140
+ let detectedTask = this.config.task;
141
+ if (!detectedTask) {
142
+ const agentConfig = findAgentConfig(config.name, this.config.cwd);
143
+ if (agentConfig?.description) {
144
+ detectedTask = agentConfig.description;
145
+ this.logStderr(`Auto-detected role: ${detectedTask.substring(0, 60)}...`, true);
146
+ }
147
+ }
119
148
  this.client = new RelayClient({
120
149
  agentName: config.name,
121
150
  socketPath: config.socketPath,
122
151
  cli: this.cliType,
123
152
  program: this.config.program,
124
153
  model: this.config.model,
125
- task: this.config.task,
154
+ task: detectedTask,
126
155
  workingDirectory: this.config.cwd ?? process.cwd(),
127
156
  quiet: true, // Keep stdout clean; we log to stderr via wrapper
128
157
  });
@@ -143,9 +172,13 @@ export class TmuxWrapper {
143
172
  this.storage = undefined;
144
173
  return false;
145
174
  });
175
+ // Initialize trajectory tracking via trail CLI
176
+ this.trajectory = getTrajectoryIntegration(projectPaths.projectId, config.name);
177
+ // Initialize continuity manager for session persistence
178
+ this.continuity = getContinuityManager({ defaultCli: this.cliType });
146
179
  // Handle incoming messages from relay
147
- this.client.onMessage = (from, payload, messageId, meta) => {
148
- this.handleIncomingMessage(from, payload, messageId, meta);
180
+ this.client.onMessage = (from, payload, messageId, meta, originalTo) => {
181
+ this.handleIncomingMessage(from, payload, messageId, meta, originalTo);
149
182
  };
150
183
  this.client.onStateChange = (state) => {
151
184
  // Only log to stderr, never stdout (user is in tmux)
@@ -180,6 +213,46 @@ export class TmuxWrapper {
180
213
  // Prefix with newline to avoid corrupting tmux status line
181
214
  process.stderr.write(`\r[relay:${this.config.name}] ${msg}\n`);
182
215
  }
216
+ /**
217
+ * Detect PDERO phase from output content and auto-transition if needed.
218
+ * Also detects tool calls and errors, recording them to the trajectory.
219
+ */
220
+ detectAndTransitionPhase(content) {
221
+ if (!this.trajectory)
222
+ return;
223
+ // Detect phase transitions
224
+ const detectedPhase = detectPhaseFromContent(content);
225
+ if (detectedPhase && detectedPhase !== this.lastDetectedPhase) {
226
+ const currentPhase = this.trajectory.getPhase();
227
+ if (detectedPhase !== currentPhase) {
228
+ this.trajectory.transition(detectedPhase, 'Auto-detected from output');
229
+ this.lastDetectedPhase = detectedPhase;
230
+ this.logStderr(`Phase transition: ${currentPhase || 'none'} → ${detectedPhase}`);
231
+ }
232
+ }
233
+ // Detect and record tool calls
234
+ // Note: We deduplicate by tool+status to record each unique tool type once per session
235
+ // (e.g., "Read" started, "Read" completed). This provides a summary of tools used
236
+ // without flooding the trajectory with every individual invocation.
237
+ const tools = detectToolCalls(content);
238
+ for (const tool of tools) {
239
+ const key = `${tool.tool}:${tool.status || 'started'}`;
240
+ if (!this.seenToolCalls.has(key)) {
241
+ this.seenToolCalls.add(key);
242
+ const statusLabel = tool.status === 'completed' ? ' (completed)' : '';
243
+ this.trajectory.event(`Tool: ${tool.tool}${statusLabel}`, 'tool_call');
244
+ }
245
+ }
246
+ // Detect and record errors
247
+ const errors = detectErrors(content);
248
+ for (const error of errors) {
249
+ if (!this.seenErrors.has(error.message)) {
250
+ this.seenErrors.add(error.message);
251
+ const prefix = error.type === 'warning' ? 'Warning' : 'Error';
252
+ this.trajectory.event(`${prefix}: ${error.message}`, 'error');
253
+ }
254
+ }
255
+ }
183
256
  /**
184
257
  * Build the full command with proper quoting
185
258
  * Args containing spaces need to be quoted
@@ -284,20 +357,24 @@ export class TmuxWrapper {
284
357
  // Ignore on older tmux versions lacking these key tables
285
358
  }
286
359
  }
287
- // Set environment variables
360
+ // Set environment variables including trail/trajectory vars
361
+ const projectPaths = getProjectPaths();
362
+ const trailEnvVars = getTrailEnvVars(projectPaths.projectId, this.config.name, projectPaths.dataDir);
288
363
  for (const [key, value] of Object.entries({
289
364
  ...this.config.env,
365
+ ...trailEnvVars,
290
366
  AGENT_RELAY_NAME: this.config.name,
291
367
  TERM: 'xterm-256color',
292
368
  })) {
293
- const escaped = value.replace(/"/g, '\\"');
369
+ // Use proper shell escaping to prevent command injection via env var values
370
+ const escaped = escapeForShell(value);
294
371
  execSync(`"${this.tmuxPath}" setenv -t ${this.sessionName} ${key} "${escaped}"`);
295
372
  }
296
373
  // Wait for shell to be ready (look for prompt)
297
374
  await this.waitForShellReady();
298
375
  // Send the command to run
299
376
  await this.sendKeysLiteral(fullCommand);
300
- await this.sleep(100);
377
+ await sleep(100);
301
378
  await this.sendKeys('Enter');
302
379
  }
303
380
  catch (err) {
@@ -308,6 +385,10 @@ export class TmuxWrapper {
308
385
  this.running = true;
309
386
  this.lastActivityTime = Date.now();
310
387
  this.activityState = 'active';
388
+ // Initialize trajectory tracking (auto-start if task provided)
389
+ this.initializeTrajectory();
390
+ // Initialize continuity and get/create agentId
391
+ this.initializeAgentId();
311
392
  // Inject instructions for the agent (after a delay to let CLI initialize)
312
393
  setTimeout(() => this.injectInstructions(), 3000);
313
394
  // Start background polling (silent - no stdout writes)
@@ -317,28 +398,126 @@ export class TmuxWrapper {
317
398
  this.attachToSession();
318
399
  }
319
400
  /**
320
- * Inject usage instructions for the agent
401
+ * Initialize trajectory tracking
402
+ * Auto-starts a trajectory if task is provided in config
403
+ */
404
+ async initializeTrajectory() {
405
+ if (!this.trajectory)
406
+ return;
407
+ // Auto-start trajectory if task is provided
408
+ if (this.config.task) {
409
+ const success = await this.trajectory.initialize(this.config.task);
410
+ if (success) {
411
+ this.logStderr(`Trajectory started for task: ${this.config.task}`);
412
+ }
413
+ }
414
+ else {
415
+ // Just initialize without starting a trajectory
416
+ await this.trajectory.initialize();
417
+ }
418
+ }
419
+ /**
420
+ * Initialize agent ID for continuity/resume functionality
421
+ */
422
+ async initializeAgentId() {
423
+ if (!this.continuity)
424
+ return;
425
+ try {
426
+ let ledger;
427
+ // If resuming from a previous agent ID, try to find that ledger
428
+ if (this.config.resumeAgentId) {
429
+ ledger = await this.continuity.findLedgerByAgentId(this.config.resumeAgentId);
430
+ if (ledger) {
431
+ this.logStderr(`Resuming agent ID: ${ledger.agentId} (from previous session)`);
432
+ }
433
+ else {
434
+ this.logStderr(`Resume agent ID ${this.config.resumeAgentId} not found, creating new`, true);
435
+ }
436
+ }
437
+ // If not resuming or resume ID not found, get or create ledger
438
+ if (!ledger) {
439
+ ledger = await this.continuity.getOrCreateLedger(this.config.name, this.cliType);
440
+ this.logStderr(`Agent ID: ${ledger.agentId} (use this to resume if agent dies)`);
441
+ }
442
+ this.agentId = ledger.agentId;
443
+ }
444
+ catch (err) {
445
+ this.logStderr(`Failed to initialize agent ID: ${err.message}`, true);
446
+ }
447
+ }
448
+ /**
449
+ * Get the current agent ID
450
+ */
451
+ getAgentId() {
452
+ return this.agentId;
453
+ }
454
+ /**
455
+ * Inject usage instructions for the agent including persistence protocol
321
456
  */
322
457
  async injectInstructions() {
323
458
  if (!this.running)
324
459
  return;
325
460
  // Use escaped prefix (\->relay:) in examples to prevent parser from treating them as real commands
326
461
  const escapedPrefix = '\\' + this.relayPrefix;
327
- const instructions = [
462
+ // Build instructions including relay and trail
463
+ const relayInstructions = [
328
464
  `[Agent Relay] You are "${this.config.name}" - connected for real-time messaging.`,
329
465
  `SEND: ${escapedPrefix}AgentName message`,
330
466
  `MULTI-LINE: ${escapedPrefix}AgentName <<<(newline)content(newline)>>> - ALWAYS end with >>> on its own line!`,
331
- `RECEIVE: Messages appear as "Relay message from X [id]: content" - use "agent-relay read <id>" for long messages`,
467
+ `PERSIST: Output [[SUMMARY]]{"currentTask":"...","context":"..."}[[/SUMMARY]] after major work.`,
468
+ `END: Output [[SESSION_END]]{"summary":"..."}[[/SESSION_END]] when session complete.`,
332
469
  ].join(' | ');
470
+ // Add trail instructions if available
471
+ const trailInstructions = getCompactTrailInstructions();
333
472
  try {
334
- await this.sendKeysLiteral(instructions);
335
- await this.sleep(50);
473
+ await this.sendKeysLiteral(relayInstructions);
474
+ await sleep(50);
336
475
  await this.sendKeys('Enter');
476
+ // Inject trail instructions
477
+ if (this.trajectory?.isTrailInstalledSync()) {
478
+ await sleep(100);
479
+ await this.sendKeysLiteral(trailInstructions);
480
+ await sleep(50);
481
+ await this.sendKeys('Enter');
482
+ }
483
+ // Inject continuity context from previous session
484
+ await this.injectContinuityContext();
337
485
  }
338
486
  catch {
339
487
  // Silent fail - instructions are nice-to-have
340
488
  }
341
489
  }
490
+ /**
491
+ * Inject continuity context from previous session
492
+ */
493
+ async injectContinuityContext() {
494
+ if (!this.continuity || !this.running)
495
+ return;
496
+ try {
497
+ const context = await this.continuity.getStartupContext(this.config.name);
498
+ if (context && context.formatted) {
499
+ // Inject a brief notification about loaded context
500
+ const notification = `[Continuity] Previous session context loaded. ${context.ledger ? `Task: ${context.ledger.currentTask?.slice(0, 50) || 'unknown'}` : ''}${context.handoff ? ` | Last handoff: ${context.handoff.createdAt.toISOString().split('T')[0]}` : ''}`;
501
+ await sleep(200);
502
+ await this.sendKeysLiteral(notification);
503
+ await sleep(50);
504
+ await this.sendKeys('Enter');
505
+ // Queue the full context for injection when agent is ready
506
+ this.messageQueue.push({
507
+ from: 'system',
508
+ body: context.formatted,
509
+ messageId: `continuity-startup-${Date.now()}`,
510
+ });
511
+ this.checkForInjectionOpportunity();
512
+ if (this.config.debug) {
513
+ this.logStderr(`[CONTINUITY] Loaded context for ${this.config.name}`);
514
+ }
515
+ }
516
+ }
517
+ catch (err) {
518
+ this.logStderr(`[CONTINUITY] Failed to load context: ${err.message}`, true);
519
+ }
520
+ }
342
521
  /**
343
522
  * Wait for tmux session to be ready
344
523
  */
@@ -372,14 +551,14 @@ export class TmuxWrapper {
372
551
  if (promptPatterns.test(lastLine)) {
373
552
  this.logStderr('Shell ready');
374
553
  // Extra delay to ensure shell is fully ready
375
- await this.sleep(200);
554
+ await sleep(200);
376
555
  return;
377
556
  }
378
557
  }
379
558
  catch {
380
559
  // Session might not be ready yet
381
560
  }
382
- await this.sleep(200);
561
+ await sleep(200);
383
562
  }
384
563
  // Fallback: proceed anyway after timeout
385
564
  this.logStderr('Shell ready timeout, proceeding anyway');
@@ -433,10 +612,10 @@ export class TmuxWrapper {
433
612
  `"${this.tmuxPath}" capture-pane -t ${this.sessionName} -p -J -S - 2>/dev/null`);
434
613
  // Always parse the FULL capture for ->relay commands
435
614
  // This handles terminal UIs that rewrite content in place
436
- const cleanContent = this.stripAnsi(stdout);
615
+ const cleanContent = stripAnsi(stdout);
437
616
  // Join continuation lines that TUIs split across multiple lines
438
617
  const joinedContent = this.joinContinuationLines(cleanContent);
439
- const { commands } = this.parser.parse(joinedContent);
618
+ const { commands, output: filteredOutput } = this.parser.parse(joinedContent);
440
619
  // Debug: log relay commands being parsed
441
620
  if (commands.length > 0 && this.config.debug) {
442
621
  for (const cmd of commands) {
@@ -449,6 +628,16 @@ export class TmuxWrapper {
449
628
  this.lastOutputTime = Date.now();
450
629
  this.markActivity();
451
630
  this.processedOutputLength = stdout.length;
631
+ // Stream new output to daemon for dashboard log viewing
632
+ // Use filtered output to exclude thinking blocks and relay commands
633
+ if (this.config.streamLogs && this.client.state === 'READY') {
634
+ // Send incremental filtered output since last log
635
+ const newContent = filteredOutput.substring(this.lastLoggedLength);
636
+ if (newContent.length > 0) {
637
+ this.client.sendLog(newContent);
638
+ this.lastLoggedLength = filteredOutput.length;
639
+ }
640
+ }
452
641
  }
453
642
  // Send any commands found (deduplication handles repeats)
454
643
  for (const cmd of commands) {
@@ -456,6 +645,10 @@ export class TmuxWrapper {
456
645
  }
457
646
  // Check for [[SUMMARY]] blocks and save to storage
458
647
  this.parseSummaryAndSave(cleanContent);
648
+ // Detect PDERO phase transitions from output content
649
+ this.detectAndTransitionPhase(cleanContent);
650
+ // Parse and handle continuity commands (->continuity:save, ->continuity:load, etc.)
651
+ await this.parseContinuityCommands(joinedContent);
459
652
  // Check for [[SESSION_END]] blocks to explicitly close session
460
653
  this.parseSessionEndAndClose(cleanContent);
461
654
  // Check for ->relay:spawn and ->relay:release commands (any agent can spawn)
@@ -471,13 +664,6 @@ export class TmuxWrapper {
471
664
  }
472
665
  }
473
666
  }
474
- /**
475
- * Strip ANSI escape codes
476
- */
477
- stripAnsi(str) {
478
- // eslint-disable-next-line no-control-regex
479
- return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
480
- }
481
667
  /**
482
668
  * Join continuation lines after ->relay commands.
483
669
  * Claude Code and other TUIs insert real newlines in output, causing
@@ -600,6 +786,8 @@ export class TmuxWrapper {
600
786
  this.queuedMessageHashes.delete(msgHash);
601
787
  const truncatedBody = cmd.body.substring(0, Math.min(RELAY_LOG_TRUNCATE_LENGTH, cmd.body.length));
602
788
  this.logStderr(`→ ${cmd.to}: ${truncatedBody}...`);
789
+ // Record in trajectory via trail
790
+ this.trajectory?.message('sent', this.config.name, cmd.to, cmd.body);
603
791
  }
604
792
  else if (this.client.state !== 'READY') {
605
793
  // Only log failure once per state change
@@ -647,7 +835,14 @@ export class TmuxWrapper {
647
835
  if (summaryHash === this.lastSummaryHash)
648
836
  return;
649
837
  this.lastSummaryHash = summaryHash;
650
- // Wait for storage to be ready before saving
838
+ // Save to continuity ledger for session recovery
839
+ // This ensures the ledger has actual data instead of placeholders
840
+ if (this.continuity) {
841
+ this.saveSummaryToLedger(summary).catch(err => {
842
+ this.logStderr(`Failed to save summary to ledger: ${err.message}`, true);
843
+ });
844
+ }
845
+ // Wait for storage to be ready before saving to project storage
651
846
  this.storageReady.then(ready => {
652
847
  if (!ready || !this.storage) {
653
848
  this.logStderr('Cannot save summary: storage not initialized');
@@ -669,6 +864,86 @@ export class TmuxWrapper {
669
864
  });
670
865
  });
671
866
  }
867
+ /**
868
+ * Save a parsed summary to the continuity ledger.
869
+ * Maps summary fields to ledger fields for session recovery.
870
+ */
871
+ async saveSummaryToLedger(summary) {
872
+ if (!this.continuity)
873
+ return;
874
+ const updates = {};
875
+ // Map summary fields to ledger fields
876
+ if (summary.currentTask) {
877
+ updates.currentTask = summary.currentTask;
878
+ }
879
+ if (summary.completedTasks && summary.completedTasks.length > 0) {
880
+ updates.completed = summary.completedTasks;
881
+ }
882
+ if (summary.context) {
883
+ // Store context in inProgress as "next steps" hint
884
+ updates.inProgress = [summary.context];
885
+ }
886
+ if (summary.files && summary.files.length > 0) {
887
+ updates.fileContext = summary.files.map((f) => ({ path: f }));
888
+ }
889
+ // Only save if we have meaningful updates
890
+ if (Object.keys(updates).length > 0) {
891
+ await this.continuity.saveLedger(this.config.name, updates);
892
+ this.logStderr('Saved summary to continuity ledger');
893
+ }
894
+ }
895
+ /**
896
+ * Parse ->continuity: commands from output and handle them.
897
+ * Supported commands:
898
+ * ->continuity:save <<<...>>> - Save session state to ledger
899
+ * ->continuity:load - Request context injection
900
+ * ->continuity:search "query" - Search past handoffs
901
+ * ->continuity:uncertain "..." - Mark item as uncertain
902
+ * ->continuity:handoff <<<...>>> - Create explicit handoff
903
+ */
904
+ async parseContinuityCommands(content) {
905
+ if (!this.continuity)
906
+ return;
907
+ if (!hasContinuityCommand(content))
908
+ return;
909
+ const command = parseContinuityCommand(content);
910
+ if (!command)
911
+ return;
912
+ // Create a hash for deduplication
913
+ // For commands with content (save, handoff, uncertain), use content hash
914
+ // For commands without content (load, search), allow each unique call
915
+ const hasContent = command.content || command.query || command.item;
916
+ const cmdHash = hasContent
917
+ ? `${command.type}:${command.content || command.query || command.item}`
918
+ : `${command.type}:${Date.now()}`; // Allow load/search to run each time
919
+ if (hasContent && this.processedContinuityCommands.has(cmdHash))
920
+ return;
921
+ this.processedContinuityCommands.add(cmdHash);
922
+ // Limit dedup set size
923
+ if (this.processedContinuityCommands.size > 100) {
924
+ const oldest = this.processedContinuityCommands.values().next().value;
925
+ if (oldest)
926
+ this.processedContinuityCommands.delete(oldest);
927
+ }
928
+ try {
929
+ if (this.config.debug) {
930
+ this.logStderr(`[CONTINUITY] Processing ${command.type} command`);
931
+ }
932
+ const response = await this.continuity.handleCommand(this.config.name, command);
933
+ // If there's a response (e.g., from load or search), inject it
934
+ if (response) {
935
+ this.messageQueue.push({
936
+ from: 'system',
937
+ body: response,
938
+ messageId: `continuity-${Date.now()}`,
939
+ });
940
+ this.checkForInjectionOpportunity();
941
+ }
942
+ }
943
+ catch (err) {
944
+ this.logStderr(`[CONTINUITY] Error: ${err.message}`, true);
945
+ }
946
+ }
672
947
  /**
673
948
  * Parse [[SESSION_END]] blocks from output and close session explicitly.
674
949
  * Agents output this to mark their work session as complete:
@@ -676,6 +951,8 @@ export class TmuxWrapper {
676
951
  * [[SESSION_END]]
677
952
  * {"summary": "Completed auth module", "completedTasks": ["login", "logout"]}
678
953
  * [[/SESSION_END]]
954
+ *
955
+ * Also stores the data for use in autoSave to populate handoff (fixes empty handoff issue).
679
956
  */
680
957
  parseSessionEndAndClose(content) {
681
958
  if (this.sessionEndProcessed)
@@ -683,6 +960,8 @@ export class TmuxWrapper {
683
960
  const sessionEnd = parseSessionEndFromOutput(content);
684
961
  if (!sessionEnd)
685
962
  return;
963
+ // Store SESSION_END data for use in autoSave (fixes empty handoff issue)
964
+ this.sessionEndData = sessionEnd;
686
965
  // Get session ID from client connection - if not available yet, don't set flag
687
966
  // so we can retry when sessionId becomes available
688
967
  const sessionId = this.client.currentSessionId;
@@ -707,64 +986,226 @@ export class TmuxWrapper {
707
986
  });
708
987
  });
709
988
  }
989
+ /**
990
+ * Execute spawn via API (if dashboardPort set) or callback
991
+ */
992
+ async executeSpawn(name, cli, task) {
993
+ if (this.config.dashboardPort) {
994
+ // Use dashboard API for spawning (works from any context, no terminal required)
995
+ try {
996
+ const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
997
+ method: 'POST',
998
+ headers: { 'Content-Type': 'application/json' },
999
+ body: JSON.stringify({ name, cli, task }),
1000
+ });
1001
+ const result = await response.json();
1002
+ if (result.success) {
1003
+ this.logStderr(`Spawned ${name} via API`);
1004
+ }
1005
+ else {
1006
+ this.logStderr(`Spawn failed: ${result.error}`, true);
1007
+ }
1008
+ }
1009
+ catch (err) {
1010
+ this.logStderr(`Spawn API call failed: ${err.message}`, true);
1011
+ }
1012
+ }
1013
+ else if (this.config.onSpawn) {
1014
+ // Fall back to callback
1015
+ try {
1016
+ await this.config.onSpawn(name, cli, task);
1017
+ }
1018
+ catch (err) {
1019
+ this.logStderr(`Spawn failed: ${err.message}`, true);
1020
+ }
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Execute release via API (if dashboardPort set) or callback
1025
+ */
1026
+ async executeRelease(name) {
1027
+ if (this.config.dashboardPort) {
1028
+ // Use dashboard API for release (works from any context, no terminal required)
1029
+ try {
1030
+ const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawned/${encodeURIComponent(name)}`, {
1031
+ method: 'DELETE',
1032
+ });
1033
+ const result = await response.json();
1034
+ if (result.success) {
1035
+ this.logStderr(`Released ${name} via API`);
1036
+ }
1037
+ else {
1038
+ this.logStderr(`Release failed: ${result.error}`, true);
1039
+ }
1040
+ }
1041
+ catch (err) {
1042
+ this.logStderr(`Release API call failed: ${err.message}`, true);
1043
+ }
1044
+ }
1045
+ else if (this.config.onRelease) {
1046
+ // Fall back to callback
1047
+ try {
1048
+ await this.config.onRelease(name);
1049
+ }
1050
+ catch (err) {
1051
+ this.logStderr(`Release failed: ${err.message}`, true);
1052
+ }
1053
+ }
1054
+ }
710
1055
  /**
711
1056
  * Parse ->relay:spawn and ->relay:release commands from output.
712
- * Format:
713
- * ->relay:spawn WorkerName cli "task description"
1057
+ * Supports two formats:
1058
+ * Single-line: ->relay:spawn WorkerName cli "task description"
1059
+ * Multi-line (fenced): ->relay:spawn WorkerName cli <<<
1060
+ * task description here
1061
+ * can span multiple lines>>>
714
1062
  * ->relay:release WorkerName
715
1063
  */
716
1064
  parseSpawnReleaseCommands(content) {
717
- // Only process if callbacks are configured
718
- if (!this.config.onSpawn && !this.config.onRelease)
1065
+ // Only process if we have API or callbacks configured
1066
+ const canSpawn = this.config.dashboardPort || this.config.onSpawn;
1067
+ const canRelease = this.config.dashboardPort || this.config.onRelease;
1068
+ if (!canSpawn && !canRelease)
719
1069
  return;
720
1070
  const lines = content.split('\n');
1071
+ // Pattern to strip common line prefixes (bullets, prompts, etc.)
1072
+ // Must include ● (U+25CF BLACK CIRCLE) used by Claude's TUI
1073
+ const linePrefixPattern = /^(?:[>$%#→➜›»●•◦‣⁃\-*⏺◆◇○□■│┃┆┇┊┋╎╏✦]\s*)+/;
721
1074
  for (const line of lines) {
722
- const trimmed = line.trim();
723
- // Match ->relay:spawn WorkerName cli "task"
724
- // Pattern: ->relay:spawn <name> <cli> "<task>" or ->relay:spawn <name> <cli> '<task>'
725
- // Allow trailing whitespace and optional bullet prefixes that TUIs might add
726
- const spawnMatch = trimmed.match(/^(?:[•\-*]\s*)?->relay:spawn\s+(\S+)\s+(\S+)\s+["'](.+?)["']\s*$/);
727
- if (spawnMatch && this.config.onSpawn) {
728
- const [, name, cli, task] = spawnMatch;
729
- const spawnKey = `${name}:${cli}:${task}`;
730
- // Dedup - only process each spawn once
1075
+ let trimmed = line.trim();
1076
+ // Strip common line prefixes (bullets, prompts) before checking for commands
1077
+ trimmed = trimmed.replace(linePrefixPattern, '');
1078
+ // If we're in fenced spawn mode, accumulate lines until we see >>>
1079
+ if (this.pendingFencedSpawn) {
1080
+ // Check for fence close (>>> at end of line or on its own line)
1081
+ const closeIdx = trimmed.indexOf('>>>');
1082
+ if (closeIdx !== -1) {
1083
+ // Add content before >>> to task
1084
+ const contentBeforeClose = trimmed.substring(0, closeIdx);
1085
+ if (contentBeforeClose) {
1086
+ this.pendingFencedSpawn.taskLines.push(contentBeforeClose);
1087
+ }
1088
+ // Execute the spawn with accumulated task
1089
+ const { name, cli, taskLines } = this.pendingFencedSpawn;
1090
+ const taskStr = taskLines.join('\n').trim();
1091
+ const spawnKey = `${name}:${cli}`;
1092
+ if (!this.processedSpawnCommands.has(spawnKey)) {
1093
+ this.processedSpawnCommands.add(spawnKey);
1094
+ if (taskStr) {
1095
+ this.logStderr(`Spawn command (fenced): ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
1096
+ }
1097
+ else {
1098
+ this.logStderr(`Spawn command (fenced): ${name} (${cli}) - no task`);
1099
+ }
1100
+ this.executeSpawn(name, cli, taskStr);
1101
+ }
1102
+ this.pendingFencedSpawn = null;
1103
+ }
1104
+ else {
1105
+ // Accumulate line as part of task
1106
+ this.pendingFencedSpawn.taskLines.push(line);
1107
+ }
1108
+ continue;
1109
+ }
1110
+ // Check for fenced spawn start: ->relay:spawn Name [cli] <<< (CLI optional, defaults to 'claude')
1111
+ // Prefixes are stripped above, so we just look for the command at start of line
1112
+ const fencedSpawnMatch = trimmed.match(/^->relay:spawn\s+(\S+)(?:\s+(\S+))?\s+<<<(.*)$/);
1113
+ if (fencedSpawnMatch && canSpawn) {
1114
+ const [, name, cliOrUndefined, inlineContent] = fencedSpawnMatch;
1115
+ const cli = cliOrUndefined || 'claude';
1116
+ // Validate name
1117
+ if (name.length < 2) {
1118
+ this.logStderr(`Fenced spawn has invalid name, skipping: name=${name}`);
1119
+ continue;
1120
+ }
1121
+ // Check if fence closes on same line (e.g., ->relay:spawn Worker cli <<<task>>>)
1122
+ const inlineCloseIdx = inlineContent.indexOf('>>>');
1123
+ if (inlineCloseIdx !== -1) {
1124
+ // Single line fenced: extract task between <<< and >>>
1125
+ const taskStr = inlineContent.substring(0, inlineCloseIdx).trim();
1126
+ const spawnKey = `${name}:${cli}`;
1127
+ if (!this.processedSpawnCommands.has(spawnKey)) {
1128
+ this.processedSpawnCommands.add(spawnKey);
1129
+ if (taskStr) {
1130
+ this.logStderr(`Spawn command: ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
1131
+ }
1132
+ else {
1133
+ this.logStderr(`Spawn command: ${name} (${cli}) - no task`);
1134
+ }
1135
+ this.executeSpawn(name, cli, taskStr);
1136
+ }
1137
+ }
1138
+ else {
1139
+ // Start multi-line fenced mode
1140
+ this.pendingFencedSpawn = {
1141
+ name,
1142
+ cli,
1143
+ taskLines: inlineContent.trim() ? [inlineContent.trim()] : [],
1144
+ };
1145
+ this.logStderr(`Starting fenced spawn capture: ${name} (${cli})`);
1146
+ }
1147
+ continue;
1148
+ }
1149
+ // Match single-line spawn: ->relay:spawn WorkerName [cli] ["task"]
1150
+ // CLI is optional - defaults to 'claude'. Task is also optional.
1151
+ // Prefixes are stripped above, so we just look for the command at start of line
1152
+ const spawnMatch = trimmed.match(/^->relay:spawn\s+(\S+)(?:\s+(\S+))?(?:\s+["'](.+?)["'])?\s*$/);
1153
+ if (spawnMatch && canSpawn) {
1154
+ const [, name, cliOrUndefined, task] = spawnMatch;
1155
+ const cli = cliOrUndefined || 'claude';
1156
+ // Validate the parsed values
1157
+ if (cli === '<<<' || cli === '>>>' || name === '<<<' || name === '>>>') {
1158
+ this.logStderr(`Invalid spawn command (fence markers), skipping: name=${name}, cli=${cli}`);
1159
+ continue;
1160
+ }
1161
+ if (name.length < 2) {
1162
+ this.logStderr(`Spawn command has suspiciously short name, skipping: name=${name}`);
1163
+ continue;
1164
+ }
1165
+ const taskStr = task || '';
1166
+ const spawnKey = `${name}:${cli}`;
731
1167
  if (!this.processedSpawnCommands.has(spawnKey)) {
732
1168
  this.processedSpawnCommands.add(spawnKey);
733
- this.logStderr(`Spawn command: ${name} (${cli}) - "${task.substring(0, 50)}..."`);
734
- this.config.onSpawn(name, cli, task).catch(err => {
735
- this.logStderr(`Spawn failed: ${err.message}`, true);
736
- });
1169
+ if (taskStr) {
1170
+ this.logStderr(`Spawn command: ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
1171
+ }
1172
+ else {
1173
+ this.logStderr(`Spawn command: ${name} (${cli}) - no task`);
1174
+ }
1175
+ this.executeSpawn(name, cli, taskStr);
737
1176
  }
738
1177
  continue;
739
1178
  }
740
1179
  // Match ->relay:release WorkerName
741
- // Allow trailing whitespace and optional bullet prefixes
742
- const releaseMatch = trimmed.match(/^(?:[•\-*]\s*)?->relay:release\s+(\S+)\s*$/);
743
- if (releaseMatch && this.config.onRelease) {
1180
+ // Prefixes are stripped above, so we just look for the command at start of line
1181
+ const releaseMatch = trimmed.match(/^->relay:release\s+(\S+)\s*$/);
1182
+ if (releaseMatch && canRelease) {
744
1183
  const [, name] = releaseMatch;
745
- // Dedup - only process each release once
746
1184
  if (!this.processedReleaseCommands.has(name)) {
747
1185
  this.processedReleaseCommands.add(name);
748
1186
  this.logStderr(`Release command: ${name}`);
749
- this.config.onRelease(name).catch(err => {
750
- this.logStderr(`Release failed: ${err.message}`, true);
751
- });
1187
+ this.executeRelease(name);
752
1188
  }
753
1189
  }
754
1190
  }
755
1191
  }
756
1192
  /**
757
1193
  * Handle incoming message from relay
1194
+ * @param originalTo - The original 'to' field from sender. '*' indicates this was a broadcast message.
1195
+ * Agents should reply to originalTo to maintain channel routing (e.g., respond to #general, not DM).
758
1196
  */
759
- handleIncomingMessage(from, payload, messageId, meta) {
1197
+ handleIncomingMessage(from, payload, messageId, meta, originalTo) {
760
1198
  if (this.hasSeenIncoming(messageId)) {
761
1199
  this.logStderr(`← ${from}: duplicate delivery (${messageId.substring(0, 8)})`);
762
1200
  return;
763
1201
  }
764
1202
  const truncatedBody = payload.body.substring(0, Math.min(DEBUG_LOG_TRUNCATE_LENGTH, payload.body.length));
765
- this.logStderr(`← ${from}: ${truncatedBody}...`);
766
- // Queue for injection
767
- this.messageQueue.push({ from, body: payload.body, messageId, thread: payload.thread, importance: meta?.importance });
1203
+ const channelInfo = originalTo === '*' ? ' [broadcast]' : '';
1204
+ this.logStderr(`← ${from}${channelInfo}: ${truncatedBody}...`);
1205
+ // Record in trajectory via trail
1206
+ this.trajectory?.message('received', from, this.config.name, payload.body);
1207
+ // Queue for injection - include originalTo so we can inform the agent how to route responses
1208
+ this.messageQueue.push({ from, body: payload.body, messageId, thread: payload.thread, importance: meta?.importance, data: payload.data, originalTo });
768
1209
  // Write to inbox if enabled
769
1210
  if (this.inbox) {
770
1211
  this.inbox.addMessage(from, payload.body);
@@ -792,7 +1233,8 @@ export class TmuxWrapper {
792
1233
  this.injectNextMessage();
793
1234
  }
794
1235
  /**
795
- * Inject message via tmux send-keys
1236
+ * Inject message via tmux send-keys.
1237
+ * Uses shared injection logic with tmux-specific callbacks.
796
1238
  */
797
1239
  async injectNextMessage() {
798
1240
  const msg = this.messageQueue.shift();
@@ -801,32 +1243,21 @@ export class TmuxWrapper {
801
1243
  this.isInjecting = true;
802
1244
  this.logStderr(`Injecting message from ${msg.from} (cli: ${this.cliType})`);
803
1245
  try {
804
- let sanitizedBody = msg.body.replace(/[\r\n]+/g, ' ').trim();
805
- // Gemini interprets certain keywords (While, For, If, etc.) as shell commands
806
- // Wrap in backticks to prevent shell keyword interpretation
807
- if (this.cliType === 'gemini') {
808
- sanitizedBody = `\`${sanitizedBody.replace(/`/g, "'")}\``;
809
- }
810
- // Short message ID for display (first 8 chars)
811
1246
  const shortId = msg.messageId.substring(0, 8);
812
- // Remove message truncation to allow full messages to pass through
813
- const wasTruncated = false;
814
- // Always include message ID; add lookup hint if truncated
815
- const idTag = `[${shortId}]`;
816
- const truncationHint = wasTruncated
817
- ? ` [TRUNCATED - run "agent-relay read ${msg.messageId}"]`
818
- : '';
819
1247
  // Wait for input to be clear before injecting
1248
+ // If input is not clear (human typing), re-queue and try later - never clear forcefully!
1249
+ // Fix for agent-relay-j9z: forceful clearing destroys human input in progress
820
1250
  const waitTimeoutMs = this.config.inputWaitTimeoutMs ?? 5000;
821
1251
  const waitPollMs = this.config.inputWaitPollMs ?? 200;
822
1252
  const inputClear = await this.waitForClearInput(waitTimeoutMs, waitPollMs);
823
1253
  if (!inputClear) {
824
- // Input still has text after timeout - clear it forcefully
825
- this.logStderr('Input not clear after waiting, clearing forcefully');
826
- await this.sendKeys('Escape');
827
- await this.sleep(30);
828
- await this.sendKeys('C-u');
829
- await this.sleep(30);
1254
+ // Input still has text after timeout - DON'T clear forcefully, re-queue instead
1255
+ // This preserves any human input in progress
1256
+ this.logStderr('Input not clear after waiting, re-queuing injection to preserve human input');
1257
+ this.messageQueue.unshift(msg);
1258
+ this.isInjecting = false;
1259
+ setTimeout(() => this.checkForInjectionOpportunity(), this.config.injectRetryMs ?? 1000);
1260
+ return;
830
1261
  }
831
1262
  // Ensure pane output is stable to avoid interleaving with active generation
832
1263
  const stablePane = await this.waitForStablePane(this.config.outputStabilityTimeoutMs ?? 2000, this.config.outputStabilityPollMs ?? 200);
@@ -841,8 +1272,8 @@ export class TmuxWrapper {
841
1272
  // If at shell prompt, skip injection to avoid shell command execution
842
1273
  if (this.cliType === 'gemini') {
843
1274
  const lastLine = await this.getLastLine();
844
- const cleanLine = this.stripAnsi(lastLine).trim();
845
- if (/^\$\s*$/.test(cleanLine) || /^\s*\$\s*$/.test(cleanLine)) {
1275
+ const cleanLine = stripAnsi(lastLine).trim();
1276
+ if (CLI_QUIRKS.isShellPrompt(cleanLine)) {
846
1277
  this.logStderr('Gemini at shell prompt, skipping injection to avoid shell execution');
847
1278
  // Re-queue the message for later
848
1279
  this.messageQueue.unshift(msg);
@@ -851,20 +1282,51 @@ export class TmuxWrapper {
851
1282
  return;
852
1283
  }
853
1284
  }
854
- // Standard injection for all CLIs including Gemini
855
- // Format: Relay message from Sender [abc12345] [thread:xxx] [!]: content
856
- // Thread/importance hints are compact and optional to not break TUIs
857
- const threadHint = msg.thread ? ` [thread:${msg.thread}]` : '';
858
- // Importance indicator: [!!] for high (>75), [!] for medium (>50), none for low/default
859
- const importanceHint = msg.importance !== undefined && msg.importance > 75 ? ' [!!]' :
860
- msg.importance !== undefined && msg.importance > 50 ? ' [!]' : '';
861
- const injection = `Relay message from ${msg.from} ${idTag}${threadHint}${importanceHint}: ${sanitizedBody}${truncationHint}`;
862
- // Paste message as a bracketed paste to avoid interleaving with active output
863
- await this.pasteLiteral(injection);
864
- await this.sleep(30);
865
- // Submit
866
- await this.sendKeys('Enter');
867
- this.logStderr(`Injection complete`);
1285
+ // Build injection string using shared utility
1286
+ let injection = buildInjectionString(msg);
1287
+ // Gemini-specific: wrap body in backticks to prevent shell keyword interpretation
1288
+ if (this.cliType === 'gemini') {
1289
+ const colonIdx = injection.indexOf(': ');
1290
+ if (colonIdx > 0) {
1291
+ const prefix = injection.substring(0, colonIdx + 2);
1292
+ const body = injection.substring(colonIdx + 2);
1293
+ injection = prefix + CLI_QUIRKS.wrapForGemini(body);
1294
+ }
1295
+ }
1296
+ // Create callbacks for shared injection logic
1297
+ const callbacks = {
1298
+ getOutput: async () => {
1299
+ try {
1300
+ const { stdout } = await execAsync(`"${this.tmuxPath}" capture-pane -t ${this.sessionName} -p -S - 2>/dev/null`);
1301
+ return stdout;
1302
+ }
1303
+ catch {
1304
+ return '';
1305
+ }
1306
+ },
1307
+ performInjection: async (inj) => {
1308
+ await this.pasteLiteral(inj);
1309
+ await sleep(INJECTION_CONSTANTS.ENTER_DELAY_MS);
1310
+ await this.sendKeys('Enter');
1311
+ },
1312
+ log: (message) => this.logStderr(message),
1313
+ logError: (message) => this.logStderr(message, true),
1314
+ getMetrics: () => this.injectionMetrics,
1315
+ };
1316
+ // Inject with retry and verification using shared logic
1317
+ const result = await sharedInjectWithRetry(injection, shortId, msg.from, callbacks);
1318
+ if (result.success) {
1319
+ this.logStderr(`Injection complete (attempt ${result.attempts})`);
1320
+ }
1321
+ else {
1322
+ // All retries failed - log and optionally fall back to inbox
1323
+ this.logStderr(`Message delivery failed after ${result.attempts} attempts: from=${msg.from} id=${shortId}`, true);
1324
+ // Write to inbox as fallback if enabled
1325
+ if (this.inbox) {
1326
+ this.inbox.addMessage(msg.from, msg.body);
1327
+ this.logStderr('Wrote message to inbox as fallback');
1328
+ }
1329
+ }
868
1330
  }
869
1331
  catch (err) {
870
1332
  this.logStderr(`Injection failed: ${err.message}`, true);
@@ -872,7 +1334,7 @@ export class TmuxWrapper {
872
1334
  finally {
873
1335
  this.isInjecting = false;
874
1336
  if (this.messageQueue.length > 0) {
875
- setTimeout(() => this.checkForInjectionOpportunity(), 1000);
1337
+ setTimeout(() => this.checkForInjectionOpportunity(), INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS);
876
1338
  }
877
1339
  }
878
1340
  }
@@ -927,7 +1389,7 @@ export class TmuxWrapper {
927
1389
  // Set tmux buffer then paste
928
1390
  // Skip bracketed paste (-p) for CLIs that don't handle it properly (droid, other)
929
1391
  await execAsync(`"${this.tmuxPath}" set-buffer -- "${escaped}"`);
930
- const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini';
1392
+ const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini' || this.cliType === 'opencode';
931
1393
  if (useBracketedPaste) {
932
1394
  await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName} -p`);
933
1395
  }
@@ -935,9 +1397,6 @@ export class TmuxWrapper {
935
1397
  await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName}`);
936
1398
  }
937
1399
  }
938
- sleep(ms) {
939
- return new Promise(r => setTimeout(r, ms));
940
- }
941
1400
  /**
942
1401
  * Reset session-specific state for wrapper reuse.
943
1402
  * Call this when starting a new session with the same wrapper instance.
@@ -946,18 +1405,13 @@ export class TmuxWrapper {
946
1405
  this.sessionEndProcessed = false;
947
1406
  this.lastSummaryHash = '';
948
1407
  this.lastSummaryRawContent = '';
1408
+ this.sessionEndData = undefined;
949
1409
  }
950
1410
  /**
951
1411
  * Get the prompt pattern for the current CLI type.
952
1412
  */
953
1413
  getPromptPattern() {
954
- const promptPatterns = {
955
- claude: /^[>›»]\s*$/, // Claude: "> " or similar
956
- gemini: /^[>›»]\s*$/, // Gemini: "> "
957
- codex: /^[>›»]\s*$/, // Codex: "> "
958
- other: /^[>$%#➜›»]\s*$/, // Shell or other: "$ ", "> ", etc.
959
- };
960
- return promptPatterns[this.cliType] || promptPatterns.other;
1414
+ return CLI_QUIRKS.getPromptPattern(this.cliType);
961
1415
  }
962
1416
  /**
963
1417
  * Capture the last non-empty line from the tmux pane.
@@ -976,7 +1430,7 @@ export class TmuxWrapper {
976
1430
  * Detect if the provided line contains visible user input (beyond the prompt).
977
1431
  */
978
1432
  hasVisibleInput(line) {
979
- const cleanLine = this.stripAnsi(line).trimEnd();
1433
+ const cleanLine = stripAnsi(line).trimEnd();
980
1434
  if (cleanLine === '')
981
1435
  return false;
982
1436
  return !this.getPromptPattern().test(cleanLine);
@@ -988,7 +1442,7 @@ export class TmuxWrapper {
988
1442
  async isInputClear(lastLine) {
989
1443
  try {
990
1444
  const lineToCheck = lastLine ?? await this.getLastLine();
991
- const cleanLine = this.stripAnsi(lineToCheck).trimEnd();
1445
+ const cleanLine = stripAnsi(lineToCheck).trimEnd();
992
1446
  const isClear = this.getPromptPattern().test(cleanLine);
993
1447
  if (this.config.debug) {
994
1448
  const truncatedLine = cleanLine.substring(0, Math.min(DEBUG_LOG_TRUNCATE_LENGTH, cleanLine.length));
@@ -1048,7 +1502,7 @@ export class TmuxWrapper {
1048
1502
  stableCursorCount = 0;
1049
1503
  lastCursorX = cursorX;
1050
1504
  }
1051
- await this.sleep(pollIntervalMs);
1505
+ await sleep(pollIntervalMs);
1052
1506
  }
1053
1507
  this.logStderr(`waitForClearInput: timed out after ${maxWaitMs}ms`);
1054
1508
  return false;
@@ -1077,7 +1531,7 @@ export class TmuxWrapper {
1077
1531
  return false;
1078
1532
  let stableCount = 0;
1079
1533
  while (Date.now() - start < maxWaitMs) {
1080
- await this.sleep(pollIntervalMs);
1534
+ await sleep(pollIntervalMs);
1081
1535
  const sig = await this.capturePaneSignature();
1082
1536
  if (!sig)
1083
1537
  continue;
@@ -1103,6 +1557,13 @@ export class TmuxWrapper {
1103
1557
  return;
1104
1558
  this.running = false;
1105
1559
  this.activityState = 'disconnected';
1560
+ // Auto-save continuity state before shutdown (fire and forget)
1561
+ // Pass sessionEndData to populate handoff (fixes empty handoff issue)
1562
+ if (this.continuity) {
1563
+ this.continuity.autoSave(this.config.name, 'session_end', this.sessionEndData).catch((err) => {
1564
+ this.logStderr(`[CONTINUITY] Auto-save failed: ${err.message}`, true);
1565
+ });
1566
+ }
1106
1567
  // Reset session state for potential reuse
1107
1568
  this.resetSessionState();
1108
1569
  // Stop polling