botmux 2.107.0 → 2.108.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (515) hide show
  1. package/README.en.md +17 -14
  2. package/README.md +19 -14
  3. package/dist/adapters/backend/sandbox.d.ts +3 -1
  4. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  5. package/dist/adapters/backend/sandbox.js +19 -3
  6. package/dist/adapters/backend/sandbox.js.map +1 -1
  7. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  8. package/dist/adapters/backend/tmux-backend.js +16 -1
  9. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  10. package/dist/adapters/backend/tmux-pipe-backend.d.ts +24 -15
  11. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-pipe-backend.js +75 -43
  13. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  14. package/dist/adapters/cli/genius.d.ts.map +1 -1
  15. package/dist/adapters/cli/genius.js +9 -15
  16. package/dist/adapters/cli/genius.js.map +1 -1
  17. package/dist/adapters/cli/grok.d.ts +73 -0
  18. package/dist/adapters/cli/grok.d.ts.map +1 -0
  19. package/dist/adapters/cli/grok.js +298 -0
  20. package/dist/adapters/cli/grok.js.map +1 -0
  21. package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
  22. package/dist/adapters/cli/oh-my-pi.js +133 -12
  23. package/dist/adapters/cli/oh-my-pi.js.map +1 -1
  24. package/dist/adapters/cli/registry.d.ts +2 -1
  25. package/dist/adapters/cli/registry.d.ts.map +1 -1
  26. package/dist/adapters/cli/registry.js +3 -1
  27. package/dist/adapters/cli/registry.js.map +1 -1
  28. package/dist/adapters/cli/shared-hints.d.ts +8 -4
  29. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  30. package/dist/adapters/cli/shared-hints.js +8 -4
  31. package/dist/adapters/cli/shared-hints.js.map +1 -1
  32. package/dist/adapters/cli/types.d.ts +18 -16
  33. package/dist/adapters/cli/types.d.ts.map +1 -1
  34. package/dist/adapters/hook-installer.d.ts +5 -4
  35. package/dist/adapters/hook-installer.d.ts.map +1 -1
  36. package/dist/adapters/hook-installer.js +42 -0
  37. package/dist/adapters/hook-installer.js.map +1 -1
  38. package/dist/cli/send-dispatch.d.ts +21 -0
  39. package/dist/cli/send-dispatch.d.ts.map +1 -1
  40. package/dist/cli/send-dispatch.js +159 -0
  41. package/dist/cli/send-dispatch.js.map +1 -1
  42. package/dist/cli/session-list-liveness.d.ts +22 -0
  43. package/dist/cli/session-list-liveness.d.ts.map +1 -0
  44. package/dist/cli/session-list-liveness.js +22 -0
  45. package/dist/cli/session-list-liveness.js.map +1 -0
  46. package/dist/cli.d.ts.map +1 -1
  47. package/dist/cli.js +182 -33
  48. package/dist/cli.js.map +1 -1
  49. package/dist/core/ask-types.d.ts +4 -0
  50. package/dist/core/ask-types.d.ts.map +1 -1
  51. package/dist/core/ask-types.js +10 -0
  52. package/dist/core/ask-types.js.map +1 -1
  53. package/dist/core/cost-calculator.d.ts.map +1 -1
  54. package/dist/core/cost-calculator.js +35 -13
  55. package/dist/core/cost-calculator.js.map +1 -1
  56. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  57. package/dist/core/dashboard-ipc-server.js +89 -16
  58. package/dist/core/dashboard-ipc-server.js.map +1 -1
  59. package/dist/core/dashboard-rows.d.ts.map +1 -1
  60. package/dist/core/dashboard-rows.js +6 -2
  61. package/dist/core/dashboard-rows.js.map +1 -1
  62. package/dist/core/dashboard-url.d.ts +6 -5
  63. package/dist/core/dashboard-url.d.ts.map +1 -1
  64. package/dist/core/dashboard-url.js +11 -8
  65. package/dist/core/dashboard-url.js.map +1 -1
  66. package/dist/core/maintenance.d.ts +11 -8
  67. package/dist/core/maintenance.d.ts.map +1 -1
  68. package/dist/core/maintenance.js +45 -20
  69. package/dist/core/maintenance.js.map +1 -1
  70. package/dist/core/per-bot-env.d.ts.map +1 -1
  71. package/dist/core/per-bot-env.js +4 -0
  72. package/dist/core/per-bot-env.js.map +1 -1
  73. package/dist/core/session-discovery.d.ts.map +1 -1
  74. package/dist/core/session-discovery.js +4 -0
  75. package/dist/core/session-discovery.js.map +1 -1
  76. package/dist/core/skills/cli-admin-command.d.ts.map +1 -1
  77. package/dist/core/skills/cli-admin-command.js +8 -2
  78. package/dist/core/skills/cli-admin-command.js.map +1 -1
  79. package/dist/core/skills/im-command.d.ts.map +1 -1
  80. package/dist/core/skills/im-command.js +3 -1
  81. package/dist/core/skills/im-command.js.map +1 -1
  82. package/dist/core/skills/sources.d.ts +33 -1
  83. package/dist/core/skills/sources.d.ts.map +1 -1
  84. package/dist/core/skills/sources.js +97 -0
  85. package/dist/core/skills/sources.js.map +1 -1
  86. package/dist/core/skills/types.d.ts +8 -0
  87. package/dist/core/skills/types.d.ts.map +1 -1
  88. package/dist/core/terminal-url.d.ts.map +1 -1
  89. package/dist/core/terminal-url.js +12 -2
  90. package/dist/core/terminal-url.js.map +1 -1
  91. package/dist/core/terminal-write-auth.d.ts +34 -0
  92. package/dist/core/terminal-write-auth.d.ts.map +1 -0
  93. package/dist/core/terminal-write-auth.js +76 -0
  94. package/dist/core/terminal-write-auth.js.map +1 -0
  95. package/dist/core/tmux-recovery.d.ts +11 -0
  96. package/dist/core/tmux-recovery.d.ts.map +1 -0
  97. package/dist/core/tmux-recovery.js +19 -0
  98. package/dist/core/tmux-recovery.js.map +1 -0
  99. package/dist/core/types.d.ts +10 -4
  100. package/dist/core/types.d.ts.map +1 -1
  101. package/dist/core/types.js.map +1 -1
  102. package/dist/core/update-check.d.ts +2 -2
  103. package/dist/core/update-check.js +2 -2
  104. package/dist/core/worker-pool.d.ts +3 -0
  105. package/dist/core/worker-pool.d.ts.map +1 -1
  106. package/dist/core/worker-pool.js +70 -3
  107. package/dist/core/worker-pool.js.map +1 -1
  108. package/dist/daemon.d.ts.map +1 -1
  109. package/dist/daemon.js +15 -8
  110. package/dist/daemon.js.map +1 -1
  111. package/dist/dashboard/auth.d.ts +1 -1
  112. package/dist/dashboard/auth.d.ts.map +1 -1
  113. package/dist/dashboard/auth.js +7 -2
  114. package/dist/dashboard/auth.js.map +1 -1
  115. package/dist/dashboard/bot-payload.d.ts +3 -0
  116. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  117. package/dist/dashboard/bot-payload.js +3 -0
  118. package/dist/dashboard/bot-payload.js.map +1 -1
  119. package/dist/dashboard/connector-api.d.ts.map +1 -1
  120. package/dist/dashboard/connector-api.js +39 -13
  121. package/dist/dashboard/connector-api.js.map +1 -1
  122. package/dist/dashboard/history-senders.d.ts +37 -0
  123. package/dist/dashboard/history-senders.d.ts.map +1 -0
  124. package/dist/dashboard/history-senders.js +60 -0
  125. package/dist/dashboard/history-senders.js.map +1 -0
  126. package/dist/dashboard/roles-batch.d.ts +31 -0
  127. package/dist/dashboard/roles-batch.d.ts.map +1 -0
  128. package/dist/dashboard/roles-batch.js +87 -0
  129. package/dist/dashboard/roles-batch.js.map +1 -0
  130. package/dist/dashboard/settings-card-model.d.ts +4 -2
  131. package/dist/dashboard/settings-card-model.d.ts.map +1 -1
  132. package/dist/dashboard/settings-card-model.js +13 -5
  133. package/dist/dashboard/settings-card-model.js.map +1 -1
  134. package/dist/dashboard/settings-write-applier.d.ts +4 -1
  135. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  136. package/dist/dashboard/settings-write-applier.js +6 -1
  137. package/dist/dashboard/settings-write-applier.js.map +1 -1
  138. package/dist/dashboard/skill-install-request.d.ts +4 -0
  139. package/dist/dashboard/skill-install-request.d.ts.map +1 -1
  140. package/dist/dashboard/skill-install-request.js +11 -1
  141. package/dist/dashboard/skill-install-request.js.map +1 -1
  142. package/dist/dashboard/trigger-api.d.ts +9 -1
  143. package/dist/dashboard/trigger-api.d.ts.map +1 -1
  144. package/dist/dashboard/trigger-api.js +48 -10
  145. package/dist/dashboard/trigger-api.js.map +1 -1
  146. package/dist/dashboard/web/app.d.ts.map +1 -1
  147. package/dist/dashboard/web/app.js +286 -271
  148. package/dist/dashboard/web/app.js.map +1 -1
  149. package/dist/dashboard/web/bot-defaults-page.d.ts +26 -0
  150. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  151. package/dist/dashboard/web/bot-defaults-page.js +1408 -12
  152. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  153. package/dist/dashboard/web/bot-defaults.d.ts +91 -4
  154. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  155. package/dist/dashboard/web/bot-defaults.js +43 -2141
  156. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  157. package/dist/dashboard/web/bot-onboarding.d.ts +6 -1
  158. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  159. package/dist/dashboard/web/bot-onboarding.js +261 -306
  160. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  161. package/dist/dashboard/web/connectors-page.d.ts +4 -0
  162. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  163. package/dist/dashboard/web/connectors-page.js +228 -48
  164. package/dist/dashboard/web/connectors-page.js.map +1 -1
  165. package/dist/dashboard/web/dashboard-api.d.ts +9 -0
  166. package/dist/dashboard/web/dashboard-api.d.ts.map +1 -0
  167. package/dist/dashboard/web/dashboard-api.js +18 -0
  168. package/dist/dashboard/web/dashboard-api.js.map +1 -0
  169. package/dist/dashboard/web/dashboard-components.d.ts +92 -0
  170. package/dist/dashboard/web/dashboard-components.d.ts.map +1 -0
  171. package/dist/dashboard/web/dashboard-components.js +250 -0
  172. package/dist/dashboard/web/dashboard-components.js.map +1 -0
  173. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  174. package/dist/dashboard/web/dashboard-routes.js +30 -183
  175. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  176. package/dist/dashboard/web/floating-scrollbars.d.ts +2 -0
  177. package/dist/dashboard/web/floating-scrollbars.d.ts.map +1 -0
  178. package/dist/dashboard/web/floating-scrollbars.js +264 -0
  179. package/dist/dashboard/web/floating-scrollbars.js.map +1 -0
  180. package/dist/dashboard/web/groups-api.d.ts +36 -0
  181. package/dist/dashboard/web/groups-api.d.ts.map +1 -0
  182. package/dist/dashboard/web/groups-api.js +44 -0
  183. package/dist/dashboard/web/groups-api.js.map +1 -0
  184. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  185. package/dist/dashboard/web/groups-page.js +746 -15
  186. package/dist/dashboard/web/groups-page.js.map +1 -1
  187. package/dist/dashboard/web/groups.d.ts +51 -18
  188. package/dist/dashboard/web/groups.d.ts.map +1 -1
  189. package/dist/dashboard/web/groups.js +145 -1096
  190. package/dist/dashboard/web/groups.js.map +1 -1
  191. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  192. package/dist/dashboard/web/i18n.js +545 -255
  193. package/dist/dashboard/web/i18n.js.map +1 -1
  194. package/dist/dashboard/web/insights-page.d.ts.map +1 -1
  195. package/dist/dashboard/web/insights-page.js +1047 -14
  196. package/dist/dashboard/web/insights-page.js.map +1 -1
  197. package/dist/dashboard/web/insights.d.ts +177 -7
  198. package/dist/dashboard/web/insights.d.ts.map +1 -1
  199. package/dist/dashboard/web/insights.js +187 -1926
  200. package/dist/dashboard/web/insights.js.map +1 -1
  201. package/dist/dashboard/web/legacy-workflow-page.d.ts +3 -0
  202. package/dist/dashboard/web/legacy-workflow-page.d.ts.map +1 -0
  203. package/dist/dashboard/web/legacy-workflow-page.js +717 -0
  204. package/dist/dashboard/web/legacy-workflow-page.js.map +1 -0
  205. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  206. package/dist/dashboard/web/monitor-room.js +10 -8
  207. package/dist/dashboard/web/monitor-room.js.map +1 -1
  208. package/dist/dashboard/web/monitoring-page.d.ts.map +1 -1
  209. package/dist/dashboard/web/monitoring-page.js +188 -14
  210. package/dist/dashboard/web/monitoring-page.js.map +1 -1
  211. package/dist/dashboard/web/office-page.d.ts.map +1 -1
  212. package/dist/dashboard/web/office-page.js +11 -23
  213. package/dist/dashboard/web/office-page.js.map +1 -1
  214. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  215. package/dist/dashboard/web/overview-page.js +141 -147
  216. package/dist/dashboard/web/overview-page.js.map +1 -1
  217. package/dist/dashboard/web/overview.d.ts +0 -1
  218. package/dist/dashboard/web/overview.d.ts.map +1 -1
  219. package/dist/dashboard/web/overview.js +3 -275
  220. package/dist/dashboard/web/overview.js.map +1 -1
  221. package/dist/dashboard/web/preferences.d.ts +1 -6
  222. package/dist/dashboard/web/preferences.d.ts.map +1 -1
  223. package/dist/dashboard/web/preferences.js +0 -21
  224. package/dist/dashboard/web/preferences.js.map +1 -1
  225. package/dist/dashboard/web/role-batch.d.ts +9 -0
  226. package/dist/dashboard/web/role-batch.d.ts.map +1 -0
  227. package/dist/dashboard/web/role-batch.js +42 -0
  228. package/dist/dashboard/web/role-batch.js.map +1 -0
  229. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  230. package/dist/dashboard/web/roles-page.js +538 -13
  231. package/dist/dashboard/web/roles-page.js.map +1 -1
  232. package/dist/dashboard/web/roles.d.ts +98 -1
  233. package/dist/dashboard/web/roles.d.ts.map +1 -1
  234. package/dist/dashboard/web/roles.js +117 -903
  235. package/dist/dashboard/web/roles.js.map +1 -1
  236. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  237. package/dist/dashboard/web/schedules-page.js +82 -24
  238. package/dist/dashboard/web/schedules-page.js.map +1 -1
  239. package/dist/dashboard/web/sessions-kanban.d.ts +67 -0
  240. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -0
  241. package/dist/dashboard/web/sessions-kanban.js +481 -0
  242. package/dist/dashboard/web/sessions-kanban.js.map +1 -0
  243. package/dist/dashboard/web/sessions-page.d.ts +4 -0
  244. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  245. package/dist/dashboard/web/sessions-page.js +1633 -18
  246. package/dist/dashboard/web/sessions-page.js.map +1 -1
  247. package/dist/dashboard/web/sessions.d.ts +45 -4
  248. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  249. package/dist/dashboard/web/sessions.js +53 -2535
  250. package/dist/dashboard/web/sessions.js.map +1 -1
  251. package/dist/dashboard/web/settings-page.d.ts +0 -2
  252. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  253. package/dist/dashboard/web/settings-page.js +127 -117
  254. package/dist/dashboard/web/settings-page.js.map +1 -1
  255. package/dist/dashboard/web/skills-page.d.ts +10 -1
  256. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  257. package/dist/dashboard/web/skills-page.js +235 -93
  258. package/dist/dashboard/web/skills-page.js.map +1 -1
  259. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  260. package/dist/dashboard/web/team-federation-page.js +446 -28
  261. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  262. package/dist/dashboard/web/team-federation.d.ts +91 -2
  263. package/dist/dashboard/web/team-federation.d.ts.map +1 -1
  264. package/dist/dashboard/web/team-federation.js +87 -474
  265. package/dist/dashboard/web/team-federation.js.map +1 -1
  266. package/dist/dashboard/web/theme-menu.d.ts +1 -0
  267. package/dist/dashboard/web/theme-menu.d.ts.map +1 -1
  268. package/dist/dashboard/web/theme-menu.js +79 -20
  269. package/dist/dashboard/web/theme-menu.js.map +1 -1
  270. package/dist/dashboard/web/ui.d.ts +1 -2
  271. package/dist/dashboard/web/ui.d.ts.map +1 -1
  272. package/dist/dashboard/web/ui.js +8 -20
  273. package/dist/dashboard/web/ui.js.map +1 -1
  274. package/dist/dashboard/web/v3-api.d.ts +16 -0
  275. package/dist/dashboard/web/v3-api.d.ts.map +1 -0
  276. package/dist/dashboard/web/v3-api.js +21 -0
  277. package/dist/dashboard/web/v3-api.js.map +1 -0
  278. package/dist/dashboard/web/v3-components.d.ts +2 -0
  279. package/dist/dashboard/web/v3-components.d.ts.map +1 -0
  280. package/dist/dashboard/web/v3-components.js +290 -0
  281. package/dist/dashboard/web/v3-components.js.map +1 -0
  282. package/dist/dashboard/web/v3-model.d.ts +80 -0
  283. package/dist/dashboard/web/v3-model.d.ts.map +1 -0
  284. package/dist/dashboard/web/v3-model.js +240 -0
  285. package/dist/dashboard/web/v3-model.js.map +1 -0
  286. package/dist/dashboard/web/v3-page.d.ts.map +1 -1
  287. package/dist/dashboard/web/v3-page.js +2 -22
  288. package/dist/dashboard/web/v3-page.js.map +1 -1
  289. package/dist/dashboard/web/v3-terminal.d.ts +14 -0
  290. package/dist/dashboard/web/v3-terminal.d.ts.map +1 -1
  291. package/dist/dashboard/web/v3-terminal.js +31 -19
  292. package/dist/dashboard/web/v3-terminal.js.map +1 -1
  293. package/dist/dashboard/web/webhook-logs-page.d.ts +16 -0
  294. package/dist/dashboard/web/webhook-logs-page.d.ts.map +1 -0
  295. package/dist/dashboard/web/webhook-logs-page.js +195 -0
  296. package/dist/dashboard/web/webhook-logs-page.js.map +1 -0
  297. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  298. package/dist/dashboard/web/whiteboards-page.js +18 -23
  299. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  300. package/dist/dashboard/web/workflow-version-switch.d.ts +8 -0
  301. package/dist/dashboard/web/workflow-version-switch.d.ts.map +1 -0
  302. package/dist/dashboard/web/workflow-version-switch.js +9 -0
  303. package/dist/dashboard/web/workflow-version-switch.js.map +1 -0
  304. package/dist/dashboard/web/workflows.d.ts +272 -1
  305. package/dist/dashboard/web/workflows.d.ts.map +1 -1
  306. package/dist/dashboard/web/workflows.js +211 -1397
  307. package/dist/dashboard/web/workflows.js.map +1 -1
  308. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  309. package/dist/dashboard/webhook-routes.js +92 -39
  310. package/dist/dashboard/webhook-routes.js.map +1 -1
  311. package/dist/dashboard-web/app.js +1 -65
  312. package/dist/dashboard-web/brand-logo.png +0 -0
  313. package/dist/dashboard-web/chunks/bot-defaults-page-HSQ6HR3R.js +1 -0
  314. package/dist/dashboard-web/chunks/chunk-6TPODDDC.js +1 -0
  315. package/dist/dashboard-web/chunks/chunk-73EJP2MO.js +1 -0
  316. package/dist/dashboard-web/chunks/chunk-BRTZFELS.js +1 -0
  317. package/dist/dashboard-web/chunks/chunk-EVK77ASG.js +1 -0
  318. package/dist/dashboard-web/chunks/chunk-R3FXBFQH.js +1 -0
  319. package/dist/dashboard-web/chunks/chunk-VRLL7SNU.js +1 -0
  320. package/dist/dashboard-web/chunks/chunk-XCPSHD3G.js +37 -0
  321. package/dist/dashboard-web/chunks/chunk-XJ7BGPJH.js +1 -0
  322. package/dist/dashboard-web/chunks/chunk-YVRWYGXR.js +1 -0
  323. package/dist/dashboard-web/chunks/chunk-ZJING6FY.js +66 -0
  324. package/dist/dashboard-web/chunks/connectors-page-ODUA5UCW.js +1 -0
  325. package/dist/dashboard-web/chunks/groups-page-OYX5M6NK.js +8 -0
  326. package/dist/dashboard-web/chunks/insights-page-YCQO6OCR.js +22 -0
  327. package/dist/dashboard-web/chunks/legacy-workflow-page-XJAUNONP.js +2 -0
  328. package/dist/dashboard-web/chunks/{monitor-room-RFX4HPS5.js → monitor-room-WTZNYAFV.js} +17 -15
  329. package/dist/dashboard-web/chunks/monitoring-page-R6LJUK7G.js +2 -0
  330. package/dist/dashboard-web/chunks/office-page-4OXK3GNR.js +1 -0
  331. package/dist/dashboard-web/chunks/overview-page-4MVEJY6U.js +1 -0
  332. package/dist/dashboard-web/chunks/roles-page-NQDW27VV.js +1 -0
  333. package/dist/dashboard-web/chunks/schedules-page-JRNKYHOX.js +1 -0
  334. package/dist/dashboard-web/chunks/sessions-page-LF7UVI7B.js +3 -0
  335. package/dist/dashboard-web/chunks/settings-page-WGW7KQQS.js +2 -0
  336. package/dist/dashboard-web/chunks/skills-page-AMD4XP3T.js +3 -0
  337. package/dist/dashboard-web/chunks/team-federation-page-SYG3K4GG.js +1 -0
  338. package/dist/dashboard-web/chunks/v3-page-6NFFWNUK.js +94 -0
  339. package/dist/dashboard-web/chunks/whiteboards-page-FBRUK2AS.js +1 -0
  340. package/dist/dashboard-web/favicon.png +0 -0
  341. package/dist/dashboard-web/index.html +5 -69
  342. package/dist/dashboard-web/style.css +18835 -7211
  343. package/dist/dashboard.js +150 -32
  344. package/dist/dashboard.js.map +1 -1
  345. package/dist/global-config.d.ts +2 -2
  346. package/dist/global-config.d.ts.map +1 -1
  347. package/dist/i18n/en.d.ts.map +1 -1
  348. package/dist/i18n/en.js +3 -0
  349. package/dist/i18n/en.js.map +1 -1
  350. package/dist/i18n/zh.d.ts.map +1 -1
  351. package/dist/i18n/zh.js +3 -0
  352. package/dist/i18n/zh.js.map +1 -1
  353. package/dist/im/lark/card-builder.d.ts +7 -5
  354. package/dist/im/lark/card-builder.d.ts.map +1 -1
  355. package/dist/im/lark/card-builder.js +10 -7
  356. package/dist/im/lark/card-builder.js.map +1 -1
  357. package/dist/im/lark/card-handler.d.ts.map +1 -1
  358. package/dist/im/lark/card-handler.js +25 -1
  359. package/dist/im/lark/card-handler.js.map +1 -1
  360. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  361. package/dist/im/lark/event-dispatcher.js +84 -0
  362. package/dist/im/lark/event-dispatcher.js.map +1 -1
  363. package/dist/platform/binding.d.ts +9 -0
  364. package/dist/platform/binding.d.ts.map +1 -1
  365. package/dist/platform/binding.js +14 -0
  366. package/dist/platform/binding.js.map +1 -1
  367. package/dist/platform/tunnel-client.d.ts.map +1 -1
  368. package/dist/platform/tunnel-client.js +9 -0
  369. package/dist/platform/tunnel-client.js.map +1 -1
  370. package/dist/services/bot-config-store.d.ts +1 -1
  371. package/dist/services/bot-config-store.d.ts.map +1 -1
  372. package/dist/services/bot-config-store.js +11 -5
  373. package/dist/services/bot-config-store.js.map +1 -1
  374. package/dist/services/file-bridge-path.d.ts +8 -0
  375. package/dist/services/file-bridge-path.d.ts.map +1 -0
  376. package/dist/services/file-bridge-path.js +75 -0
  377. package/dist/services/file-bridge-path.js.map +1 -0
  378. package/dist/services/grok-paths.d.ts +27 -0
  379. package/dist/services/grok-paths.d.ts.map +1 -0
  380. package/dist/services/grok-paths.js +106 -0
  381. package/dist/services/grok-paths.js.map +1 -0
  382. package/dist/services/grok-transcript.d.ts +67 -0
  383. package/dist/services/grok-transcript.d.ts.map +1 -0
  384. package/dist/services/grok-transcript.js +630 -0
  385. package/dist/services/grok-transcript.js.map +1 -0
  386. package/dist/services/hermes-session-filter.d.ts +28 -0
  387. package/dist/services/hermes-session-filter.d.ts.map +1 -0
  388. package/dist/services/hermes-session-filter.js +53 -0
  389. package/dist/services/hermes-session-filter.js.map +1 -0
  390. package/dist/services/hermes-transcript.d.ts +3 -0
  391. package/dist/services/hermes-transcript.d.ts.map +1 -1
  392. package/dist/services/hermes-transcript.js +20 -2
  393. package/dist/services/hermes-transcript.js.map +1 -1
  394. package/dist/services/resumable-session-discovery.d.ts +3 -0
  395. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  396. package/dist/services/resumable-session-discovery.js +6 -1
  397. package/dist/services/resumable-session-discovery.js.map +1 -1
  398. package/dist/services/skill-registry-store.d.ts +9 -0
  399. package/dist/services/skill-registry-store.d.ts.map +1 -1
  400. package/dist/services/skill-registry-store.js +304 -1
  401. package/dist/services/skill-registry-store.js.map +1 -1
  402. package/dist/services/structured-bridge-clis.d.ts +29 -0
  403. package/dist/services/structured-bridge-clis.d.ts.map +1 -0
  404. package/dist/services/structured-bridge-clis.js +76 -0
  405. package/dist/services/structured-bridge-clis.js.map +1 -0
  406. package/dist/services/trigger-log-store.d.ts +59 -0
  407. package/dist/services/trigger-log-store.d.ts.map +1 -1
  408. package/dist/services/trigger-log-store.js +81 -10
  409. package/dist/services/trigger-log-store.js.map +1 -1
  410. package/dist/services/usage-ledger.d.ts +4 -1
  411. package/dist/services/usage-ledger.d.ts.map +1 -1
  412. package/dist/services/usage-ledger.js +48 -10
  413. package/dist/services/usage-ledger.js.map +1 -1
  414. package/dist/services/webhook-audit.d.ts +10 -0
  415. package/dist/services/webhook-audit.d.ts.map +1 -0
  416. package/dist/services/webhook-audit.js +116 -0
  417. package/dist/services/webhook-audit.js.map +1 -0
  418. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  419. package/dist/setup/bot-config-editor.js +2 -0
  420. package/dist/setup/bot-config-editor.js.map +1 -1
  421. package/dist/setup/ensure-tmux.d.ts +26 -12
  422. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  423. package/dist/setup/ensure-tmux.js +85 -18
  424. package/dist/setup/ensure-tmux.js.map +1 -1
  425. package/dist/skills/definitions.d.ts.map +1 -1
  426. package/dist/skills/definitions.js +14 -1
  427. package/dist/skills/definitions.js.map +1 -1
  428. package/dist/skills/injection-mode.d.ts +16 -0
  429. package/dist/skills/injection-mode.d.ts.map +1 -1
  430. package/dist/skills/injection-mode.js +25 -0
  431. package/dist/skills/injection-mode.js.map +1 -1
  432. package/dist/types.d.ts +9 -0
  433. package/dist/types.d.ts.map +1 -1
  434. package/dist/utils/global-install.d.ts +22 -0
  435. package/dist/utils/global-install.d.ts.map +1 -0
  436. package/dist/utils/global-install.js +106 -0
  437. package/dist/utils/global-install.js.map +1 -0
  438. package/dist/utils/install-diagnostics.d.ts +1 -1
  439. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  440. package/dist/utils/install-diagnostics.js +6 -4
  441. package/dist/utils/install-diagnostics.js.map +1 -1
  442. package/dist/utils/install-info.d.ts +2 -0
  443. package/dist/utils/install-info.d.ts.map +1 -1
  444. package/dist/utils/install-info.js +10 -4
  445. package/dist/utils/install-info.js.map +1 -1
  446. package/dist/worker.js +293 -172
  447. package/dist/worker.js.map +1 -1
  448. package/dist/workflows/events/payloads.d.ts +10 -10
  449. package/dist/workflows/events/schema.d.ts +28 -28
  450. package/package.json +8 -7
  451. package/dist/dashboard/web/connectors.d.ts +0 -2
  452. package/dist/dashboard/web/connectors.d.ts.map +0 -1
  453. package/dist/dashboard/web/connectors.js +0 -2
  454. package/dist/dashboard/web/connectors.js.map +0 -1
  455. package/dist/dashboard/web/office.d.ts +0 -2
  456. package/dist/dashboard/web/office.d.ts.map +0 -1
  457. package/dist/dashboard/web/office.js +0 -2
  458. package/dist/dashboard/web/office.js.map +0 -1
  459. package/dist/dashboard/web/schedules.d.ts +0 -2
  460. package/dist/dashboard/web/schedules.d.ts.map +0 -1
  461. package/dist/dashboard/web/schedules.js +0 -2
  462. package/dist/dashboard/web/schedules.js.map +0 -1
  463. package/dist/dashboard/web/settings.d.ts +0 -2
  464. package/dist/dashboard/web/settings.d.ts.map +0 -1
  465. package/dist/dashboard/web/settings.js +0 -2
  466. package/dist/dashboard/web/settings.js.map +0 -1
  467. package/dist/dashboard/web/skills.d.ts +0 -2
  468. package/dist/dashboard/web/skills.d.ts.map +0 -1
  469. package/dist/dashboard/web/skills.js +0 -2
  470. package/dist/dashboard/web/skills.js.map +0 -1
  471. package/dist/dashboard/web/skin-intro.d.ts +0 -4
  472. package/dist/dashboard/web/skin-intro.d.ts.map +0 -1
  473. package/dist/dashboard/web/skin-intro.js +0 -49
  474. package/dist/dashboard/web/skin-intro.js.map +0 -1
  475. package/dist/dashboard/web/v3.d.ts +0 -3
  476. package/dist/dashboard/web/v3.d.ts.map +0 -1
  477. package/dist/dashboard/web/v3.js +0 -539
  478. package/dist/dashboard/web/v3.js.map +0 -1
  479. package/dist/dashboard/web/whiteboards.d.ts +0 -2
  480. package/dist/dashboard/web/whiteboards.d.ts.map +0 -1
  481. package/dist/dashboard/web/whiteboards.js +0 -2
  482. package/dist/dashboard/web/whiteboards.js.map +0 -1
  483. package/dist/dashboard-web/chunks/bot-defaults-page-OCIHPGEU.js +0 -438
  484. package/dist/dashboard-web/chunks/chunk-3N2PPQVD.js +0 -1
  485. package/dist/dashboard-web/chunks/chunk-3UJYRR2Q.js +0 -37
  486. package/dist/dashboard-web/chunks/chunk-5E3MJJL4.js +0 -1
  487. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  488. package/dist/dashboard-web/chunks/chunk-JTSE5VP6.js +0 -54
  489. package/dist/dashboard-web/chunks/chunk-LXIK7OUJ.js +0 -1
  490. package/dist/dashboard-web/chunks/chunk-PE6W3C2C.js +0 -1
  491. package/dist/dashboard-web/chunks/chunk-QW54DWU3.js +0 -325
  492. package/dist/dashboard-web/chunks/connectors-IGETYWCP.js +0 -1
  493. package/dist/dashboard-web/chunks/groups-page-MYBS4ZYJ.js +0 -207
  494. package/dist/dashboard-web/chunks/insights-page-UPA32V2S.js +0 -309
  495. package/dist/dashboard-web/chunks/monitoring-page-7T3BESZ2.js +0 -1
  496. package/dist/dashboard-web/chunks/office-page-6DXYOG55.js +0 -1
  497. package/dist/dashboard-web/chunks/overview-page-LJSBCQW4.js +0 -1
  498. package/dist/dashboard-web/chunks/roles-page-ESURTVQH.js +0 -98
  499. package/dist/dashboard-web/chunks/schedules-LML2ISOT.js +0 -1
  500. package/dist/dashboard-web/chunks/sessions-YB73XOTU.js +0 -1
  501. package/dist/dashboard-web/chunks/sessions-page-MCO5QR7Z.js +0 -1
  502. package/dist/dashboard-web/chunks/settings-page-OSDKNVS3.js +0 -2
  503. package/dist/dashboard-web/chunks/skills-page-PXBZJ6P6.js +0 -2
  504. package/dist/dashboard-web/chunks/team-federation-page-TTPVHFPX.js +0 -10
  505. package/dist/dashboard-web/chunks/v3-page-FVPUL665.js +0 -149
  506. package/dist/dashboard-web/chunks/whiteboards-page-HLCTYSSV.js +0 -1
  507. package/dist/dashboard-web/chunks/workflows-FCPM4S4D.js +0 -184
  508. package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
  509. package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
  510. package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
  511. package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
  512. package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
  513. package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
  514. package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
  515. package/dist/dashboard-web/skins/zzz-pattern.svg +0 -25
