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,118 @@
1
+ // gh#556 Part 2 — drone-worktree discovery for `borg launch-all`.
2
+ //
3
+ // Scheme-agnostic: enumerates `git worktree list --porcelain` (the authoritative
4
+ // source covering BOTH old sibling paths and new ~/.borg/worktrees/<repo>/<name>
5
+ // paths from Part 1) and cross-references ~/.config/borgmcp/cubes.json, keeping
6
+ // only worktrees whose saved identity has cubeId === target.
7
+
8
+ import type { LaunchAllDeps, RunSyncFn } from './launch-all-deps.js';
9
+
10
+ export interface DroneCandidate {
11
+ worktreeDir: string;
12
+ cubeId: string;
13
+ droneId: string;
14
+ droneLabel: string;
15
+ sessionToken: string;
16
+ apiUrl: string;
17
+ serverTrustIdentity?: string;
18
+ }
19
+
20
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
21
+ function isUuid(s: string): boolean {
22
+ return UUID_RE.test(s);
23
+ }
24
+
25
+ /**
26
+ * --only TIER-1 (local, no server call): exact case-insensitive droneLabel match,
27
+ * OR droneLabel prefix match (`--only drone` matches `drone-1`, `drone-2`, ...).
28
+ * Tier-2 (role-name) matching is best-effort in the orchestrator (spec §8.4).
29
+ */
30
+ export function matchesOnlyLabel(droneLabel: string, only: string): boolean {
31
+ const l = droneLabel.toLowerCase();
32
+ const o = only.toLowerCase();
33
+ return l === o || l.startsWith(o);
34
+ }
35
+
36
+ /**
37
+ * Enumerate the LINKED worktree paths from `git worktree list --porcelain`,
38
+ * dropping the main worktree (always block[0]). Throws a user-readable error if
39
+ * the command fails (not inside a git repo).
40
+ */
41
+ export function enumerateLinkedWorktrees(runSync: RunSyncFn): string[] {
42
+ let raw: string;
43
+ try {
44
+ raw = runSync('git', ['worktree', 'list', '--porcelain']);
45
+ } catch (e) {
46
+ throw new Error(
47
+ `launch-all: git worktree list failed — must be run from inside a git repository\n` +
48
+ ` (inner: ${e instanceof Error ? e.message : String(e)})`
49
+ );
50
+ }
51
+ const blocks = raw.trim().split(/\n\n+/);
52
+ // blocks[0] is the main worktree — always drop it.
53
+ return blocks
54
+ .slice(1)
55
+ .map((block) => {
56
+ const m = block.match(/^worktree (.+)$/m);
57
+ return m ? m[1].trim() : null;
58
+ })
59
+ .filter((p): p is string => p !== null);
60
+ }
61
+
62
+ export interface DiscoverOpts {
63
+ targetCubeId: string;
64
+ /** --only filter (tier-1 label match applied here). */
65
+ only?: string;
66
+ }
67
+
68
+ /**
69
+ * Full discovery pipeline (spec §3.5): enumerate → cubes.json lookup → filter
70
+ * (dir-present / has-entry / cubeId-match / UUID-valid / --only) → candidates in
71
+ * stable porcelain order.
72
+ */
73
+ export async function discoverDroneCandidates(
74
+ opts: DiscoverOpts,
75
+ deps: LaunchAllDeps
76
+ ): Promise<DroneCandidate[]> {
77
+ const worktreePaths = enumerateLinkedWorktrees((cmd, args) => deps.runSync(cmd, args));
78
+ const identities = await deps.readAllProjectIdentities();
79
+ const byPath = new Map(identities.map((e) => [e.projectPath, e.cube]));
80
+
81
+ const candidates: DroneCandidate[] = [];
82
+ for (const worktreeDir of worktreePaths) {
83
+ // 1. directory not present on disk (orphaned worktree)
84
+ if (!deps.pathExists(worktreeDir)) {
85
+ deps.stderr(
86
+ `skipping ${worktreeDir}: directory not found (orphaned worktree — run \`git worktree prune\`)\n`
87
+ );
88
+ continue;
89
+ }
90
+ // 2. no cubes.json entry → not a drone worktree; skip silently
91
+ const cube = byPath.get(worktreeDir);
92
+ if (!cube) continue;
93
+ // 3. cubeId mismatch → different cube; skip silently
94
+ if (cube.cubeId !== opts.targetCubeId) continue;
95
+ // 4. malformed entry (cubeId/droneId not UUID) → warn + skip
96
+ if (!isUuid(cube.cubeId) || !isUuid(cube.droneId)) {
97
+ deps.stderr(
98
+ `skipping ${worktreeDir}: its saved seat has a malformed cubeId/droneId — re-assimilate to fix\n`
99
+ );
100
+ continue;
101
+ }
102
+ // 5. --only filter (tier-1 label) → skip silently (counted in the filter report)
103
+ if (opts.only !== undefined && !matchesOnlyLabel(cube.droneLabel, opts.only)) continue;
104
+
105
+ candidates.push({
106
+ worktreeDir,
107
+ cubeId: cube.cubeId,
108
+ droneId: cube.droneId,
109
+ droneLabel: cube.droneLabel,
110
+ sessionToken: cube.sessionToken,
111
+ apiUrl: cube.apiUrl,
112
+ ...(cube.serverTrustIdentity === undefined
113
+ ? {}
114
+ : { serverTrustIdentity: cube.serverTrustIdentity }),
115
+ });
116
+ }
117
+ return candidates;
118
+ }
@@ -0,0 +1,74 @@
1
+ // gh#556 Part 2 — per-worktree launch lock markers (spec §6).
2
+ //
3
+ // Timestamp-based markers (the agent PID is not synchronously available when
4
+ // `borg assimilate --here` is dispatched via tmux send-keys). A marker <5min old
5
+ // means the seat appears live → skip unless --force. Markers are never deleted by
6
+ // launch-all itself; the next invocation sweeps mtime-stale (>5min) markers.
7
+
8
+ import { createHash } from 'node:crypto';
9
+ import { join } from 'node:path';
10
+ import type { LaunchAllDeps } from './launch-all-deps.js';
11
+
12
+ export const LOCK_STALE_MS = 5 * 60 * 1000;
13
+
14
+ /** SHA-1 hex of the worktree abs path → fixed-length collision-safe filename. */
15
+ export function worktreeLockName(absPath: string): string {
16
+ return createHash('sha1').update(absPath, 'utf8').digest('hex');
17
+ }
18
+
19
+ export function locksDir(homeDir: string, cubeId: string): string {
20
+ return join(homeDir, '.config', 'borgmcp', 'locks', cubeId);
21
+ }
22
+
23
+ export function lockPath(homeDir: string, cubeId: string, absPath: string): string {
24
+ return join(locksDir(homeDir, cubeId), worktreeLockName(absPath) + '.pid');
25
+ }
26
+
27
+ export interface LockMarker {
28
+ launchedAt: string;
29
+ droneLabel: string;
30
+ worktreeDir: string;
31
+ }
32
+
33
+ /** Write the launch marker (mkdir -p the cube's locks dir first). Mode 0o600. */
34
+ export function writeLockMarker(
35
+ deps: LaunchAllDeps,
36
+ cubeId: string,
37
+ droneLabel: string,
38
+ worktreeDir: string,
39
+ launchedAtISO: string
40
+ ): void {
41
+ deps.mkdirp(locksDir(deps.homedir(), cubeId));
42
+ const marker: LockMarker = { launchedAt: launchedAtISO, droneLabel, worktreeDir };
43
+ deps.writeFile(lockPath(deps.homedir(), cubeId, worktreeDir), JSON.stringify(marker), 0o600);
44
+ }
45
+
46
+ /** Delete mtime-stale (>5min) `.pid` markers in locks/<cubeId>/ (crash cleanup). */
47
+ export function sweepStaleLocks(deps: LaunchAllDeps, cubeId: string, nowMs: number): void {
48
+ const dir = locksDir(deps.homedir(), cubeId);
49
+ for (const name of deps.listDir(dir)) {
50
+ if (!name.endsWith('.pid')) continue;
51
+ const p = join(dir, name);
52
+ const mtime = deps.statMtime(p);
53
+ if (mtime !== null && nowMs - mtime > LOCK_STALE_MS) deps.unlinkOpt(p);
54
+ }
55
+ }
56
+
57
+ /** True iff a fresh (<=5min by its launchedAt content) marker exists for the seat. */
58
+ export function isLockLive(
59
+ deps: LaunchAllDeps,
60
+ cubeId: string,
61
+ worktreeDir: string,
62
+ nowMs: number
63
+ ): { live: boolean; launchedAt?: string } {
64
+ const raw = deps.readFileOpt(lockPath(deps.homedir(), cubeId, worktreeDir));
65
+ if (raw === null) return { live: false };
66
+ try {
67
+ const marker = JSON.parse(raw) as LockMarker;
68
+ const t = Date.parse(marker.launchedAt);
69
+ if (!Number.isFinite(t)) return { live: false };
70
+ return { live: nowMs - t <= LOCK_STALE_MS, launchedAt: marker.launchedAt };
71
+ } catch {
72
+ return { live: false };
73
+ }
74
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * gh#673 P1 — the borg-launch ACTIVATION gate (WI-4 + WI-5-claude).
3
+ *
4
+ * The `borg` wrapper sets BORG_SESSION=1 in the agent's launch env
5
+ * (claude.ts / assimilate-cmd.ts launchEnv — same pattern as
6
+ * BORG_CODEX_REMOTE_WAKE). Claude Code spawns the borg-mcp MCP child and
7
+ * runs hook commands with that env inherited, so four activation bins
8
+ * gate on it:
9
+ * 1. the MCP tool surface (index.ts CallTool top) — a vanilla session
10
+ * gets a non-silent re-launch notice per tool, never a half-success;
11
+ * 2. the borg-regen SessionStart hook bin — exit-0 no-op;
12
+ * 3. the borg-clear-rewake clear-only hook bin — exit-0 no-op;
13
+ * 4. the borg-log-audit UserPromptSubmit hook bin — exit-0 no-op.
14
+ * Result: `claude` launched directly is vanilla Claude Code; `borg`
15
+ * launches get the full surface.
16
+ *
17
+ * Codex (V2/V2b probes): codex does NOT forward parent env to MCP
18
+ * children — only the pinned [mcp_servers.borg.env] list — so simple
19
+ * launchEnv inheritance can't deliver the marker there. But a
20
+ * PER-INVOCATION `-c 'mcp_servers.borg.env.BORG_SESSION="1"'` config
21
+ * override DOES reach the child (V2b-proven). The borg wrapper appends
22
+ * `codexBorgSessionConfigArgs()` to every codex launch, so codex rides
23
+ * the exact same isBorgSession() gate: vanilla codex children lack the
24
+ * var and gate dormant; borg-launched children carry it. The codex
25
+ * SessionStart hook bin gates via the codex PROCESS env, which the
26
+ * wrapper's launchEnv marks directly.
27
+ *
28
+ * SR-BINDING (1482e7f9): BORG_SESSION is ACTIVATION-ONLY. It is
29
+ * user-settable by design (manual BORG_SESSION=1 is a supported override
30
+ * for power users/tests) and MUST NEVER be consulted for an access or
31
+ * security decision — server-side drone-session auth is the
32
+ * security boundary and is unchanged by this gate.
33
+ */
34
+
35
+ import { envToggleOn } from './auth-env.js';
36
+ import { debugLog } from './debug.js';
37
+
38
+ export const BORG_SESSION_ENV = 'BORG_SESSION';
39
+
40
+ /** True when this process runs inside a borg-launched session. */
41
+ export function isBorgSession(env: NodeJS.ProcessEnv = process.env): boolean {
42
+ return envToggleOn(env[BORG_SESSION_ENV]);
43
+ }
44
+
45
+ /**
46
+ * The non-silent per-tool notice a vanilla (non-borg-launched) session
47
+ * receives when invoking a borg_* tool. Explains the state — nothing is
48
+ * wrong with the cube — and points at the borg launch path.
49
+ */
50
+ export function borgSessionToolNotice(toolName: string): string {
51
+ return (
52
+ `◼ ${toolName} is inactive: this session wasn't launched via \`borg\`, so the borg ` +
53
+ `coordination surface is dormant (vanilla Claude Code). To use cube coordination, ` +
54
+ `re-launch from a terminal with \`borg\` (or \`borg assimilate\` to set up a seat first). ` +
55
+ `Power users can opt in manually by launching with BORG_SESSION=1.`
56
+ );
57
+ }
58
+
59
+ /**
60
+ * The per-invocation codex config override that injects BORG_SESSION into
61
+ * the codex-spawned borg-mcp child's pinned env (V2b-proven mechanism —
62
+ * inherited env never reaches codex MCP children). Appended to the codex
63
+ * launch args by both launch sites (claude.ts + assimilate-cmd.ts).
64
+ * TOML string value, hence the inner quotes.
65
+ */
66
+ export function codexBorgSessionConfigArgs(): string[] {
67
+ return ['-c', `mcp_servers.borg.env.${BORG_SESSION_ENV}="1"`];
68
+ }
69
+
70
+ /**
71
+ * Gate decision with BORG_DEBUG observability — one funnel so every bin
72
+ * logs refusals the same way.
73
+ */
74
+ export function gateAllowsActivation(surface: string, env: NodeJS.ProcessEnv = process.env): boolean {
75
+ const allowed = isBorgSession(env);
76
+ if (!allowed) {
77
+ debugLog(`launch-gate: ${surface} dormant — BORG_SESSION absent (not a borg-launched session; gh#673)`);
78
+ }
79
+ return allowed;
80
+ }
@@ -0,0 +1,144 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const STATE_FILE = join(homedir(), '.config', 'borgmcp', 'lifecycle-log-state.json');
6
+ const ARRIVAL_DUPLICATE_WINDOW_MS = 10 * 60 * 1000;
7
+
8
+ export type LifecycleSignal = 'arrival' | 'ready';
9
+
10
+ export interface LifecycleLogSubject {
11
+ cubeId: string;
12
+ droneId: string;
13
+ }
14
+
15
+ interface LifecycleStateEntry {
16
+ lastArrival?: {
17
+ message: string;
18
+ at: string;
19
+ };
20
+ idleReady?: {
21
+ message: string;
22
+ open: boolean;
23
+ at: string;
24
+ };
25
+ }
26
+
27
+ interface LifecycleStateFile {
28
+ entries: Record<string, LifecycleStateEntry>;
29
+ }
30
+
31
+ export function lifecycleSignalForMessage(message: string): LifecycleSignal | null {
32
+ if (message.startsWith('ARRIVAL: ')) return 'arrival';
33
+ if (
34
+ message.startsWith('READY: ') &&
35
+ message.includes('capacity clean') &&
36
+ message.includes('awaiting next dispatch')
37
+ ) {
38
+ return 'ready';
39
+ }
40
+ return null;
41
+ }
42
+
43
+ function stateKey(subject: LifecycleLogSubject): string {
44
+ return `${subject.cubeId}:${subject.droneId}`;
45
+ }
46
+
47
+ async function readState(): Promise<LifecycleStateFile> {
48
+ try {
49
+ const raw = await readFile(STATE_FILE, 'utf8');
50
+ const parsed = JSON.parse(raw);
51
+ if (
52
+ parsed &&
53
+ typeof parsed === 'object' &&
54
+ parsed.entries &&
55
+ typeof parsed.entries === 'object' &&
56
+ !Array.isArray(parsed.entries)
57
+ ) {
58
+ return parsed as LifecycleStateFile;
59
+ }
60
+ } catch (err: any) {
61
+ if (err?.code !== 'ENOENT') throw err;
62
+ }
63
+ return { entries: {} };
64
+ }
65
+
66
+ async function writeState(state: LifecycleStateFile): Promise<void> {
67
+ await mkdir(dirname(STATE_FILE), { recursive: true });
68
+ await writeFile(STATE_FILE, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
69
+ }
70
+
71
+ export function shouldSuppressLifecycleLogFromState(
72
+ message: string,
73
+ state: LifecycleStateEntry | undefined,
74
+ nowMs: number = Date.now()
75
+ ): { suppress: boolean; signal: LifecycleSignal | null } {
76
+ const signal = lifecycleSignalForMessage(message);
77
+ if (!signal) return { suppress: false, signal: null };
78
+
79
+ if (signal === 'arrival') {
80
+ const lastArrivalAt = state?.lastArrival?.at
81
+ ? new Date(state.lastArrival.at).getTime()
82
+ : NaN;
83
+ const isRecent =
84
+ Number.isFinite(lastArrivalAt) &&
85
+ nowMs - lastArrivalAt < ARRIVAL_DUPLICATE_WINDOW_MS;
86
+ return {
87
+ suppress: state?.lastArrival?.message === message && isRecent,
88
+ signal,
89
+ };
90
+ }
91
+
92
+ return {
93
+ suppress: state?.idleReady?.open === true && state.idleReady.message === message,
94
+ signal,
95
+ };
96
+ }
97
+
98
+ export async function shouldSuppressLifecycleLog(
99
+ subject: LifecycleLogSubject,
100
+ message: string
101
+ ): Promise<{ suppress: boolean; signal: LifecycleSignal | null }> {
102
+ const state = await readState();
103
+ return shouldSuppressLifecycleLogFromState(
104
+ message,
105
+ state.entries[stateKey(subject)]
106
+ );
107
+ }
108
+
109
+ export function nextLifecycleStateAfterLog(
110
+ message: string,
111
+ current: LifecycleStateEntry | undefined,
112
+ nowIso: string = new Date().toISOString()
113
+ ): LifecycleStateEntry {
114
+ const signal = lifecycleSignalForMessage(message);
115
+ if (signal === 'arrival') {
116
+ return {
117
+ ...current,
118
+ lastArrival: { message, at: nowIso },
119
+ };
120
+ }
121
+ if (signal === 'ready') {
122
+ return {
123
+ ...current,
124
+ idleReady: { message, open: true, at: nowIso },
125
+ };
126
+ }
127
+ if (current?.idleReady?.open) {
128
+ return {
129
+ ...current,
130
+ idleReady: { ...current.idleReady, open: false, at: nowIso },
131
+ };
132
+ }
133
+ return current ?? {};
134
+ }
135
+
136
+ export async function recordLifecycleLog(
137
+ subject: LifecycleLogSubject,
138
+ message: string
139
+ ): Promise<void> {
140
+ const state = await readState();
141
+ const key = stateKey(subject);
142
+ state.entries[key] = nextLifecycleStateAfterLog(message, state.entries[key]);
143
+ await writeState(state);
144
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Sprint 6 / gh#153 — pure render function for `borg_list-roles` MCP tool.
3
+ *
4
+ * Extracted from the inline `case 'borg_list-roles'` handler in
5
+ * `index.ts` per drone-3's QA-FAIL 2026-05-18T13:27:53Z so the render
6
+ * logic is unit-testable without exercising the full MCP tool dispatch
7
+ * stack. The handler simply calls `renderRoleList(roles, cubeId)` and
8
+ * returns its output.
9
+ */
10
+
11
+ export interface RoleForRender {
12
+ id: string;
13
+ name: string;
14
+ short_description?: string;
15
+ is_default?: boolean;
16
+ is_mandatory?: boolean;
17
+ is_human_seat?: boolean;
18
+ can_broadcast?: boolean;
19
+ receives_all_direct?: boolean;
20
+ role_class?: string;
21
+ }
22
+
23
+ /**
24
+ * Render the role registry for a cube as a markdown list with role IDs
25
+ * exposed for use with `borg_reassign-drone`. Returns the empty-roles
26
+ * placeholder when the input array is empty.
27
+ *
28
+ * Each role line shape:
29
+ * - **<name>**(<tags>) `<uuid>` — <description>
30
+ *
31
+ * Tags collected in order: Queen, human-seat, default, mandatory, can-broadcast,
32
+ * receives-all-direct. Joined with `, `; suffix omitted entirely when no tags apply.
33
+ */
34
+ export function renderRoleList(roles: RoleForRender[], cubeId: string): string {
35
+ if (!roles.length) {
36
+ return 'No roles in this cube yet.';
37
+ }
38
+ const lines = roles.map((r) => {
39
+ const tags = [
40
+ r.role_class === 'queen' ? 'Queen' : null,
41
+ r.is_human_seat ? 'human-seat' : null,
42
+ r.is_default ? 'default' : null,
43
+ r.is_mandatory ? 'mandatory' : null,
44
+ r.can_broadcast ? 'can-broadcast' : null,
45
+ r.receives_all_direct ? 'receives-all-direct' : null,
46
+ ].filter(Boolean).join(', ');
47
+ const tagSuffix = tags ? ` (${tags})` : '';
48
+ const desc = r.short_description || '_(no description)_';
49
+ return `- **${r.name}**${tagSuffix} \`${r.id}\` — ${desc}`;
50
+ });
51
+ return `Roles in cube ${cubeId} (${roles.length}):\n\n${lines.join('\n')}\n\nUse the role IDs above with \`borg_reassign-drone\` to change a drone's role.`;
52
+ }
@@ -0,0 +1,106 @@
1
+ import { roleSlug } from './role-resolver.js';
2
+
3
+ export interface LocalRoutingDrone {
4
+ id: string;
5
+ label?: string | null;
6
+ role_id?: string | null;
7
+ }
8
+
9
+ export interface LocalRoutingRole {
10
+ id: string;
11
+ name: string;
12
+ is_human_seat?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Resolve the public `borg_log to:` addressing forms against the local
17
+ * server's authoritative cube roster. This intentionally mirrors the hosted
18
+ * route resolver: exact drone id/label first, then displayed short UUID, then
19
+ * role name/slug expansion. Every failure is closed before the log POST so a
20
+ * miss can never degrade into a broadcast.
21
+ */
22
+ export function resolveLocalLogRecipients(
23
+ recipients: readonly string[],
24
+ drones: readonly LocalRoutingDrone[],
25
+ roles: readonly LocalRoutingRole[],
26
+ ): string[] {
27
+ const ids = new Set<string>();
28
+
29
+ for (const recipient of recipients) {
30
+ if (recipient === '@human-seat') {
31
+ const roleIds = new Set(
32
+ roles.filter((role) => role.is_human_seat === true).map((role) => role.id),
33
+ );
34
+ const matches = drones.filter((drone) =>
35
+ drone.role_id !== null && drone.role_id !== undefined && roleIds.has(drone.role_id)
36
+ );
37
+ if (roleIds.size === 0 || matches.length === 0) {
38
+ throw new Error(
39
+ 'Direct-message role recipient has no active drones: @human-seat. ' +
40
+ 'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.',
41
+ );
42
+ }
43
+ for (const drone of matches) ids.add(drone.id);
44
+ continue;
45
+ }
46
+
47
+ const droneMatches = drones.filter(
48
+ (drone) => drone.id === recipient || drone.label === recipient,
49
+ );
50
+ const uniqueDroneIds = [...new Set(droneMatches.map((drone) => drone.id))];
51
+ if (uniqueDroneIds.length > 1) {
52
+ throw new Error(
53
+ `Ambiguous direct-message recipient: ${recipient} matches multiple drones`,
54
+ );
55
+ }
56
+ if (uniqueDroneIds.length === 1) {
57
+ ids.add(uniqueDroneIds[0]!);
58
+ continue;
59
+ }
60
+
61
+ const shortUuid = recipient.replace(/`/g, '').replace(/^id:/i, '').toLowerCase();
62
+ if (/^[0-9a-f]{8,}$/.test(shortUuid)) {
63
+ const prefixMatches = drones.filter((drone) =>
64
+ drone.id.toLowerCase().startsWith(shortUuid)
65
+ );
66
+ const uniquePrefixIds = [...new Set(prefixMatches.map((drone) => drone.id))];
67
+ if (uniquePrefixIds.length > 1) {
68
+ const listed = prefixMatches
69
+ .map((drone) => `${drone.id} (${drone.label ?? 'unlabeled'})`)
70
+ .join(', ');
71
+ throw new Error(
72
+ `Ambiguous short-uuid recipient: ${recipient} matches multiple drones — ${listed}. ` +
73
+ 'Address by the full drone id.',
74
+ );
75
+ }
76
+ if (uniquePrefixIds.length === 1) {
77
+ ids.add(uniquePrefixIds[0]!);
78
+ continue;
79
+ }
80
+ }
81
+
82
+ const roleMatches = roles.filter((role) => roleSlug(role.name) === roleSlug(recipient));
83
+ const uniqueRoleIds = [...new Set(roleMatches.map((role) => role.id))];
84
+ if (uniqueRoleIds.length > 1) {
85
+ throw new Error(
86
+ `Ambiguous direct-message recipient: ${recipient} matches multiple roles`,
87
+ );
88
+ }
89
+ if (uniqueRoleIds.length === 0) {
90
+ throw new Error(
91
+ `Unknown direct-message recipient: ${recipient}. ` +
92
+ 'Use an exact drone label, drone id, role name, or role slug.',
93
+ );
94
+ }
95
+ const roleDrones = drones.filter((drone) => drone.role_id === uniqueRoleIds[0]);
96
+ if (roleDrones.length === 0) {
97
+ throw new Error(
98
+ `Direct-message role recipient has no active drones: ${recipient}. ` +
99
+ 'Use borg_roster to find active recipients, or omit to broadcast if your role can broadcast.',
100
+ );
101
+ }
102
+ for (const drone of roleDrones) ids.add(drone.id);
103
+ }
104
+
105
+ return [...ids];
106
+ }
@@ -0,0 +1,10 @@
1
+ import { LocalManageRequiredError } from './server-errors.js';
2
+
3
+ export function formatLocalManageToolResult(error: unknown) {
4
+ if (!(error instanceof LocalManageRequiredError)) return null;
5
+
6
+ return {
7
+ content: [{ type: 'text' as const, text: error.message }],
8
+ isError: true as const,
9
+ };
10
+ }