botmux 2.47.0 → 2.47.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 (472) hide show
  1. package/README.en.md +10 -5
  2. package/README.md +10 -5
  3. package/dist/adapters/adopt-route.d.ts +63 -0
  4. package/dist/adapters/adopt-route.d.ts.map +1 -0
  5. package/dist/adapters/adopt-route.js +195 -0
  6. package/dist/adapters/adopt-route.js.map +1 -0
  7. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  8. package/dist/adapters/backend/tmux-backend.js +11 -0
  9. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  10. package/dist/adapters/backend/tmux-pipe-backend.d.ts +11 -0
  11. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-pipe-backend.js +17 -1
  13. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  14. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  15. package/dist/adapters/cli/claude-code.js +36 -9
  16. package/dist/adapters/cli/claude-code.js.map +1 -1
  17. package/dist/adapters/cli/coco.d.ts.map +1 -1
  18. package/dist/adapters/cli/coco.js +26 -1
  19. package/dist/adapters/cli/coco.js.map +1 -1
  20. package/dist/adapters/cli/codex-app.d.ts +4 -0
  21. package/dist/adapters/cli/codex-app.d.ts.map +1 -0
  22. package/dist/adapters/cli/codex-app.js +72 -0
  23. package/dist/adapters/cli/codex-app.js.map +1 -0
  24. package/dist/adapters/cli/codex.d.ts.map +1 -1
  25. package/dist/adapters/cli/codex.js +6 -1
  26. package/dist/adapters/cli/codex.js.map +1 -1
  27. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  28. package/dist/adapters/cli/cursor.js +58 -12
  29. package/dist/adapters/cli/cursor.js.map +1 -1
  30. package/dist/adapters/cli/gemini.d.ts.map +1 -1
  31. package/dist/adapters/cli/gemini.js +5 -1
  32. package/dist/adapters/cli/gemini.js.map +1 -1
  33. package/dist/adapters/cli/hermes.d.ts +4 -0
  34. package/dist/adapters/cli/hermes.d.ts.map +1 -0
  35. package/dist/adapters/cli/hermes.js +40 -0
  36. package/dist/adapters/cli/hermes.js.map +1 -0
  37. package/dist/adapters/cli/mira.d.ts +4 -0
  38. package/dist/adapters/cli/mira.d.ts.map +1 -0
  39. package/dist/adapters/cli/mira.js +67 -0
  40. package/dist/adapters/cli/mira.js.map +1 -0
  41. package/dist/adapters/cli/mtr.d.ts +5 -0
  42. package/dist/adapters/cli/mtr.d.ts.map +1 -0
  43. package/dist/adapters/cli/mtr.js +62 -0
  44. package/dist/adapters/cli/mtr.js.map +1 -0
  45. package/dist/adapters/cli/opencode.d.ts.map +1 -1
  46. package/dist/adapters/cli/opencode.js +19 -1
  47. package/dist/adapters/cli/opencode.js.map +1 -1
  48. package/dist/adapters/cli/registry.d.ts +5 -1
  49. package/dist/adapters/cli/registry.d.ts.map +1 -1
  50. package/dist/adapters/cli/registry.js +22 -2
  51. package/dist/adapters/cli/registry.js.map +1 -1
  52. package/dist/adapters/cli/shared-hints.d.ts +1 -1
  53. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  54. package/dist/adapters/cli/shared-hints.js +2 -1
  55. package/dist/adapters/cli/shared-hints.js.map +1 -1
  56. package/dist/adapters/cli/types.d.ts +35 -2
  57. package/dist/adapters/cli/types.d.ts.map +1 -1
  58. package/dist/adapters/hook-command.d.ts +18 -0
  59. package/dist/adapters/hook-command.d.ts.map +1 -0
  60. package/dist/adapters/hook-command.js +38 -0
  61. package/dist/adapters/hook-command.js.map +1 -0
  62. package/dist/adapters/hook-installer.d.ts +14 -0
  63. package/dist/adapters/hook-installer.d.ts.map +1 -0
  64. package/dist/adapters/hook-installer.js +192 -0
  65. package/dist/adapters/hook-installer.js.map +1 -0
  66. package/dist/bot-registry.d.ts +59 -0
  67. package/dist/bot-registry.d.ts.map +1 -1
  68. package/dist/bot-registry.js +67 -0
  69. package/dist/bot-registry.js.map +1 -1
  70. package/dist/cli/bots-list-output.d.ts +8 -0
  71. package/dist/cli/bots-list-output.d.ts.map +1 -1
  72. package/dist/cli/bots-list-output.js +9 -0
  73. package/dist/cli/bots-list-output.js.map +1 -1
  74. package/dist/cli.d.ts +15 -1
  75. package/dist/cli.d.ts.map +1 -1
  76. package/dist/cli.js +603 -106
  77. package/dist/cli.js.map +1 -1
  78. package/dist/codex-app-runner.d.ts +3 -0
  79. package/dist/codex-app-runner.d.ts.map +1 -0
  80. package/dist/codex-app-runner.js +512 -0
  81. package/dist/codex-app-runner.js.map +1 -0
  82. package/dist/config.d.ts +11 -2
  83. package/dist/config.d.ts.map +1 -1
  84. package/dist/config.js +17 -4
  85. package/dist/config.js.map +1 -1
  86. package/dist/core/ask-api.d.ts +47 -0
  87. package/dist/core/ask-api.d.ts.map +1 -0
  88. package/dist/core/ask-api.js +139 -0
  89. package/dist/core/ask-api.js.map +1 -0
  90. package/dist/core/ask-args.d.ts +53 -0
  91. package/dist/core/ask-args.d.ts.map +1 -0
  92. package/dist/core/ask-args.js +122 -0
  93. package/dist/core/ask-args.js.map +1 -0
  94. package/dist/core/ask-broker.d.ts +98 -0
  95. package/dist/core/ask-broker.d.ts.map +1 -0
  96. package/dist/core/ask-broker.js +329 -0
  97. package/dist/core/ask-broker.js.map +1 -0
  98. package/dist/core/ask-hook/claude-code.d.ts +50 -0
  99. package/dist/core/ask-hook/claude-code.d.ts.map +1 -0
  100. package/dist/core/ask-hook/claude-code.js +145 -0
  101. package/dist/core/ask-hook/claude-code.js.map +1 -0
  102. package/dist/core/ask-hook/codex.d.ts +43 -0
  103. package/dist/core/ask-hook/codex.d.ts.map +1 -0
  104. package/dist/core/ask-hook/codex.js +69 -0
  105. package/dist/core/ask-hook/codex.js.map +1 -0
  106. package/dist/core/ask-hook/opencode.d.ts +41 -0
  107. package/dist/core/ask-hook/opencode.d.ts.map +1 -0
  108. package/dist/core/ask-hook/opencode.js +108 -0
  109. package/dist/core/ask-hook/opencode.js.map +1 -0
  110. package/dist/core/ask-hook/registry.d.ts +3 -0
  111. package/dist/core/ask-hook/registry.d.ts.map +1 -0
  112. package/dist/core/ask-hook/registry.js +12 -0
  113. package/dist/core/ask-hook/registry.js.map +1 -0
  114. package/dist/core/ask-hook/types.d.ts +26 -0
  115. package/dist/core/ask-hook/types.d.ts.map +1 -0
  116. package/dist/core/ask-hook/types.js +2 -0
  117. package/dist/core/ask-hook/types.js.map +1 -0
  118. package/dist/core/ask-types.d.ts +146 -0
  119. package/dist/core/ask-types.d.ts.map +1 -0
  120. package/dist/core/ask-types.js +18 -0
  121. package/dist/core/ask-types.js.map +1 -0
  122. package/dist/core/command-handler.d.ts +20 -0
  123. package/dist/core/command-handler.d.ts.map +1 -1
  124. package/dist/core/command-handler.js +774 -311
  125. package/dist/core/command-handler.js.map +1 -1
  126. package/dist/core/dashboard-ipc-server.d.ts +2 -0
  127. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  128. package/dist/core/dashboard-ipc-server.js +222 -2
  129. package/dist/core/dashboard-ipc-server.js.map +1 -1
  130. package/dist/core/role-resolver.d.ts +17 -1
  131. package/dist/core/role-resolver.d.ts.map +1 -1
  132. package/dist/core/role-resolver.js +64 -10
  133. package/dist/core/role-resolver.js.map +1 -1
  134. package/dist/core/session-discovery.d.ts.map +1 -1
  135. package/dist/core/session-discovery.js +19 -5
  136. package/dist/core/session-discovery.js.map +1 -1
  137. package/dist/core/session-manager.d.ts +1 -1
  138. package/dist/core/session-manager.d.ts.map +1 -1
  139. package/dist/core/session-manager.js +37 -20
  140. package/dist/core/session-manager.js.map +1 -1
  141. package/dist/core/trigger-session.d.ts +9 -0
  142. package/dist/core/trigger-session.d.ts.map +1 -0
  143. package/dist/core/trigger-session.js +158 -0
  144. package/dist/core/trigger-session.js.map +1 -0
  145. package/dist/core/types.d.ts +5 -0
  146. package/dist/core/types.d.ts.map +1 -1
  147. package/dist/core/types.js.map +1 -1
  148. package/dist/core/worker-pool.d.ts +141 -0
  149. package/dist/core/worker-pool.d.ts.map +1 -1
  150. package/dist/core/worker-pool.js +543 -24
  151. package/dist/core/worker-pool.js.map +1 -1
  152. package/dist/daemon.d.ts.map +1 -1
  153. package/dist/daemon.js +213 -58
  154. package/dist/daemon.js.map +1 -1
  155. package/dist/dashboard/auth.d.ts +6 -1
  156. package/dist/dashboard/auth.d.ts.map +1 -1
  157. package/dist/dashboard/auth.js +9 -1
  158. package/dist/dashboard/auth.js.map +1 -1
  159. package/dist/dashboard/connector-api.d.ts +3 -0
  160. package/dist/dashboard/connector-api.d.ts.map +1 -0
  161. package/dist/dashboard/connector-api.js +351 -0
  162. package/dist/dashboard/connector-api.js.map +1 -0
  163. package/dist/dashboard/federated-group-core.d.ts +54 -0
  164. package/dist/dashboard/federated-group-core.d.ts.map +1 -0
  165. package/dist/dashboard/federated-group-core.js +165 -0
  166. package/dist/dashboard/federated-group-core.js.map +1 -0
  167. package/dist/dashboard/federation-api.d.ts +42 -0
  168. package/dist/dashboard/federation-api.d.ts.map +1 -0
  169. package/dist/dashboard/federation-api.js +408 -0
  170. package/dist/dashboard/federation-api.js.map +1 -0
  171. package/dist/dashboard/federation-spoke-api.d.ts +76 -0
  172. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -0
  173. package/dist/dashboard/federation-spoke-api.js +618 -0
  174. package/dist/dashboard/federation-spoke-api.js.map +1 -0
  175. package/dist/dashboard/team-group.d.ts +18 -0
  176. package/dist/dashboard/team-group.d.ts.map +1 -0
  177. package/dist/dashboard/team-group.js +7 -0
  178. package/dist/dashboard/team-group.js.map +1 -0
  179. package/dist/dashboard/trigger-api.d.ts +13 -0
  180. package/dist/dashboard/trigger-api.d.ts.map +1 -0
  181. package/dist/dashboard/trigger-api.js +77 -0
  182. package/dist/dashboard/trigger-api.js.map +1 -0
  183. package/dist/dashboard/web/app.js +8 -0
  184. package/dist/dashboard/web/app.js.map +1 -1
  185. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  186. package/dist/dashboard/web/bot-defaults.js +205 -21
  187. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  188. package/dist/dashboard/web/connectors.d.ts +2 -0
  189. package/dist/dashboard/web/connectors.d.ts.map +1 -0
  190. package/dist/dashboard/web/connectors.js +187 -0
  191. package/dist/dashboard/web/connectors.js.map +1 -0
  192. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  193. package/dist/dashboard/web/i18n.js +43 -5
  194. package/dist/dashboard/web/i18n.js.map +1 -1
  195. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  196. package/dist/dashboard/web/sessions.js +4 -0
  197. package/dist/dashboard/web/sessions.js.map +1 -1
  198. package/dist/dashboard/web/team-federation.d.ts +3 -0
  199. package/dist/dashboard/web/team-federation.d.ts.map +1 -0
  200. package/dist/dashboard/web/team-federation.js +487 -0
  201. package/dist/dashboard/web/team-federation.js.map +1 -0
  202. package/dist/dashboard/web/workflows.js +3 -3
  203. package/dist/dashboard/web/workflows.js.map +1 -1
  204. package/dist/dashboard/webhook-routes.d.ts +19 -0
  205. package/dist/dashboard/webhook-routes.d.ts.map +1 -0
  206. package/dist/dashboard/webhook-routes.js +321 -0
  207. package/dist/dashboard/webhook-routes.js.map +1 -0
  208. package/dist/dashboard/workflow-api.d.ts +8 -1
  209. package/dist/dashboard/workflow-api.d.ts.map +1 -1
  210. package/dist/dashboard/workflow-api.js +19 -4
  211. package/dist/dashboard/workflow-api.js.map +1 -1
  212. package/dist/dashboard-web/app.js +539 -375
  213. package/dist/dashboard-web/index.html +3 -1
  214. package/dist/dashboard-web/style.css +22 -0
  215. package/dist/dashboard.js +199 -2
  216. package/dist/dashboard.js.map +1 -1
  217. package/dist/i18n/en.d.ts.map +1 -1
  218. package/dist/i18n/en.js +104 -11
  219. package/dist/i18n/en.js.map +1 -1
  220. package/dist/i18n/zh.d.ts.map +1 -1
  221. package/dist/i18n/zh.js +104 -11
  222. package/dist/i18n/zh.js.map +1 -1
  223. package/dist/im/lark/ask-card.d.ts +55 -0
  224. package/dist/im/lark/ask-card.d.ts.map +1 -0
  225. package/dist/im/lark/ask-card.js +328 -0
  226. package/dist/im/lark/ask-card.js.map +1 -0
  227. package/dist/im/lark/card-builder.d.ts +108 -3
  228. package/dist/im/lark/card-builder.d.ts.map +1 -1
  229. package/dist/im/lark/card-builder.js +480 -50
  230. package/dist/im/lark/card-builder.js.map +1 -1
  231. package/dist/im/lark/card-handler.d.ts.map +1 -1
  232. package/dist/im/lark/card-handler.js +241 -18
  233. package/dist/im/lark/card-handler.js.map +1 -1
  234. package/dist/im/lark/client.d.ts +83 -0
  235. package/dist/im/lark/client.d.ts.map +1 -1
  236. package/dist/im/lark/client.js +286 -70
  237. package/dist/im/lark/client.js.map +1 -1
  238. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  239. package/dist/im/lark/event-dispatcher.js +29 -4
  240. package/dist/im/lark/event-dispatcher.js.map +1 -1
  241. package/dist/im/lark/grant-command.d.ts +2 -1
  242. package/dist/im/lark/grant-command.d.ts.map +1 -1
  243. package/dist/im/lark/grant-command.js +3 -2
  244. package/dist/im/lark/grant-command.js.map +1 -1
  245. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  246. package/dist/im/lark/identity-cache.js +3 -3
  247. package/dist/im/lark/identity-cache.js.map +1 -1
  248. package/dist/im/lark/md-card.d.ts +20 -2
  249. package/dist/im/lark/md-card.d.ts.map +1 -1
  250. package/dist/im/lark/md-card.js +49 -17
  251. package/dist/im/lark/md-card.js.map +1 -1
  252. package/dist/im/lark/message-parser.d.ts.map +1 -1
  253. package/dist/im/lark/message-parser.js +87 -31
  254. package/dist/im/lark/message-parser.js.map +1 -1
  255. package/dist/im/lark/workflow-card-handler.d.ts +2 -2
  256. package/dist/im/lark/workflow-card-handler.d.ts.map +1 -1
  257. package/dist/im/lark/workflow-card-handler.js +12 -1
  258. package/dist/im/lark/workflow-card-handler.js.map +1 -1
  259. package/dist/im/lark/workflow-progress-card.d.ts.map +1 -1
  260. package/dist/im/lark/workflow-progress-card.js +53 -0
  261. package/dist/im/lark/workflow-progress-card.js.map +1 -1
  262. package/dist/mira-output.d.ts +3 -0
  263. package/dist/mira-output.d.ts.map +1 -0
  264. package/dist/mira-output.js +136 -0
  265. package/dist/mira-output.js.map +1 -0
  266. package/dist/mira-runner.d.ts +3 -0
  267. package/dist/mira-runner.d.ts.map +1 -0
  268. package/dist/mira-runner.js +534 -0
  269. package/dist/mira-runner.js.map +1 -0
  270. package/dist/services/bot-owner-store.d.ts +28 -0
  271. package/dist/services/bot-owner-store.d.ts.map +1 -0
  272. package/dist/services/bot-owner-store.js +82 -0
  273. package/dist/services/bot-owner-store.js.map +1 -0
  274. package/dist/services/bot-profile-store.d.ts +16 -0
  275. package/dist/services/bot-profile-store.d.ts.map +1 -0
  276. package/dist/services/bot-profile-store.js +98 -0
  277. package/dist/services/bot-profile-store.js.map +1 -0
  278. package/dist/services/brand-store.d.ts +15 -0
  279. package/dist/services/brand-store.d.ts.map +1 -0
  280. package/dist/services/brand-store.js +47 -0
  281. package/dist/services/brand-store.js.map +1 -0
  282. package/dist/services/card-prefs-store.d.ts +20 -0
  283. package/dist/services/card-prefs-store.d.ts.map +1 -0
  284. package/dist/services/card-prefs-store.js +82 -0
  285. package/dist/services/card-prefs-store.js.map +1 -0
  286. package/dist/services/codex-bridge-queue.d.ts +1 -0
  287. package/dist/services/codex-bridge-queue.d.ts.map +1 -1
  288. package/dist/services/codex-bridge-queue.js +23 -0
  289. package/dist/services/codex-bridge-queue.js.map +1 -1
  290. package/dist/services/codex-transcript.d.ts +1 -0
  291. package/dist/services/codex-transcript.d.ts.map +1 -1
  292. package/dist/services/codex-transcript.js.map +1 -1
  293. package/dist/services/connector-store.d.ts +58 -0
  294. package/dist/services/connector-store.d.ts.map +1 -0
  295. package/dist/services/connector-store.js +79 -0
  296. package/dist/services/connector-store.js.map +1 -0
  297. package/dist/services/deployment-identity.d.ts +22 -0
  298. package/dist/services/deployment-identity.d.ts.map +1 -0
  299. package/dist/services/deployment-identity.js +67 -0
  300. package/dist/services/deployment-identity.js.map +1 -0
  301. package/dist/services/federation-membership-store.d.ts +23 -0
  302. package/dist/services/federation-membership-store.d.ts.map +1 -0
  303. package/dist/services/federation-membership-store.js +66 -0
  304. package/dist/services/federation-membership-store.js.map +1 -0
  305. package/dist/services/federation-roster.d.ts +54 -0
  306. package/dist/services/federation-roster.d.ts.map +1 -0
  307. package/dist/services/federation-roster.js +51 -0
  308. package/dist/services/federation-roster.js.map +1 -0
  309. package/dist/services/federation-store.d.ts +76 -0
  310. package/dist/services/federation-store.d.ts.map +1 -0
  311. package/dist/services/federation-store.js +133 -0
  312. package/dist/services/federation-store.js.map +1 -0
  313. package/dist/services/grant-store.d.ts +12 -2
  314. package/dist/services/grant-store.d.ts.map +1 -1
  315. package/dist/services/grant-store.js +51 -4
  316. package/dist/services/grant-store.js.map +1 -1
  317. package/dist/services/group-creator.d.ts +10 -0
  318. package/dist/services/group-creator.d.ts.map +1 -1
  319. package/dist/services/group-creator.js +26 -1
  320. package/dist/services/group-creator.js.map +1 -1
  321. package/dist/services/groups-store.d.ts +30 -0
  322. package/dist/services/groups-store.d.ts.map +1 -1
  323. package/dist/services/groups-store.js +85 -12
  324. package/dist/services/groups-store.js.map +1 -1
  325. package/dist/services/hermes-transcript.d.ts +7 -0
  326. package/dist/services/hermes-transcript.d.ts.map +1 -0
  327. package/dist/services/hermes-transcript.js +117 -0
  328. package/dist/services/hermes-transcript.js.map +1 -0
  329. package/dist/services/invite-store.d.ts +28 -0
  330. package/dist/services/invite-store.d.ts.map +1 -0
  331. package/dist/services/invite-store.js +85 -0
  332. package/dist/services/invite-store.js.map +1 -0
  333. package/dist/services/pairing-store.d.ts +47 -0
  334. package/dist/services/pairing-store.d.ts.map +1 -0
  335. package/dist/services/pairing-store.js +132 -0
  336. package/dist/services/pairing-store.js.map +1 -0
  337. package/dist/services/project-scanner.d.ts +10 -0
  338. package/dist/services/project-scanner.d.ts.map +1 -1
  339. package/dist/services/project-scanner.js +11 -0
  340. package/dist/services/project-scanner.js.map +1 -1
  341. package/dist/services/relay-picker.d.ts +22 -0
  342. package/dist/services/relay-picker.d.ts.map +1 -0
  343. package/dist/services/relay-picker.js +62 -0
  344. package/dist/services/relay-picker.js.map +1 -0
  345. package/dist/services/send-policy.d.ts +55 -0
  346. package/dist/services/send-policy.d.ts.map +1 -0
  347. package/dist/services/send-policy.js +47 -0
  348. package/dist/services/send-policy.js.map +1 -0
  349. package/dist/services/session-store.js +1 -1
  350. package/dist/services/session-store.js.map +1 -1
  351. package/dist/services/team-roster.d.ts +38 -0
  352. package/dist/services/team-roster.d.ts.map +1 -0
  353. package/dist/services/team-roster.js +82 -0
  354. package/dist/services/team-roster.js.map +1 -0
  355. package/dist/services/team-store.d.ts +54 -0
  356. package/dist/services/team-store.d.ts.map +1 -0
  357. package/dist/services/team-store.js +156 -0
  358. package/dist/services/team-store.js.map +1 -0
  359. package/dist/services/trigger-log-store.d.ts +46 -0
  360. package/dist/services/trigger-log-store.d.ts.map +1 -0
  361. package/dist/services/trigger-log-store.js +132 -0
  362. package/dist/services/trigger-log-store.js.map +1 -0
  363. package/dist/services/trigger-types.d.ts +57 -0
  364. package/dist/services/trigger-types.d.ts.map +1 -0
  365. package/dist/services/trigger-types.js +28 -0
  366. package/dist/services/trigger-types.js.map +1 -0
  367. package/dist/services/webhook-key.d.ts +16 -0
  368. package/dist/services/webhook-key.d.ts.map +1 -0
  369. package/dist/services/webhook-key.js +123 -0
  370. package/dist/services/webhook-key.js.map +1 -0
  371. package/dist/services/webhook-lifecycle-extractors.d.ts +15 -0
  372. package/dist/services/webhook-lifecycle-extractors.d.ts.map +1 -0
  373. package/dist/services/webhook-lifecycle-extractors.js +59 -0
  374. package/dist/services/webhook-lifecycle-extractors.js.map +1 -0
  375. package/dist/services/webhook-lifecycle-store.d.ts +45 -0
  376. package/dist/services/webhook-lifecycle-store.d.ts.map +1 -0
  377. package/dist/services/webhook-lifecycle-store.js +159 -0
  378. package/dist/services/webhook-lifecycle-store.js.map +1 -0
  379. package/dist/setup/bot-config-editor.d.ts +8 -1
  380. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  381. package/dist/setup/bot-config-editor.js +20 -2
  382. package/dist/setup/bot-config-editor.js.map +1 -1
  383. package/dist/setup/ensure-tmux.d.ts +0 -22
  384. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  385. package/dist/setup/ensure-tmux.js +25 -1
  386. package/dist/setup/ensure-tmux.js.map +1 -1
  387. package/dist/setup/verify-permissions.d.ts.map +1 -1
  388. package/dist/setup/verify-permissions.js +15 -1
  389. package/dist/setup/verify-permissions.js.map +1 -1
  390. package/dist/skills/definitions.d.ts +2 -0
  391. package/dist/skills/definitions.d.ts.map +1 -1
  392. package/dist/skills/definitions.js +178 -12
  393. package/dist/skills/definitions.js.map +1 -1
  394. package/dist/skills/installer.d.ts +34 -0
  395. package/dist/skills/installer.d.ts.map +1 -1
  396. package/dist/skills/installer.js +119 -2
  397. package/dist/skills/installer.js.map +1 -1
  398. package/dist/types.d.ts +25 -0
  399. package/dist/types.d.ts.map +1 -1
  400. package/dist/utils/bot-routing.d.ts +50 -0
  401. package/dist/utils/bot-routing.d.ts.map +1 -1
  402. package/dist/utils/bot-routing.js +83 -0
  403. package/dist/utils/bot-routing.js.map +1 -1
  404. package/dist/utils/daemon-discovery.d.ts +11 -0
  405. package/dist/utils/daemon-discovery.d.ts.map +1 -0
  406. package/dist/utils/daemon-discovery.js +59 -0
  407. package/dist/utils/daemon-discovery.js.map +1 -0
  408. package/dist/utils/user-token.d.ts.map +1 -1
  409. package/dist/utils/user-token.js +0 -2
  410. package/dist/utils/user-token.js.map +1 -1
  411. package/dist/worker.js +198 -27
  412. package/dist/worker.js.map +1 -1
  413. package/dist/workflows/attempt-resume.d.ts.map +1 -1
  414. package/dist/workflows/attempt-resume.js +2 -2
  415. package/dist/workflows/attempt-resume.js.map +1 -1
  416. package/dist/workflows/definition.d.ts +412 -9
  417. package/dist/workflows/definition.d.ts.map +1 -1
  418. package/dist/workflows/definition.js +238 -3
  419. package/dist/workflows/definition.js.map +1 -1
  420. package/dist/workflows/events/payloads.d.ts +114 -11
  421. package/dist/workflows/events/payloads.d.ts.map +1 -1
  422. package/dist/workflows/events/payloads.js +46 -0
  423. package/dist/workflows/events/payloads.js.map +1 -1
  424. package/dist/workflows/events/replay.d.ts +21 -0
  425. package/dist/workflows/events/replay.d.ts.map +1 -1
  426. package/dist/workflows/events/replay.js +103 -0
  427. package/dist/workflows/events/replay.js.map +1 -1
  428. package/dist/workflows/events/schema.d.ts +1301 -606
  429. package/dist/workflows/events/schema.d.ts.map +1 -1
  430. package/dist/workflows/events/schema.js +37 -1
  431. package/dist/workflows/events/schema.js.map +1 -1
  432. package/dist/workflows/events/types.d.ts +5 -1
  433. package/dist/workflows/events/types.d.ts.map +1 -1
  434. package/dist/workflows/loader.d.ts +14 -0
  435. package/dist/workflows/loader.d.ts.map +1 -1
  436. package/dist/workflows/loader.js +27 -0
  437. package/dist/workflows/loader.js.map +1 -1
  438. package/dist/workflows/loop.js +58 -0
  439. package/dist/workflows/loop.js.map +1 -1
  440. package/dist/workflows/ops-projection.d.ts +58 -0
  441. package/dist/workflows/ops-projection.d.ts.map +1 -1
  442. package/dist/workflows/ops-projection.js +74 -0
  443. package/dist/workflows/ops-projection.js.map +1 -1
  444. package/dist/workflows/orchestrator.d.ts +65 -1
  445. package/dist/workflows/orchestrator.d.ts.map +1 -1
  446. package/dist/workflows/orchestrator.js +486 -74
  447. package/dist/workflows/orchestrator.js.map +1 -1
  448. package/dist/workflows/output-binding.d.ts +8 -1
  449. package/dist/workflows/output-binding.d.ts.map +1 -1
  450. package/dist/workflows/output-binding.js +75 -11
  451. package/dist/workflows/output-binding.js.map +1 -1
  452. package/dist/workflows/runtime.d.ts +1 -1
  453. package/dist/workflows/runtime.d.ts.map +1 -1
  454. package/dist/workflows/runtime.js +39 -4
  455. package/dist/workflows/runtime.js.map +1 -1
  456. package/dist/workflows/trigger-from-envelope.d.ts +13 -0
  457. package/dist/workflows/trigger-from-envelope.d.ts.map +1 -0
  458. package/dist/workflows/trigger-from-envelope.js +67 -0
  459. package/dist/workflows/trigger-from-envelope.js.map +1 -0
  460. package/dist/workflows/wait.d.ts +23 -2
  461. package/dist/workflows/wait.d.ts.map +1 -1
  462. package/dist/workflows/wait.js +39 -17
  463. package/dist/workflows/wait.js.map +1 -1
  464. package/package.json +1 -1
  465. package/dist/services/feishu-task-client.d.ts +0 -28
  466. package/dist/services/feishu-task-client.d.ts.map +0 -1
  467. package/dist/services/feishu-task-client.js +0 -123
  468. package/dist/services/feishu-task-client.js.map +0 -1
  469. package/dist/services/task-store.d.ts +0 -37
  470. package/dist/services/task-store.d.ts.map +0 -1
  471. package/dist/services/task-store.js +0 -115
  472. package/dist/services/task-store.js.map +0 -1
