borgmcp 1.1.15 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. package/CONTRIBUTING.md +15 -0
  2. package/LICENSE +201 -33
  3. package/NOTICE +2 -0
  4. package/README.md +85 -36
  5. package/SECURITY.md +7 -0
  6. package/dist/agent-runtime.d.ts.map +1 -0
  7. package/dist/agent-runtime.js +63 -1
  8. package/dist/agent-runtime.js.map +1 -0
  9. package/dist/assimilate-cmd.d.ts +96 -17
  10. package/dist/assimilate-cmd.d.ts.map +1 -0
  11. package/dist/assimilate-cmd.js +1396 -48
  12. package/dist/assimilate-cmd.js.map +1 -0
  13. package/dist/assimilate-deps.d.ts.map +1 -0
  14. package/dist/assimilate-deps.js +384 -3
  15. package/dist/assimilate-deps.js.map +1 -0
  16. package/dist/assimilate-guard.d.ts +5 -7
  17. package/dist/assimilate-guard.d.ts.map +1 -0
  18. package/dist/assimilate-guard.js +60 -2
  19. package/dist/assimilate-guard.js.map +1 -0
  20. package/dist/assimilate-welcome.d.ts +1 -1
  21. package/dist/assimilate-welcome.d.ts.map +1 -0
  22. package/dist/assimilate-welcome.js +32 -2
  23. package/dist/assimilate-welcome.js.map +1 -0
  24. package/dist/auth-env.d.ts +7 -50
  25. package/dist/auth-env.d.ts.map +1 -0
  26. package/dist/auth-env.js +22 -1
  27. package/dist/auth-env.js.map +1 -0
  28. package/dist/backends/launch-all-pastelist.d.ts.map +1 -0
  29. package/dist/backends/launch-all-pastelist.js +11 -4
  30. package/dist/backends/launch-all-pastelist.js.map +1 -0
  31. package/dist/backends/launch-all-tmux.d.ts.map +1 -0
  32. package/dist/backends/launch-all-tmux.js +49 -1
  33. package/dist/backends/launch-all-tmux.js.map +1 -0
  34. package/dist/backends/launch-all-windows.d.ts.map +1 -0
  35. package/dist/backends/launch-all-windows.js +86 -12
  36. package/dist/backends/launch-all-windows.js.map +1 -0
  37. package/dist/bare-launch-menu.d.ts.map +1 -0
  38. package/dist/bare-launch-menu.js +83 -6
  39. package/dist/bare-launch-menu.js.map +1 -0
  40. package/dist/claude-launch-args.d.ts.map +1 -0
  41. package/dist/claude-launch-args.js +34 -1
  42. package/dist/claude-launch-args.js.map +1 -0
  43. package/dist/claude.d.ts.map +1 -0
  44. package/dist/claude.js +486 -14
  45. package/dist/claude.js.map +1 -0
  46. package/dist/cleanup-cmd.d.ts +5 -5
  47. package/dist/cleanup-cmd.d.ts.map +1 -0
  48. package/dist/cleanup-cmd.js +453 -14
  49. package/dist/cleanup-cmd.js.map +1 -0
  50. package/dist/clear-rewake-core.d.ts.map +1 -0
  51. package/dist/clear-rewake-core.js +23 -2
  52. package/dist/clear-rewake-core.js.map +1 -0
  53. package/dist/clear-rewake.d.ts.map +1 -0
  54. package/dist/clear-rewake.js +19 -1
  55. package/dist/clear-rewake.js.map +1 -0
  56. package/dist/cli-help.d.ts +5 -0
  57. package/dist/cli-help.d.ts.map +1 -0
  58. package/dist/cli-help.js +104 -56
  59. package/dist/cli-help.js.map +1 -0
  60. package/dist/cli-platform.d.ts.map +1 -0
  61. package/dist/cli-platform.js +106 -4
  62. package/dist/cli-platform.js.map +1 -0
  63. package/dist/cli-tool-approval.d.ts +84 -0
  64. package/dist/cli-tool-approval.d.ts.map +1 -0
  65. package/dist/cli-tool-approval.js +583 -0
  66. package/dist/cli-tool-approval.js.map +1 -0
  67. package/dist/codex-app-server.d.ts.map +1 -0
  68. package/dist/codex-app-server.js +228 -4
  69. package/dist/codex-app-server.js.map +1 -0
  70. package/dist/codex-app-wake.d.ts.map +1 -0
  71. package/dist/codex-app-wake.js +432 -2
  72. package/dist/codex-app-wake.js.map +1 -0
  73. package/dist/codex-launch.d.ts.map +1 -0
  74. package/dist/codex-launch.js +102 -2
  75. package/dist/codex-launch.js.map +1 -0
  76. package/dist/codex-remote.d.ts +6 -1
  77. package/dist/codex-remote.d.ts.map +1 -0
  78. package/dist/codex-remote.js +394 -1
  79. package/dist/codex-remote.js.map +1 -0
  80. package/dist/codex-wake-resolve.d.ts.map +1 -0
  81. package/dist/codex-wake-resolve.js +127 -1
  82. package/dist/codex-wake-resolve.js.map +1 -0
  83. package/dist/config-utils.d.ts +2 -2
  84. package/dist/config-utils.d.ts.map +1 -0
  85. package/dist/config-utils.js +766 -3
  86. package/dist/config-utils.js.map +1 -0
  87. package/dist/config.d.ts +98 -61
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +426 -1
  90. package/dist/config.js.map +1 -0
  91. package/dist/console-prefix.d.ts.map +1 -0
  92. package/dist/console-prefix.js +96 -1
  93. package/dist/console-prefix.js.map +1 -0
  94. package/dist/cube-name.d.ts.map +1 -0
  95. package/dist/cube-name.js +67 -1
  96. package/dist/cube-name.js.map +1 -0
  97. package/dist/cubes.d.ts +126 -18
  98. package/dist/cubes.d.ts.map +1 -0
  99. package/dist/cubes.js +412 -4
  100. package/dist/cubes.js.map +1 -0
  101. package/dist/debug.d.ts.map +1 -0
  102. package/dist/debug.js +71 -1
  103. package/dist/debug.js.map +1 -0
  104. package/dist/direct-log.d.ts.map +1 -0
  105. package/dist/direct-log.js +11 -1
  106. package/dist/direct-log.js.map +1 -0
  107. package/dist/docs-sections.d.ts +11 -10
  108. package/dist/docs-sections.d.ts.map +1 -0
  109. package/dist/docs-sections.js +113 -3
  110. package/dist/docs-sections.js.map +1 -0
  111. package/dist/drone-lifecycle.d.ts +5 -21
  112. package/dist/drone-lifecycle.d.ts.map +1 -0
  113. package/dist/drone-lifecycle.js +57 -8
  114. package/dist/drone-lifecycle.js.map +1 -0
  115. package/dist/drone-management.d.ts +29 -0
  116. package/dist/drone-management.d.ts.map +1 -0
  117. package/dist/drone-management.js +109 -0
  118. package/dist/drone-management.js.map +1 -0
  119. package/dist/ensure-mcp-config.d.ts.map +1 -0
  120. package/dist/ensure-mcp-config.js +36 -1
  121. package/dist/ensure-mcp-config.js.map +1 -0
  122. package/dist/evict-drone.d.ts +10 -2
  123. package/dist/evict-drone.d.ts.map +1 -0
  124. package/dist/evict-drone.js +60 -1
  125. package/dist/evict-drone.js.map +1 -0
  126. package/dist/gc-orphan-inboxes.d.ts.map +1 -0
  127. package/dist/gc-orphan-inboxes.js +177 -1
  128. package/dist/gc-orphan-inboxes.js.map +1 -0
  129. package/dist/inbox-monitor.d.ts +1 -1
  130. package/dist/inbox-monitor.d.ts.map +1 -0
  131. package/dist/inbox-monitor.js +856 -3
  132. package/dist/inbox-monitor.js.map +1 -0
  133. package/dist/index.d.ts +6 -4
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +1034 -37
  136. package/dist/index.js.map +1 -0
  137. package/dist/launch-all-cmd.d.ts.map +1 -0
  138. package/dist/launch-all-cmd.js +377 -37
  139. package/dist/launch-all-cmd.js.map +1 -0
  140. package/dist/launch-all-command.d.ts.map +1 -0
  141. package/dist/launch-all-command.js +24 -1
  142. package/dist/launch-all-command.js.map +1 -0
  143. package/dist/launch-all-deps.d.ts +3 -8
  144. package/dist/launch-all-deps.d.ts.map +1 -0
  145. package/dist/launch-all-deps.js +99 -1
  146. package/dist/launch-all-deps.js.map +1 -0
  147. package/dist/launch-all-discovery.d.ts +1 -0
  148. package/dist/launch-all-discovery.d.ts.map +1 -0
  149. package/dist/launch-all-discovery.js +90 -4
  150. package/dist/launch-all-discovery.js.map +1 -0
  151. package/dist/launch-all-locks.d.ts.map +1 -0
  152. package/dist/launch-all-locks.js +54 -1
  153. package/dist/launch-all-locks.js.map +1 -0
  154. package/dist/launch-gate.d.ts +2 -2
  155. package/dist/launch-gate.d.ts.map +1 -0
  156. package/dist/launch-gate.js +73 -1
  157. package/dist/launch-gate.js.map +1 -0
  158. package/dist/lifecycle-log-guard.d.ts.map +1 -0
  159. package/dist/lifecycle-log-guard.js +93 -2
  160. package/dist/lifecycle-log-guard.js.map +1 -0
  161. package/dist/list-roles-render.d.ts.map +1 -0
  162. package/dist/list-roles-render.js +40 -6
  163. package/dist/list-roles-render.js.map +1 -0
  164. package/dist/local-log-routing.d.ts +19 -0
  165. package/dist/local-log-routing.d.ts.map +1 -0
  166. package/dist/local-log-routing.js +67 -0
  167. package/dist/local-log-routing.js.map +1 -0
  168. package/dist/local-manage-tool-result.d.ts +8 -0
  169. package/dist/local-manage-tool-result.d.ts.map +1 -0
  170. package/dist/local-manage-tool-result.js +10 -0
  171. package/dist/local-manage-tool-result.js.map +1 -0
  172. package/dist/local-server-cursor.d.ts +34 -0
  173. package/dist/local-server-cursor.d.ts.map +1 -0
  174. package/dist/local-server-cursor.js +151 -0
  175. package/dist/local-server-cursor.js.map +1 -0
  176. package/dist/log-audit.d.ts.map +1 -0
  177. package/dist/log-audit.js +194 -3
  178. package/dist/log-audit.js.map +1 -0
  179. package/dist/log-stream.d.ts +31 -18
  180. package/dist/log-stream.d.ts.map +1 -0
  181. package/dist/log-stream.js +1214 -12
  182. package/dist/log-stream.js.map +1 -0
  183. package/dist/model-presets.d.ts.map +1 -0
  184. package/dist/model-presets.js +19 -1
  185. package/dist/model-presets.js.map +1 -0
  186. package/dist/name-validator.d.ts.map +1 -0
  187. package/dist/name-validator.js +22 -1
  188. package/dist/name-validator.js.map +1 -0
  189. package/dist/opencode-drone.d.ts.map +1 -0
  190. package/dist/opencode-drone.js +438 -4
  191. package/dist/opencode-drone.js.map +1 -0
  192. package/dist/opencode-plugin.d.ts +1 -1
  193. package/dist/opencode-plugin.d.ts.map +1 -0
  194. package/dist/opencode-plugin.js +29 -4
  195. package/dist/opencode-plugin.js.map +1 -0
  196. package/dist/parse-assimilate-args.d.ts +1 -1
  197. package/dist/parse-assimilate-args.d.ts.map +1 -0
  198. package/dist/parse-assimilate-args.js +125 -1
  199. package/dist/parse-assimilate-args.js.map +1 -0
  200. package/dist/parse-launch-all-args.d.ts.map +1 -0
  201. package/dist/parse-launch-all-args.js +70 -1
  202. package/dist/parse-launch-all-args.js.map +1 -0
  203. package/dist/postinstall-banner.d.ts.map +1 -0
  204. package/dist/postinstall-banner.js +27 -2
  205. package/dist/postinstall-banner.js.map +1 -0
  206. package/dist/postinstall.d.ts.map +1 -0
  207. package/dist/postinstall.js +32 -5
  208. package/dist/postinstall.js.map +1 -0
  209. package/dist/readiness-probe.d.ts.map +1 -0
  210. package/dist/readiness-probe.js +9 -1
  211. package/dist/readiness-probe.js.map +1 -0
  212. package/dist/regen-format.d.ts.map +1 -0
  213. package/dist/regen-format.js +545 -13
  214. package/dist/regen-format.js.map +1 -0
  215. package/dist/regen.d.ts.map +1 -0
  216. package/dist/regen.js +141 -5
  217. package/dist/regen.js.map +1 -0
  218. package/dist/remote-client.d.ts +65 -175
  219. package/dist/remote-client.d.ts.map +1 -0
  220. package/dist/remote-client.js +942 -1
  221. package/dist/remote-client.js.map +1 -0
  222. package/dist/reset-local-seat-cmd.d.ts +50 -0
  223. package/dist/reset-local-seat-cmd.d.ts.map +1 -0
  224. package/dist/reset-local-seat-cmd.js +169 -0
  225. package/dist/reset-local-seat-cmd.js.map +1 -0
  226. package/dist/role-match.d.ts.map +1 -0
  227. package/dist/role-match.js +23 -1
  228. package/dist/role-match.js.map +1 -0
  229. package/dist/role-resolver.d.ts.map +1 -0
  230. package/dist/role-resolver.js +60 -1
  231. package/dist/role-resolver.js.map +1 -0
  232. package/dist/roster-render.d.ts.map +1 -0
  233. package/dist/roster-render.js +121 -3
  234. package/dist/roster-render.js.map +1 -0
  235. package/dist/seat-probe.d.ts +32 -13
  236. package/dist/seat-probe.d.ts.map +1 -0
  237. package/dist/seat-probe.js +78 -1
  238. package/dist/seat-probe.js.map +1 -0
  239. package/dist/seat-store.d.ts +76 -0
  240. package/dist/seat-store.d.ts.map +1 -0
  241. package/dist/seat-store.js +309 -0
  242. package/dist/seat-store.js.map +1 -0
  243. package/dist/seats.d.ts +303 -0
  244. package/dist/seats.d.ts.map +1 -0
  245. package/dist/seats.js +551 -0
  246. package/dist/seats.js.map +1 -0
  247. package/dist/self-path.d.ts +27 -0
  248. package/dist/self-path.d.ts.map +1 -0
  249. package/dist/self-path.js +43 -0
  250. package/dist/self-path.js.map +1 -0
  251. package/dist/server-endpoint.d.ts +9 -0
  252. package/dist/server-endpoint.d.ts.map +1 -0
  253. package/dist/server-endpoint.js +38 -0
  254. package/dist/server-endpoint.js.map +1 -0
  255. package/dist/server-errors.d.ts +50 -0
  256. package/dist/server-errors.d.ts.map +1 -0
  257. package/dist/server-errors.js +79 -0
  258. package/dist/server-errors.js.map +1 -0
  259. package/dist/server-handshake.d.ts +197 -0
  260. package/dist/server-handshake.d.ts.map +1 -0
  261. package/dist/server-handshake.js +505 -0
  262. package/dist/server-handshake.js.map +1 -0
  263. package/dist/server-response.d.ts +3 -0
  264. package/dist/server-response.d.ts.map +1 -0
  265. package/dist/server-response.js +67 -0
  266. package/dist/server-response.js.map +1 -0
  267. package/dist/server-trust.d.ts +14 -0
  268. package/dist/server-trust.d.ts.map +1 -0
  269. package/dist/server-trust.js +225 -0
  270. package/dist/server-trust.js.map +1 -0
  271. package/dist/setup-confirm.d.ts +3 -3
  272. package/dist/setup-confirm.d.ts.map +1 -0
  273. package/dist/setup-confirm.js +126 -2
  274. package/dist/setup-confirm.js.map +1 -0
  275. package/dist/setup.d.ts +5 -3
  276. package/dist/setup.d.ts.map +1 -0
  277. package/dist/setup.js +169 -46
  278. package/dist/setup.js.map +1 -0
  279. package/dist/shell-escape.d.ts.map +1 -0
  280. package/dist/shell-escape.js +22 -1
  281. package/dist/shell-escape.js.map +1 -0
  282. package/dist/spawn.d.ts +2 -2
  283. package/dist/spawn.d.ts.map +1 -0
  284. package/dist/spawn.js +29 -10
  285. package/dist/spawn.js.map +1 -0
  286. package/dist/startup-services.d.ts +0 -1
  287. package/dist/startup-services.d.ts.map +1 -0
  288. package/dist/startup-services.js +26 -1
  289. package/dist/startup-services.js.map +1 -0
  290. package/dist/stream-owner.d.ts +6 -2
  291. package/dist/stream-owner.d.ts.map +1 -0
  292. package/dist/stream-owner.js +547 -2
  293. package/dist/stream-owner.js.map +1 -0
  294. package/dist/stream-status.d.ts +1 -1
  295. package/dist/stream-status.d.ts.map +1 -0
  296. package/dist/stream-status.js +268 -3
  297. package/dist/stream-status.js.map +1 -0
  298. package/dist/sync-roles-render.d.ts.map +1 -0
  299. package/dist/sync-roles-render.js +118 -3
  300. package/dist/sync-roles-render.js.map +1 -0
  301. package/dist/sync.d.ts +1 -1
  302. package/dist/sync.d.ts.map +1 -0
  303. package/dist/sync.js +286 -22
  304. package/dist/sync.js.map +1 -0
  305. package/dist/terminal-title.d.ts.map +1 -0
  306. package/dist/terminal-title.js +68 -1
  307. package/dist/terminal-title.js.map +1 -0
  308. package/dist/token-store.d.ts +17 -111
  309. package/dist/token-store.d.ts.map +1 -0
  310. package/dist/token-store.js +81 -1
  311. package/dist/token-store.js.map +1 -0
  312. package/dist/tool-manifest.d.ts +4 -8
  313. package/dist/tool-manifest.d.ts.map +1 -0
  314. package/dist/tool-manifest.js +554 -1
  315. package/dist/tool-manifest.js.map +1 -0
  316. package/dist/tool-scope.d.ts +8 -12
  317. package/dist/tool-scope.d.ts.map +1 -0
  318. package/dist/tool-scope.js +97 -1
  319. package/dist/tool-scope.js.map +1 -0
  320. package/dist/types.d.ts +1 -5
  321. package/dist/types.d.ts.map +1 -0
  322. package/dist/types.js +5 -0
  323. package/dist/types.js.map +1 -0
  324. package/dist/unknown-subcommand.d.ts +1 -1
  325. package/dist/unknown-subcommand.d.ts.map +1 -0
  326. package/dist/unknown-subcommand.js +37 -1
  327. package/dist/unknown-subcommand.js.map +1 -0
  328. package/dist/version.d.ts +1 -1
  329. package/dist/version.d.ts.map +1 -0
  330. package/dist/version.js +78 -2
  331. package/dist/version.js.map +1 -0
  332. package/dist/working-repo.d.ts.map +1 -0
  333. package/dist/working-repo.js +81 -1
  334. package/dist/working-repo.js.map +1 -0
  335. package/dist/worktree-lifecycle.d.ts.map +1 -0
  336. package/dist/worktree-lifecycle.js +205 -2
  337. package/dist/worktree-lifecycle.js.map +1 -0
  338. package/docs/EXTRACTION_PROVENANCE.md +40 -0
  339. package/docs/LOCAL_SERVER.md +103 -0
  340. package/docs/RELEASING.md +120 -0
  341. package/package.json +52 -34
  342. package/src/agent-runtime.ts +69 -0
  343. package/src/assimilate-cmd.ts +1864 -0
  344. package/src/assimilate-deps.ts +444 -0
  345. package/src/assimilate-guard.ts +84 -0
  346. package/src/assimilate-welcome.ts +39 -0
  347. package/src/auth-env.ts +21 -0
  348. package/src/backends/launch-all-pastelist.ts +18 -0
  349. package/src/backends/launch-all-tmux.ts +75 -0
  350. package/src/backends/launch-all-windows.ts +110 -0
  351. package/src/bare-launch-menu.ts +148 -0
  352. package/src/claude-launch-args.ts +35 -0
  353. package/src/claude.ts +561 -0
  354. package/src/cleanup-cmd.ts +581 -0
  355. package/src/clear-rewake-core.ts +34 -0
  356. package/src/clear-rewake.ts +18 -0
  357. package/src/cli-help.ts +116 -0
  358. package/src/cli-platform.ts +127 -0
  359. package/src/cli-tool-approval.ts +716 -0
  360. package/src/codex-app-server.ts +238 -0
  361. package/src/codex-app-wake.ts +512 -0
  362. package/src/codex-launch.ts +140 -0
  363. package/src/codex-remote.ts +500 -0
  364. package/src/codex-wake-resolve.ts +155 -0
  365. package/src/config-utils.ts +811 -0
  366. package/src/config.ts +595 -0
  367. package/src/console-prefix.ts +99 -0
  368. package/src/cube-name.ts +69 -0
  369. package/src/cubes.ts +583 -0
  370. package/src/debug.ts +73 -0
  371. package/src/direct-log.ts +9 -0
  372. package/src/docs-sections.ts +129 -0
  373. package/src/drone-lifecycle.ts +62 -0
  374. package/src/drone-management.ts +162 -0
  375. package/src/ensure-mcp-config.ts +54 -0
  376. package/src/evict-drone.ts +82 -0
  377. package/src/gc-orphan-inboxes.ts +229 -0
  378. package/src/inbox-monitor.ts +1005 -0
  379. package/src/index.ts +1201 -0
  380. package/src/launch-all-cmd.ts +450 -0
  381. package/src/launch-all-command.ts +30 -0
  382. package/src/launch-all-deps.ts +188 -0
  383. package/src/launch-all-discovery.ts +118 -0
  384. package/src/launch-all-locks.ts +74 -0
  385. package/src/launch-gate.ts +80 -0
  386. package/src/lifecycle-log-guard.ts +144 -0
  387. package/src/list-roles-render.ts +52 -0
  388. package/src/local-log-routing.ts +106 -0
  389. package/src/local-manage-tool-result.ts +10 -0
  390. package/src/local-server-cursor.ts +188 -0
  391. package/src/log-audit.ts +207 -0
  392. package/src/log-stream.ts +1505 -0
  393. package/src/model-presets.ts +22 -0
  394. package/src/name-validator.ts +23 -0
  395. package/src/opencode-drone.ts +528 -0
  396. package/src/opencode-plugin.ts +38 -0
  397. package/src/parse-assimilate-args.ts +125 -0
  398. package/src/parse-launch-all-args.ts +100 -0
  399. package/src/postinstall-banner.ts +37 -0
  400. package/src/postinstall.ts +36 -0
  401. package/src/readiness-probe.ts +12 -0
  402. package/src/regen-format.ts +651 -0
  403. package/src/regen.ts +150 -0
  404. package/src/remote-client.ts +1379 -0
  405. package/src/reset-local-seat-cmd.ts +227 -0
  406. package/src/role-match.ts +29 -0
  407. package/src/role-resolver.ts +93 -0
  408. package/src/roster-render.ts +196 -0
  409. package/src/seat-probe.ts +115 -0
  410. package/src/seat-store.ts +333 -0
  411. package/src/seats.ts +741 -0
  412. package/src/self-path.ts +51 -0
  413. package/src/server-endpoint.ts +38 -0
  414. package/src/server-errors.ts +93 -0
  415. package/src/server-handshake.ts +783 -0
  416. package/src/server-response.ts +76 -0
  417. package/src/server-trust.ts +245 -0
  418. package/src/setup-confirm.ts +171 -0
  419. package/src/setup.ts +186 -0
  420. package/src/shell-escape.ts +22 -0
  421. package/src/spawn.ts +31 -0
  422. package/src/startup-services.ts +36 -0
  423. package/src/stream-owner.ts +686 -0
  424. package/src/stream-status.ts +337 -0
  425. package/src/sync-roles-render.ts +163 -0
  426. package/src/sync.ts +363 -0
  427. package/src/terminal-title.ts +76 -0
  428. package/src/token-store.ts +102 -0
  429. package/src/tool-manifest.ts +610 -0
  430. package/src/tool-scope.ts +110 -0
  431. package/src/types.ts +5 -0
  432. package/src/unknown-subcommand.ts +35 -0
  433. package/src/version.ts +82 -0
  434. package/src/working-repo.ts +99 -0
  435. package/src/worktree-lifecycle.ts +254 -0
  436. package/dist/auth-recovery.d.ts +0 -33
  437. package/dist/auth-recovery.js +0 -1
  438. package/dist/auth.d.ts +0 -103
  439. package/dist/auth.js +0 -39
  440. package/dist/device-auth.d.ts +0 -75
  441. package/dist/device-auth.js +0 -1
  442. package/dist/drone-address.d.ts +0 -26
  443. package/dist/drone-address.js +0 -1
  444. package/dist/get-started.d.ts +0 -20
  445. package/dist/get-started.js +0 -2
  446. package/dist/health-beat.d.ts +0 -134
  447. package/dist/health-beat.js +0 -1
  448. package/dist/role-section.d.ts +0 -110
  449. package/dist/role-section.js +0 -8
  450. package/dist/setup-action.d.ts +0 -20
  451. package/dist/setup-action.js +0 -1
  452. package/dist/stale-version-check.d.ts +0 -40
  453. package/dist/stale-version-check.js +0 -1
  454. package/dist/subscription-retry.d.ts +0 -40
  455. package/dist/subscription-retry.js +0 -1
  456. package/dist/templates.d.ts +0 -107
  457. package/dist/templates.js +0 -359
  458. package/dist/token-crypto.d.ts +0 -50
  459. package/dist/token-crypto.js +0 -1
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Real-IO factory for the `borg assimilate` orchestrator. Produces a
3
+ * fully-wired `AssimilateDeps` whose seams call into the existing
4
+ * client modules (remote-client HTTP, cubes.ts persistence, auth.ts
5
+ * setup wizard, terminal-title helper).
6
+ *
7
+ * Test code never calls this — tests construct stub deps directly
8
+ * (see `client/__tests__/assimilate-cmd.test.ts:makeStubDeps`).
9
+ */
10
+
11
+ import { spawnSync, spawn as spawnChild } from 'node:child_process';
12
+ import { randomBytes } from 'node:crypto';
13
+ import { existsSync, mkdirSync } from 'node:fs';
14
+ import { hostname as osHostname, homedir as osHomedir } from 'node:os';
15
+ import { createInterface } from 'node:readline/promises';
16
+ import prompts from 'prompts';
17
+ import { readinessProbeEnv } from './readiness-probe.js';
18
+ import { resolveMcpBinaryPath } from './self-path.js';
19
+
20
+ import type { AssimilateDeps } from './assimilate-cmd.js';
21
+ import {
22
+ listCubes as remoteListCubes,
23
+ getCube as remoteGetCube,
24
+ } from './remote-client.js';
25
+ import {
26
+ DEFAULT_LOCAL_SERVER_ORIGIN,
27
+ connectLocalBorgServer,
28
+ createLocalBorgServerCube,
29
+ enrollLocalBorgServer,
30
+ probeLocalBorgServer,
31
+ resumeLocalBorgServerEnrollment,
32
+ sendBorgServerAttach,
33
+ } from './server-handshake.js';
34
+ import {
35
+ findIncompleteSiblingAttempt,
36
+ observeSeat,
37
+ prepareSeat,
38
+ seatRef,
39
+ type ActivateSeatOutcome,
40
+ type SeatOperation as ServerSessionOperation,
41
+ } from './seats.js';
42
+ import {
43
+ readPersistedLocalSeat,
44
+ } from './cubes.js';
45
+ import { loadBorgServerTrust } from './server-trust.js';
46
+ import { defaultProbeSeat } from './seat-probe.js';
47
+ import { BorgServerError } from './server-errors.js';
48
+ import {
49
+ findProjectRoot as cubesFindProjectRoot,
50
+ getActiveCube as cubesGetActive,
51
+ hasPersistedActiveCube as cubesHasPersistedActive,
52
+ setActiveCube as cubesSetActive,
53
+ inboxPathForDrone,
54
+ setCodexWakeTarget,
55
+ } from './cubes.js';
56
+ import { addProjectSessionStartHook } from './config-utils.js';
57
+ import { setTerminalTitle as setTitle } from './terminal-title.js';
58
+ import { defaultCliChoiceDeps, resolveCliChoice } from './cli-platform.js';
59
+ import { prepareCodexRemoteLaunch, defaultCodexRemoteDeps } from './codex-remote.js';
60
+ import { findLoadedCodexThread } from './codex-app-server.js';
61
+ import { defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
62
+
63
+ export function buildDefaultAssimilateDeps(): AssimilateDeps {
64
+ return {
65
+ runSync: (cmd, args, cwd) => {
66
+ const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
67
+ return {
68
+ status: r.status,
69
+ stdout: r.stdout ?? '',
70
+ stderr: r.stderr ?? '',
71
+ };
72
+ },
73
+ pathExists: (p) => existsSync(p),
74
+ cwd: () => process.cwd(),
75
+ chdir: (p) => process.chdir(p),
76
+ // gh#556 Part 1: ~/.borg/worktrees relocation. homedir seams $HOME;
77
+ // mkdirp is a plain recursive create (NO chmod of existing parents — the
78
+ // ~/.borg credentials file's perms stay untouched).
79
+ homedir: () => osHomedir(),
80
+ mkdirp: (dir) => mkdirSync(dir, { recursive: true }),
81
+ exec: (cmd, args, cwd, env) =>
82
+ new Promise<number>((resolveExit, rejectExit) => {
83
+ // assimilate-cmd builds the complete child env before calling exec. Use
84
+ // it directly so caller-selected runtime values are not overwritten.
85
+ const child = spawnChild(cmd, args, {
86
+ cwd,
87
+ stdio: 'inherit',
88
+ shell: false,
89
+ env: env ?? process.env,
90
+ });
91
+ child.on('error', rejectExit);
92
+ child.on('exit', (code) => resolveExit(code ?? 0));
93
+ }),
94
+
95
+ stderr: (line) => process.stderr.write(line),
96
+ stdout: (line) => process.stdout.write(line),
97
+ prompt: async (message: string): Promise<string> => {
98
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
99
+ try {
100
+ return await rl.question(message);
101
+ } finally {
102
+ rl.close();
103
+ }
104
+ },
105
+ promptSecret: async (message: string): Promise<string> => {
106
+ const result = await prompts({
107
+ type: 'password',
108
+ name: 'invitation',
109
+ message,
110
+ });
111
+ return typeof result.invitation === 'string' ? result.invitation : '';
112
+ },
113
+ isTTY: () => process.stdin.isTTY === true,
114
+ resolveCliApprovals: (cli, cwd) => resolveLaunchBorgApprovals(
115
+ cli,
116
+ defaultApprovalIo(
117
+ async (message) => {
118
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
119
+ try {
120
+ return await rl.question(message);
121
+ } finally {
122
+ rl.close();
123
+ }
124
+ },
125
+ () => process.stdin.isTTY === true,
126
+ { cwd, env: process.env, codexArgs: [] }
127
+ )
128
+ ),
129
+
130
+ getHostname: () => osHostname(),
131
+ setTerminalTitle: (label, cubeName) => {
132
+ setTitle({ label, cubeName }, cubeName);
133
+ },
134
+
135
+ getActiveCube: () => cubesGetActive(),
136
+ hasPersistedActiveCube: () => cubesHasPersistedActive(),
137
+ readPersistedLocalSeat: () => readPersistedLocalSeat(),
138
+ peekServerSessionRecord: async (credentialRef, binding) =>
139
+ (await observeSeat(credentialRef, binding)).state !== 'absent',
140
+ // CR#3: recover an in-flight implicit-sibling attempt (unbound pending sibling
141
+ // record) by source repo, so a rerun re-derives the EXACT seat + reuses the bearer.
142
+ findIncompleteSiblingAttempt: async (binding) => {
143
+ const record = await findIncompleteSiblingAttempt(binding);
144
+ if (!record) return null;
145
+ return { operation: record.operation, roleId: record.roleId, credentialRef: seatRef(record) };
146
+ },
147
+ probeSeat: (sessionToken, apiUrl, serverTrustIdentity) =>
148
+ defaultProbeSeat(sessionToken, apiUrl, serverTrustIdentity),
149
+ setActiveCube: (a) => cubesSetActive(a),
150
+ // Single-store FINALIZE: the merged activate+bind (reached via the injected
151
+ // `activate` thunk from sendBorgServerAttach) stamps the exact
152
+ // digest-matched PENDING record ACTIVE and binds the decided worktree in ONE
153
+ // atomic commit. PREPARE-time revalidation already ran (prepareSeat), so the
154
+ // only outcomes here are committed or a post-mint activation failure (CR#5:
155
+ // missing/replaced/throw → the pending record is the rerunnable locator; the
156
+ // caller PRESERVES the worktree). expectation-mismatch is produced upstream at
157
+ // PREPARE (result.prepareAborted), never here.
158
+ finalizeServerSeat: async ({ active, activate }) => {
159
+ const binding = {
160
+ worktree: cubesFindProjectRoot(process.cwd()),
161
+ name: active.name,
162
+ droneLabel: active.droneLabel,
163
+ ...(active.roleName !== undefined ? { roleName: active.roleName } : {}),
164
+ ...(active.roleClass !== undefined ? { roleClass: active.roleClass } : {}),
165
+ ...(active.isHumanSeat !== undefined ? { isHumanSeat: active.isHumanSeat } : {}),
166
+ };
167
+ let outcome: ActivateSeatOutcome;
168
+ try {
169
+ outcome = (await activate(binding)) as ActivateSeatOutcome;
170
+ } catch {
171
+ return { committed: false, reason: 'activation-failed' };
172
+ }
173
+ return outcome === 'activated'
174
+ ? { committed: true }
175
+ : { committed: false, reason: 'activation-failed' };
176
+ },
177
+ findProjectRoot: (cwd) => cubesFindProjectRoot(cwd),
178
+
179
+ // gh#673 P2 (WI-1): project-local SessionStart hook for the launch root.
180
+ installProjectSessionHook: (projectRoot) => {
181
+ addProjectSessionStartHook(projectRoot);
182
+ },
183
+
184
+ // #1015: discovery is advisory but still verifies the server-owned CA.
185
+ detectLocalServer: async () =>
186
+ (await probeLocalBorgServer(DEFAULT_LOCAL_SERVER_ORIGIN))
187
+ ? DEFAULT_LOCAL_SERVER_ORIGIN
188
+ : null,
189
+ connectServer: async (apiUrl, enrollment) => {
190
+ if (enrollment) {
191
+ return enrollLocalBorgServer(apiUrl, enrollment.invitation, {
192
+ clientName: osHostname(),
193
+ });
194
+ }
195
+ return connectLocalBorgServer(apiUrl);
196
+ },
197
+ resumeServerEnrollment: async (apiUrl, onPending) =>
198
+ resumeLocalBorgServerEnrollment(apiUrl, {
199
+ ...(onPending === undefined ? {} : { onPending }),
200
+ }),
201
+
202
+ listCubes: async (apiUrl, token, serverTrustIdentity) => {
203
+ if (serverTrustIdentity === undefined) {
204
+ throw new Error('Selected Borg server authority state is missing or unreadable');
205
+ }
206
+ const { cubes } = await remoteListCubes({
207
+ apiUrl,
208
+ authToken: token,
209
+ serverTrustIdentity,
210
+ });
211
+ return cubes.map((c: any) => ({ id: c.id, name: c.name }));
212
+ },
213
+ getCube: async (apiUrl, token, cubeId, serverTrustIdentity) => {
214
+ if (serverTrustIdentity === undefined) {
215
+ throw new Error('Selected Borg server authority state is missing or unreadable');
216
+ }
217
+ // BUG-2 fix (v0.9.2): remote-client now unwraps the server's
218
+ // `{cube, roles, drones}` shape, so the returned object is
219
+ // already flat with id/name/roles at the top level.
220
+ const cube = await remoteGetCube(cubeId, {
221
+ apiUrl,
222
+ authToken: token,
223
+ serverTrustIdentity,
224
+ });
225
+ return {
226
+ id: cube.id,
227
+ name: cube.name,
228
+ roles: cube.roles,
229
+ drones: (cube.drones ?? []).map((d: { role_id: string }) => ({ role_id: d.role_id })),
230
+ };
231
+ },
232
+ createCube: async (apiUrl, token, params, serverTrustIdentity) => {
233
+ if (serverTrustIdentity === undefined) {
234
+ throw new Error('Selected Borg server authority state is missing or unreadable');
235
+ }
236
+ {
237
+ if (!params.name || !params.projectRoot) {
238
+ throw new Error('Local Borg server cube creation requires a repository name and root');
239
+ }
240
+ const created = await createLocalBorgServerCube(
241
+ apiUrl,
242
+ serverTrustIdentity,
243
+ token,
244
+ { projectRoot: params.projectRoot, name: params.name },
245
+ );
246
+ const cube = await remoteGetCube(created.cube_id, {
247
+ apiUrl,
248
+ authToken: token,
249
+ serverTrustIdentity,
250
+ });
251
+ if (
252
+ cube.id !== created.cube_id ||
253
+ !Array.isArray(cube.roles) ||
254
+ !cube.roles.some((role: { id?: string }) => role.id === created.default_worker_role_id)
255
+ ) {
256
+ throw new Error('Borg server returned cube details outside the creation result');
257
+ }
258
+ return {
259
+ id: cube.id,
260
+ name: cube.name,
261
+ roles: cube.roles,
262
+ drones: cube.drones ?? [],
263
+ };
264
+ }
265
+ },
266
+ assimilate: async (apiUrl, token, params, serverTrustIdentity) => {
267
+ if (serverTrustIdentity === undefined) {
268
+ throw new Error('Selected Borg server authority state is missing or unreadable');
269
+ }
270
+ {
271
+ if (params.session_operation === undefined) {
272
+ throw new Error('Borg server attach operation identity is missing');
273
+ }
274
+ const operation = params.session_operation;
275
+ const trust = await loadBorgServerTrust(apiUrl);
276
+ if (trust.identity !== serverTrustIdentity) {
277
+ throw new Error('Borg server trust identity changed; refusing the attach');
278
+ }
279
+ // CR #1: MINT under the SINGLE store flock via prepareSeat, which REVALIDATES
280
+ // the typed prepare-time expectation and mints the PENDING record in one lock
281
+ // hold — so a reset/writer that wins before PREPARE aborts the attach here
282
+ // before any credential is created or sent. Eviction-remint discards the
283
+ // known-invalid saved record (scrubBeforeMint) in the same flock. A fresh
284
+ // sibling spawn has no in-place binding to revalidate, so it passes
285
+ // revalidate:false but still mints under the one flock (never a bypass). The
286
+ // client CSPRNG-generates the bearer here; a lost-response retry / crash-in-gap
287
+ // is recovered by prepareSeat's idempotent mint-or-reuse (identical bearer).
288
+ const seed = {
289
+ origin: apiUrl,
290
+ trustIdentity: serverTrustIdentity,
291
+ cubeId: params.cube_id,
292
+ roleId: params.role_id,
293
+ operation,
294
+ credential: randomBytes(32).toString('base64url'),
295
+ };
296
+ const preparedMint = await prepareSeat({
297
+ expected: params.session_expected ?? { kind: 'absent' },
298
+ revalidate: params.revalidate_at_prepare === true && params.session_expected !== undefined,
299
+ scrubBeforeMint: params.remint_invalid_prior === true,
300
+ seed,
301
+ });
302
+ if (!preparedMint.ok) {
303
+ return {
304
+ cube_id: params.cube_id,
305
+ drone_id: '',
306
+ drone_label: '',
307
+ role_id: params.role_id,
308
+ prepareAborted: true,
309
+ };
310
+ }
311
+ const pending = preparedMint.record;
312
+ // Network only — the pending→ACTIVE flip + worktree BIND are merged into the
313
+ // single-store activate+bind op, deferred to the FINALIZE thunk so the
314
+ // binding lands ATOMICALLY WITH activation (ACTIVE-without-binding unreachable).
315
+ const prepared = await sendBorgServerAttach(
316
+ apiUrl,
317
+ serverTrustIdentity,
318
+ token,
319
+ {
320
+ cubeId: params.cube_id,
321
+ roleId: params.role_id,
322
+ operation,
323
+ ...(params.prior_drone_id === undefined
324
+ ? {}
325
+ : { priorDroneId: params.prior_drone_id }),
326
+ },
327
+ pending.credential,
328
+ { fetchImpl: trust.fetchImpl },
329
+ );
330
+ if (params.prior_drone_id) {
331
+ // The seat identity did not match the reattach/remint intent. Scrub the
332
+ // caller's own pending record (no-op unless it is still ours + pending)
333
+ // before surfacing, so no orphan pending lingers.
334
+ if (params.remint_invalid_prior) {
335
+ if (prepared.result !== 'created' || prepared.drone.id === params.prior_drone_id) {
336
+ await prepared.scrubPending();
337
+ throw new Error('Borg server did not remint a fresh seat after eviction');
338
+ }
339
+ } else if (prepared.result !== 'reused' || prepared.drone.id !== params.prior_drone_id) {
340
+ await prepared.scrubPending();
341
+ throw new BorgServerError(
342
+ 'ATTACH_CONFLICT',
343
+ 'Borg server did not reattach the saved seat',
344
+ );
345
+ }
346
+ }
347
+ return {
348
+ cube_id: prepared.cube.id,
349
+ drone_id: prepared.drone.id,
350
+ drone_label: prepared.drone.label,
351
+ role_id: prepared.role.id,
352
+ result: prepared.result,
353
+ local_session: {
354
+ credential_ref: prepared.credentialRef,
355
+ expires_at: prepared.session.expiresAt,
356
+ },
357
+ // Handles for the cube-lock-held FINALIZE (assimilate-cmd Step 8).
358
+ finalize: {
359
+ activate: prepared.activate,
360
+ scrubPending: prepared.scrubPending,
361
+ // CR#2: bind the surviving PENDING record to the preserved worktree on an
362
+ // activation failure so the rerun-from-there resumes the exact operation.
363
+ bindPending: prepared.bindPending,
364
+ },
365
+ };
366
+ }
367
+
368
+ },
369
+ // CR-PE-F1 wiring (Phase E merge brought this seam in): compute the
370
+ // inbox file path used by the borg-inbox-monitor command in step 8
371
+ // kickoff. Pure helper from cubes.ts; same computation claude.ts uses.
372
+ getInboxPath: (cubeId, droneId) => inboxPathForDrone(cubeId, droneId),
373
+
374
+ // BUG-5 / v0.9.3 wiring: spawn `borg-mcp` as a stdio child, send
375
+ // an initialize request, await a response (or timeout), then kill
376
+ // the child. Probe success indicates the MCP server starts cleanly
377
+ // and tools/list is reachable; failure means the launched Claude
378
+ // session will hit the race and need the kickoff prompt's
379
+ // ToolSearch recovery clause.
380
+ probeMcpReady: () =>
381
+ new Promise<boolean>((resolveProbe) => {
382
+ // gh#client#18: use absolute path to THIS installation's binary so the
383
+ // readiness probe starts the same server version that will be registered.
384
+ const child = spawnChild(resolveMcpBinaryPath(), [], {
385
+ stdio: ['pipe', 'pipe', 'pipe'],
386
+ shell: false,
387
+ env: readinessProbeEnv(),
388
+ });
389
+ let buffer = '';
390
+ let settled = false;
391
+ const settle = (result: boolean) => {
392
+ if (settled) return;
393
+ settled = true;
394
+ try { child.kill('SIGTERM'); } catch { /* ignore */ }
395
+ resolveProbe(result);
396
+ };
397
+ const timeout = setTimeout(() => settle(false), 2000);
398
+ child.on('error', () => { clearTimeout(timeout); settle(false); });
399
+ child.on('exit', () => { clearTimeout(timeout); settle(settled); });
400
+ child.stdout?.on('data', (chunk: Buffer) => {
401
+ buffer += chunk.toString('utf-8');
402
+ // initialize response contains "result" with "protocolVersion"
403
+ // and "capabilities". Light parse: line-buffered JSON-RPC.
404
+ for (const line of buffer.split('\n')) {
405
+ if (line.includes('"protocolVersion"') && line.includes('"result"')) {
406
+ clearTimeout(timeout);
407
+ settle(true);
408
+ return;
409
+ }
410
+ }
411
+ });
412
+ const initReq = JSON.stringify({
413
+ jsonrpc: '2.0',
414
+ id: 1,
415
+ method: 'initialize',
416
+ params: {
417
+ protocolVersion: '2024-11-05',
418
+ capabilities: {},
419
+ clientInfo: { name: 'borg-assimilate-probe', version: '0.9.3' },
420
+ },
421
+ });
422
+ try {
423
+ child.stdin?.write(initReq + '\n');
424
+ } catch {
425
+ clearTimeout(timeout);
426
+ settle(false);
427
+ }
428
+ }),
429
+ resolveCli: (explicit) => resolveCliChoice(
430
+ explicit,
431
+ defaultCliChoiceDeps(async (message) => {
432
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
433
+ try {
434
+ return await rl.question(message);
435
+ } finally {
436
+ rl.close();
437
+ }
438
+ }, () => process.stdin.isTTY === true)
439
+ ),
440
+ prepareCodexRemoteLaunch: () => prepareCodexRemoteLaunch(defaultCodexRemoteDeps()),
441
+ setCodexWakeTarget,
442
+ findLoadedCodexThread,
443
+ };
444
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * gh#780 option (ii): in-session borg_assimilate is RE-ATTACH-ONLY
3
+ * (Queen ruling 33a62d94).
4
+ *
5
+ * Root-cause context: the retired attach path always
6
+ * mints a new drone row — so agents
7
+ * "recovering" from auth blips spawned orphan seats. The tool is now
8
+ * structurally incapable of minting: it either re-attaches to the
9
+ * worktree's saved identity (cubes.json) or refuses with CLI guidance.
10
+ * Seat CREATION stays in the CLI (`borg assimilate` in a terminal), where
11
+ * worktree spawn + identity persistence are handled coherently.
12
+ */
13
+
14
+ import type { ActiveCube } from './cubes.js';
15
+
16
+ export type AssimilateDecision =
17
+ | { kind: 'reattach' }
18
+ | { kind: 'no-identity' }
19
+ | { kind: 'different-cube'; activeCubeName: string };
20
+
21
+ /** Cube names are lowercase server-side; tolerate caller case/whitespace. */
22
+ function normalizeCubeName(name: string): string {
23
+ return name.trim().toLowerCase();
24
+ }
25
+
26
+ /**
27
+ * Classify an in-session assimilate request against the worktree's saved
28
+ * identity. Pure — I/O and rendering stay outside this helper.
29
+ */
30
+ export function classifyInSessionAssimilate(
31
+ active: Pick<ActiveCube, 'name'> | null,
32
+ requestedCubeName: string
33
+ ): AssimilateDecision {
34
+ if (!active) return { kind: 'no-identity' };
35
+ if (normalizeCubeName(active.name) === normalizeCubeName(requestedCubeName)) {
36
+ return { kind: 'reattach' };
37
+ }
38
+ return { kind: 'different-cube', activeCubeName: active.name };
39
+ }
40
+
41
+ /**
42
+ * Agent-facing refusal text for the two non-reattach decisions. Always
43
+ * directs seat creation to the CLI; never suggests an in-session mint.
44
+ */
45
+ export function reattachOnlyRefusal(
46
+ decision: Exclude<AssimilateDecision, { kind: 'reattach' }>,
47
+ requestedCubeName: string
48
+ ): string {
49
+ if (decision.kind === 'no-identity') {
50
+ return (
51
+ `◼ This session has no drone seat for this worktree, and in-session borg_assimilate is ` +
52
+ `re-attach-only (it never creates seats — gh#780). To create a seat for cube ` +
53
+ `"${requestedCubeName}", run \`borg assimilate\` in a terminal — it spawns the worktree, ` +
54
+ `persists the identity, and launches the agent in one step.`
55
+ );
56
+ }
57
+ return (
58
+ `◼ This worktree is attached to cube "${decision.activeCubeName}"; in-session ` +
59
+ `borg_assimilate is re-attach-only and cannot switch to "${requestedCubeName}" (gh#780). ` +
60
+ `To work in "${requestedCubeName}", run \`borg assimilate\` in a terminal from that ` +
61
+ `project (or spawn a fresh worktree for it).`
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Failure advice when a re-attach's server-validated calls fail.
67
+ *
68
+ * The seat is unreachable (evicted seat, revoked session, dead cube):
69
+ * surface the server error verbatim plus CLI guidance. NEVER advise an
70
+ * in-session re-mint (SR cond-4: no fabricated success, lean on server
71
+ * re-validation).
72
+ */
73
+ export function reattachFailureMessage(error: {
74
+ name?: string;
75
+ message?: string;
76
+ }): string {
77
+ const detail = error.message ?? String(error);
78
+ return (
79
+ `◼ Re-attach failed — this worktree's saved seat is unreachable (likely evicted or its ` +
80
+ `session was revoked). Server said: ${detail}\n` +
81
+ `Recover by running \`borg assimilate\` in a terminal to create a fresh seat; ` +
82
+ `in-session borg_assimilate never re-mints (gh#780).`
83
+ );
84
+ }
@@ -0,0 +1,39 @@
1
+ // Pedagogical hint rendered to stdout immediately after assimilation
2
+ // completes, before Claude Code launches. The cube-agnostic shape points
3
+ // the user at borg_whoami / borg_roster rather than embedding role-specific
4
+ // text — so
5
+ // cubes using non-default templates (writers-room, ops, etc.) render identically
6
+ // to software-dev cubes. The concrete drone, role seat, and cube names come from
7
+ // the completed server attach response.
8
+ //
9
+ // Color: ANSI green on the ✓ glyph only; gated on the caller's useColor
10
+ // boolean (computed from process.stdout.isTTY && !NO_COLOR && !CI in the
11
+ // assimilate-cmd call site). Body text carries no ANSI by design — color
12
+ // is hierarchy-cueing, not decoration.
13
+
14
+ const GREEN = '\x1b[32m';
15
+ const RESET = '\x1b[0m';
16
+
17
+ export function renderAssimilationWelcome(
18
+ droneLabel: string,
19
+ roleName: string,
20
+ cubeName: string,
21
+ useColor: boolean,
22
+ localApiUrl?: string,
23
+ ): string {
24
+ const check = useColor ? `${GREEN}✓${RESET}` : '✓';
25
+ const teammateLines = localApiUrl === undefined
26
+ ? [`Add a teammate: run \`borg assimilate <role>\` in another terminal.`]
27
+ : [
28
+ `Add a teammate from the intended worktree:`,
29
+ `run \`borg assimilate --host ${localApiUrl} <role>\` in another terminal.`,
30
+ ];
31
+ return [
32
+ `${check} Attached \`${droneLabel}\` to \`${roleName}\` in cube \`${cubeName}\`.`,
33
+ ``,
34
+ `In the launched agent, run \`borg_whoami\` and \`borg_roster\` to verify the seat`,
35
+ `and begin coordinating.`,
36
+ ...teammateLines,
37
+ ``,
38
+ ].join('\n');
39
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Local environment / capability primitives.
3
+ *
4
+ * Pure helpers kept free of side effects (env is injected) so the decision logic
5
+ * is unit-testable. (The OS-keychain availability probe was removed with the
6
+ * Queen rescope — credentials now rest in the 0600 file store, so there is no
7
+ * keychain to probe.)
8
+ */
9
+
10
+ /**
11
+ * A BORG_* toggle is "on" only when present and not one of the falsy
12
+ * spellings. Mirrors how the rest of the client reads boolean env vars:
13
+ * an unset var and the explicit "0"/"false"/"" spellings are all off.
14
+ * Used by the gh#673 launch-gate (BORG_SESSION) so every BORG_* boolean
15
+ * reads through one convention.
16
+ */
17
+ export function envToggleOn(value: string | undefined): boolean {
18
+ if (value === undefined) return false;
19
+ const v = value.trim().toLowerCase();
20
+ return v !== '' && v !== '0' && v !== 'false' && v !== 'no';
21
+ }
@@ -0,0 +1,18 @@
1
+ // gh#556 Part 2 — pastelist backend (never-fail floor, spec §4.3).
2
+ // Prints copy-pasteable `cd <dir> && borg assimilate --here` lines.
3
+
4
+ import type { DroneCandidate } from '../launch-all-discovery.js';
5
+ import type { LaunchAllDeps } from '../launch-all-deps.js';
6
+ import { buildLaunchCommand } from '../launch-all-command.js';
7
+
8
+ export function runPastelistBackend(
9
+ candidates: DroneCandidate[],
10
+ borgPath: string,
11
+ deps: LaunchAllDeps
12
+ ): void {
13
+ deps.stdout('# borg launch-all: open each worktree in a terminal window and run:\n\n');
14
+ for (const c of candidates) {
15
+ // No keep-open-on-fail: the operator owns their own shell in pastelist mode.
16
+ deps.stdout(buildLaunchCommand(c.worktreeDir, borgPath) + '\n');
17
+ }
18
+ }
@@ -0,0 +1,75 @@
1
+ // gh#556 Part 2 — tmux backend (primary, spec §4.1).
2
+ // One detached `borg-<cube>` session, one window per drone, each running
3
+ // `borg assimilate --here`. Idempotent: reuses an existing session (adds windows).
4
+
5
+ import type { DroneCandidate } from '../launch-all-discovery.js';
6
+ import type { LaunchAllDeps } from '../launch-all-deps.js';
7
+ import { buildLaunchCommand } from '../launch-all-command.js';
8
+ import { writeLockMarker } from '../launch-all-locks.js';
9
+
10
+ export interface TmuxOpts {
11
+ sessionName: string;
12
+ borgPath: string;
13
+ /** 'attach' = attach-session; 'switch' = switch-client (nested tmux); 'none' = skip. */
14
+ attachMode: 'attach' | 'switch' | 'none';
15
+ /** ISO-8601 captured before the first send-keys (lock-marker launchedAt). */
16
+ launchedAtISO: string;
17
+ /** Stagger between drone launches (ms) to avoid the rate limiter; 0 disables. */
18
+ launchDelayMs: number;
19
+ /** Injectable sleep (real setTimeout in prod; no-op spy in tests). */
20
+ sleep: (ms: number) => Promise<void>;
21
+ }
22
+
23
+ export async function runTmuxBackend(
24
+ candidates: DroneCandidate[],
25
+ opts: TmuxOpts,
26
+ deps: LaunchAllDeps
27
+ ): Promise<void> {
28
+ const { sessionName, borgPath, attachMode, launchedAtISO, launchDelayMs, sleep } = opts;
29
+
30
+ const sessionExists = deps.runSyncExitCode('tmux', ['has-session', '-t', sessionName]) === 0;
31
+
32
+ for (let i = 0; i < candidates.length; i++) {
33
+ const c = candidates[i];
34
+
35
+ // Rate-limit stagger: wait BEFORE each launch after the first, so a fleet's
36
+ // agents don't all bootstrap (assimilate + regen + roster) simultaneously and
37
+ // trip the per-user/IP limiter. Window creation is ~instant otherwise, so
38
+ // without this N agents start within ~100ms. 0 → no delay (first never waits).
39
+ if (i > 0 && launchDelayMs > 0) {
40
+ await sleep(launchDelayMs);
41
+ }
42
+
43
+ // CR f98b2d70: target windows by their CAPTURED id (`-P -F '#{window_id}'` prints
44
+ // e.g. `@3`), NOT a computed 0-based index. Index-targeting hard-failed under the
45
+ // common `set -g base-index 1` config (new-session creates window 1, not 0) and was
46
+ // fragile to index gaps on the reuse path. Capturing the real id is base-index-proof
47
+ // and gap-proof. new-session AND new-window both print the new window's id with -P -F.
48
+ let windowId: string;
49
+ if (i === 0 && !sessionExists) {
50
+ windowId = deps
51
+ .runSync('tmux', ['new-session', '-d', '-P', '-F', '#{window_id}', '-s', sessionName, '-c', c.worktreeDir])
52
+ .trim();
53
+ } else {
54
+ windowId = deps
55
+ .runSync('tmux', ['new-window', '-P', '-F', '#{window_id}', '-t', sessionName, '-c', c.worktreeDir])
56
+ .trim();
57
+ }
58
+
59
+ // tmux-level window name (status bar). The agent's own OSC-0 may update it later.
60
+ deps.runSync('tmux', ['rename-window', '-t', windowId, c.droneLabel]);
61
+
62
+ const cmd = buildLaunchCommand(c.worktreeDir, borgPath, { keepOpenOnFail: true });
63
+ deps.runSync('tmux', ['send-keys', '-t', windowId, cmd, 'Enter']);
64
+
65
+ // Lock marker written immediately after dispatch (best-effort liveness signal).
66
+ writeLockMarker(deps, c.cubeId, c.droneLabel, c.worktreeDir, launchedAtISO);
67
+ }
68
+
69
+ if (attachMode === 'switch') {
70
+ deps.attachInteractive('tmux', ['switch-client', '-t', sessionName]);
71
+ } else if (attachMode === 'attach') {
72
+ deps.attachInteractive('tmux', ['attach-session', '-t', sessionName]);
73
+ }
74
+ // 'none' → caller prints the cheat-sheet instead.
75
+ }