agent-relay 2.0.29 → 2.0.32

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 (894) hide show
  1. package/README.md +19 -0
  2. package/dist/index.cjs +85691 -0
  3. package/dist/src/bridge/index.d.ts.map +1 -0
  4. package/dist/src/bridge/index.js.map +1 -0
  5. package/dist/src/cli/commands/doctor.d.ts +2 -0
  6. package/dist/src/cli/commands/doctor.d.ts.map +1 -0
  7. package/dist/src/cli/commands/doctor.js +451 -0
  8. package/dist/src/cli/commands/doctor.js.map +1 -0
  9. package/dist/src/cli/index.d.ts.map +1 -0
  10. package/dist/src/cli/index.js +29 -1
  11. package/dist/src/cli/index.js.map +1 -0
  12. package/dist/src/config/relay-config.d.ts.map +1 -0
  13. package/dist/src/config/relay-config.js.map +1 -0
  14. package/dist/src/continuity/index.d.ts.map +1 -0
  15. package/dist/src/continuity/index.js.map +1 -0
  16. package/dist/src/daemon/index.d.ts.map +1 -0
  17. package/dist/src/daemon/index.js.map +1 -0
  18. package/dist/src/health-worker-manager.d.ts.map +1 -0
  19. package/dist/src/health-worker-manager.js.map +1 -0
  20. package/dist/src/health-worker.d.ts.map +1 -0
  21. package/dist/src/health-worker.js.map +1 -0
  22. package/dist/src/hooks/index.d.ts.map +1 -0
  23. package/dist/src/hooks/index.js.map +1 -0
  24. package/dist/src/index.d.ts.map +1 -0
  25. package/dist/src/index.js.map +1 -0
  26. package/dist/src/memory/index.d.ts.map +1 -0
  27. package/dist/src/memory/index.js.map +1 -0
  28. package/dist/src/policy/index.d.ts.map +1 -0
  29. package/dist/src/policy/index.js.map +1 -0
  30. package/dist/src/protocol/index.d.ts.map +1 -0
  31. package/dist/src/protocol/index.js.map +1 -0
  32. package/dist/src/resiliency/index.d.ts.map +1 -0
  33. package/dist/src/resiliency/index.js.map +1 -0
  34. package/dist/src/shared/cli-auth-config.d.ts.map +1 -0
  35. package/dist/src/shared/cli-auth-config.js.map +1 -0
  36. package/dist/src/state/index.d.ts.map +1 -0
  37. package/dist/src/state/index.js.map +1 -0
  38. package/dist/src/storage/index.d.ts.map +1 -0
  39. package/dist/src/storage/index.js.map +1 -0
  40. package/dist/src/trajectory/index.d.ts.map +1 -0
  41. package/dist/src/trajectory/index.js.map +1 -0
  42. package/dist/src/utils/index.d.ts.map +1 -0
  43. package/dist/src/utils/index.js.map +1 -0
  44. package/dist/src/wrapper/index.d.ts.map +1 -0
  45. package/dist/src/wrapper/index.js.map +1 -0
  46. package/package.json +83 -20
  47. package/packages/api-types/dist/index.d.ts.map +1 -0
  48. package/packages/api-types/dist/index.js.map +1 -0
  49. package/packages/api-types/dist/schemas/agent.d.ts.map +1 -0
  50. package/packages/api-types/dist/schemas/agent.js.map +1 -0
  51. package/packages/api-types/dist/schemas/api.d.ts.map +1 -0
  52. package/packages/api-types/dist/schemas/api.js.map +1 -0
  53. package/packages/api-types/dist/schemas/decision.d.ts.map +1 -0
  54. package/packages/api-types/dist/schemas/decision.js.map +1 -0
  55. package/packages/api-types/dist/schemas/fleet.d.ts.map +1 -0
  56. package/packages/api-types/dist/schemas/fleet.js.map +1 -0
  57. package/packages/api-types/dist/schemas/history.d.ts.map +1 -0
  58. package/packages/api-types/dist/schemas/history.js.map +1 -0
  59. package/packages/api-types/dist/schemas/index.d.ts.map +1 -0
  60. package/packages/api-types/dist/schemas/index.js.map +1 -0
  61. package/packages/api-types/dist/schemas/message.d.ts.map +1 -0
  62. package/packages/api-types/dist/schemas/message.js.map +1 -0
  63. package/packages/api-types/dist/schemas/session.d.ts.map +1 -0
  64. package/packages/api-types/dist/schemas/session.js.map +1 -0
  65. package/packages/api-types/dist/schemas/task.d.ts.map +1 -0
  66. package/packages/api-types/dist/schemas/task.js.map +1 -0
  67. package/packages/api-types/package.json +1 -1
  68. package/packages/api-types/src/index.ts +22 -0
  69. package/packages/api-types/src/schemas/agent.test.ts +164 -0
  70. package/packages/api-types/src/schemas/agent.ts +110 -0
  71. package/packages/api-types/src/schemas/api.test.ts +372 -0
  72. package/packages/api-types/src/schemas/api.ts +194 -0
  73. package/packages/api-types/src/schemas/decision.test.ts +324 -0
  74. package/packages/api-types/src/schemas/decision.ts +136 -0
  75. package/packages/api-types/src/schemas/fleet.test.ts +212 -0
  76. package/packages/api-types/src/schemas/fleet.ts +83 -0
  77. package/packages/api-types/src/schemas/history.test.ts +242 -0
  78. package/packages/api-types/src/schemas/history.ts +84 -0
  79. package/packages/api-types/src/schemas/index.ts +148 -0
  80. package/packages/api-types/src/schemas/message.test.ts +192 -0
  81. package/packages/api-types/src/schemas/message.ts +98 -0
  82. package/packages/api-types/src/schemas/session.test.ts +104 -0
  83. package/packages/api-types/src/schemas/session.ts +40 -0
  84. package/packages/api-types/src/schemas/task.test.ts +192 -0
  85. package/packages/api-types/src/schemas/task.ts +78 -0
  86. package/packages/api-types/tsconfig.json +19 -0
  87. package/packages/api-types/vitest.config.ts +9 -0
  88. package/packages/benchmark/README.md +200 -0
  89. package/packages/benchmark/datasets/coding-tasks.yaml +127 -0
  90. package/packages/benchmark/datasets/coordination-tasks.yaml +122 -0
  91. package/packages/benchmark/dist/benchmark.d.ts +47 -0
  92. package/packages/benchmark/dist/benchmark.d.ts.map +1 -0
  93. package/packages/benchmark/dist/benchmark.js +224 -0
  94. package/packages/benchmark/dist/benchmark.js.map +1 -0
  95. package/packages/benchmark/dist/cli.d.ts +8 -0
  96. package/packages/benchmark/dist/cli.d.ts.map +1 -0
  97. package/packages/benchmark/dist/cli.js +185 -0
  98. package/packages/benchmark/dist/cli.js.map +1 -0
  99. package/packages/benchmark/dist/harbor.d.ts +53 -0
  100. package/packages/benchmark/dist/harbor.d.ts.map +1 -0
  101. package/packages/benchmark/dist/harbor.js +127 -0
  102. package/packages/benchmark/dist/harbor.js.map +1 -0
  103. package/packages/benchmark/dist/index.d.ts +48 -0
  104. package/packages/benchmark/dist/index.d.ts.map +1 -0
  105. package/packages/benchmark/dist/index.js +50 -0
  106. package/packages/benchmark/dist/index.js.map +1 -0
  107. package/packages/benchmark/dist/runners/base.d.ts +63 -0
  108. package/packages/benchmark/dist/runners/base.d.ts.map +1 -0
  109. package/packages/benchmark/dist/runners/base.js +155 -0
  110. package/packages/benchmark/dist/runners/base.js.map +1 -0
  111. package/packages/benchmark/dist/runners/index.d.ts +10 -0
  112. package/packages/benchmark/dist/runners/index.d.ts.map +1 -0
  113. package/packages/benchmark/dist/runners/index.js +10 -0
  114. package/packages/benchmark/dist/runners/index.js.map +1 -0
  115. package/packages/benchmark/dist/runners/single.d.ts +19 -0
  116. package/packages/benchmark/dist/runners/single.d.ts.map +1 -0
  117. package/packages/benchmark/dist/runners/single.js +111 -0
  118. package/packages/benchmark/dist/runners/single.js.map +1 -0
  119. package/packages/benchmark/dist/runners/subagent.d.ts +32 -0
  120. package/packages/benchmark/dist/runners/subagent.d.ts.map +1 -0
  121. package/packages/benchmark/dist/runners/subagent.js +212 -0
  122. package/packages/benchmark/dist/runners/subagent.js.map +1 -0
  123. package/packages/benchmark/dist/runners/swarm.d.ts +36 -0
  124. package/packages/benchmark/dist/runners/swarm.d.ts.map +1 -0
  125. package/packages/benchmark/dist/runners/swarm.js +273 -0
  126. package/packages/benchmark/dist/runners/swarm.js.map +1 -0
  127. package/packages/benchmark/dist/types.d.ts +178 -0
  128. package/packages/benchmark/dist/types.d.ts.map +1 -0
  129. package/packages/benchmark/dist/types.js +16 -0
  130. package/packages/benchmark/dist/types.js.map +1 -0
  131. package/packages/benchmark/package.json +80 -0
  132. package/packages/benchmark/src/benchmark.ts +298 -0
  133. package/packages/benchmark/src/cli.ts +240 -0
  134. package/packages/benchmark/src/harbor.ts +170 -0
  135. package/packages/benchmark/src/index.ts +73 -0
  136. package/packages/benchmark/src/runners/base.ts +204 -0
  137. package/packages/benchmark/src/runners/index.ts +10 -0
  138. package/packages/benchmark/src/runners/single.ts +121 -0
  139. package/packages/benchmark/src/runners/subagent.ts +240 -0
  140. package/packages/benchmark/src/runners/swarm.ts +326 -0
  141. package/packages/benchmark/src/types.ts +205 -0
  142. package/packages/benchmark/tsconfig.json +20 -0
  143. package/packages/bridge/dist/index.d.ts.map +1 -0
  144. package/packages/bridge/dist/index.js.map +1 -0
  145. package/packages/bridge/dist/multi-project-client.d.ts.map +1 -0
  146. package/packages/bridge/dist/multi-project-client.js.map +1 -0
  147. package/packages/bridge/dist/shadow-cli.d.ts.map +1 -0
  148. package/packages/bridge/dist/shadow-cli.js.map +1 -0
  149. package/packages/bridge/dist/spawner.d.ts.map +1 -0
  150. package/packages/bridge/dist/spawner.js +10 -2
  151. package/packages/bridge/dist/spawner.js.map +1 -0
  152. package/packages/bridge/dist/types.d.ts.map +1 -0
  153. package/packages/bridge/dist/types.js.map +1 -0
  154. package/packages/bridge/dist/utils.d.ts.map +1 -0
  155. package/packages/bridge/dist/utils.js.map +1 -0
  156. package/packages/bridge/package.json +8 -8
  157. package/packages/bridge/src/index.ts +25 -0
  158. package/packages/bridge/src/multi-project-client.test.ts +340 -0
  159. package/packages/bridge/src/multi-project-client.ts +469 -0
  160. package/packages/bridge/src/shadow-cli.ts +95 -0
  161. package/packages/bridge/src/spawner-mcp.test.ts +505 -0
  162. package/packages/bridge/src/spawner.ts +1724 -0
  163. package/packages/bridge/src/types.ts +145 -0
  164. package/packages/bridge/src/utils.test.ts +98 -0
  165. package/packages/bridge/src/utils.ts +67 -0
  166. package/packages/bridge/tsconfig.json +29 -0
  167. package/packages/bridge/vitest.config.ts +9 -0
  168. package/packages/cli-tester/dist/index.d.ts.map +1 -0
  169. package/packages/cli-tester/dist/index.js.map +1 -0
  170. package/packages/cli-tester/dist/utils/credential-check.d.ts.map +1 -0
  171. package/packages/cli-tester/dist/utils/credential-check.js.map +1 -0
  172. package/packages/cli-tester/dist/utils/socket-client.d.ts.map +1 -0
  173. package/packages/cli-tester/dist/utils/socket-client.js.map +1 -0
  174. package/packages/cli-tester/docker/Dockerfile +61 -0
  175. package/packages/cli-tester/docker/docker-compose.yml +71 -0
  176. package/packages/cli-tester/package.json +1 -1
  177. package/packages/cli-tester/src/index.ts +40 -0
  178. package/packages/cli-tester/src/utils/credential-check.ts +284 -0
  179. package/packages/cli-tester/src/utils/socket-client.ts +211 -0
  180. package/packages/cli-tester/tests/credential-check.test.ts +56 -0
  181. package/packages/cli-tester/tsconfig.json +11 -0
  182. package/packages/config/dist/agent-config.d.ts.map +1 -0
  183. package/packages/config/dist/agent-config.js.map +1 -0
  184. package/packages/config/dist/bridge-config.d.ts.map +1 -0
  185. package/packages/config/dist/bridge-config.js.map +1 -0
  186. package/packages/config/dist/bridge-utils.d.ts.map +1 -0
  187. package/packages/config/dist/bridge-utils.js.map +1 -0
  188. package/packages/config/dist/cli-auth-config.d.ts.map +1 -0
  189. package/packages/config/dist/cli-auth-config.js.map +1 -0
  190. package/packages/config/dist/cloud-config.d.ts.map +1 -0
  191. package/packages/config/dist/cloud-config.js.map +1 -0
  192. package/packages/config/dist/index.d.ts.map +1 -0
  193. package/packages/config/dist/index.js.map +1 -0
  194. package/packages/config/dist/project-namespace.d.ts.map +1 -0
  195. package/packages/config/dist/project-namespace.js.map +1 -0
  196. package/packages/config/dist/relay-config.d.ts.map +1 -0
  197. package/packages/config/dist/relay-config.js.map +1 -0
  198. package/packages/config/dist/relay-file-writer.d.ts.map +1 -0
  199. package/packages/config/dist/relay-file-writer.js.map +1 -0
  200. package/packages/config/dist/schemas.d.ts.map +1 -0
  201. package/packages/config/dist/schemas.js.map +1 -0
  202. package/packages/config/dist/shadow-config.d.ts.map +1 -0
  203. package/packages/config/dist/shadow-config.js.map +1 -0
  204. package/packages/config/dist/teams-config.d.ts.map +1 -0
  205. package/packages/config/dist/teams-config.js.map +1 -0
  206. package/packages/config/dist/trajectory-config.d.ts.map +1 -0
  207. package/packages/config/dist/trajectory-config.js.map +1 -0
  208. package/packages/config/package.json +2 -2
  209. package/packages/config/src/agent-config.test.ts +245 -0
  210. package/packages/config/src/agent-config.ts +160 -0
  211. package/packages/config/src/bridge-config.test.ts +132 -0
  212. package/packages/config/src/bridge-config.ts +189 -0
  213. package/packages/config/src/bridge-utils.ts +59 -0
  214. package/packages/config/src/cli-auth-config.ts +548 -0
  215. package/packages/config/src/cloud-config.ts +208 -0
  216. package/packages/config/src/index.ts +12 -0
  217. package/packages/config/src/project-namespace.ts +344 -0
  218. package/packages/config/src/relay-config.test.ts +51 -0
  219. package/packages/config/src/relay-config.ts +36 -0
  220. package/packages/config/src/relay-file-writer.test.ts +351 -0
  221. package/packages/config/src/relay-file-writer.ts +508 -0
  222. package/packages/config/src/schemas.test.ts +59 -0
  223. package/packages/config/src/schemas.ts +201 -0
  224. package/packages/config/src/shadow-config.ts +205 -0
  225. package/packages/config/src/teams-config.ts +135 -0
  226. package/packages/config/src/trajectory-config.ts +222 -0
  227. package/packages/config/tsconfig.json +21 -0
  228. package/packages/config/vitest.config.ts +9 -0
  229. package/packages/continuity/dist/formatter.d.ts.map +1 -0
  230. package/packages/continuity/dist/formatter.js.map +1 -0
  231. package/packages/continuity/dist/handoff-store.d.ts.map +1 -0
  232. package/packages/continuity/dist/handoff-store.js.map +1 -0
  233. package/packages/continuity/dist/index.d.ts.map +1 -0
  234. package/packages/continuity/dist/index.js.map +1 -0
  235. package/packages/continuity/dist/ledger-store.d.ts.map +1 -0
  236. package/packages/continuity/dist/ledger-store.js.map +1 -0
  237. package/packages/continuity/dist/manager.d.ts.map +1 -0
  238. package/packages/continuity/dist/manager.js.map +1 -0
  239. package/packages/continuity/dist/parser.d.ts.map +1 -0
  240. package/packages/continuity/dist/parser.js.map +1 -0
  241. package/packages/continuity/dist/types.d.ts.map +1 -0
  242. package/packages/continuity/dist/types.js.map +1 -0
  243. package/packages/continuity/package.json +1 -1
  244. package/packages/continuity/src/formatter.ts +371 -0
  245. package/packages/continuity/src/handoff-store.ts +523 -0
  246. package/packages/continuity/src/index.ts +9 -0
  247. package/packages/continuity/src/ledger-store.ts +594 -0
  248. package/packages/continuity/src/manager.test.ts +291 -0
  249. package/packages/continuity/src/manager.ts +774 -0
  250. package/packages/continuity/src/parser.test.ts +292 -0
  251. package/packages/continuity/src/parser.ts +680 -0
  252. package/packages/continuity/src/types.ts +211 -0
  253. package/packages/continuity/tsconfig.json +21 -0
  254. package/packages/continuity/vitest.config.ts +9 -0
  255. package/packages/daemon/dist/agent-manager.d.ts.map +1 -0
  256. package/packages/daemon/dist/agent-manager.js.map +1 -0
  257. package/packages/daemon/dist/agent-registry.d.ts.map +1 -0
  258. package/packages/daemon/dist/agent-registry.js.map +1 -0
  259. package/packages/daemon/dist/agent-signing.d.ts.map +1 -0
  260. package/packages/daemon/dist/agent-signing.js.map +1 -0
  261. package/packages/daemon/dist/api.d.ts.map +1 -0
  262. package/packages/daemon/dist/api.js.map +1 -0
  263. package/packages/daemon/dist/auth.d.ts.map +1 -0
  264. package/packages/daemon/dist/auth.js.map +1 -0
  265. package/packages/daemon/dist/channel-membership-store.d.ts.map +1 -0
  266. package/packages/daemon/dist/channel-membership-store.js.map +1 -0
  267. package/packages/daemon/dist/cli-auth.d.ts.map +1 -0
  268. package/packages/daemon/dist/cli-auth.js.map +1 -0
  269. package/packages/daemon/dist/cloud-sync.d.ts.map +1 -0
  270. package/packages/daemon/dist/cloud-sync.js.map +1 -0
  271. package/packages/daemon/dist/connection.d.ts.map +1 -0
  272. package/packages/daemon/dist/connection.js.map +1 -0
  273. package/packages/daemon/dist/consensus-integration.d.ts.map +1 -0
  274. package/packages/daemon/dist/consensus-integration.js.map +1 -0
  275. package/packages/daemon/dist/consensus.d.ts.map +1 -0
  276. package/packages/daemon/dist/consensus.js.map +1 -0
  277. package/packages/daemon/dist/delivery-tracker.d.ts.map +1 -0
  278. package/packages/daemon/dist/delivery-tracker.js.map +1 -0
  279. package/packages/daemon/dist/enhanced-features.d.ts.map +1 -0
  280. package/packages/daemon/dist/enhanced-features.js.map +1 -0
  281. package/packages/daemon/dist/index.d.ts.map +1 -0
  282. package/packages/daemon/dist/index.js.map +1 -0
  283. package/packages/daemon/dist/migrations/index.d.ts.map +1 -0
  284. package/packages/daemon/dist/migrations/index.js.map +1 -0
  285. package/packages/daemon/dist/orchestrator.d.ts.map +1 -0
  286. package/packages/daemon/dist/orchestrator.js.map +1 -0
  287. package/packages/daemon/dist/rate-limiter.d.ts.map +1 -0
  288. package/packages/daemon/dist/rate-limiter.js.map +1 -0
  289. package/packages/daemon/dist/registry.d.ts.map +1 -0
  290. package/packages/daemon/dist/registry.js.map +1 -0
  291. package/packages/daemon/dist/relay-ledger.d.ts.map +1 -0
  292. package/packages/daemon/dist/relay-ledger.js.map +1 -0
  293. package/packages/daemon/dist/relay-watchdog.d.ts.map +1 -0
  294. package/packages/daemon/dist/relay-watchdog.js.map +1 -0
  295. package/packages/daemon/dist/repo-manager.d.ts.map +1 -0
  296. package/packages/daemon/dist/repo-manager.js.map +1 -0
  297. package/packages/daemon/dist/router.d.ts.map +1 -0
  298. package/packages/daemon/dist/router.js.map +1 -0
  299. package/packages/daemon/dist/server.d.ts +1 -0
  300. package/packages/daemon/dist/server.d.ts.map +1 -0
  301. package/packages/daemon/dist/server.js +46 -16
  302. package/packages/daemon/dist/server.js.map +1 -0
  303. package/packages/daemon/dist/spawn-manager.d.ts.map +1 -0
  304. package/packages/daemon/dist/spawn-manager.js.map +1 -0
  305. package/packages/daemon/dist/sync-queue.d.ts.map +1 -0
  306. package/packages/daemon/dist/sync-queue.js.map +1 -0
  307. package/packages/daemon/dist/types.d.ts.map +1 -0
  308. package/packages/daemon/dist/types.js.map +1 -0
  309. package/packages/daemon/dist/workspace-manager.d.ts.map +1 -0
  310. package/packages/daemon/dist/workspace-manager.js.map +1 -0
  311. package/packages/daemon/package.json +12 -12
  312. package/packages/daemon/src/agent-manager.ts +679 -0
  313. package/packages/daemon/src/agent-registry.ts +284 -0
  314. package/packages/daemon/src/agent-signing.ts +707 -0
  315. package/packages/daemon/src/api.ts +1012 -0
  316. package/packages/daemon/src/auth.ts +276 -0
  317. package/packages/daemon/src/channel-membership-store.ts +217 -0
  318. package/packages/daemon/src/cli-auth.ts +906 -0
  319. package/packages/daemon/src/cloud-sync.ts +902 -0
  320. package/packages/daemon/src/connection.ts +534 -0
  321. package/packages/daemon/src/consensus-integration.ts +510 -0
  322. package/packages/daemon/src/consensus.ts +848 -0
  323. package/packages/daemon/src/delivery-tracker.ts +145 -0
  324. package/packages/daemon/src/enhanced-features.ts +390 -0
  325. package/packages/daemon/src/index.ts +52 -0
  326. package/packages/daemon/src/migrations/0001_initial.sql +72 -0
  327. package/packages/daemon/src/migrations/index.test.ts +195 -0
  328. package/packages/daemon/src/migrations/index.ts +286 -0
  329. package/packages/daemon/src/orchestrator.test.ts +231 -0
  330. package/packages/daemon/src/orchestrator.ts +1376 -0
  331. package/packages/daemon/src/rate-limiter.ts +172 -0
  332. package/packages/daemon/src/registry.ts +8 -0
  333. package/packages/daemon/src/relay-ledger.test.ts +358 -0
  334. package/packages/daemon/src/relay-ledger.ts +713 -0
  335. package/packages/daemon/src/relay-watchdog.test.ts +881 -0
  336. package/packages/daemon/src/relay-watchdog.ts +785 -0
  337. package/packages/daemon/src/repo-manager.ts +468 -0
  338. package/packages/daemon/src/router.test.ts +149 -0
  339. package/packages/daemon/src/router.ts +1885 -0
  340. package/packages/daemon/src/server.ts +1871 -0
  341. package/packages/daemon/src/spawn-manager.ts +275 -0
  342. package/packages/daemon/src/sync-queue.ts +477 -0
  343. package/packages/daemon/src/types.ts +158 -0
  344. package/packages/daemon/src/workspace-manager.ts +371 -0
  345. package/packages/daemon/tsconfig.json +21 -0
  346. package/packages/hooks/dist/browser.d.ts.map +1 -0
  347. package/packages/hooks/dist/browser.js.map +1 -0
  348. package/packages/hooks/dist/emitter.d.ts.map +1 -0
  349. package/packages/hooks/dist/emitter.js.map +1 -0
  350. package/packages/hooks/dist/inbox-check/hook.d.ts.map +1 -0
  351. package/packages/hooks/dist/inbox-check/hook.js.map +1 -0
  352. package/packages/hooks/dist/inbox-check/index.d.ts.map +1 -0
  353. package/packages/hooks/dist/inbox-check/index.js.map +1 -0
  354. package/packages/hooks/dist/inbox-check/types.d.ts.map +1 -0
  355. package/packages/hooks/dist/inbox-check/types.js.map +1 -0
  356. package/packages/hooks/dist/inbox-check/utils.d.ts.map +1 -0
  357. package/packages/hooks/dist/inbox-check/utils.js.map +1 -0
  358. package/packages/hooks/dist/index.d.ts.map +1 -0
  359. package/packages/hooks/dist/index.js.map +1 -0
  360. package/packages/hooks/dist/registry.d.ts.map +1 -0
  361. package/packages/hooks/dist/registry.js.map +1 -0
  362. package/packages/hooks/dist/trajectory-hooks.d.ts.map +1 -0
  363. package/packages/hooks/dist/trajectory-hooks.js.map +1 -0
  364. package/packages/hooks/dist/types.d.ts.map +1 -0
  365. package/packages/hooks/dist/types.js.map +1 -0
  366. package/packages/hooks/package.json +4 -4
  367. package/packages/hooks/src/browser.ts +2 -0
  368. package/packages/hooks/src/emitter.ts +84 -0
  369. package/packages/hooks/src/inbox-check/hook.ts +114 -0
  370. package/packages/hooks/src/inbox-check/index.ts +8 -0
  371. package/packages/hooks/src/inbox-check/types.ts +39 -0
  372. package/packages/hooks/src/inbox-check/utils.test.ts +287 -0
  373. package/packages/hooks/src/inbox-check/utils.ts +125 -0
  374. package/packages/hooks/src/index.ts +11 -0
  375. package/packages/hooks/src/registry.ts +614 -0
  376. package/packages/hooks/src/shims.d.ts +3 -0
  377. package/packages/hooks/src/trajectory-hooks.ts +251 -0
  378. package/packages/hooks/src/types.ts +342 -0
  379. package/packages/hooks/tsconfig.json +21 -0
  380. package/packages/hooks/vitest.config.ts +9 -0
  381. package/packages/mcp/dist/bin.d.ts.map +1 -0
  382. package/packages/mcp/dist/bin.js.map +1 -0
  383. package/packages/mcp/dist/client.d.ts +9 -15
  384. package/packages/mcp/dist/client.d.ts.map +1 -0
  385. package/packages/mcp/dist/client.js +42 -74
  386. package/packages/mcp/dist/client.js.map +1 -0
  387. package/packages/mcp/dist/cloud.d.ts.map +1 -0
  388. package/packages/mcp/dist/cloud.js.map +1 -0
  389. package/packages/mcp/dist/errors.d.ts.map +1 -0
  390. package/packages/mcp/dist/errors.js.map +1 -0
  391. package/packages/mcp/dist/file-transport.d.ts.map +1 -0
  392. package/packages/mcp/dist/file-transport.js.map +1 -0
  393. package/packages/mcp/dist/hybrid-client.d.ts.map +1 -0
  394. package/packages/mcp/dist/hybrid-client.js.map +1 -0
  395. package/packages/mcp/dist/index.d.ts.map +1 -0
  396. package/packages/mcp/dist/index.js.map +1 -0
  397. package/packages/mcp/dist/install-cli.d.ts.map +1 -0
  398. package/packages/mcp/dist/install-cli.js.map +1 -0
  399. package/packages/mcp/dist/install.d.ts.map +1 -0
  400. package/packages/mcp/dist/install.js.map +1 -0
  401. package/packages/mcp/dist/prompts/index.d.ts.map +1 -0
  402. package/packages/mcp/dist/prompts/index.js.map +1 -0
  403. package/packages/mcp/dist/prompts/protocol.d.ts.map +1 -0
  404. package/packages/mcp/dist/prompts/protocol.js.map +1 -0
  405. package/packages/mcp/dist/resources/agents.d.ts.map +1 -0
  406. package/packages/mcp/dist/resources/agents.js.map +1 -0
  407. package/packages/mcp/dist/resources/inbox.d.ts.map +1 -0
  408. package/packages/mcp/dist/resources/inbox.js.map +1 -0
  409. package/packages/mcp/dist/resources/index.d.ts.map +1 -0
  410. package/packages/mcp/dist/resources/index.js.map +1 -0
  411. package/packages/mcp/dist/resources/project.d.ts.map +1 -0
  412. package/packages/mcp/dist/resources/project.js.map +1 -0
  413. package/packages/mcp/dist/server.d.ts.map +1 -0
  414. package/packages/mcp/dist/server.js.map +1 -0
  415. package/packages/mcp/dist/simple.d.ts +2 -5
  416. package/packages/mcp/dist/simple.d.ts.map +1 -0
  417. package/packages/mcp/dist/simple.js.map +1 -0
  418. package/packages/mcp/dist/tools/index.d.ts.map +1 -0
  419. package/packages/mcp/dist/tools/index.js.map +1 -0
  420. package/packages/mcp/dist/tools/relay-broadcast.d.ts.map +1 -0
  421. package/packages/mcp/dist/tools/relay-broadcast.js.map +1 -0
  422. package/packages/mcp/dist/tools/relay-channel.d.ts.map +1 -0
  423. package/packages/mcp/dist/tools/relay-channel.js.map +1 -0
  424. package/packages/mcp/dist/tools/relay-connected.d.ts.map +1 -0
  425. package/packages/mcp/dist/tools/relay-connected.js.map +1 -0
  426. package/packages/mcp/dist/tools/relay-consensus.d.ts.map +1 -0
  427. package/packages/mcp/dist/tools/relay-consensus.js.map +1 -0
  428. package/packages/mcp/dist/tools/relay-continuity.d.ts.map +1 -0
  429. package/packages/mcp/dist/tools/relay-continuity.js.map +1 -0
  430. package/packages/mcp/dist/tools/relay-health.d.ts.map +1 -0
  431. package/packages/mcp/dist/tools/relay-health.js.map +1 -0
  432. package/packages/mcp/dist/tools/relay-inbox.d.ts.map +1 -0
  433. package/packages/mcp/dist/tools/relay-inbox.js.map +1 -0
  434. package/packages/mcp/dist/tools/relay-logs.d.ts.map +1 -0
  435. package/packages/mcp/dist/tools/relay-logs.js.map +1 -0
  436. package/packages/mcp/dist/tools/relay-metrics.d.ts.map +1 -0
  437. package/packages/mcp/dist/tools/relay-metrics.js.map +1 -0
  438. package/packages/mcp/dist/tools/relay-release.d.ts.map +1 -0
  439. package/packages/mcp/dist/tools/relay-release.js.map +1 -0
  440. package/packages/mcp/dist/tools/relay-remove-agent.d.ts.map +1 -0
  441. package/packages/mcp/dist/tools/relay-remove-agent.js.map +1 -0
  442. package/packages/mcp/dist/tools/relay-send.d.ts.map +1 -0
  443. package/packages/mcp/dist/tools/relay-send.js +4 -2
  444. package/packages/mcp/dist/tools/relay-send.js.map +1 -0
  445. package/packages/mcp/dist/tools/relay-shadow.d.ts.map +1 -0
  446. package/packages/mcp/dist/tools/relay-shadow.js.map +1 -0
  447. package/packages/mcp/dist/tools/relay-spawn.d.ts.map +1 -0
  448. package/packages/mcp/dist/tools/relay-spawn.js.map +1 -0
  449. package/packages/mcp/dist/tools/relay-status.d.ts.map +1 -0
  450. package/packages/mcp/dist/tools/relay-status.js.map +1 -0
  451. package/packages/mcp/dist/tools/relay-subscribe.d.ts.map +1 -0
  452. package/packages/mcp/dist/tools/relay-subscribe.js.map +1 -0
  453. package/packages/mcp/dist/tools/relay-who.d.ts.map +1 -0
  454. package/packages/mcp/dist/tools/relay-who.js.map +1 -0
  455. package/packages/mcp/package.json +3 -3
  456. package/packages/mcp/src/bin.ts +149 -0
  457. package/packages/mcp/src/client.ts +400 -0
  458. package/packages/mcp/src/cloud.ts +523 -0
  459. package/packages/mcp/src/errors.ts +54 -0
  460. package/packages/mcp/src/file-transport.ts +268 -0
  461. package/packages/mcp/src/hybrid-client.ts +209 -0
  462. package/packages/mcp/src/index.ts +122 -0
  463. package/packages/mcp/src/install-cli.ts +210 -0
  464. package/packages/mcp/src/install.ts +745 -0
  465. package/packages/mcp/src/prompts/index.ts +1 -0
  466. package/packages/mcp/src/prompts/protocol.ts +164 -0
  467. package/packages/mcp/src/resources/agents.ts +21 -0
  468. package/packages/mcp/src/resources/inbox.ts +21 -0
  469. package/packages/mcp/src/resources/index.ts +3 -0
  470. package/packages/mcp/src/resources/project.ts +29 -0
  471. package/packages/mcp/src/server.ts +431 -0
  472. package/packages/mcp/src/simple.ts +214 -0
  473. package/packages/mcp/src/tools/index.ts +133 -0
  474. package/packages/mcp/src/tools/relay-broadcast.ts +32 -0
  475. package/packages/mcp/src/tools/relay-channel.ts +93 -0
  476. package/packages/mcp/src/tools/relay-connected.ts +52 -0
  477. package/packages/mcp/src/tools/relay-consensus.ts +92 -0
  478. package/packages/mcp/src/tools/relay-continuity.ts +127 -0
  479. package/packages/mcp/src/tools/relay-health.ts +148 -0
  480. package/packages/mcp/src/tools/relay-inbox.ts +70 -0
  481. package/packages/mcp/src/tools/relay-logs.ts +106 -0
  482. package/packages/mcp/src/tools/relay-metrics.ts +140 -0
  483. package/packages/mcp/src/tools/relay-release.ts +54 -0
  484. package/packages/mcp/src/tools/relay-remove-agent.ts +58 -0
  485. package/packages/mcp/src/tools/relay-send.ts +84 -0
  486. package/packages/mcp/src/tools/relay-shadow.ts +67 -0
  487. package/packages/mcp/src/tools/relay-spawn.ts +87 -0
  488. package/packages/mcp/src/tools/relay-status.ts +57 -0
  489. package/packages/mcp/src/tools/relay-subscribe.ts +61 -0
  490. package/packages/mcp/src/tools/relay-who.ts +59 -0
  491. package/packages/mcp/tests/client.test.ts +476 -0
  492. package/packages/mcp/tests/discover.test.ts +195 -0
  493. package/packages/mcp/tests/install.test.ts +123 -0
  494. package/packages/mcp/tests/prompts.test.ts +12 -0
  495. package/packages/mcp/tests/resources.test.ts +53 -0
  496. package/packages/mcp/tests/tools.test.ts +1242 -0
  497. package/packages/mcp/tsconfig.json +22 -0
  498. package/packages/mcp/vitest.config.ts +9 -0
  499. package/packages/memory/dist/adapters/index.d.ts.map +1 -0
  500. package/packages/memory/dist/adapters/index.js.map +1 -0
  501. package/packages/memory/dist/adapters/inmemory.d.ts.map +1 -0
  502. package/packages/memory/dist/adapters/inmemory.js.map +1 -0
  503. package/packages/memory/dist/adapters/supermemory.d.ts.map +1 -0
  504. package/packages/memory/dist/adapters/supermemory.js.map +1 -0
  505. package/packages/memory/dist/context-compaction.d.ts.map +1 -0
  506. package/packages/memory/dist/context-compaction.js.map +1 -0
  507. package/packages/memory/dist/factory.d.ts.map +1 -0
  508. package/packages/memory/dist/factory.js.map +1 -0
  509. package/packages/memory/dist/index.d.ts.map +1 -0
  510. package/packages/memory/dist/index.js.map +1 -0
  511. package/packages/memory/dist/memory-hooks.d.ts.map +1 -0
  512. package/packages/memory/dist/memory-hooks.js.map +1 -0
  513. package/packages/memory/dist/service.d.ts.map +1 -0
  514. package/packages/memory/dist/service.js.map +1 -0
  515. package/packages/memory/dist/types.d.ts.map +1 -0
  516. package/packages/memory/dist/types.js.map +1 -0
  517. package/packages/memory/package.json +2 -2
  518. package/packages/memory/src/adapters/index.ts +8 -0
  519. package/packages/memory/src/adapters/inmemory.ts +265 -0
  520. package/packages/memory/src/adapters/supermemory.ts +449 -0
  521. package/packages/memory/src/context-compaction.test.ts +660 -0
  522. package/packages/memory/src/context-compaction.ts +612 -0
  523. package/packages/memory/src/factory.ts +170 -0
  524. package/packages/memory/src/index.ts +33 -0
  525. package/packages/memory/src/memory-hooks.ts +410 -0
  526. package/packages/memory/src/service.ts +194 -0
  527. package/packages/memory/src/types.ts +211 -0
  528. package/packages/memory/tsconfig.json +21 -0
  529. package/packages/memory/vitest.config.ts +9 -0
  530. package/packages/policy/dist/agent-policy.d.ts.map +1 -0
  531. package/packages/policy/dist/agent-policy.js.map +1 -0
  532. package/packages/policy/dist/cloud-policy-fetcher.d.ts.map +1 -0
  533. package/packages/policy/dist/cloud-policy-fetcher.js.map +1 -0
  534. package/packages/policy/dist/index.d.ts.map +1 -0
  535. package/packages/policy/dist/index.js.map +1 -0
  536. package/packages/policy/package.json +2 -2
  537. package/packages/policy/src/agent-policy.ts +866 -0
  538. package/packages/policy/src/cloud-policy-fetcher.ts +78 -0
  539. package/packages/policy/src/index.ts +21 -0
  540. package/packages/policy/tsconfig.json +21 -0
  541. package/packages/policy/vitest.config.ts +9 -0
  542. package/packages/protocol/dist/channels.d.ts.map +1 -0
  543. package/packages/protocol/dist/channels.js.map +1 -0
  544. package/packages/protocol/dist/framing.d.ts.map +1 -0
  545. package/packages/protocol/dist/framing.js.map +1 -0
  546. package/packages/protocol/dist/id-generator.d.ts.map +1 -0
  547. package/packages/protocol/dist/id-generator.js.map +1 -0
  548. package/packages/protocol/dist/index.d.ts.map +1 -0
  549. package/packages/protocol/dist/index.js.map +1 -0
  550. package/packages/protocol/dist/relay-pty-schemas.d.ts +70 -2
  551. package/packages/protocol/dist/relay-pty-schemas.d.ts.map +1 -0
  552. package/packages/protocol/dist/relay-pty-schemas.js.map +1 -0
  553. package/packages/protocol/dist/types.d.ts +8 -0
  554. package/packages/protocol/dist/types.d.ts.map +1 -0
  555. package/packages/protocol/dist/types.js.map +1 -0
  556. package/packages/protocol/package.json +1 -1
  557. package/packages/protocol/src/channels.test.ts +330 -0
  558. package/packages/protocol/src/channels.ts +270 -0
  559. package/packages/protocol/src/framing.test.ts +164 -0
  560. package/packages/protocol/src/framing.ts +242 -0
  561. package/packages/protocol/src/id-generator.ts +69 -0
  562. package/packages/protocol/src/index.ts +4 -0
  563. package/packages/protocol/src/relay-pty-schemas.ts +400 -0
  564. package/packages/protocol/src/types.test.ts +271 -0
  565. package/packages/protocol/src/types.ts +846 -0
  566. package/packages/protocol/tsconfig.json +21 -0
  567. package/packages/protocol/vitest.config.ts +9 -0
  568. package/packages/resiliency/dist/cgroup-manager.d.ts.map +1 -0
  569. package/packages/resiliency/dist/cgroup-manager.js.map +1 -0
  570. package/packages/resiliency/dist/context-persistence.d.ts.map +1 -0
  571. package/packages/resiliency/dist/context-persistence.js.map +1 -0
  572. package/packages/resiliency/dist/crash-insights.d.ts.map +1 -0
  573. package/packages/resiliency/dist/crash-insights.js.map +1 -0
  574. package/packages/resiliency/dist/gossip-health.d.ts.map +1 -0
  575. package/packages/resiliency/dist/gossip-health.js.map +1 -0
  576. package/packages/resiliency/dist/health-monitor.d.ts.map +1 -0
  577. package/packages/resiliency/dist/health-monitor.js.map +1 -0
  578. package/packages/resiliency/dist/index.d.ts.map +1 -0
  579. package/packages/resiliency/dist/index.js.map +1 -0
  580. package/packages/resiliency/dist/leader-watchdog.d.ts.map +1 -0
  581. package/packages/resiliency/dist/leader-watchdog.js.map +1 -0
  582. package/packages/resiliency/dist/logger.d.ts.map +1 -0
  583. package/packages/resiliency/dist/logger.js.map +1 -0
  584. package/packages/resiliency/dist/memory-monitor.d.ts.map +1 -0
  585. package/packages/resiliency/dist/memory-monitor.js.map +1 -0
  586. package/packages/resiliency/dist/metrics.d.ts.map +1 -0
  587. package/packages/resiliency/dist/metrics.js.map +1 -0
  588. package/packages/resiliency/dist/provider-context.d.ts.map +1 -0
  589. package/packages/resiliency/dist/provider-context.js.map +1 -0
  590. package/packages/resiliency/dist/stateless-lead.d.ts.map +1 -0
  591. package/packages/resiliency/dist/stateless-lead.js.map +1 -0
  592. package/packages/resiliency/dist/supervisor.d.ts.map +1 -0
  593. package/packages/resiliency/dist/supervisor.js.map +1 -0
  594. package/packages/resiliency/package.json +1 -1
  595. package/packages/resiliency/src/cgroup-manager.ts +468 -0
  596. package/packages/resiliency/src/context-persistence.ts +538 -0
  597. package/packages/resiliency/src/crash-insights.test.ts +620 -0
  598. package/packages/resiliency/src/crash-insights.ts +660 -0
  599. package/packages/resiliency/src/gossip-health.ts +333 -0
  600. package/packages/resiliency/src/health-monitor.ts +371 -0
  601. package/packages/resiliency/src/index.ts +157 -0
  602. package/packages/resiliency/src/leader-watchdog.ts +260 -0
  603. package/packages/resiliency/src/logger.ts +320 -0
  604. package/packages/resiliency/src/memory-monitor.test.ts +637 -0
  605. package/packages/resiliency/src/memory-monitor.ts +740 -0
  606. package/packages/resiliency/src/metrics.ts +311 -0
  607. package/packages/resiliency/src/provider-context.ts +452 -0
  608. package/packages/resiliency/src/stateless-lead.ts +408 -0
  609. package/packages/resiliency/src/supervisor.ts +578 -0
  610. package/packages/resiliency/tsconfig.json +21 -0
  611. package/packages/resiliency/vitest.config.ts +9 -0
  612. package/packages/sdk/dist/client.d.ts.map +1 -0
  613. package/packages/sdk/dist/client.js.map +1 -0
  614. package/packages/sdk/dist/index.d.ts.map +1 -0
  615. package/packages/sdk/dist/index.js.map +1 -0
  616. package/packages/sdk/dist/logs.d.ts.map +1 -0
  617. package/packages/sdk/dist/logs.js.map +1 -0
  618. package/packages/sdk/dist/protocol/index.d.ts.map +1 -0
  619. package/packages/sdk/dist/protocol/index.js.map +1 -0
  620. package/packages/sdk/dist/standalone.d.ts.map +1 -0
  621. package/packages/sdk/dist/standalone.js.map +1 -0
  622. package/packages/sdk/examples/SWARM_CAPABILITIES.md +498 -0
  623. package/packages/sdk/examples/SWARM_PATTERNS.md +541 -0
  624. package/packages/sdk/package.json +2 -2
  625. package/packages/sdk/src/client.test.ts +568 -0
  626. package/packages/sdk/src/client.ts +1418 -0
  627. package/packages/sdk/src/index.ts +103 -0
  628. package/packages/sdk/src/logs.test.ts +98 -0
  629. package/packages/sdk/src/logs.ts +126 -0
  630. package/packages/sdk/src/protocol/framing.test.ts +164 -0
  631. package/packages/sdk/src/protocol/index.ts +8 -0
  632. package/packages/sdk/src/standalone.ts +176 -0
  633. package/packages/sdk/tsconfig.json +22 -0
  634. package/packages/sdk/vitest.config.ts +9 -0
  635. package/packages/spawner/.trajectories/index.json +5 -0
  636. package/packages/spawner/dist/index.d.ts.map +1 -0
  637. package/packages/spawner/dist/index.js.map +1 -0
  638. package/packages/spawner/dist/types.d.ts.map +1 -0
  639. package/packages/spawner/dist/types.js.map +1 -0
  640. package/packages/spawner/package.json +1 -1
  641. package/packages/spawner/src/index.ts +8 -0
  642. package/packages/spawner/src/types.test.ts +385 -0
  643. package/packages/spawner/src/types.ts +228 -0
  644. package/packages/spawner/tsconfig.json +19 -0
  645. package/packages/spawner/vitest.config.ts +9 -0
  646. package/packages/state/dist/agent-state.d.ts.map +1 -0
  647. package/packages/state/dist/agent-state.js.map +1 -0
  648. package/packages/state/dist/index.d.ts.map +1 -0
  649. package/packages/state/dist/index.js.map +1 -0
  650. package/packages/state/package.json +1 -1
  651. package/packages/state/src/agent-state.test.ts +335 -0
  652. package/packages/state/src/agent-state.ts +153 -0
  653. package/packages/state/src/index.ts +12 -0
  654. package/packages/state/tsconfig.json +21 -0
  655. package/packages/state/vitest.config.ts +9 -0
  656. package/packages/storage/dist/adapter.d.ts +28 -1
  657. package/packages/storage/dist/adapter.d.ts.map +1 -0
  658. package/packages/storage/dist/adapter.js +104 -10
  659. package/packages/storage/dist/adapter.js.map +1 -0
  660. package/packages/storage/dist/batched-sqlite-adapter.d.ts.map +1 -0
  661. package/packages/storage/dist/batched-sqlite-adapter.js.map +1 -0
  662. package/packages/storage/dist/dead-letter-queue.d.ts.map +1 -0
  663. package/packages/storage/dist/dead-letter-queue.js.map +1 -0
  664. package/packages/storage/dist/dlq-adapter.d.ts.map +1 -0
  665. package/packages/storage/dist/dlq-adapter.js.map +1 -0
  666. package/packages/storage/dist/index.d.ts +1 -0
  667. package/packages/storage/dist/index.d.ts.map +1 -0
  668. package/packages/storage/dist/index.js +1 -0
  669. package/packages/storage/dist/index.js.map +1 -0
  670. package/packages/storage/dist/jsonl-adapter.d.ts +77 -0
  671. package/packages/storage/dist/jsonl-adapter.d.ts.map +1 -0
  672. package/packages/storage/dist/jsonl-adapter.js +505 -0
  673. package/packages/storage/dist/jsonl-adapter.js.map +1 -0
  674. package/packages/storage/dist/sqlite-adapter.d.ts +6 -1
  675. package/packages/storage/dist/sqlite-adapter.d.ts.map +1 -0
  676. package/packages/storage/dist/sqlite-adapter.js +47 -0
  677. package/packages/storage/dist/sqlite-adapter.js.map +1 -0
  678. package/packages/storage/package.json +2 -2
  679. package/packages/storage/src/adapter.ts +438 -0
  680. package/packages/storage/src/batched-sqlite-adapter.test.ts +240 -0
  681. package/packages/storage/src/batched-sqlite-adapter.ts +239 -0
  682. package/packages/storage/src/dead-letter-queue.ts +643 -0
  683. package/packages/storage/src/dlq-adapter.test.ts +492 -0
  684. package/packages/storage/src/dlq-adapter.ts +954 -0
  685. package/packages/storage/src/index.ts +6 -0
  686. package/packages/storage/src/jsonl-adapter.test.ts +200 -0
  687. package/packages/storage/src/jsonl-adapter.ts +618 -0
  688. package/packages/storage/src/memory-adapter.test.ts +36 -0
  689. package/packages/storage/src/sqlite-adapter.test.ts +562 -0
  690. package/packages/storage/src/sqlite-adapter.ts +1058 -0
  691. package/packages/storage/tsconfig.json +21 -0
  692. package/packages/storage/vitest.config.ts +9 -0
  693. package/packages/telemetry/dist/client.d.ts.map +1 -0
  694. package/packages/telemetry/dist/client.js.map +1 -0
  695. package/packages/telemetry/dist/config.d.ts.map +1 -0
  696. package/packages/telemetry/dist/config.js.map +1 -0
  697. package/packages/telemetry/dist/events.d.ts.map +1 -0
  698. package/packages/telemetry/dist/events.js.map +1 -0
  699. package/packages/telemetry/dist/index.d.ts.map +1 -0
  700. package/packages/telemetry/dist/index.js.map +1 -0
  701. package/packages/telemetry/dist/machine-id.d.ts.map +1 -0
  702. package/packages/telemetry/dist/machine-id.js.map +1 -0
  703. package/packages/telemetry/dist/posthog-config.d.ts.map +1 -0
  704. package/packages/telemetry/dist/posthog-config.js.map +1 -0
  705. package/packages/telemetry/package.json +1 -1
  706. package/packages/telemetry/src/client.ts +158 -0
  707. package/packages/telemetry/src/config.ts +110 -0
  708. package/packages/telemetry/src/events.ts +137 -0
  709. package/packages/telemetry/src/index.ts +46 -0
  710. package/packages/telemetry/src/machine-id.ts +63 -0
  711. package/packages/telemetry/src/posthog-config.ts +39 -0
  712. package/packages/telemetry/tsconfig.json +21 -0
  713. package/packages/trajectory/dist/index.d.ts.map +1 -0
  714. package/packages/trajectory/dist/index.js.map +1 -0
  715. package/packages/trajectory/dist/integration.d.ts.map +1 -0
  716. package/packages/trajectory/dist/integration.js.map +1 -0
  717. package/packages/trajectory/package.json +2 -2
  718. package/packages/trajectory/src/index.ts +1 -0
  719. package/packages/trajectory/src/integration.ts +1268 -0
  720. package/packages/trajectory/tsconfig.json +21 -0
  721. package/packages/trajectory/vitest.config.ts +9 -0
  722. package/packages/user-directory/dist/index.d.ts.map +1 -0
  723. package/packages/user-directory/dist/index.js.map +1 -0
  724. package/packages/user-directory/dist/user-directory.d.ts.map +1 -0
  725. package/packages/user-directory/dist/user-directory.js.map +1 -0
  726. package/packages/user-directory/package.json +2 -2
  727. package/packages/user-directory/src/index.ts +12 -0
  728. package/packages/user-directory/src/user-directory.ts +393 -0
  729. package/packages/user-directory/tsconfig.json +21 -0
  730. package/packages/user-directory/vitest.config.ts +9 -0
  731. package/packages/utils/dist/cjs/client-helpers.js +127 -0
  732. package/packages/utils/dist/cjs/command-resolver.js +89 -0
  733. package/packages/utils/dist/cjs/error-tracking.js +106 -0
  734. package/packages/utils/dist/cjs/git-remote.js +120 -0
  735. package/packages/utils/dist/cjs/index.js +40 -0
  736. package/packages/utils/dist/cjs/logger.js +105 -0
  737. package/packages/utils/dist/cjs/model-mapping.js +54 -0
  738. package/packages/utils/dist/cjs/name-generator.js +179 -0
  739. package/packages/utils/dist/cjs/package.json +3 -0
  740. package/packages/utils/dist/cjs/precompiled-patterns.js +271 -0
  741. package/packages/utils/dist/cjs/relay-pty-path.js +143 -0
  742. package/packages/utils/dist/cjs/update-checker.js +185 -0
  743. package/packages/utils/dist/client-helpers.d.ts +73 -0
  744. package/packages/utils/dist/client-helpers.d.ts.map +1 -0
  745. package/packages/utils/dist/client-helpers.js +130 -0
  746. package/packages/utils/dist/client-helpers.js.map +1 -0
  747. package/packages/utils/dist/command-resolver.d.ts.map +1 -0
  748. package/packages/utils/dist/command-resolver.js.map +1 -0
  749. package/packages/utils/dist/error-tracking.d.ts.map +1 -0
  750. package/packages/utils/dist/error-tracking.js.map +1 -0
  751. package/packages/utils/dist/git-remote.d.ts.map +1 -0
  752. package/packages/utils/dist/git-remote.js.map +1 -0
  753. package/packages/utils/dist/index.d.ts +1 -0
  754. package/packages/utils/dist/index.d.ts.map +1 -0
  755. package/packages/utils/dist/index.js +1 -0
  756. package/packages/utils/dist/index.js.map +1 -0
  757. package/packages/utils/dist/logger.d.ts.map +1 -0
  758. package/packages/utils/dist/logger.js.map +1 -0
  759. package/packages/utils/dist/model-mapping.d.ts.map +1 -0
  760. package/packages/utils/dist/model-mapping.js.map +1 -0
  761. package/packages/utils/dist/name-generator.d.ts.map +1 -0
  762. package/packages/utils/dist/name-generator.js.map +1 -0
  763. package/packages/utils/dist/precompiled-patterns.d.ts.map +1 -0
  764. package/packages/utils/dist/precompiled-patterns.js.map +1 -0
  765. package/packages/utils/dist/relay-pty-path.d.ts +11 -5
  766. package/packages/utils/dist/relay-pty-path.d.ts.map +1 -0
  767. package/packages/utils/dist/relay-pty-path.js +60 -5
  768. package/packages/utils/dist/relay-pty-path.js.map +1 -0
  769. package/packages/utils/dist/update-checker.d.ts.map +1 -0
  770. package/packages/utils/dist/update-checker.js.map +1 -0
  771. package/packages/utils/package.json +37 -14
  772. package/packages/utils/scripts/build-cjs.mjs +24 -0
  773. package/packages/utils/src/client-helpers.ts +221 -0
  774. package/packages/utils/src/command-resolver.ts +82 -0
  775. package/packages/utils/src/error-tracking.ts +189 -0
  776. package/packages/utils/src/git-remote.ts +143 -0
  777. package/packages/utils/src/index.ts +10 -0
  778. package/packages/utils/src/logger.ts +107 -0
  779. package/packages/utils/src/model-mapping.test.ts +122 -0
  780. package/packages/utils/src/model-mapping.ts +58 -0
  781. package/packages/utils/src/name-generator.test.ts +259 -0
  782. package/packages/utils/src/name-generator.ts +56 -0
  783. package/packages/utils/src/precompiled-patterns.test.ts +452 -0
  784. package/packages/utils/src/precompiled-patterns.ts +395 -0
  785. package/packages/utils/src/relay-pty-path.ts +196 -0
  786. package/packages/utils/src/update-checker.test.ts +260 -0
  787. package/packages/utils/src/update-checker.ts +211 -0
  788. package/packages/utils/tsconfig.json +21 -0
  789. package/packages/utils/vitest.config.ts +9 -0
  790. package/packages/wrapper/dist/__fixtures__/claude-outputs.d.ts.map +1 -0
  791. package/packages/wrapper/dist/__fixtures__/claude-outputs.js.map +1 -0
  792. package/packages/wrapper/dist/__fixtures__/codex-outputs.d.ts.map +1 -0
  793. package/packages/wrapper/dist/__fixtures__/codex-outputs.js.map +1 -0
  794. package/packages/wrapper/dist/__fixtures__/gemini-outputs.d.ts.map +1 -0
  795. package/packages/wrapper/dist/__fixtures__/gemini-outputs.js.map +1 -0
  796. package/packages/wrapper/dist/__fixtures__/index.d.ts.map +1 -0
  797. package/packages/wrapper/dist/__fixtures__/index.js.map +1 -0
  798. package/packages/wrapper/dist/auth-detection.d.ts.map +1 -0
  799. package/packages/wrapper/dist/auth-detection.js.map +1 -0
  800. package/packages/wrapper/dist/base-wrapper.d.ts.map +1 -0
  801. package/packages/wrapper/dist/base-wrapper.js.map +1 -0
  802. package/packages/wrapper/dist/client.d.ts.map +1 -0
  803. package/packages/wrapper/dist/client.js.map +1 -0
  804. package/packages/wrapper/dist/id-generator.d.ts.map +1 -0
  805. package/packages/wrapper/dist/id-generator.js.map +1 -0
  806. package/packages/wrapper/dist/idle-detector.d.ts.map +1 -0
  807. package/packages/wrapper/dist/idle-detector.js.map +1 -0
  808. package/packages/wrapper/dist/inbox.d.ts.map +1 -0
  809. package/packages/wrapper/dist/inbox.js.map +1 -0
  810. package/packages/wrapper/dist/index.d.ts.map +1 -0
  811. package/packages/wrapper/dist/index.js.map +1 -0
  812. package/packages/wrapper/dist/parser.d.ts.map +1 -0
  813. package/packages/wrapper/dist/parser.js.map +1 -0
  814. package/packages/wrapper/dist/prompt-composer.d.ts.map +1 -0
  815. package/packages/wrapper/dist/prompt-composer.js.map +1 -0
  816. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +10 -0
  817. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts.map +1 -0
  818. package/packages/wrapper/dist/relay-pty-orchestrator.js +69 -0
  819. package/packages/wrapper/dist/relay-pty-orchestrator.js.map +1 -0
  820. package/packages/wrapper/dist/shared.d.ts.map +1 -0
  821. package/packages/wrapper/dist/shared.js.map +1 -0
  822. package/packages/wrapper/dist/stuck-detector.d.ts.map +1 -0
  823. package/packages/wrapper/dist/stuck-detector.js.map +1 -0
  824. package/packages/wrapper/dist/tmux-resolver.d.ts.map +1 -0
  825. package/packages/wrapper/dist/tmux-resolver.js.map +1 -0
  826. package/packages/wrapper/dist/tmux-wrapper.d.ts.map +1 -0
  827. package/packages/wrapper/dist/tmux-wrapper.js.map +1 -0
  828. package/packages/wrapper/dist/trajectory-integration.d.ts.map +1 -0
  829. package/packages/wrapper/dist/trajectory-integration.js.map +1 -0
  830. package/packages/wrapper/dist/wrapper-types.d.ts.map +1 -0
  831. package/packages/wrapper/dist/wrapper-types.js.map +1 -0
  832. package/packages/wrapper/package.json +6 -9
  833. package/packages/wrapper/src/__fixtures__/claude-outputs.ts +471 -0
  834. package/packages/wrapper/src/__fixtures__/codex-outputs.ts +99 -0
  835. package/packages/wrapper/src/__fixtures__/gemini-outputs.ts +151 -0
  836. package/packages/wrapper/src/__fixtures__/index.ts +47 -0
  837. package/packages/wrapper/src/auth-detection.ts +244 -0
  838. package/packages/wrapper/src/base-wrapper.test.ts +589 -0
  839. package/packages/wrapper/src/base-wrapper.ts +810 -0
  840. package/packages/wrapper/src/client.test.ts +262 -0
  841. package/packages/wrapper/src/client.ts +984 -0
  842. package/packages/wrapper/src/id-generator.test.ts +71 -0
  843. package/packages/wrapper/src/id-generator.ts +69 -0
  844. package/packages/wrapper/src/idle-detector.test.ts +418 -0
  845. package/packages/wrapper/src/idle-detector.ts +384 -0
  846. package/packages/wrapper/src/inbox.test.ts +233 -0
  847. package/packages/wrapper/src/inbox.ts +89 -0
  848. package/packages/wrapper/src/index.ts +170 -0
  849. package/packages/wrapper/src/parser.regression.test.ts +251 -0
  850. package/packages/wrapper/src/parser.test.ts +1359 -0
  851. package/packages/wrapper/src/parser.ts +1477 -0
  852. package/packages/wrapper/src/prompt-composer.test.ts +219 -0
  853. package/packages/wrapper/src/prompt-composer.ts +231 -0
  854. package/packages/wrapper/src/relay-pty-orchestrator.test.ts +1204 -0
  855. package/packages/wrapper/src/relay-pty-orchestrator.ts +2626 -0
  856. package/packages/wrapper/src/shared.test.ts +322 -0
  857. package/packages/wrapper/src/shared.ts +495 -0
  858. package/packages/wrapper/src/stuck-detector.test.ts +303 -0
  859. package/packages/wrapper/src/stuck-detector.ts +511 -0
  860. package/packages/wrapper/src/tmux-resolver.test.ts +104 -0
  861. package/packages/wrapper/src/tmux-resolver.ts +207 -0
  862. package/packages/wrapper/src/tmux-wrapper.test.ts +316 -0
  863. package/packages/wrapper/src/tmux-wrapper.ts +2095 -0
  864. package/packages/wrapper/src/trajectory-detection.test.ts +151 -0
  865. package/packages/wrapper/src/trajectory-integration.ts +1261 -0
  866. package/packages/wrapper/src/wrapper-types.ts +45 -0
  867. package/packages/wrapper/tsconfig.json +19 -0
  868. package/packages/wrapper/vitest.config.ts +9 -0
  869. package/scripts/build-cjs.mjs +23 -0
  870. package/scripts/postinstall.js +132 -0
  871. package/.cursor/mcp.json +0 -11
  872. package/.gitattributes +0 -3
  873. package/.gitleaks.toml +0 -26
  874. package/.mcp.json +0 -11
  875. package/.nvmrc +0 -1
  876. package/ARCHITECTURE.md +0 -1245
  877. package/CHANGELOG.md +0 -231
  878. package/TESTING.md +0 -278
  879. package/TRAIL_GIT_AUTH_FIX.md +0 -113
  880. package/scripts/demos/README.md +0 -79
  881. package/scripts/demos/server-capacity.sh +0 -69
  882. package/scripts/demos/sprint-planning.sh +0 -73
  883. package/scripts/hooks/install.sh +0 -16
  884. package/scripts/hooks/pre-commit +0 -60
  885. package/scripts/post-publish-verify/README.md +0 -80
  886. package/scripts/post-publish-verify/run-verify.sh +0 -127
  887. package/scripts/post-publish-verify/verify-install.sh +0 -249
  888. package/scripts/stress-test-orchestrator-integration.mts +0 -1366
  889. package/scripts/stress-test-orchestrator.mjs +0 -584
  890. package/scripts/stress-test-relay-pty.sh +0 -452
  891. package/scripts/test-interactive-terminal.sh +0 -248
  892. package/specs/PRIMITIVES_ROADMAP.md +0 -2154
  893. package/tests/benchmarks/protocol.bench.ts +0 -310
  894. package/turbo.json +0 -37
