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,118 +1,19 @@
1
- // Bot Defaults page: per-bot configuration for "default oncall mode on new
2
- // chats". Strictly per-bot (no chat × bot matrix here — that lives in the
3
- // Groups & Bots tab). Saving here only affects NEW group chats first observed
4
- // after the save; existing chats are left alone, and chats already auto-bound
5
- // once stay user-controlled.
6
1
  import { store } from './store.js';
7
- import { botAvatarHtml, escapeHtml, loadNameMaps, loadingHtml, t } from './ui.js';
8
- import { parseSubstituteTargets, formatSubstituteTargets } from './substitute-targets.js';
9
- let cache = { bots: [] };
10
- let loadError = null;
11
- let cliOptions = [
2
+ export const fallbackCliOptions = [
12
3
  { id: 'claude-code', label: 'Claude' },
13
4
  { id: 'codex', label: 'Codex' },
14
5
  { id: 'traex', label: 'traex' },
15
6
  ];
16
- let cliOptionsLoaded = false;
17
- let ttadkModelDefault = 'glm-5.1';
18
- let ttadkModelSuggestions = [];
19
- // master-detail:左侧员工名册选中谁,右侧就渲染谁的档案
20
- let selectedAppId = null;
7
+ export const fallbackCliOptionsState = {
8
+ options: fallbackCliOptions,
9
+ ttadkModelDefault: 'glm-5.1',
10
+ ttadkModelSuggestions: [],
11
+ };
21
12
  export function displayCliId(bot, sessionFallback) {
22
13
  return typeof bot?.cliId === 'string' && bot.cliId ? bot.cliId : sessionFallback;
23
14
  }
24
- // 飞书 Web 登录态刷新 modal:POST /api/feishu-login/start 起扫码流程,轮询
25
- // /api/feishu-login/status 展示二维码/进度,扫码成功后回调 onSuccess(重试改名)。
26
- // 机器级单例流程,同一时刻只需一个 overlay。
27
- function openFeishuLoginModal(onSuccess) {
28
- document.querySelector('.feishu-login-overlay')?.remove();
29
- const overlay = document.createElement('div');
30
- overlay.className = 'feishu-login-overlay';
31
- overlay.innerHTML = `
32
- <div class="feishu-login-modal" role="dialog" aria-modal="true">
33
- <button type="button" class="feishu-login-close" data-close aria-label="${escapeHtml(t('feishuLogin.close'))}">✕</button>
34
- <h3 class="feishu-login-title">${escapeHtml(t('feishuLogin.title'))}</h3>
35
- <p class="feishu-login-hint" data-hint>${escapeHtml(t('feishuLogin.starting'))}</p>
36
- <div class="feishu-login-qr" data-qr></div>
37
- <div class="feishu-login-actions">
38
- <button type="button" class="primary" data-retry hidden>${escapeHtml(t('feishuLogin.retry'))}</button>
39
- </div>
40
- </div>`;
41
- document.body.appendChild(overlay);
42
- const hintEl = overlay.querySelector('[data-hint]');
43
- const qrEl = overlay.querySelector('[data-qr]');
44
- const retryBtn = overlay.querySelector('[data-retry]');
45
- let timer = null;
46
- const stopTimer = () => { if (timer !== null) {
47
- window.clearInterval(timer);
48
- timer = null;
49
- } };
50
- const cleanup = () => { stopTimer(); overlay.remove(); };
51
- overlay.addEventListener('click', (e) => {
52
- const el = e.target;
53
- if (el === overlay || el.closest('[data-close]'))
54
- cleanup();
55
- });
56
- function render(login) {
57
- if (!login)
58
- return 'active';
59
- if (login.status === 'awaiting_scan' && login.qrDataUrl) {
60
- qrEl.innerHTML = `<img class="qr-image" src="${login.qrDataUrl}" alt="${escapeHtml(t('feishuLogin.qrAlt'))}">`;
61
- hintEl.textContent = login.message || t('feishuLogin.scanHint');
62
- retryBtn.hidden = true;
63
- return 'active';
64
- }
65
- if (login.status === 'starting') {
66
- hintEl.textContent = login.message || t('feishuLogin.starting');
67
- return 'active';
68
- }
69
- if (login.status === 'success') {
70
- stopTimer();
71
- qrEl.innerHTML = '';
72
- hintEl.textContent = t('feishuLogin.success');
73
- window.setTimeout(() => { cleanup(); onSuccess(); }, 900);
74
- return 'done';
75
- }
76
- // failed
77
- stopTimer();
78
- qrEl.innerHTML = '';
79
- hintEl.textContent = t('feishuLogin.failed', { reason: login.message || login.reason || '' });
80
- retryBtn.hidden = false;
81
- return 'done';
82
- }
83
- async function poll() {
84
- try {
85
- const r = await fetch('/api/feishu-login/status');
86
- const body = await r.json().catch(() => ({}));
87
- render(body.login);
88
- }
89
- catch { /* transient — keep polling */ }
90
- }
91
- async function begin() {
92
- stopTimer();
93
- hintEl.textContent = t('feishuLogin.starting');
94
- qrEl.innerHTML = '';
95
- retryBtn.hidden = true;
96
- let phase = 'active';
97
- try {
98
- const r = await fetch('/api/feishu-login/start', { method: 'POST' });
99
- const body = await r.json().catch(() => ({}));
100
- phase = render(body.login);
101
- }
102
- catch (e) {
103
- hintEl.textContent = t('feishuLogin.failed', { reason: e?.message ?? String(e) });
104
- retryBtn.hidden = false;
105
- return;
106
- }
107
- if (phase === 'active' && timer === null)
108
- timer = window.setInterval(() => void poll(), 1500);
109
- }
110
- retryBtn.addEventListener('click', () => void begin());
111
- void begin();
112
- }
113
- const botProfileRoleCache = new Map();
114
15
  /** Fallback for old /api/bots payloads: infer from the bot's recent sessions. */
115
- function cliIdOf(appId) {
16
+ export function cliIdOf(appId) {
116
17
  let best = null;
117
18
  for (const s of store.sessions.values()) {
118
19
  if (s.larkAppId !== appId || !s.cliId)
@@ -122,189 +23,64 @@ function cliIdOf(appId) {
122
23
  }
123
24
  return best?.cliId ?? '';
124
25
  }
125
- async function loadCliOptions() {
126
- if (cliOptionsLoaded)
127
- return;
128
- cliOptionsLoaded = true;
129
- try {
130
- const r = await fetch('/api/cli-options');
131
- const body = await r.json().catch(() => ({}));
132
- if (r.ok && Array.isArray(body?.options)) {
133
- cliOptions = body.options.filter((o) => o && typeof o.id === 'string' && typeof o.label === 'string');
134
- if (typeof body.ttadkModelDefault === 'string' && body.ttadkModelDefault.trim()) {
135
- ttadkModelDefault = body.ttadkModelDefault.trim();
136
- }
137
- if (Array.isArray(body.ttadkModelSuggestions)) {
138
- ttadkModelSuggestions = body.ttadkModelSuggestions.filter((s) => typeof s === 'string');
139
- }
140
- }
141
- }
142
- catch {
143
- // Keep the static fallback; saving still works for plain claude-code.
144
- }
145
- }
146
- function agentSelectionKey(bot, sessionFallback) {
147
- const explicit = typeof bot?.agentSelectionKey === 'string' && bot.agentSelectionKey ? bot.agentSelectionKey : '';
26
+ export function agentSelectionKey(bot, sessionFallback) {
27
+ const explicit = typeof bot.agentSelectionKey === 'string' && bot.agentSelectionKey ? bot.agentSelectionKey : '';
148
28
  if (explicit)
149
29
  return explicit;
150
30
  const cli = displayCliId(bot, sessionFallback);
151
31
  return cli || 'claude-code';
152
32
  }
153
- function selectedCliOption(key) {
154
- return cliOptions.find(o => o.id === key);
155
- }
156
- // A single resolved-target chip: ✓ green when resolved to a person, ✗ red when
157
- // the email / id could not be resolved (out of 通讯录可见范围). Shared by the
158
- // initial render and the post-save refresh.
159
- function substituteChipHtml(c) {
160
- const label = (c.name && c.name.trim()) || c.input || '';
161
- const color = c.ok ? '#16a34a' : '#dc2626';
162
- const suffix = c.ok ? '' : ` · ${t('botDefaults.substituteUnresolved')}`;
163
- return `<span style="display:inline-flex;align-items:center;gap:4px;padding:2px 9px;margin:0 6px 6px 0;`
164
- + `border-radius:12px;font-size:12px;line-height:1.6;border:1px solid ${color}40;color:${color};background:${color}14">`
165
- + `<span>${c.ok ? '✓' : '✗'}</span><span>${escapeHtml(label)}${escapeHtml(suffix)}</span></span>`;
33
+ export function selectedCliOption(options, key) {
34
+ return options.find(o => o.id === key);
166
35
  }
167
- // Chips rendered on first paint from the already-resolved stored targets.
168
- function substituteChipsHtml(mode) {
169
- const targets = Array.isArray(mode?.targets) ? mode.targets : [];
170
- return targets
171
- .map((tg) => substituteChipHtml({
172
- ok: true,
173
- name: (typeof tg?.name === 'string' && tg.name) || tg?.email || tg?.openId || tg?.unionId || tg?.userId,
174
- input: tg?.email || tg?.openId,
175
- }))
176
- .join('');
177
- }
178
- // Chips rendered after a save, from the server's per-entry resolution report.
179
- function substituteChipsFromResolution(resolution) {
180
- if (!Array.isArray(resolution) || resolution.length === 0)
181
- return '';
182
- return resolution
183
- .map((r) => substituteChipHtml({ ok: r?.ok === true, name: r?.name, input: r?.input }))
184
- .join('');
185
- }
186
- // Re-derive the editor text from the resolution report so a save keeps every
187
- // line the user typed — resolved ones gain a `# name` annotation, failed ones
188
- // stay verbatim to be fixed. Empty report (e.g. the "off" button) clears it.
189
- function editorTextFromResolution(resolution) {
190
- if (!Array.isArray(resolution))
191
- return '';
192
- return resolution
193
- .map((r) => {
194
- const input = String(r?.input ?? '').trim();
195
- if (!input)
196
- return '';
197
- return r?.ok && typeof r?.name === 'string' && r.name.trim() ? `${input} # ${r.name.trim()}` : input;
198
- })
199
- .filter(Boolean)
200
- .join('\n');
201
- }
202
- function modelSuggestionsForOption(opt) {
36
+ export function modelSuggestionsForOption(opt, cliState) {
203
37
  if (opt?.gateway === 'ttadk' && opt.acceptsModel !== false)
204
- return ttadkModelSuggestions;
38
+ return cliState.ttadkModelSuggestions;
205
39
  return [];
206
40
  }
207
- export function renderBotAgentSection(b, sessionFallback) {
208
- const key = agentSelectionKey(b, sessionFallback);
209
- const optHtml = cliOptions
210
- .map(o => `<option value="${escapeHtml(o.id)}" ${o.id === key ? 'selected' : ''}>${escapeHtml(o.label)}(${escapeHtml(o.id)})</option>`)
211
- .join('');
212
- const model = typeof b?.model === 'string' ? b.model : '';
213
- const suggestions = modelSuggestionsForOption(selectedCliOption(key));
214
- const disabled = selectedCliOption(key)?.gateway === 'ttadk' && selectedCliOption(key)?.acceptsModel === false;
215
- // botmux skills 注入方式. `support` decides how the control renders:
216
- // - 'dynamic' (claude-family, --plugin-dir): disabled, shows 动态注入 as the
217
- // fixed mode — not configurable.
218
- // - 'global' (codex-family, global skills dir): prompt/global/off selectable;
219
- // 动态注入 shown but disabled (hint: this CLI can't do dynamic injection).
220
- // - 'none' (no skill dir): the whole row is omitted.
221
- // The selected value is the RESOLVED mode (per-bot override → machine default),
222
- // which is `prompt` out of the box — so there is no separate "follow" option.
223
- const siSupport = b?.skillInjectionSupport === 'dynamic' ? 'dynamic' : b?.skillInjectionSupport === 'global' ? 'global' : 'none';
224
- const siOverride = (b?.skillInjection === 'global' || b?.skillInjection === 'prompt' || b?.skillInjection === 'off') ? b.skillInjection : '';
225
- const siDefault = (b?.skillInjectionDefault === 'global' || b?.skillInjectionDefault === 'off') ? b.skillInjectionDefault : 'prompt';
226
- const siResolved = siOverride || siDefault; // 'prompt' | 'global' | 'off'
227
- const skillRow = siSupport === 'none' ? '' : siSupport === 'dynamic'
228
- ? `<div class="bd-row">
229
- <label>
230
- <span>${t('botDefaults.skillInjection')}</span>
231
- <select data-input="skillInjection" disabled>
232
- <option value="dynamic" selected>${escapeHtml(t('botDefaults.skillInjectionDynamic'))}</option>
233
- </select>
234
- </label>
235
- <small class="bd-help">${t('botDefaults.skillInjectionHelpDynamic')}</small>
236
- </div>`
237
- : `<div class="bd-row">
238
- <label>
239
- <span>${t('botDefaults.skillInjection')}</span>
240
- <select data-input="skillInjection">
241
- <option value="dynamic" disabled>${escapeHtml(t('botDefaults.skillInjectionDynamicUnsupported'))}</option>
242
- <option value="prompt" ${siResolved === 'prompt' ? 'selected' : ''}>${escapeHtml(t('botDefaults.skillInjectionPrompt'))}</option>
243
- <option value="global" ${siResolved === 'global' ? 'selected' : ''}>${escapeHtml(t('botDefaults.skillInjectionGlobal'))}</option>
244
- <option value="off" ${siResolved === 'off' ? 'selected' : ''}>${escapeHtml(t('botDefaults.skillInjectionOff'))}</option>
245
- </select>
246
- </label>
247
- <small class="bd-help">${t('botDefaults.skillInjectionHelp')}</small>
248
- <div class="actions">
249
- <span class="oncall-status" data-skill-injection-status></span>
250
- </div>
251
- </div>`;
252
- return `<section class="bd-section">
253
- <h3 class="bd-section-title">${t('botDefaults.sectionAgent')}</h3>
254
- <div class="bd-row">
255
- <label>
256
- <span>${t('botDefaults.agentCli')}</span>
257
- <select data-input="agentCliId">${optHtml}</select>
258
- </label>
259
- </div>
260
- <div class="bd-row">
261
- <label>
262
- <span>${t('botDefaults.agentModel')}</span>
263
- <input type="text" data-input="agentModel" list="agent-model-suggestions-${escapeHtml(b.larkAppId)}"
264
- placeholder="${escapeHtml(t('botDefaults.agentModelPlaceholder'))}"
265
- value="${escapeHtml(model)}" ${disabled ? 'disabled' : ''}>
266
- <datalist id="agent-model-suggestions-${escapeHtml(b.larkAppId)}">
267
- ${suggestions.map(m => `<option value="${escapeHtml(m)}"></option>`).join('')}
268
- </datalist>
269
- </label>
270
- <small class="bd-help">${t('botDefaults.agentHelp')}</small>
271
- <div class="actions">
272
- <button type="button" class="primary" data-action="save-agent">${t('botDefaults.agentSave')}</button>
273
- <span class="oncall-status" data-agent-status></span>
274
- </div>
275
- </div>
276
- ${skillRow}
277
- </section>`;
278
- }
279
- async function loadBots() {
41
+ export async function fetchBotDefaults() {
280
42
  try {
281
43
  const r = await fetch('/api/bots');
282
44
  const body = await r.json().catch(() => ({}));
283
45
  if (!r.ok) {
284
- // Common case: backend was upgraded on disk but the dashboard process
285
- // hasn't been restarted, so /api/bots isn't registered yet. Surface
286
- // that instead of throwing — the empty list area is what the user
287
- // sees as "blank page".
288
- loadError = body?.error
46
+ const error = body?.error
289
47
  ? `HTTP ${r.status}: ${body.error}${body.path ? ` (${body.path})` : ''}`
290
48
  : `HTTP ${r.status}`;
291
- cache = { bots: [] };
292
- return;
49
+ return { bots: [], error };
293
50
  }
294
51
  if (!body || !Array.isArray(body.bots)) {
295
- loadError = 'unexpected response shape (no `bots` array)';
296
- cache = { bots: [] };
297
- return;
52
+ return { bots: [], error: 'unexpected response shape (no `bots` array)' };
298
53
  }
299
- loadError = null;
300
- cache = body;
54
+ return { bots: body.bots, error: null };
301
55
  }
302
56
  catch (e) {
303
- loadError = e?.message ?? String(e);
304
- cache = { bots: [] };
57
+ return { bots: [], error: e?.message ?? String(e) };
305
58
  }
306
59
  }
307
- function fmtSince(since) {
60
+ export async function fetchCliOptions() {
61
+ try {
62
+ const r = await fetch('/api/cli-options');
63
+ const body = await r.json().catch(() => ({}));
64
+ if (!r.ok || !Array.isArray(body?.options))
65
+ return fallbackCliOptionsState;
66
+ const options = body.options.filter((o) => o && typeof o.id === 'string' && typeof o.label === 'string');
67
+ const ttadkModelDefault = typeof body.ttadkModelDefault === 'string' && body.ttadkModelDefault.trim()
68
+ ? body.ttadkModelDefault.trim()
69
+ : fallbackCliOptionsState.ttadkModelDefault;
70
+ const ttadkModelSuggestions = Array.isArray(body.ttadkModelSuggestions)
71
+ ? body.ttadkModelSuggestions.filter((s) => typeof s === 'string')
72
+ : [];
73
+ return {
74
+ options: options.length ? options : fallbackCliOptions,
75
+ ttadkModelDefault,
76
+ ttadkModelSuggestions,
77
+ };
78
+ }
79
+ catch {
80
+ return fallbackCliOptionsState;
81
+ }
82
+ }
83
+ export function fmtSince(since) {
308
84
  if (!since)
309
85
  return '—';
310
86
  const d = new Date(since);
@@ -312,1878 +88,4 @@ function fmtSince(since) {
312
88
  return '—';
313
89
  return d.toLocaleString();
314
90
  }
315
- export function wireBotDefaultsPage(root) {
316
- const listEl = root.querySelector('#bd-list');
317
- const rosterEl = root.querySelector('#bd-roster');
318
- const form = root.querySelector('#bd-filters');
319
- const refreshBtn = root.querySelector('#bd-refresh');
320
- let disposed = false;
321
- let readyToRender = false;
322
- refreshBtn.onclick = async () => {
323
- refreshBtn.disabled = true;
324
- try {
325
- botProfileRoleCache.clear();
326
- await Promise.all([loadBots(), loadCliOptions()]);
327
- safeRerender();
328
- }
329
- finally {
330
- if (!disposed)
331
- refreshBtn.disabled = false;
332
- }
333
- };
334
- // 帮助文字默认折叠成一行;点说明文字本身展开/收起(preventDefault 拦掉
335
- // label 默认行为,避免一点说明就把开关也切了)。只绑一次,委托不随 rerender 重建。
336
- const listClickHandler = (e) => {
337
- const sm = e.target.closest('.toggle-tx small, small.bd-help');
338
- if (sm) {
339
- e.preventDefault();
340
- sm.classList.toggle('open');
341
- }
342
- };
343
- listEl.addEventListener('click', listClickHandler);
344
- // /api/bots 要逐 daemon 探活,慢——先亮 loading 占住右侧详情区。
345
- listEl.innerHTML = loadingHtml();
346
- void (async () => {
347
- await Promise.all([loadBots(), loadCliOptions()]);
348
- readyToRender = true;
349
- safeRerender();
350
- await loadNameMaps(); // 头像表就绪后重绘,让 /api/bots 这边也出真实头像
351
- safeRerender();
352
- })();
353
- function rerender() {
354
- const f = new FormData(form);
355
- const q = (f.get('q') ?? '').toLowerCase();
356
- const filtered = cache.bots.filter((b) => !q ||
357
- (b.botName ?? '').toLowerCase().includes(q) ||
358
- (b.larkAppId ?? '').toLowerCase().includes(q));
359
- if (loadError) {
360
- rosterEl.innerHTML = '';
361
- listEl.innerHTML = `<p class="hint-warn">无法加载 bot 列表:${escapeHtml(loadError)}<br>` +
362
- `常见原因:dashboard / daemon 进程还在跑旧代码,执行 <code>botmux restart</code> 后刷新。</p>`;
363
- return;
364
- }
365
- if (filtered.length === 0) {
366
- rosterEl.innerHTML = '';
367
- listEl.innerHTML = `<p class="empty">${t('botDefaults.empty')}</p>`;
368
- return;
369
- }
370
- if (!selectedAppId || !filtered.some((b) => b.larkAppId === selectedAppId)) {
371
- selectedAppId = filtered[0].larkAppId;
372
- }
373
- rosterEl.innerHTML = filtered.map(renderRosterItem).join('');
374
- rosterEl.querySelectorAll('.bd-roster-item').forEach(el => {
375
- el.onclick = () => {
376
- selectedAppId = el.dataset.appid;
377
- safeRerender();
378
- };
379
- });
380
- const sel = filtered.find((b) => b.larkAppId === selectedAppId);
381
- listEl.innerHTML = renderBotCard(sel);
382
- wireCardHandlers();
383
- }
384
- function renderRosterItem(b) {
385
- const name = b.botName ?? b.larkAppId;
386
- const cli = displayCliId(b, cliIdOf(b.larkAppId));
387
- const flag = b.defaultOncall?.enabled
388
- ? `<span class="bd-roster-flag">oncall</span>`
389
- : '';
390
- return `<div class="bd-roster-item${b.larkAppId === selectedAppId ? ' on' : ''}" data-appid="${escapeHtml(b.larkAppId)}" role="button" tabindex="0">
391
- ${botAvatarHtml({ name, larkAppId: b.larkAppId, size: 'sm' })}
392
- <div class="bd-roster-tx">
393
- <b>${escapeHtml(name)}</b>
394
- <span>${escapeHtml(cli || b.larkAppId.slice(0, 14))}</span>
395
- </div>
396
- ${flag}
397
- </div>`;
398
- }
399
- function renderBotCard(b) {
400
- if (b.error) {
401
- return `<article class="bd-card bd-profile" data-appid="${escapeHtml(b.larkAppId)}">
402
- <header class="bd-profile-head">
403
- ${botAvatarHtml({ name: b.botName ?? b.larkAppId, larkAppId: b.larkAppId })}
404
- <div class="bd-profile-id"><strong>${escapeHtml(b.botName ?? b.larkAppId)}</strong>
405
- <code>${escapeHtml(b.larkAppId)}</code></div>
406
- </header>
407
- <p class="hint-warn-inline">查询失败:${escapeHtml(b.error)}</p>
408
- </article>`;
409
- }
410
- const def = b.defaultOncall ?? { enabled: false, workingDir: '', since: 0 };
411
- // 默认工作目录模式(三选一,dashboard 互斥):oncall > default > off。
412
- const wdMode = def.enabled ? 'oncall' : (b.defaultWorkingDir ? 'default' : 'off');
413
- // 输入框预填「最可能的目录」:defaultWorkingDir 优先,否则 oncall 目录(disable 后仍保留),
414
- // 便于来回切换模式不用重输。
415
- const wdDirValue = b.defaultWorkingDir || def.workingDir || '';
416
- const name = b.botName ?? b.larkAppId;
417
- const cli = displayCliId(b, cliIdOf(b.larkAppId));
418
- return `<article class="bd-card bd-profile" data-appid="${escapeHtml(b.larkAppId)}">
419
- <header class="bd-profile-head">
420
- ${botAvatarHtml({ name, larkAppId: b.larkAppId, dot: 'ok' })}
421
- <div class="bd-profile-id">
422
- <span class="bd-name-row" data-name-row>
423
- <strong data-bot-name>${escapeHtml(name)}</strong>
424
- <button type="button" class="bd-name-edit" data-action="edit-bot-name"
425
- title="${escapeHtml(t('botDefaults.renameTitle'))}" aria-label="${escapeHtml(t('botDefaults.renameTitle'))}">
426
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
427
- </button>
428
- </span>
429
- <span class="bd-name-editor" data-name-editor hidden>
430
- <input type="text" class="bd-name-input" data-input="botRename" maxlength="64" value="${escapeHtml(name)}">
431
- <button type="button" class="primary" data-action="save-bot-name">${t('botDefaults.renameSave')}</button>
432
- <button type="button" data-action="cancel-bot-name">${t('botDefaults.renameCancel')}</button>
433
- </span>
434
- ${cli ? `<span class="mate-role">${escapeHtml(cli)}</span>` : ''}
435
- <code>${escapeHtml(b.larkAppId)}</code>
436
- <small class="bd-name-status oncall-status" data-name-status></small>
437
- <button type="button" class="bd-feishu-login" data-action="feishu-login" hidden>${t('feishuLogin.entry')}</button>
438
- </div>
439
- <div class="bd-profile-meta bd-meta">
440
- <small class="bd-meta-ok">● ${t('botDefaults.metaOnline')}</small>
441
- <small data-oncall-since>${t('botDefaults.lastEnabled')}: ${escapeHtml(fmtSince(def.since ?? 0))}</small>
442
- <small>${t('botDefaults.autobound', { count: b.autoboundChatCount ?? 0 })}</small>
443
- </div>
444
- </header>
445
- <div class="bd-body bd-grid">
446
- <section class="bd-tile">
447
- ${renderBotAgentSection(b, cli)}
448
- <section class="bd-section">
449
- <h3 class="bd-section-title">${t('botDefaults.sectionWorkingDir')}</h3>
450
- <div class="bd-row">
451
- <label>
452
- <span>${t('botDefaults.workingDirMode')}</span>
453
- <select data-input="workingDirMode">
454
- <option value="off" ${wdMode === 'off' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeOff'))}</option>
455
- <option value="default" ${wdMode === 'default' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeDefault'))}</option>
456
- <option value="oncall" ${wdMode === 'oncall' ? 'selected' : ''}>${escapeHtml(t('botDefaults.workingDirModeOncall'))}</option>
457
- </select>
458
- </label>
459
- <small class="bd-help">${t('botDefaults.workingDirModeHelp')}</small>
460
- </div>
461
- <div class="bd-row" data-wd-dir-row ${wdMode === 'off' ? 'hidden' : ''}>
462
- <label>
463
- <span>${t('botDefaults.workingDirField')}</span>
464
- <input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
465
- value="${escapeHtml(wdDirValue)}">
466
- </label>
467
- </div>
468
- <label class="toggle-row" data-wd-worktree-row ${wdMode === 'default' ? '' : 'hidden'}>
469
- <input type="checkbox" data-input="autoWorktree" ${b.defaultWorkingDirAutoWorktree ? 'checked' : ''}>
470
- <span class="switch" aria-hidden="true"></span>
471
- <span class="toggle-tx"><strong>${t('botDefaults.autoWorktree')}</strong>
472
- <small>${t('botDefaults.autoWorktreeHelp')}</small></span>
473
- </label>
474
- <div class="actions">
475
- <button type="button" class="primary" data-action="save-working-dir">${t('botDefaults.save')}</button>
476
- <span class="oncall-status" data-status></span>
477
- </div>
478
- ${renderAutoStartControls(b)}
479
- </section>
480
- ${renderSandboxSection(b)}
481
- </section>
482
- <section class="bd-tile">${renderRoleSection(b)}</section>
483
- <section class="bd-tile">${renderSessionModeSection(b)}${renderSubstituteModeSection(b)}${renderCrossBotSection(b)}${renderSessionCapSection(b)}${renderStartupCommandsSection(b)}${renderLaunchShellSection(b)}${renderEnvSection(b)}</section>
484
- <section class="bd-tile">${renderCardBehaviorSection(b)}${renderSummaryTriggerSection(b)}${renderBrandSection(b)}</section>
485
- <section class="bd-tile">${renderGrantSection(b)}</section>
486
- </div>
487
- </article>`;
488
- }
489
- // Team-level role editor (one role per bot, cross-chat). This is the
490
- // canonical place to EDIT the team role — the Team page only shows it
491
- // read-only. The role isn't part of the /api/bots payload, so it's fetched
492
- // once per bot via GET /api/team/local-bots/{app}/role and cached onto the
493
- // bot snapshot (b.teamRole) — the page re-renders on every search keystroke,
494
- // so caching avoids a fetch-per-keystroke storm. undefined = not loaded yet
495
- // (render disabled + lazy-load); string = loaded (render it directly).
496
- function renderRoleSection(b) {
497
- const loaded = typeof b.teamRole === 'string';
498
- return `<section class="bd-section">
499
- <h3 class="bd-section-title">${t('botDefaults.sectionRole')}</h3>
500
- <p class="bd-section-note">${t('botDefaults.roleHelp')}</p>
501
- <textarea data-input="teamRole" rows="6"
502
- placeholder="${escapeHtml(t('botDefaults.rolePlaceholder'))}"
503
- style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px"${loaded ? '' : ' disabled'}>${loaded ? escapeHtml(b.teamRole) : ''}</textarea>
504
- <div class="actions">
505
- <button type="button" class="primary" data-action="save-role"${loaded ? '' : ' disabled'}>${t('botDefaults.roleSave')}</button>
506
- <button type="button" data-action="delete-role"${loaded ? '' : ' disabled'}>${t('botDefaults.roleDelete')}</button>
507
- <span class="oncall-status" data-role-status></span>
508
- </div>
509
- <div class="bd-profile-roles" data-profile-roles>
510
- <h4 class="bd-subsection-title">${t('botDefaults.profileRoles')}</h4>
511
- <p class="bd-section-note">${t('botDefaults.profileRolesHelp')}</p>
512
- <div class="bd-profile-role-list" data-profile-role-list>${renderProfileRoleList(b.larkAppId)}</div>
513
- </div>
514
- </section>`;
515
- }
516
- function renderProfileRoleList(appId) {
517
- const state = botProfileRoleCache.get(appId);
518
- if (!state || state.loading)
519
- return loadingHtml();
520
- if (state.error)
521
- return `<p class="hint-warn-inline">${escapeHtml(t('botDefaults.profileRolesLoadFailed', { error: state.error }))}</p>`;
522
- if (state.items.length === 0)
523
- return `<p class="empty">${t('botDefaults.profileRolesEmpty')}</p>`;
524
- return state.items.map(item => `
525
- <details class="bd-profile-role-entry" data-profile-id="${escapeHtml(item.profileId)}">
526
- <summary><code>${escapeHtml(item.profileId)}</code></summary>
527
- <div class="bd-profile-role-content" data-profile-role-body="${escapeHtml(item.profileId)}">
528
- ${renderProfileRoleContent(item)}
529
- </div>
530
- </details>
531
- `).join('');
532
- }
533
- function renderProfileRoleContent(item) {
534
- if (item.loading)
535
- return loadingHtml();
536
- if (item.error)
537
- return `<p class="hint-warn-inline">${escapeHtml(t('botDefaults.profileRoleDetailLoadFailed', { error: item.error }))}</p>`;
538
- if (!item.loaded)
539
- return `<p class="empty">${t('botDefaults.profileRoleClickToLoad')}</p>`;
540
- return `<pre>${escapeHtml(item.content ?? '')}</pre>`;
541
- }
542
- // brandLabel is null when unset (→ default botmux), '' when off, else custom.
543
- // The input shows the configured string ('' for both unset and off); a small
544
- // state line disambiguates which of the three the bot is currently in.
545
- function brandStateLabel(brand) {
546
- if (brand == null)
547
- return t('botDefaults.brandStateDefault');
548
- return brand.trim() === '' ? t('botDefaults.brandStateOff') : t('botDefaults.brandStateCustom');
549
- }
550
- function renderBrandSection(b) {
551
- const brand = b.brandLabel ?? null;
552
- return `<section class="bd-section">
553
- <h3 class="bd-section-title">${t('botDefaults.sectionBrand')}</h3>
554
- <div class="bd-row bd-brand">
555
- <label>
556
- <span>${t('botDefaults.brandLabel')}</span>
557
- <input type="text" data-input="brandLabel"
558
- placeholder="${escapeHtml(t('botDefaults.brandLabelPlaceholder'))}"
559
- value="${escapeHtml(brand ?? '')}">
560
- </label>
561
- <small data-brand-state>${escapeHtml(brandStateLabel(brand))}</small>
562
- <small class="bd-help">${t('botDefaults.brandLabelHelp')}</small>
563
- <div class="actions">
564
- <button type="button" class="primary" data-action="save-brand">${t('botDefaults.brandSave')}</button>
565
- <button type="button" data-action="reset-brand">${t('botDefaults.brandReset')}</button>
566
- <span class="oncall-status" data-brand-status></span>
567
- </div>
568
- </div>
569
- </section>`;
570
- }
571
- // Per-bot card-behaviour toggles. Each auto-saves on change (no explicit save
572
- // button — each checkbox PUTs immediately). Two are gated on the streaming-card
573
- // state: the writable-link toggle is moot WHILE the card is disabled; the
574
- // status-reactions toggle only matters WHILE the card is disabled (the ✋→✅
575
- // reactions only appear in card-off sessions), so it's editable only then.
576
- function renderCardBehaviorSection(b) {
577
- const disableStreaming = b.disableStreamingCard === true;
578
- const silentReactions = b.silentTurnReactions === true;
579
- const writableLink = b.writableTerminalLinkInCard === true;
580
- const privateCard = b.privateCard === true;
581
- return `<section class="bd-section">
582
- <h3 class="bd-section-title">${t('botDefaults.sectionCard')}</h3>
583
- <label class="toggle-row">
584
- <input type="checkbox" data-action="toggle-disable-streaming" ${disableStreaming ? 'checked' : ''}>
585
- <span class="switch" aria-hidden="true"></span>
586
- <span class="toggle-tx"><strong>${t('botDefaults.disableStreaming')}</strong>
587
- <small>${t('botDefaults.disableStreamingHelp')}</small></span>
588
- </label>
589
- <label class="toggle-row">
590
- <input type="checkbox" data-action="toggle-silent-reactions" ${silentReactions ? 'checked' : ''} ${disableStreaming ? '' : 'disabled'}>
591
- <span class="switch" aria-hidden="true"></span>
592
- <span class="toggle-tx"><strong>${t('botDefaults.silentTurnReactions')}</strong>
593
- <small>${t('botDefaults.silentTurnReactionsHelp')}</small></span>
594
- </label>
595
- <label class="toggle-row">
596
- <input type="checkbox" data-action="toggle-writable-link" ${writableLink ? 'checked' : ''} ${disableStreaming ? 'disabled' : ''}>
597
- <span class="switch" aria-hidden="true"></span>
598
- <span class="toggle-tx"><strong>${t('botDefaults.writableLink')}</strong>
599
- <small>${t('botDefaults.writableLinkHelp')}</small></span>
600
- </label>
601
- <label class="toggle-row">
602
- <input type="checkbox" data-action="toggle-private-card" ${privateCard ? 'checked' : ''}>
603
- <span class="switch" aria-hidden="true"></span>
604
- <span class="toggle-tx"><strong>${t('botDefaults.privateCard')}</strong>
605
- <small>${t('botDefaults.privateCardHelp')}</small></span>
606
- </label>
607
- <div class="actions">
608
- <small data-card-pref-moot class="hint-warn-inline" ${disableStreaming ? '' : 'hidden'}>${t('botDefaults.writableLinkMoot')}</small>
609
- <span class="oncall-status" data-card-pref-status></span>
610
- </div>
611
- </section>`;
612
- }
613
- // bot@bot 同目录拉起 (cross-bot working-dir inheritance). Default ON. Auto-saves
614
- // on change via the shared card-prefs PUT.
615
- function renderCrossBotSection(b) {
616
- const sameDir = b.botToBotSameDir !== false;
617
- return `<section class="bd-section">
618
- <h3 class="bd-section-title">${t('botDefaults.sectionCrossBot')}</h3>
619
- <label class="toggle-row">
620
- <input type="checkbox" data-action="toggle-cross-bot-samedir" ${sameDir ? 'checked' : ''}>
621
- <span class="switch" aria-hidden="true"></span>
622
- <span class="toggle-tx"><strong>${t('botDefaults.botToBotSameDir')}</strong>
623
- <small>${t('botDefaults.botToBotSameDirHelp')}</small></span>
624
- </label>
625
- <div class="actions"><span class="oncall-status" data-crossbot-status></span></div>
626
- </section>`;
627
- }
628
- function renderSummaryTriggerSection(b) {
629
- const range = b.summaryRange ?? { limit: 50, sinceHours: 24 };
630
- const limit = Number.isInteger(range.limit) && range.limit >= 0 ? range.limit : 50;
631
- const sinceHours = Number.isInteger(range.sinceHours) && range.sinceHours >= 0 ? range.sinceHours : 24;
632
- return `<section class="bd-section">
633
- <h3 class="bd-section-title">${t('botDefaults.sectionSummaryTrigger')}</h3>
634
- <div class="bd-row bd-summary-limits">
635
- <label>
636
- <span>${t('botDefaults.summaryLimit')}</span>
637
- <input type="number" min="0" step="1" data-input="summaryLimit" value="${limit}">
638
- </label>
639
- <label>
640
- <span>${t('botDefaults.summarySinceHours')}</span>
641
- <input type="number" min="0" step="1" data-input="summarySinceHours" value="${sinceHours}">
642
- </label>
643
- </div>
644
- <small class="bd-help">${t('botDefaults.summaryLimitHelp')}</small>
645
- <div class="actions">
646
- <button type="button" class="primary" data-action="save-summary-trigger">${t('botDefaults.summarySave')}</button>
647
- <span class="oncall-status" data-summary-trigger-status></span>
648
- </div>
649
- </section>`;
650
- }
651
- // 会话模式:私聊(p2pMode)+ 普通群(regularGroupReplyMode)两个默认会话方式
652
- // 放在同一板块,各自一个下拉、一改即保存。
653
- // • p2pMode → PUT /api/bots/:appId/p2p-mode(走 applyConfigField,与 /botconfig 同路径)
654
- // • 普通群默认模式 mode → PUT /api/bots/:appId/card-prefs 的 regularGroupReplyMode
655
- // (chat | chat-topic | new-topic | shared,默认 chat)
656
- // per-chat 的 /reply-mode 可覆盖此 per-bot 默认。
657
- function renderSessionModeSection(b) {
658
- const p2p = b.p2pMode === 'chat' ? 'chat' : 'thread';
659
- const regular = (b.regularGroupReplyMode === 'new-topic' || b.regularGroupReplyMode === 'shared' || b.regularGroupReplyMode === 'chat-topic')
660
- ? b.regularGroupReplyMode : 'chat';
661
- const mention = (b.regularGroupMentionMode === 'topic' || b.regularGroupMentionMode === 'never' || b.regularGroupMentionMode === 'ambient')
662
- ? b.regularGroupMentionMode : 'always';
663
- const docMode = b.docSubscribeDefaultMode === 'all' ? 'all' : 'mention-only';
664
- const opt = (v, label) => `<option value="${v}" ${regular === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
665
- const mopt = (v, label) => `<option value="${v}" ${mention === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
666
- const dopt = (v, label) => `<option value="${v}" ${docMode === v ? 'selected' : ''}>${escapeHtml(label)}</option>`;
667
- return `<section class="bd-section">
668
- <h3 class="bd-section-title">${t('botDefaults.sectionSessionMode')}</h3>
669
- <div class="bd-row">
670
- <label>
671
- <span>${t('botDefaults.p2pMode')}</span>
672
- <select data-input="p2pMode">
673
- <option value="thread" ${p2p === 'chat' ? '' : 'selected'}>${escapeHtml(t('botDefaults.p2pThread'))}</option>
674
- <option value="chat" ${p2p === 'chat' ? 'selected' : ''}>${escapeHtml(t('botDefaults.p2pChat'))}</option>
675
- </select>
676
- </label>
677
- <small class="bd-help">${t('botDefaults.p2pHelp')}</small>
678
- <div class="actions">
679
- <span class="oncall-status" data-p2p-status></span>
680
- </div>
681
- </div>
682
- <div class="bd-row">
683
- <label>
684
- <span>${t('botDefaults.regularGroupMode')}</span>
685
- <select data-input="regularGroupMode">
686
- ${opt('chat', t('botDefaults.regularGroupModeChat'))}
687
- ${opt('chat-topic', t('botDefaults.regularGroupModeChatTopic'))}
688
- ${opt('new-topic', t('botDefaults.regularGroupModeNewTopic'))}
689
- ${opt('shared', t('botDefaults.regularGroupModeShared'))}
690
- </select>
691
- </label>
692
- <small class="bd-help">${t('botDefaults.regularGroupModeHelp')}</small>
693
- <div class="actions">
694
- <span class="oncall-status" data-regular-group-status></span>
695
- </div>
696
- </div>
697
- <div class="bd-row">
698
- <label>
699
- <span>${t('botDefaults.mentionMode')}</span>
700
- <select data-input="regularGroupMentionMode">
701
- ${mopt('always', t('botDefaults.mentionModeAlways'))}
702
- ${mopt('topic', t('botDefaults.mentionModeTopic'))}
703
- ${mopt('never', t('botDefaults.mentionModeNever'))}
704
- ${mopt('ambient', t('botDefaults.mentionModeAmbient'))}
705
- </select>
706
- </label>
707
- <small class="bd-help">${t('botDefaults.mentionModeHelp')}</small>
708
- <div class="actions">
709
- <span class="oncall-status" data-mention-mode-status></span>
710
- </div>
711
- </div>
712
- <div class="bd-row">
713
- <label>
714
- <span>${t('botDefaults.docSubscribeMode')}</span>
715
- <select data-input="docSubscribeDefaultMode">
716
- ${dopt('mention-only', t('botDefaults.docSubscribeModeMention'))}
717
- ${dopt('all', t('botDefaults.docSubscribeModeAll'))}
718
- </select>
719
- </label>
720
- <small class="bd-help">${t('botDefaults.docSubscribeModeHelp')}</small>
721
- <div class="actions">
722
- <span class="oncall-status" data-doc-subscribe-mode-status></span>
723
- </div>
724
- </div>
725
- </section>`;
726
- }
727
- function renderSubstituteModeSection(b) {
728
- const mode = b.substituteMode && typeof b.substituteMode === 'object' ? b.substituteMode : null;
729
- const enabled = mode?.enabled === true;
730
- const disclosure = mode?.disclosure === 'none' ? 'none' : 'prefix';
731
- return `<section class="bd-section">
732
- <h3 class="bd-section-title">${t('botDefaults.sectionSubstitute')}</h3>
733
- <label class="toggle-row">
734
- <input type="checkbox" data-action="toggle-substitute-mode" ${enabled ? 'checked' : ''}>
735
- <span class="switch" aria-hidden="true"></span>
736
- <span class="toggle-tx"><strong>${t('botDefaults.substituteEnabled')}</strong>
737
- <small>${t('botDefaults.substituteHelp')}</small></span>
738
- </label>
739
- <div class="bd-row">
740
- <label>
741
- <span>${t('botDefaults.substituteDisclosure')}</span>
742
- <select data-input="substituteDisclosure">
743
- <option value="prefix" ${disclosure === 'prefix' ? 'selected' : ''}>${escapeHtml(t('botDefaults.substituteDisclosurePrefix'))}</option>
744
- <option value="none" ${disclosure === 'none' ? 'selected' : ''}>${escapeHtml(t('botDefaults.substituteDisclosureNone'))}</option>
745
- </select>
746
- </label>
747
- </div>
748
- <textarea data-input="substituteTargets" rows="5"
749
- placeholder="${escapeHtml(t('botDefaults.substituteTargetsPlaceholder'))}"
750
- style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">${escapeHtml(formatSubstituteTargets(mode))}</textarea>
751
- <small class="bd-help">${t('botDefaults.substituteTargetsHelp')}</small>
752
- <div class="substitute-chips" data-substitute-chips style="margin:6px 0 0">${substituteChipsHtml(mode)}</div>
753
- <div class="actions">
754
- <button type="button" class="primary" data-action="save-substitute-mode">${t('botDefaults.substituteSave')}</button>
755
- <button type="button" data-action="off-substitute-mode">${t('botDefaults.substituteOff')}</button>
756
- <span class="oncall-status" data-substitute-status></span>
757
- </div>
758
- </section>`;
759
- }
760
- function sessionCapStateLabel(cap) {
761
- return cap == null
762
- ? t('botDefaults.maxLiveWorkersStateDefault')
763
- : t('botDefaults.maxLiveWorkersStateOn', { count: cap });
764
- }
765
- // 最大同时活跃会话数(maxLiveWorkers):数字输入 + 保存/恢复默认按钮(空=用默认 30)。
766
- // 超过上限时最久未用的会话自动休眠(worker+CLI 一起杀回收内存),下条消息冷恢复。
767
- // PUT /api/bots/:appId/max-live-workers 落 bots.json,daemon 每分钟读实时值即时生效。
768
- function renderSessionCapSection(b) {
769
- const cap = typeof b.maxLiveWorkers === 'number' ? b.maxLiveWorkers : null;
770
- return `<div class="bd-subsection">
771
- <h4 class="bd-subsection-title">${t('botDefaults.sectionSessionCap')}</h4>
772
- <div class="bd-row bd-quota">
773
- <label>
774
- <span>${t('botDefaults.maxLiveWorkers')}</span>
775
- <input type="number" min="1" step="1" data-input="maxLiveWorkers"
776
- placeholder="${escapeHtml(t('botDefaults.maxLiveWorkersPlaceholder'))}"
777
- value="${cap == null ? '' : cap}">
778
- </label>
779
- <small data-session-cap-state>${escapeHtml(sessionCapStateLabel(cap))}</small>
780
- <small class="bd-help">${t('botDefaults.maxLiveWorkersHelp')}</small>
781
- <div class="actions">
782
- <button type="button" class="primary" data-action="save-session-cap">${t('botDefaults.maxLiveWorkersSave')}</button>
783
- <button type="button" data-action="off-session-cap">${t('botDefaults.maxLiveWorkersOff')}</button>
784
- <span class="oncall-status" data-session-cap-status></span>
785
- </div>
786
- </div>
787
- </div>`;
788
- }
789
- // 启动命令 startupCommands:开会话后、首条消息前自动按序发给 CLI 的 slash 命令(可带
790
- // 参数,如 /effort ultracode)。文本域,逗号/换行分隔,每行一条;空=不发。next-session
791
- // 生效(含 resume,每次新会话重放)。PUT /api/bots/:appId/startup-commands 落 bots.json。
792
- function renderStartupCommandsSection(b) {
793
- const val = typeof b.startupCommands === 'string' ? b.startupCommands : '';
794
- return `<div class="bd-subsection">
795
- <h4 class="bd-subsection-title">${t('botDefaults.sectionStartupCommands')}</h4>
796
- <p class="bd-section-note">${t('botDefaults.startupCommandsHelp')}</p>
797
- <textarea data-input="startupCommands" rows="3"
798
- placeholder="${escapeHtml(t('botDefaults.startupCommandsPlaceholder'))}"
799
- style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">${escapeHtml(val)}</textarea>
800
- <div class="actions">
801
- <button type="button" class="primary" data-action="save-startup-commands">${t('botDefaults.startupCommandsSave')}</button>
802
- <span class="oncall-status" data-startup-commands-status></span>
803
- </div>
804
- </div>`;
805
- }
806
- // 启动 shell launchShell:启动 CLI 用的 shell(zsh|bash|sh 或绝对路径),覆盖 $SHELL。
807
- // 用于登录 $SHELL(如 bash)的 rc 文件里 `exec zsh` 跳转、导致 CLI 起不来的场景。
808
- // next-session 生效。PUT /api/bot-launch-shell 落 bots.json。
809
- function renderLaunchShellSection(b) {
810
- const val = typeof b.launchShell === 'string' ? b.launchShell : '';
811
- return `<div class="bd-subsection">
812
- <h4 class="bd-subsection-title">${t('botDefaults.sectionLaunchShell')}</h4>
813
- <p class="bd-section-note">${t('botDefaults.launchShellHelp')}</p>
814
- <input type="text" data-input="launchShell"
815
- placeholder="${escapeHtml(t('botDefaults.launchShellPlaceholder'))}"
816
- value="${escapeHtml(val)}"
817
- style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">
818
- <div class="actions">
819
- <button type="button" class="primary" data-action="save-launch-shell">${t('botDefaults.launchShellSave')}</button>
820
- <span class="oncall-status" data-launch-shell-status></span>
821
- </div>
822
- </div>`;
823
- }
824
- // 环境变量 env:注入到本 bot CLI 进程的环境变量(JSON 对象),如让某个 bot 走 GLM/
825
- // 第三方服务商(ANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKEN)或设 HTTPS_PROXY。next-session
826
- // 生效(下个新会话起注入)。PUT /api/bots/:appId/env 落 bots.json,跨后端按会话注入
827
- // (不进共享 tmux server 全局 env,不会串到别的 bot)。
828
- function renderEnvSection(b) {
829
- const val = typeof b.env === 'string' ? b.env : '';
830
- return `<div class="bd-subsection">
831
- <h4 class="bd-subsection-title">${t('botDefaults.sectionEnv')}</h4>
832
- <p class="bd-section-note">${t('botDefaults.envHelp')}</p>
833
- <textarea data-input="env" rows="5"
834
- placeholder="${escapeHtml(t('botDefaults.envPlaceholder'))}"
835
- style="width:100%;box-sizing:border-box;font:13px/1.5 ui-monospace,Menlo,monospace;padding:10px">${escapeHtml(val)}</textarea>
836
- <div class="actions">
837
- <button type="button" class="primary" data-action="save-env">${t('botDefaults.envSave')}</button>
838
- <span class="oncall-status" data-env-status></span>
839
- </div>
840
- </div>`;
841
- }
842
- // File sandbox (oncall): a per-bot toggle. ON → this bot's sessions run inside
843
- // a per-session bwrap file sandbox (Linux). Auto-saves on change.
844
- function renderSandboxSection(b) {
845
- const on = b.sandbox === true;
846
- return `<section class="bd-section">
847
- <h3 class="bd-section-title">${t('botDefaults.sectionSandbox')}</h3>
848
- <label class="toggle-row">
849
- <input type="checkbox" data-action="toggle-sandbox" ${on ? 'checked' : ''}>
850
- <span class="switch" aria-hidden="true"></span>
851
- <span class="toggle-tx"><strong>${t('botDefaults.sandboxToggle')}</strong>
852
- <small>${t('botDefaults.sandboxHelp')}</small></span>
853
- </label>
854
- <div class="actions">
855
- <span class="oncall-status" data-sandbox-status></span>
856
- </div>
857
- </section>`;
858
- }
859
- function quotaStateLabel(quota) {
860
- return quota == null
861
- ? t('botDefaults.quotaStateOff')
862
- : t('botDefaults.quotaStateOn', { count: quota });
863
- }
864
- // 授权(/grant)相关:自动申请卡、命令限制开关 + 默认消息额度。都通过
865
- // PUT /api/bots/:appId/grant-prefs 落到 bots.json,daemon 内存同步即时生效。
866
- function renderGrantSection(b) {
867
- const restrict = b.restrictGrantCommands === true;
868
- const autoCard = b.autoGrantRequestCards !== false;
869
- const quota = typeof b.messageQuotaDefaultLimit === 'number' ? b.messageQuotaDefaultLimit : null;
870
- return `<section class="bd-section">
871
- <h3 class="bd-section-title">${t('botDefaults.sectionGrant')}</h3>
872
- <label class="toggle-row">
873
- <input type="checkbox" data-action="toggle-auto-grant-card" ${autoCard ? 'checked' : ''}>
874
- <span class="switch" aria-hidden="true"></span>
875
- <span class="toggle-tx"><strong>${t('botDefaults.autoGrantCard')}</strong>
876
- <small>${t('botDefaults.autoGrantCardHelp')}</small></span>
877
- </label>
878
- <label class="toggle-row">
879
- <input type="checkbox" data-action="toggle-restrict-grant" ${restrict ? 'checked' : ''}>
880
- <span class="switch" aria-hidden="true"></span>
881
- <span class="toggle-tx"><strong>${t('botDefaults.restrictGrant')}</strong>
882
- <small>${t('botDefaults.restrictGrantHelp')}</small></span>
883
- </label>
884
- <div class="bd-row bd-quota">
885
- <label>
886
- <span>${t('botDefaults.quotaDefault')}</span>
887
- <input type="number" min="1" step="1" data-input="quotaLimit"
888
- placeholder="${escapeHtml(t('botDefaults.quotaPlaceholder'))}"
889
- value="${quota == null ? '' : quota}">
890
- </label>
891
- <small data-quota-state>${escapeHtml(quotaStateLabel(quota))}</small>
892
- <small class="bd-help">${t('botDefaults.quotaHelp')}</small>
893
- <div class="actions">
894
- <button type="button" class="primary" data-action="save-quota">${t('botDefaults.quotaSave')}</button>
895
- <button type="button" data-action="off-quota">${t('botDefaults.quotaOff')}</button>
896
- <span class="oncall-status" data-grant-status></span>
897
- </div>
898
- </div>
899
- </section>`;
900
- }
901
- // 主动开工 — rendered as a sub-block INSIDE the 新群 Oncall section (it's part
902
- // of the same "proactively engage" config family). The two checkboxes auto-save
903
- // on change; the 场景① prompt has its own save button (a textarea shouldn't PUT
904
- // per keystroke). Data-action hooks are unchanged → wireCardHandlers still finds them.
905
- function renderAutoStartControls(b) {
906
- const onJoin = b.autoStartOnGroupJoin === true;
907
- const onTopic = b.autoStartOnNewTopic === true;
908
- const joinPrompt = typeof b.autoStartOnGroupJoinPrompt === 'string' ? b.autoStartOnGroupJoinPrompt : '';
909
- return `<div class="bd-subsection">
910
- <h4 class="bd-subsection-title">${t('botDefaults.sectionAutoStart')}</h4>
911
- <label class="toggle-row">
912
- <input type="checkbox" data-action="toggle-auto-join" ${onJoin ? 'checked' : ''}>
913
- <span class="switch" aria-hidden="true"></span>
914
- <span class="toggle-tx"><strong>${t('botDefaults.autoStartJoin')}</strong>
915
- <small>${t('botDefaults.autoStartJoinHelp')}</small></span>
916
- </label>
917
- <div class="bd-row">
918
- <label>
919
- <span>${t('botDefaults.autoStartJoinPrompt')}</span>
920
- <textarea data-input="autoJoinPrompt" rows="3"
921
- placeholder="${escapeHtml(t('botDefaults.autoStartJoinPromptPlaceholder'))}">${escapeHtml(joinPrompt)}</textarea>
922
- </label>
923
- <div class="actions">
924
- <button type="button" class="primary" data-action="save-auto-join-prompt">${t('botDefaults.autoStartJoinPromptSave')}</button>
925
- </div>
926
- </div>
927
- <label class="toggle-row">
928
- <input type="checkbox" data-action="toggle-auto-topic" ${onTopic ? 'checked' : ''}>
929
- <span class="switch" aria-hidden="true"></span>
930
- <span class="toggle-tx"><strong>${t('botDefaults.autoStartTopic')}</strong>
931
- <small>${t('botDefaults.autoStartTopicHelp')}</small></span>
932
- </label>
933
- <div class="actions">
934
- <span class="oncall-status" data-auto-start-status></span>
935
- </div>
936
- </div>`;
937
- }
938
- function liveCard(appId) {
939
- return listEl.querySelector(`.bd-card[data-appid="${CSS.escape(appId)}"]`);
940
- }
941
- function renderProfileRolesInto(card, appId) {
942
- const target = card?.querySelector('[data-profile-role-list]');
943
- if (!target)
944
- return;
945
- target.innerHTML = renderProfileRoleList(appId);
946
- wireProfileRoleDetails(card, appId);
947
- }
948
- async function ensureProfileRolesLoaded(appId, card) {
949
- let state = botProfileRoleCache.get(appId);
950
- if (state?.loaded || state?.loading) {
951
- renderProfileRolesInto(card, appId);
952
- return;
953
- }
954
- state = { loaded: false, loading: true, items: [] };
955
- botProfileRoleCache.set(appId, state);
956
- renderProfileRolesInto(card, appId);
957
- try {
958
- const r = await fetch('/api/role-profiles');
959
- const body = await r.json().catch(() => ({}));
960
- if (!r.ok)
961
- throw new Error(body?.error ?? String(r.status));
962
- const profiles = Array.isArray(body.profiles) ? body.profiles : [];
963
- state.items = profiles
964
- .filter((profile) => (profile.botEntries ?? []).some((entry) => entry?.larkAppId === appId && entry?.hasEntry))
965
- .map((profile) => ({ profileId: String(profile.profileId) }));
966
- state.loaded = true;
967
- }
968
- catch (e) {
969
- state.error = e?.message ?? String(e);
970
- state.loaded = true;
971
- }
972
- finally {
973
- state.loading = false;
974
- renderProfileRolesInto(liveCard(appId), appId);
975
- }
976
- }
977
- function wireProfileRoleDetails(card, appId) {
978
- card.querySelectorAll('details.bd-profile-role-entry[data-profile-id]').forEach(detail => {
979
- detail.addEventListener('toggle', () => {
980
- if (!detail.open)
981
- return;
982
- const profileId = detail.dataset.profileId;
983
- if (profileId)
984
- void ensureProfileRoleDetailLoaded(appId, profileId);
985
- });
986
- });
987
- }
988
- async function ensureProfileRoleDetailLoaded(appId, profileId) {
989
- const state = botProfileRoleCache.get(appId);
990
- const item = state?.items.find(i => i.profileId === profileId);
991
- if (!item || item.loaded || item.loading)
992
- return;
993
- item.loading = true;
994
- renderProfileRoleBody(appId, profileId);
995
- try {
996
- const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(appId)}`);
997
- const body = await r.json().catch(() => ({}));
998
- if (!r.ok)
999
- throw new Error(body?.error ?? String(r.status));
1000
- item.content = body?.hasEntry ? String(body.content ?? '') : '';
1001
- item.loaded = true;
1002
- }
1003
- catch (e) {
1004
- item.error = e?.message ?? String(e);
1005
- }
1006
- finally {
1007
- item.loading = false;
1008
- renderProfileRoleBody(appId, profileId);
1009
- }
1010
- }
1011
- function renderProfileRoleBody(appId, profileId) {
1012
- const state = botProfileRoleCache.get(appId);
1013
- const item = state?.items.find(i => i.profileId === profileId);
1014
- if (!item)
1015
- return;
1016
- const body = liveCard(appId)?.querySelector(`[data-profile-role-body="${CSS.escape(profileId)}"]`);
1017
- if (body)
1018
- body.innerHTML = renderProfileRoleContent(item);
1019
- }
1020
- function wireCardHandlers() {
1021
- listEl.querySelectorAll('.bd-card').forEach(card => {
1022
- const appId = card.dataset.appid;
1023
- void ensureProfileRolesLoaded(appId, card);
1024
- // ── 默认工作目录模式(三选一互斥:off / default / oncall)─────────────────
1025
- const wdModeSel = card.querySelector('select[data-input=workingDirMode]');
1026
- const input = card.querySelector('input[data-input=workingDir]');
1027
- const wdDirRow = card.querySelector('[data-wd-dir-row]');
1028
- const wdWorktreeRow = card.querySelector('[data-wd-worktree-row]');
1029
- const autoWorktreeInput = card.querySelector('input[data-input=autoWorktree]');
1030
- const saveBtn = card.querySelector('button[data-action=save-working-dir]');
1031
- const statusEl = card.querySelector('[data-status]');
1032
- if (!wdModeSel || !input || !saveBtn || !statusEl)
1033
- return; // error card
1034
- // ── Agent CLI / model (next-session) ─────────────────────────────────
1035
- const agentCliSel = card.querySelector('select[data-input=agentCliId]');
1036
- const agentModelInput = card.querySelector('input[data-input=agentModel]');
1037
- const agentSaveBtn = card.querySelector('button[data-action=save-agent]');
1038
- const agentStatusEl = card.querySelector('[data-agent-status]');
1039
- function syncAgentModelField() {
1040
- if (!agentCliSel || !agentModelInput)
1041
- return;
1042
- const list = card.querySelector(`#agent-model-suggestions-${CSS.escape(appId)}`);
1043
- const opt = selectedCliOption(agentCliSel.value);
1044
- const isTtadk = opt?.gateway === 'ttadk';
1045
- const acceptsModel = isTtadk && opt.acceptsModel !== false;
1046
- if (isTtadk && !acceptsModel) {
1047
- if (list)
1048
- list.innerHTML = '';
1049
- agentModelInput.value = '';
1050
- agentModelInput.disabled = true;
1051
- agentModelInput.placeholder = t('botOnboarding.modelTtadkCocoPlaceholder');
1052
- return;
1053
- }
1054
- agentModelInput.disabled = false;
1055
- if (acceptsModel) {
1056
- if (list)
1057
- list.innerHTML = ttadkModelSuggestions.map(m => `<option value="${escapeHtml(m)}"></option>`).join('');
1058
- agentModelInput.placeholder = t('botOnboarding.modelTtadkPlaceholder').replace('{model}', ttadkModelDefault);
1059
- if (!agentModelInput.value.trim())
1060
- agentModelInput.value = ttadkModelDefault;
1061
- }
1062
- else {
1063
- if (list)
1064
- list.innerHTML = '';
1065
- agentModelInput.placeholder = t('botDefaults.agentModelPlaceholder');
1066
- if (agentModelInput.value.trim() === ttadkModelDefault)
1067
- agentModelInput.value = '';
1068
- }
1069
- }
1070
- if (agentCliSel && agentModelInput && agentSaveBtn && agentStatusEl) {
1071
- agentCliSel.addEventListener('change', syncAgentModelField);
1072
- agentSaveBtn.addEventListener('click', async () => {
1073
- agentStatusEl.textContent = '';
1074
- agentStatusEl.className = 'oncall-status';
1075
- agentSaveBtn.disabled = true;
1076
- agentCliSel.disabled = true;
1077
- agentModelInput.disabled = true;
1078
- try {
1079
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/agent`, {
1080
- method: 'PUT',
1081
- headers: { 'content-type': 'application/json' },
1082
- body: JSON.stringify({ cliId: agentCliSel.value, model: agentModelInput.value }),
1083
- });
1084
- const body = await r.json().catch(() => ({}));
1085
- if (r.ok && body.ok) {
1086
- agentStatusEl.textContent = `✓ ${t('botDefaults.agentSaved')}`;
1087
- agentStatusEl.classList.add('hint-ok');
1088
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1089
- if (cached) {
1090
- cached.cliId = body.cliId;
1091
- cached.wrapperCli = body.wrapperCli ?? null;
1092
- cached.model = body.model ?? '';
1093
- cached.agentSelectionKey = body.selectionKey ?? agentCliSel.value;
1094
- }
1095
- }
1096
- else {
1097
- agentStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1098
- agentStatusEl.classList.add('hint-warn-inline');
1099
- }
1100
- }
1101
- catch (e) {
1102
- agentStatusEl.textContent = `✗ ${e?.message ?? e}`;
1103
- agentStatusEl.classList.add('hint-warn-inline');
1104
- }
1105
- finally {
1106
- agentSaveBtn.disabled = false;
1107
- agentCliSel.disabled = false;
1108
- agentModelInput.disabled = false;
1109
- syncAgentModelField();
1110
- }
1111
- });
1112
- }
1113
- // 选「关闭」隐藏目录输入框;选其它则显示并聚焦(off 不需要目录)。
1114
- // 「自动创建 worktree」开关仅「仅默认目录」模式可见(脱离该模式无意义)。
1115
- wdModeSel.addEventListener('change', () => {
1116
- const off = wdModeSel.value === 'off';
1117
- if (wdDirRow)
1118
- wdDirRow.hidden = off;
1119
- if (wdWorktreeRow)
1120
- wdWorktreeRow.hidden = wdModeSel.value !== 'default';
1121
- if (!off)
1122
- input.focus();
1123
- });
1124
- saveBtn.addEventListener('click', async () => {
1125
- statusEl.textContent = '';
1126
- statusEl.className = 'oncall-status';
1127
- const mode = wdModeSel.value;
1128
- const workingDir = input.value.trim();
1129
- if (mode !== 'off' && !workingDir) {
1130
- statusEl.textContent = t('botDefaults.required');
1131
- statusEl.classList.add('hint-warn-inline');
1132
- return;
1133
- }
1134
- // 「自动创建 worktree」仅「仅默认目录」模式有效;其它模式一律传 false(后端也会强制清)。
1135
- const autoWorktree = mode === 'default' && !!autoWorktreeInput?.checked;
1136
- saveBtn.disabled = true;
1137
- try {
1138
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/working-dir-mode`, {
1139
- method: 'PUT',
1140
- headers: { 'content-type': 'application/json' },
1141
- body: JSON.stringify({ mode, workingDir, autoWorktree }),
1142
- });
1143
- const body = await r.json().catch(() => ({}));
1144
- if (r.ok && body.ok) {
1145
- const resolvedNote = body.resolvedPath ? ` → ${body.resolvedPath}` : '';
1146
- statusEl.textContent = `✓ ${t('botDefaults.workingDirSaved')}${resolvedNote}`;
1147
- statusEl.classList.add('hint-ok');
1148
- // Patch in-cache snapshot so the next manual Refresh / filter
1149
- // rerender reflects the new mode. We deliberately don't call
1150
- // rerender() here — that would rebuild the card and wipe the toast.
1151
- const cached = cache.bots.find((b) => b.larkAppId === appId);
1152
- if (cached) {
1153
- if (body.defaultOncall)
1154
- cached.defaultOncall = body.defaultOncall;
1155
- cached.defaultWorkingDir = body.defaultWorkingDir ?? null;
1156
- cached.defaultWorkingDirAutoWorktree = body.defaultWorkingDirAutoWorktree === true;
1157
- }
1158
- // Oncall 模式 re-stamps `since` → reflect it in the meta line.
1159
- const metaEl = card.querySelector('[data-oncall-since]');
1160
- if (metaEl && body.defaultOncall?.since != null) {
1161
- metaEl.textContent = `${t('botDefaults.lastEnabled')}: ${fmtSince(body.defaultOncall.since)}`;
1162
- }
1163
- }
1164
- else {
1165
- statusEl.textContent = `✗ ${body.error ?? r.status}`;
1166
- statusEl.classList.add('hint-warn-inline');
1167
- }
1168
- }
1169
- catch (e) {
1170
- statusEl.textContent = `✗ ${e?.message ?? e}`;
1171
- statusEl.classList.add('hint-warn-inline');
1172
- }
1173
- finally {
1174
- saveBtn.disabled = false;
1175
- }
1176
- });
1177
- // ── Brand label (independent of oncall save) ──────────────────────────
1178
- const brandInput = card.querySelector('input[data-input=brandLabel]');
1179
- const brandSaveBtn = card.querySelector('button[data-action=save-brand]');
1180
- const brandResetBtn = card.querySelector('button[data-action=reset-brand]');
1181
- const brandStatusEl = card.querySelector('[data-brand-status]');
1182
- const brandStateEl = card.querySelector('[data-brand-state]');
1183
- // PUT the given brandLabel (string '' = off, null = revert to default),
1184
- // then reflect the new state inline without a full rerender.
1185
- async function putBrand(brandLabel, btn) {
1186
- if (!brandStatusEl)
1187
- return;
1188
- brandStatusEl.textContent = '';
1189
- brandStatusEl.className = 'oncall-status';
1190
- btn.disabled = true;
1191
- try {
1192
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/brand-label`, {
1193
- method: 'PUT',
1194
- headers: { 'content-type': 'application/json' },
1195
- body: JSON.stringify({ brandLabel }),
1196
- });
1197
- const body = await r.json().catch(() => ({}));
1198
- if (r.ok && body.ok) {
1199
- const next = body.brandLabel ?? null;
1200
- brandStatusEl.textContent = '✓';
1201
- brandStatusEl.classList.add('hint-ok');
1202
- if (brandInput)
1203
- brandInput.value = next ?? '';
1204
- if (brandStateEl)
1205
- brandStateEl.textContent = brandStateLabel(next);
1206
- const cached = cache.bots.find((b) => b.larkAppId === appId);
1207
- if (cached)
1208
- cached.brandLabel = next;
1209
- }
1210
- else {
1211
- brandStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1212
- brandStatusEl.classList.add('hint-warn-inline');
1213
- }
1214
- }
1215
- catch (e) {
1216
- brandStatusEl.textContent = `✗ ${e?.message ?? e}`;
1217
- brandStatusEl.classList.add('hint-warn-inline');
1218
- }
1219
- finally {
1220
- btn.disabled = false;
1221
- }
1222
- }
1223
- if (brandInput && brandSaveBtn) {
1224
- // Empty input saved as '' = brand off (per "配置为空就可以关").
1225
- brandSaveBtn.addEventListener('click', () => putBrand(brandInput.value, brandSaveBtn));
1226
- }
1227
- if (brandResetBtn) {
1228
- brandResetBtn.addEventListener('click', () => putBrand(null, brandResetBtn));
1229
- }
1230
- // ── 机器人改名(档案头 ✎ → 行内输入框)────────────────────────────────
1231
- // 主路径走飞书开放平台真改应用名(daemon 侧自动建版发布,约 5-10 秒);
1232
- // 失败自动降级为仅改 dashboard 展示名并明示原因。成功后就地更新卡片标题、
1233
- // 左侧名册与缓存,不整卡重绘(避免吹掉状态提示)。
1234
- const nameRowEl = card.querySelector('[data-name-row]');
1235
- const nameEditorEl = card.querySelector('[data-name-editor]');
1236
- const nameStrongEl = card.querySelector('[data-bot-name]');
1237
- const nameInputEl = card.querySelector('input[data-input=botRename]');
1238
- const nameEditBtn = card.querySelector('button[data-action=edit-bot-name]');
1239
- const nameSaveBtn = card.querySelector('button[data-action=save-bot-name]');
1240
- const nameCancelBtn = card.querySelector('button[data-action=cancel-bot-name]');
1241
- const nameStatusEl = card.querySelector('[data-name-status]');
1242
- const feishuLoginBtn = card.querySelector('button[data-action=feishu-login]');
1243
- function setNameEditMode(on) {
1244
- if (!nameRowEl || !nameEditorEl)
1245
- return;
1246
- nameRowEl.hidden = on;
1247
- nameEditorEl.hidden = !on;
1248
- if (on && nameInputEl) {
1249
- nameInputEl.value = nameStrongEl?.textContent ?? '';
1250
- nameInputEl.focus();
1251
- nameInputEl.select();
1252
- }
1253
- }
1254
- function renameWarningText(warning, message) {
1255
- const known = ['no_session', 'session_expired', 'no_access', 'unsupported_brand'];
1256
- const detail = known.includes(warning)
1257
- ? t(`botDefaults.renameWarn.${warning}`)
1258
- : (message || warning);
1259
- return t('botDefaults.renameLocalOnly', { reason: detail });
1260
- }
1261
- async function submitRename() {
1262
- if (!nameInputEl || !nameStatusEl)
1263
- return;
1264
- const name = nameInputEl.value.trim();
1265
- if (!name) {
1266
- nameStatusEl.textContent = `✗ ${t('botDefaults.renameEmpty')}`;
1267
- nameStatusEl.className = 'bd-name-status oncall-status hint-warn-inline';
1268
- return;
1269
- }
1270
- nameInputEl.disabled = true;
1271
- if (nameSaveBtn)
1272
- nameSaveBtn.disabled = true;
1273
- if (nameCancelBtn)
1274
- nameCancelBtn.disabled = true;
1275
- nameStatusEl.className = 'bd-name-status oncall-status';
1276
- nameStatusEl.textContent = `⏳ ${t('botDefaults.renaming')}`;
1277
- try {
1278
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/rename`, {
1279
- method: 'PUT',
1280
- headers: { 'content-type': 'application/json' },
1281
- body: JSON.stringify({ name }),
1282
- });
1283
- const body = await r.json().catch(() => ({}));
1284
- if (r.ok && body.ok) {
1285
- const effective = (typeof body.botName === 'string' && body.botName) ? body.botName : name;
1286
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1287
- if (cached) {
1288
- cached.botName = effective;
1289
- if (body.mode === 'feishu') {
1290
- cached.larkBotName = name;
1291
- cached.displayName = null;
1292
- }
1293
- else
1294
- cached.displayName = name;
1295
- }
1296
- if (nameStrongEl)
1297
- nameStrongEl.textContent = effective;
1298
- const rosterName = rosterEl.querySelector(`.bd-roster-item[data-appid="${CSS.escape(appId)}"] .bd-roster-tx b`);
1299
- if (rosterName)
1300
- rosterName.textContent = effective;
1301
- setNameEditMode(false);
1302
- if (body.mode === 'feishu') {
1303
- nameStatusEl.textContent = `✓ ${t('botDefaults.renameOkFeishu')}`;
1304
- nameStatusEl.classList.add('hint-ok');
1305
- if (feishuLoginBtn)
1306
- feishuLoginBtn.hidden = true;
1307
- }
1308
- else {
1309
- nameStatusEl.textContent = `⚠ ${renameWarningText(String(body.warning ?? ''), body.message)}`;
1310
- nameStatusEl.classList.add('hint-warn-inline');
1311
- if (typeof body.message === 'string' && body.message)
1312
- nameStatusEl.title = body.message;
1313
- // 登录态缺失/过期 → 亮出「扫码登录」,让用户当场刷登录态后重试真改名。
1314
- const needsLogin = body.warning === 'no_session' || body.warning === 'session_expired';
1315
- if (feishuLoginBtn)
1316
- feishuLoginBtn.hidden = !needsLogin;
1317
- }
1318
- }
1319
- else {
1320
- nameStatusEl.textContent = `✗ ${t('botDefaults.renameFailed', { error: String(body.error ?? r.status) })}`;
1321
- nameStatusEl.classList.add('hint-warn-inline');
1322
- }
1323
- }
1324
- catch (e) {
1325
- nameStatusEl.textContent = `✗ ${t('botDefaults.renameFailed', { error: e?.message ?? String(e) })}`;
1326
- nameStatusEl.classList.add('hint-warn-inline');
1327
- }
1328
- finally {
1329
- nameInputEl.disabled = false;
1330
- if (nameSaveBtn)
1331
- nameSaveBtn.disabled = false;
1332
- if (nameCancelBtn)
1333
- nameCancelBtn.disabled = false;
1334
- }
1335
- }
1336
- if (nameEditBtn) {
1337
- nameEditBtn.addEventListener('click', () => {
1338
- if (nameStatusEl) {
1339
- nameStatusEl.textContent = '';
1340
- nameStatusEl.className = 'bd-name-status oncall-status';
1341
- }
1342
- if (feishuLoginBtn)
1343
- feishuLoginBtn.hidden = true;
1344
- setNameEditMode(true);
1345
- });
1346
- }
1347
- // 「扫码登录飞书」→ 弹二维码 modal,扫码成功后自动重试真改名。
1348
- if (feishuLoginBtn) {
1349
- feishuLoginBtn.addEventListener('click', () => {
1350
- openFeishuLoginModal(() => {
1351
- feishuLoginBtn.hidden = true;
1352
- void submitRename();
1353
- });
1354
- });
1355
- }
1356
- if (nameCancelBtn)
1357
- nameCancelBtn.addEventListener('click', () => setNameEditMode(false));
1358
- if (nameSaveBtn)
1359
- nameSaveBtn.addEventListener('click', () => void submitRename());
1360
- if (nameInputEl) {
1361
- nameInputEl.addEventListener('keydown', (e) => {
1362
- if (e.key === 'Enter') {
1363
- e.preventDefault();
1364
- void submitRename();
1365
- }
1366
- else if (e.key === 'Escape')
1367
- setNameEditMode(false);
1368
- });
1369
- }
1370
- // ── Card behaviour toggles (auto-save on change) ──────────────────────
1371
- const disableStreamingCb = card.querySelector('input[data-action=toggle-disable-streaming]');
1372
- const silentReactionsCb = card.querySelector('input[data-action=toggle-silent-reactions]');
1373
- const writableLinkCb = card.querySelector('input[data-action=toggle-writable-link]');
1374
- const privateCardCb = card.querySelector('input[data-action=toggle-private-card]');
1375
- const cardPrefStatusEl = card.querySelector('[data-card-pref-status]');
1376
- const cardPrefMootEl = card.querySelector('[data-card-pref-moot]');
1377
- // PUT a partial card-prefs patch (booleans and/or the auto-start prompt
1378
- // string). `selfEl` is the control that triggered it (disabled during the
1379
- // request to block double-submit); `statusEl` is where the result toast
1380
- // lands (defaults to the card-behaviour status line).
1381
- async function putCardPref(patch, selfEl, statusEl = cardPrefStatusEl) {
1382
- if (!statusEl)
1383
- return;
1384
- statusEl.textContent = '';
1385
- statusEl.className = 'oncall-status';
1386
- selfEl.disabled = true;
1387
- try {
1388
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/card-prefs`, {
1389
- method: 'PUT',
1390
- headers: { 'content-type': 'application/json' },
1391
- body: JSON.stringify(patch),
1392
- });
1393
- const body = await r.json().catch(() => ({}));
1394
- if (r.ok && body.ok) {
1395
- statusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1396
- statusEl.classList.add('hint-ok');
1397
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1398
- if (cached) {
1399
- cached.disableStreamingCard = body.disableStreamingCard;
1400
- cached.silentTurnReactions = body.silentTurnReactions;
1401
- cached.writableTerminalLinkInCard = body.writableTerminalLinkInCard;
1402
- cached.privateCard = body.privateCard;
1403
- cached.botToBotSameDir = body.botToBotSameDir;
1404
- cached.autoStartOnGroupJoin = body.autoStartOnGroupJoin;
1405
- cached.autoStartOnGroupJoinPrompt = body.autoStartOnGroupJoinPrompt;
1406
- cached.autoStartOnNewTopic = body.autoStartOnNewTopic;
1407
- cached.regularGroupReplyMode = body.regularGroupReplyMode;
1408
- cached.regularGroupMentionMode = body.regularGroupMentionMode;
1409
- cached.docSubscribeDefaultMode = body.docSubscribeDefaultMode;
1410
- }
1411
- }
1412
- else {
1413
- statusEl.textContent = `✗ ${body.error ?? r.status}`;
1414
- statusEl.classList.add('hint-warn-inline');
1415
- }
1416
- }
1417
- catch (e) {
1418
- statusEl.textContent = `✗ ${e?.message ?? e}`;
1419
- statusEl.classList.add('hint-warn-inline');
1420
- }
1421
- finally {
1422
- // The writable-link checkbox stays disabled while streaming is off.
1423
- if (selfEl === writableLinkCb)
1424
- selfEl.disabled = !!disableStreamingCb?.checked;
1425
- // The status-reactions checkbox is only editable while streaming is off.
1426
- else if (selfEl === silentReactionsCb)
1427
- selfEl.disabled = !disableStreamingCb?.checked;
1428
- else
1429
- selfEl.disabled = false;
1430
- }
1431
- }
1432
- if (disableStreamingCb) {
1433
- disableStreamingCb.addEventListener('change', () => {
1434
- const off = disableStreamingCb.checked;
1435
- // Streaming off → the writable-link toggle has nothing to attach to.
1436
- if (writableLinkCb)
1437
- writableLinkCb.disabled = off;
1438
- // Status reactions only exist in card-off sessions, so this toggle is
1439
- // editable only while streaming is off.
1440
- if (silentReactionsCb)
1441
- silentReactionsCb.disabled = !off;
1442
- if (cardPrefMootEl)
1443
- cardPrefMootEl.hidden = !off;
1444
- putCardPref({ disableStreamingCard: off }, disableStreamingCb);
1445
- });
1446
- }
1447
- if (silentReactionsCb) {
1448
- silentReactionsCb.addEventListener('change', () => {
1449
- putCardPref({ silentTurnReactions: silentReactionsCb.checked }, silentReactionsCb);
1450
- });
1451
- }
1452
- if (writableLinkCb) {
1453
- writableLinkCb.addEventListener('change', () => {
1454
- putCardPref({ writableTerminalLinkInCard: writableLinkCb.checked }, writableLinkCb);
1455
- });
1456
- }
1457
- if (privateCardCb) {
1458
- privateCardCb.addEventListener('change', () => {
1459
- putCardPref({ privateCard: privateCardCb.checked }, privateCardCb);
1460
- });
1461
- }
1462
- const crossBotCb = card.querySelector('input[data-action=toggle-cross-bot-samedir]');
1463
- const crossBotStatusEl = card.querySelector('[data-crossbot-status]');
1464
- if (crossBotCb) {
1465
- crossBotCb.addEventListener('change', () => {
1466
- putCardPref({ botToBotSameDir: crossBotCb.checked }, crossBotCb, crossBotStatusEl);
1467
- });
1468
- }
1469
- // ── /summary 总结范围 ───────────────────────────────────────────────
1470
- const summaryLimitInput = card.querySelector('input[data-input=summaryLimit]');
1471
- const summarySinceInput = card.querySelector('input[data-input=summarySinceHours]');
1472
- const summarySaveBtn = card.querySelector('button[data-action=save-summary-trigger]');
1473
- const summaryStatusEl = card.querySelector('[data-summary-trigger-status]');
1474
- function readNonNegativeInt(input, fallback) {
1475
- const raw = input.value.trim();
1476
- if (raw === '')
1477
- return fallback;
1478
- if (!/^(0|[1-9]\d*)$/.test(raw))
1479
- return null;
1480
- return Number(raw);
1481
- }
1482
- if (summaryLimitInput && summarySinceInput && summarySaveBtn) {
1483
- summarySaveBtn.addEventListener('click', async () => {
1484
- if (!summaryStatusEl)
1485
- return;
1486
- summaryStatusEl.textContent = '';
1487
- summaryStatusEl.className = 'oncall-status';
1488
- const limit = readNonNegativeInt(summaryLimitInput, 50);
1489
- const sinceHours = readNonNegativeInt(summarySinceInput, 24);
1490
- if (limit == null || sinceHours == null) {
1491
- summaryStatusEl.textContent = `✗ ${t('botDefaults.summaryNumberInvalid')}`;
1492
- summaryStatusEl.classList.add('hint-warn-inline');
1493
- return;
1494
- }
1495
- summarySaveBtn.disabled = true;
1496
- try {
1497
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/summary-range`, {
1498
- method: 'PUT',
1499
- headers: { 'content-type': 'application/json' },
1500
- body: JSON.stringify({
1501
- limit,
1502
- sinceHours,
1503
- }),
1504
- });
1505
- const body = await r.json().catch(() => ({}));
1506
- if (r.ok && body.ok) {
1507
- summaryStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1508
- summaryStatusEl.classList.add('hint-ok');
1509
- const next = body.summaryRange ?? { limit, sinceHours };
1510
- summaryLimitInput.value = String(Number.isInteger(next.limit) && next.limit >= 0 ? next.limit : limit);
1511
- summarySinceInput.value = String(Number.isInteger(next.sinceHours) && next.sinceHours >= 0 ? next.sinceHours : sinceHours);
1512
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1513
- if (cached)
1514
- cached.summaryRange = next;
1515
- }
1516
- else {
1517
- summaryStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1518
- summaryStatusEl.classList.add('hint-warn-inline');
1519
- }
1520
- }
1521
- catch (e) {
1522
- summaryStatusEl.textContent = `✗ ${e?.message ?? e}`;
1523
- summaryStatusEl.classList.add('hint-warn-inline');
1524
- }
1525
- finally {
1526
- summarySaveBtn.disabled = false;
1527
- }
1528
- });
1529
- }
1530
- // ── File sandbox toggle (auto-save on change) ─────────────────────────
1531
- const sandboxCb = card.querySelector('input[data-action=toggle-sandbox]');
1532
- const sandboxStatusEl = card.querySelector('[data-sandbox-status]');
1533
- if (sandboxCb) {
1534
- sandboxCb.addEventListener('change', async () => {
1535
- const enabled = sandboxCb.checked;
1536
- if (sandboxStatusEl) {
1537
- sandboxStatusEl.textContent = '';
1538
- sandboxStatusEl.className = 'oncall-status';
1539
- }
1540
- sandboxCb.disabled = true;
1541
- try {
1542
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/sandbox`, {
1543
- method: 'PUT',
1544
- headers: { 'content-type': 'application/json' },
1545
- body: JSON.stringify({ enabled }),
1546
- });
1547
- const body = await r.json().catch(() => ({}));
1548
- if (r.ok && body.ok) {
1549
- if (sandboxStatusEl) {
1550
- sandboxStatusEl.textContent = `✓ ${t('botDefaults.sandboxSaved')}`;
1551
- sandboxStatusEl.classList.add('hint-ok');
1552
- }
1553
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1554
- if (cached)
1555
- cached.sandbox = body.sandbox === true;
1556
- }
1557
- else {
1558
- if (sandboxStatusEl) {
1559
- sandboxStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1560
- sandboxStatusEl.classList.add('hint-warn-inline');
1561
- }
1562
- sandboxCb.checked = !enabled; // revert on failure
1563
- }
1564
- }
1565
- catch (e) {
1566
- if (sandboxStatusEl) {
1567
- sandboxStatusEl.textContent = `✗ ${e?.message ?? e}`;
1568
- sandboxStatusEl.classList.add('hint-warn-inline');
1569
- }
1570
- sandboxCb.checked = !enabled;
1571
- }
1572
- finally {
1573
- sandboxCb.disabled = false;
1574
- }
1575
- });
1576
- }
1577
- // ── 主动开工 toggles + 场景① prompt ───────────────────────────────────
1578
- const autoJoinCb = card.querySelector('input[data-action=toggle-auto-join]');
1579
- const autoTopicCb = card.querySelector('input[data-action=toggle-auto-topic]');
1580
- const autoJoinPromptEl = card.querySelector('textarea[data-input=autoJoinPrompt]');
1581
- const autoJoinPromptSaveBtn = card.querySelector('button[data-action=save-auto-join-prompt]');
1582
- const autoStartStatusEl = card.querySelector('[data-auto-start-status]');
1583
- if (autoJoinCb) {
1584
- autoJoinCb.addEventListener('change', () => {
1585
- putCardPref({ autoStartOnGroupJoin: autoJoinCb.checked }, autoJoinCb, autoStartStatusEl);
1586
- });
1587
- }
1588
- if (autoTopicCb) {
1589
- autoTopicCb.addEventListener('change', () => {
1590
- putCardPref({ autoStartOnNewTopic: autoTopicCb.checked }, autoTopicCb, autoStartStatusEl);
1591
- });
1592
- }
1593
- if (autoJoinPromptEl && autoJoinPromptSaveBtn) {
1594
- autoJoinPromptSaveBtn.addEventListener('click', () => {
1595
- putCardPref({ autoStartOnGroupJoinPrompt: autoJoinPromptEl.value }, autoJoinPromptSaveBtn, autoStartStatusEl);
1596
- });
1597
- }
1598
- // ── 私聊单聊模式 p2pMode select ───────────────────────────────────────
1599
- const p2pModeSel = card.querySelector('select[data-input=p2pMode]');
1600
- const p2pStatusEl = card.querySelector('[data-p2p-status]');
1601
- if (p2pModeSel && p2pStatusEl) {
1602
- p2pModeSel.addEventListener('change', async () => {
1603
- const mode = p2pModeSel.value === 'chat' ? 'chat' : 'thread';
1604
- p2pStatusEl.textContent = '';
1605
- p2pStatusEl.className = 'oncall-status';
1606
- p2pModeSel.disabled = true;
1607
- try {
1608
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/p2p-mode`, {
1609
- method: 'PUT',
1610
- headers: { 'content-type': 'application/json' },
1611
- body: JSON.stringify({ p2pMode: mode }),
1612
- });
1613
- const body = await r.json().catch(() => ({}));
1614
- if (r.ok && body.ok) {
1615
- p2pStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1616
- p2pStatusEl.classList.add('hint-ok');
1617
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1618
- if (cached)
1619
- cached.p2pMode = body.p2pMode === 'chat' ? 'chat' : 'thread';
1620
- }
1621
- else {
1622
- p2pStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1623
- p2pStatusEl.classList.add('hint-warn-inline');
1624
- }
1625
- }
1626
- catch (e) {
1627
- p2pStatusEl.textContent = `✗ ${e?.message ?? e}`;
1628
- p2pStatusEl.classList.add('hint-warn-inline');
1629
- }
1630
- finally {
1631
- p2pModeSel.disabled = false;
1632
- }
1633
- });
1634
- }
1635
- // ── 内置技能注入模式 skillInjection select ────────────────────────────
1636
- // '' = 清回机器级默认(botmux skills injection);global|prompt|off 显式覆盖。
1637
- // 走 /api/bots/:appId/skill-injection → applyConfigField(与 /config 同路径)。
1638
- const skillInjSel = card.querySelector('select[data-input=skillInjection]');
1639
- const skillInjStatusEl = card.querySelector('[data-skill-injection-status]');
1640
- if (skillInjSel && skillInjStatusEl) {
1641
- skillInjSel.addEventListener('change', async () => {
1642
- const mode = skillInjSel.value; // '' | 'global' | 'prompt' | 'off'
1643
- skillInjStatusEl.textContent = '';
1644
- skillInjStatusEl.className = 'oncall-status';
1645
- skillInjSel.disabled = true;
1646
- try {
1647
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/skill-injection`, {
1648
- method: 'PUT',
1649
- headers: { 'content-type': 'application/json' },
1650
- body: JSON.stringify({ skillInjection: mode }),
1651
- });
1652
- const body = await r.json().catch(() => ({}));
1653
- if (r.ok && body.ok) {
1654
- skillInjStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1655
- skillInjStatusEl.classList.add('hint-ok');
1656
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1657
- if (cached)
1658
- cached.skillInjection = body.skillInjection ?? null;
1659
- }
1660
- else {
1661
- skillInjStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1662
- skillInjStatusEl.classList.add('hint-warn-inline');
1663
- }
1664
- }
1665
- catch (e) {
1666
- skillInjStatusEl.textContent = `✗ ${e?.message ?? e}`;
1667
- skillInjStatusEl.classList.add('hint-warn-inline');
1668
- }
1669
- finally {
1670
- skillInjSel.disabled = false;
1671
- }
1672
- });
1673
- }
1674
- // ── 普通群默认会话模式 regularGroupReplyMode select ─────────────────────
1675
- // chat = 整群一个连续会话(默认);new-topic = 每条顶层 @ 开独立话题;
1676
- // shared = 话题模式但复用同一个 session。走 card-prefs 路径。
1677
- const regularGroupModeSel = card.querySelector('select[data-input=regularGroupMode]');
1678
- const regularGroupStatusEl = card.querySelector('[data-regular-group-status]');
1679
- if (regularGroupModeSel) {
1680
- regularGroupModeSel.addEventListener('change', () => {
1681
- putCardPref({ regularGroupReplyMode: regularGroupModeSel.value }, regularGroupModeSel, regularGroupStatusEl);
1682
- });
1683
- }
1684
- // ── 群聊 @ 策略三档(bot-global)──────────────────────────────────────
1685
- // always = 都需要 @(默认);topic = 仅 shared 话题内免 @;never = 都不需要 @。
1686
- const mentionModeSel = card.querySelector('select[data-input=regularGroupMentionMode]');
1687
- const mentionModeStatusEl = card.querySelector('[data-mention-mode-status]');
1688
- if (mentionModeSel) {
1689
- mentionModeSel.addEventListener('change', () => {
1690
- putCardPref({ regularGroupMentionMode: mentionModeSel.value }, mentionModeSel, mentionModeStatusEl);
1691
- });
1692
- }
1693
- // ── 文档订阅默认触发范围(bot-global)─────────────────────────────────
1694
- // mention-only = 仅评论 @ 我才触发(默认);all = 所有新评论都触发。
1695
- const docModeSel = card.querySelector('select[data-input=docSubscribeDefaultMode]');
1696
- const docModeStatusEl = card.querySelector('[data-doc-subscribe-mode-status]');
1697
- if (docModeSel) {
1698
- docModeSel.addEventListener('change', () => {
1699
- putCardPref({ docSubscribeDefaultMode: docModeSel.value }, docModeSel, docModeStatusEl);
1700
- });
1701
- }
1702
- // ── 替身模式 substituteMode ─────────────────────────────────────────
1703
- const substituteEnabledCb = card.querySelector('input[data-action=toggle-substitute-mode]');
1704
- const substituteDisclosureSel = card.querySelector('select[data-input=substituteDisclosure]');
1705
- const substituteTargetsTa = card.querySelector('textarea[data-input=substituteTargets]');
1706
- const substituteSaveBtn = card.querySelector('button[data-action=save-substitute-mode]');
1707
- const substituteOffBtn = card.querySelector('button[data-action=off-substitute-mode]');
1708
- const substituteStatusEl = card.querySelector('[data-substitute-status]');
1709
- const substituteChipsEl = card.querySelector('[data-substitute-chips]');
1710
- async function putSubstituteMode(body, btn) {
1711
- if (!substituteStatusEl)
1712
- return;
1713
- substituteStatusEl.textContent = t('botDefaults.substituteResolving');
1714
- substituteStatusEl.className = 'oncall-status';
1715
- btn.disabled = true;
1716
- try {
1717
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/substitute-mode`, {
1718
- method: 'PUT',
1719
- headers: { 'content-type': 'application/json' },
1720
- body: JSON.stringify(body),
1721
- });
1722
- const resp = await r.json().catch(() => ({}));
1723
- // Resolution chips render on success AND failure so the user can always
1724
- // see which entries resolved to a person and which didn't.
1725
- if (substituteChipsEl)
1726
- substituteChipsEl.innerHTML = substituteChipsFromResolution(resp.resolution);
1727
- if (r.ok && resp.ok) {
1728
- substituteStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1729
- substituteStatusEl.classList.add('hint-ok');
1730
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1731
- if (cached)
1732
- cached.substituteMode = resp.substituteMode ?? null;
1733
- if (substituteEnabledCb)
1734
- substituteEnabledCb.checked = !!resp.substituteMode?.enabled;
1735
- if (substituteDisclosureSel)
1736
- substituteDisclosureSel.value = resp.substituteMode?.disclosure === 'none' ? 'none' : 'prefix';
1737
- // Re-render the editor from the resolution report (not the stored
1738
- // config): a disabled save keeps the list, and failed entries stay
1739
- // visible so they can be corrected instead of silently vanishing.
1740
- if (substituteTargetsTa)
1741
- substituteTargetsTa.value = editorTextFromResolution(resp.resolution);
1742
- }
1743
- else {
1744
- const reason = resp.error === 'targets_required'
1745
- ? t('botDefaults.substituteTargetsRequired')
1746
- : (resp.error ?? r.status);
1747
- substituteStatusEl.textContent = `✗ ${reason}`;
1748
- substituteStatusEl.classList.add('hint-warn-inline');
1749
- }
1750
- }
1751
- catch (e) {
1752
- substituteStatusEl.textContent = `✗ ${e?.message ?? e}`;
1753
- substituteStatusEl.classList.add('hint-warn-inline');
1754
- }
1755
- finally {
1756
- btn.disabled = false;
1757
- }
1758
- }
1759
- if (substituteSaveBtn && substituteTargetsTa && substituteDisclosureSel && substituteEnabledCb) {
1760
- substituteSaveBtn.addEventListener('click', () => {
1761
- if (!substituteStatusEl)
1762
- return;
1763
- const { targets, invalid } = parseSubstituteTargets(substituteTargetsTa.value);
1764
- if (invalid.length) {
1765
- substituteStatusEl.textContent = `✗ ${t('botDefaults.substituteTargetsInvalid')}: ${invalid.join('; ')}`;
1766
- substituteStatusEl.className = 'oncall-status hint-warn-inline';
1767
- return;
1768
- }
1769
- putSubstituteMode({
1770
- enabled: substituteEnabledCb.checked,
1771
- targets,
1772
- disclosure: substituteDisclosureSel.value === 'none' ? 'none' : 'prefix',
1773
- }, substituteSaveBtn);
1774
- });
1775
- }
1776
- if (substituteOffBtn) {
1777
- substituteOffBtn.addEventListener('click', () => {
1778
- putSubstituteMode({ enabled: false, targets: [] }, substituteOffBtn);
1779
- });
1780
- }
1781
- // ── Team role (one role per bot, cross-chat) ──────────────────────────
1782
- const roleTextarea = card.querySelector('textarea[data-input=teamRole]');
1783
- const roleSaveBtn = card.querySelector('button[data-action=save-role]');
1784
- const roleDeleteBtn = card.querySelector('button[data-action=delete-role]');
1785
- const roleStatusEl = card.querySelector('[data-role-status]');
1786
- if (roleTextarea && roleSaveBtn && roleDeleteBtn && roleStatusEl) {
1787
- const roleUrl = `/api/team/local-bots/${encodeURIComponent(appId)}/role`;
1788
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1789
- // Until the role is loaded, the textarea AND both buttons render
1790
- // disabled. This is load-bearing: an empty not-yet-loaded textarea
1791
- // saved as "" is treated as a DELETE by the server (federation-spoke-api
1792
- // role PUT), so a mis-click during a slow load would silently wipe an
1793
- // existing role. We only enable the editor once GET has returned.
1794
- function enableLiveEditor(value) {
1795
- const live = listEl.querySelector(`.bd-card[data-appid="${CSS.escape(appId)}"]`);
1796
- if (!live)
1797
- return; // filtered out by search — next render draws it enabled from cache
1798
- const ta = live.querySelector('textarea[data-input=teamRole]');
1799
- const sv = live.querySelector('button[data-action=save-role]');
1800
- const dl = live.querySelector('button[data-action=delete-role]');
1801
- if (ta) {
1802
- ta.value = value;
1803
- ta.disabled = false;
1804
- }
1805
- if (sv)
1806
- sv.disabled = false;
1807
- if (dl)
1808
- dl.disabled = false;
1809
- }
1810
- // Lazily load the role ONCE per bot, then stash it onto the snapshot
1811
- // (cached.teamRole) so later re-renders — one per search keystroke —
1812
- // render from cache instead of re-fetching. The teamRoleLoading sentinel
1813
- // guards against a re-render firing a second concurrent GET while the
1814
- // first is still in flight. enableLiveEditor re-queries the *current*
1815
- // DOM so a mid-load re-render doesn't leave a stale (detached) textarea
1816
- // stuck disabled.
1817
- if (cached && typeof cached.teamRole !== 'string' && !cached.teamRoleLoading) {
1818
- cached.teamRoleLoading = true;
1819
- (async () => {
1820
- try {
1821
- const r = await fetch(roleUrl);
1822
- const body = await r.json().catch(() => ({}));
1823
- if (r.ok && body.ok) {
1824
- cached.teamRole = body.role ?? '';
1825
- enableLiveEditor(cached.teamRole);
1826
- }
1827
- else {
1828
- roleStatusEl.textContent = `✗ ${t('botDefaults.roleLoadErr')}: ${body.error ?? r.status}`;
1829
- roleStatusEl.classList.add('hint-warn-inline');
1830
- }
1831
- }
1832
- catch (e) {
1833
- roleStatusEl.textContent = `✗ ${t('botDefaults.roleLoadErr')}: ${e?.message ?? e}`;
1834
- roleStatusEl.classList.add('hint-warn-inline');
1835
- }
1836
- finally {
1837
- cached.teamRoleLoading = false;
1838
- }
1839
- })();
1840
- }
1841
- // PUT the role ('' = delete on the server). `deleted` picks the success
1842
- // toast; both buttons share this path. Server trims + deletes on empty,
1843
- // so we mirror the stored value into the cache for consistent re-renders.
1844
- async function putRole(role, btn, deleted) {
1845
- if (!roleStatusEl)
1846
- return;
1847
- // Defense-in-depth: never PUT before the role is loaded (would risk a
1848
- // ""-as-delete). The buttons render disabled until then, but guard the
1849
- // entry too in case of a stale handler firing.
1850
- if (!cached || typeof cached.teamRole !== 'string')
1851
- return;
1852
- roleStatusEl.textContent = '';
1853
- roleStatusEl.className = 'oncall-status';
1854
- roleSaveBtn.disabled = true;
1855
- roleDeleteBtn.disabled = true;
1856
- try {
1857
- const r = await fetch(roleUrl, {
1858
- method: 'PUT',
1859
- headers: { 'content-type': 'application/json' },
1860
- body: JSON.stringify({ role }),
1861
- });
1862
- const body = await r.json().catch(() => ({}));
1863
- if (r.ok && body.ok) {
1864
- if (cached)
1865
- cached.teamRole = role.trim();
1866
- roleStatusEl.textContent = `✓ ${deleted ? t('botDefaults.roleDeleted') : t('botDefaults.roleSaved')}`;
1867
- roleStatusEl.classList.add('hint-ok');
1868
- }
1869
- else {
1870
- roleStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1871
- roleStatusEl.classList.add('hint-warn-inline');
1872
- }
1873
- }
1874
- catch (e) {
1875
- roleStatusEl.textContent = `✗ ${e?.message ?? e}`;
1876
- roleStatusEl.classList.add('hint-warn-inline');
1877
- }
1878
- finally {
1879
- roleSaveBtn.disabled = false;
1880
- roleDeleteBtn.disabled = false;
1881
- }
1882
- }
1883
- roleSaveBtn.addEventListener('click', () => putRole(roleTextarea.value, roleSaveBtn, false));
1884
- roleDeleteBtn.addEventListener('click', () => {
1885
- roleTextarea.value = '';
1886
- putRole('', roleDeleteBtn, true);
1887
- });
1888
- }
1889
- // ── 授权偏好:自动申请卡 + 命令限制开关 + 默认消息额度 ──────────────
1890
- const autoGrantCardCb = card.querySelector('input[data-action=toggle-auto-grant-card]');
1891
- const restrictCb = card.querySelector('input[data-action=toggle-restrict-grant]');
1892
- const quotaInput = card.querySelector('input[data-input=quotaLimit]');
1893
- const quotaSaveBtn = card.querySelector('button[data-action=save-quota]');
1894
- const quotaOffBtn = card.querySelector('button[data-action=off-quota]');
1895
- const grantStatusEl = card.querySelector('[data-grant-status]');
1896
- const quotaStateEl = card.querySelector('[data-quota-state]');
1897
- // PUT a partial grant-prefs patch ({ autoGrantRequestCards? },
1898
- // { restrictGrantCommands? } and/or { messageQuotaDefaultLimit: number|null }).
1899
- // Mirrors putCardPref.
1900
- async function putGrantPref(patch, selfEl) {
1901
- if (!grantStatusEl)
1902
- return;
1903
- grantStatusEl.textContent = '';
1904
- grantStatusEl.className = 'oncall-status';
1905
- selfEl.disabled = true;
1906
- try {
1907
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/grant-prefs`, {
1908
- method: 'PUT',
1909
- headers: { 'content-type': 'application/json' },
1910
- body: JSON.stringify(patch),
1911
- });
1912
- const body = await r.json().catch(() => ({}));
1913
- if (r.ok && body.ok) {
1914
- grantStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1915
- grantStatusEl.classList.add('hint-ok');
1916
- const next = typeof body.messageQuotaDefaultLimit === 'number' ? body.messageQuotaDefaultLimit : null;
1917
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
1918
- if (cached) {
1919
- cached.autoGrantRequestCards = body.autoGrantRequestCards !== false;
1920
- cached.restrictGrantCommands = body.restrictGrantCommands === true;
1921
- cached.messageQuotaDefaultLimit = next;
1922
- }
1923
- if (quotaStateEl)
1924
- quotaStateEl.textContent = quotaStateLabel(next);
1925
- if (quotaInput && 'messageQuotaDefaultLimit' in patch) {
1926
- quotaInput.value = next == null ? '' : String(next);
1927
- }
1928
- }
1929
- else {
1930
- grantStatusEl.textContent = `✗ ${body.error ?? r.status}`;
1931
- grantStatusEl.classList.add('hint-warn-inline');
1932
- }
1933
- }
1934
- catch (e) {
1935
- grantStatusEl.textContent = `✗ ${e?.message ?? e}`;
1936
- grantStatusEl.classList.add('hint-warn-inline');
1937
- }
1938
- finally {
1939
- selfEl.disabled = false;
1940
- }
1941
- }
1942
- if (autoGrantCardCb) {
1943
- autoGrantCardCb.addEventListener('change', () => {
1944
- putGrantPref({ autoGrantRequestCards: autoGrantCardCb.checked }, autoGrantCardCb);
1945
- });
1946
- }
1947
- if (restrictCb) {
1948
- restrictCb.addEventListener('change', () => {
1949
- putGrantPref({ restrictGrantCommands: restrictCb.checked }, restrictCb);
1950
- });
1951
- }
1952
- if (quotaInput && quotaSaveBtn) {
1953
- quotaSaveBtn.addEventListener('click', () => {
1954
- const raw = quotaInput.value.trim();
1955
- if (raw === '') {
1956
- putGrantPref({ messageQuotaDefaultLimit: null }, quotaSaveBtn);
1957
- return;
1958
- } // 空=关闭
1959
- // 只认纯正整数 token(拒 1e2 / 1.0 / 01),与 /grant @x N 的数字语义一致。
1960
- if (!/^[1-9]\d*$/.test(raw)) {
1961
- if (grantStatusEl) {
1962
- grantStatusEl.textContent = `✗ ${t('botDefaults.quotaInvalid')}`;
1963
- grantStatusEl.className = 'oncall-status hint-warn-inline';
1964
- }
1965
- return;
1966
- }
1967
- putGrantPref({ messageQuotaDefaultLimit: Number(raw) }, quotaSaveBtn);
1968
- });
1969
- }
1970
- if (quotaInput && quotaOffBtn) {
1971
- quotaOffBtn.addEventListener('click', () => {
1972
- quotaInput.value = '';
1973
- putGrantPref({ messageQuotaDefaultLimit: null }, quotaOffBtn);
1974
- });
1975
- }
1976
- // ── 最大同时活跃会话数 maxLiveWorkers(空=回落默认 30) ──────────────────
1977
- const capInput = card.querySelector('input[data-input=maxLiveWorkers]');
1978
- const capSaveBtn = card.querySelector('button[data-action=save-session-cap]');
1979
- const capOffBtn = card.querySelector('button[data-action=off-session-cap]');
1980
- const capStatusEl = card.querySelector('[data-session-cap-status]');
1981
- const capStateEl = card.querySelector('[data-session-cap-state]');
1982
- // PUT { maxLiveWorkers: number | null } to the bot's daemon (via the
1983
- // dashboard proxy). null = unlimited. Mirrors putGrantPref.
1984
- async function putMaxLiveWorkers(value, selfEl) {
1985
- if (!capStatusEl)
1986
- return;
1987
- capStatusEl.textContent = '';
1988
- capStatusEl.className = 'oncall-status';
1989
- selfEl.disabled = true;
1990
- try {
1991
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/max-live-workers`, {
1992
- method: 'PUT',
1993
- headers: { 'content-type': 'application/json' },
1994
- body: JSON.stringify({ maxLiveWorkers: value }),
1995
- });
1996
- const body = await r.json().catch(() => ({}));
1997
- if (r.ok && body.ok) {
1998
- capStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
1999
- capStatusEl.classList.add('hint-ok');
2000
- const next = typeof body.maxLiveWorkers === 'number' ? body.maxLiveWorkers : null;
2001
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
2002
- if (cached)
2003
- cached.maxLiveWorkers = next;
2004
- if (capStateEl)
2005
- capStateEl.textContent = sessionCapStateLabel(next);
2006
- if (capInput)
2007
- capInput.value = next == null ? '' : String(next);
2008
- }
2009
- else {
2010
- capStatusEl.textContent = `✗ ${body.error ?? r.status}`;
2011
- capStatusEl.classList.add('hint-warn-inline');
2012
- }
2013
- }
2014
- catch (e) {
2015
- capStatusEl.textContent = `✗ ${e?.message ?? e}`;
2016
- capStatusEl.classList.add('hint-warn-inline');
2017
- }
2018
- finally {
2019
- selfEl.disabled = false;
2020
- }
2021
- }
2022
- if (capInput && capSaveBtn) {
2023
- capSaveBtn.addEventListener('click', () => {
2024
- const raw = capInput.value.trim();
2025
- if (raw === '') {
2026
- putMaxLiveWorkers(null, capSaveBtn);
2027
- return;
2028
- } // 空=清回默认 30
2029
- // 只认纯正整数 token(拒 1e2 / 1.0 / 01),与额度输入同口径。
2030
- if (!/^[1-9]\d*$/.test(raw)) {
2031
- if (capStatusEl) {
2032
- capStatusEl.textContent = `✗ ${t('botDefaults.maxLiveWorkersInvalid')}`;
2033
- capStatusEl.className = 'oncall-status hint-warn-inline';
2034
- }
2035
- return;
2036
- }
2037
- putMaxLiveWorkers(Number(raw), capSaveBtn);
2038
- });
2039
- }
2040
- if (capInput && capOffBtn) {
2041
- capOffBtn.addEventListener('click', () => {
2042
- capInput.value = '';
2043
- putMaxLiveWorkers(null, capOffBtn);
2044
- });
2045
- }
2046
- // ── 启动命令 startupCommands(逗号/换行分隔;空=清除,不发任何命令) ──────────
2047
- const startupEl = card.querySelector('textarea[data-input=startupCommands]');
2048
- const startupSaveBtn = card.querySelector('button[data-action=save-startup-commands]');
2049
- const startupStatusEl = card.querySelector('[data-startup-commands-status]');
2050
- if (startupEl && startupSaveBtn) {
2051
- startupSaveBtn.addEventListener('click', async () => {
2052
- if (!startupStatusEl)
2053
- return;
2054
- startupStatusEl.textContent = '';
2055
- startupStatusEl.className = 'oncall-status';
2056
- startupSaveBtn.disabled = true;
2057
- try {
2058
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/startup-commands`, {
2059
- method: 'PUT',
2060
- headers: { 'content-type': 'application/json' },
2061
- body: JSON.stringify({ startupCommands: startupEl.value }),
2062
- });
2063
- const body = await r.json().catch(() => ({}));
2064
- if (r.ok && body.ok) {
2065
- startupStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
2066
- startupStatusEl.classList.add('hint-ok');
2067
- // Server returns the normalized, newline-joined list — reflect it
2068
- // back so the textarea shows exactly what was persisted.
2069
- const next = typeof body.startupCommands === 'string' ? body.startupCommands : '';
2070
- startupEl.value = next;
2071
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
2072
- if (cached)
2073
- cached.startupCommands = next;
2074
- }
2075
- else {
2076
- startupStatusEl.textContent = `✗ ${body.error ?? r.status}`;
2077
- startupStatusEl.classList.add('hint-warn-inline');
2078
- }
2079
- }
2080
- catch (e) {
2081
- startupStatusEl.textContent = `✗ ${e?.message ?? e}`;
2082
- startupStatusEl.classList.add('hint-warn-inline');
2083
- }
2084
- finally {
2085
- startupSaveBtn.disabled = false;
2086
- }
2087
- });
2088
- }
2089
- // ── 启动 shell launchShell(shell 名或绝对路径;空=清除→回 $SHELL) ──────
2090
- const launchShellEl = card.querySelector('input[data-input=launchShell]');
2091
- const launchShellSaveBtn = card.querySelector('button[data-action=save-launch-shell]');
2092
- const launchShellStatusEl = card.querySelector('[data-launch-shell-status]');
2093
- if (launchShellEl && launchShellSaveBtn) {
2094
- launchShellSaveBtn.addEventListener('click', async () => {
2095
- if (!launchShellStatusEl)
2096
- return;
2097
- launchShellStatusEl.textContent = '';
2098
- launchShellStatusEl.className = 'oncall-status';
2099
- launchShellSaveBtn.disabled = true;
2100
- try {
2101
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/launch-shell`, {
2102
- method: 'PUT',
2103
- headers: { 'content-type': 'application/json' },
2104
- body: JSON.stringify({ launchShell: launchShellEl.value }),
2105
- });
2106
- const body = await r.json().catch(() => ({}));
2107
- if (r.ok && body.ok) {
2108
- launchShellStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
2109
- launchShellStatusEl.classList.add('hint-ok');
2110
- const next = typeof body.launchShell === 'string' ? body.launchShell : '';
2111
- launchShellEl.value = next;
2112
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
2113
- if (cached)
2114
- cached.launchShell = next;
2115
- }
2116
- else {
2117
- launchShellStatusEl.textContent = `✗ ${body.error ?? r.status}`;
2118
- launchShellStatusEl.classList.add('hint-warn-inline');
2119
- }
2120
- }
2121
- catch (e) {
2122
- launchShellStatusEl.textContent = `✗ ${e?.message ?? e}`;
2123
- launchShellStatusEl.classList.add('hint-warn-inline');
2124
- }
2125
- finally {
2126
- launchShellSaveBtn.disabled = false;
2127
- }
2128
- });
2129
- }
2130
- // ── 环境变量 env(JSON 对象;空=清除) ──────────────────────────────
2131
- const envEl = card.querySelector('textarea[data-input=env]');
2132
- const envSaveBtn = card.querySelector('button[data-action=save-env]');
2133
- const envStatusEl = card.querySelector('[data-env-status]');
2134
- if (envEl && envSaveBtn) {
2135
- envSaveBtn.addEventListener('click', async () => {
2136
- if (!envStatusEl)
2137
- return;
2138
- envStatusEl.textContent = '';
2139
- envStatusEl.className = 'oncall-status';
2140
- envSaveBtn.disabled = true;
2141
- try {
2142
- const r = await fetch(`/api/bots/${encodeURIComponent(appId)}/env`, {
2143
- method: 'PUT',
2144
- headers: { 'content-type': 'application/json' },
2145
- body: JSON.stringify({ env: envEl.value }),
2146
- });
2147
- const body = await r.json().catch(() => ({}));
2148
- if (r.ok && body.ok) {
2149
- envStatusEl.textContent = `✓ ${t('botDefaults.cardPrefSaved')}`;
2150
- envStatusEl.classList.add('hint-ok');
2151
- // Server returns the sanitized, pretty-printed JSON — reflect it
2152
- // back so the textarea shows exactly what was persisted.
2153
- const next = typeof body.env === 'string' ? body.env : '';
2154
- envEl.value = next;
2155
- const cached = cache.bots.find((bb) => bb.larkAppId === appId);
2156
- if (cached)
2157
- cached.env = next;
2158
- }
2159
- else {
2160
- envStatusEl.textContent = `✗ ${body.error ?? r.status}`;
2161
- envStatusEl.classList.add('hint-warn-inline');
2162
- }
2163
- }
2164
- catch (e) {
2165
- envStatusEl.textContent = `✗ ${e?.message ?? e}`;
2166
- envStatusEl.classList.add('hint-warn-inline');
2167
- }
2168
- finally {
2169
- envSaveBtn.disabled = false;
2170
- }
2171
- });
2172
- }
2173
- });
2174
- }
2175
- function safeRerender() {
2176
- if (!disposed && readyToRender)
2177
- rerender();
2178
- }
2179
- form.addEventListener('input', safeRerender);
2180
- return () => {
2181
- disposed = true;
2182
- refreshBtn.onclick = null;
2183
- form.removeEventListener('input', safeRerender);
2184
- listEl.removeEventListener('click', listClickHandler);
2185
- rosterEl.replaceChildren();
2186
- listEl.replaceChildren();
2187
- };
2188
- }
2189
91
  //# sourceMappingURL=bot-defaults.js.map