@@ -1,17 +1,46 @@
1
- // Groups & Bots page: chat × bot membership matrix + add-bots modal.
2
- // The aggregator at /api/groups fans out to all online daemons and merges chats
3
- // by chatId; the dashboard displays this as a matrix where each cell shows
4
- // whether a bot is a member of a given chat.
5
- import { chatAvatarHtml, escapeHtml, t } from './ui.js';
6
- import { hasExplicitChatRole, summarizeGroupProfileMatches, } from './role-profile-match.js';
7
- let cache = { chats: [], bots: [] };
1
+ import { t } from './ui.js';
2
+ import { emptyGroupsSnapshot, fetchGroupsSnapshot, } from './groups-api.js';
3
+ import { effectiveRoleKey, loadEffectiveRoleMap } from './role-batch.js';
4
+ export { emptyGroupsSnapshot, fetchGroupsSnapshot, };
8
5
  const PROFILE_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;
9
- const roleKey = (larkAppId, chatId) => `${larkAppId}\u0000${chatId}`;
10
- let roleProfiles = [];
11
- let roleProfileEntriesById = new Map();
12
- let groupRoleContentByBot = new Map();
13
- let roleProfileContextLoaded = false;
14
- function isValidProfileId(profileId) {
6
+ const GROUP_ROLE_CONTEXT_CONCURRENCY = 6;
7
+ export const GROUPS_PAGE_SIZE = 30;
8
+ export function roleKey(larkAppId, chatId) {
9
+ return effectiveRoleKey(larkAppId, chatId);
10
+ }
11
+ /** Keep the expensive group coverage matrix bounded to one client-side page. */
12
+ export function paginateGroupRows(rows, requestedPage, pageSize = GROUPS_PAGE_SIZE) {
13
+ const safePageSize = Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : GROUPS_PAGE_SIZE;
14
+ const total = rows.length;
15
+ const totalPages = Math.max(1, Math.ceil(total / safePageSize));
16
+ const normalizedPage = Number.isFinite(requestedPage) ? Math.floor(requestedPage) : 1;
17
+ const page = Math.min(totalPages, Math.max(1, normalizedPage));
18
+ const start = (page - 1) * safePageSize;
19
+ const to = Math.min(total, start + safePageSize);
20
+ return {
21
+ rows: rows.slice(start, to),
22
+ page,
23
+ totalPages,
24
+ from: total === 0 ? 0 : start + 1,
25
+ to,
26
+ total,
27
+ };
28
+ }
29
+ async function mapWithConcurrency(items, limit, worker) {
30
+ if (items.length === 0)
31
+ return [];
32
+ const results = new Array(items.length);
33
+ let nextIndex = 0;
34
+ const workerCount = Math.min(Math.max(1, limit), items.length);
35
+ await Promise.all(Array.from({ length: workerCount }, async () => {
36
+ while (nextIndex < items.length) {
37
+ const index = nextIndex++;
38
+ results[index] = await worker(items[index]);
39
+ }
40
+ }));
41
+ return results;
42
+ }
43
+ export function isValidProfileId(profileId) {
15
44
  return PROFILE_ID_RE.test(profileId) && profileId !== '.' && profileId !== '..';
16
45
  }
17
46
  export function suggestRoleProfileIdFromChat(value) {
@@ -23,21 +52,14 @@ export function suggestRoleProfileIdFromChat(value) {
23
52
  .slice(0, 64);
24
53
  return isValidProfileId(cleaned) ? cleaned : 'profile';
25
54
  }
26
- async function loadGroups() {
27
- const r = await fetch('/api/groups');
28
- cache = await r.json();
29
- }
30
- /** Fetch /api/groups once and return the parsed payload without mutating
31
- * `cache` — used by refreshUntilSeen so we can decide whether to commit. */
32
- async function fetchGroups() {
33
- const r = await fetch('/api/groups');
34
- return r.json();
55
+ export async function fetchRoleProfileSummaries() {
56
+ const r = await fetch('/api/role-profiles');
57
+ const body = await r.json().catch(() => ({}));
58
+ return Array.isArray(body.profiles) ? body.profiles : [];
35
59
  }
36
- async function loadGroupRoleProfileContext() {
37
- const profileResp = await fetch('/api/role-profiles');
38
- const profileBody = await profileResp.json().catch(() => ({}));
39
- const nextProfiles = Array.isArray(profileBody.profiles) ? profileBody.profiles : [];
40
- const detailPairs = await Promise.all(nextProfiles.map(async (profile) => {
60
+ export async function loadGroupRoleProfileContext(snapshot) {
61
+ const nextProfiles = await fetchRoleProfileSummaries();
62
+ const detailPairs = await mapWithConcurrency(nextProfiles, GROUP_ROLE_CONTEXT_CONCURRENCY, async (profile) => {
41
63
  try {
42
64
  const r = await fetch(`/api/role-profiles/${encodeURIComponent(profile.profileId)}`);
43
65
  const body = await r.json().catch(() => ({}));
@@ -46,43 +68,75 @@ async function loadGroupRoleProfileContext() {
46
68
  catch {
47
69
  return [profile.profileId, []];
48
70
  }
49
- }));
50
- const nextGroupRoles = new Map();
71
+ });
51
72
  const seenRoleKeys = new Set();
52
- await Promise.all((cache.chats ?? []).flatMap((chat) => (chat.memberBots ?? [])
53
- .filter((bot) => bot?.inChat && bot?.larkAppId)
54
- .map(async (bot) => {
55
- const key = roleKey(bot.larkAppId, chat.chatId);
56
- if (seenRoleKeys.has(key))
57
- return;
58
- seenRoleKeys.add(key);
73
+ const roleTargets = [];
74
+ for (const chat of snapshot.chats ?? []) {
75
+ for (const bot of chat.memberBots ?? []) {
76
+ // Profile matching intentionally considers explicit chat roles only.
77
+ // /api/groups already tells us whether one exists, so skip every
78
+ // unconfigured membership instead of reading its effective team role.
79
+ if (!bot?.inChat || !bot?.hasRole || !bot?.larkAppId)
80
+ continue;
81
+ const key = roleKey(bot.larkAppId, chat.chatId);
82
+ if (seenRoleKeys.has(key))
83
+ continue;
84
+ seenRoleKeys.add(key);
85
+ roleTargets.push({ chatId: chat.chatId, larkAppId: bot.larkAppId });
86
+ }
87
+ }
88
+ const nextGroupRoles = await loadEffectiveRoleMap(roleTargets);
89
+ return {
90
+ profiles: nextProfiles,
91
+ entriesById: new Map(detailPairs),
92
+ groupRoleContentByBot: nextGroupRoles,
93
+ loaded: true,
94
+ };
95
+ }
96
+ export async function collectGroupProfileEntries(chat) {
97
+ const inChat = (chat.memberBots ?? []).filter(bot => bot?.inChat && bot?.larkAppId);
98
+ return mapWithConcurrency(inChat, GROUP_ROLE_CONTEXT_CONCURRENCY, async (bot) => {
59
99
  try {
60
100
  const r = await fetch(`/api/roles/${encodeURIComponent(bot.larkAppId)}/${encodeURIComponent(chat.chatId)}`);
61
101
  const body = await r.json().catch(() => ({}));
62
102
  const hasEffectiveRole = body?.hasEffectiveRole ?? body?.hasRole;
63
103
  const effectiveContent = 'effectiveContent' in body ? body.effectiveContent : body.content;
64
- nextGroupRoles.set(key, {
65
- content: hasEffectiveRole ? String(effectiveContent ?? '') : null,
66
- source: body?.effectiveSource ?? (body?.hasRole ? 'chat' : 'none'),
67
- });
104
+ const content = hasEffectiveRole ? String(effectiveContent ?? '').trim() : '';
105
+ const source = body?.effectiveSource === 'chat' || body?.effectiveSource === 'team'
106
+ ? body.effectiveSource
107
+ : null;
108
+ return {
109
+ larkAppId: bot.larkAppId,
110
+ botName: bot.botName,
111
+ content,
112
+ status: content ? (source ?? 'chat') : 'empty',
113
+ };
68
114
  }
69
115
  catch {
70
- nextGroupRoles.set(key, null);
116
+ return {
117
+ larkAppId: bot.larkAppId,
118
+ botName: bot.botName,
119
+ content: '',
120
+ status: 'error',
121
+ };
71
122
  }
72
- })));
73
- roleProfiles = nextProfiles;
74
- roleProfileEntriesById = new Map(detailPairs);
75
- groupRoleContentByBot = nextGroupRoles;
123
+ });
76
124
  }
77
- async function fetchRoleProfileSummaries() {
78
- const r = await fetch('/api/role-profiles');
79
- const body = await r.json().catch(() => ({}));
80
- return Array.isArray(body.profiles) ? body.profiles : [];
125
+ export function availableBotsForPicker(bots, excludeIds) {
126
+ return bots.filter(bot => !excludeIds || !excludeIds.has(bot.larkAppId));
127
+ }
128
+ export function filterGroupChats(chats, filters) {
129
+ const q = filters.q.trim().toLowerCase();
130
+ return chats
131
+ .filter(chat => !q ||
132
+ (chat.name ?? '').toLowerCase().includes(q) ||
133
+ chat.chatId.toLowerCase().includes(q) ||
134
+ (chat.ownerId ?? '').toLowerCase().includes(q))
135
+ .filter(chat => !filters.missingOnly || (chat.memberBots ?? []).some(member => !member.inChat));
81
136
  }
82
137
  /** True iff every expected bot id appears in the row's memberBots with
83
138
  * inChat:true. Used by refreshUntilSeen to defer committing a canonical
84
- * snapshot until all invited bots have caught up Lark-side. Exported so
85
- * tests can exercise the predicate without spinning up jsdom. */
139
+ * snapshot until all invited bots have caught up Lark-side. */
86
140
  export function allExpectedInChat(row, expectedBotIds) {
87
141
  if (expectedBotIds.size === 0)
88
142
  return true;
@@ -93,1062 +147,57 @@ export function allExpectedInChat(row, expectedBotIds) {
93
147
  }
94
148
  return true;
95
149
  }
96
- /** Render the bot-picker checkboxes shared by "Create new group" and
97
- * "Add bots". Always iterates `bots` in its given order (the aggregator
98
- * hands cache.bots back sorted by botIndex), so both modals show the
99
- * same order. `excludeIds` removes bots that are already in the chat
100
- * for the Add-bots flow. Exported for tests. */
101
- export function renderBotCheckboxes(bots, excludeIds) {
102
- return bots
103
- .filter(b => !excludeIds || !excludeIds.has(b.larkAppId))
104
- .map(b => `
105
- <label class="checkbox-row">
106
- <input type="checkbox" name="bot" value="${escapeHtml(b.larkAppId)}">
107
- ${escapeHtml(b.botName ?? b.larkAppId)} <small>(${escapeHtml(b.larkAppId)})</small>
108
- </label>
109
- `).join('');
150
+ export function summarizeAddBotsResult(result) {
151
+ const rows = Array.isArray(result) ? result : [];
152
+ const okCount = rows.filter(row => !!row?.ok).length;
153
+ return { rows, okCount, failed: rows.length - okCount };
110
154
  }
111
- export function renderRoleProfileBootstrapSummary(profileId, messageId, error) {
155
+ export function roleProfileBootstrapStatus(profileId, messageId, error) {
112
156
  const cleanProfileId = String(profileId ?? '').trim();
113
157
  if (!cleanProfileId)
114
- return '';
158
+ return null;
115
159
  if (error) {
116
- return `<p class="hint-warn">${escapeHtml(t('groups.roleProfileBootstrapFailed', {
117
- name: cleanProfileId,
118
- reason: String(error),
119
- }))}</p>`;
160
+ return {
161
+ kind: 'warn',
162
+ text: t('groups.roleProfileBootstrapFailed', {
163
+ name: cleanProfileId,
164
+ reason: String(error),
165
+ }),
166
+ };
120
167
  }
121
168
  const cleanMessageId = typeof messageId === 'string' && messageId.trim() ? messageId.trim() : '';
122
169
  if (cleanMessageId) {
123
- return `<p class="hint-ok">${escapeHtml(t('groups.roleProfileBootstrapSent', {
124
- name: cleanProfileId,
125
- messageId: cleanMessageId,
126
- }))}</p>`;
127
- }
128
- return `<p class="hint-ok">${escapeHtml(t('groups.roleProfileBootstrapDone', { name: cleanProfileId }))}</p>`;
129
- }
130
- export function renderAddBotsResultSummary(result) {
131
- const rows = Array.isArray(result) ? result : [];
132
- if (!rows.length) {
133
- return `<p class="hint-warn">没有返回添加结果。</p>`;
134
- }
135
- const okCount = rows.filter(x => !!x?.ok).length;
136
- const failed = rows.length - okCount;
137
- const cls = failed ? 'hint-warn' : 'hint-ok';
138
- const items = rows.map(x => {
139
- const id = String(x?.id ?? '?');
140
- return x?.ok
141
- ? `<li><code>${escapeHtml(id)}</code>: OK</li>`
142
- : `<li><code>${escapeHtml(id)}</code>: failed (${escapeHtml(String(x?.error ?? 'unknown'))})</li>`;
143
- }).join('');
144
- return `<div class="${cls}">
145
- <strong>添加结果:成功 ${okCount}/${rows.length}${failed ? `,失败 ${failed}` : ''}</strong>
146
- <ul>${items}</ul>
147
- </div>`;
148
- }
149
- export function wireGroupsPage(root) {
150
- let disposed = false;
151
- const timers = new Set();
152
- const delayResolvers = new Map();
153
- const setTimer = (fn, ms) => {
154
- const id = window.setTimeout(() => {
155
- timers.delete(id);
156
- if (!disposed)
157
- fn();
158
- }, ms);
159
- timers.add(id);
160
- return id;
161
- };
162
- const delay = (ms) => new Promise(resolve => {
163
- const id = window.setTimeout(() => {
164
- timers.delete(id);
165
- delayResolvers.delete(id);
166
- resolve();
167
- }, ms);
168
- timers.add(id);
169
- delayResolvers.set(id, resolve);
170
- });
171
- const head = root.querySelector('#g-head');
172
- const body = root.querySelector('#g-body');
173
- const form = root.querySelector('#g-filters');
174
- const refreshBtn = root.querySelector('#g-refresh');
175
- const drawer = root.querySelector('#g-drawer');
176
- refreshBtn.onclick = async () => {
177
- refreshBtn.disabled = true;
178
- try {
179
- await loadGroups();
180
- if (disposed)
181
- return;
182
- rerender();
183
- void refreshRoleProfileContext();
184
- }
185
- finally {
186
- if (!disposed)
187
- refreshBtn.disabled = false;
188
- }
189
- };
190
- const createBtn = root.querySelector('#g-create');
191
- createBtn.onclick = () => { void openCreateModal(); };
192
- // /api/groups 要扇出到所有 daemon、逐群查成员,慢——先亮 loading,回来再换表格。
193
- const loadingEl = root.querySelector('#g-loading');
194
- const tableWrap = root.querySelector('#g-table-wrap');
195
- void (async () => {
196
- try {
197
- await loadGroups();
198
- }
199
- finally {
200
- if (disposed)
201
- return;
202
- loadingEl.remove();
203
- tableWrap.hidden = false;
204
- rerender();
205
- void refreshRoleProfileContext();
206
- }
207
- })();
208
- async function refreshRoleProfileContext() {
209
- try {
210
- await loadGroupRoleProfileContext();
211
- }
212
- catch {
213
- roleProfiles = [];
214
- roleProfileEntriesById = new Map();
215
- groupRoleContentByBot = new Map();
216
- }
217
- finally {
218
- if (disposed)
219
- return;
220
- roleProfileContextLoaded = true;
221
- rerender();
222
- }
223
- }
224
- function setDialogStatus(el, html) {
225
- el.innerHTML = html;
226
- }
227
- function renderDialogError(title, reason) {
228
- return `<p class="hint-warn"><strong>${escapeHtml(title)}</strong><br><small>${escapeHtml(String(reason ?? 'unknown'))}</small></p>`;
229
- }
230
- function restoreSubmit(btn, label) {
231
- if (!btn)
232
- return;
233
- btn.disabled = false;
234
- btn.textContent = label;
235
- }
236
- async function openCreateModal() {
237
- const allBots = cache.bots;
238
- if (allBots.length === 0) {
239
- alert(t('groups.noBotsOnline'));
240
- return;
241
- }
242
- let roleProfiles = [];
243
- try {
244
- const r = await fetch('/api/role-profiles');
245
- const data = await r.json();
246
- roleProfiles = data.profiles ?? [];
247
- }
248
- catch { /* profile selector is optional */ }
249
- if (disposed)
250
- return;
251
- drawer.innerHTML = `
252
- <article>
253
- <header><h3>${t('groups.createTitle')}</h3></header>
254
- <p>${t('groups.createHelp')}</p>
255
- <form id="g-createform">
256
- <label class="form-row">
257
- <span>${t('groups.name')}</span>
258
- <input type="text" name="name" placeholder="${t('groups.namePlaceholder')}" maxlength="60">
259
- </label>
260
- <label class="form-row">
261
- <span>${t('groups.bindDir')}</span>
262
- <input type="text" name="bindWorkingDir" placeholder="e.g. ~/projects/botmux">
263
- <small>${t('groups.bindDirHelp')}</small>
264
- </label>
265
- <label class="form-row">
266
- <span>${t('groups.roleProfile')}</span>
267
- <select name="roleProfileId">
268
- <option value="">${t('groups.roleProfileNone')}</option>
269
- ${roleProfiles.map(p => `<option value="${escapeHtml(p.profileId)}">${escapeHtml(p.profileId)}</option>`).join('')}
270
- </select>
271
- <small>${t('groups.roleProfileHelp')}</small>
272
- </label>
273
- <fieldset>
274
- <legend>${t('groups.botPicker')}</legend>
275
- ${renderBotCheckboxes(allBots)}
276
- </fieldset>
277
- <div data-create-status aria-live="polite"></div>
278
- <div class="actions">
279
- <button type="submit" class="primary">${t('groups.createSubmit')}</button>
280
- <button type="button" id="g-create-cancel">${t('groups.cancel')}</button>
281
- </div>
282
- </form>
283
- </article>`;
284
- drawer.showModal();
285
- drawer.querySelector('#g-create-cancel').onclick = () => drawer.close();
286
- drawer.querySelector('#g-createform').onsubmit = async (ev) => {
287
- ev.preventDefault();
288
- const fd = new FormData(ev.target);
289
- const name = (fd.get('name') ?? '').trim();
290
- const bindWorkingDir = (fd.get('bindWorkingDir') ?? '').trim();
291
- const roleProfileId = (fd.get('roleProfileId') ?? '').trim();
292
- const ids = fd.getAll('bot');
293
- const statusEl = drawer.querySelector('[data-create-status]');
294
- if (ids.length === 0) {
295
- setDialogStatus(statusEl, renderDialogError('请选择 bot', '至少选择一个 bot 后再创建群聊。'));
296
- return;
297
- }
298
- const submitBtn = ev.target.querySelector('button[type=submit]');
299
- if (submitBtn) {
300
- submitBtn.disabled = true;
301
- submitBtn.textContent = 'Creating...';
302
- }
303
- setDialogStatus(statusEl, '');
304
- try {
305
- const r = await fetch('/api/groups/create', {
306
- method: 'POST',
307
- headers: { 'content-type': 'application/json' },
308
- body: JSON.stringify({
309
- name: name || undefined,
310
- larkAppIds: ids,
311
- bindWorkingDir: bindWorkingDir || undefined,
312
- roleProfileId: roleProfileId || undefined,
313
- }),
314
- });
315
- // 兜底:平台反代抖动时可能回非 JSON 体(旧平台)或 502。别再让 r.json() 抛
316
- // SyntaxError 弹「网络错误」——解析失败就当作一次失败,走下面的「创建失败」分支。
317
- const respBody = await r.json().catch(() => ({ ok: false, error: `HTTP ${r.status}` }));
318
- if (disposed)
319
- return;
320
- if (respBody.ok && respBody.chatId) {
321
- renderCreateSuccess(respBody);
322
- // Lark's chat.list has eventual consistency: the chat we just
323
- // created via /api/groups/create often doesn't appear in the next
324
- // /api/groups response for a few seconds. Without intervention the
325
- // background refresh would overwrite cache with a stale snapshot
326
- // and the new group would silently vanish from the matrix until
327
- // the user hit Refresh manually.
328
- //
329
- // Optimistic insert: render the new group instantly using the
330
- // creator's larkAppId + the form's selected bot ids minus any
331
- // invalidBotIds Lark rejected. The refresh poll below keeps
332
- // re-fetching until Lark confirms ALL expected bots show inChat,
333
- // then commits the canonical response; until then we keep the
334
- // optimistic row. Stopping on first sight of just `chatId` would
335
- // commit a partial-membership snapshot — see Codex review.
336
- const invalidBotIds = Array.isArray(respBody.invalidBotIds) ? respBody.invalidBotIds : [];
337
- const validIds = ids.filter(id => !invalidBotIds.includes(id));
338
- const expectedBotIds = new Set(validIds);
339
- if (typeof respBody.creator === 'string' && respBody.creator)
340
- expectedBotIds.add(respBody.creator);
341
- injectOptimisticChat(respBody.chatId, name || respBody.chatId, validIds, respBody.creator);
342
- rerender();
343
- void refreshRoleProfileContext();
344
- void refreshUntilSeen(respBody.chatId, expectedBotIds).catch(() => { });
345
- }
346
- else {
347
- setDialogStatus(statusEl, renderDialogError('创建失败', respBody.error ?? `HTTP ${r.status}`));
348
- restoreSubmit(submitBtn, t('groups.createSubmit'));
349
- }
350
- }
351
- catch (e) {
352
- setDialogStatus(statusEl, renderDialogError('网络错误', e));
353
- restoreSubmit(submitBtn, t('groups.createSubmit'));
354
- }
170
+ return {
171
+ kind: 'ok',
172
+ text: t('groups.roleProfileBootstrapSent', {
173
+ name: cleanProfileId,
174
+ messageId: cleanMessageId,
175
+ }),
355
176
  };
356
- function injectOptimisticChat(chatId, displayName, memberIds, creator) {
357
- const inChatSet = new Set(memberIds);
358
- if (creator)
359
- inChatSet.add(creator);
360
- const memberBots = cache.bots.map((b) => ({
361
- larkAppId: b.larkAppId,
362
- botName: b.botName,
363
- inChat: inChatSet.has(b.larkAppId),
364
- oncallChat: null,
365
- }));
366
- const optimistic = {
367
- chatId,
368
- name: displayName,
369
- ownerId: creator ?? null,
370
- memberBots,
371
- };
372
- // Drop any duplicate (defensive — shouldn't fire, but cheap) and
373
- // prepend so the new row lands at the top of the matrix.
374
- cache.chats = [optimistic, ...cache.chats.filter((c) => c.chatId !== chatId)];
375
- }
376
- async function refreshUntilSeen(chatId, expectedBotIds) {
377
- // Total budget ≈ 0.6 + 5*1.2 = ~6.6s. Commit only when the row is
378
- // present AND every expected bot reports inChat:true — otherwise we'd
379
- // overwrite our optimistic ✓ marks with a partial canonical snapshot
380
- // (creator daemon often lags vs. invitee daemons on Lark's side).
381
- // If the budget runs out the optimistic row stays put; next manual
382
- // Refresh will reconcile.
383
- const delays = [600, 1200, 1200, 1200, 1200, 1200];
384
- for (const d of delays) {
385
- await delay(d);
386
- if (disposed)
387
- return;
388
- let next;
389
- try {
390
- next = await fetchGroups();
391
- }
392
- catch {
393
- continue;
394
- }
395
- if (disposed)
396
- return;
397
- const row = (next.chats ?? []).find((c) => c.chatId === chatId);
398
- if (row && allExpectedInChat(row, expectedBotIds)) {
399
- cache = next;
400
- rerender();
401
- void refreshRoleProfileContext();
402
- return;
403
- }
404
- }
405
- }
406
177
  }
407
- function renderCreateSuccess(resp) {
408
- const chatId = String(resp.chatId);
409
- // 优先用服务端返回的 shareLink(由 Lark/飞书建群 API 给出,天然带正确品牌域名);
410
- // 仅当缺失(share-link API 失败)时回退到 applink。浏览器侧拿不到 brand,这个
411
- // 回退默认 feishu host——属极少触发的兜底,不影响 Lark 正常建群(有 shareLink)。
412
- const appLink = typeof resp.shareLink === 'string' && resp.shareLink
413
- ? resp.shareLink
414
- : `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
415
- const invalidBots = (resp.invalidBotIds ?? []);
416
- const invalidUsers = (resp.invalidUserIds ?? []);
417
- const auto = resp.autoInvitedOpenId;
418
- const rejected = !!resp.autoInviteRejected;
419
- const ownerTo = resp.ownerTransferredTo;
420
- const transferErr = resp.transferError;
421
- const notifyMsgId = resp.notifyMessageId;
422
- const notifyErr = resp.notifyError;
423
- const binds = Array.isArray(resp.oncallBindings) ? resp.oncallBindings : [];
424
- const bindOk = binds.filter(b => b?.ok).length;
425
- const bindFailed = binds.filter(b => !b?.ok);
426
- const bindNote = binds.length > 0
427
- ? bindFailed.length === 0
428
- ? `<p class="hint-ok">已绑定目录:<code>${escapeHtml(resp.bindResolvedPath ?? '')}</code>(${bindOk}/${binds.length} bots)</p>`
429
- : `<p class="hint-warn">目录绑定部分失败:成功 ${bindOk}/${binds.length}。${bindFailed.map(b => `<br><code>${escapeHtml(b.larkAppId ?? '?')}</code>: ${escapeHtml(b.error ?? 'unknown')}`).join('')}</p>`
430
- : '';
431
- let inviteNote;
432
- if (auto) {
433
- const transferLine = ownerTo
434
- ? `<br><small>群主已从机器人转让给你。</small>`
435
- : transferErr
436
- ? `<br><small class="hint-warn-inline">⚠ 自动转让群主失败(${escapeHtml(transferErr)}),你现在是成员但群主仍是机器人。</small>`
437
- : '';
438
- const notifyLine = notifyMsgId
439
- ? `<br><small>机器人已在群里 @ 了你(消息 id <code>${escapeHtml(notifyMsgId)}</code>),看飞书通知就能进群。</small>`
440
- : notifyErr
441
- ? `<br><small class="hint-warn-inline">⚠ 自动 @ 通知失败(${escapeHtml(notifyErr)}),新群可能不会主动出现在你侧边栏,建议从下面按钮跳进去。</small>`
442
- : '';
443
- inviteNote = `<p class="hint-ok">已自动邀请你(<code>${escapeHtml(auto)}</code>)作为成员。${transferLine}${notifyLine}</p>`;
444
- }
445
- else if (rejected) {
446
- inviteNote = `<p class="hint-warn">飞书拒绝了自动邀请(你的 open_id 在创建者 bot 的 scope 下不可用)。<strong>你目前不是新群成员</strong>,需要让群里的某个机器人手动把你加进来。</p>`;
447
- }
448
- else {
449
- inviteNote = `<p class="hint-warn">没在 dashboard 缓存里找到 ownerOpenId,<strong>没有自动邀请你</strong>。点开下面链接前,先让群里任一机器人手动把你加进去。</p>`;
450
- }
451
- const invalidNote = [
452
- invalidBots.length ? `<li>无效 bot id: <code>${invalidBots.map(escapeHtml).join(', ')}</code></li>` : '',
453
- invalidUsers.length ? `<li>无效用户 open_id: <code>${invalidUsers.map(escapeHtml).join(', ')}</code></li>` : '',
454
- ].filter(Boolean).join('');
455
- const profileNote = renderRoleProfileBootstrapSummary(typeof resp.roleProfileId === 'string' ? resp.roleProfileId : '', resp.roleProfileBootstrapMessageId, resp.roleProfileBootstrapError);
456
- drawer.innerHTML = `
457
- <article>
458
- <header><h3>${t('groups.successTitle')}</h3></header>
459
- <p><b>chatId:</b> <code>${escapeHtml(chatId)}</code> <button type="button" data-copy="${escapeHtml(chatId)}">${t('sessions.copy')}</button></p>
460
- <p><b>创建者:</b> <code>${escapeHtml(resp.creator ?? '?')}</code></p>
461
- ${inviteNote}
462
- ${bindNote}
463
- ${profileNote}
464
- ${invalidNote ? `<ul>${invalidNote}</ul>` : ''}
465
- <div class="actions">
466
- <a class="btn-link primary" href="${appLink}" target="_blank" rel="noopener">${t('groups.openGroup')}</a>
467
- <button type="button" id="g-create-close">${t('sessions.dismiss')}</button>
468
- </div>
469
- </article>`;
470
- drawer.querySelectorAll('[data-copy]').forEach(b => {
471
- b.onclick = () => {
472
- navigator.clipboard.writeText(b.dataset.copy ?? '');
473
- b.textContent = t('sessions.copied');
474
- setTimer(() => { b.textContent = t('sessions.copy'); }, 800);
475
- };
476
- });
477
- drawer.querySelector('#g-create-close').onclick = () => drawer.close();
478
- }
479
- function renderGroupProfileStatus(chat) {
480
- if (!roleProfiles.length || !roleProfileContextLoaded)
481
- return '';
482
- const rolesByBot = new Map();
483
- for (const bot of chat.memberBots ?? []) {
484
- if (!bot?.inChat)
485
- continue;
486
- rolesByBot.set(bot.larkAppId, groupRoleContentByBot.get(roleKey(bot.larkAppId, chat.chatId)) ?? null);
487
- }
488
- if (!hasExplicitChatRole(rolesByBot))
489
- return '';
490
- const matches = summarizeGroupProfileMatches(chat.memberBots ?? [], roleProfiles, roleProfileEntriesById, rolesByBot);
491
- const best = matches[0];
492
- if (!best) {
493
- return `<div class="g-profile-status muted">${t('groups.profileStatusUnmatched')}</div>`;
494
- }
495
- const key = best.kind === 'full' ? 'groups.profileStatusFullChat' : 'groups.profileStatusPartial';
496
- return `<div class="g-profile-status ${best.kind}">
497
- ${escapeHtml(t(key, {
498
- name: best.profileId,
499
- matched: best.matched,
500
- total: best.total,
501
- chat: best.chatMatched,
502
- }))}
503
- </div>`;
504
- }
505
- function renderHead() {
506
- head.innerHTML = `<tr>
507
- <th>${t('groups.chat')}</th>
508
- ${cache.bots.map(b => `<th>${escapeHtml(b.botName ?? b.larkAppId)}</th>`).join('')}
509
- <th>${t('groups.actions')}</th>
510
- </tr>`;
511
- }
512
- function rerender() {
513
- renderHead();
514
- const f = new FormData(form);
515
- const q = (f.get('q') ?? '').toLowerCase();
516
- const onlyMissing = !!f.get('missing');
517
- const filtered = cache.chats
518
- .filter(c => !q ||
519
- (c.name ?? '').toLowerCase().includes(q) ||
520
- c.chatId.toLowerCase().includes(q) ||
521
- (c.ownerId ?? '').toLowerCase().includes(q))
522
- .filter(c => !onlyMissing || c.memberBots.some((m) => !m.inChat));
523
- if (filtered.length === 0) {
524
- body.innerHTML = `<tr><td colspan="${cache.bots.length + 2}" class="empty">${t('groups.empty')}</td></tr>`;
525
- return;
526
- }
527
- body.innerHTML = filtered.map(c => `<tr data-chat="${escapeHtml(c.chatId)}">
528
- <td>
529
- <div class="g-chat-cell">
530
- ${chatAvatarHtml({ chatId: c.chatId, name: c.name, avatarUrl: c.avatar, size: 'sm' })}
531
- <div class="g-chat-meta">
532
- <strong>${escapeHtml(c.name ?? c.chatId)}</strong><br>
533
- <small><code>${escapeHtml(c.chatId)}</code></small>
534
- ${renderGroupProfileStatus(c)}
535
- </div>
536
- </div>
537
- </td>
538
- ${cache.bots.map(b => {
539
- const m = c.memberBots.find((m) => m.larkAppId === b.larkAppId);
540
- const cell = !m ? '?' : m.error ? '!' : m.inChat ? '✓' : '✗';
541
- const cls = !m ? 'cell-unknown' : m.error ? 'cell-error' : m.inChat ? 'cell-in' : 'cell-out';
542
- return `<td class="${cls}" title="${escapeHtml(m?.error ?? '')}">${cell}</td>`;
543
- }).join('')}
544
- <td>
545
- <button class="add-bots" type="button">${t('groups.addBots')}</button>
546
- <button class="save-profile" type="button">${t('groups.saveAsProfile')}</button>
547
- <button class="manage-chat" type="button">${t('groups.manage')}</button>
548
- </td>
549
- </tr>`).join('');
550
- }
551
- const onAddBotsClick = async (e) => {
552
- const btn = e.target.closest('button.add-bots');
553
- if (!btn)
554
- return;
555
- const tr = btn.closest('tr[data-chat]');
556
- const chatId = tr.dataset.chat;
557
- const chat = cache.chats.find(c => c.chatId === chatId);
558
- if (!chat)
559
- return;
560
- // Iterate cache.bots (botIndex-sorted by the aggregator) so this modal's
561
- // order matches "Create new group" exactly. memberBots is only used to
562
- // compute which bots to hide as already-in-chat.
563
- const inChatSet = new Set(chat.memberBots.filter((m) => m.inChat).map((m) => m.larkAppId));
564
- const missing = cache.bots.filter((b) => !inChatSet.has(b.larkAppId));
565
- if (!missing.length) {
566
- alert('All configured bots are already in this chat.');
567
- return;
568
- }
569
- drawer.innerHTML = `
570
- <article>
571
- <header><h3>${t('groups.addBots')} · ${escapeHtml(chat.name ?? chat.chatId)}</h3></header>
572
- <p>${t('groups.createHelp')}</p>
573
- <form id="g-addform">
574
- ${renderBotCheckboxes(cache.bots, inChatSet)}
575
- <div data-add-status aria-live="polite"></div>
576
- <div class="actions">
577
- <button type="submit" class="primary">${t('groups.addBots')}</button>
578
- <button type="button" id="g-cancel">${t('groups.cancel')}</button>
579
- </div>
580
- </form>
581
- </article>`;
582
- drawer.showModal();
583
- drawer.querySelector('#g-cancel').onclick = () => drawer.close();
584
- drawer.querySelector('#g-addform').onsubmit = async (ev) => {
585
- ev.preventDefault();
586
- const fd = new FormData(ev.target);
587
- const ids = fd.getAll('bot');
588
- const statusEl = drawer.querySelector('[data-add-status]');
589
- if (ids.length === 0) {
590
- setDialogStatus(statusEl, renderDialogError('请选择 bot', '至少选择一个 bot 后再添加。'));
591
- return;
592
- }
593
- const submitBtn = ev.target.querySelector('button[type=submit]');
594
- if (submitBtn) {
595
- submitBtn.disabled = true;
596
- submitBtn.textContent = 'Adding...';
597
- }
598
- setDialogStatus(statusEl, '');
599
- try {
600
- const r = await fetch(`/api/groups/${encodeURIComponent(chatId)}/add-bots`, {
601
- method: 'POST',
602
- headers: { 'content-type': 'application/json' },
603
- body: JSON.stringify({ larkAppIds: ids }),
604
- });
605
- const respBody = await r.json();
606
- if (disposed)
607
- return;
608
- if (respBody.error === 'no_proxy_bot') {
609
- setDialogStatus(statusEl, renderDialogError('无法添加 bot', '当前群里没有可代理操作的 bot。请先在飞书里手动拉入一个 bot,然后重试。'));
610
- }
611
- else if (respBody.result) {
612
- const resultHtml = renderAddBotsResultSummary(respBody.result);
613
- setDialogStatus(statusEl, resultHtml);
614
- try {
615
- await loadGroups();
616
- if (disposed)
617
- return;
618
- rerender();
619
- void refreshRoleProfileContext();
620
- }
621
- catch (e) {
622
- if (!disposed) {
623
- setDialogStatus(statusEl, `${resultHtml}${renderDialogError('刷新失败', `添加结果已返回,但刷新群组列表失败:${e}`)}`);
624
- }
625
- }
626
- }
627
- else {
628
- setDialogStatus(statusEl, renderDialogError('响应异常', JSON.stringify(respBody)));
629
- }
630
- }
631
- catch (e) {
632
- setDialogStatus(statusEl, renderDialogError('网络错误', e));
633
- }
634
- finally {
635
- if (!disposed)
636
- restoreSubmit(submitBtn, t('groups.addBots'));
637
- }
638
- };
639
- };
640
- body.addEventListener('click', onAddBotsClick);
641
- const onSaveProfileClick = async (e) => {
642
- const btn = e.target.closest('button.save-profile');
643
- if (!btn)
644
- return;
645
- const tr = btn.closest('tr[data-chat]');
646
- const chatId = tr.dataset.chat;
647
- const chat = cache.chats.find(c => c.chatId === chatId);
648
- if (!chat)
649
- return;
650
- await saveGroupRolesAsProfile(chat, btn);
178
+ return {
179
+ kind: 'ok',
180
+ text: t('groups.roleProfileBootstrapDone', { name: cleanProfileId }),
651
181
  };
652
- body.addEventListener('click', onSaveProfileClick);
653
- async function saveGroupRolesAsProfile(chat, btn) {
654
- const suggestedByName = suggestRoleProfileIdFromChat(chat.name ?? '');
655
- const suggested = suggestedByName === 'profile'
656
- ? suggestRoleProfileIdFromChat(chat.chatId)
657
- : suggestedByName;
658
- btn.disabled = true;
659
- const originalText = btn.textContent;
660
- btn.textContent = t('groups.saveProfileSaving');
661
- try {
662
- const [entries, profiles] = await Promise.all([
663
- collectGroupProfileEntries(chat),
664
- fetchRoleProfileSummaries(),
665
- ]);
666
- if (disposed)
667
- return;
668
- openSaveProfileDialog(chat, suggested, entries, profiles);
669
- }
670
- finally {
671
- if (!disposed) {
672
- btn.disabled = false;
673
- btn.textContent = originalText;
674
- }
675
- }
676
- }
677
- async function collectGroupProfileEntries(chat) {
678
- const inChat = (chat.memberBots ?? []).filter((bot) => bot?.inChat && bot?.larkAppId);
679
- const roleRows = await Promise.all(inChat.map(async (bot) => {
680
- try {
681
- const r = await fetch(`/api/roles/${encodeURIComponent(bot.larkAppId)}/${encodeURIComponent(chat.chatId)}`);
682
- const body = await r.json().catch(() => ({}));
683
- const hasEffectiveRole = body?.hasEffectiveRole ?? body?.hasRole;
684
- const effectiveContent = 'effectiveContent' in body ? body.effectiveContent : body.content;
685
- const content = hasEffectiveRole ? String(effectiveContent ?? '').trim() : '';
686
- const source = body?.effectiveSource === 'chat' || body?.effectiveSource === 'team'
687
- ? body.effectiveSource
688
- : null;
689
- return {
690
- larkAppId: bot.larkAppId,
691
- botName: bot.botName,
692
- content,
693
- status: content ? (source ?? 'chat') : 'empty',
694
- };
695
- }
696
- catch {
697
- return {
698
- larkAppId: bot.larkAppId,
699
- botName: bot.botName,
700
- content: '',
701
- status: 'error',
702
- };
703
- }
704
- }));
705
- return roleRows;
706
- }
707
- function openSaveProfileDialog(chat, suggestedProfileId, entries, profiles) {
708
- const sortedProfiles = [...profiles].sort((a, b) => a.profileId.localeCompare(b.profileId));
709
- const profileButtons = sortedProfiles
710
- .map((profile, index) => `
711
- <button type="button"
712
- class="g-save-profile-pick ${index === 0 ? 'selected' : ''}"
713
- data-profile-id="${escapeHtml(profile.profileId)}"
714
- aria-pressed="${index === 0 ? 'true' : 'false'}">
715
- <span>${escapeHtml(profile.profileId)}</span>
716
- <small>${escapeHtml(t('groups.saveProfileExistingMeta', { count: profile.entryCount ?? 0 }))}</small>
717
- </button>`)
718
- .join('');
719
- const hasExistingProfiles = sortedProfiles.length > 0;
720
- const emptyCount = entries.filter(entry => entry.status === 'empty').length;
721
- const failedCount = entries.filter(entry => entry.status === 'error').length;
722
- const canSubmitSnapshot = entries.length > 0 && failedCount === 0;
723
- const entryRows = entries.map(entry => `
724
- <div class="g-save-profile-entry ${entry.status === 'error' ? 'error' : ''}">
725
- <div>
726
- <strong>${escapeHtml(entry.botName ?? entry.larkAppId)}</strong>
727
- <code>${escapeHtml(entry.larkAppId)}</code>
728
- </div>
729
- <span class="g-save-profile-entry-status ${entry.status === 'error' ? 'error' : 'ok'}">
730
- ${t(entry.status === 'error' ? 'groups.saveProfileStatus.error' : 'groups.saveProfileStatus.entry')}
731
- </span>
732
- </div>`).join('');
733
- drawer.innerHTML = `
734
- <article class="g-save-profile-dialog">
735
- <header>
736
- <h3>${t('groups.saveProfileTitle')}</h3>
737
- <p>${escapeHtml(t('groups.saveProfileIntro', {
738
- name: chat.name ?? chat.chatId,
739
- count: entries.length,
740
- }))}</p>
741
- </header>
742
- <form id="g-save-profile-form">
743
- <section class="g-save-profile-panel">
744
- <div class="g-save-profile-section-head">
745
- <span>${t('groups.saveProfileScope')}</span>
746
- <small>${t('groups.saveProfileScopeHelp')}</small>
747
- </div>
748
- <div class="g-save-profile-stats">
749
- <span>${t('groups.saveProfileBotCount')} <strong>${entries.length}</strong></span>
750
- ${failedCount ? `<span class="warn">${t('groups.saveProfileLoadFailed')} <strong>${failedCount}</strong></span>` : ''}
751
- </div>
752
- <div class="g-save-profile-entry-list">
753
- ${entryRows || `<div class="g-save-profile-empty">${t('groups.saveProfileNoRoles')}</div>`}
754
- </div>
755
- </section>
756
-
757
- <div class="form-row">
758
- <span>${t('groups.saveProfileMode')}</span>
759
- <div class="g-save-profile-switch" role="tablist" aria-label="${t('groups.saveProfileMode')}">
760
- <button type="button" class="active" data-save-profile-mode="new">${t('groups.saveProfileNew')}</button>
761
- <button type="button" data-save-profile-mode="overwrite" ${hasExistingProfiles ? '' : 'disabled'}>${t('groups.saveProfileOverwrite')}</button>
762
- </div>
763
- </div>
764
- <label class="form-row" data-profile-mode-row="new">
765
- <span>${t('groups.saveProfileIdLabel')}</span>
766
- <input type="text" name="profileId" value="${escapeHtml(suggestedProfileId)}" maxlength="64" autocomplete="off">
767
- <small>${t('groups.saveProfileInvalid')}</small>
768
- </label>
769
- <div class="form-row" data-profile-mode-row="overwrite" hidden>
770
- <span>${t('groups.saveProfileExistingLabel')}</span>
771
- ${hasExistingProfiles
772
- ? `<div class="g-save-profile-picker">${profileButtons}</div>`
773
- : `<div class="g-save-profile-summary warn">${t('groups.saveProfileExistingEmpty')}</div>`}
774
- <small>${t('groups.saveProfileOverwriteHelp')}</small>
775
- </div>
776
- <div class="g-save-profile-target">
777
- <span>${t('groups.saveProfileTarget')}</span>
778
- <code data-save-profile-target>${escapeHtml(suggestedProfileId)}</code>
779
- <small data-save-profile-target-mode>${t('groups.saveProfileTargetNew')}</small>
780
- </div>
781
- <div class="g-save-profile-summary ${canSubmitSnapshot ? '' : 'warn'}">
782
- ${failedCount
783
- ? escapeHtml(t('groups.saveProfileFailedLoadSummary', { count: failedCount }))
784
- : entries.length
785
- ? escapeHtml(emptyCount
786
- ? t('groups.saveProfileEntrySummaryWithEmpty', { count: entries.length, emptyCount })
787
- : t('groups.saveProfileEntrySummary', { count: entries.length }))
788
- : escapeHtml(t('groups.saveProfileNoRoles'))}
789
- </div>
790
- <div class="g-save-profile-status" data-save-profile-status></div>
791
- <div class="actions">
792
- <button type="submit" class="primary" ${canSubmitSnapshot ? '' : 'disabled'}>${t('groups.saveProfileSubmit')}</button>
793
- <button type="button" id="g-save-profile-cancel">${t('groups.cancel')}</button>
794
- </div>
795
- </form>
796
- </article>`;
797
- drawer.showModal();
798
- const formEl = drawer.querySelector('#g-save-profile-form');
799
- const modeRows = [...drawer.querySelectorAll('[data-profile-mode-row]')];
800
- const statusEl = drawer.querySelector('[data-save-profile-status]');
801
- const submitBtn = formEl.querySelector('button[type=submit]');
802
- const modeButtons = [...formEl.querySelectorAll('[data-save-profile-mode]')];
803
- const profilePickButtons = [...formEl.querySelectorAll('[data-profile-id]')];
804
- const profileIdInput = formEl.querySelector('input[name=profileId]');
805
- const targetEl = formEl.querySelector('[data-save-profile-target]');
806
- const targetModeEl = formEl.querySelector('[data-save-profile-target-mode]');
807
- let selectedMode = 'new';
808
- let selectedExistingProfileId = sortedProfiles[0]?.profileId ?? '';
809
- function currentProfileId() {
810
- return selectedMode === 'overwrite'
811
- ? selectedExistingProfileId
812
- : profileIdInput.value.trim();
813
- }
814
- function updateMode() {
815
- for (const row of modeRows) {
816
- row.hidden = row.dataset.profileModeRow !== selectedMode;
817
- }
818
- for (const button of modeButtons) {
819
- const isActive = button.dataset.saveProfileMode === selectedMode;
820
- button.classList.toggle('active', isActive);
821
- button.setAttribute('aria-pressed', String(isActive));
822
- }
823
- for (const button of profilePickButtons) {
824
- const isSelected = button.dataset.profileId === selectedExistingProfileId;
825
- button.classList.toggle('selected', isSelected);
826
- button.setAttribute('aria-pressed', String(isSelected));
827
- }
828
- submitBtn.textContent = selectedMode === 'overwrite'
829
- ? t('groups.saveProfileOverwriteSubmit')
830
- : t('groups.saveProfileSubmit');
831
- submitBtn.disabled = !canSubmitSnapshot || (selectedMode === 'overwrite' && !selectedExistingProfileId);
832
- targetEl.textContent = currentProfileId() || '-';
833
- targetModeEl.textContent = selectedMode === 'overwrite'
834
- ? t('groups.saveProfileTargetOverwrite')
835
- : t('groups.saveProfileTargetNew');
836
- statusEl.textContent = '';
837
- statusEl.className = 'g-save-profile-status';
838
- }
839
- modeButtons.forEach(button => {
840
- button.addEventListener('click', () => {
841
- const mode = button.dataset.saveProfileMode === 'overwrite' ? 'overwrite' : 'new';
842
- if (mode === 'overwrite' && !hasExistingProfiles)
843
- return;
844
- selectedMode = mode;
845
- updateMode();
846
- });
847
- });
848
- profilePickButtons.forEach(button => {
849
- button.addEventListener('click', () => {
850
- selectedExistingProfileId = button.dataset.profileId ?? '';
851
- selectedMode = 'overwrite';
852
- updateMode();
853
- });
854
- });
855
- profileIdInput.addEventListener('input', updateMode);
856
- updateMode();
857
- drawer.querySelector('#g-save-profile-cancel').onclick = () => drawer.close();
858
- formEl.onsubmit = async (ev) => {
859
- ev.preventDefault();
860
- if (!canSubmitSnapshot)
861
- return;
862
- const profileId = currentProfileId();
863
- if (!isValidProfileId(profileId)) {
864
- statusEl.textContent = t('groups.saveProfileInvalid');
865
- statusEl.className = 'g-save-profile-status error';
866
- return;
867
- }
868
- submitBtn.disabled = true;
869
- submitBtn.textContent = t('groups.saveProfileSaving');
870
- statusEl.textContent = t('groups.saveProfileSaving');
871
- statusEl.className = 'g-save-profile-status';
872
- try {
873
- const results = await Promise.all(entries.map(async (entry) => {
874
- const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(entry.larkAppId)}`, {
875
- method: 'PUT',
876
- headers: { 'content-type': 'application/json' },
877
- body: JSON.stringify({ content: entry.content, allowEmpty: true }),
878
- });
879
- return r.ok;
880
- }));
881
- if (disposed)
882
- return;
883
- const saved = results.filter(Boolean).length;
884
- if (saved !== entries.length) {
885
- statusEl.textContent = t('groups.saveProfileFailed', { saved, total: entries.length });
886
- statusEl.className = 'g-save-profile-status error';
887
- submitBtn.disabled = false;
888
- submitBtn.textContent = selectedMode === 'overwrite'
889
- ? t('groups.saveProfileOverwriteSubmit')
890
- : t('groups.saveProfileSubmit');
891
- return;
892
- }
893
- statusEl.textContent = t('groups.saveProfileDone', { name: profileId, count: saved });
894
- statusEl.className = 'g-save-profile-status ok';
895
- await refreshRoleProfileContext();
896
- if (disposed)
897
- return;
898
- setTimer(() => drawer.close(), 700);
899
- }
900
- catch (err) {
901
- statusEl.textContent = String(err);
902
- statusEl.className = 'g-save-profile-status error';
903
- submitBtn.disabled = false;
904
- submitBtn.textContent = selectedMode === 'overwrite'
905
- ? t('groups.saveProfileOverwriteSubmit')
906
- : t('groups.saveProfileSubmit');
907
- }
908
- };
909
- }
910
- const onManageChatClick = async (e) => {
911
- const btn = e.target.closest('button.manage-chat');
912
- if (!btn)
913
- return;
914
- const tr = btn.closest('tr[data-chat]');
915
- const chatId = tr.dataset.chat;
916
- const chat = cache.chats.find(c => c.chatId === chatId);
917
- if (!chat)
918
- return;
919
- openManageDrawer(chat);
182
+ }
183
+ export function injectOptimisticChat(snapshot, chatId, displayName, memberIds, creator) {
184
+ const inChatSet = new Set(memberIds);
185
+ if (creator)
186
+ inChatSet.add(creator);
187
+ const optimistic = {
188
+ chatId,
189
+ name: displayName,
190
+ ownerId: creator ?? null,
191
+ memberBots: snapshot.bots.map(bot => ({
192
+ larkAppId: bot.larkAppId,
193
+ botName: bot.botName,
194
+ inChat: inChatSet.has(bot.larkAppId),
195
+ oncallChat: null,
196
+ })),
920
197
  };
921
- body.addEventListener('click', onManageChatClick);
922
- function openManageDrawer(chat) {
923
- const inChat = chat.memberBots.filter((m) => m.inChat);
924
- // Lark `owner_id` returns app_id format for bot owners (string match works).
925
- // For user owners it'll be an open_id which won't match any larkAppId.
926
- const ownerAppId = typeof chat.ownerId === 'string' ? chat.ownerId : '';
927
- drawer.innerHTML = `
928
- <article>
929
- <header><h3>${t('groups.manageTitle', { name: chat.name ?? chat.chatId })}</h3></header>
930
- <p><b>chatId:</b> <code>${escapeHtml(chat.chatId)}</code></p>
931
- <p><b>${t('groups.owner')}:</b> <code>${escapeHtml(chat.ownerId ?? t('common.unknown'))}</code></p>
932
-
933
- <fieldset>
934
- <legend>${t('groups.oncall')}</legend>
935
- <p><small>${t('groups.oncallHelp')}</small></p>
936
- ${inChat.length === 0
937
- ? `<p class="empty">没有机器人在群里</p>`
938
- : inChat.map((m) => {
939
- const enabled = !!m.oncallChat;
940
- const wd = m.oncallChat?.workingDir ?? '';
941
- return `
942
- <div class="oncall-row" data-bot="${escapeHtml(m.larkAppId)}">
943
- <label class="checkbox-row">
944
- <input type="checkbox" data-action="toggle" ${enabled ? 'checked' : ''}>
945
- <strong>${escapeHtml(m.botName ?? m.larkAppId)}</strong>
946
- <small>(${escapeHtml(m.larkAppId)})</small>
947
- </label>
948
- <div class="oncall-row-body">
949
- <input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
950
- value="${escapeHtml(wd)}" ${enabled ? '' : 'disabled'}>
951
- <button type="button" data-action="save">${t('groups.save')}</button>
952
- <span class="oncall-status" data-status></span>
953
- </div>
954
- </div>
955
- `;
956
- }).join('')}
957
- </fieldset>
958
-
959
- <fieldset>
960
- <legend>${t('groups.leaveTitle')}</legend>
961
- ${inChat.length === 0
962
- ? `<p class="empty">没有机器人在群里</p>`
963
- : inChat.map((m) => `
964
- <label class="checkbox-row">
965
- <input type="checkbox" name="leave-bot" value="${escapeHtml(m.larkAppId)}">
966
- ${escapeHtml(m.botName ?? m.larkAppId)}
967
- <small>${m.larkAppId === ownerAppId ? '· 群主' : ''}</small>
968
- </label>
969
- `).join('')}
970
- </fieldset>
971
-
972
- <div class="actions">
973
- <button id="g-leave-btn" type="button" ${inChat.length === 0 ? 'disabled' : ''}>${t('groups.leaveSelected')}</button>
974
- <button id="g-disband-btn" type="button" class="contrast" ${inChat.length === 0 ? 'disabled' : ''}>${t('groups.disband')}</button>
975
- </div>
976
- <p class="hint-warn"><small>${t('groups.dangerHint')}</small></p>
977
- <form method="dialog"><button>${t('sessions.dismiss')}</button></form>
978
- </article>`;
979
- drawer.showModal();
980
- // Oncall row interactions: toggle enables/disables the input; Save commits.
981
- drawer.querySelectorAll('.oncall-row').forEach(row => {
982
- const appId = row.dataset.bot;
983
- const cb = row.querySelector('input[data-action=toggle]');
984
- const input = row.querySelector('input[data-input=workingDir]');
985
- const saveBtn = row.querySelector('button[data-action=save]');
986
- const statusEl = row.querySelector('[data-status]');
987
- cb.addEventListener('change', () => {
988
- input.disabled = !cb.checked;
989
- if (cb.checked)
990
- input.focus();
991
- });
992
- saveBtn.addEventListener('click', async () => {
993
- statusEl.textContent = '';
994
- statusEl.className = 'oncall-status';
995
- const want = cb.checked;
996
- const wd = input.value.trim();
997
- if (want && !wd) {
998
- statusEl.textContent = t('groups.needWorkingDir');
999
- statusEl.classList.add('hint-warn-inline');
1000
- return;
1001
- }
1002
- saveBtn.disabled = true;
1003
- try {
1004
- const url = `/api/groups/${encodeURIComponent(chat.chatId)}/oncall/${encodeURIComponent(appId)}`;
1005
- const r = want
1006
- ? await fetch(url, {
1007
- method: 'PUT',
1008
- headers: { 'content-type': 'application/json' },
1009
- body: JSON.stringify({ workingDir: wd }),
1010
- })
1011
- : await fetch(url, { method: 'DELETE' });
1012
- const body = await r.json().catch(() => ({}));
1013
- if (disposed)
1014
- return;
1015
- if (r.ok && body.ok) {
1016
- statusEl.textContent = want
1017
- ? `✓ 已绑定 → ${body.resolvedPath ?? wd}`
1018
- : '✓ 已解绑';
1019
- statusEl.classList.add('hint-ok');
1020
- // Refresh aggregator cache + matrix; drawer state stays as-is
1021
- // (current row reflects the just-saved values).
1022
- try {
1023
- await loadGroups();
1024
- if (disposed)
1025
- return;
1026
- rerender();
1027
- void refreshRoleProfileContext();
1028
- }
1029
- catch { /* tolerate */ }
1030
- }
1031
- else {
1032
- statusEl.textContent = `✗ ${body.error ?? r.status}`;
1033
- statusEl.classList.add('hint-warn-inline');
1034
- }
1035
- }
1036
- catch (e) {
1037
- statusEl.textContent = `✗ ${e?.message ?? e}`;
1038
- statusEl.classList.add('hint-warn-inline');
1039
- }
1040
- finally {
1041
- saveBtn.disabled = false;
1042
- }
1043
- });
1044
- });
1045
- drawer.querySelector('#g-leave-btn').onclick = async () => {
1046
- const checked = [...drawer.querySelectorAll('input[name=leave-bot]:checked')]
1047
- .map(i => i.value);
1048
- if (checked.length === 0) {
1049
- alert('至少选一个机器人');
1050
- return;
1051
- }
1052
- if (!confirm(`确定让 ${checked.length} 个机器人退出群聊?该 bot 在此群的会话会一并关闭。`))
1053
- return;
1054
- try {
1055
- const r = await fetch(`/api/groups/${encodeURIComponent(chat.chatId)}/leave`, {
1056
- method: 'POST',
1057
- headers: { 'content-type': 'application/json' },
1058
- body: JSON.stringify({ larkAppIds: checked }),
1059
- });
1060
- const respBody = await r.json();
1061
- if (disposed)
1062
- return;
1063
- const lines = (respBody.result ?? []).map((x) => {
1064
- if (!x.ok)
1065
- return `${x.larkAppId}: 失败 (${x.error ?? 'unknown'})`;
1066
- const closed = (x.closedSessions ?? []);
1067
- const failed = closed.filter(c => !c.ok).length;
1068
- const ok = closed.length - failed;
1069
- const note = closed.length === 0
1070
- ? ''
1071
- : failed === 0 ? `(关闭 ${ok} 个会话)` : `(关闭 ${ok} 个,${failed} 个失败)`;
1072
- return `${x.larkAppId}: OK${note}`;
1073
- }).join('\n');
1074
- alert(lines || `Unexpected: ${JSON.stringify(respBody)}`);
1075
- await loadGroups();
1076
- if (disposed)
1077
- return;
1078
- rerender();
1079
- void refreshRoleProfileContext();
1080
- }
1081
- catch (e) {
1082
- alert('Network error: ' + e);
1083
- }
1084
- finally {
1085
- if (!disposed)
1086
- drawer.close();
1087
- }
1088
- };
1089
- drawer.querySelector('#g-disband-btn').onclick = async () => {
1090
- if (inChat.length === 0)
1091
- return;
1092
- if (!confirm(`确定解散群聊「${chat.name ?? chat.chatId}」?此操作不可恢复,本群所有机器人会话也会一并关闭。`))
1093
- return;
1094
- // Try the owner bot first (highest success probability), then fall back
1095
- // to other in-chat bots in case our ownerId match was wrong (e.g. owner
1096
- // is a user, but a creator-bot with operate_as_owner scope is in chat).
1097
- const ordered = [...inChat].sort((a, b) => (b.larkAppId === ownerAppId ? 1 : 0) - (a.larkAppId === ownerAppId ? 1 : 0));
1098
- const errs = [];
1099
- for (const m of ordered) {
1100
- try {
1101
- const r = await fetch(`/api/groups/${encodeURIComponent(chat.chatId)}/disband`, {
1102
- method: 'POST',
1103
- headers: { 'content-type': 'application/json' },
1104
- body: JSON.stringify({ larkAppId: m.larkAppId }),
1105
- });
1106
- const respBody = await r.json();
1107
- if (disposed)
1108
- return;
1109
- if (respBody.ok) {
1110
- const closed = (respBody.closedSessions ?? []);
1111
- const failed = closed.filter(c => !c.ok).length;
1112
- const ok = closed.length - failed;
1113
- const closedNote = closed.length === 0
1114
- ? ''
1115
- : failed === 0 ? `\n关闭了 ${ok} 个会话。` : `\n关闭了 ${ok} 个会话,${failed} 个会话关闭失败。`;
1116
- alert(`已解散(由 ${m.botName ?? m.larkAppId} 执行)${closedNote}`);
1117
- await loadGroups();
1118
- if (disposed)
1119
- return;
1120
- rerender();
1121
- void refreshRoleProfileContext();
1122
- drawer.close();
1123
- return;
1124
- }
1125
- errs.push(`${m.botName ?? m.larkAppId}: ${respBody.error ?? r.status}`);
1126
- }
1127
- catch (e) {
1128
- errs.push(`${m.botName ?? m.larkAppId}: ${e}`);
1129
- }
1130
- }
1131
- alert(`所有在群机器人均无法解散:\n${errs.join('\n')}\n\n建议改用「退出群聊」。`);
1132
- };
1133
- }
1134
- form.addEventListener('input', rerender);
1135
- return () => {
1136
- disposed = true;
1137
- for (const id of timers)
1138
- window.clearTimeout(id);
1139
- timers.clear();
1140
- for (const resolve of delayResolvers.values())
1141
- resolve();
1142
- delayResolvers.clear();
1143
- refreshBtn.onclick = null;
1144
- createBtn.onclick = null;
1145
- form.removeEventListener('input', rerender);
1146
- body.removeEventListener('click', onAddBotsClick);
1147
- body.removeEventListener('click', onSaveProfileClick);
1148
- body.removeEventListener('click', onManageChatClick);
1149
- if (drawer.open)
1150
- drawer.close();
1151
- drawer.replaceChildren();
198
+ return {
199
+ bots: snapshot.bots,
200
+ chats: [optimistic, ...snapshot.chats.filter(chat => chat.chatId !== chatId)],
1152
201
  };
1153
202
  }
1154
203
  //# sourceMappingURL=groups.js.map