@@ -0,0 +1,1724 @@
1
+ /**
2
+ * Agent Spawner
3
+ * Handles spawning and releasing worker agents via relay-pty.
4
+ * Workers run headlessly with output capture for logs.
5
+ */
6
+
7
+ import fs from 'node:fs';
8
+ import { execFile } from 'node:child_process';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { sleep } from './utils.js';
12
+ import { getProjectPaths, getAgentOutboxTemplate } from '@agent-relay/config';
13
+ import { resolveCommand } from '@agent-relay/utils/command-resolver';
14
+ import { createTraceableError } from '@agent-relay/utils/error-tracking';
15
+ import { createLogger } from '@agent-relay/utils/logger';
16
+ import { mapModelToCli } from '@agent-relay/utils/model-mapping';
17
+ import { findRelayPtyBinary as findRelayPtyBinaryUtil, getLastSearchPaths } from '@agent-relay/utils/relay-pty-path';
18
+ import { RelayPtyOrchestrator, type RelayPtyOrchestratorConfig } from '@agent-relay/wrapper';
19
+ import type { SummaryEvent, SessionEndEvent } from '@agent-relay/wrapper';
20
+ import { selectShadowCli } from './shadow-cli.js';
21
+
22
+ // Get the directory where this module is located (for binary path resolution)
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
25
+ import { AgentPolicyService, type CloudPolicyFetcher } from '@agent-relay/policy';
26
+ import { buildClaudeArgs, findAgentConfig } from '@agent-relay/config/agent-config';
27
+ import { composeForAgent, type AgentRole } from '@agent-relay/wrapper';
28
+ import { getUserDirectoryService } from '@agent-relay/user-directory';
29
+ import { installMcpConfig } from '@agent-relay/mcp';
30
+ import type {
31
+ SpawnRequest,
32
+ SpawnResult,
33
+ WorkerInfo,
34
+ SpawnWithShadowRequest,
35
+ SpawnWithShadowResult,
36
+ SpeakOnTrigger,
37
+ } from './types.js';
38
+
39
+ // Logger instance for spawner (uses daemon log system instead of console)
40
+ const log = createLogger('spawner');
41
+
42
+ /**
43
+ * CLI command mapping for providers
44
+ * Maps provider names to actual CLI command names
45
+ */
46
+ const CLI_COMMAND_MAP: Record<string, string> = {
47
+ cursor: 'agent', // Cursor CLI installs as 'agent'
48
+ google: 'gemini', // Google provider uses 'gemini' CLI
49
+ // Other providers use their name as the command (claude, codex, etc.)
50
+ };
51
+
52
+ function extractGhTokenFromHosts(content: string): string | null {
53
+ const lines = content.split(/\r?\n/);
54
+ let inGithubSection = false;
55
+ for (const line of lines) {
56
+ const trimmed = line.trim();
57
+ if (!trimmed) {
58
+ continue;
59
+ }
60
+ if (!line.startsWith(' ') && !line.startsWith('\t')) {
61
+ const host = trimmed.replace(/:$/, '');
62
+ inGithubSection = host === 'github.com';
63
+ continue;
64
+ }
65
+ if (!inGithubSection) {
66
+ continue;
67
+ }
68
+ const match = line.match(/^\s*(oauth_token|token):\s*(.+)$/);
69
+ if (!match) {
70
+ continue;
71
+ }
72
+ let token = match[2].split('#')[0].trim();
73
+ token = token.replace(/^['"]|['"]$/g, '');
74
+ if (token) {
75
+ return token;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Cloud persistence handler interface.
83
+ * Implement this to persist agent session data to cloud storage.
84
+ */
85
+ export interface CloudPersistenceHandler {
86
+ onSummary: (agentName: string, event: SummaryEvent) => Promise<void>;
87
+ onSessionEnd: (agentName: string, event: SessionEndEvent) => Promise<void>;
88
+ /** Optional cleanup method for tests and graceful shutdown */
89
+ destroy?: () => void;
90
+ }
91
+
92
+ /** Worker metadata stored in workers.json */
93
+ interface WorkerMeta {
94
+ name: string;
95
+ cli: string;
96
+ task: string;
97
+ /** Optional team name this agent belongs to */
98
+ team?: string;
99
+ /** Optional user ID for per-user credential scoping */
100
+ userId?: string;
101
+ spawnedAt: number;
102
+ pid?: number;
103
+ logFile?: string;
104
+ }
105
+
106
+ /** Stored listener references for cleanup */
107
+ interface ListenerBindings {
108
+ output?: (data: string) => void;
109
+ summary?: (event: SummaryEvent) => void;
110
+ sessionEnd?: (event: SessionEndEvent) => void;
111
+ }
112
+
113
+ /** Type alias for the wrapper - uses RelayPtyOrchestrator (relay-pty Rust binary) */
114
+ type AgentWrapper = RelayPtyOrchestrator;
115
+
116
+ interface ActiveWorker extends WorkerInfo {
117
+ pty: AgentWrapper;
118
+ logFile?: string;
119
+ listeners?: ListenerBindings;
120
+ userId?: string;
121
+ }
122
+
123
+ /** Callback for agent death notifications */
124
+ export type OnAgentDeathCallback = (info: {
125
+ name: string;
126
+ exitCode: number | null;
127
+ agentId?: string;
128
+ resumeInstructions?: string;
129
+ /** Traceable error ID for support lookup */
130
+ errorId?: string;
131
+ }) => void;
132
+
133
+ /**
134
+ * Ensure MCP permissions are pre-configured for the given CLI type.
135
+ * This prevents MCP approval prompts from blocking agent initialization.
136
+ *
137
+ * For Claude Code: Creates/updates .claude/settings.local.json with:
138
+ * - enableAllProjectMcpServers: true (auto-approve project MCP servers)
139
+ * - permissions.allow: ["mcp__agent-relay__*"] (pre-approve all agent-relay MCP tools)
140
+ *
141
+ * For Cursor: Creates/updates .cursor/settings.json with MCP permissions
142
+ * For Gemini: Creates/updates .gemini/settings.json with MCP permissions
143
+ * For Windsurf: Creates/updates .windsurf/settings.json with MCP permissions
144
+ * Other CLIs: May use CLI flags instead of config-based permissions
145
+ *
146
+ * @param projectRoot - The project root directory
147
+ * @param cliType - The CLI type (claude, codex, gemini, cursor, etc.)
148
+ * @param debug - Whether to log debug information
149
+ */
150
+ export function ensureMcpPermissions(projectRoot: string, cliType: string, debug = false): void {
151
+ // Determine settings path based on CLI type
152
+ interface McpPermissionConfig {
153
+ settingsDir: string;
154
+ settingsFile: string;
155
+ permissionKey?: string; // If different from 'permissions.allow'
156
+ enableAllKey?: string; // If supports enableAllProjectMcpServers
157
+ globalSettingsDir?: string; // For global settings that enable project MCP
158
+ }
159
+
160
+ const home = process.env.HOME || '';
161
+ const configMap: Record<string, McpPermissionConfig> = {
162
+ claude: {
163
+ // Use global settings for Claude
164
+ // enableAllProjectMcpServers enables project-local .mcp.json files
165
+ settingsDir: path.join(home, '.claude'),
166
+ settingsFile: 'settings.local.json',
167
+ permissionKey: 'permissions.allow',
168
+ enableAllKey: 'enableAllProjectMcpServers',
169
+ },
170
+ cursor: {
171
+ settingsDir: path.join(projectRoot, '.cursor'),
172
+ settingsFile: 'settings.json',
173
+ permissionKey: 'permissions.allow',
174
+ },
175
+ gemini: {
176
+ settingsDir: path.join(projectRoot, '.gemini'),
177
+ settingsFile: 'settings.json',
178
+ permissionKey: 'permissions.allow',
179
+ },
180
+ windsurf: {
181
+ settingsDir: path.join(projectRoot, '.windsurf'),
182
+ settingsFile: 'settings.json',
183
+ permissionKey: 'permissions.allow',
184
+ },
185
+ // Codex uses TOML config and --dangerously-bypass-approvals-and-sandbox flag
186
+ // OpenCode and Droid may not need config-based permissions
187
+ };
188
+
189
+ // Normalize CLI type
190
+ const normalizedCli = cliType.toLowerCase().replace(/^(claude|codex|gemini|cursor|agent|windsurf).*/, '$1');
191
+
192
+ // Map 'agent' (Cursor CLI) to 'cursor'
193
+ const effectiveCli = normalizedCli === 'agent' ? 'cursor' : normalizedCli;
194
+
195
+ const config = configMap[effectiveCli];
196
+ if (!config) {
197
+ // CLI doesn't use config-based MCP permissions (uses CLI flags instead)
198
+ if (debug) log.debug(`CLI ${cliType} uses flag-based permissions, skipping config setup`);
199
+ return;
200
+ }
201
+
202
+ const settingsPath = path.join(config.settingsDir, config.settingsFile);
203
+
204
+ try {
205
+ // Ensure settings directory exists
206
+ if (!fs.existsSync(config.settingsDir)) {
207
+ fs.mkdirSync(config.settingsDir, { recursive: true });
208
+ }
209
+
210
+ // Read existing settings or start fresh
211
+ let settings: Record<string, unknown> = {};
212
+ if (fs.existsSync(settingsPath)) {
213
+ try {
214
+ const content = fs.readFileSync(settingsPath, 'utf-8');
215
+ settings = JSON.parse(content);
216
+ } catch {
217
+ // Invalid JSON, start fresh
218
+ settings = {};
219
+ }
220
+ }
221
+
222
+ // Set enableAllProjectMcpServers if supported (Claude-specific)
223
+ if (config.enableAllKey && settings[config.enableAllKey] !== true) {
224
+ settings[config.enableAllKey] = true;
225
+ if (debug) log.debug(`Setting ${config.enableAllKey}: true`);
226
+ }
227
+
228
+ // Ensure permissions.allow includes agent-relay MCP
229
+ if (config.permissionKey) {
230
+ // Parse nested key (e.g., 'permissions.allow')
231
+ const keyParts = config.permissionKey.split('.');
232
+ let current: Record<string, unknown> = settings;
233
+
234
+ // Navigate/create nested structure
235
+ for (let i = 0; i < keyParts.length - 1; i++) {
236
+ const key = keyParts[i];
237
+ if (!current[key] || typeof current[key] !== 'object') {
238
+ current[key] = {};
239
+ }
240
+ current = current[key] as Record<string, unknown>;
241
+ }
242
+
243
+ // Ensure allow list exists
244
+ const finalKey = keyParts[keyParts.length - 1];
245
+ if (!Array.isArray(current[finalKey])) {
246
+ current[finalKey] = [];
247
+ }
248
+ const allowList = current[finalKey] as string[];
249
+
250
+ // Add agent-relay MCP permission if not already present
251
+ // Format: mcp__<serverName>__* approves all tools from that server
252
+ const agentRelayPermission = 'mcp__agent-relay__*';
253
+ if (!allowList.includes(agentRelayPermission)) {
254
+ allowList.push(agentRelayPermission);
255
+ if (debug) log.debug(`Added MCP permission: ${agentRelayPermission}`);
256
+ }
257
+ }
258
+
259
+ // Write updated settings
260
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
261
+ if (debug) log.debug(`MCP permissions configured at ${settingsPath}`);
262
+ } catch (err) {
263
+ // Log but don't fail - this is a best-effort optimization
264
+ log.warn('Failed to pre-configure MCP permissions', {
265
+ cli: cliType,
266
+ error: err instanceof Error ? err.message : String(err),
267
+ });
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Get MCP tools reference for spawned agents.
273
+ * Only included when MCP is configured for the project.
274
+ */
275
+ function getMcpToolsReference(): string {
276
+ return [
277
+ '## MCP Tools Available',
278
+ '',
279
+ 'You have access to MCP tools for agent communication (recommended over file protocol):',
280
+ '- `relay_send(to, message)` - Send message to agent/channel',
281
+ '- `relay_spawn(name, cli, task)` - Create worker agent',
282
+ '- `relay_inbox()` - Check your messages',
283
+ '- `relay_who()` - List online agents',
284
+ '- `relay_release(name)` - Stop a worker agent',
285
+ '- `relay_status()` - Check connection status',
286
+ '',
287
+ ].join('\n');
288
+ }
289
+
290
+ /**
291
+ * Get relay protocol instructions for a spawned agent.
292
+ * This provides the agent with the communication protocol it needs to work with the relay.
293
+ *
294
+ * Uses the legacy outbox path (/tmp/relay-outbox/) which is symlinked to workspace paths.
295
+ * This keeps agent instructions simple while supporting workspace isolation.
296
+ *
297
+ * @param agentName - Name of the agent
298
+ * @param options - Configuration options
299
+ * @param options.hasMcp - Whether MCP tools are available (based on .mcp.json existence)
300
+ * @param options.includeWorkflowConventions - Include ACK/DONE workflow conventions (default: false)
301
+ */
302
+ function getRelayInstructions(agentName: string, options: { hasMcp?: boolean; includeWorkflowConventions?: boolean } = {}): string {
303
+ const { hasMcp = false, includeWorkflowConventions = false } = options;
304
+ // Get the outbox path template and replace variable with actual agent name
305
+ const outboxBase = getAgentOutboxTemplate(agentName);
306
+
307
+ const parts: string[] = [
308
+ '# Agent Relay Protocol',
309
+ '',
310
+ `You are agent "${agentName}" connected to Agent Relay for multi-agent coordination.`,
311
+ '',
312
+ ];
313
+
314
+ // Add MCP tools reference if available
315
+ if (hasMcp) {
316
+ parts.push(getMcpToolsReference());
317
+ }
318
+
319
+ parts.push(
320
+ '## Sending Messages',
321
+ '',
322
+ 'Write a file to your outbox, then output the trigger:',
323
+ '',
324
+ '```bash',
325
+ `cat > ${outboxBase}/msg << 'EOF'`,
326
+ 'TO: TargetAgent',
327
+ '',
328
+ 'Your message here.',
329
+ 'EOF',
330
+ '```',
331
+ '',
332
+ 'Then output: `->relay-file:msg`',
333
+ );
334
+
335
+ // Only include ACK/DONE workflow conventions if explicitly requested
336
+ if (includeWorkflowConventions) {
337
+ parts.push(
338
+ '',
339
+ '## Communication Rules',
340
+ '',
341
+ '1. **ACK immediately** - When you receive a task:',
342
+ '```bash',
343
+ `cat > ${outboxBase}/ack << 'EOF'`,
344
+ 'TO: Sender',
345
+ '',
346
+ 'ACK: Brief description of task received',
347
+ 'EOF',
348
+ '```',
349
+ 'Then: `->relay-file:ack`',
350
+ '',
351
+ '2. **Report completion** - When done:',
352
+ '```bash',
353
+ `cat > ${outboxBase}/done << 'EOF'`,
354
+ 'TO: Sender',
355
+ '',
356
+ 'DONE: Brief summary of what was completed',
357
+ 'EOF',
358
+ '```',
359
+ 'Then: `->relay-file:done`',
360
+ );
361
+ }
362
+
363
+ parts.push(
364
+ '',
365
+ '## Message Format',
366
+ '',
367
+ '```',
368
+ 'TO: Target',
369
+ 'THREAD: optional-thread',
370
+ '',
371
+ 'Message body (everything after blank line)',
372
+ '```',
373
+ '',
374
+ '| TO Value | Behavior |',
375
+ '|----------|----------|',
376
+ '| `AgentName` | Direct message |',
377
+ '| `*` | Broadcast to all |',
378
+ '| `#channel` | Channel message |',
379
+ );
380
+
381
+ return parts.join('\n');
382
+ }
383
+
384
+ /**
385
+ * Check if the relay-pty binary is available.
386
+ * Returns the path to the binary if found, null otherwise.
387
+ * Uses shared utility from @agent-relay/utils.
388
+ */
389
+ function findRelayPtyBinary(): string | null {
390
+ return findRelayPtyBinaryUtil(__dirname);
391
+ }
392
+
393
+ /** Cached result of relay-pty binary check */
394
+ let relayPtyBinaryPath: string | null | undefined;
395
+ let relayPtyBinaryChecked = false;
396
+
397
+ /**
398
+ * Check if relay-pty binary is available (cached).
399
+ * Returns true if the binary exists, false otherwise.
400
+ */
401
+ function hasRelayPtyBinary(): boolean {
402
+ if (!relayPtyBinaryChecked) {
403
+ relayPtyBinaryPath = findRelayPtyBinary();
404
+ relayPtyBinaryChecked = true;
405
+ if (process.env.DEBUG_SPAWN === '1') {
406
+ if (relayPtyBinaryPath) {
407
+ log.debug(`relay-pty binary found: ${relayPtyBinaryPath}`);
408
+ } else {
409
+ log.debug('relay-pty binary not found, will use PtyWrapper fallback');
410
+ }
411
+ }
412
+ }
413
+ return relayPtyBinaryPath !== null;
414
+ }
415
+
416
+ /** Options for AgentSpawner constructor */
417
+ export interface AgentSpawnerOptions {
418
+ projectRoot: string;
419
+ /** Explicit socket path for daemon connection (if not provided, derived from projectRoot) */
420
+ socketPath?: string;
421
+ /** Explicit team directory for agent registration files (if not provided, derived from projectRoot) */
422
+ teamDir?: string;
423
+ tmuxSession?: string;
424
+ dashboardPort?: number;
425
+ /**
426
+ * Callback to mark an agent as spawning (before HELLO completes).
427
+ * Messages sent to this agent will be queued for delivery after registration.
428
+ */
429
+ onMarkSpawning?: (agentName: string) => void;
430
+ /**
431
+ * Callback to clear the spawning flag for an agent.
432
+ * Called when spawn fails or is cancelled.
433
+ */
434
+ onClearSpawning?: (agentName: string) => void;
435
+ }
436
+
437
+ export class AgentSpawner {
438
+ private static readonly ONLINE_THRESHOLD_MS = 30_000;
439
+ private activeWorkers: Map<string, ActiveWorker> = new Map();
440
+ private agentsPath: string;
441
+ private registryPath: string;
442
+ private projectRoot: string;
443
+ private socketPath?: string;
444
+ private logsDir: string;
445
+ private workersPath: string;
446
+ private dashboardPort?: number;
447
+ private onAgentDeath?: OnAgentDeathCallback;
448
+ private cloudPersistence?: CloudPersistenceHandler;
449
+ private policyService?: AgentPolicyService;
450
+ private policyEnforcementEnabled = false;
451
+ private onMarkSpawning?: (agentName: string) => void;
452
+ private onClearSpawning?: (agentName: string) => void;
453
+
454
+ constructor(projectRoot: string, _tmuxSession?: string, dashboardPort?: number);
455
+ constructor(options: AgentSpawnerOptions);
456
+ constructor(projectRootOrOptions: string | AgentSpawnerOptions, _tmuxSession?: string, dashboardPort?: number) {
457
+ // Handle both old and new constructor signatures
458
+ const options: AgentSpawnerOptions = typeof projectRootOrOptions === 'string'
459
+ ? { projectRoot: projectRootOrOptions, tmuxSession: _tmuxSession, dashboardPort }
460
+ : projectRootOrOptions;
461
+
462
+ const paths = getProjectPaths(options.projectRoot);
463
+ this.projectRoot = paths.projectRoot;
464
+ // Use explicit teamDir if provided (ensures spawner checks same files as daemon)
465
+ // This is critical in cloud workspaces where detectWorkspacePath may return different paths
466
+ const effectiveTeamDir = options.teamDir ?? paths.teamDir;
467
+ // Use connected-agents.json (live socket connections) instead of agents.json (historical registry)
468
+ // This ensures spawned agents have actual daemon connections for channel message delivery
469
+ this.agentsPath = path.join(effectiveTeamDir, 'connected-agents.json');
470
+ this.registryPath = path.join(effectiveTeamDir, 'agents.json');
471
+
472
+ // Debug: log path configuration
473
+ log.info(`AgentSpawner paths: projectRoot=${this.projectRoot} teamDir=${effectiveTeamDir} (explicit=${!!options.teamDir}) agentsPath=${this.agentsPath}`);
474
+ // Use explicit socketPath if provided (ensures spawned agents connect to same daemon)
475
+ // Otherwise derive from project paths
476
+ this.socketPath = options.socketPath ?? paths.socketPath;
477
+ this.logsDir = path.join(effectiveTeamDir, 'worker-logs');
478
+ this.workersPath = path.join(effectiveTeamDir, 'workers.json');
479
+ this.dashboardPort = options.dashboardPort;
480
+
481
+ // Store spawn tracking callbacks
482
+ this.onMarkSpawning = options.onMarkSpawning;
483
+ this.onClearSpawning = options.onClearSpawning;
484
+
485
+ // Ensure logs directory exists
486
+ fs.mkdirSync(this.logsDir, { recursive: true });
487
+
488
+ // Initialize policy service if enforcement is enabled
489
+ if (process.env.AGENT_POLICY_ENFORCEMENT === '1') {
490
+ this.policyEnforcementEnabled = true;
491
+ this.policyService = new AgentPolicyService({
492
+ projectRoot: this.projectRoot,
493
+ workspaceId: process.env.WORKSPACE_ID,
494
+ strictMode: process.env.AGENT_POLICY_STRICT === '1',
495
+ });
496
+ log.info('Policy enforcement enabled');
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Set cloud policy fetcher for workspace-level policies
502
+ */
503
+ setCloudPolicyFetcher(fetcher: CloudPolicyFetcher): void {
504
+ if (this.policyService) {
505
+ // Recreate policy service with cloud fetcher
506
+ this.policyService = new AgentPolicyService({
507
+ projectRoot: this.projectRoot,
508
+ workspaceId: process.env.WORKSPACE_ID,
509
+ cloudFetcher: fetcher,
510
+ strictMode: process.env.AGENT_POLICY_STRICT === '1',
511
+ });
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Get the policy service (for external access to policy checks)
517
+ */
518
+ getPolicyService(): AgentPolicyService | undefined {
519
+ return this.policyService;
520
+ }
521
+
522
+ private async fetchGhTokenFromCloud(): Promise<string | null> {
523
+ const cloudApiUrl = process.env.CLOUD_API_URL || process.env.AGENT_RELAY_CLOUD_URL;
524
+ const workspaceId = process.env.WORKSPACE_ID;
525
+ const workspaceToken = process.env.WORKSPACE_TOKEN;
526
+
527
+ if (!cloudApiUrl || !workspaceId || !workspaceToken) {
528
+ return null;
529
+ }
530
+
531
+ const normalizedUrl = cloudApiUrl.replace(/\/$/, '');
532
+ const url = `${normalizedUrl}/api/git/token?workspaceId=${encodeURIComponent(workspaceId)}`;
533
+
534
+ try {
535
+ // Use AbortController for timeout (5 seconds - don't block spawning)
536
+ const controller = new AbortController();
537
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
538
+
539
+ const response = await fetch(url, {
540
+ headers: {
541
+ Authorization: `Bearer ${workspaceToken}`,
542
+ },
543
+ signal: controller.signal,
544
+ });
545
+
546
+ clearTimeout(timeoutId);
547
+
548
+ if (!response.ok) {
549
+ log.warn(`Failed to fetch GH token from cloud: ${response.status} ${response.statusText}`);
550
+ return null;
551
+ }
552
+
553
+ const data = await response.json() as { userToken?: string | null; token?: string | null };
554
+ return data.userToken || data.token || null;
555
+ } catch (err) {
556
+ // Don't log timeout errors loudly - this is expected when cloud is unreachable
557
+ const message = err instanceof Error ? err.message : String(err);
558
+ if (message.includes('abort')) {
559
+ log.info('Cloud API timeout (5s) - using local auth');
560
+ } else {
561
+ log.warn('Failed to fetch GH token from cloud', { error: message });
562
+ }
563
+ return null;
564
+ }
565
+ }
566
+
567
+ private resolveGhTokenFromHostsFile(homeDir?: string): string | null {
568
+ const resolvedHome = homeDir || process.env.HOME;
569
+ const configHome = process.env.XDG_CONFIG_HOME || (resolvedHome ? path.join(resolvedHome, '.config') : undefined);
570
+ const candidates = new Set<string>();
571
+ if (configHome) {
572
+ candidates.add(path.join(configHome, 'gh', 'hosts.yml'));
573
+ }
574
+ if (resolvedHome) {
575
+ candidates.add(path.join(resolvedHome, '.config', 'gh', 'hosts.yml'));
576
+ }
577
+
578
+ for (const hostPath of candidates) {
579
+ if (!hostPath || !fs.existsSync(hostPath)) {
580
+ continue;
581
+ }
582
+ try {
583
+ const content = fs.readFileSync(hostPath, 'utf8');
584
+ const token = extractGhTokenFromHosts(content);
585
+ if (token) {
586
+ return token;
587
+ }
588
+ } catch {
589
+ continue;
590
+ }
591
+ }
592
+
593
+ return null;
594
+ }
595
+
596
+ private async resolveGhTokenFromGhCli(): Promise<string | null> {
597
+ // Check common gh CLI installation paths across platforms
598
+ const ghPathCandidates = [
599
+ '/usr/bin/gh', // Linux package managers
600
+ '/usr/local/bin/gh', // Homebrew (Intel Mac), manual install
601
+ '/opt/homebrew/bin/gh', // Homebrew (Apple Silicon Mac)
602
+ '/home/linuxbrew/.linuxbrew/bin/gh', // Linuxbrew
603
+ ];
604
+
605
+ const ghPath = ghPathCandidates.find((p) => fs.existsSync(p));
606
+ if (!ghPath) {
607
+ return null;
608
+ }
609
+
610
+ return await new Promise((resolve) => {
611
+ execFile(ghPath, ['auth', 'token', '--hostname', 'github.com'], { timeout: 5000 }, (err, stdout) => {
612
+ if (err) {
613
+ resolve(null);
614
+ return;
615
+ }
616
+ const token = stdout.trim();
617
+ resolve(token || null);
618
+ });
619
+ });
620
+ }
621
+
622
+ /**
623
+ * Resolve GitHub token using multiple fallback sources.
624
+ *
625
+ * Fallback order (same as git-credential-relay for consistency):
626
+ * 1. Environment - GH_TOKEN or GITHUB_TOKEN (fastest, set by entrypoint)
627
+ * 2. hosts.yml - gh CLI config file (~/.config/gh/hosts.yml)
628
+ * 3. gh CLI - execute `gh auth token` command
629
+ * 4. Cloud API - workspace-scoped token from Nango (requires network)
630
+ *
631
+ * Environment is checked first because:
632
+ * - It's the fastest (no I/O or network)
633
+ * - The entrypoint pre-fetches and caches GH_TOKEN at startup
634
+ * - This avoids delays when cloud API is slow/unreachable
635
+ */
636
+ private async resolveGhToken(homeDir?: string): Promise<string | null> {
637
+ // 1. Check environment variables first (fastest - set by entrypoint at startup)
638
+ const envToken = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
639
+ if (envToken) {
640
+ return envToken;
641
+ }
642
+
643
+ // 2. Parse gh CLI hosts.yml config file
644
+ const hostsToken = this.resolveGhTokenFromHostsFile(homeDir);
645
+ if (hostsToken) {
646
+ return hostsToken;
647
+ }
648
+
649
+ // 3. Execute gh CLI if available
650
+ const cliToken = await this.resolveGhTokenFromGhCli();
651
+ if (cliToken) {
652
+ return cliToken;
653
+ }
654
+
655
+ // 4. Try cloud API as last resort (may be slow or unreachable)
656
+ return await this.fetchGhTokenFromCloud();
657
+ }
658
+
659
+ /**
660
+ * Set the dashboard port (for nested spawn API calls).
661
+ * Called after the dashboard server starts and we know the actual port.
662
+ */
663
+ setDashboardPort(port: number): void {
664
+ log.info(`Dashboard port set to ${port} - nested spawns now enabled`);
665
+ this.dashboardPort = port;
666
+ }
667
+
668
+ /**
669
+ * Set callback for agent death notifications.
670
+ * Called when an agent exits unexpectedly (non-zero exit code).
671
+ */
672
+ setOnAgentDeath(callback: OnAgentDeathCallback): void {
673
+ this.onAgentDeath = callback;
674
+ }
675
+
676
+ /**
677
+ * Set cloud persistence handler for forwarding RelayPtyOrchestrator events.
678
+ * When set, 'summary' and 'session-end' events from spawned agents
679
+ * are forwarded to the handler for cloud persistence (PostgreSQL/Redis).
680
+ *
681
+ * Note: Enable via RELAY_CLOUD_ENABLED=true environment variable.
682
+ */
683
+ setCloudPersistence(handler: CloudPersistenceHandler): void {
684
+ this.cloudPersistence = handler;
685
+ log.info('Cloud persistence handler set');
686
+ }
687
+
688
+ /**
689
+ * Bind cloud persistence event handlers to a RelayPtyOrchestrator.
690
+ * Returns the listener references for cleanup.
691
+ */
692
+ private bindCloudPersistenceEvents(name: string, pty: AgentWrapper): Partial<ListenerBindings> {
693
+ if (!this.cloudPersistence) return {};
694
+
695
+ const summaryListener = async (event: SummaryEvent) => {
696
+ try {
697
+ await this.cloudPersistence!.onSummary(name, event);
698
+ } catch (err) {
699
+ log.error(`Cloud persistence summary error for ${name}`, { error: err instanceof Error ? err.message : String(err) });
700
+ }
701
+ };
702
+
703
+ const sessionEndListener = async (event: SessionEndEvent) => {
704
+ try {
705
+ await this.cloudPersistence!.onSessionEnd(name, event);
706
+ } catch (err) {
707
+ log.error(`Cloud persistence session-end error for ${name}`, { error: err instanceof Error ? err.message : String(err) });
708
+ }
709
+ };
710
+
711
+ pty.on('summary', summaryListener);
712
+ pty.on('session-end', sessionEndListener);
713
+
714
+ return { summary: summaryListener, sessionEnd: sessionEndListener };
715
+ }
716
+
717
+ /**
718
+ * Unbind all tracked listeners from a RelayPtyOrchestrator.
719
+ */
720
+ private unbindListeners(pty: AgentWrapper, listeners?: ListenerBindings): void {
721
+ if (!listeners) return;
722
+
723
+ if (listeners.output) {
724
+ pty.off('output', listeners.output);
725
+ }
726
+ if (listeners.summary) {
727
+ pty.off('summary', listeners.summary);
728
+ }
729
+ if (listeners.sessionEnd) {
730
+ pty.off('session-end', listeners.sessionEnd);
731
+ }
732
+ }
733
+
734
+ /**
735
+ * Spawn a new worker agent using relay-pty
736
+ */
737
+ async spawn(request: SpawnRequest): Promise<SpawnResult> {
738
+ const { name, cli, task, team, spawnerName, userId, includeWorkflowConventions, interactive } = request;
739
+ const debug = process.env.DEBUG_SPAWN === '1';
740
+
741
+ // Validate agent name to prevent path traversal attacks
742
+ if (name.includes('..') || name.includes('/') || name.includes('\\')) {
743
+ return {
744
+ success: false,
745
+ name,
746
+ error: `Invalid agent name: "${name}" contains path traversal characters`,
747
+ };
748
+ }
749
+
750
+ // Check if worker already exists in this spawner
751
+ if (this.activeWorkers.has(name)) {
752
+ return {
753
+ success: false,
754
+ name,
755
+ error: `Agent "${name}" is already running. Use a different name or release the existing agent first.`,
756
+ };
757
+ }
758
+
759
+ // Check if agent is already connected to daemon (prevents duplicate connection storms)
760
+ if (this.isAgentConnected(name)) {
761
+ return {
762
+ success: false,
763
+ name,
764
+ error: `Agent "${name}" is already connected to the daemon. Use a different name or wait for the existing agent to disconnect.`,
765
+ };
766
+ }
767
+
768
+ // Enforce agent limit based on plan (MAX_AGENTS is set by provisioner based on plan)
769
+ const maxAgents = parseInt(process.env.MAX_AGENTS ?? '', 10) || 10_000;
770
+ const currentAgentCount = this.activeWorkers.size;
771
+ if (currentAgentCount >= maxAgents) {
772
+ log.warn(`Agent limit reached: ${currentAgentCount}/${maxAgents}`);
773
+ return {
774
+ success: false,
775
+ name,
776
+ error: `Agent limit reached (${currentAgentCount}/${maxAgents}). Upgrade your plan for more agents.`,
777
+ };
778
+ }
779
+
780
+ // Policy enforcement: check if the spawner is authorized to spawn this agent
781
+ if (this.policyEnforcementEnabled && this.policyService && spawnerName) {
782
+ const decision = await this.policyService.canSpawn(spawnerName, name, cli);
783
+ if (!decision.allowed) {
784
+ log.warn(`Policy blocked spawn: ${spawnerName} -> ${name}: ${decision.reason}`);
785
+ return {
786
+ success: false,
787
+ name,
788
+ error: `Policy denied: ${decision.reason}`,
789
+ policyDecision: decision,
790
+ };
791
+ }
792
+ if (debug) {
793
+ log.debug(`Policy allowed spawn: ${spawnerName} -> ${name} (source: ${decision.policySource})`);
794
+ }
795
+ }
796
+
797
+ try {
798
+ // Parse CLI command and apply mapping (e.g., cursor -> agent)
799
+ const cliParts = cli.split(' ');
800
+ const rawCommandName = cliParts[0];
801
+ const commandName = CLI_COMMAND_MAP[rawCommandName] || rawCommandName;
802
+ const args = cliParts.slice(1);
803
+
804
+ if (commandName !== rawCommandName && debug) {
805
+ log.debug(`Mapped CLI '${rawCommandName}' -> '${commandName}'`);
806
+ }
807
+
808
+ // Resolve full path to avoid posix_spawnp failures
809
+ const command = resolveCommand(commandName);
810
+ if (debug) log.debug(`Resolved '${commandName}' -> '${command}'`);
811
+ if (command === commandName && !commandName.startsWith('/')) {
812
+ // Command wasn't resolved - it might not exist
813
+ log.warn(`Could not resolve path for '${commandName}', spawn may fail`);
814
+ }
815
+
816
+ // Pre-configure MCP permissions for all supported CLIs
817
+ // This creates/updates CLI-specific settings files with agent-relay permissions
818
+ ensureMcpPermissions(this.projectRoot, commandName, debug);
819
+
820
+ // Add auto-accept flags for non-interactive agents (normal spawns, not setup terminals)
821
+ // When interactive=true (setup flows), we SKIP these flags so users can respond to prompts
822
+ const isClaudeCli = commandName.startsWith('claude');
823
+ const isCursorCli = commandName === 'agent' || rawCommandName === 'cursor';
824
+
825
+ if (!interactive) {
826
+ // Add --dangerously-skip-permissions for Claude agents
827
+ if (isClaudeCli && !args.includes('--dangerously-skip-permissions')) {
828
+ args.push('--dangerously-skip-permissions');
829
+ }
830
+
831
+ // Add --force for Cursor agents (auto-approve tool usage in non-interactive mode)
832
+ if (isCursorCli && !args.includes('--force')) {
833
+ args.push('--force');
834
+ }
835
+ } else {
836
+ // Interactive mode: log that we're skipping auto-accept flags
837
+ if (debug) log.debug(`Interactive mode: skipping auto-accept flags for ${name}`);
838
+ }
839
+
840
+ // Apply agent config (model, --agent flag) from .claude/agents/ if available
841
+ // This ensures spawned agents respect their profile settings
842
+ if (isClaudeCli) {
843
+ // Get agent config for model tracking and CLI variant selection
844
+ const agentConfig = findAgentConfig(name, this.projectRoot);
845
+ const modelFromProfile = agentConfig?.model?.trim();
846
+
847
+ // Map model to CLI variant (e.g., 'opus' -> 'claude:opus')
848
+ // This allows agent profiles to specify model preferences
849
+ const cliVariant = modelFromProfile
850
+ ? mapModelToCli(modelFromProfile)
851
+ : mapModelToCli(); // defaults to claude:sonnet
852
+
853
+ // Extract effective model name for logging
854
+ const effectiveModel = modelFromProfile || 'opus';
855
+
856
+ const configuredArgs = buildClaudeArgs(name, args, this.projectRoot);
857
+ // Replace args with configured version (includes --model and --agent if found)
858
+ args.length = 0;
859
+ args.push(...configuredArgs);
860
+
861
+ // Cost tracking: log which model is being used
862
+ log.info(`Agent ${name}: model=${effectiveModel}, cli=${cli}, variant=${cliVariant}`);
863
+ if (debug) log.debug(`Applied agent config for ${name}: ${args.join(' ')}`);
864
+ }
865
+
866
+ // Add auto-accept flags for Codex and Gemini (only in non-interactive mode)
867
+ const isCodexCli = commandName.startsWith('codex');
868
+ const isGeminiCli = commandName === 'gemini';
869
+
870
+ if (!interactive) {
871
+ // Add --dangerously-bypass-approvals-and-sandbox for Codex agents
872
+ if (isCodexCli && !args.includes('--dangerously-bypass-approvals-and-sandbox')) {
873
+ args.push('--dangerously-bypass-approvals-and-sandbox');
874
+ }
875
+
876
+ // Add --yolo for Gemini agents (auto-accept all prompts)
877
+ if (isGeminiCli && !args.includes('--yolo')) {
878
+ args.push('--yolo');
879
+ }
880
+ }
881
+
882
+ // Auto-install MCP config if not present (project-local)
883
+ // Uses .mcp.json in the project root - doesn't modify global settings
884
+ // Feature gated: set RELAY_MCP_AUTO_INSTALL=1 to enable
885
+ const projectMcpConfigPath = path.join(this.projectRoot, '.mcp.json');
886
+ const mcpSocketPath = path.join(this.projectRoot, '.agent-relay', 'relay.sock');
887
+ const hasMcpConfig = fs.existsSync(projectMcpConfigPath);
888
+ const mcpAutoInstallEnabled = process.env.RELAY_MCP_AUTO_INSTALL === '1';
889
+
890
+ if (!hasMcpConfig && mcpAutoInstallEnabled) {
891
+ try {
892
+ const result = installMcpConfig(projectMcpConfigPath, {
893
+ configKey: 'mcpServers',
894
+ // Set RELAY_SOCKET so MCP server finds daemon regardless of CWD
895
+ env: { RELAY_SOCKET: mcpSocketPath },
896
+ });
897
+ if (result.success) {
898
+ if (debug) log.debug(`Auto-installed MCP config at ${projectMcpConfigPath}`);
899
+ } else {
900
+ log.warn(`Failed to auto-install MCP config: ${result.error}`);
901
+ }
902
+ } catch (err) {
903
+ log.warn('Failed to auto-install MCP config', {
904
+ error: err instanceof Error ? err.message : String(err),
905
+ });
906
+ }
907
+ }
908
+
909
+ // Check if MCP tools are available
910
+ // Must verify BOTH conditions (matching inbox hook behavior from commit 18bab59):
911
+ // 1. MCP config exists (user or project scope)
912
+ // 2. Relay daemon socket is accessible (daemon must be running)
913
+ // Without both, MCP context would be shown but tools wouldn't work
914
+ // Use the actual socket path from config (project-local .agent-relay/relay.sock)
915
+ // or fall back to environment variable
916
+ const relaySocket = this.socketPath || process.env.RELAY_SOCKET || path.join(this.projectRoot, '.agent-relay', 'relay.sock');
917
+ let hasMcp = false;
918
+ // Check either user-scope or project-scope MCP config
919
+ // hasMcpConfig was already computed above
920
+ if (hasMcpConfig) {
921
+ try {
922
+ hasMcp = fs.statSync(relaySocket).isSocket();
923
+ } catch {
924
+ // Socket doesn't exist or isn't accessible - daemon not running
925
+ hasMcp = false;
926
+ }
927
+ }
928
+ if (debug && hasMcp) log.debug(`MCP tools available for ${name} (MCP config found, socket ${relaySocket})`);
929
+
930
+ // Inject relay protocol instructions via CLI-specific system prompt
931
+ let relayInstructions = getRelayInstructions(name, { hasMcp, includeWorkflowConventions });
932
+
933
+ // Compose role-specific prompts if agent has a role defined in .claude/agents/
934
+ const agentConfigForRole = isClaudeCli ? findAgentConfig(name, this.projectRoot) : null;
935
+ if (agentConfigForRole?.role) {
936
+ const validRoles: AgentRole[] = ['planner', 'worker', 'reviewer', 'lead', 'shadow'];
937
+ const role = agentConfigForRole.role.toLowerCase() as AgentRole;
938
+ if (validRoles.includes(role)) {
939
+ try {
940
+ const composed = await composeForAgent(
941
+ { name, role },
942
+ this.projectRoot,
943
+ { taskDescription: task }
944
+ );
945
+ if (composed.content) {
946
+ relayInstructions = `${composed.content}\n\n---\n\n${relayInstructions}`;
947
+ if (debug) log.debug(`Composed role prompt for ${name} (role: ${role})`);
948
+ }
949
+ } catch (err: any) {
950
+ log.warn(`Failed to compose role prompt for ${name}: ${err.message}`);
951
+ }
952
+ }
953
+ }
954
+
955
+ if (isClaudeCli && !args.includes('--append-system-prompt')) {
956
+ args.push('--append-system-prompt', relayInstructions);
957
+ } else if (isCodexCli && !args.some(a => a.includes('developer_instructions'))) {
958
+ args.push('--config', `developer_instructions=${relayInstructions}`);
959
+ }
960
+
961
+ // Codex requires an initial prompt in TTY mode (unlike Claude which waits for input)
962
+ // Pass the task as the initial prompt, or a generic "ready" message if no task
963
+ if (isCodexCli) {
964
+ const initialPrompt = task || 'You are ready. Wait for messages from the relay system.';
965
+ args.push(initialPrompt);
966
+ }
967
+
968
+ // Cursor requires 'login' subcommand for interactive auth flows (setup terminals)
969
+ // Unlike Claude/Codex which auto-start auth, Cursor needs explicit 'agent login'
970
+ if (isCursorCli && interactive && !task) {
971
+ args.unshift('login');
972
+ }
973
+
974
+ if (debug) log.debug(`Spawning ${name} with: ${command} ${args.join(' ')}`);
975
+
976
+ // Create PtyWrapper config
977
+ // Use dashboardPort for nested spawns (API-based, works in non-TTY contexts)
978
+ // Fall back to callbacks only if no dashboardPort is not set
979
+ // Note: Spawned agents CAN spawn sub-workers intentionally - the parser is strict enough
980
+ // to avoid accidental spawns from documentation text (requires line start, PascalCase, known CLI)
981
+ // Use request.cwd if specified, otherwise use projectRoot
982
+ // Validate cwd to prevent path traversal attacks
983
+ let agentCwd: string;
984
+ if (request.cwd && typeof request.cwd === 'string') {
985
+ // Resolve cwd relative to project root and ensure it stays within that root
986
+ const resolvedCwd = path.resolve(this.projectRoot, request.cwd);
987
+ const normalizedProjectRoot = path.resolve(this.projectRoot);
988
+ const projectRootWithSep = normalizedProjectRoot.endsWith(path.sep)
989
+ ? normalizedProjectRoot
990
+ : normalizedProjectRoot + path.sep;
991
+
992
+ // Ensure the resolved cwd is within the project root to prevent traversal
993
+ if (resolvedCwd !== normalizedProjectRoot && !resolvedCwd.startsWith(projectRootWithSep)) {
994
+ return {
995
+ success: false,
996
+ name,
997
+ error: `Invalid cwd: "${request.cwd}" must be within the project root`,
998
+ };
999
+ }
1000
+ agentCwd = resolvedCwd;
1001
+ } else {
1002
+ agentCwd = this.projectRoot;
1003
+ }
1004
+
1005
+ // Log whether nested spawning will be enabled for this agent
1006
+ log.info(`Spawning ${name}: dashboardPort=${this.dashboardPort || 'none'} (${this.dashboardPort ? 'nested spawns enabled' : 'nested spawns disabled'})`);
1007
+
1008
+ let userEnv: Record<string, string> | undefined;
1009
+ if (userId) {
1010
+ try {
1011
+ const userDirService = getUserDirectoryService();
1012
+ userEnv = userDirService.getUserEnvironment(userId);
1013
+ } catch (err) {
1014
+ log.warn('Failed to resolve user environment, using default', {
1015
+ userId,
1016
+ error: err instanceof Error ? err.message : String(err),
1017
+ });
1018
+ }
1019
+ }
1020
+
1021
+ const mergedUserEnv = { ...(userEnv ?? {}) };
1022
+ if (!mergedUserEnv.GH_TOKEN) {
1023
+ const ghToken = await this.resolveGhToken(userEnv?.HOME);
1024
+ if (ghToken) {
1025
+ mergedUserEnv.GH_TOKEN = ghToken;
1026
+ }
1027
+ }
1028
+ if (Object.keys(mergedUserEnv).length > 0) {
1029
+ userEnv = mergedUserEnv;
1030
+ }
1031
+
1032
+ if (debug) log.debug(`Socket path for ${name}: ${this.socketPath ?? 'undefined'}`);
1033
+
1034
+ // Require relay-pty binary
1035
+ if (!hasRelayPtyBinary()) {
1036
+ const checkedPaths = getLastSearchPaths();
1037
+ const tracedError = createTraceableError('relay-pty binary not found', {
1038
+ agentName: name,
1039
+ cli,
1040
+ callerDir: __dirname,
1041
+ checkedPaths: checkedPaths.slice(0, 5), // First 5 paths for brevity
1042
+ totalPathsChecked: checkedPaths.length,
1043
+ hint: 'Set RELAY_PTY_BINARY env var to override, or reinstall: npm install agent-relay',
1044
+ });
1045
+ log.error(tracedError.logMessage);
1046
+ if (debug) {
1047
+ log.debug('All paths checked for relay-pty binary:');
1048
+ checkedPaths.forEach((p, i) => log.debug(` ${i + 1}. ${p}`));
1049
+ }
1050
+ return {
1051
+ success: false,
1052
+ name,
1053
+ error: tracedError.userMessage,
1054
+ errorId: tracedError.errorId,
1055
+ };
1056
+ }
1057
+
1058
+ // Common exit handler for both wrapper types
1059
+ const onExitHandler = (code: number) => {
1060
+ if (debug) log.debug(`Worker ${name} exited with code ${code}`);
1061
+
1062
+ // Get the agentId and clean up listeners before removing from active workers
1063
+ const worker = this.activeWorkers.get(name);
1064
+ const agentId = worker?.pty?.getAgentId?.();
1065
+ if (worker?.listeners) {
1066
+ this.unbindListeners(worker.pty, worker.listeners);
1067
+ }
1068
+
1069
+ this.activeWorkers.delete(name);
1070
+ try {
1071
+ this.saveWorkersMetadata();
1072
+ } catch (err) {
1073
+ log.error('Failed to save metadata on exit', { error: err instanceof Error ? err.message : String(err) });
1074
+ }
1075
+
1076
+ // Notify if agent died unexpectedly (non-zero exit)
1077
+ if (code !== 0 && code !== null && this.onAgentDeath) {
1078
+ const crashError = createTraceableError('Agent crashed unexpectedly', {
1079
+ agentName: name,
1080
+ exitCode: code,
1081
+ cli,
1082
+ agentId,
1083
+ });
1084
+ log.error(crashError.logMessage);
1085
+ this.onAgentDeath({
1086
+ name,
1087
+ exitCode: code,
1088
+ agentId,
1089
+ errorId: crashError.errorId,
1090
+ resumeInstructions: agentId
1091
+ ? `To resume this agent's work, use: --resume ${agentId}`
1092
+ : undefined,
1093
+ });
1094
+ }
1095
+ };
1096
+
1097
+ // Common spawn/release handlers
1098
+ const onSpawnHandler = this.dashboardPort ? undefined : async (workerName: string, workerCli: string, workerTask: string) => {
1099
+ if (debug) log.debug(`Nested spawn: ${workerName}`);
1100
+ await this.spawn({
1101
+ name: workerName,
1102
+ cli: workerCli,
1103
+ task: workerTask,
1104
+ userId,
1105
+ });
1106
+ };
1107
+
1108
+ const onReleaseHandler = this.dashboardPort ? undefined : async (workerName: string) => {
1109
+ if (debug) log.debug(`Release request: ${workerName}`);
1110
+ await this.release(workerName);
1111
+ };
1112
+
1113
+ // Create RelayPtyOrchestrator (relay-pty Rust binary)
1114
+ const ptyConfig: RelayPtyOrchestratorConfig = {
1115
+ name,
1116
+ command,
1117
+ args,
1118
+ socketPath: this.socketPath,
1119
+ cwd: agentCwd,
1120
+ dashboardPort: this.dashboardPort,
1121
+ env: {
1122
+ ...userEnv,
1123
+ ...(spawnerName ? { AGENT_RELAY_SPAWNER: spawnerName } : {}),
1124
+ // Pass socket path for MCP server discovery
1125
+ // This allows the MCP server (started by Claude Code) to connect to the daemon
1126
+ ...(relaySocket ? { RELAY_SOCKET: relaySocket } : {}),
1127
+ // Pass agent name so MCP server knows its identity
1128
+ RELAY_AGENT_NAME: name,
1129
+ },
1130
+ streamLogs: true,
1131
+ shadowOf: request.shadowOf,
1132
+ shadowSpeakOn: request.shadowSpeakOn,
1133
+ skipContinuity: true,
1134
+ onSpawn: onSpawnHandler,
1135
+ onRelease: onReleaseHandler,
1136
+ onExit: onExitHandler,
1137
+ headless: true, // Force headless mode for spawned agents to enable task injection via stdin
1138
+ // In cloud environments (WORKSPACE_ID set), limit CPU per agent to prevent
1139
+ // one agent (e.g., running npm install) from starving others
1140
+ // Default: 100% of one core per agent. Set AGENT_CPU_LIMIT to override.
1141
+ cpuLimitPercent: process.env.WORKSPACE_ID
1142
+ ? parseInt(process.env.AGENT_CPU_LIMIT || '100', 10)
1143
+ : undefined,
1144
+ };
1145
+ const pty = new RelayPtyOrchestrator(ptyConfig);
1146
+ if (debug) log.debug(`Using RelayPtyOrchestrator for ${name}`);
1147
+
1148
+ // Track listener references for proper cleanup
1149
+ const listeners: ListenerBindings = {};
1150
+
1151
+ // Hook up output events for live log streaming
1152
+ const outputListener = (data: string) => {
1153
+ // Broadcast to any connected WebSocket clients via global function
1154
+ const broadcast = (global as any).__broadcastLogOutput;
1155
+ if (broadcast) {
1156
+ broadcast(name, data);
1157
+ }
1158
+ };
1159
+ pty.on('output', outputListener);
1160
+ listeners.output = outputListener;
1161
+
1162
+ // Bind cloud persistence events (if enabled) and store references
1163
+ const cloudListeners = this.bindCloudPersistenceEvents(name, pty);
1164
+ if (cloudListeners.summary) listeners.summary = cloudListeners.summary;
1165
+ if (cloudListeners.sessionEnd) listeners.sessionEnd = cloudListeners.sessionEnd;
1166
+
1167
+ // Mark agent as spawning BEFORE starting PTY
1168
+ // This allows messages sent to this agent to be queued until HELLO completes
1169
+ if (this.onMarkSpawning) {
1170
+ this.onMarkSpawning(name);
1171
+ if (debug) log.debug(`Marked ${name} as spawning`);
1172
+ }
1173
+
1174
+ await pty.start();
1175
+
1176
+ if (debug) log.debug(`PTY started, pid: ${pty.pid}`);
1177
+
1178
+ // Cursor CLI shows "Press any key to log in..." prompt that blocks all progress
1179
+ // Send a keystroke after startup to bypass this initial prompt
1180
+ // Only do this for interactive/setup flows where auth is needed
1181
+ // For normal spawns (already authenticated), this prompt won't appear
1182
+ if (isCursorCli && interactive) {
1183
+ // Wait a moment for CLI to initialize and show the prompt
1184
+ await sleep(1500);
1185
+ if (debug) log.debug(`Sending initial keystroke for Cursor setup to bypass "Press any key" prompt`);
1186
+ try {
1187
+ // Send Enter key to proceed past the initial prompt
1188
+ await pty.write('\r');
1189
+ } catch (err) {
1190
+ log.warn(`Failed to send initial keystroke for Cursor: ${err}`);
1191
+ }
1192
+ }
1193
+
1194
+ // Wait for the agent to register with the daemon
1195
+ const registered = await this.waitForAgentRegistration(name, 30_000, 500);
1196
+ if (!registered) {
1197
+ const tracedError = createTraceableError('Agent registration timeout', {
1198
+ agentName: name,
1199
+ cli,
1200
+ pid: pty.pid,
1201
+ timeoutMs: 30_000,
1202
+ });
1203
+ log.error(tracedError.logMessage);
1204
+ // Clear spawning flag since spawn failed
1205
+ if (this.onClearSpawning) {
1206
+ this.onClearSpawning(name);
1207
+ }
1208
+ await pty.kill();
1209
+ return {
1210
+ success: false,
1211
+ name,
1212
+ error: tracedError.userMessage,
1213
+ errorId: tracedError.errorId,
1214
+ };
1215
+ }
1216
+
1217
+ // Send task to the newly spawned agent if provided
1218
+ // We do this AFTER registration AND after the orchestrator is FULLY ready for messages
1219
+ // This includes: CLI started, CLI idle, socket connected, readyForMessages flag set
1220
+ if (task && task.trim()) {
1221
+ const maxRetries = 3;
1222
+ const retryDelayMs = 2000;
1223
+ let taskSent = false;
1224
+
1225
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1226
+ try {
1227
+ // Wait for full orchestrator readiness (CLI + socket + internal flags)
1228
+ if ('waitUntilReadyForMessages' in pty) {
1229
+ const orchestrator = pty as RelayPtyOrchestrator;
1230
+ const ready = await orchestrator.waitUntilReadyForMessages(20000, 100);
1231
+ if (!ready) {
1232
+ // Log retry attempts at DEBUG level to avoid terminal noise
1233
+ log.debug(`Attempt ${attempt}/${maxRetries}: ${name} not ready for messages within timeout`);
1234
+ if (attempt < maxRetries) {
1235
+ await sleep(retryDelayMs);
1236
+ continue;
1237
+ }
1238
+ log.error(`${name} failed to become ready after ${maxRetries} attempts - task may be lost`);
1239
+ break;
1240
+ }
1241
+ } else if ('waitUntilCliReady' in pty) {
1242
+ // Fallback for older wrapper types
1243
+ await (pty as RelayPtyOrchestrator).waitUntilCliReady(15000, 100);
1244
+ }
1245
+
1246
+ // Inject task via socket (with verification and retries)
1247
+ const success = await pty.injectTask(task, spawnerName || 'spawner');
1248
+ if (success) {
1249
+ taskSent = true;
1250
+ if (debug) log.debug(`Task injected to ${name} (attempt ${attempt})`);
1251
+ break;
1252
+ } else {
1253
+ throw new Error('Task injection returned false');
1254
+ }
1255
+ } catch (err: any) {
1256
+ // Log retry attempts at DEBUG level to avoid terminal noise
1257
+ // Only the final summary (if all attempts fail) is logged at ERROR level
1258
+ log.debug(`Attempt ${attempt}/${maxRetries}: Error injecting task for ${name}: ${err.message}`);
1259
+ if (attempt < maxRetries) {
1260
+ await sleep(retryDelayMs);
1261
+ }
1262
+ }
1263
+ }
1264
+
1265
+ if (!taskSent) {
1266
+ const tracedError = createTraceableError('Task injection failed', {
1267
+ agentName: name,
1268
+ cli,
1269
+ attempts: maxRetries,
1270
+ taskLength: task.length,
1271
+ });
1272
+ log.error(`CRITICAL: ${tracedError.logMessage}`);
1273
+ // Note: We don't return an error here because the agent is running,
1274
+ // but we track the errorId so support can investigate if user reports it
1275
+ }
1276
+ }
1277
+
1278
+ // Track the worker
1279
+ const workerInfo: ActiveWorker = {
1280
+ name,
1281
+ cli,
1282
+ task,
1283
+ team,
1284
+ userId,
1285
+ spawnedAt: Date.now(),
1286
+ pid: pty.pid,
1287
+ pty,
1288
+ logFile: pty.logPath,
1289
+ listeners, // Store for cleanup
1290
+ };
1291
+ this.activeWorkers.set(name, workerInfo);
1292
+ this.saveWorkersMetadata();
1293
+
1294
+ const teamInfo = team ? ` [team: ${team}]` : '';
1295
+ const shadowInfo = request.shadowOf ? ` [shadow of: ${request.shadowOf}]` : '';
1296
+ log.info(`Spawned ${name} (${cli})${teamInfo}${shadowInfo} [pid: ${pty.pid}]`);
1297
+
1298
+ return {
1299
+ success: true,
1300
+ name,
1301
+ pid: pty.pid,
1302
+ };
1303
+ } catch (err: any) {
1304
+ const tracedError = createTraceableError('Agent spawn failed', {
1305
+ agentName: name,
1306
+ cli,
1307
+ task: task?.substring(0, 100),
1308
+ }, err instanceof Error ? err : undefined);
1309
+ log.error(tracedError.logMessage);
1310
+ if (debug) log.debug('Full error', { error: err?.stack || String(err) });
1311
+ // Clear spawning flag since spawn failed
1312
+ if (this.onClearSpawning) {
1313
+ this.onClearSpawning(name);
1314
+ }
1315
+ return {
1316
+ success: false,
1317
+ name,
1318
+ error: tracedError.userMessage,
1319
+ errorId: tracedError.errorId,
1320
+ };
1321
+ }
1322
+ }
1323
+
1324
+ /** Role presets for shadow agents */
1325
+ private static readonly ROLE_PRESETS: Record<string, SpeakOnTrigger[]> = {
1326
+ reviewer: ['CODE_WRITTEN', 'REVIEW_REQUEST', 'EXPLICIT_ASK'],
1327
+ auditor: ['SESSION_END', 'EXPLICIT_ASK'],
1328
+ active: ['ALL_MESSAGES'],
1329
+ };
1330
+
1331
+ /**
1332
+ * Spawn a primary agent with its shadow agent
1333
+ *
1334
+ * Example usage:
1335
+ * ```ts
1336
+ * const result = await spawner.spawnWithShadow({
1337
+ * primary: { name: 'Lead', command: 'claude', task: 'Implement feature X' },
1338
+ * shadow: { name: 'Auditor', role: 'reviewer', speakOn: ['CODE_WRITTEN'] }
1339
+ * });
1340
+ * ```
1341
+ */
1342
+ async spawnWithShadow(request: SpawnWithShadowRequest): Promise<SpawnWithShadowResult> {
1343
+ const { primary, shadow } = request;
1344
+ const debug = process.env.DEBUG_SPAWN === '1';
1345
+
1346
+ // Resolve shadow speakOn triggers
1347
+ let speakOn: SpeakOnTrigger[] = ['EXPLICIT_ASK']; // Default
1348
+
1349
+ // Check for role preset
1350
+ if (shadow.role && AgentSpawner.ROLE_PRESETS[shadow.role.toLowerCase()]) {
1351
+ speakOn = AgentSpawner.ROLE_PRESETS[shadow.role.toLowerCase()];
1352
+ }
1353
+
1354
+ // Override with explicit speakOn if provided
1355
+ if (shadow.speakOn && shadow.speakOn.length > 0) {
1356
+ speakOn = shadow.speakOn;
1357
+ }
1358
+
1359
+ // Build shadow task prompt
1360
+ const defaultPrompt = `You are a shadow agent monitoring "${primary.name}". You receive copies of their messages. Your role: ${shadow.role || 'observer'}. Stay passive unless your triggers activate: ${speakOn.join(', ')}.`;
1361
+ const shadowTask = shadow.prompt || defaultPrompt;
1362
+
1363
+ // Decide how to run the shadow (subagent for Claude/OpenCode primaries, process fallback otherwise)
1364
+ let shadowSelection: Awaited<ReturnType<typeof selectShadowCli>> | null = null;
1365
+ try {
1366
+ shadowSelection = await selectShadowCli(primary.command || 'claude', {
1367
+ preferredShadowCli: shadow.command,
1368
+ });
1369
+ } catch (err: any) {
1370
+ log.warn(`Shadow CLI selection failed for ${shadow.name}: ${err.message}`);
1371
+ }
1372
+
1373
+ if (debug) {
1374
+ const mode = shadowSelection?.mode ?? 'unknown';
1375
+ const cli = shadowSelection?.command ?? shadow.command ?? primary.command ?? 'claude';
1376
+ log.debug(
1377
+ `spawnWithShadow: primary=${primary.name}, shadow=${shadow.name}, mode=${mode}, cli=${cli}, speakOn=${speakOn.join(',')}`
1378
+ );
1379
+ }
1380
+
1381
+ // Step 1: Spawn primary agent
1382
+ const primaryResult = await this.spawn({
1383
+ name: primary.name,
1384
+ cli: primary.command || 'claude',
1385
+ task: primary.task || '',
1386
+ team: primary.team,
1387
+ });
1388
+
1389
+ if (!primaryResult.success) {
1390
+ return {
1391
+ success: false,
1392
+ primary: primaryResult,
1393
+ error: `Failed to spawn primary agent: ${primaryResult.error}`,
1394
+ };
1395
+ }
1396
+
1397
+ // Step 2: Wait for primary to register before spawning shadow
1398
+ // The spawn() method already waits, but we add a small delay for stability
1399
+ await sleep(1000);
1400
+
1401
+ // Subagent mode: no separate process needed
1402
+ if (shadowSelection?.mode === 'subagent') {
1403
+ log.info(
1404
+ `Shadow ${shadow.name} will run as ${shadowSelection.cli} subagent inside ${primary.name} (no separate process)`
1405
+ );
1406
+ return {
1407
+ success: true,
1408
+ primary: primaryResult,
1409
+ shadow: {
1410
+ success: true,
1411
+ name: shadow.name,
1412
+ },
1413
+ };
1414
+ }
1415
+
1416
+ // No available shadow CLI - proceed without spawning a shadow process
1417
+ if (!shadowSelection) {
1418
+ log.warn(`No authenticated shadow CLI available; ${primary.name} will run without a shadow`);
1419
+ return {
1420
+ success: true,
1421
+ primary: primaryResult,
1422
+ error: 'Shadow spawn skipped: no authenticated shadow CLI available',
1423
+ };
1424
+ }
1425
+
1426
+ // Step 3: Spawn shadow agent with shadowOf and shadowSpeakOn
1427
+ const shadowResult = await this.spawn({
1428
+ name: shadow.name,
1429
+ // Use the selected/validated CLI for process-mode shadows
1430
+ cli: shadowSelection.command || shadow.command || primary.command || 'claude',
1431
+ task: shadowTask,
1432
+ shadowOf: primary.name,
1433
+ shadowSpeakOn: speakOn,
1434
+ });
1435
+
1436
+ if (!shadowResult.success) {
1437
+ log.warn(`Shadow agent ${shadow.name} failed to spawn, primary ${primary.name} continues without shadow`);
1438
+ return {
1439
+ success: true, // Primary succeeded, overall operation is partial success
1440
+ primary: primaryResult,
1441
+ shadow: shadowResult,
1442
+ error: `Shadow spawn failed: ${shadowResult.error}`,
1443
+ };
1444
+ }
1445
+
1446
+ log.info(`Spawned pair: ${primary.name} with shadow ${shadow.name} (speakOn: ${speakOn.join(',')})`);
1447
+
1448
+ return {
1449
+ success: true,
1450
+ primary: primaryResult,
1451
+ shadow: shadowResult,
1452
+ };
1453
+ }
1454
+
1455
+ /**
1456
+ * Release (terminate) a worker
1457
+ */
1458
+ async release(name: string): Promise<boolean> {
1459
+ const worker = this.activeWorkers.get(name);
1460
+ if (!worker) {
1461
+ log.debug(`Worker ${name} not found`);
1462
+ return false;
1463
+ }
1464
+
1465
+ try {
1466
+ // Unbind all listeners first to prevent memory leaks
1467
+ this.unbindListeners(worker.pty, worker.listeners);
1468
+
1469
+ // Stop the pty process gracefully (handles auto-save internally)
1470
+ await worker.pty.stop();
1471
+
1472
+ // Force kill if still running
1473
+ if (worker.pty.isRunning) {
1474
+ await worker.pty.kill();
1475
+ }
1476
+
1477
+ this.activeWorkers.delete(name);
1478
+ this.saveWorkersMetadata();
1479
+ log.info(`Released ${name}`);
1480
+
1481
+ return true;
1482
+ } catch (err: any) {
1483
+ log.error(`Failed to release ${name}: ${err.message}`);
1484
+ // Still unbind and remove from tracking
1485
+ this.unbindListeners(worker.pty, worker.listeners);
1486
+ this.activeWorkers.delete(name);
1487
+ this.saveWorkersMetadata();
1488
+ return false;
1489
+ }
1490
+ }
1491
+
1492
+ /**
1493
+ * Release all workers
1494
+ */
1495
+ async releaseAll(): Promise<void> {
1496
+ const workers = Array.from(this.activeWorkers.keys());
1497
+ for (const name of workers) {
1498
+ await this.release(name);
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Get all active workers (returns WorkerInfo without pty reference)
1504
+ */
1505
+ getActiveWorkers(): WorkerInfo[] {
1506
+ return Array.from(this.activeWorkers.values()).map((w) => ({
1507
+ name: w.name,
1508
+ cli: w.cli,
1509
+ task: w.task,
1510
+ team: w.team,
1511
+ spawnedAt: w.spawnedAt,
1512
+ pid: w.pid,
1513
+ }));
1514
+ }
1515
+
1516
+ /**
1517
+ * Check if a worker exists
1518
+ */
1519
+ hasWorker(name: string): boolean {
1520
+ return this.activeWorkers.has(name);
1521
+ }
1522
+
1523
+ /**
1524
+ * Get worker info
1525
+ */
1526
+ getWorker(name: string): WorkerInfo | undefined {
1527
+ const worker = this.activeWorkers.get(name);
1528
+ if (!worker) return undefined;
1529
+ return {
1530
+ name: worker.name,
1531
+ cli: worker.cli,
1532
+ task: worker.task,
1533
+ team: worker.team,
1534
+ spawnedAt: worker.spawnedAt,
1535
+ pid: worker.pid,
1536
+ };
1537
+ }
1538
+
1539
+ /**
1540
+ * Get output logs from a worker
1541
+ */
1542
+ getWorkerOutput(name: string, limit?: number): string[] | null {
1543
+ const worker = this.activeWorkers.get(name);
1544
+ if (!worker) return null;
1545
+ return worker.pty.getOutput(limit);
1546
+ }
1547
+
1548
+ /**
1549
+ * Get raw output from a worker
1550
+ */
1551
+ getWorkerRawOutput(name: string): string | null {
1552
+ const worker = this.activeWorkers.get(name);
1553
+ if (!worker) return null;
1554
+ return worker.pty.getRawOutput();
1555
+ }
1556
+
1557
+ /**
1558
+ * Send input to a worker's PTY (for interactive terminal support)
1559
+ * @param name - Worker name
1560
+ * @param data - Input data to send (keystrokes, text, etc.)
1561
+ * @returns true if input was sent, false if worker not found
1562
+ */
1563
+ sendWorkerInput(name: string, data: string): boolean {
1564
+ const worker = this.activeWorkers.get(name);
1565
+ if (!worker) return false;
1566
+ worker.pty.write(data);
1567
+ return true;
1568
+ }
1569
+
1570
+ /**
1571
+ * Wait for an agent to appear in the connected list and registry (connected-agents.json + agents.json).
1572
+ */
1573
+ private async waitForAgentRegistration(
1574
+ name: string,
1575
+ timeoutMs = 30_000,
1576
+ pollIntervalMs = 500
1577
+ ): Promise<boolean> {
1578
+ const deadline = Date.now() + timeoutMs;
1579
+ let pollCount = 0;
1580
+
1581
+ while (Date.now() < deadline) {
1582
+ pollCount++;
1583
+ const connected = this.isAgentConnected(name);
1584
+ const recentlySeen = this.isAgentRecentlySeen(name);
1585
+
1586
+ // Log first few polls and every 10th poll after that
1587
+ if (pollCount <= 3 || pollCount % 10 === 0) {
1588
+ log.info(`Registration poll #${pollCount} for ${name}: connected=${connected} recentlySeen=${recentlySeen} agentsPath=${this.agentsPath}`);
1589
+ }
1590
+
1591
+ if (connected && recentlySeen) {
1592
+ log.info(`Agent ${name} registered after ${pollCount} polls`);
1593
+ return true;
1594
+ }
1595
+
1596
+ await sleep(pollIntervalMs);
1597
+ }
1598
+
1599
+ log.info(`Registration timeout for ${name} after ${pollCount} polls`);
1600
+ return false;
1601
+ }
1602
+
1603
+ private isAgentRegistered(name: string): boolean {
1604
+ return this.isAgentConnected(name) && this.isAgentRecentlySeen(name);
1605
+ }
1606
+
1607
+ private isAgentConnected(name: string): boolean {
1608
+ const debug = process.env.DEBUG_SPAWN === '1';
1609
+ if (!this.agentsPath) {
1610
+ if (debug) log.debug(`isAgentConnected(${name}): no agentsPath`);
1611
+ return false;
1612
+ }
1613
+ if (!fs.existsSync(this.agentsPath)) {
1614
+ if (debug) log.debug(`isAgentConnected(${name}): file not found: ${this.agentsPath}`);
1615
+ return false;
1616
+ }
1617
+
1618
+ try {
1619
+ const raw = JSON.parse(fs.readFileSync(this.agentsPath, 'utf-8'));
1620
+ // connected-agents.json format: { agents: string[], users: string[], updatedAt: number }
1621
+ // agents is a string array of connected agent names (not objects)
1622
+ const agents: string[] = Array.isArray(raw?.agents) ? raw.agents : [];
1623
+ const updatedAt = typeof raw?.updatedAt === 'number' ? raw.updatedAt : 0;
1624
+ const isFresh = Date.now() - updatedAt <= AgentSpawner.ONLINE_THRESHOLD_MS;
1625
+
1626
+ if (debug) {
1627
+ log.debug(`isAgentConnected(${name}): path=${this.agentsPath} agents=${agents.join(',')} updatedAt=${updatedAt} isFresh=${isFresh}`);
1628
+ }
1629
+
1630
+ if (!isFresh) return false;
1631
+
1632
+ // Case-insensitive check to match router behavior
1633
+ const lowerName = name.toLowerCase();
1634
+ return agents.some((a) => typeof a === 'string' && a.toLowerCase() === lowerName);
1635
+ } catch (err: any) {
1636
+ log.error('Failed to read connected-agents.json', { error: err.message, path: this.agentsPath });
1637
+ return false;
1638
+ }
1639
+ }
1640
+
1641
+ private isAgentRecentlySeen(name: string): boolean {
1642
+ if (!this.registryPath) return false;
1643
+ if (!fs.existsSync(this.registryPath)) return false;
1644
+
1645
+ try {
1646
+ const raw = JSON.parse(fs.readFileSync(this.registryPath, 'utf-8'));
1647
+ const agents = Array.isArray(raw?.agents)
1648
+ ? raw.agents
1649
+ : typeof raw?.agents === 'object' && raw?.agents !== null
1650
+ ? Object.values(raw.agents)
1651
+ : [];
1652
+ const lowerName = name.toLowerCase();
1653
+ const agent = agents.find((entry: { name?: string; lastSeen?: string }) => typeof entry?.name === 'string' && entry.name.toLowerCase() === lowerName);
1654
+ if (!agent?.lastSeen) return false;
1655
+ return Date.now() - new Date(agent.lastSeen).getTime() <= AgentSpawner.ONLINE_THRESHOLD_MS;
1656
+ } catch (err: any) {
1657
+ log.error('Failed to read agents.json', { error: err.message });
1658
+ return false;
1659
+ }
1660
+ }
1661
+
1662
+ /**
1663
+ * Save workers metadata to disk for CLI access
1664
+ */
1665
+ private saveWorkersMetadata(): void {
1666
+ try {
1667
+ const workers: WorkerMeta[] = Array.from(this.activeWorkers.values()).map((w) => ({
1668
+ name: w.name,
1669
+ cli: w.cli,
1670
+ task: w.task,
1671
+ team: w.team,
1672
+ userId: w.userId,
1673
+ spawnedAt: w.spawnedAt,
1674
+ pid: w.pid,
1675
+ logFile: w.logFile,
1676
+ }));
1677
+
1678
+ fs.writeFileSync(this.workersPath, JSON.stringify({ workers }, null, 2));
1679
+ } catch (err: any) {
1680
+ log.error('Failed to save workers metadata', { error: err.message });
1681
+ }
1682
+ }
1683
+
1684
+ /**
1685
+ * Get path to logs directory
1686
+ */
1687
+ getLogsDir(): string {
1688
+ return this.logsDir;
1689
+ }
1690
+
1691
+ /**
1692
+ * Get path to workers metadata file
1693
+ */
1694
+ getWorkersPath(): string {
1695
+ return this.workersPath;
1696
+ }
1697
+ }
1698
+
1699
+ /**
1700
+ * Read workers metadata from disk (for CLI use)
1701
+ */
1702
+ export function readWorkersMetadata(projectRoot: string): WorkerMeta[] {
1703
+ const paths = getProjectPaths(projectRoot);
1704
+ const workersPath = path.join(paths.teamDir, 'workers.json');
1705
+
1706
+ if (!fs.existsSync(workersPath)) {
1707
+ return [];
1708
+ }
1709
+
1710
+ try {
1711
+ const raw = JSON.parse(fs.readFileSync(workersPath, 'utf-8'));
1712
+ return Array.isArray(raw?.workers) ? raw.workers : [];
1713
+ } catch {
1714
+ return [];
1715
+ }
1716
+ }
1717
+
1718
+ /**
1719
+ * Get the worker logs directory path
1720
+ */
1721
+ export function getWorkerLogsDir(projectRoot: string): string {
1722
+ const paths = getProjectPaths(projectRoot);
1723
+ return path.join(paths.teamDir, 'worker-logs');
1724
+ }