@@ -0,0 +1,618 @@
1
+ import { randomBytes, randomUUID } from 'node:crypto';
2
+ import { writeFileSync, readFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { config } from '../config.js';
5
+ import { jsonRes } from './workflow-api.js';
6
+ import { buildTeamRoster } from '../services/team-roster.js';
7
+ import { buildFederatedRoster } from '../services/federation-roster.js';
8
+ import { getDeploymentIdentity, setDeploymentName } from '../services/deployment-identity.js';
9
+ import { addMembership, listMemberships, removeMembership } from '../services/federation-membership-store.js';
10
+ import { ensureDefaultTeam, DEFAULT_TEAM_ID, listTeams, createTeam, deleteTeam, getTeam } from '../services/team-store.js';
11
+ import { createInvite, deleteInvitesForTeam } from '../services/invite-store.js';
12
+ import { removeTeamFederation, removeDeployment } from '../services/federation-store.js';
13
+ import { loadBotConfigs, registerBot, getBot } from '../bot-registry.js';
14
+ import { setBotCapability, clearBotCapability } from '../services/bot-profile-store.js';
15
+ import { setBotOwner } from '../services/bot-owner-store.js';
16
+ import { setDeploymentOwner } from '../services/deployment-identity.js';
17
+ import { createPairing, getPairingStatus, consumePairing } from '../services/pairing-store.js';
18
+ import { resolveAllowedUsersWithMap, resolveUserUnionId } from '../im/lark/client.js';
19
+ import { fetchWithTimeout, hubError, orchestrateFederatedGroup } from './federated-group-core.js';
20
+ /** Resolve this deployment's owner identity from bots.json `allowedUsers` using
21
+ * each bot's OWN app credentials (no /pair, no shared pairings.json — immune to
22
+ * the dataDir-split that broke /pair). Walks bots with allowedUsers in order;
23
+ * returns the first bot's distinct resolved {unionId,name} (skips bots that
24
+ * resolve to nothing, so one mis-config doesn't hide the rest). The UI auto-binds
25
+ * when there's exactly one, else lets the owner pick. */
26
+ export async function resolveOwnerCandidatesFromAllowedUsers(d = {}) {
27
+ const loadConfigs = d.configs ?? loadBotConfigs;
28
+ const ensureClient = d.ensureClient ?? ((cfg) => { try {
29
+ getBot(cfg.larkAppId);
30
+ }
31
+ catch {
32
+ registerBot(cfg);
33
+ } });
34
+ const resolveAllowed = d.resolveAllowed ?? (async (id, a) => (await resolveAllowedUsersWithMap(id, a)).resolved);
35
+ const resolveUnion = d.resolveUnion ?? resolveUserUnionId;
36
+ let configs = [];
37
+ try {
38
+ configs = loadConfigs();
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ for (const cfg of configs) {
44
+ const allowed = cfg.allowedUsers ?? [];
45
+ if (allowed.length === 0)
46
+ continue;
47
+ try {
48
+ ensureClient(cfg);
49
+ }
50
+ catch {
51
+ continue;
52
+ }
53
+ let openIds = [];
54
+ try {
55
+ openIds = await resolveAllowed(cfg.larkAppId, allowed);
56
+ }
57
+ catch {
58
+ continue;
59
+ } // one bot failing → try next
60
+ const byUnion = new Map();
61
+ for (const oid of openIds) {
62
+ if (!oid.startsWith('ou_'))
63
+ continue;
64
+ const u = await resolveUnion(cfg.larkAppId, oid);
65
+ if (u.unionId)
66
+ byUnion.set(u.unionId, { unionId: u.unionId, name: u.name ?? '' });
67
+ }
68
+ if (byUnion.size > 0)
69
+ return [...byUnion.values()];
70
+ }
71
+ return [];
72
+ }
73
+ const MAX_ROLE_BYTES = 4 * 1024;
74
+ /** Team-level role file at {dataDir}/team-roles/{larkAppId}.md (matches role-resolver). */
75
+ function teamRolePath(dataDir, larkAppId) {
76
+ return join(dataDir, 'team-roles', `${larkAppId}.md`);
77
+ }
78
+ function writeTeamRole(dataDir, larkAppId, content) {
79
+ const fp = teamRolePath(dataDir, larkAppId);
80
+ mkdirSync(dirname(fp), { recursive: true });
81
+ let out = content.trim();
82
+ while (Buffer.byteLength(out, 'utf-8') > MAX_ROLE_BYTES)
83
+ out = out.slice(0, -1);
84
+ writeFileSync(fp, out, 'utf-8');
85
+ }
86
+ async function readBody(req, maxBytes = 64 * 1024) {
87
+ const chunks = [];
88
+ let total = 0;
89
+ for await (const c of req) {
90
+ const b = c;
91
+ total += b.length;
92
+ if (total > maxBytes)
93
+ throw new Error('too_large');
94
+ chunks.push(b);
95
+ }
96
+ if (chunks.length === 0)
97
+ return {};
98
+ return JSON.parse(Buffer.concat(chunks).toString('utf-8'));
99
+ }
100
+ /** Normalize a hub base URL (strip trailing slash); only http/https allowed. */
101
+ function normalizeHubUrl(raw) {
102
+ const s = String(raw ?? '').trim().replace(/\/+$/, '');
103
+ if (!/^https?:\/\/.+/i.test(s))
104
+ return null;
105
+ return s;
106
+ }
107
+ /** bots.json (config) order of larkAppIds, so federated rosters match the dashboard. */
108
+ function botConfigOrder() {
109
+ try {
110
+ return loadBotConfigs().map(b => b.larkAppId);
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ /** This deployment's bots, in the shape the hub federates (bots.json order).
117
+ * Prefer the live daemon registry (authoritative) over bots-info.json. */
118
+ function localBots(dataDir, live) {
119
+ return buildTeamRoster(dataDir, undefined, undefined, live).bots.map(b => ({
120
+ larkAppId: b.larkAppId,
121
+ botName: b.name,
122
+ cliId: b.cliId,
123
+ capability: b.capability,
124
+ hasTeamRole: b.hasTeamRole,
125
+ // owner (union_id+name) federated so the hub can pull owners into 拉群
126
+ ownerUnionId: b.owner?.unionId,
127
+ ownerName: b.owner?.name,
128
+ // botUnionId: not needed — 拉群 adds bots by app_id (larkAppId), see docs
129
+ }));
130
+ }
131
+ /** Push this deployment's current bots to every joined hub. Best-effort. */
132
+ export async function syncAllMemberships(dataDir, fetcher = fetch, live) {
133
+ const bots = localBots(dataDir, live);
134
+ const me = getDeploymentIdentity(dataDir);
135
+ let synced = 0, failed = 0;
136
+ for (const m of listMemberships(dataDir)) {
137
+ try {
138
+ const r = await fetchWithTimeout(fetcher, `${m.hubUrl}/api/federation/sync`, {
139
+ method: 'POST',
140
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${m.syncToken}` },
141
+ body: JSON.stringify({ syncToken: m.syncToken, bots, ownerUnionId: me.ownerUnionId, ownerName: me.ownerName, name: me.name }),
142
+ });
143
+ if (r.ok)
144
+ synced++;
145
+ else
146
+ failed++;
147
+ }
148
+ catch {
149
+ failed++;
150
+ }
151
+ }
152
+ return { synced, failed };
153
+ }
154
+ export async function handleFederationSpokeApi(req, res, url, deps = {}) {
155
+ const path = url.pathname;
156
+ const LOCAL = new Set(['/api/team/local', '/api/team/local-invite', '/api/team/rename-deployment', '/api/team/federated-group',
157
+ '/api/team/identity/start', '/api/team/identity/status', '/api/team/identity/consume', '/api/team/identity/auto-bind',
158
+ '/api/team/hosted']);
159
+ const REMOTE = new Set(['/api/team/join-remote', '/api/team/remote-roster', '/api/team/sync-remote', '/api/team/leave-remote', '/api/team/remote-group']);
160
+ const localBotEdit = path.match(/^\/api\/team\/local-bots\/([^/]+)\/(capability|role)$/);
161
+ const memberDel = path.match(/^\/api\/team\/hosted\/([^/]+)\/members\/([^/]+)$/);
162
+ const hostedDel = path.match(/^\/api\/team\/hosted\/([^/]+)$/);
163
+ if (!LOCAL.has(path) && !REMOTE.has(path) && !localBotEdit && !memberDel && !hostedDel)
164
+ return false;
165
+ const dataDir = deps.dataDir ?? config.session.dataDir;
166
+ const fetcher = deps.fetcher ?? fetch;
167
+ const method = req.method ?? 'GET';
168
+ const live = deps.liveBots?.(); // live registry bots (authoritative over bots-info.json)
169
+ // Edit a LOCAL bot's capability label / team role (federated bots are read-only
170
+ // — they're owned by another deployment and synced over). Local bots only.
171
+ if (localBotEdit) {
172
+ const larkAppId = decodeURIComponent(localBotEdit[1]);
173
+ const field = localBotEdit[2];
174
+ const localIds = new Set(buildTeamRoster(dataDir, undefined, undefined, live).bots.map(b => b.larkAppId));
175
+ if (!localIds.has(larkAppId)) {
176
+ jsonRes(res, 404, { ok: false, error: 'not_a_local_bot' });
177
+ return true;
178
+ }
179
+ if (field === 'role' && method === 'GET') {
180
+ const fp = teamRolePath(dataDir, larkAppId);
181
+ jsonRes(res, 200, { ok: true, role: existsSync(fp) ? readFileSync(fp, 'utf-8') : '' });
182
+ return true;
183
+ }
184
+ if (method === 'PUT') {
185
+ let body;
186
+ try {
187
+ body = await readBody(req);
188
+ }
189
+ catch {
190
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
191
+ return true;
192
+ }
193
+ if (field === 'capability') {
194
+ const cap = String(body?.capability ?? '').trim();
195
+ if (cap)
196
+ setBotCapability(dataDir, larkAppId, cap);
197
+ else
198
+ clearBotCapability(dataDir, larkAppId);
199
+ }
200
+ else {
201
+ const role = String(body?.role ?? '').trim();
202
+ if (role)
203
+ writeTeamRole(dataDir, larkAppId, role);
204
+ else {
205
+ try {
206
+ unlinkSync(teamRolePath(dataDir, larkAppId));
207
+ }
208
+ catch { /* already gone */ }
209
+ }
210
+ }
211
+ jsonRes(res, 200, { ok: true });
212
+ return true;
213
+ }
214
+ jsonRes(res, 405, { ok: false, error: 'method_not_allowed' });
215
+ return true;
216
+ }
217
+ // Cross-deployment 拉群: create a Feishu group with selected bots (local +
218
+ // federated). Bots are added by larkAppId (app_id) — the creator is picked
219
+ // from local online bots; federated bots (other apps, same tenant) are added
220
+ // as members. See docs/federation-design.md.
221
+ if (path === '/api/team/federated-group' && method === 'POST') {
222
+ if (!deps.createTeamGroup) {
223
+ jsonRes(res, 501, { ok: false, error: 'group_create_unavailable' });
224
+ return true;
225
+ }
226
+ let body;
227
+ try {
228
+ body = await readBody(req);
229
+ }
230
+ catch {
231
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
232
+ return true;
233
+ }
234
+ const larkAppIds = Array.isArray(body?.larkAppIds) ? body.larkAppIds.filter((x) => typeof x === 'string') : [];
235
+ const name = String(body?.name ?? '').trim() || '协作群';
236
+ // Operator = THIS deployment's bound owner (local initiation). Hub-derived
237
+ // operator (for spoke-initiated /api/federation/group) is handled separately.
238
+ const operatorUnionId = getDeploymentIdentity(dataDir).ownerUnionId;
239
+ const teamId = String(body?.teamId ?? '').trim() || DEFAULT_TEAM_ID; // which hosted team's roster gates the selection
240
+ // Validate the team exists — buildFederatedRoster falls back to the default
241
+ // team for an unknown teamId, so a stale/deleted teamId must be refused here
242
+ // (else 拉群 would silently use the default team's roster). ensureDefaultTeam
243
+ // first so the implicit default team always passes.
244
+ ensureDefaultTeam(dataDir);
245
+ if (!getTeam(dataDir, teamId)) {
246
+ jsonRes(res, 404, { ok: false, error: 'team_not_found' });
247
+ return true;
248
+ }
249
+ // Operator must end up in the group; only a bot in the operator's OWN
250
+ // deployment+scope can add them. If this deployment HAS online bots but the
251
+ // user selected none, the group would be built by a remote bot that can't add
252
+ // the operator → make them pick one. (If there are no local online bots at
253
+ // all, fall through to delegate-build — degenerate case, operator may not be
254
+ // addable, surfaced as missingOperatorIdentity.)
255
+ const localOnline = new Set((live ?? []).map(b => b.larkAppId));
256
+ if (localOnline.size > 0 && !larkAppIds.some(id => localOnline.has(id))) {
257
+ jsonRes(res, 400, { ok: false, error: 'no_local_online_bot' });
258
+ return true;
259
+ }
260
+ const out = await orchestrateFederatedGroup(dataDir, { name, larkAppIds, operatorUnionId, requestId: randomUUID(), teamId }, { createTeamGroup: deps.createTeamGroup, fetcher, live });
261
+ jsonRes(res, out.status, out.body);
262
+ return true;
263
+ }
264
+ // ── Bind THIS deployment's owner Feishu identity (reuse /pair) ─────────────
265
+ // Owner sends `/pair <code>` to one of our bots; we capture their union_id so
266
+ // 拉群 can pull the operator into groups, and own our bots (no-steal).
267
+ if (path === '/api/team/identity/start' && method === 'POST') {
268
+ const p = createPairing(dataDir, 5 * 60 * 1000);
269
+ jsonRes(res, 200, { ok: true, pairingId: p.pairingId, code: p.code, browserToken: p.browserToken, expiresAt: p.expiresAt });
270
+ return true;
271
+ }
272
+ if (path === '/api/team/identity/status' && method === 'POST') {
273
+ let body;
274
+ try {
275
+ body = await readBody(req);
276
+ }
277
+ catch {
278
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
279
+ return true;
280
+ }
281
+ const v = getPairingStatus(dataDir, String(body?.pairingId ?? ''), String(body?.browserToken ?? ''));
282
+ if (v.status === 'not_found') {
283
+ jsonRes(res, 200, { ok: true, status: 'not_found' });
284
+ return true;
285
+ }
286
+ jsonRes(res, 200, { ok: true, status: v.status, name: v.status === 'claimed' ? v.claimedBy.name : undefined });
287
+ return true;
288
+ }
289
+ if (path === '/api/team/identity/consume' && method === 'POST') {
290
+ let body;
291
+ try {
292
+ body = await readBody(req);
293
+ }
294
+ catch {
295
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
296
+ return true;
297
+ }
298
+ const c = consumePairing(dataDir, String(body?.pairingId ?? ''), String(body?.browserToken ?? ''));
299
+ if (!c.ok) {
300
+ jsonRes(res, 409, { ok: false, error: c.reason });
301
+ return true;
302
+ }
303
+ const owner = { unionId: c.claimedBy.unionId, name: c.claimedBy.name };
304
+ setDeploymentOwner(dataDir, owner);
305
+ // Own THIS deployment's bots (no-steal: only unassigned; keep manual owners).
306
+ for (const b of buildTeamRoster(dataDir, undefined, undefined, live).bots) {
307
+ setBotOwner(dataDir, b.larkAppId, { unionId: owner.unionId, name: owner.name }, { override: false });
308
+ }
309
+ // Push the new owner identity to every joined hub NOW (best-effort) so a 拉群
310
+ // immediately after binding can derive the operator — don't wait for the
311
+ // 2-min periodic sync, otherwise #3 (operator not invited) reproduces.
312
+ const sync = await syncAllMemberships(dataDir, fetcher, live).catch(() => ({ synced: 0, failed: 0 }));
313
+ jsonRes(res, 200, { ok: true, owner, hubsSynced: sync.synced, hubsFailed: sync.failed });
314
+ return true;
315
+ }
316
+ // ── Bind owner WITHOUT /pair: resolve allowedUsers via the bots' own creds ──
317
+ // No code to copy, no shared pairings.json (immune to dataDir-split). If the
318
+ // bots' allowedUsers resolve to exactly one person, bind them; if several,
319
+ // return candidates for the owner to pick (re-POST with {unionId}).
320
+ if (path === '/api/team/identity/auto-bind' && method === 'POST') {
321
+ let body;
322
+ try {
323
+ body = await readBody(req);
324
+ }
325
+ catch {
326
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
327
+ return true;
328
+ }
329
+ const candidates = await (deps.ownerCandidates ?? resolveOwnerCandidatesFromAllowedUsers)();
330
+ if (candidates.length === 0) {
331
+ jsonRes(res, 200, { ok: false, error: 'no_candidates' });
332
+ return true;
333
+ }
334
+ const want = String(body?.unionId ?? '').trim();
335
+ const chosen = want ? candidates.find(c => c.unionId === want) : (candidates.length === 1 ? candidates[0] : undefined);
336
+ if (!chosen) {
337
+ jsonRes(res, 200, { ok: true, needChoice: true, candidates });
338
+ return true;
339
+ }
340
+ const owner = { unionId: chosen.unionId, name: chosen.name };
341
+ setDeploymentOwner(dataDir, owner);
342
+ for (const b of buildTeamRoster(dataDir, undefined, undefined, live).bots) {
343
+ setBotOwner(dataDir, b.larkAppId, { unionId: owner.unionId, name: owner.name }, { override: false });
344
+ }
345
+ const sync = await syncAllMemberships(dataDir, fetcher, live).catch(() => ({ synced: 0, failed: 0 }));
346
+ jsonRes(res, 200, { ok: true, owner, hubsSynced: sync.synced, hubsFailed: sync.failed });
347
+ return true;
348
+ }
349
+ // ── Initiate 拉群 on a JOINED remote team (spoke → hub orchestrates) ────────
350
+ if (path === '/api/team/remote-group' && method === 'POST') {
351
+ let body;
352
+ try {
353
+ body = await readBody(req);
354
+ }
355
+ catch {
356
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
357
+ return true;
358
+ }
359
+ const hubUrl = normalizeHubUrl(body?.hubUrl);
360
+ const teamId = String(body?.teamId ?? '').trim();
361
+ const larkAppIds = Array.isArray(body?.larkAppIds) ? body.larkAppIds.filter((x) => typeof x === 'string') : [];
362
+ const name = String(body?.name ?? '').trim() || '协作群';
363
+ if (!hubUrl || !teamId) {
364
+ jsonRes(res, 400, { ok: false, error: 'bad_request' });
365
+ return true;
366
+ }
367
+ if (larkAppIds.length === 0) {
368
+ jsonRes(res, 400, { ok: false, error: 'no_bots_selected' });
369
+ return true;
370
+ }
371
+ const m = listMemberships(dataDir).find(x => x.hubUrl === hubUrl && x.teamId === teamId);
372
+ if (!m) {
373
+ jsonRes(res, 404, { ok: false, error: 'not_a_member' });
374
+ return true;
375
+ }
376
+ // Push our latest owner + bots to the hub BEFORE it orchestrates, so the
377
+ // operator union_id (hub-derived from our synced record) is fresh — otherwise
378
+ // a 拉群 right after binding could still see a stale/empty owner (the periodic
379
+ // sync is every 2 min) → operator not invited.
380
+ await syncAllMemberships(dataDir, fetcher, live).catch(() => { });
381
+ try {
382
+ const r = await fetchWithTimeout(fetcher, `${hubUrl}/api/federation/group`, {
383
+ method: 'POST',
384
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${m.syncToken}` },
385
+ body: JSON.stringify({ name, larkAppIds, requestId: randomUUID() }),
386
+ });
387
+ const j = await r.json().catch(() => ({}));
388
+ jsonRes(res, r.ok ? 200 : (r.status === 400 || r.status === 403 ? r.status : 502), j);
389
+ }
390
+ catch (e) {
391
+ const he = hubError(e);
392
+ jsonRes(res, he.status, { ok: false, error: he.error });
393
+ }
394
+ return true;
395
+ }
396
+ // ── Local team (this deployment as a Hub: identity + own roster + invites) ──
397
+ if (path === '/api/team/local' && method === 'GET') {
398
+ ensureDefaultTeam(dataDir);
399
+ const me = getDeploymentIdentity(dataDir);
400
+ const suggestedHubUrl = `http://${config.dashboard.externalHost}:${config.dashboard.port}`;
401
+ jsonRes(res, 200, { ok: true, deployment: me, suggestedHubUrl, ...buildFederatedRoster(dataDir, DEFAULT_TEAM_ID, botConfigOrder(), undefined, live) });
402
+ return true;
403
+ }
404
+ // All teams THIS deployment hosts (default + any created), each with its
405
+ // aggregated roster — the SPA「我的团队」renders one block per team.
406
+ if (path === '/api/team/hosted' && method === 'GET') {
407
+ ensureDefaultTeam(dataDir);
408
+ const me = getDeploymentIdentity(dataDir);
409
+ const suggestedHubUrl = `http://${config.dashboard.externalHost}:${config.dashboard.port}`;
410
+ const teams = listTeams(dataDir).map(t => ({
411
+ teamId: t.id, name: t.name, isDefault: t.id === DEFAULT_TEAM_ID,
412
+ ...buildFederatedRoster(dataDir, t.id, botConfigOrder(), undefined, live),
413
+ }));
414
+ jsonRes(res, 200, { ok: true, deployment: me, suggestedHubUrl, teams });
415
+ return true;
416
+ }
417
+ if (path === '/api/team/hosted' && method === 'POST') {
418
+ let body;
419
+ try {
420
+ body = await readBody(req);
421
+ }
422
+ catch {
423
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
424
+ return true;
425
+ }
426
+ const name = String(body?.name ?? '').trim();
427
+ if (!name) {
428
+ jsonRes(res, 400, { ok: false, error: 'name_required' });
429
+ return true;
430
+ }
431
+ if (name.length > 64) {
432
+ jsonRes(res, 400, { ok: false, error: 'name_too_long' });
433
+ return true;
434
+ }
435
+ if (listTeams(dataDir).length >= 100) {
436
+ jsonRes(res, 400, { ok: false, error: 'too_many_teams' });
437
+ return true;
438
+ } // guardrail (team-internal trust, not a security boundary)
439
+ const t = createTeam(dataDir, name);
440
+ jsonRes(res, 200, { ok: true, teamId: t.id, name: t.name });
441
+ return true;
442
+ }
443
+ // Remove a member deployment from a team I host (hub kicks a joined spoke).
444
+ if (memberDel && method === 'DELETE') {
445
+ const teamId = decodeURIComponent(memberDel[1]);
446
+ const deploymentId = decodeURIComponent(memberDel[2]);
447
+ if (deploymentId === getDeploymentIdentity(dataDir).deploymentId) {
448
+ jsonRes(res, 400, { ok: false, error: 'cannot_remove_self' });
449
+ return true;
450
+ }
451
+ const removed = removeDeployment(dataDir, teamId, deploymentId);
452
+ jsonRes(res, removed ? 200 : 404, { ok: removed, ...(removed ? {} : { error: 'member_not_found' }) });
453
+ return true;
454
+ }
455
+ if (hostedDel && method === 'DELETE') {
456
+ const teamId = decodeURIComponent(hostedDel[1]);
457
+ if (teamId === DEFAULT_TEAM_ID) {
458
+ jsonRes(res, 400, { ok: false, error: 'cannot_delete_default' });
459
+ return true;
460
+ }
461
+ if (!getTeam(dataDir, teamId)) {
462
+ jsonRes(res, 404, { ok: false, error: 'team_not_found' });
463
+ return true;
464
+ }
465
+ deleteTeam(dataDir, teamId);
466
+ removeTeamFederation(dataDir, teamId); // drop joined spokes' records for this team
467
+ deleteInvitesForTeam(dataDir, teamId); // invalidate its outstanding invites
468
+ jsonRes(res, 200, { ok: true });
469
+ return true;
470
+ }
471
+ // Generate an invite for a hosted team (defaults to the default team).
472
+ if (path === '/api/team/local-invite' && method === 'POST') {
473
+ // Empty body → default team (readBody returns {}); malformed/oversized JSON
474
+ // throws → reject with bad_json (don't silently fall back to default team).
475
+ let body;
476
+ try {
477
+ body = await readBody(req);
478
+ }
479
+ catch {
480
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
481
+ return true;
482
+ }
483
+ ensureDefaultTeam(dataDir);
484
+ const teamId = String(body?.teamId ?? '').trim() || DEFAULT_TEAM_ID;
485
+ if (!getTeam(dataDir, teamId)) {
486
+ jsonRes(res, 404, { ok: false, error: 'team_not_found' });
487
+ return true;
488
+ }
489
+ const inv = createInvite(dataDir, teamId, getDeploymentIdentity(dataDir).deploymentId);
490
+ jsonRes(res, 200, { ok: true, code: inv.code, expiresAt: inv.expiresAt, teamId });
491
+ return true;
492
+ }
493
+ if (path === '/api/team/rename-deployment' && method === 'POST') {
494
+ let body;
495
+ try {
496
+ body = await readBody(req);
497
+ }
498
+ catch {
499
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
500
+ return true;
501
+ }
502
+ const name = String(body?.name ?? '').trim();
503
+ if (!name) {
504
+ jsonRes(res, 400, { ok: false, error: 'name_required' });
505
+ return true;
506
+ }
507
+ jsonRes(res, 200, { ok: true, deployment: setDeploymentName(dataDir, name) });
508
+ return true;
509
+ }
510
+ // Accept an invite from another deployment's hub: register our bots there.
511
+ if (path === '/api/team/join-remote' && method === 'POST') {
512
+ let body;
513
+ try {
514
+ body = await readBody(req);
515
+ }
516
+ catch {
517
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
518
+ return true;
519
+ }
520
+ const hubUrl = normalizeHubUrl(body?.hubUrl);
521
+ const inviteCode = String(body?.inviteCode ?? '').trim();
522
+ if (!hubUrl) {
523
+ jsonRes(res, 400, { ok: false, error: 'bad_hub_url' });
524
+ return true;
525
+ }
526
+ if (!inviteCode) {
527
+ jsonRes(res, 400, { ok: false, error: 'code_required' });
528
+ return true;
529
+ }
530
+ const me = getDeploymentIdentity(dataDir);
531
+ // Issue a delegationToken to the hub + tell it our callback URL, so the hub
532
+ // can delegate 拉群 back to us (hub→spoke) when it has no local creator.
533
+ const delegationToken = randomBytes(24).toString('base64url');
534
+ const callbackUrl = `http://${config.dashboard.externalHost}:${config.dashboard.port}`;
535
+ let hubRes;
536
+ try {
537
+ hubRes = await fetchWithTimeout(fetcher, `${hubUrl}/api/federation/join`, {
538
+ method: 'POST',
539
+ headers: { 'content-type': 'application/json' },
540
+ body: JSON.stringify({ inviteCode, deployment: { deploymentId: me.deploymentId, name: me.name, ownerUnionId: me.ownerUnionId, ownerName: me.ownerName, bots: localBots(dataDir, live), callbackUrl, delegationToken } }),
541
+ });
542
+ }
543
+ catch (e) {
544
+ const he = hubError(e);
545
+ jsonRes(res, he.status, { ok: false, error: he.error });
546
+ return true;
547
+ }
548
+ const j = await hubRes.json().catch(() => ({}));
549
+ if (!hubRes.ok || !j?.ok) {
550
+ const status = [400, 403, 409].includes(hubRes.status) ? hubRes.status : 502;
551
+ jsonRes(res, status, { ok: false, error: j?.error || `hub_${hubRes.status}` });
552
+ return true;
553
+ }
554
+ addMembership(dataDir, { hubUrl, teamId: j.teamId, teamName: j.teamName, syncToken: j.syncToken, deploymentId: me.deploymentId, delegationToken });
555
+ jsonRes(res, 200, { ok: true, hubUrl, teamId: j.teamId, teamName: j.teamName });
556
+ return true;
557
+ }
558
+ // Pull each joined hub's aggregated roster for display (token in header).
559
+ if (path === '/api/team/remote-roster' && method === 'GET') {
560
+ const out = [];
561
+ for (const m of listMemberships(dataDir)) {
562
+ try {
563
+ const r = await fetchWithTimeout(fetcher, `${m.hubUrl}/api/federation/roster`, {
564
+ headers: { authorization: `Bearer ${m.syncToken}` },
565
+ });
566
+ const j = await r.json().catch(() => ({}));
567
+ out.push({ hubUrl: m.hubUrl, teamId: m.teamId, teamName: m.teamName, ok: r.ok && j?.ok, roster: j?.ok ? { deployments: j.deployments, bots: j.bots, team: j.team } : null, error: j?.error });
568
+ }
569
+ catch (e) {
570
+ out.push({ hubUrl: m.hubUrl, teamId: m.teamId, teamName: m.teamName, ok: false, roster: null, error: hubError(e).error });
571
+ }
572
+ }
573
+ jsonRes(res, 200, { ok: true, memberships: out });
574
+ return true;
575
+ }
576
+ // Manually push bots + heartbeat to all joined hubs.
577
+ if (path === '/api/team/sync-remote' && method === 'POST') {
578
+ const r = await syncAllMemberships(dataDir, fetcher, live);
579
+ jsonRes(res, 200, { ok: true, ...r });
580
+ return true;
581
+ }
582
+ // Leave a remote team: best-effort revoke at the hub (so it drops our
583
+ // deployment + token + stale bots), then forget the membership locally.
584
+ if (path === '/api/team/leave-remote' && method === 'POST') {
585
+ let body;
586
+ try {
587
+ body = await readBody(req);
588
+ }
589
+ catch {
590
+ jsonRes(res, 400, { ok: false, error: 'bad_json' });
591
+ return true;
592
+ }
593
+ const hubUrl = normalizeHubUrl(body?.hubUrl);
594
+ const teamId = String(body?.teamId ?? '').trim();
595
+ if (!hubUrl || !teamId) {
596
+ jsonRes(res, 400, { ok: false, error: 'bad_request' });
597
+ return true;
598
+ }
599
+ const m = listMemberships(dataDir).find(x => x.hubUrl === hubUrl && x.teamId === teamId);
600
+ let hubRevoked = false;
601
+ if (m) {
602
+ try {
603
+ const r = await fetchWithTimeout(fetcher, `${hubUrl}/api/federation/leave`, {
604
+ method: 'POST',
605
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${m.syncToken}` },
606
+ body: JSON.stringify({ syncToken: m.syncToken }),
607
+ });
608
+ hubRevoked = r.ok;
609
+ }
610
+ catch { /* hub unreachable — still forget locally below */ }
611
+ }
612
+ const removed = removeMembership(dataDir, hubUrl, teamId);
613
+ jsonRes(res, removed ? 200 : 404, { ok: removed, hubRevoked });
614
+ return true;
615
+ }
616
+ return false;
617
+ }
618
+ //# sourceMappingURL=federation-spoke-api.js.map