botmux 2.33.0 → 2.34.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 (281) hide show
  1. package/README.en.md +12 -1
  2. package/README.md +45 -1
  3. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  4. package/dist/adapters/cli/claude-code.js +11 -0
  5. package/dist/adapters/cli/claude-code.js.map +1 -1
  6. package/dist/cli/bots-list-output.d.ts +21 -0
  7. package/dist/cli/bots-list-output.d.ts.map +1 -0
  8. package/dist/cli/bots-list-output.js +23 -0
  9. package/dist/cli/bots-list-output.js.map +1 -0
  10. package/dist/cli/workflow.d.ts +13 -0
  11. package/dist/cli/workflow.d.ts.map +1 -0
  12. package/dist/cli/workflow.js +781 -0
  13. package/dist/cli/workflow.js.map +1 -0
  14. package/dist/cli.js +69 -14
  15. package/dist/cli.js.map +1 -1
  16. package/dist/core/command-handler.d.ts.map +1 -1
  17. package/dist/core/command-handler.js +219 -6
  18. package/dist/core/command-handler.js.map +1 -1
  19. package/dist/core/session-manager.d.ts +6 -1
  20. package/dist/core/session-manager.d.ts.map +1 -1
  21. package/dist/core/session-manager.js +22 -12
  22. package/dist/core/session-manager.js.map +1 -1
  23. package/dist/core/worker-pool.d.ts +13 -0
  24. package/dist/core/worker-pool.d.ts.map +1 -1
  25. package/dist/core/worker-pool.js +100 -6
  26. package/dist/core/worker-pool.js.map +1 -1
  27. package/dist/daemon.d.ts +3 -0
  28. package/dist/daemon.d.ts.map +1 -1
  29. package/dist/daemon.js +884 -3
  30. package/dist/daemon.js.map +1 -1
  31. package/dist/dashboard/auth.d.ts +36 -0
  32. package/dist/dashboard/auth.d.ts.map +1 -1
  33. package/dist/dashboard/auth.js +22 -0
  34. package/dist/dashboard/auth.js.map +1 -1
  35. package/dist/dashboard/web/app.js +20 -1
  36. package/dist/dashboard/web/app.js.map +1 -1
  37. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  38. package/dist/dashboard/web/i18n.js +356 -0
  39. package/dist/dashboard/web/i18n.js.map +1 -1
  40. package/dist/dashboard/web/workflow-catalog.d.ts +2 -0
  41. package/dist/dashboard/web/workflow-catalog.d.ts.map +1 -0
  42. package/dist/dashboard/web/workflow-catalog.js +323 -0
  43. package/dist/dashboard/web/workflow-catalog.js.map +1 -0
  44. package/dist/dashboard/web/workflows.d.ts +2 -0
  45. package/dist/dashboard/web/workflows.d.ts.map +1 -0
  46. package/dist/dashboard/web/workflows.js +1618 -0
  47. package/dist/dashboard/web/workflows.js.map +1 -0
  48. package/dist/dashboard/workflow-api.d.ts +23 -0
  49. package/dist/dashboard/workflow-api.d.ts.map +1 -0
  50. package/dist/dashboard/workflow-api.js +463 -0
  51. package/dist/dashboard/workflow-api.js.map +1 -0
  52. package/dist/dashboard-web/app.js +494 -199
  53. package/dist/dashboard-web/index.html +1 -0
  54. package/dist/dashboard-web/style.css +160 -6
  55. package/dist/dashboard-web/terminal-replay.html +227 -0
  56. package/dist/dashboard.js +29 -12
  57. package/dist/dashboard.js.map +1 -1
  58. package/dist/i18n/en.d.ts.map +1 -1
  59. package/dist/i18n/en.js +12 -0
  60. package/dist/i18n/en.js.map +1 -1
  61. package/dist/i18n/zh.d.ts.map +1 -1
  62. package/dist/i18n/zh.js +12 -0
  63. package/dist/i18n/zh.js.map +1 -1
  64. package/dist/im/lark/card-handler.d.ts +3 -0
  65. package/dist/im/lark/card-handler.d.ts.map +1 -1
  66. package/dist/im/lark/card-handler.js +27 -1
  67. package/dist/im/lark/card-handler.js.map +1 -1
  68. package/dist/im/lark/client.d.ts +19 -2
  69. package/dist/im/lark/client.d.ts.map +1 -1
  70. package/dist/im/lark/client.js +21 -2
  71. package/dist/im/lark/client.js.map +1 -1
  72. package/dist/im/lark/workflow-card-handler.d.ts +50 -0
  73. package/dist/im/lark/workflow-card-handler.d.ts.map +1 -0
  74. package/dist/im/lark/workflow-card-handler.js +152 -0
  75. package/dist/im/lark/workflow-card-handler.js.map +1 -0
  76. package/dist/im/lark/workflow-cards.d.ts +46 -0
  77. package/dist/im/lark/workflow-cards.d.ts.map +1 -0
  78. package/dist/im/lark/workflow-cards.js +226 -0
  79. package/dist/im/lark/workflow-cards.js.map +1 -0
  80. package/dist/im/lark/workflow-progress-card.d.ts +76 -0
  81. package/dist/im/lark/workflow-progress-card.d.ts.map +1 -0
  82. package/dist/im/lark/workflow-progress-card.js +279 -0
  83. package/dist/im/lark/workflow-progress-card.js.map +1 -0
  84. package/dist/im/lark/workflow-slash-command.d.ts +92 -0
  85. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -0
  86. package/dist/im/lark/workflow-slash-command.js +185 -0
  87. package/dist/im/lark/workflow-slash-command.js.map +1 -0
  88. package/dist/services/group-creator.d.ts.map +1 -1
  89. package/dist/services/group-creator.js +17 -4
  90. package/dist/services/group-creator.js.map +1 -1
  91. package/dist/services/groups-store.d.ts +11 -0
  92. package/dist/services/groups-store.d.ts.map +1 -1
  93. package/dist/services/groups-store.js +26 -0
  94. package/dist/services/groups-store.js.map +1 -1
  95. package/dist/services/jsonl-cursor.d.ts +12 -0
  96. package/dist/services/jsonl-cursor.d.ts.map +1 -0
  97. package/dist/services/jsonl-cursor.js +45 -0
  98. package/dist/services/jsonl-cursor.js.map +1 -0
  99. package/dist/services/schedule-store.d.ts +35 -0
  100. package/dist/services/schedule-store.d.ts.map +1 -1
  101. package/dist/services/schedule-store.js +108 -1
  102. package/dist/services/schedule-store.js.map +1 -1
  103. package/dist/skills/definitions.d.ts.map +1 -1
  104. package/dist/skills/definitions.js +399 -0
  105. package/dist/skills/definitions.js.map +1 -1
  106. package/dist/types.d.ts +4 -0
  107. package/dist/types.d.ts.map +1 -1
  108. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  109. package/dist/utils/cli-usage-limit.js +4 -0
  110. package/dist/utils/cli-usage-limit.js.map +1 -1
  111. package/dist/worker.js +118 -14
  112. package/dist/worker.js.map +1 -1
  113. package/dist/workflows/attempt-resume.d.ts +114 -0
  114. package/dist/workflows/attempt-resume.d.ts.map +1 -0
  115. package/dist/workflows/attempt-resume.js +385 -0
  116. package/dist/workflows/attempt-resume.js.map +1 -0
  117. package/dist/workflows/attempt-terminal.d.ts +21 -0
  118. package/dist/workflows/attempt-terminal.d.ts.map +1 -0
  119. package/dist/workflows/attempt-terminal.js +7 -0
  120. package/dist/workflows/attempt-terminal.js.map +1 -0
  121. package/dist/workflows/blob.d.ts +27 -0
  122. package/dist/workflows/blob.d.ts.map +1 -0
  123. package/dist/workflows/blob.js +39 -0
  124. package/dist/workflows/blob.js.map +1 -0
  125. package/dist/workflows/cancel-run.d.ts +45 -0
  126. package/dist/workflows/cancel-run.d.ts.map +1 -0
  127. package/dist/workflows/cancel-run.js +99 -0
  128. package/dist/workflows/cancel-run.js.map +1 -0
  129. package/dist/workflows/cancel.d.ts +111 -0
  130. package/dist/workflows/cancel.d.ts.map +1 -0
  131. package/dist/workflows/cancel.js +120 -0
  132. package/dist/workflows/cancel.js.map +1 -0
  133. package/dist/workflows/catalog.d.ts +60 -0
  134. package/dist/workflows/catalog.d.ts.map +1 -0
  135. package/dist/workflows/catalog.js +119 -0
  136. package/dist/workflows/catalog.js.map +1 -0
  137. package/dist/workflows/cold-attach.d.ts +30 -0
  138. package/dist/workflows/cold-attach.d.ts.map +1 -0
  139. package/dist/workflows/cold-attach.js +40 -0
  140. package/dist/workflows/cold-attach.js.map +1 -0
  141. package/dist/workflows/cold-scan.d.ts +21 -0
  142. package/dist/workflows/cold-scan.d.ts.map +1 -0
  143. package/dist/workflows/cold-scan.js +70 -0
  144. package/dist/workflows/cold-scan.js.map +1 -0
  145. package/dist/workflows/daemon-spawn.d.ts +117 -0
  146. package/dist/workflows/daemon-spawn.d.ts.map +1 -0
  147. package/dist/workflows/daemon-spawn.js +551 -0
  148. package/dist/workflows/daemon-spawn.js.map +1 -0
  149. package/dist/workflows/definition.d.ts +1309 -0
  150. package/dist/workflows/definition.d.ts.map +1 -0
  151. package/dist/workflows/definition.js +334 -0
  152. package/dist/workflows/definition.js.map +1 -0
  153. package/dist/workflows/effect-input.d.ts +4 -0
  154. package/dist/workflows/effect-input.d.ts.map +1 -0
  155. package/dist/workflows/effect-input.js +18 -0
  156. package/dist/workflows/effect-input.js.map +1 -0
  157. package/dist/workflows/events/append.d.ts +77 -0
  158. package/dist/workflows/events/append.d.ts.map +1 -0
  159. package/dist/workflows/events/append.js +214 -0
  160. package/dist/workflows/events/append.js.map +1 -0
  161. package/dist/workflows/events/idempotency.d.ts +77 -0
  162. package/dist/workflows/events/idempotency.d.ts.map +1 -0
  163. package/dist/workflows/events/idempotency.js +116 -0
  164. package/dist/workflows/events/idempotency.js.map +1 -0
  165. package/dist/workflows/events/index.d.ts +7 -0
  166. package/dist/workflows/events/index.d.ts.map +1 -0
  167. package/dist/workflows/events/index.js +7 -0
  168. package/dist/workflows/events/index.js.map +1 -0
  169. package/dist/workflows/events/payloads.d.ts +917 -0
  170. package/dist/workflows/events/payloads.d.ts.map +1 -0
  171. package/dist/workflows/events/payloads.js +337 -0
  172. package/dist/workflows/events/payloads.js.map +1 -0
  173. package/dist/workflows/events/replay.d.ts +238 -0
  174. package/dist/workflows/events/replay.d.ts.map +1 -0
  175. package/dist/workflows/events/replay.js +608 -0
  176. package/dist/workflows/events/replay.js.map +1 -0
  177. package/dist/workflows/events/schema.d.ts +5242 -0
  178. package/dist/workflows/events/schema.d.ts.map +1 -0
  179. package/dist/workflows/events/schema.js +295 -0
  180. package/dist/workflows/events/schema.js.map +1 -0
  181. package/dist/workflows/events/types.d.ts +34 -0
  182. package/dist/workflows/events/types.d.ts.map +1 -0
  183. package/dist/workflows/events/types.js +2 -0
  184. package/dist/workflows/events/types.js.map +1 -0
  185. package/dist/workflows/fanout.d.ts +36 -0
  186. package/dist/workflows/fanout.d.ts.map +1 -0
  187. package/dist/workflows/fanout.js +114 -0
  188. package/dist/workflows/fanout.js.map +1 -0
  189. package/dist/workflows/hostExecutors/botmux-schedule.d.ts +41 -0
  190. package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -0
  191. package/dist/workflows/hostExecutors/botmux-schedule.js +121 -0
  192. package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -0
  193. package/dist/workflows/hostExecutors/feishu-im.d.ts +12 -0
  194. package/dist/workflows/hostExecutors/feishu-im.d.ts.map +1 -0
  195. package/dist/workflows/hostExecutors/feishu-im.js +49 -0
  196. package/dist/workflows/hostExecutors/feishu-im.js.map +1 -0
  197. package/dist/workflows/hostExecutors/feishu-reply.d.ts +24 -0
  198. package/dist/workflows/hostExecutors/feishu-reply.d.ts.map +1 -0
  199. package/dist/workflows/hostExecutors/feishu-reply.js +88 -0
  200. package/dist/workflows/hostExecutors/feishu-reply.js.map +1 -0
  201. package/dist/workflows/hostExecutors/feishu-send.d.ts +23 -0
  202. package/dist/workflows/hostExecutors/feishu-send.d.ts.map +1 -0
  203. package/dist/workflows/hostExecutors/feishu-send.js +124 -0
  204. package/dist/workflows/hostExecutors/feishu-send.js.map +1 -0
  205. package/dist/workflows/hostExecutors/index.d.ts +8 -0
  206. package/dist/workflows/hostExecutors/index.d.ts.map +1 -0
  207. package/dist/workflows/hostExecutors/index.js +8 -0
  208. package/dist/workflows/hostExecutors/index.js.map +1 -0
  209. package/dist/workflows/hostExecutors/protocol.d.ts +42 -0
  210. package/dist/workflows/hostExecutors/protocol.d.ts.map +1 -0
  211. package/dist/workflows/hostExecutors/protocol.js +181 -0
  212. package/dist/workflows/hostExecutors/protocol.js.map +1 -0
  213. package/dist/workflows/hostExecutors/registry.d.ts +10 -0
  214. package/dist/workflows/hostExecutors/registry.d.ts.map +1 -0
  215. package/dist/workflows/hostExecutors/registry.js +36 -0
  216. package/dist/workflows/hostExecutors/registry.js.map +1 -0
  217. package/dist/workflows/hostExecutors/types.d.ts +78 -0
  218. package/dist/workflows/hostExecutors/types.d.ts.map +1 -0
  219. package/dist/workflows/hostExecutors/types.js +2 -0
  220. package/dist/workflows/hostExecutors/types.js.map +1 -0
  221. package/dist/workflows/loader.d.ts +16 -0
  222. package/dist/workflows/loader.d.ts.map +1 -0
  223. package/dist/workflows/loader.js +56 -0
  224. package/dist/workflows/loader.js.map +1 -0
  225. package/dist/workflows/loop.d.ts +50 -0
  226. package/dist/workflows/loop.d.ts.map +1 -0
  227. package/dist/workflows/loop.js +350 -0
  228. package/dist/workflows/loop.js.map +1 -0
  229. package/dist/workflows/ops-projection.d.ts +168 -0
  230. package/dist/workflows/ops-projection.d.ts.map +1 -0
  231. package/dist/workflows/ops-projection.js +707 -0
  232. package/dist/workflows/ops-projection.js.map +1 -0
  233. package/dist/workflows/orchestrator.d.ts +107 -0
  234. package/dist/workflows/orchestrator.d.ts.map +1 -0
  235. package/dist/workflows/orchestrator.js +197 -0
  236. package/dist/workflows/orchestrator.js.map +1 -0
  237. package/dist/workflows/output-binding.d.ts +70 -0
  238. package/dist/workflows/output-binding.d.ts.map +1 -0
  239. package/dist/workflows/output-binding.js +265 -0
  240. package/dist/workflows/output-binding.js.map +1 -0
  241. package/dist/workflows/params.d.ts +61 -0
  242. package/dist/workflows/params.d.ts.map +1 -0
  243. package/dist/workflows/params.js +195 -0
  244. package/dist/workflows/params.js.map +1 -0
  245. package/dist/workflows/resume.d.ts +263 -0
  246. package/dist/workflows/resume.d.ts.map +1 -0
  247. package/dist/workflows/resume.js +808 -0
  248. package/dist/workflows/resume.js.map +1 -0
  249. package/dist/workflows/run-id.d.ts +2 -0
  250. package/dist/workflows/run-id.d.ts.map +1 -0
  251. package/dist/workflows/run-id.js +7 -0
  252. package/dist/workflows/run-id.js.map +1 -0
  253. package/dist/workflows/run-init.d.ts +48 -0
  254. package/dist/workflows/run-init.d.ts.map +1 -0
  255. package/dist/workflows/run-init.js +99 -0
  256. package/dist/workflows/run-init.js.map +1 -0
  257. package/dist/workflows/runs-dir.d.ts +4 -0
  258. package/dist/workflows/runs-dir.d.ts.map +1 -0
  259. package/dist/workflows/runs-dir.js +15 -0
  260. package/dist/workflows/runs-dir.js.map +1 -0
  261. package/dist/workflows/runtime.d.ts +211 -0
  262. package/dist/workflows/runtime.d.ts.map +1 -0
  263. package/dist/workflows/runtime.js +594 -0
  264. package/dist/workflows/runtime.js.map +1 -0
  265. package/dist/workflows/spawn-bot.d.ts +165 -0
  266. package/dist/workflows/spawn-bot.d.ts.map +1 -0
  267. package/dist/workflows/spawn-bot.js +215 -0
  268. package/dist/workflows/spawn-bot.js.map +1 -0
  269. package/dist/workflows/system.d.ts +49 -0
  270. package/dist/workflows/system.d.ts.map +1 -0
  271. package/dist/workflows/system.js +48 -0
  272. package/dist/workflows/system.js.map +1 -0
  273. package/dist/workflows/trigger-run.d.ts +70 -0
  274. package/dist/workflows/trigger-run.d.ts.map +1 -0
  275. package/dist/workflows/trigger-run.js +88 -0
  276. package/dist/workflows/trigger-run.js.map +1 -0
  277. package/dist/workflows/wait.d.ts +120 -0
  278. package/dist/workflows/wait.d.ts.map +1 -0
  279. package/dist/workflows/wait.js +181 -0
  280. package/dist/workflows/wait.js.map +1 -0
  281. package/package.json +3 -3
@@ -1,21 +1,31 @@
1
- "use strict";(()=>{var z=class{sessions=new Map;schedules=new Map;online=!0;listeners=new Set;upsertSessions(s){for(let r of s)this.sessions.set(r.sessionId,r);this.emit()}upsertSchedules(s){for(let r of s)this.schedules.set(r.id,r);this.emit()}applySse(s,r){if(s==="session.spawned")this.sessions.set(r.session.sessionId,r.session);else if(s==="session.update"){let m=this.sessions.get(r.sessionId);m&&this.sessions.set(r.sessionId,{...m,...r.patch})}else if(s==="session.exited"){let m=this.sessions.get(r.sessionId);m&&this.sessions.set(r.sessionId,{...m,status:"closed"})}else if(s==="schedule.created")this.schedules.set(r.schedule.id,r.schedule);else if(s==="schedule.updated"){let m=this.schedules.get(r.id);m&&this.schedules.set(r.id,{...m,...r.patch})}else if(s==="schedule.deleted")this.schedules.delete(r.id);else return;this.emit()}setOnline(s){this.online!==s&&(this.online=s,this.emit())}on(s){return this.listeners.add(s),()=>this.listeners.delete(s)}emit(){for(let s of this.listeners)s()}},C=new z;async function Z(){let[t,s]=await Promise.all([fetch("/api/sessions").then(v=>v.json()),fetch("/api/schedules").then(v=>v.json())]);C.upsertSessions(t.sessions??[]),C.upsertSchedules(s.schedules??[]);let r=new EventSource("/events"),m=["session.spawned","session.update","session.exited","schedule.created","schedule.updated","schedule.deleted","schedule.fired","heartbeat"];for(let v of m)r.addEventListener(v,c=>{try{let T=JSON.parse(c.data);C.applySse(v,T.body??T)}catch{}});r.onerror=()=>C.setOnline(!1),r.onopen=()=>C.setOnline(!0)}var J="botmux.dashboard.locale",we={"app.name":"botmux","app.subtitle":"\u98DE\u4E66 AI CLI \u63A7\u5236\u53F0","nav.overview":"\u603B\u89C8","nav.sessions":"\u4F1A\u8BDD","nav.groups":"\u7FA4\u7EC4","nav.schedules":"\u5B9A\u65F6","nav.botDefaults":"\u9ED8\u8BA4 Bot","status.live":"\u5B9E\u65F6\u8FDE\u63A5","status.disconnected":"\u8FDE\u63A5\u65AD\u5F00","status.system":"\u7CFB\u7EDF","status.light":"\u6D45\u8272","status.dark":"\u6697\u9ED1","status.language":"\u8BED\u8A00","status.theme":"\u4E3B\u9898","botOnboarding.add":"\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.title":"\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.intro":"\u7528\u98DE\u4E66 App \u626B\u7801\u521B\u5EFA PersonalAgent \u5E94\u7528\uFF0C\u6210\u529F\u540E\u4F1A\u5199\u5165\u672C\u673A bots.json\u3002","botOnboarding.starting":"\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801...","botOnboarding.waiting":"\u8BF7\u7528\u98DE\u4E66 App \u626B\u7801\u786E\u8BA4\u3002","botOnboarding.verifying":"\u626B\u7801\u6210\u529F\uFF0C\u6B63\u5728\u6821\u9A8C\u51ED\u8BC1...","botOnboarding.completed":"\u673A\u5668\u4EBA\u5DF2\u6DFB\u52A0\u3002","botOnboarding.failed":"\u6DFB\u52A0\u5931\u8D25","botOnboarding.openLink":"\u6253\u4E0D\u5F00\u626B\u7801\uFF1F\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00","botOnboarding.close":"\u5173\u95ED","botOnboarding.restartHint":"\u5DF2\u5199\u5165 bots.json\u3002\u6267\u884C pnpm daemon:restart \u540E\u65B0\u673A\u5668\u4EBA\u751F\u6548\u3002","botOnboarding.qrAlt":"\u98DE\u4E66\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA\u4E8C\u7EF4\u7801","overview.title":"\u63A7\u5236\u53F0\u603B\u89C8","overview.subtitle":"\u8DE8 bot\u3001\u7FA4\u804A\u3001\u4F1A\u8BDD\u548C\u5B9A\u65F6\u4EFB\u52A1\u7684\u5B9E\u65F6\u7BA1\u63A7\u9762\u3002","overview.openSessions":"\u6D3B\u8DC3\u4F1A\u8BDD","overview.workingSessions":"\u5DE5\u4F5C\u4E2D","overview.onlineBots":"\u5728\u7EBF Bot","overview.schedules":"\u5B9A\u65F6\u4EFB\u52A1","overview.groups":"\u7FA4\u804A\u8986\u76D6","overview.enabledSchedules":"\u5DF2\u542F\u7528","overview.total":"\u603B\u8BA1","overview.active":"\u6D3B\u8DC3","overview.daemonRegistry":"daemon \u6CE8\u518C\u8868","overview.chatMatrix":"\u7FA4\u804A\u77E9\u9635","overview.recentSessions":"\u6700\u8FD1\u4F1A\u8BDD","overview.nextSchedules":"\u5373\u5C06\u6267\u884C","overview.noSessions":"\u6682\u65E0\u4F1A\u8BDD\u3002","overview.noSchedules":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","sessions.title":"\u4F1A\u8BDD\u63A7\u5236","sessions.subtitle":"\u5B9A\u4F4D\u98DE\u4E66\u8BDD\u9898\u3001\u6253\u5F00 Web Terminal\u3001\u5173\u95ED\u6216\u6062\u590D CLI \u4F1A\u8BDD\u3002","sessions.search":"\u641C\u7D22\u5DE5\u4F5C\u76EE\u5F55 / \u6807\u9898 / ID","sessions.anyStatus":"\u5168\u90E8\u72B6\u6001","sessions.adoptAny":"adopt: \u5168\u90E8","sessions.adoptYes":"adopt: \u662F","sessions.adoptNo":"adopt: \u5426","sessions.activeOnly":"\u4EC5\u6D3B\u8DC3","sessions.closeSelected":"\u5173\u95ED\u9009\u4E2D","sessions.clearSelection":"\u53D6\u6D88\u9009\u62E9","sessions.selectedCount":"\u5DF2\u9009 {count} \u4E2A\u4F1A\u8BDD","sessions.bot":"bot","sessions.cli":"CLI","sessions.status":"\u72B6\u6001","sessions.titleCol":"\u6807\u9898","sessions.workingDir":"\u5DE5\u4F5C\u76EE\u5F55","sessions.created":"\u521B\u5EFA","sessions.last":"\u6700\u8FD1","sessions.adopt":"\u63A5\u5165","sessions.actions":"\u64CD\u4F5C","sessions.details":"\u8BE6\u60C5","sessions.copy":"\u590D\u5236","sessions.copied":"\u5DF2\u590D\u5236","sessions.locate":"\u5B9A\u4F4D\u8BDD\u9898","sessions.locating":"\u53D1\u9001\u4E2D...","sessions.cooldown":"\u51B7\u5374 {seconds}s","sessions.openTerminal":"\u7EC8\u7AEF","sessions.close":"\u5173\u95ED\u4F1A\u8BDD","sessions.resume":"\u6062\u590D\u4F1A\u8BDD","sessions.dismiss":"\u5173\u95ED","sessions.closeConfirm":"\u5173\u95ED\u8FD9\u4E2A\u4F1A\u8BDD\uFF1F","sessions.resumeFailed":"\u6062\u590D\u5931\u8D25","sessions.closeBulkConfirm":"\u5173\u95ED\u9009\u4E2D\u7684 {count} \u4E2A\u4F1A\u8BDD\uFF1F","sessions.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u4F1A\u8BDD\u3002","groups.title":"\u7FA4\u7EC4\u4E0E Bot","groups.subtitle":"\u67E5\u770B chat x bot \u8986\u76D6\u77E9\u9635\uFF0C\u7BA1\u7406\u62C9\u7FA4\u3001oncall\u3001\u9000\u7FA4\u548C\u89E3\u6563\u3002","groups.search":"\u641C\u7D22\u7FA4\u540D / ID / owner","groups.missingOnly":"\u4EC5\u7F3A bot","groups.refresh":"\u5237\u65B0","groups.create":"\u65B0\u5EFA\u7FA4","groups.chat":"\u7FA4\u804A","groups.actions":"\u64CD\u4F5C","groups.addBots":"\u6DFB\u52A0 bot","groups.manage":"\u7BA1\u7406","groups.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u7FA4\u804A\u3002","groups.createTitle":"\u65B0\u5EFA\u7FA4\u804A","groups.createHelp":"\u9009\u62E9\u8981\u9080\u8BF7\u7684 bot\u3002dashboard \u4F1A\u81EA\u52A8\u9009\u62E9\u5728\u7EBF daemon \u4F5C\u4E3A\u521B\u5EFA\u8005\u3002","groups.name":"\u7FA4\u540D","groups.namePlaceholder":"\u4F8B\u5982 AI ChangeLog","groups.bindDir":"\u7ED1\u5B9A\u76EE\u5F55","groups.bindDirHelp":"\u65B0\u8BDD\u9898\u76F4\u63A5\u7528\u8BE5\u76EE\u5F55\u542F\u52A8 CLI\uFF0C\u8DF3\u8FC7 repo \u9009\u62E9\u3002","groups.botPicker":"Bot","groups.createSubmit":"\u521B\u5EFA","groups.cancel":"\u53D6\u6D88","groups.successTitle":"\u7FA4\u521B\u5EFA\u6210\u529F","groups.openGroup":"\u6253\u5F00\u65B0\u7FA4","groups.manageTitle":"\u7BA1\u7406 {name}","groups.owner":"\u7FA4\u4E3B","groups.oncall":"Oncall \u6A21\u5F0F","groups.oncallHelp":"\u5F00\u542F\u540E\uFF0C\u7FA4\u5185\u6210\u5458\u53EF @ \u673A\u5668\u4EBA\uFF1B\u65B0\u8BDD\u9898\u76F4\u63A5\u4F7F\u7528\u7ED1\u5B9A\u76EE\u5F55\u3002","groups.leaveTitle":"\u9009\u62E9\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.leaveSelected":"\u9009\u4E2D\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.disband":"\u89E3\u6563\u7FA4\u804A","groups.dangerHint":"\u89E3\u6563\u4EC5\u5F53\u5728\u7FA4\u673A\u5668\u4EBA\u662F\u7FA4\u4E3B\u65F6\u624D\u4F1A\u6210\u529F\uFF0C\u5426\u5219\u5EFA\u8BAE\u4F7F\u7528\u9000\u51FA\u7FA4\u804A\u3002","groups.save":"\u4FDD\u5B58","groups.needWorkingDir":"\u8BF7\u586B\u5DE5\u4F5C\u76EE\u5F55","groups.noBotsOnline":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u8BF7\u5148\u91CD\u542F daemon\u3002","schedules.title":"\u5B9A\u65F6\u4EFB\u52A1","schedules.subtitle":"\u8DE8 daemon \u67E5\u770B\u3001\u6682\u505C\u3001\u6062\u590D\u548C\u7ACB\u5373\u89E6\u53D1\u5B9A\u65F6\u4EFB\u52A1\u3002","schedules.search":"\u641C\u7D22\u540D\u79F0 / prompt / \u5DE5\u4F5C\u76EE\u5F55","schedules.anyKind":"\u5168\u90E8\u7C7B\u578B","schedules.enabledOnly":"\u4EC5\u542F\u7528","schedules.name":"\u540D\u79F0","schedules.bot":"bot","schedules.schedule":"\u89C4\u5219","schedules.next":"\u4E0B\u6B21","schedules.last":"\u4E0A\u6B21","schedules.repeat":"\u91CD\u590D","schedules.enabled":"\u542F\u7528","schedules.actions":"\u64CD\u4F5C","schedules.runNow":"\u7ACB\u5373\u8FD0\u884C","schedules.pause":"\u6682\u505C","schedules.resume":"\u6062\u590D","schedules.empty":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","botDefaults.title":"Bot \u9ED8\u8BA4 Oncall","botDefaults.subtitle":"\u914D\u7F6E\u6BCF\u4E2A bot \u5728\u65B0\u7FA4\u91CC\u7684\u9ED8\u8BA4 oncall \u884C\u4E3A\u3002","botDefaults.search":"\u641C\u7D22 bot \u540D / app id","botDefaults.refresh":"\u5237\u65B0","botDefaults.warning":"\u5F00\u542F\u540E\uFF0C\u6CA1\u6709 oncall binding \u7684\u7FA4\u4F1A\u5728\u4E0B\u6B21\u5F00\u65B0\u8BDD\u9898\u65F6\u81EA\u52A8\u7ED1\u5B9A\u5230\u8BE5\u76EE\u5F55\uFF1B\u624B\u52A8\u7ED1\u5B9A\u6216\u624B\u52A8\u89E3\u7ED1\u8FC7\u7684\u7FA4\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002","botDefaults.empty":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u5148 botmux restart \u8BA9 daemon \u4E0A\u7EBF\u3002","botDefaults.defaultOncall":"\u9ED8\u8BA4\u8FDB\u5165 oncall \u6A21\u5F0F","botDefaults.defaultOncallHelp":"\u6240\u6709\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8\u7ED1\u5B9A","botDefaults.workingDir":"\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55","botDefaults.lastEnabled":"\u4E0A\u6B21\u542F\u7528\u65F6\u95F4","botDefaults.autobound":"\u5DF2\u81EA\u52A8\u7ED1\u5B9A {count} \u4E2A\u7FA4","botDefaults.save":"\u4FDD\u5B58","botDefaults.required":"\u5F00\u542F\u65F6\u5FC5\u987B\u586B\u5DE5\u4F5C\u76EE\u5F55","common.none":"\u65E0","common.unknown":"\u672A\u77E5","common.now":"\u521A\u521A","common.never":"\u4ECE\u672A"},$e={"app.name":"botmux","app.subtitle":"Feishu AI CLI Control","nav.overview":"Overview","nav.sessions":"Sessions","nav.groups":"Groups","nav.schedules":"Schedules","nav.botDefaults":"Bot Defaults","status.live":"Live","status.disconnected":"Disconnected","status.system":"System","status.light":"Light","status.dark":"Dark","status.language":"Language","status.theme":"Theme","botOnboarding.add":"Add Bot","botOnboarding.title":"Scan to Add Bot","botOnboarding.intro":"Scan with the Feishu app to create a PersonalAgent app. The dashboard writes it to local bots.json after success.","botOnboarding.starting":"Generating QR code...","botOnboarding.waiting":"Scan with the Feishu app to continue.","botOnboarding.verifying":"Scan accepted. Verifying credentials...","botOnboarding.completed":"Bot added.","botOnboarding.failed":"Add failed","botOnboarding.openLink":"Open scan link in browser","botOnboarding.close":"Close","botOnboarding.restartHint":"bots.json has been updated. Run pnpm daemon:restart for the new bot to take effect.","botOnboarding.qrAlt":"Feishu bot onboarding QR code","overview.title":"Control Overview","overview.subtitle":"A realtime control plane across bots, chats, CLI sessions, and schedules.","overview.openSessions":"Active Sessions","overview.workingSessions":"Working","overview.onlineBots":"Online Bots","overview.schedules":"Schedules","overview.groups":"Groups Seen","overview.enabledSchedules":"Enabled","overview.total":"total","overview.active":"active","overview.daemonRegistry":"daemon registry","overview.chatMatrix":"chat matrix","overview.recentSessions":"Recent Sessions","overview.nextSchedules":"Next Runs","overview.noSessions":"No sessions yet.","overview.noSchedules":"No schedules yet.","sessions.title":"Session Control","sessions.subtitle":"Locate Feishu topics, open Web Terminal, close or resume CLI sessions.","sessions.search":"Search working dir / title / IDs","sessions.anyStatus":"Any status","sessions.adoptAny":"adopt: any","sessions.adoptYes":"adopt: yes","sessions.adoptNo":"adopt: no","sessions.activeOnly":"Active only","sessions.closeSelected":"Close selected","sessions.clearSelection":"Clear","sessions.selectedCount":"{count} sessions selected","sessions.bot":"Bot","sessions.cli":"CLI","sessions.status":"Status","sessions.titleCol":"Title","sessions.workingDir":"Working Dir","sessions.created":"Created","sessions.last":"Last","sessions.adopt":"Adopt","sessions.actions":"Actions","sessions.details":"Details","sessions.copy":"Copy","sessions.copied":"Copied","sessions.locate":"Locate Topic","sessions.locating":"Sending...","sessions.cooldown":"Cooldown {seconds}s","sessions.openTerminal":"Terminal","sessions.close":"Close Session","sessions.resume":"Resume Session","sessions.dismiss":"Close","sessions.closeConfirm":"Close this session?","sessions.resumeFailed":"Resume failed","sessions.closeBulkConfirm":"Close {count} selected sessions?","sessions.empty":"No sessions match the filters.","groups.title":"Groups & Bots","groups.subtitle":"Inspect the chat x bot matrix and manage group creation, oncall, leave, and disband flows.","groups.search":"Search chat name / ID / owner","groups.missingOnly":"Missing bot only","groups.refresh":"Refresh","groups.create":"New Group","groups.chat":"Chat","groups.actions":"Actions","groups.addBots":"Add Bots","groups.manage":"Manage","groups.empty":"No chats match the filters.","groups.createTitle":"Create New Group","groups.createHelp":"Pick bots to invite. The dashboard chooses an online daemon as creator.","groups.name":"Group Name","groups.namePlaceholder":"e.g. AI ChangeLog","groups.bindDir":"Bind Directory","groups.bindDirHelp":"New topics start the CLI here and skip repo selection.","groups.botPicker":"Bots","groups.createSubmit":"Create","groups.cancel":"Cancel","groups.successTitle":"Group Created","groups.openGroup":"Open Group","groups.manageTitle":"Manage {name}","groups.owner":"Owner","groups.oncall":"Oncall Mode","groups.oncallHelp":"When enabled, group members can @ the bot; new topics use the bound directory.","groups.leaveTitle":"Select Bots to Leave","groups.leaveSelected":"Selected Bots Leave","groups.disband":"Disband Group","groups.dangerHint":"Disband only works when an in-chat bot is the owner. Otherwise prefer leaving the chat.","groups.save":"Save","groups.needWorkingDir":"Working directory is required","groups.noBotsOnline":"No bots online. Restart the daemon first.","schedules.title":"Schedules","schedules.subtitle":"View, pause, resume, and run scheduled tasks across daemons.","schedules.search":"Search name / prompt / working dir","schedules.anyKind":"Any kind","schedules.enabledOnly":"Enabled only","schedules.name":"Name","schedules.bot":"Bot","schedules.schedule":"Schedule","schedules.next":"Next","schedules.last":"Last","schedules.repeat":"Repeat","schedules.enabled":"Enabled","schedules.actions":"Actions","schedules.runNow":"Run Now","schedules.pause":"Pause","schedules.resume":"Resume","schedules.empty":"No schedules.","botDefaults.title":"Bot Default Oncall","botDefaults.subtitle":"Configure each bot's default oncall behavior in new chats.","botDefaults.search":"Search bot name / app id","botDefaults.refresh":"Refresh","botDefaults.warning":"When enabled, chats without an oncall binding auto-bind to this directory on their next new topic. Manually bound or unbound chats are preserved.","botDefaults.empty":"No bots online. Run botmux restart first.","botDefaults.defaultOncall":"Default to oncall mode","botDefaults.defaultOncallHelp":"Unbound chats auto-bind on the next new topic","botDefaults.workingDir":"Default Working Directory","botDefaults.lastEnabled":"Last Enabled","botDefaults.autobound":"{count} chats auto-bound","botDefaults.save":"Save","botDefaults.required":"Working directory is required when enabled","common.none":"None","common.unknown":"Unknown","common.now":"now","common.never":"never"},ee={zh:we,en:$e};function te(t){if(typeof t!="string")return null;let s=t.trim().toLowerCase();return s==="zh"||s.startsWith("zh-")?"zh":s==="en"||s.startsWith("en-")?"en":null}function ke(t=[]){for(let s of t){let r=te(s);if(r)return r}return"zh"}function U(t){return(s,r)=>{let m=ee[t][s]??ee.zh[s]??s;return r?m.replace(/\{(\w+)\}/g,(v,c)=>{let T=r[c];return T==null?`{${c}}`:String(T)}):m}}function se(t,s){return(t?te(t.getItem(J)):null)??ke(s)}var K="botmux.dashboard.theme";function Le(t){return t==="system"||t==="light"||t==="dark"?t:null}function ne(t,s){return t==="system"?s?"dark":"light":t}function oe(t){return Le(t?.getItem(K))??"system"}var Q=class{locale="zh";themeMode="system";resolvedTheme="light";listeners=new Set;translate=U(this.locale);mediaQuery=null;init(){let s=typeof window<"u"?window:void 0;this.locale=se(s?.localStorage,Se()),this.translate=U(this.locale),this.themeMode=oe(s?.localStorage),this.mediaQuery=s?.matchMedia?.("(prefers-color-scheme: dark)")??null,this.mediaQuery?.addEventListener("change",()=>{this.applyTheme(),this.emit()}),this.applyTheme(),this.applyLocale()}t(s,r){return this.translate(s,r)}setLocale(s){this.locale!==s&&(this.locale=s,this.translate=U(s),window.localStorage.setItem(J,s),this.applyLocale(),this.emit())}setThemeMode(s){this.themeMode!==s&&(this.themeMode=s,window.localStorage.setItem(K,s),this.applyTheme(),this.emit())}on(s){return this.listeners.add(s),()=>this.listeners.delete(s)}emit(){for(let s of this.listeners)s()}applyTheme(){this.resolvedTheme=ne(this.themeMode,!!this.mediaQuery?.matches),document.documentElement.dataset.theme=this.resolvedTheme,document.documentElement.dataset.themeMode=this.themeMode}applyLocale(){document.documentElement.lang=this.locale==="zh"?"zh-CN":"en"}};function Se(){return typeof navigator>"u"?[]:navigator.languages?.length?navigator.languages:[navigator.language].filter(Boolean)}var O=new Q;function e(t,s){return O.t(t,s)}function o(t){return t.replace(/[&<>"']/g,s=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[s])}function j(t){if(!t)return"-";let s=Date.now()-t;return s<6e4?e("common.now"):s<36e5?Math.floor(s/6e4)+"m":s<864e5?Math.floor(s/36e5)+"h":Math.floor(s/864e5)+"d"}var Y={chats:[],bots:[]};async function Te(){try{let t=await fetch("/api/groups");if(!t.ok)return;Y=await t.json()}catch{}}function Ie(t){return`status status-${o(t||"unknown")}`}function Me(t){return`<li class="overview-list-row">
1
+ "use strict";(()=>{var ye=class{sessions=new Map;schedules=new Map;online=!0;listeners=new Set;upsertSessions(n){for(let o of n)this.sessions.set(o.sessionId,o);this.emit()}upsertSchedules(n){for(let o of n)this.schedules.set(o.id,o);this.emit()}applySse(n,o){if(n==="session.spawned")this.sessions.set(o.session.sessionId,o.session);else if(n==="session.update"){let r=this.sessions.get(o.sessionId);r&&this.sessions.set(o.sessionId,{...r,...o.patch})}else if(n==="session.exited"){let r=this.sessions.get(o.sessionId);r&&this.sessions.set(o.sessionId,{...r,status:"closed"})}else if(n==="schedule.created")this.schedules.set(o.schedule.id,o.schedule);else if(n==="schedule.updated"){let r=this.schedules.get(o.id);r&&this.schedules.set(o.id,{...r,...o.patch})}else if(n==="schedule.deleted")this.schedules.delete(o.id);else return;this.emit()}setOnline(n){this.online!==n&&(this.online=n,this.emit())}on(n){return this.listeners.add(n),()=>this.listeners.delete(n)}emit(){for(let n of this.listeners)n()}},D=new ye;async function De(){let[e,n]=await Promise.all([fetch("/api/sessions").then(s=>s.json()),fetch("/api/schedules").then(s=>s.json())]);D.upsertSessions(e.sessions??[]),D.upsertSchedules(n.schedules??[]);let o=new EventSource("/events"),r=["session.spawned","session.update","session.exited","schedule.created","schedule.updated","schedule.deleted","schedule.fired","heartbeat"];for(let s of r)o.addEventListener(s,a=>{try{let c=JSON.parse(a.data);D.applySse(s,c.body??c)}catch{}});o.onerror=()=>D.setOnline(!1),o.onopen=()=>D.setOnline(!0)}var $e="botmux.dashboard.locale",St={"app.name":"botmux","app.subtitle":"\u98DE\u4E66 AI CLI \u63A7\u5236\u53F0","time.secondsAgo":"{value} \u79D2\u524D","time.minutesAgo":"{value} \u5206\u949F\u524D","time.hoursAgo":"{value} \u5C0F\u65F6\u524D","nav.overview":"\u603B\u89C8","nav.sessions":"\u4F1A\u8BDD","nav.groups":"\u7FA4\u7EC4","nav.schedules":"\u5B9A\u65F6","nav.botDefaults":"\u9ED8\u8BA4 Bot","status.live":"\u5B9E\u65F6\u8FDE\u63A5","status.disconnected":"\u8FDE\u63A5\u65AD\u5F00","status.system":"\u7CFB\u7EDF","status.light":"\u6D45\u8272","status.dark":"\u6697\u9ED1","status.language":"\u8BED\u8A00","status.theme":"\u4E3B\u9898","botOnboarding.add":"\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.title":"\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.intro":"\u7528\u98DE\u4E66 App \u626B\u7801\u521B\u5EFA PersonalAgent \u5E94\u7528\uFF0C\u6210\u529F\u540E\u4F1A\u5199\u5165\u672C\u673A bots.json\u3002","botOnboarding.starting":"\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801...","botOnboarding.waiting":"\u8BF7\u7528\u98DE\u4E66 App \u626B\u7801\u786E\u8BA4\u3002","botOnboarding.verifying":"\u626B\u7801\u6210\u529F\uFF0C\u6B63\u5728\u6821\u9A8C\u51ED\u8BC1...","botOnboarding.completed":"\u673A\u5668\u4EBA\u5DF2\u6DFB\u52A0\u3002","botOnboarding.failed":"\u6DFB\u52A0\u5931\u8D25","botOnboarding.openLink":"\u6253\u4E0D\u5F00\u626B\u7801\uFF1F\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00","botOnboarding.close":"\u5173\u95ED","botOnboarding.restartHint":"\u5DF2\u5199\u5165 bots.json\u3002\u6267\u884C pnpm daemon:restart \u540E\u65B0\u673A\u5668\u4EBA\u751F\u6548\u3002","botOnboarding.qrAlt":"\u98DE\u4E66\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA\u4E8C\u7EF4\u7801","overview.title":"\u63A7\u5236\u53F0\u603B\u89C8","overview.subtitle":"\u8DE8 bot\u3001\u7FA4\u804A\u3001\u4F1A\u8BDD\u548C\u5B9A\u65F6\u4EFB\u52A1\u7684\u5B9E\u65F6\u7BA1\u63A7\u9762\u3002","overview.openSessions":"\u6D3B\u8DC3\u4F1A\u8BDD","overview.workingSessions":"\u5DE5\u4F5C\u4E2D","overview.onlineBots":"\u5728\u7EBF Bot","overview.schedules":"\u5B9A\u65F6\u4EFB\u52A1","overview.groups":"\u7FA4\u804A\u8986\u76D6","overview.enabledSchedules":"\u5DF2\u542F\u7528","overview.total":"\u603B\u8BA1","overview.active":"\u6D3B\u8DC3","overview.daemonRegistry":"daemon \u6CE8\u518C\u8868","overview.chatMatrix":"\u7FA4\u804A\u77E9\u9635","overview.recentSessions":"\u6700\u8FD1\u4F1A\u8BDD","overview.nextSchedules":"\u5373\u5C06\u6267\u884C","overview.noSessions":"\u6682\u65E0\u4F1A\u8BDD\u3002","overview.noSchedules":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","sessions.title":"\u4F1A\u8BDD\u63A7\u5236","sessions.subtitle":"\u5B9A\u4F4D\u98DE\u4E66\u8BDD\u9898\u3001\u6253\u5F00 Web Terminal\u3001\u5173\u95ED\u6216\u6062\u590D CLI \u4F1A\u8BDD\u3002","sessions.search":"\u641C\u7D22\u5DE5\u4F5C\u76EE\u5F55 / \u6807\u9898 / ID","sessions.anyStatus":"\u5168\u90E8\u72B6\u6001","sessions.adoptAny":"adopt: \u5168\u90E8","sessions.adoptYes":"adopt: \u662F","sessions.adoptNo":"adopt: \u5426","sessions.activeOnly":"\u4EC5\u6D3B\u8DC3","sessions.closeSelected":"\u5173\u95ED\u9009\u4E2D","sessions.clearSelection":"\u53D6\u6D88\u9009\u62E9","sessions.selectedCount":"\u5DF2\u9009 {count} \u4E2A\u4F1A\u8BDD","sessions.bot":"bot","sessions.cli":"CLI","sessions.status":"\u72B6\u6001","sessions.titleCol":"\u6807\u9898","sessions.workingDir":"\u5DE5\u4F5C\u76EE\u5F55","sessions.created":"\u521B\u5EFA","sessions.last":"\u6700\u8FD1","sessions.adopt":"\u63A5\u5165","sessions.actions":"\u64CD\u4F5C","sessions.details":"\u8BE6\u60C5","sessions.copy":"\u590D\u5236","sessions.copied":"\u5DF2\u590D\u5236","sessions.locate":"\u5B9A\u4F4D\u8BDD\u9898","sessions.locating":"\u53D1\u9001\u4E2D...","sessions.cooldown":"\u51B7\u5374 {seconds}s","sessions.openTerminal":"\u7EC8\u7AEF","sessions.close":"\u5173\u95ED\u4F1A\u8BDD","sessions.resume":"\u6062\u590D\u4F1A\u8BDD","sessions.dismiss":"\u5173\u95ED","sessions.closeConfirm":"\u5173\u95ED\u8FD9\u4E2A\u4F1A\u8BDD\uFF1F","sessions.resumeFailed":"\u6062\u590D\u5931\u8D25","sessions.closeBulkConfirm":"\u5173\u95ED\u9009\u4E2D\u7684 {count} \u4E2A\u4F1A\u8BDD\uFF1F","sessions.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u4F1A\u8BDD\u3002","groups.title":"\u7FA4\u7EC4\u4E0E Bot","groups.subtitle":"\u67E5\u770B chat x bot \u8986\u76D6\u77E9\u9635\uFF0C\u7BA1\u7406\u62C9\u7FA4\u3001oncall\u3001\u9000\u7FA4\u548C\u89E3\u6563\u3002","groups.search":"\u641C\u7D22\u7FA4\u540D / ID / owner","groups.missingOnly":"\u4EC5\u7F3A bot","groups.refresh":"\u5237\u65B0","groups.create":"\u65B0\u5EFA\u7FA4","groups.chat":"\u7FA4\u804A","groups.actions":"\u64CD\u4F5C","groups.addBots":"\u6DFB\u52A0 bot","groups.manage":"\u7BA1\u7406","groups.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u7FA4\u804A\u3002","groups.createTitle":"\u65B0\u5EFA\u7FA4\u804A","groups.createHelp":"\u9009\u62E9\u8981\u9080\u8BF7\u7684 bot\u3002dashboard \u4F1A\u81EA\u52A8\u9009\u62E9\u5728\u7EBF daemon \u4F5C\u4E3A\u521B\u5EFA\u8005\u3002","groups.name":"\u7FA4\u540D","groups.namePlaceholder":"\u4F8B\u5982 AI ChangeLog","groups.bindDir":"\u7ED1\u5B9A\u76EE\u5F55","groups.bindDirHelp":"\u65B0\u8BDD\u9898\u76F4\u63A5\u7528\u8BE5\u76EE\u5F55\u542F\u52A8 CLI\uFF0C\u8DF3\u8FC7 repo \u9009\u62E9\u3002","groups.botPicker":"Bot","groups.createSubmit":"\u521B\u5EFA","groups.cancel":"\u53D6\u6D88","groups.successTitle":"\u7FA4\u521B\u5EFA\u6210\u529F","groups.openGroup":"\u6253\u5F00\u65B0\u7FA4","groups.manageTitle":"\u7BA1\u7406 {name}","groups.owner":"\u7FA4\u4E3B","groups.oncall":"Oncall \u6A21\u5F0F","groups.oncallHelp":"\u5F00\u542F\u540E\uFF0C\u7FA4\u5185\u6210\u5458\u53EF @ \u673A\u5668\u4EBA\uFF1B\u65B0\u8BDD\u9898\u76F4\u63A5\u4F7F\u7528\u7ED1\u5B9A\u76EE\u5F55\u3002","groups.leaveTitle":"\u9009\u62E9\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.leaveSelected":"\u9009\u4E2D\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.disband":"\u89E3\u6563\u7FA4\u804A","groups.dangerHint":"\u89E3\u6563\u4EC5\u5F53\u5728\u7FA4\u673A\u5668\u4EBA\u662F\u7FA4\u4E3B\u65F6\u624D\u4F1A\u6210\u529F\uFF0C\u5426\u5219\u5EFA\u8BAE\u4F7F\u7528\u9000\u51FA\u7FA4\u804A\u3002","groups.save":"\u4FDD\u5B58","groups.needWorkingDir":"\u8BF7\u586B\u5DE5\u4F5C\u76EE\u5F55","groups.noBotsOnline":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u8BF7\u5148\u91CD\u542F daemon\u3002","schedules.title":"\u5B9A\u65F6\u4EFB\u52A1","schedules.subtitle":"\u8DE8 daemon \u67E5\u770B\u3001\u6682\u505C\u3001\u6062\u590D\u548C\u7ACB\u5373\u89E6\u53D1\u5B9A\u65F6\u4EFB\u52A1\u3002","schedules.search":"\u641C\u7D22\u540D\u79F0 / prompt / \u5DE5\u4F5C\u76EE\u5F55","schedules.anyKind":"\u5168\u90E8\u7C7B\u578B","schedules.enabledOnly":"\u4EC5\u542F\u7528","schedules.name":"\u540D\u79F0","schedules.bot":"bot","schedules.schedule":"\u89C4\u5219","schedules.next":"\u4E0B\u6B21","schedules.last":"\u4E0A\u6B21","schedules.repeat":"\u91CD\u590D","schedules.enabled":"\u542F\u7528","schedules.actions":"\u64CD\u4F5C","schedules.runNow":"\u7ACB\u5373\u8FD0\u884C","schedules.pause":"\u6682\u505C","schedules.resume":"\u6062\u590D","schedules.empty":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","botDefaults.title":"Bot \u9ED8\u8BA4 Oncall","botDefaults.subtitle":"\u914D\u7F6E\u6BCF\u4E2A bot \u5728\u65B0\u7FA4\u91CC\u7684\u9ED8\u8BA4 oncall \u884C\u4E3A\u3002","botDefaults.search":"\u641C\u7D22 bot \u540D / app id","botDefaults.refresh":"\u5237\u65B0","botDefaults.warning":"\u5F00\u542F\u540E\uFF0C\u6CA1\u6709 oncall binding \u7684\u7FA4\u4F1A\u5728\u4E0B\u6B21\u5F00\u65B0\u8BDD\u9898\u65F6\u81EA\u52A8\u7ED1\u5B9A\u5230\u8BE5\u76EE\u5F55\uFF1B\u624B\u52A8\u7ED1\u5B9A\u6216\u624B\u52A8\u89E3\u7ED1\u8FC7\u7684\u7FA4\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002","botDefaults.empty":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u5148 botmux restart \u8BA9 daemon \u4E0A\u7EBF\u3002","botDefaults.defaultOncall":"\u9ED8\u8BA4\u8FDB\u5165 oncall \u6A21\u5F0F","botDefaults.defaultOncallHelp":"\u6240\u6709\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8\u7ED1\u5B9A","botDefaults.workingDir":"\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55","botDefaults.lastEnabled":"\u4E0A\u6B21\u542F\u7528\u65F6\u95F4","botDefaults.autobound":"\u5DF2\u81EA\u52A8\u7ED1\u5B9A {count} \u4E2A\u7FA4","botDefaults.save":"\u4FDD\u5B58","botDefaults.required":"\u5F00\u542F\u65F6\u5FC5\u987B\u586B\u5DE5\u4F5C\u76EE\u5F55","common.none":"\u65E0","common.unknown":"\u672A\u77E5","common.now":"\u521A\u521A","common.never":"\u4ECE\u672A","nav.workflows":"\u5DE5\u4F5C\u6D41(beta)","nav.workflowCatalog":"\u76EE\u5F55","workflow.subnav.runs":"\u8FD0\u884C","workflow.subnav.catalog":"\u76EE\u5F55","workflow.searchPlaceholder":"\u641C\u7D22 runId / workflowId / chatId","workflow.filter.nonTerminal":"\u975E\u7EC8\u6001","workflow.filter.all":"\u5168\u90E8","workflow.status.pending":"\u5F85\u5F00\u59CB","workflow.status.running":"\u8FD0\u884C\u4E2D","workflow.status.waiting":"\u7B49\u5F85\u4E2D","workflow.status.effectAttempting":"\u526F\u4F5C\u7528\u4E2D","workflow.status.timedOut":"\u5DF2\u8D85\u65F6","workflow.status.succeeded":"\u6210\u529F","workflow.status.failed":"\u5931\u8D25","workflow.status.cancelled":"\u5DF2\u53D6\u6D88","workflow.table.run":"\u8FD0\u884C","workflow.table.workflow":"\u5DE5\u4F5C\u6D41","workflow.table.status":"\u72B6\u6001","workflow.table.lastSeq":"\u6700\u540E\u5E8F\u53F7","workflow.table.dangling":"\u60AC\u6302 dEf/dAct/dWait","workflow.table.updated":"\u66F4\u65B0\u65F6\u95F4","workflow.table.chatApp":"\u7FA4\u804A / \u5E94\u7528","workflow.list.failedLoad":"\u52A0\u8F7D\u5931\u8D25\uFF1A{error}","workflow.list.noRuns":"\u6CA1\u6709\u5339\u914D\u7684\u8FD0\u884C\u3002","workflow.list.noFilterMatch":"\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u8FD0\u884C\u3002","workflow.list.loaded":"{count} \u4E2A\u8FD0\u884C \xB7 \u5237\u65B0\u4E8E {time}","workflow.list.error":"\u9519\u8BEF\uFF1A{error}","workflow.detail.back":"\u8FD4\u56DE","workflow.detail.loading":"\u52A0\u8F7D\u4E2D...","workflow.detail.loadFailed":"\u52A0\u8F7D\u5931\u8D25","workflow.detail.cancel":"\u53D6\u6D88","workflow.detail.cliCancelOnly":"\u4EC5 CLI \u53EF\u53D6\u6D88","workflow.detail.cancelTitle":"\u53D6\u6D88\u8FD9\u4E2A\u5DE5\u4F5C\u6D41\u8FD0\u884C","workflow.detail.cliCancelTitle":"\u65E0\u6CD5\u5728\u9875\u9762\u53D6\u6D88\uFF1A\u8BF7\u4F7F\u7528 botmux workflow cancel {runId}","workflow.detail.nodes":"\u8282\u70B9 / Activity","workflow.detail.parallel":"\u5E76\u53D1\u6267\u884C","workflow.detail.parallelMeta":"{count} \u6B21\u5C1D\u8BD5 \xB7 \u6700\u9AD8\u5E76\u53D1 {max} \xB7 \u8FD0\u884C\u4E2D {running}","workflow.detail.noParallelData":"\u8FD8\u6CA1\u6709 attempt \u65F6\u95F4\u6570\u636E\u3002","workflow.detail.parallelNow":"\u73B0\u5728","workflow.detail.node":"\u8282\u70B9","workflow.detail.nodeStatus":"\u8282\u70B9\u72B6\u6001","workflow.detail.activity":"Activity","workflow.detail.activityStatus":"Activity \u72B6\u6001","workflow.detail.attempts":"\u5C1D\u8BD5\u6B21\u6570","workflow.detail.current":"\u5F53\u524D\u5C1D\u8BD5","workflow.detail.detail":"\u8BE6\u60C5","workflow.detail.nodeIO":"\u8282\u70B9\u8F93\u5165\u8F93\u51FA","workflow.detail.timeline":"\u65F6\u95F4\u7EBF","workflow.detail.loadOlder":"\u52A0\u8F7D\u66F4\u65E9\u4E8B\u4EF6","workflow.detail.seq":"\u5E8F\u53F7","workflow.detail.actor":"\u6267\u884C\u8005","workflow.detail.error":"\u9519\u8BEF","workflow.detail.event":"\u4E8B\u4EF6","workflow.detail.time":"\u65F6\u95F4","workflow.detail.refreshed":"\u5237\u65B0\u4E8E {time}","workflow.detail.unknownRun":"\u672A\u77E5\u8FD0\u884C","workflow.detail.snapshotHttp":"snapshot HTTP {status}","workflow.detail.eventsHttp":"events HTTP {status}","workflow.detail.cancelUnavailable":"\u65E0\u6CD5\u53D6\u6D88\uFF1A\u8BF7\u4F7F\u7528 botmux workflow cancel {runId}","workflow.detail.cancelConfirm":`\u786E\u8BA4\u53D6\u6D88\u5DE5\u4F5C\u6D41\u8FD0\u884C {runId}\uFF1F
2
+
3
+ {total} \u4E2A\u60AC\u6302\u9879\u4F1A\u7531 cancel recovery \u5904\u7406\u3002
4
+ effects={effects}, activities={activities}, waits={waits}, cancels={cancels}`,"workflow.detail.writeAccessCancel":"\u9700\u8981\u5199\u6743\u9650\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C `botmux dashboard` \u83B7\u53D6\u4E00\u6B21\u6027 URL\uFF0C\u6253\u5F00\u540E\u5199\u5165 cookie\uFF0C\u518D\u56DE\u6765\u70B9\u51FB\u53D6\u6D88\u3002","workflow.detail.cancelHttp":"cancel HTTP {status}","workflow.detail.cancelPending":"\u53D6\u6D88\u5DF2\u63D0\u4EA4\uFF1B\u7B49\u5F85\u8FD0\u884C\u4E2D\u7684 activity \u6536\u655B","workflow.detail.writeAccessApproval":"\u9700\u8981\u5199\u6743\u9650\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C `botmux dashboard` \u83B7\u53D6\u4E00\u6B21\u6027 URL\uFF0C\u6253\u5F00\u540E\u5199\u5165 cookie\uFF0C\u518D\u56DE\u6765\u5BA1\u6279\u3002","workflow.detail.actionHttp":"{action} HTTP {status}","workflow.detail.approved":"\u5DF2\u901A\u8FC7","workflow.detail.rejected":"\u5DF2\u62D2\u7EDD","workflow.detail.alreadyTerminal":"\u8FD0\u884C\u5DF2\u7EC8\u6001\uFF1B\u672A\u5E94\u7528\u201C{label}\u201D\u3002","workflow.detail.workflowContinue":"{label}\uFF1B\u7B49\u5F85\u5DE5\u4F5C\u6D41\u7EE7\u7EED\u6267\u884C\u3002","workflow.detail.workflowRefreshing":"{label}\uFF1B\u6B63\u5728\u5237\u65B0\u5DE5\u4F5C\u6D41\u72B6\u6001\u3002","workflow.detail.eventsLoaded":"\u5DF2\u52A0\u8F7D {loaded}/{total} \u4E2A\u4E8B\u4EF6","workflow.detail.dangling":"\u60AC\u6302\u9879","workflow.detail.noDangling":"\u6CA1\u6709\u60AC\u6302\u5DE5\u4F5C\u3002","workflow.detail.none":"\u65E0","workflow.detail.noNodes":"\u8FD8\u6CA1\u6709\u8282\u70B9\u3002","workflow.detail.idle":"\u7A7A\u95F2","workflow.detail.noNodeIO":"\u8FD8\u6CA1\u6709\u8282\u70B9\u8F93\u5165\u8F93\u51FA\u3002","workflow.detail.notDispatched":"\u5C1A\u672A\u6D3E\u53D1","workflow.detail.noAttempt":"\u8FD8\u6CA1\u6709\u5C1D\u8BD5","workflow.detail.attempt":"\u5C1D\u8BD5","workflow.detail.authoredInput":"\u539F\u59CB\u8F93\u5165","workflow.detail.resolvedInput":"\u89E3\u6790\u540E\u8F93\u5165","workflow.detail.output":"\u8F93\u51FA","workflow.detail.executionLog":"\u6267\u884C\u65E5\u5FD7","workflow.detail.liveTerminal":"\u5B9E\u65F6\u7EC8\u7AEF","workflow.detail.terminalLive":"\u5728\u7EBF","workflow.detail.terminalClosedShort":"\u5DF2\u5173\u95ED","workflow.detail.terminalClosed":"\u7EC8\u7AEF\u5DF2\u5173\u95ED\u3002\u8BF7\u67E5\u770B\u4E0B\u65B9\u6267\u884C\u65E5\u5FD7\u83B7\u53D6\u6700\u7EC8\u8BB0\u5F55\u3002","workflow.detail.openTerminalNewTab":"\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u7EC8\u7AEF","workflow.detail.terminalReplay":"\u7EC8\u7AEF\u56DE\u653E","workflow.detail.openReplayNewTab":"\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u56DE\u653E","workflow.detail.downloadFullLog":"\u4E0B\u8F7D\u5B8C\u6574\u65E5\u5FD7","workflow.detail.terminalResume":"\u8C03\u8BD5\u4F1A\u8BDD","workflow.detail.openResumeNewTab":"\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u8C03\u8BD5\u4F1A\u8BDD","workflow.detail.resumeSession":"\u7EE7\u7EED\u4F1A\u8BDD","workflow.detail.resumeStarting":"\u6B63\u5728\u542F\u52A8\u2026","workflow.detail.endResumeSession":"\u7ED3\u675F\u8C03\u8BD5\u4F1A\u8BDD","workflow.detail.resumeEnding":"\u7ED3\u675F\u4E2D\u2026","workflow.detail.resumeUnsupportedCli":"{cliId} CLI \u4E0D\u652F\u6301 resume","workflow.detail.resumeMissingCliSession":"\u7F3A\u5C11 cliSessionId\uFF0C\u65E0\u6CD5 resume","workflow.detail.resumeStartFailed":"\u542F\u52A8\u8C03\u8BD5\u4F1A\u8BDD\u5931\u8D25 (HTTP {status})","workflow.detail.resumeEndFailed":"\u7ED3\u675F\u8C03\u8BD5\u4F1A\u8BDD\u5931\u8D25 (HTTP {status})","workflow.detail.writeAccessResume":"\u9700\u8981\u5199\u5165\u6743\u9650\u624D\u80FD resume \u4F1A\u8BDD\u3002","workflow.detail.waitPrompt":"\u7B49\u5F85\u63D0\u793A","workflow.detail.approvalComment":"\u5BA1\u6279\u5907\u6CE8","workflow.detail.optionalComment":"\u53EF\u9009\u5907\u6CE8","workflow.detail.approve":"\u901A\u8FC7","workflow.detail.reject":"\u62D2\u7EDD","workflow.detail.submitting":"\u63D0\u4EA4\u4E2D...","workflow.detail.empty":"\u7A7A","workflow.detail.truncated":"\u5DF2\u622A\u65AD","workflow.detail.noData":"\u6CA1\u6709\u6570\u636E\u3002","workflow.detail.noPreview":"\u6CA1\u6709\u9884\u89C8\u3002","workflow.detail.open":"\u6253\u5F00","workflow.detail.deadline":"\u622A\u6B62","workflow.detail.effect":"\u526F\u4F5C\u7528","workflow.detail.wait":"\u7B49\u5F85","workflow.detail.noEvents":"\u8FD8\u6CA1\u6709\u4E8B\u4EF6\u3002","workflow.summary.workflow":"\u5DE5\u4F5C\u6D41","workflow.summary.status":"\u72B6\u6001","workflow.summary.lastSeq":"\u6700\u540E\u5E8F\u53F7","workflow.summary.updated":"\u66F4\u65B0\u65F6\u95F4","workflow.summary.revision":"\u4FEE\u8BA2","workflow.summary.initiator":"\u53D1\u8D77\u4EBA","workflow.summary.failedNode":"\u5931\u8D25\u8282\u70B9","workflow.summary.cancelOrigin":"\u53D6\u6D88\u6765\u6E90","workflow.summary.chat":"\u7FA4\u804A","workflow.summary.app":"\u5E94\u7528","workflow.dangling.activities":"Activities","workflow.dangling.effects":"Effects","workflow.dangling.waits":"Waits","workflow.dangling.cancels":"Cancels","catalog.title":"\u5DE5\u4F5C\u6D41\u76EE\u5F55","catalog.subtitle":"\u4ECE\u5DF2\u4FDD\u5B58\u7684 workflow \u5B9A\u4E49\u521B\u5EFA\u4E00\u6B21\u8FD0\u884C\u3002","catalog.searchPlaceholder":"\u641C\u7D22 workflowId / \u8DEF\u5F84","catalog.refresh":"\u5237\u65B0","catalog.loading":"\u6B63\u5728\u52A0\u8F7D\u76EE\u5F55...","catalog.loadFailed":"\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25\uFF1A{error}","catalog.noDefinitions":"\u6CA1\u6709\u627E\u5230 workflow \u5B9A\u4E49\u3002","catalog.noFilterMatch":"\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u5B9A\u4E49\u3002","catalog.table.workflow":"\u5DE5\u4F5C\u6D41","catalog.table.version":"\u7248\u672C","catalog.table.params":"\u53C2\u6570","catalog.table.nodes":"\u8282\u70B9","catalog.table.revision":"\u4FEE\u8BA2","catalog.table.path":"\u8DEF\u5F84","catalog.paramSummary":"{required}/{total} \u5FC5\u586B","catalog.back":"\u8FD4\u56DE\u76EE\u5F55","catalog.detailTitle":"\u5DE5\u4F5C\u6D41\u5B9A\u4E49","catalog.definitionLoadFailed":"\u5B9A\u4E49\u52A0\u8F7D\u5931\u8D25\uFF1A{error}","catalog.summary":"\u6458\u8981","catalog.paramsSchema":"\u53C2\u6570 Schema","catalog.definitionJson":"\u5B9A\u4E49 JSON","catalog.runPanel":"\u8FD0\u884C\u5DE5\u4F5C\u6D41","catalog.paramsJson":"\u53C2\u6570 JSON","catalog.paramsPlaceholder":`{
5
+ "city": "\u5317\u4EAC"
6
+ }`,"catalog.chatId":"\u7FA4\u804A ID","catalog.larkAppId":"\u98DE\u4E66\u5E94\u7528 ID","catalog.chatBindingHint":"\u5FC5\u586B\uFF0C\u7528\u4E8E\u786E\u5B9A humanGate \u5361\u7247\u53D1\u9001\u5230\u54EA\u4E2A\u98DE\u4E66\u7FA4\uFF0C\u4EE5\u53CA\u53D6\u6D88\u8DEF\u7531\u5F52\u5C5E\u3002","catalog.run":"\u8FD0\u884C","catalog.running":"\u542F\u52A8\u4E2D...","catalog.badParamsJson":"\u53C2\u6570\u5FC5\u987B\u662F JSON object\u3002","catalog.writeAccess":"\u9700\u8981\u5199\u6743\u9650\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C `botmux dashboard` \u83B7\u53D6\u4E00\u6B21\u6027 URL\uFF0C\u6253\u5F00\u540E\u5199\u5165 cookie\uFF0C\u518D\u56DE\u6765\u8FD0\u884C\u3002","catalog.runHttp":"run HTTP {status}","catalog.runStarted":"\u8FD0\u884C\u5DF2\u542F\u52A8\uFF1B\u6B63\u5728\u6253\u5F00\u8BE6\u60C5\u9875...","catalog.invalidParams":"\u53C2\u6570\u65E0\u6548","catalog.issue":"{path}: {message}","catalog.noParams":"\u6CA1\u6709\u58F0\u660E\u53C2\u6570\u3002","catalog.required":"\u5FC5\u586B","catalog.optional":"\u53EF\u9009","catalog.default":"\u9ED8\u8BA4\u503C","catalog.description":"\u8BF4\u660E","catalog.path":"\u8DEF\u5F84","catalog.revision":"\u4FEE\u8BA2","catalog.nodeCount":"\u8282\u70B9\u6570"},It={"app.name":"botmux","app.subtitle":"Feishu AI CLI Control","time.secondsAgo":"{value}s ago","time.minutesAgo":"{value}m ago","time.hoursAgo":"{value}h ago","nav.overview":"Overview","nav.sessions":"Sessions","nav.groups":"Groups","nav.schedules":"Schedules","nav.botDefaults":"Bot Defaults","status.live":"Live","status.disconnected":"Disconnected","status.system":"System","status.light":"Light","status.dark":"Dark","status.language":"Language","status.theme":"Theme","botOnboarding.add":"Add Bot","botOnboarding.title":"Scan to Add Bot","botOnboarding.intro":"Scan with the Feishu app to create a PersonalAgent app. The dashboard writes it to local bots.json after success.","botOnboarding.starting":"Generating QR code...","botOnboarding.waiting":"Scan with the Feishu app to continue.","botOnboarding.verifying":"Scan accepted. Verifying credentials...","botOnboarding.completed":"Bot added.","botOnboarding.failed":"Add failed","botOnboarding.openLink":"Open scan link in browser","botOnboarding.close":"Close","botOnboarding.restartHint":"bots.json has been updated. Run pnpm daemon:restart for the new bot to take effect.","botOnboarding.qrAlt":"Feishu bot onboarding QR code","overview.title":"Control Overview","overview.subtitle":"A realtime control plane across bots, chats, CLI sessions, and schedules.","overview.openSessions":"Active Sessions","overview.workingSessions":"Working","overview.onlineBots":"Online Bots","overview.schedules":"Schedules","overview.groups":"Groups Seen","overview.enabledSchedules":"Enabled","overview.total":"total","overview.active":"active","overview.daemonRegistry":"daemon registry","overview.chatMatrix":"chat matrix","overview.recentSessions":"Recent Sessions","overview.nextSchedules":"Next Runs","overview.noSessions":"No sessions yet.","overview.noSchedules":"No schedules yet.","sessions.title":"Session Control","sessions.subtitle":"Locate Feishu topics, open Web Terminal, close or resume CLI sessions.","sessions.search":"Search working dir / title / IDs","sessions.anyStatus":"Any status","sessions.adoptAny":"adopt: any","sessions.adoptYes":"adopt: yes","sessions.adoptNo":"adopt: no","sessions.activeOnly":"Active only","sessions.closeSelected":"Close selected","sessions.clearSelection":"Clear","sessions.selectedCount":"{count} sessions selected","sessions.bot":"Bot","sessions.cli":"CLI","sessions.status":"Status","sessions.titleCol":"Title","sessions.workingDir":"Working Dir","sessions.created":"Created","sessions.last":"Last","sessions.adopt":"Adopt","sessions.actions":"Actions","sessions.details":"Details","sessions.copy":"Copy","sessions.copied":"Copied","sessions.locate":"Locate Topic","sessions.locating":"Sending...","sessions.cooldown":"Cooldown {seconds}s","sessions.openTerminal":"Terminal","sessions.close":"Close Session","sessions.resume":"Resume Session","sessions.dismiss":"Close","sessions.closeConfirm":"Close this session?","sessions.resumeFailed":"Resume failed","sessions.closeBulkConfirm":"Close {count} selected sessions?","sessions.empty":"No sessions match the filters.","groups.title":"Groups & Bots","groups.subtitle":"Inspect the chat x bot matrix and manage group creation, oncall, leave, and disband flows.","groups.search":"Search chat name / ID / owner","groups.missingOnly":"Missing bot only","groups.refresh":"Refresh","groups.create":"New Group","groups.chat":"Chat","groups.actions":"Actions","groups.addBots":"Add Bots","groups.manage":"Manage","groups.empty":"No chats match the filters.","groups.createTitle":"Create New Group","groups.createHelp":"Pick bots to invite. The dashboard chooses an online daemon as creator.","groups.name":"Group Name","groups.namePlaceholder":"e.g. AI ChangeLog","groups.bindDir":"Bind Directory","groups.bindDirHelp":"New topics start the CLI here and skip repo selection.","groups.botPicker":"Bots","groups.createSubmit":"Create","groups.cancel":"Cancel","groups.successTitle":"Group Created","groups.openGroup":"Open Group","groups.manageTitle":"Manage {name}","groups.owner":"Owner","groups.oncall":"Oncall Mode","groups.oncallHelp":"When enabled, group members can @ the bot; new topics use the bound directory.","groups.leaveTitle":"Select Bots to Leave","groups.leaveSelected":"Selected Bots Leave","groups.disband":"Disband Group","groups.dangerHint":"Disband only works when an in-chat bot is the owner. Otherwise prefer leaving the chat.","groups.save":"Save","groups.needWorkingDir":"Working directory is required","groups.noBotsOnline":"No bots online. Restart the daemon first.","schedules.title":"Schedules","schedules.subtitle":"View, pause, resume, and run scheduled tasks across daemons.","schedules.search":"Search name / prompt / working dir","schedules.anyKind":"Any kind","schedules.enabledOnly":"Enabled only","schedules.name":"Name","schedules.bot":"Bot","schedules.schedule":"Schedule","schedules.next":"Next","schedules.last":"Last","schedules.repeat":"Repeat","schedules.enabled":"Enabled","schedules.actions":"Actions","schedules.runNow":"Run Now","schedules.pause":"Pause","schedules.resume":"Resume","schedules.empty":"No schedules.","botDefaults.title":"Bot Default Oncall","botDefaults.subtitle":"Configure each bot's default oncall behavior in new chats.","botDefaults.search":"Search bot name / app id","botDefaults.refresh":"Refresh","botDefaults.warning":"When enabled, chats without an oncall binding auto-bind to this directory on their next new topic. Manually bound or unbound chats are preserved.","botDefaults.empty":"No bots online. Run botmux restart first.","botDefaults.defaultOncall":"Default to oncall mode","botDefaults.defaultOncallHelp":"Unbound chats auto-bind on the next new topic","botDefaults.workingDir":"Default Working Directory","botDefaults.lastEnabled":"Last Enabled","botDefaults.autobound":"{count} chats auto-bound","botDefaults.save":"Save","botDefaults.required":"Working directory is required when enabled","common.none":"None","common.unknown":"Unknown","common.now":"now","common.never":"never","nav.workflows":"Workflows (beta)","nav.workflowCatalog":"Catalog","workflow.subnav.runs":"Runs","workflow.subnav.catalog":"Catalog","workflow.searchPlaceholder":"search runId / workflowId / chatId","workflow.filter.nonTerminal":"non-terminal","workflow.filter.all":"all","workflow.status.pending":"pending","workflow.status.running":"running","workflow.status.waiting":"waiting","workflow.status.effectAttempting":"effect","workflow.status.timedOut":"timed out","workflow.status.succeeded":"succeeded","workflow.status.failed":"failed","workflow.status.cancelled":"cancelled","workflow.table.run":"run","workflow.table.workflow":"workflow","workflow.table.status":"status","workflow.table.lastSeq":"lastSeq","workflow.table.dangling":"dEf/dAct/dWait","workflow.table.updated":"updated","workflow.table.chatApp":"chat / app","workflow.list.failedLoad":"Failed to load: {error}","workflow.list.noRuns":"No runs match.","workflow.list.noFilterMatch":"No runs match this filter.","workflow.list.loaded":"{count} runs \xB7 refreshed {time}","workflow.list.error":"error: {error}","workflow.detail.back":"Back","workflow.detail.loading":"Loading...","workflow.detail.loadFailed":"Load failed","workflow.detail.cancel":"Cancel","workflow.detail.cliCancelOnly":"CLI cancel only","workflow.detail.cancelTitle":"Cancel this workflow run","workflow.detail.cliCancelTitle":"Cancel unavailable: use botmux workflow cancel {runId}","workflow.detail.nodes":"Nodes / Activities","workflow.detail.parallel":"Parallel execution","workflow.detail.parallelMeta":"{count} attempt(s) \xB7 max parallel {max} \xB7 running {running}","workflow.detail.noParallelData":"No attempt timing data yet.","workflow.detail.parallelNow":"now","workflow.detail.node":"node","workflow.detail.nodeStatus":"node status","workflow.detail.activity":"activity","workflow.detail.activityStatus":"activity status","workflow.detail.attempts":"attempts","workflow.detail.current":"current","workflow.detail.detail":"detail","workflow.detail.nodeIO":"Node I/O","workflow.detail.timeline":"Timeline","workflow.detail.loadOlder":"Load older","workflow.detail.seq":"seq","workflow.detail.actor":"actor","workflow.detail.error":"error","workflow.detail.event":"event","workflow.detail.time":"time","workflow.detail.refreshed":"refreshed {time}","workflow.detail.unknownRun":"unknown run","workflow.detail.snapshotHttp":"snapshot HTTP {status}","workflow.detail.eventsHttp":"events HTTP {status}","workflow.detail.cancelUnavailable":"cancel unavailable: use botmux workflow cancel {runId}","workflow.detail.cancelConfirm":`Cancel workflow run {runId}?
7
+
8
+ {total} dangling item(s) will be handled by cancel-driven recovery.
9
+ effects={effects}, activities={activities}, waits={waits}, cancels={cancels}`,"workflow.detail.writeAccessCancel":"write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and click cancel again.","workflow.detail.cancelHttp":"cancel HTTP {status}","workflow.detail.cancelPending":"cancel pending; waiting for running activity to drain","workflow.detail.writeAccessApproval":"write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and approve/reject again.","workflow.detail.actionHttp":"{action} HTTP {status}","workflow.detail.approved":"approved","workflow.detail.rejected":"rejected","workflow.detail.alreadyTerminal":"Run already terminal; {label} was not applied.","workflow.detail.workflowContinue":"{label}; waiting for workflow to continue.","workflow.detail.workflowRefreshing":"{label}; refreshing workflow state.","workflow.detail.eventsLoaded":"{loaded}/{total} events loaded","workflow.detail.dangling":"Dangling","workflow.detail.noDangling":"No dangling work.","workflow.detail.none":"none","workflow.detail.noNodes":"No nodes yet.","workflow.detail.idle":"idle","workflow.detail.noNodeIO":"No node I/O yet.","workflow.detail.notDispatched":"not dispatched","workflow.detail.noAttempt":"No attempt yet","workflow.detail.attempt":"attempt","workflow.detail.authoredInput":"Authored input","workflow.detail.resolvedInput":"Resolved input","workflow.detail.output":"Output","workflow.detail.executionLog":"Execution log","workflow.detail.liveTerminal":"Live terminal","workflow.detail.terminalLive":"live","workflow.detail.terminalClosedShort":"closed","workflow.detail.terminalClosed":"Terminal is closed. Use the execution log below for the final transcript.","workflow.detail.openTerminalNewTab":"Open terminal in new tab","workflow.detail.terminalReplay":"Terminal replay","workflow.detail.openReplayNewTab":"Open replay in new tab","workflow.detail.downloadFullLog":"Download full log","workflow.detail.terminalResume":"Debug session","workflow.detail.openResumeNewTab":"Open debug session in new tab","workflow.detail.resumeSession":"Resume session","workflow.detail.resumeStarting":"Starting\u2026","workflow.detail.endResumeSession":"End debug session","workflow.detail.resumeEnding":"Ending\u2026","workflow.detail.resumeUnsupportedCli":'CLI "{cliId}" does not support resume',"workflow.detail.resumeMissingCliSession":"Missing cliSessionId \u2014 cannot resume","workflow.detail.resumeStartFailed":"Failed to start debug session (HTTP {status})","workflow.detail.resumeEndFailed":"Failed to end debug session (HTTP {status})","workflow.detail.writeAccessResume":"Resume requires dashboard write access.","workflow.detail.waitPrompt":"Wait prompt","workflow.detail.approvalComment":"Approval comment","workflow.detail.optionalComment":"Optional comment","workflow.detail.approve":"Approve","workflow.detail.reject":"Reject","workflow.detail.submitting":"Submitting...","workflow.detail.empty":"empty","workflow.detail.truncated":"truncated","workflow.detail.noData":"No data.","workflow.detail.noPreview":"No preview.","workflow.detail.open":"open","workflow.detail.deadline":"deadline","workflow.detail.effect":"effect","workflow.detail.wait":"wait","workflow.detail.noEvents":"No events.","workflow.summary.workflow":"workflow","workflow.summary.status":"status","workflow.summary.lastSeq":"lastSeq","workflow.summary.updated":"updated","workflow.summary.revision":"revision","workflow.summary.initiator":"initiator","workflow.summary.failedNode":"failedNode","workflow.summary.cancelOrigin":"cancelOrigin","workflow.summary.chat":"chat","workflow.summary.app":"app","workflow.dangling.activities":"activities","workflow.dangling.effects":"effects","workflow.dangling.waits":"waits","workflow.dangling.cancels":"cancels","catalog.title":"Workflow catalog","catalog.subtitle":"Create a workflow run from a saved workflow definition.","catalog.searchPlaceholder":"search workflowId / path","catalog.refresh":"Refresh","catalog.loading":"Loading catalog...","catalog.loadFailed":"Failed to load catalog: {error}","catalog.noDefinitions":"No workflow definitions found.","catalog.noFilterMatch":"No definitions match this filter.","catalog.table.workflow":"workflow","catalog.table.version":"version","catalog.table.params":"params","catalog.table.nodes":"nodes","catalog.table.revision":"revision","catalog.table.path":"path","catalog.paramSummary":"{required}/{total} required","catalog.back":"Back to catalog","catalog.detailTitle":"Workflow definition","catalog.definitionLoadFailed":"Failed to load definition: {error}","catalog.summary":"Summary","catalog.paramsSchema":"Params schema","catalog.definitionJson":"Definition JSON","catalog.runPanel":"Run workflow","catalog.paramsJson":"Params JSON","catalog.paramsPlaceholder":`{
10
+ "city": "\u5317\u4EAC"
11
+ }`,"catalog.chatId":"Chat ID","catalog.larkAppId":"Lark app ID","catalog.chatBindingHint":"Required so humanGate cards and cancel routing know which Lark chat owns the run.","catalog.run":"Run","catalog.running":"Starting...","catalog.badParamsJson":"Params must be a JSON object.","catalog.writeAccess":"write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and run again.","catalog.runHttp":"run HTTP {status}","catalog.runStarted":"Run started; opening detail page...","catalog.invalidParams":"Invalid params","catalog.issue":"{path}: {message}","catalog.noParams":"No params declared.","catalog.required":"required","catalog.optional":"optional","catalog.default":"default","catalog.description":"description","catalog.path":"path","catalog.revision":"revision","catalog.nodeCount":"nodes"},qe={zh:St,en:It};function Pe(e){if(typeof e!="string")return null;let n=e.trim().toLowerCase();return n==="zh"||n.startsWith("zh-")?"zh":n==="en"||n.startsWith("en-")?"en":null}function Tt(e=[]){for(let n of e){let o=Pe(n);if(o)return o}return"zh"}function fe(e){return(n,o)=>{let r=qe[e][n]??qe.zh[n]??n;return o?r.replace(/\{(\w+)\}/g,(s,a)=>{let c=o[a];return c==null?`{${a}}`:String(c)}):r}}function Ne(e,n){return(e?Pe(e.getItem($e)):null)??Tt(n)}var Se="botmux.dashboard.theme";function Lt(e){return e==="system"||e==="light"||e==="dark"?e:null}function Be(e,n){return e==="system"?n?"dark":"light":e}function je(e){return Lt(e?.getItem(Se))??"system"}var Ie=class{locale="zh";themeMode="system";resolvedTheme="light";listeners=new Set;translate=fe(this.locale);mediaQuery=null;init(){let n=typeof window<"u"?window:void 0;this.locale=Ne(n?.localStorage,Et()),this.translate=fe(this.locale),this.themeMode=je(n?.localStorage),this.mediaQuery=n?.matchMedia?.("(prefers-color-scheme: dark)")??null,this.mediaQuery?.addEventListener("change",()=>{this.applyTheme(),this.emit()}),this.applyTheme(),this.applyLocale()}t(n,o){return this.translate(n,o)}setLocale(n){this.locale!==n&&(this.locale=n,this.translate=fe(n),window.localStorage.setItem($e,n),this.applyLocale(),this.emit())}setThemeMode(n){this.themeMode!==n&&(this.themeMode=n,window.localStorage.setItem(Se,n),this.applyTheme(),this.emit())}on(n){return this.listeners.add(n),()=>this.listeners.delete(n)}emit(){for(let n of this.listeners)n()}applyTheme(){this.resolvedTheme=Be(this.themeMode,!!this.mediaQuery?.matches),document.documentElement.dataset.theme=this.resolvedTheme,document.documentElement.dataset.themeMode=this.themeMode}applyLocale(){document.documentElement.lang=this.locale==="zh"?"zh-CN":"en"}};function Et(){return typeof navigator>"u"?[]:navigator.languages?.length?navigator.languages:[navigator.language].filter(Boolean)}var J=new Ie;function t(e,n){return J.t(e,n)}function w(e){return e.replace(/[&<>"']/g,n=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[n])}function te(e){if(!e)return"-";let n=Date.now()-e;return n<6e4?t("common.now"):n<36e5?Math.floor(n/6e4)+"m":n<864e5?Math.floor(n/36e5)+"h":Math.floor(n/864e5)+"d"}var Te={chats:[],bots:[]};async function Mt(){try{let e=await fetch("/api/groups");if(!e.ok)return;Te=await e.json()}catch{}}function At(e){return`status status-${w(e||"unknown")}`}function Ct(e){return`<li class="overview-list-row">
2
12
  <div>
3
- <strong>${o(t.title??t.sessionId)}</strong>
4
- <span>${o(t.botName??"")} \xB7 ${o(t.cliId??"unknown")}</span>
13
+ <strong>${w(e.title??e.sessionId)}</strong>
14
+ <span>${w(e.botName??"")} \xB7 ${w(e.cliId??"unknown")}</span>
5
15
  </div>
6
- <span class="${Ie(t.status)}">${o(t.status??"unknown")}</span>
7
- </li>`}function Ee(t){let s=t.nextRunAt?new Date(t.nextRunAt).toLocaleString():"-";return`<li class="overview-list-row">
16
+ <span class="${At(e.status)}">${w(e.status??"unknown")}</span>
17
+ </li>`}function Ht(e){let n=e.nextRunAt?new Date(e.nextRunAt).toLocaleString():"-";return`<li class="overview-list-row">
8
18
  <div>
9
- <strong>${o(t.name??t.id)}</strong>
10
- <span>${o(t.botName??t.larkAppId??"")} \xB7 ${o(t.parsed?.display??"")}</span>
19
+ <strong>${w(e.name??e.id)}</strong>
20
+ <span>${w(e.botName??e.larkAppId??"")} \xB7 ${w(e.parsed?.display??"")}</span>
11
21
  </div>
12
- <span>${o(s)}</span>
13
- </li>`}async function ae(t){t.innerHTML=`<section class="page hero-page">
22
+ <span>${w(n)}</span>
23
+ </li>`}async function Ue(e){e.innerHTML=`<section class="page hero-page">
14
24
  <div class="page-heading">
15
25
  <div>
16
- <p class="eyebrow">${e("app.subtitle")}</p>
17
- <h1>${e("overview.title")}</h1>
18
- <p>${e("overview.subtitle")}</p>
26
+ <p class="eyebrow">${t("app.subtitle")}</p>
27
+ <h1>${t("overview.title")}</h1>
28
+ <p>${t("overview.subtitle")}</p>
19
29
  </div>
20
30
  </div>
21
31
  <div class="metric-grid" id="overview-metrics"></div>
@@ -23,251 +33,251 @@
23
33
  <section class="panel">
24
34
  <header class="panel-header">
25
35
  <div>
26
- <h2>${e("overview.recentSessions")}</h2>
27
- <p>${e("sessions.subtitle")}</p>
36
+ <h2>${t("overview.recentSessions")}</h2>
37
+ <p>${t("sessions.subtitle")}</p>
28
38
  </div>
29
- <a class="btn-link" href="#/sessions">${e("nav.sessions")}</a>
39
+ <a class="btn-link" href="#/sessions">${t("nav.sessions")}</a>
30
40
  </header>
31
41
  <ul class="overview-list" id="recent-sessions"></ul>
32
42
  </section>
33
43
  <section class="panel">
34
44
  <header class="panel-header">
35
45
  <div>
36
- <h2>${e("overview.nextSchedules")}</h2>
37
- <p>${e("schedules.subtitle")}</p>
46
+ <h2>${t("overview.nextSchedules")}</h2>
47
+ <p>${t("schedules.subtitle")}</p>
38
48
  </div>
39
- <a class="btn-link" href="#/schedules">${e("nav.schedules")}</a>
49
+ <a class="btn-link" href="#/schedules">${t("nav.schedules")}</a>
40
50
  </header>
41
51
  <ul class="overview-list" id="next-schedules"></ul>
42
52
  </section>
43
53
  </div>
44
- </section>`;let s=t.querySelector("#overview-metrics"),r=t.querySelector("#recent-sessions"),m=t.querySelector("#next-schedules");function v(){let c=[...C.sessions.values()],T=[...C.schedules.values()],f=c.filter(h=>h.status!=="closed"),E=c.filter(h=>h.status==="working"||h.status==="analyzing"||h.status==="starting"),S=T.filter(h=>h.enabled),y=Y.bots?.length||new Set(c.map(h=>h.larkAppId).filter(Boolean)).size,D=[{label:e("overview.openSessions"),value:f.length,meta:`${c.length} ${e("overview.total")}`},{label:e("overview.workingSessions"),value:E.length,meta:`${f.length} ${e("overview.active")}`},{label:e("overview.onlineBots"),value:y,meta:e("overview.daemonRegistry")},{label:e("overview.schedules"),value:T.length,meta:`${S.length} ${e("overview.enabledSchedules")}`},{label:e("overview.groups"),value:Y.chats?.length??0,meta:e("overview.chatMatrix")}];s.innerHTML=D.map(h=>`<article class="metric-card">
45
- <span>${o(h.label)}</span>
46
- <strong>${h.value}</strong>
47
- <small>${o(h.meta)}</small>
48
- </article>`).join("");let i=c.sort((h,d)=>Number(d.lastMessageAt??0)-Number(h.lastMessageAt??0)).slice(0,6);r.innerHTML=i.length?i.map(h=>Me({...h,title:h.title??`${j(h.lastMessageAt)} \xB7 ${h.sessionId}`})).join(""):`<li class="empty">${e("overview.noSessions")}</li>`;let k=T.filter(h=>h.nextRunAt).sort((h,d)=>Date.parse(h.nextRunAt)-Date.parse(d.nextRunAt)).slice(0,6);m.innerHTML=k.length?k.map(Ee).join(""):`<li class="empty">${e("overview.noSchedules")}</li>`}C.on(v),v(),Te().then(v)}function B(t,s){return`<th data-sort="${t}" data-label="${o(s)}">${o(s)}</th>`}var re=["claude-code","codex","cursor","gemini","opencode","aiden","coco","unknown"];function De(){return`<div class="filter-check-group" role="group" aria-label="${e("sessions.cli")}">
49
- <span class="filter-check-label">${e("sessions.cli")}</span>
50
- ${re.map(t=>`
54
+ </section>`;let n=e.querySelector("#overview-metrics"),o=e.querySelector("#recent-sessions"),r=e.querySelector("#next-schedules");function s(){let a=[...D.sessions.values()],c=[...D.schedules.values()],p=a.filter(L=>L.status!=="closed"),y=a.filter(L=>L.status==="working"||L.status==="analyzing"||L.status==="starting"),S=c.filter(L=>L.enabled),v=Te.bots?.length||new Set(a.map(L=>L.larkAppId).filter(Boolean)).size,T=[{label:t("overview.openSessions"),value:p.length,meta:`${a.length} ${t("overview.total")}`},{label:t("overview.workingSessions"),value:y.length,meta:`${p.length} ${t("overview.active")}`},{label:t("overview.onlineBots"),value:v,meta:t("overview.daemonRegistry")},{label:t("overview.schedules"),value:c.length,meta:`${S.length} ${t("overview.enabledSchedules")}`},{label:t("overview.groups"),value:Te.chats?.length??0,meta:t("overview.chatMatrix")}];n.innerHTML=T.map(L=>`<article class="metric-card">
55
+ <span>${w(L.label)}</span>
56
+ <strong>${L.value}</strong>
57
+ <small>${w(L.meta)}</small>
58
+ </article>`).join("");let l=a.sort((L,g)=>Number(g.lastMessageAt??0)-Number(L.lastMessageAt??0)).slice(0,6);o.innerHTML=l.length?l.map(L=>Ct({...L,title:L.title??`${te(L.lastMessageAt)} \xB7 ${L.sessionId}`})).join(""):`<li class="empty">${t("overview.noSessions")}</li>`;let I=c.filter(L=>L.nextRunAt).sort((L,g)=>Date.parse(L.nextRunAt)-Date.parse(g.nextRunAt)).slice(0,6);r.innerHTML=I.length?I.map(Ht).join(""):`<li class="empty">${t("overview.noSchedules")}</li>`}D.on(s),s(),Mt().then(s)}function G(e,n){return`<th data-sort="${e}" data-label="${w(n)}">${w(n)}</th>`}var Fe=["claude-code","codex","cursor","gemini","opencode","aiden","coco","unknown"];function Rt(){return`<div class="filter-check-group" role="group" aria-label="${t("sessions.cli")}">
59
+ <span class="filter-check-label">${t("sessions.cli")}</span>
60
+ ${Fe.map(e=>`
51
61
  <label class="filter-check">
52
- <input type="checkbox" name="cli" value="${o(t)}" checked>
53
- <span>${o(t)}</span>
62
+ <input type="checkbox" name="cli" value="${w(e)}" checked>
63
+ <span>${w(e)}</span>
54
64
  </label>
55
65
  `).join("")}
56
- </div>`}function Ce(){return`<section class="page">
66
+ </div>`}function Ot(){return`<section class="page">
57
67
  <div class="page-heading">
58
68
  <div>
59
- <p class="eyebrow">${e("nav.sessions")}</p>
60
- <h1>${e("sessions.title")}</h1>
61
- <p>${e("sessions.subtitle")}</p>
69
+ <p class="eyebrow">${t("nav.sessions")}</p>
70
+ <h1>${t("sessions.title")}</h1>
71
+ <p>${t("sessions.subtitle")}</p>
62
72
  </div>
63
73
  </div>
64
74
  <form id="filters" class="filters sessions-filters">
65
- <input type="search" name="q" placeholder="${e("sessions.search")}" />
75
+ <input type="search" name="q" placeholder="${t("sessions.search")}" />
66
76
  <select name="status">
67
- <option value="">${e("sessions.anyStatus")}</option>
77
+ <option value="">${t("sessions.anyStatus")}</option>
68
78
  <option>starting</option><option>working</option><option>idle</option>
69
79
  <option>analyzing</option><option>active</option><option>closed</option>
70
80
  </select>
71
81
  <select name="adopt">
72
- <option value="">${e("sessions.adoptAny")}</option>
73
- <option value="yes">${e("sessions.adoptYes")}</option>
74
- <option value="no">${e("sessions.adoptNo")}</option>
82
+ <option value="">${t("sessions.adoptAny")}</option>
83
+ <option value="yes">${t("sessions.adoptYes")}</option>
84
+ <option value="no">${t("sessions.adoptNo")}</option>
75
85
  </select>
76
- <label class="filter-toggle"><input type="checkbox" name="active" checked> ${e("sessions.activeOnly")}</label>
77
- ${De()}
86
+ <label class="filter-toggle"><input type="checkbox" name="active" checked> ${t("sessions.activeOnly")}</label>
87
+ ${Rt()}
78
88
  </form>
79
89
  <div id="bulk-bar" class="bulk-bar" hidden>
80
90
  <span id="bulk-count"></span>
81
- <button type="button" id="bulk-close" class="contrast">${e("sessions.closeSelected")}</button>
82
- <button type="button" id="bulk-clear">${e("sessions.clearSelection")}</button>
91
+ <button type="button" id="bulk-close" class="contrast">${t("sessions.closeSelected")}</button>
92
+ <button type="button" id="bulk-clear">${t("sessions.clearSelection")}</button>
83
93
  </div>
84
94
  <table id="sessions-table">
85
95
  <thead><tr>
86
- <th><input type="checkbox" id="select-all" title="${e("sessions.activeOnly")}"></th>
87
- ${B("botName",e("sessions.bot"))}
88
- ${B("cliId",e("sessions.cli"))}
89
- ${B("status",e("sessions.status"))}
90
- ${B("title",e("sessions.titleCol"))}
91
- ${B("workingDir",e("sessions.workingDir"))}
92
- ${B("spawnedAt",e("sessions.created"))}
93
- ${B("lastMessageAt",e("sessions.last"))}
94
- ${B("adopt",e("sessions.adopt"))}
95
- <th>${e("sessions.actions")}</th>
96
+ <th><input type="checkbox" id="select-all" title="${t("sessions.activeOnly")}"></th>
97
+ ${G("botName",t("sessions.bot"))}
98
+ ${G("cliId",t("sessions.cli"))}
99
+ ${G("status",t("sessions.status"))}
100
+ ${G("title",t("sessions.titleCol"))}
101
+ ${G("workingDir",t("sessions.workingDir"))}
102
+ ${G("spawnedAt",t("sessions.created"))}
103
+ ${G("lastMessageAt",t("sessions.last"))}
104
+ ${G("adopt",t("sessions.adopt"))}
105
+ <th>${t("sessions.actions")}</th>
96
106
  </tr></thead>
97
107
  <tbody></tbody>
98
108
  </table>
99
109
  <dialog id="drawer"></dialog>
100
- </section>`}function ie(t){t.innerHTML=Ce();let s=t.querySelector("#sessions-table tbody"),r=t.querySelector("#filters"),m=t.querySelector("#drawer"),v=t.querySelector("#select-all"),c=t.querySelector("#bulk-bar"),T=t.querySelector("#bulk-count"),f=t.querySelector("#bulk-close"),E=t.querySelector("#bulk-clear"),S=t.querySelector("#sessions-table"),y=new Set,D="lastMessageAt",i="desc";function k(n){let a=n.status==="closed",l=y.has(n.sessionId)?"checked":"";return`<tr data-id="${o(n.sessionId)}">
101
- <td><input type="checkbox" class="row-select" ${l} ${a?"disabled":""}></td>
102
- <td>${o(n.botName??"")}</td>
103
- <td><span class="badge cli-${o(n.cliId??"unknown")}">${o(n.cliId??"unknown")}</span></td>
104
- <td><span class="status status-${o(n.status??"unknown")}">${o(n.status??"unknown")}</span></td>
105
- <td>${o((n.title??"").slice(0,48))}</td>
106
- <td title="${o(n.workingDir??"")}">${o((n.workingDir??"").slice(-34))}</td>
107
- <td>${j(n.spawnedAt)}</td>
108
- <td>${j(n.lastMessageAt)}</td>
109
- <td>${n.adopt?'<span class="badge">adopt</span>':""}</td>
110
- <td><button class="open" type="button">${e("sessions.details")}</button></td>
111
- </tr>`}function h(){let n=new FormData(r),a=(n.get("q")??"").toLowerCase(),l=n.getAll("cli"),b=l.length>0&&l.length<re.length,L=n.get("status"),I=n.get("adopt"),H=!!n.get("active"),x=[...C.sessions.values()].filter(M=>!b||l.includes(M.cliId??"unknown")).filter(M=>!L||M.status===L).filter(M=>!I||I==="yes"==!!M.adopt).filter(M=>!H||M.status!=="closed").filter(M=>!a||JSON.stringify(M).toLowerCase().includes(a));return x.sort(u),x}function d(n,a){return a==="spawnedAt"||a==="lastMessageAt"?Number(n[a]??0):a==="adopt"?!!n.adopt:String(n[a]??"").toLowerCase()}function u(n,a){let l=d(n,D),b=d(a,D),L=0;return typeof l=="number"&&typeof b=="number"?L=l-b:typeof l=="boolean"&&typeof b=="boolean"?L=Number(l)-Number(b):L=String(l).localeCompare(String(b)),L===0&&(L=Number(n.lastMessageAt??0)-Number(a.lastMessageAt??0)),i==="asc"?L:-L}function g(){S.querySelectorAll("th[data-sort]").forEach(n=>{let a=n.dataset.sort===D;n.classList.toggle("sorted",a),n.setAttribute("aria-sort",a?i==="asc"?"ascending":"descending":"none");let l=n.dataset.label??n.textContent?.trim()??"";n.textContent=a?`${l} ${i==="asc"?"\u25B2":"\u25BC"}`:l})}function w(n){c.hidden=y.size===0,T.textContent=e("sessions.selectedCount",{count:y.size});let a=n.filter(b=>b.status!=="closed");if(a.length===0){v.checked=!1,v.indeterminate=!1,v.disabled=!0;return}v.disabled=!1;let l=a.filter(b=>y.has(b.sessionId)).length;v.checked=l===a.length,v.indeterminate=l>0&&l<a.length}function p(){let n=h();for(let a of[...y]){let l=C.sessions.get(a);(!l||l.status==="closed")&&y.delete(a)}s.innerHTML=n.length?n.map(k).join(""):`<tr><td colspan="10" class="empty">${e("sessions.empty")}</td></tr>`,g(),w(n)}function $(n){let a=n.status==="closed";m.innerHTML=`<article>
110
+ </section>`}function We(e){e.innerHTML=Ot();let n=e.querySelector("#sessions-table tbody"),o=e.querySelector("#filters"),r=e.querySelector("#drawer"),s=e.querySelector("#select-all"),a=e.querySelector("#bulk-bar"),c=e.querySelector("#bulk-count"),p=e.querySelector("#bulk-close"),y=e.querySelector("#bulk-clear"),S=e.querySelector("#sessions-table"),v=new Set,T="lastMessageAt",l="desc";function I(d){let f=d.status==="closed",h=v.has(d.sessionId)?"checked":"";return`<tr data-id="${w(d.sessionId)}">
111
+ <td><input type="checkbox" class="row-select" ${h} ${f?"disabled":""}></td>
112
+ <td>${w(d.botName??"")}</td>
113
+ <td><span class="badge cli-${w(d.cliId??"unknown")}">${w(d.cliId??"unknown")}</span></td>
114
+ <td><span class="status status-${w(d.status??"unknown")}">${w(d.status??"unknown")}</span></td>
115
+ <td>${w((d.title??"").slice(0,48))}</td>
116
+ <td title="${w(d.workingDir??"")}">${w((d.workingDir??"").slice(-34))}</td>
117
+ <td>${te(d.spawnedAt)}</td>
118
+ <td>${te(d.lastMessageAt)}</td>
119
+ <td>${d.adopt?'<span class="badge">adopt</span>':""}</td>
120
+ <td><button class="open" type="button">${t("sessions.details")}</button></td>
121
+ </tr>`}function L(){let d=new FormData(o),f=(d.get("q")??"").toLowerCase(),h=d.getAll("cli"),E=h.length>0&&h.length<Fe.length,C=d.get("status"),R=d.get("adopt"),q=!!d.get("active"),P=[...D.sessions.values()].filter(x=>!E||h.includes(x.cliId??"unknown")).filter(x=>!C||x.status===C).filter(x=>!R||R==="yes"==!!x.adopt).filter(x=>!q||x.status!=="closed").filter(x=>!f||JSON.stringify(x).toLowerCase().includes(f));return P.sort(u),P}function g(d,f){return f==="spawnedAt"||f==="lastMessageAt"?Number(d[f]??0):f==="adopt"?!!d.adopt:String(d[f]??"").toLowerCase()}function u(d,f){let h=g(d,T),E=g(f,T),C=0;return typeof h=="number"&&typeof E=="number"?C=h-E:typeof h=="boolean"&&typeof E=="boolean"?C=Number(h)-Number(E):C=String(h).localeCompare(String(E)),C===0&&(C=Number(d.lastMessageAt??0)-Number(f.lastMessageAt??0)),l==="asc"?C:-C}function $(){S.querySelectorAll("th[data-sort]").forEach(d=>{let f=d.dataset.sort===T;d.classList.toggle("sorted",f),d.setAttribute("aria-sort",f?l==="asc"?"ascending":"descending":"none");let h=d.dataset.label??d.textContent?.trim()??"";d.textContent=f?`${h} ${l==="asc"?"\u25B2":"\u25BC"}`:h})}function k(d){a.hidden=v.size===0,c.textContent=t("sessions.selectedCount",{count:v.size});let f=d.filter(E=>E.status!=="closed");if(f.length===0){s.checked=!1,s.indeterminate=!1,s.disabled=!0;return}s.disabled=!1;let h=f.filter(E=>v.has(E.sessionId)).length;s.checked=h===f.length,s.indeterminate=h>0&&h<f.length}function b(){let d=L();for(let f of[...v]){let h=D.sessions.get(f);(!h||h.status==="closed")&&v.delete(f)}n.innerHTML=d.length?d.map(I).join(""):`<tr><td colspan="10" class="empty">${t("sessions.empty")}</td></tr>`,$(),k(d)}function A(d){let f=d.status==="closed";r.innerHTML=`<article>
112
122
  <header>
113
- <h3>${o(n.title??n.sessionId)}</h3>
114
- <span class="status status-${o(n.status??"unknown")}">${o(n.status??"unknown")}</span>
115
- <p><code>${o(n.sessionId)}</code> <button data-copy="${o(n.sessionId)}">${e("sessions.copy")}</button></p>
123
+ <h3>${w(d.title??d.sessionId)}</h3>
124
+ <span class="status status-${w(d.status??"unknown")}">${w(d.status??"unknown")}</span>
125
+ <p><code>${w(d.sessionId)}</code> <button data-copy="${w(d.sessionId)}">${t("sessions.copy")}</button></p>
116
126
  </header>
117
- <p><b>${e("sessions.bot")}:</b> ${o(n.botName??"-")} \xB7 <b>${e("sessions.cli")}:</b> ${o(n.cliId??"?")}</p>
118
- <p><b>chatId:</b> <code>${o(n.chatId??"")}</code> <button data-copy="${o(n.chatId??"")}">${e("sessions.copy")}</button></p>
119
- <p><b>rootMessageId:</b> <code>${o(n.rootMessageId??"")}</code> <button data-copy="${o(n.rootMessageId??"")}">${e("sessions.copy")}</button></p>
120
- ${n.threadId?`<p><b>threadId:</b> <code>${o(n.threadId)}</code></p>`:""}
121
- <p><b>${e("sessions.workingDir")}:</b> ${o(n.workingDir??"-")}</p>
127
+ <p><b>${t("sessions.bot")}:</b> ${w(d.botName??"-")} \xB7 <b>${t("sessions.cli")}:</b> ${w(d.cliId??"?")}</p>
128
+ <p><b>chatId:</b> <code>${w(d.chatId??"")}</code> <button data-copy="${w(d.chatId??"")}">${t("sessions.copy")}</button></p>
129
+ <p><b>rootMessageId:</b> <code>${w(d.rootMessageId??"")}</code> <button data-copy="${w(d.rootMessageId??"")}">${t("sessions.copy")}</button></p>
130
+ ${d.threadId?`<p><b>threadId:</b> <code>${w(d.threadId)}</code></p>`:""}
131
+ <p><b>${t("sessions.workingDir")}:</b> ${w(d.workingDir??"-")}</p>
122
132
  <div class="actions">
123
- <button id="locate-btn" type="button">${e("sessions.locate")}</button>
124
- ${n.webPort?`<a class="btn-link primary" href="http://${o(location.hostname)}:${n.webPort}" target="_blank" rel="noopener">${e("sessions.openTerminal")}</a>`:""}
125
- ${a?`<button id="resume-btn" type="button" class="primary">${e("sessions.resume")}</button>`:""}
126
- ${a?"":`<button id="close-btn" type="button" class="contrast">${e("sessions.close")}</button>`}
133
+ <button id="locate-btn" type="button">${t("sessions.locate")}</button>
134
+ ${d.webPort?`<a class="btn-link primary" href="http://${w(location.hostname)}:${d.webPort}" target="_blank" rel="noopener">${t("sessions.openTerminal")}</a>`:""}
135
+ ${f?`<button id="resume-btn" type="button" class="primary">${t("sessions.resume")}</button>`:""}
136
+ ${f?"":`<button id="close-btn" type="button" class="contrast">${t("sessions.close")}</button>`}
127
137
  </div>
128
- <form method="dialog"><button>${e("sessions.dismiss")}</button></form>
129
- </article>`,m.querySelectorAll("[data-copy]").forEach(I=>{I.onclick=()=>{navigator.clipboard.writeText(I.dataset.copy??""),I.textContent=e("sessions.copied"),setTimeout(()=>{I.textContent=e("sessions.copy")},800)}});let l=m.querySelector("#locate-btn");l&&(l.onclick=async()=>{l.disabled=!0,l.textContent=e("sessions.locating");try{let I=await fetch(`/api/sessions/${encodeURIComponent(n.sessionId)}/locate`,{method:"POST"}),H=await I.json();if(H.ok){let x=30;l.textContent=e("sessions.cooldown",{seconds:x});let M=setInterval(()=>{x-=1,x<=0?(clearInterval(M),l.disabled=!1,l.textContent=e("sessions.locate")):l.textContent=e("sessions.cooldown",{seconds:x})},1e3)}else alert(`Locate failed: ${H.error??I.status}`),l.disabled=!1,l.textContent=e("sessions.locate")}catch(I){alert(`Locate error: ${I}`),l.disabled=!1,l.textContent=e("sessions.locate")}});let b=m.querySelector("#resume-btn");b&&(b.onclick=async()=>{b.disabled=!0;try{let I=await fetch(`/api/sessions/${encodeURIComponent(n.sessionId)}/resume`,{method:"POST"}),H=await I.json().catch(()=>({}));if(!I.ok||H.ok===!1){alert(`${e("sessions.resumeFailed")}: ${H?.error??I.status}`),b.disabled=!1;return}m.close()}catch(I){alert(`${e("sessions.resumeFailed")}: ${I}`),b.disabled=!1}});let L=m.querySelector("#close-btn");L&&(L.onclick=async()=>{if(confirm(e("sessions.closeConfirm"))){L.disabled=!0;try{await fetch(`/api/sessions/${encodeURIComponent(n.sessionId)}/close`,{method:"POST"})}finally{m.close()}}}),m.showModal()}s.addEventListener("click",n=>{let a=n.target;if(a.classList.contains("row-select")){let I=a.closest("tr[data-id]");if(!I)return;a.checked?y.add(I.dataset.id):y.delete(I.dataset.id),w(h());return}let l=a.closest("td");if(l&&l.querySelector(".row-select"))return;let b=a.closest("tr[data-id]");if(!b)return;let L=C.sessions.get(b.dataset.id);L&&$(L)}),v.addEventListener("change",()=>{let n=h().filter(a=>a.status!=="closed");for(let a of n)v.checked?y.add(a.sessionId):y.delete(a.sessionId);p()}),E.addEventListener("click",()=>{y.clear(),p()}),f.addEventListener("click",async()=>{let n=[...y];if(n.length===0||!confirm(e("sessions.closeBulkConfirm",{count:n.length})))return;f.disabled=!0,E.disabled=!0;let a=f.textContent,l=0,b=0,L=[...n];f.textContent=`0/${n.length}`;async function I(){for(;L.length;){let H=L.shift();try{let x=await fetch(`/api/sessions/${encodeURIComponent(H)}/close`,{method:"POST"}),M=await x.json().catch(()=>({}));(!x.ok||M?.ok===!1)&&(b+=1)}catch{b+=1}finally{l+=1,f.textContent=`${l}/${n.length}`}}}await Promise.all(Array.from({length:Math.min(6,n.length)},()=>I())),f.textContent=a,f.disabled=!1,E.disabled=!1,y.clear(),p(),b>0&&alert(`Failed: ${b}/${n.length}`)}),S.querySelectorAll("th[data-sort]").forEach(n=>{n.addEventListener("click",()=>{let a=n.dataset.sort;D===a?i=i==="asc"?"desc":"asc":(D=a,i=a==="spawnedAt"||a==="lastMessageAt"?"desc":"asc"),p()})}),r.addEventListener("input",p),C.on(p),p()}function He(){return`<section class="page">
138
+ <form method="dialog"><button>${t("sessions.dismiss")}</button></form>
139
+ </article>`,r.querySelectorAll("[data-copy]").forEach(R=>{R.onclick=()=>{navigator.clipboard.writeText(R.dataset.copy??""),R.textContent=t("sessions.copied"),setTimeout(()=>{R.textContent=t("sessions.copy")},800)}});let h=r.querySelector("#locate-btn");h&&(h.onclick=async()=>{h.disabled=!0,h.textContent=t("sessions.locating");try{let R=await fetch(`/api/sessions/${encodeURIComponent(d.sessionId)}/locate`,{method:"POST"}),q=await R.json();if(q.ok){let P=30;h.textContent=t("sessions.cooldown",{seconds:P});let x=setInterval(()=>{P-=1,P<=0?(clearInterval(x),h.disabled=!1,h.textContent=t("sessions.locate")):h.textContent=t("sessions.cooldown",{seconds:P})},1e3)}else alert(`Locate failed: ${q.error??R.status}`),h.disabled=!1,h.textContent=t("sessions.locate")}catch(R){alert(`Locate error: ${R}`),h.disabled=!1,h.textContent=t("sessions.locate")}});let E=r.querySelector("#resume-btn");E&&(E.onclick=async()=>{E.disabled=!0;try{let R=await fetch(`/api/sessions/${encodeURIComponent(d.sessionId)}/resume`,{method:"POST"}),q=await R.json().catch(()=>({}));if(!R.ok||q.ok===!1){alert(`${t("sessions.resumeFailed")}: ${q?.error??R.status}`),E.disabled=!1;return}r.close()}catch(R){alert(`${t("sessions.resumeFailed")}: ${R}`),E.disabled=!1}});let C=r.querySelector("#close-btn");C&&(C.onclick=async()=>{if(confirm(t("sessions.closeConfirm"))){C.disabled=!0;try{await fetch(`/api/sessions/${encodeURIComponent(d.sessionId)}/close`,{method:"POST"})}finally{r.close()}}}),r.showModal()}n.addEventListener("click",d=>{let f=d.target;if(f.classList.contains("row-select")){let R=f.closest("tr[data-id]");if(!R)return;f.checked?v.add(R.dataset.id):v.delete(R.dataset.id),k(L());return}let h=f.closest("td");if(h&&h.querySelector(".row-select"))return;let E=f.closest("tr[data-id]");if(!E)return;let C=D.sessions.get(E.dataset.id);C&&A(C)}),s.addEventListener("change",()=>{let d=L().filter(f=>f.status!=="closed");for(let f of d)s.checked?v.add(f.sessionId):v.delete(f.sessionId);b()}),y.addEventListener("click",()=>{v.clear(),b()}),p.addEventListener("click",async()=>{let d=[...v];if(d.length===0||!confirm(t("sessions.closeBulkConfirm",{count:d.length})))return;p.disabled=!0,y.disabled=!0;let f=p.textContent,h=0,E=0,C=[...d];p.textContent=`0/${d.length}`;async function R(){for(;C.length;){let q=C.shift();try{let P=await fetch(`/api/sessions/${encodeURIComponent(q)}/close`,{method:"POST"}),x=await P.json().catch(()=>({}));(!P.ok||x?.ok===!1)&&(E+=1)}catch{E+=1}finally{h+=1,p.textContent=`${h}/${d.length}`}}}await Promise.all(Array.from({length:Math.min(6,d.length)},()=>R())),p.textContent=f,p.disabled=!1,y.disabled=!1,v.clear(),b(),E>0&&alert(`Failed: ${E}/${d.length}`)}),S.querySelectorAll("th[data-sort]").forEach(d=>{d.addEventListener("click",()=>{let f=d.dataset.sort;T===f?l=l==="asc"?"desc":"asc":(T=f,l=f==="spawnedAt"||f==="lastMessageAt"?"desc":"asc"),b()})}),o.addEventListener("input",b),D.on(b),b()}function xt(){return`<section class="page">
130
140
  <div class="page-heading">
131
141
  <div>
132
- <p class="eyebrow">${e("nav.schedules")}</p>
133
- <h1>${e("schedules.title")}</h1>
134
- <p>${e("schedules.subtitle")}</p>
142
+ <p class="eyebrow">${t("nav.schedules")}</p>
143
+ <h1>${t("schedules.title")}</h1>
144
+ <p>${t("schedules.subtitle")}</p>
135
145
  </div>
136
146
  </div>
137
147
  <form id="sched-filters" class="filters">
138
- <input type="search" name="q" placeholder="${e("schedules.search")}" />
148
+ <input type="search" name="q" placeholder="${t("schedules.search")}" />
139
149
  <select name="kind">
140
- <option value="">${e("schedules.anyKind")}</option>
150
+ <option value="">${t("schedules.anyKind")}</option>
141
151
  <option>cron</option>
142
152
  <option>interval</option>
143
153
  <option>once</option>
144
154
  </select>
145
- <label><input type="checkbox" name="enabled"> ${e("schedules.enabledOnly")}</label>
155
+ <label><input type="checkbox" name="enabled"> ${t("schedules.enabledOnly")}</label>
146
156
  </form>
147
157
  <table>
148
158
  <thead><tr>
149
- <th>${e("schedules.name")}</th><th>${e("schedules.bot")}</th><th>${e("schedules.schedule")}</th><th>${e("schedules.next")}</th><th>${e("schedules.last")}</th>
150
- <th>${e("schedules.repeat")}</th><th>${e("schedules.enabled")}</th><th>${e("schedules.actions")}</th>
159
+ <th>${t("schedules.name")}</th><th>${t("schedules.bot")}</th><th>${t("schedules.schedule")}</th><th>${t("schedules.next")}</th><th>${t("schedules.last")}</th>
160
+ <th>${t("schedules.repeat")}</th><th>${t("schedules.enabled")}</th><th>${t("schedules.actions")}</th>
151
161
  </tr></thead>
152
162
  <tbody id="schedules-tbody"></tbody>
153
163
  </table>
154
- </section>`}function le(t){if(!t)return"\u2014";try{return new Date(t).toLocaleString()}catch{return t}}function ce(t){t.innerHTML=He();let s=t.querySelector("#schedules-tbody"),r=t.querySelector("#sched-filters");function m(){let c=new FormData(r),T=(c.get("q")??"").toLowerCase(),f=c.get("kind"),E=!!c.get("enabled");return[...C.schedules.values()].filter(S=>!f||S.parsed?.kind===f).filter(S=>!E||S.enabled).filter(S=>!T||JSON.stringify(S).toLowerCase().includes(T)).sort((S,y)=>{if(S.enabled!==y.enabled)return S.enabled?-1:1;let D=S.nextRunAt?Date.parse(S.nextRunAt):1/0,i=y.nextRunAt?Date.parse(y.nextRunAt):1/0;return D-i})}function v(){s.innerHTML=m().map(c=>`<tr data-id="${o(c.id)}">
155
- <td>${o(c.name??c.id)}</td>
156
- <td>${o(c.botName??c.larkAppId??"-")}</td>
157
- <td><code>${o(c.parsed?.display??"?")}</code></td>
158
- <td>${le(c.nextRunAt)}</td>
159
- <td>${le(c.lastRunAt)} ${c.lastStatus==="error"?"\u26A0\uFE0F":""}</td>
160
- <td>${c.repeat?`${c.repeat.completed}/${c.repeat.times??"\u221E"}`:"\u2014"}</td>
161
- <td>${c.enabled?"\u2713":"\u2717"}</td>
164
+ </section>`}function _e(e){if(!e)return"\u2014";try{return new Date(e).toLocaleString()}catch{return e}}function Je(e){e.innerHTML=xt();let n=e.querySelector("#schedules-tbody"),o=e.querySelector("#sched-filters");function r(){let a=new FormData(o),c=(a.get("q")??"").toLowerCase(),p=a.get("kind"),y=!!a.get("enabled");return[...D.schedules.values()].filter(S=>!p||S.parsed?.kind===p).filter(S=>!y||S.enabled).filter(S=>!c||JSON.stringify(S).toLowerCase().includes(c)).sort((S,v)=>{if(S.enabled!==v.enabled)return S.enabled?-1:1;let T=S.nextRunAt?Date.parse(S.nextRunAt):1/0,l=v.nextRunAt?Date.parse(v.nextRunAt):1/0;return T-l})}function s(){n.innerHTML=r().map(a=>`<tr data-id="${w(a.id)}">
165
+ <td>${w(a.name??a.id)}</td>
166
+ <td>${w(a.botName??a.larkAppId??"-")}</td>
167
+ <td><code>${w(a.parsed?.display??"?")}</code></td>
168
+ <td>${_e(a.nextRunAt)}</td>
169
+ <td>${_e(a.lastRunAt)} ${a.lastStatus==="error"?"\u26A0\uFE0F":""}</td>
170
+ <td>${a.repeat?`${a.repeat.completed}/${a.repeat.times??"\u221E"}`:"\u2014"}</td>
171
+ <td>${a.enabled?"\u2713":"\u2717"}</td>
162
172
  <td class="actions-cell">
163
- <button data-op="run" type="button">${e("schedules.runNow")}</button>
164
- ${c.enabled?`<button data-op="pause" type="button">${e("schedules.pause")}</button>`:`<button data-op="resume" type="button">${e("schedules.resume")}</button>`}
173
+ <button data-op="run" type="button">${t("schedules.runNow")}</button>
174
+ ${a.enabled?`<button data-op="pause" type="button">${t("schedules.pause")}</button>`:`<button data-op="resume" type="button">${t("schedules.resume")}</button>`}
165
175
  </td>
166
- </tr>`).join("")||`<tr><td colspan="8" class="empty">${e("schedules.empty")}</td></tr>`}s.addEventListener("click",async c=>{let T=c.target.closest("button[data-op]");if(!T)return;let f=T.closest("tr[data-id]");if(!f)return;let E=f.dataset.id,S=T.dataset.op;T.disabled=!0;let y=T.textContent;T.textContent="...";try{let D=await fetch(`/api/schedules/${encodeURIComponent(E)}/${S}`,{method:"POST"}),i=await D.json().catch(()=>({}));(!D.ok||i.ok===!1)&&alert(`Failed: ${D.status} ${i?.error??""}`.trim())}catch(D){alert("Network error: "+D)}finally{T.disabled=!1,T.textContent=y}}),r.addEventListener("input",v),C.on(v),v()}var A={chats:[],bots:[]};function Ae(){return`<section class="page">
176
+ </tr>`).join("")||`<tr><td colspan="8" class="empty">${t("schedules.empty")}</td></tr>`}n.addEventListener("click",async a=>{let c=a.target.closest("button[data-op]");if(!c)return;let p=c.closest("tr[data-id]");if(!p)return;let y=p.dataset.id,S=c.dataset.op;c.disabled=!0;let v=c.textContent;c.textContent="...";try{let T=await fetch(`/api/schedules/${encodeURIComponent(y)}/${S}`,{method:"POST"}),l=await T.json().catch(()=>({}));(!T.ok||l.ok===!1)&&alert(`Failed: ${T.status} ${l?.error??""}`.trim())}catch(T){alert("Network error: "+T)}finally{c.disabled=!1,c.textContent=v}}),o.addEventListener("input",s),D.on(s),s()}var B={chats:[],bots:[]};function Dt(){return`<section class="page">
167
177
  <div class="page-heading">
168
178
  <div>
169
- <p class="eyebrow">${e("nav.groups")}</p>
170
- <h1>${e("groups.title")}</h1>
171
- <p>${e("groups.subtitle")}</p>
179
+ <p class="eyebrow">${t("nav.groups")}</p>
180
+ <h1>${t("groups.title")}</h1>
181
+ <p>${t("groups.subtitle")}</p>
172
182
  </div>
173
183
  </div>
174
184
  <form id="g-filters" class="filters">
175
- <input type="search" name="q" placeholder="${e("groups.search")}" />
176
- <label><input type="checkbox" name="missing"> ${e("groups.missingOnly")}</label>
177
- <button type="button" id="g-refresh">${e("groups.refresh")}</button>
178
- <button type="button" id="g-create" class="primary">${e("groups.create")}</button>
185
+ <input type="search" name="q" placeholder="${t("groups.search")}" />
186
+ <label><input type="checkbox" name="missing"> ${t("groups.missingOnly")}</label>
187
+ <button type="button" id="g-refresh">${t("groups.refresh")}</button>
188
+ <button type="button" id="g-create" class="primary">${t("groups.create")}</button>
179
189
  </form>
180
190
  <table>
181
191
  <thead id="g-head"></thead>
182
192
  <tbody id="g-body"></tbody>
183
193
  </table>
184
194
  <dialog id="g-drawer"></dialog>
185
- </section>`}async function N(){A=await(await fetch("/api/groups")).json()}async function xe(){return(await fetch("/api/groups")).json()}function Oe(t,s){if(s.size===0)return!0;let r=t?.memberBots??[];for(let m of s)if(!r.some(v=>v.larkAppId===m&&v.inChat))return!1;return!0}function de(t,s){return t.filter(r=>!s||!s.has(r.larkAppId)).map(r=>`
195
+ </section>`}async function Y(){B=await(await fetch("/api/groups")).json()}async function qt(){return(await fetch("/api/groups")).json()}function Pt(e,n){if(n.size===0)return!0;let o=e?.memberBots??[];for(let r of n)if(!o.some(s=>s.larkAppId===r&&s.inChat))return!1;return!0}function Ge(e,n){return e.filter(o=>!n||!n.has(o.larkAppId)).map(o=>`
186
196
  <label class="checkbox-row">
187
- <input type="checkbox" name="bot" value="${o(r.larkAppId)}">
188
- ${o(r.botName??r.larkAppId)} <small>(${o(r.larkAppId)})</small>
197
+ <input type="checkbox" name="bot" value="${w(o.larkAppId)}">
198
+ ${w(o.botName??o.larkAppId)} <small>(${w(o.larkAppId)})</small>
189
199
  </label>
190
- `).join("")}async function ue(t){t.innerHTML=Ae();let s=t.querySelector("#g-head"),r=t.querySelector("#g-body"),m=t.querySelector("#g-filters"),v=t.querySelector("#g-refresh"),c=t.querySelector("#g-drawer");v.onclick=async()=>{v.disabled=!0;try{await N(),y()}finally{v.disabled=!1}};let T=t.querySelector("#g-create");T.onclick=()=>f(),await N();function f(){let i=A.bots;if(i.length===0){alert(e("groups.noBotsOnline"));return}c.innerHTML=`
200
+ `).join("")}async function ze(e){e.innerHTML=Dt();let n=e.querySelector("#g-head"),o=e.querySelector("#g-body"),r=e.querySelector("#g-filters"),s=e.querySelector("#g-refresh"),a=e.querySelector("#g-drawer");s.onclick=async()=>{s.disabled=!0;try{await Y(),v()}finally{s.disabled=!1}};let c=e.querySelector("#g-create");c.onclick=()=>p(),await Y();function p(){let l=B.bots;if(l.length===0){alert(t("groups.noBotsOnline"));return}a.innerHTML=`
191
201
  <article>
192
- <header><h3>${e("groups.createTitle")}</h3></header>
193
- <p>${e("groups.createHelp")}</p>
202
+ <header><h3>${t("groups.createTitle")}</h3></header>
203
+ <p>${t("groups.createHelp")}</p>
194
204
  <form id="g-createform">
195
205
  <label class="form-row">
196
- <span>${e("groups.name")}</span>
197
- <input type="text" name="name" placeholder="${e("groups.namePlaceholder")}" maxlength="60">
206
+ <span>${t("groups.name")}</span>
207
+ <input type="text" name="name" placeholder="${t("groups.namePlaceholder")}" maxlength="60">
198
208
  </label>
199
209
  <label class="form-row">
200
- <span>${e("groups.bindDir")}</span>
210
+ <span>${t("groups.bindDir")}</span>
201
211
  <input type="text" name="bindWorkingDir" placeholder="e.g. ~/projects/botmux">
202
- <small>${e("groups.bindDirHelp")}</small>
212
+ <small>${t("groups.bindDirHelp")}</small>
203
213
  </label>
204
214
  <fieldset>
205
- <legend>${e("groups.botPicker")}</legend>
206
- ${de(i)}
215
+ <legend>${t("groups.botPicker")}</legend>
216
+ ${Ge(l)}
207
217
  </fieldset>
208
218
  <div class="actions">
209
- <button type="submit" class="primary">${e("groups.createSubmit")}</button>
210
- <button type="button" id="g-create-cancel">${e("groups.cancel")}</button>
219
+ <button type="submit" class="primary">${t("groups.createSubmit")}</button>
220
+ <button type="button" id="g-create-cancel">${t("groups.cancel")}</button>
211
221
  </div>
212
222
  </form>
213
- </article>`,c.showModal(),c.querySelector("#g-create-cancel").onclick=()=>c.close(),c.querySelector("#g-createform").onsubmit=async d=>{d.preventDefault();let u=new FormData(d.target),g=(u.get("name")??"").trim(),w=(u.get("bindWorkingDir")??"").trim(),p=u.getAll("bot");if(p.length===0){alert("Pick at least one bot.");return}let $=d.target.querySelector("button[type=submit]");$&&($.disabled=!0,$.textContent="Creating...");try{let n=await fetch("/api/groups/create",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:g||void 0,larkAppIds:p,bindWorkingDir:w||void 0})}),a=await n.json();if(a.ok&&a.chatId){E(a);let l=Array.isArray(a.invalidBotIds)?a.invalidBotIds:[],b=p.filter(I=>!l.includes(I)),L=new Set(b);typeof a.creator=="string"&&a.creator&&L.add(a.creator),k(a.chatId,g||a.chatId,b,a.creator),y(),h(a.chatId,L).catch(()=>{})}else alert(`Failed: ${a.error??n.status}`),c.close()}catch(n){alert("Network error: "+n),c.close()}};function k(d,u,g,w){let p=new Set(g);w&&p.add(w);let $=A.bots.map(a=>({larkAppId:a.larkAppId,botName:a.botName,inChat:p.has(a.larkAppId),oncallChat:null})),n={chatId:d,name:u,ownerId:w??null,memberBots:$};A.chats=[n,...A.chats.filter(a=>a.chatId!==d)]}async function h(d,u){let g=[600,1200,1200,1200,1200,1200];for(let w of g){await new Promise(n=>setTimeout(n,w));let p;try{p=await xe()}catch{continue}let $=(p.chats??[]).find(n=>n.chatId===d);if($&&Oe($,u)){A=p,y();return}}}}function E(i){let k=String(i.chatId),h=`https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(k)}`,d=i.invalidBotIds??[],u=i.invalidUserIds??[],g=i.autoInvitedOpenId,w=!!i.autoInviteRejected,p=i.ownerTransferredTo,$=i.transferError,n=i.notifyMessageId,a=i.notifyError,l=Array.isArray(i.oncallBindings)?i.oncallBindings:[],b=l.filter(M=>M?.ok).length,L=l.filter(M=>!M?.ok),I=l.length>0?L.length===0?`<p class="hint-ok">\u5DF2\u7ED1\u5B9A\u76EE\u5F55\uFF1A<code>${o(i.bindResolvedPath??"")}</code>\uFF08${b}/${l.length} bots\uFF09</p>`:`<p class="hint-warn">\u76EE\u5F55\u7ED1\u5B9A\u90E8\u5206\u5931\u8D25\uFF1A\u6210\u529F ${b}/${l.length}\u3002${L.map(M=>`<br><code>${o(M.larkAppId??"?")}</code>: ${o(M.error??"unknown")}`).join("")}</p>`:"",H;if(g){let M=p?"<br><small>\u7FA4\u4E3B\u5DF2\u4ECE\u673A\u5668\u4EBA\u8F6C\u8BA9\u7ED9\u4F60\u3002</small>":$?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8\u8F6C\u8BA9\u7FA4\u4E3B\u5931\u8D25\uFF08${o($)}\uFF09\uFF0C\u4F60\u73B0\u5728\u662F\u6210\u5458\u4F46\u7FA4\u4E3B\u4ECD\u662F\u673A\u5668\u4EBA\u3002</small>`:"",ve=n?`<br><small>\u673A\u5668\u4EBA\u5DF2\u5728\u7FA4\u91CC @ \u4E86\u4F60\uFF08\u6D88\u606F id <code>${o(n)}</code>\uFF09\uFF0C\u770B\u98DE\u4E66\u901A\u77E5\u5C31\u80FD\u8FDB\u7FA4\u3002</small>`:a?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8 @ \u901A\u77E5\u5931\u8D25\uFF08${o(a)}\uFF09\uFF0C\u65B0\u7FA4\u53EF\u80FD\u4E0D\u4F1A\u4E3B\u52A8\u51FA\u73B0\u5728\u4F60\u4FA7\u8FB9\u680F\uFF0C\u5EFA\u8BAE\u4ECE\u4E0B\u9762\u6309\u94AE\u8DF3\u8FDB\u53BB\u3002</small>`:"";H=`<p class="hint-ok">\u5DF2\u81EA\u52A8\u9080\u8BF7\u4F60\uFF08<code>${o(g)}</code>\uFF09\u4F5C\u4E3A\u6210\u5458\u3002${M}${ve}</p>`}else w?H='<p class="hint-warn">\u98DE\u4E66\u62D2\u7EDD\u4E86\u81EA\u52A8\u9080\u8BF7\uFF08\u4F60\u7684 open_id \u5728\u521B\u5EFA\u8005 bot \u7684 scope \u4E0B\u4E0D\u53EF\u7528\uFF09\u3002<strong>\u4F60\u76EE\u524D\u4E0D\u662F\u65B0\u7FA4\u6210\u5458</strong>\uFF0C\u9700\u8981\u8BA9\u7FA4\u91CC\u7684\u67D0\u4E2A\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u6765\u3002</p>':H='<p class="hint-warn">\u6CA1\u5728 dashboard \u7F13\u5B58\u91CC\u627E\u5230 ownerOpenId\uFF0C<strong>\u6CA1\u6709\u81EA\u52A8\u9080\u8BF7\u4F60</strong>\u3002\u70B9\u5F00\u4E0B\u9762\u94FE\u63A5\u524D\uFF0C\u5148\u8BA9\u7FA4\u91CC\u4EFB\u4E00\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u53BB\u3002</p>';let x=[d.length?`<li>\u65E0\u6548 bot id: <code>${d.map(o).join(", ")}</code></li>`:"",u.length?`<li>\u65E0\u6548\u7528\u6237 open_id: <code>${u.map(o).join(", ")}</code></li>`:""].filter(Boolean).join("");c.innerHTML=`
223
+ </article>`,a.showModal(),a.querySelector("#g-create-cancel").onclick=()=>a.close(),a.querySelector("#g-createform").onsubmit=async g=>{g.preventDefault();let u=new FormData(g.target),$=(u.get("name")??"").trim(),k=(u.get("bindWorkingDir")??"").trim(),b=u.getAll("bot");if(b.length===0){alert("Pick at least one bot.");return}let A=g.target.querySelector("button[type=submit]");A&&(A.disabled=!0,A.textContent="Creating...");try{let d=await fetch("/api/groups/create",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:$||void 0,larkAppIds:b,bindWorkingDir:k||void 0})}),f=await d.json();if(f.ok&&f.chatId){y(f);let h=Array.isArray(f.invalidBotIds)?f.invalidBotIds:[],E=b.filter(R=>!h.includes(R)),C=new Set(E);typeof f.creator=="string"&&f.creator&&C.add(f.creator),I(f.chatId,$||f.chatId,E,f.creator),v(),L(f.chatId,C).catch(()=>{})}else alert(`Failed: ${f.error??d.status}`),a.close()}catch(d){alert("Network error: "+d),a.close()}};function I(g,u,$,k){let b=new Set($);k&&b.add(k);let A=B.bots.map(f=>({larkAppId:f.larkAppId,botName:f.botName,inChat:b.has(f.larkAppId),oncallChat:null})),d={chatId:g,name:u,ownerId:k??null,memberBots:A};B.chats=[d,...B.chats.filter(f=>f.chatId!==g)]}async function L(g,u){let $=[600,1200,1200,1200,1200,1200];for(let k of $){await new Promise(d=>setTimeout(d,k));let b;try{b=await qt()}catch{continue}let A=(b.chats??[]).find(d=>d.chatId===g);if(A&&Pt(A,u)){B=b,v();return}}}}function y(l){let I=String(l.chatId),L=`https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(I)}`,g=l.invalidBotIds??[],u=l.invalidUserIds??[],$=l.autoInvitedOpenId,k=!!l.autoInviteRejected,b=l.ownerTransferredTo,A=l.transferError,d=l.notifyMessageId,f=l.notifyError,h=Array.isArray(l.oncallBindings)?l.oncallBindings:[],E=h.filter(x=>x?.ok).length,C=h.filter(x=>!x?.ok),R=h.length>0?C.length===0?`<p class="hint-ok">\u5DF2\u7ED1\u5B9A\u76EE\u5F55\uFF1A<code>${w(l.bindResolvedPath??"")}</code>\uFF08${E}/${h.length} bots\uFF09</p>`:`<p class="hint-warn">\u76EE\u5F55\u7ED1\u5B9A\u90E8\u5206\u5931\u8D25\uFF1A\u6210\u529F ${E}/${h.length}\u3002${C.map(x=>`<br><code>${w(x.larkAppId??"?")}</code>: ${w(x.error??"unknown")}`).join("")}</p>`:"",q;if($){let x=b?"<br><small>\u7FA4\u4E3B\u5DF2\u4ECE\u673A\u5668\u4EBA\u8F6C\u8BA9\u7ED9\u4F60\u3002</small>":A?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8\u8F6C\u8BA9\u7FA4\u4E3B\u5931\u8D25\uFF08${w(A)}\uFF09\uFF0C\u4F60\u73B0\u5728\u662F\u6210\u5458\u4F46\u7FA4\u4E3B\u4ECD\u662F\u673A\u5668\u4EBA\u3002</small>`:"",be=d?`<br><small>\u673A\u5668\u4EBA\u5DF2\u5728\u7FA4\u91CC @ \u4E86\u4F60\uFF08\u6D88\u606F id <code>${w(d)}</code>\uFF09\uFF0C\u770B\u98DE\u4E66\u901A\u77E5\u5C31\u80FD\u8FDB\u7FA4\u3002</small>`:f?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8 @ \u901A\u77E5\u5931\u8D25\uFF08${w(f)}\uFF09\uFF0C\u65B0\u7FA4\u53EF\u80FD\u4E0D\u4F1A\u4E3B\u52A8\u51FA\u73B0\u5728\u4F60\u4FA7\u8FB9\u680F\uFF0C\u5EFA\u8BAE\u4ECE\u4E0B\u9762\u6309\u94AE\u8DF3\u8FDB\u53BB\u3002</small>`:"";q=`<p class="hint-ok">\u5DF2\u81EA\u52A8\u9080\u8BF7\u4F60\uFF08<code>${w($)}</code>\uFF09\u4F5C\u4E3A\u6210\u5458\u3002${x}${be}</p>`}else k?q='<p class="hint-warn">\u98DE\u4E66\u62D2\u7EDD\u4E86\u81EA\u52A8\u9080\u8BF7\uFF08\u4F60\u7684 open_id \u5728\u521B\u5EFA\u8005 bot \u7684 scope \u4E0B\u4E0D\u53EF\u7528\uFF09\u3002<strong>\u4F60\u76EE\u524D\u4E0D\u662F\u65B0\u7FA4\u6210\u5458</strong>\uFF0C\u9700\u8981\u8BA9\u7FA4\u91CC\u7684\u67D0\u4E2A\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u6765\u3002</p>':q='<p class="hint-warn">\u6CA1\u5728 dashboard \u7F13\u5B58\u91CC\u627E\u5230 ownerOpenId\uFF0C<strong>\u6CA1\u6709\u81EA\u52A8\u9080\u8BF7\u4F60</strong>\u3002\u70B9\u5F00\u4E0B\u9762\u94FE\u63A5\u524D\uFF0C\u5148\u8BA9\u7FA4\u91CC\u4EFB\u4E00\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u53BB\u3002</p>';let P=[g.length?`<li>\u65E0\u6548 bot id: <code>${g.map(w).join(", ")}</code></li>`:"",u.length?`<li>\u65E0\u6548\u7528\u6237 open_id: <code>${u.map(w).join(", ")}</code></li>`:""].filter(Boolean).join("");a.innerHTML=`
214
224
  <article>
215
- <header><h3>${e("groups.successTitle")}</h3></header>
216
- <p><b>chatId:</b> <code>${o(k)}</code> <button type="button" data-copy="${o(k)}">${e("sessions.copy")}</button></p>
217
- <p><b>\u521B\u5EFA\u8005:</b> <code>${o(i.creator??"?")}</code></p>
218
- ${H}
219
- ${I}
220
- ${x?`<ul>${x}</ul>`:""}
225
+ <header><h3>${t("groups.successTitle")}</h3></header>
226
+ <p><b>chatId:</b> <code>${w(I)}</code> <button type="button" data-copy="${w(I)}">${t("sessions.copy")}</button></p>
227
+ <p><b>\u521B\u5EFA\u8005:</b> <code>${w(l.creator??"?")}</code></p>
228
+ ${q}
229
+ ${R}
230
+ ${P?`<ul>${P}</ul>`:""}
221
231
  <div class="actions">
222
- <a class="btn-link primary" href="${h}" target="_blank" rel="noopener">${e("groups.openGroup")}</a>
223
- <button type="button" id="g-create-close">${e("sessions.dismiss")}</button>
232
+ <a class="btn-link primary" href="${L}" target="_blank" rel="noopener">${t("groups.openGroup")}</a>
233
+ <button type="button" id="g-create-close">${t("sessions.dismiss")}</button>
224
234
  </div>
225
- </article>`,c.querySelectorAll("[data-copy]").forEach(M=>{M.onclick=()=>{navigator.clipboard.writeText(M.dataset.copy??""),M.textContent=e("sessions.copied"),setTimeout(()=>{M.textContent=e("sessions.copy")},800)}}),c.querySelector("#g-create-close").onclick=()=>c.close()}function S(){s.innerHTML=`<tr>
226
- <th>${e("groups.chat")}</th>
227
- ${A.bots.map(i=>`<th>${o(i.botName??i.larkAppId)}</th>`).join("")}
228
- <th>${e("groups.actions")}</th>
229
- </tr>`}function y(){S();let i=new FormData(m),k=(i.get("q")??"").toLowerCase(),h=!!i.get("missing"),d=A.chats.filter(u=>!k||(u.name??"").toLowerCase().includes(k)||u.chatId.toLowerCase().includes(k)||(u.ownerId??"").toLowerCase().includes(k)).filter(u=>!h||u.memberBots.some(g=>!g.inChat));if(d.length===0){r.innerHTML=`<tr><td colspan="${A.bots.length+2}" class="empty">${e("groups.empty")}</td></tr>`;return}r.innerHTML=d.map(u=>`<tr data-chat="${o(u.chatId)}">
235
+ </article>`,a.querySelectorAll("[data-copy]").forEach(x=>{x.onclick=()=>{navigator.clipboard.writeText(x.dataset.copy??""),x.textContent=t("sessions.copied"),setTimeout(()=>{x.textContent=t("sessions.copy")},800)}}),a.querySelector("#g-create-close").onclick=()=>a.close()}function S(){n.innerHTML=`<tr>
236
+ <th>${t("groups.chat")}</th>
237
+ ${B.bots.map(l=>`<th>${w(l.botName??l.larkAppId)}</th>`).join("")}
238
+ <th>${t("groups.actions")}</th>
239
+ </tr>`}function v(){S();let l=new FormData(r),I=(l.get("q")??"").toLowerCase(),L=!!l.get("missing"),g=B.chats.filter(u=>!I||(u.name??"").toLowerCase().includes(I)||u.chatId.toLowerCase().includes(I)||(u.ownerId??"").toLowerCase().includes(I)).filter(u=>!L||u.memberBots.some($=>!$.inChat));if(g.length===0){o.innerHTML=`<tr><td colspan="${B.bots.length+2}" class="empty">${t("groups.empty")}</td></tr>`;return}o.innerHTML=g.map(u=>`<tr data-chat="${w(u.chatId)}">
230
240
  <td>
231
- <strong>${o(u.name??u.chatId)}</strong><br>
232
- <small><code>${o(u.chatId)}</code></small>
241
+ <strong>${w(u.name??u.chatId)}</strong><br>
242
+ <small><code>${w(u.chatId)}</code></small>
233
243
  </td>
234
- ${A.bots.map(g=>{let w=u.memberBots.find(n=>n.larkAppId===g.larkAppId),p=w?w.error?"!":w.inChat?"\u2713":"\u2717":"?";return`<td class="${w?w.error?"cell-error":w.inChat?"cell-in":"cell-out":"cell-unknown"}" title="${o(w?.error??"")}">${p}</td>`}).join("")}
244
+ ${B.bots.map($=>{let k=u.memberBots.find(d=>d.larkAppId===$.larkAppId),b=k?k.error?"!":k.inChat?"\u2713":"\u2717":"?";return`<td class="${k?k.error?"cell-error":k.inChat?"cell-in":"cell-out":"cell-unknown"}" title="${w(k?.error??"")}">${b}</td>`}).join("")}
235
245
  <td>
236
- <button class="add-bots" type="button">${e("groups.addBots")}</button>
237
- <button class="manage-chat" type="button">${e("groups.manage")}</button>
246
+ <button class="add-bots" type="button">${t("groups.addBots")}</button>
247
+ <button class="manage-chat" type="button">${t("groups.manage")}</button>
238
248
  </td>
239
- </tr>`).join("")}y(),r.addEventListener("click",async i=>{let k=i.target.closest("button.add-bots");if(!k)return;let d=k.closest("tr[data-chat]").dataset.chat,u=A.chats.find(p=>p.chatId===d);if(!u)return;let g=new Set(u.memberBots.filter(p=>p.inChat).map(p=>p.larkAppId));if(!A.bots.filter(p=>!g.has(p.larkAppId)).length){alert("All configured bots are already in this chat.");return}c.innerHTML=`
249
+ </tr>`).join("")}v(),o.addEventListener("click",async l=>{let I=l.target.closest("button.add-bots");if(!I)return;let g=I.closest("tr[data-chat]").dataset.chat,u=B.chats.find(b=>b.chatId===g);if(!u)return;let $=new Set(u.memberBots.filter(b=>b.inChat).map(b=>b.larkAppId));if(!B.bots.filter(b=>!$.has(b.larkAppId)).length){alert("All configured bots are already in this chat.");return}a.innerHTML=`
240
250
  <article>
241
- <header><h3>${e("groups.addBots")} \xB7 ${o(u.name??u.chatId)}</h3></header>
242
- <p>${e("groups.createHelp")}</p>
251
+ <header><h3>${t("groups.addBots")} \xB7 ${w(u.name??u.chatId)}</h3></header>
252
+ <p>${t("groups.createHelp")}</p>
243
253
  <form id="g-addform">
244
- ${de(A.bots,g)}
254
+ ${Ge(B.bots,$)}
245
255
  <div class="actions">
246
- <button type="submit" class="primary">${e("groups.addBots")}</button>
247
- <button type="button" id="g-cancel">${e("groups.cancel")}</button>
256
+ <button type="submit" class="primary">${t("groups.addBots")}</button>
257
+ <button type="button" id="g-cancel">${t("groups.cancel")}</button>
248
258
  </div>
249
259
  </form>
250
- </article>`,c.showModal(),c.querySelector("#g-cancel").onclick=()=>c.close(),c.querySelector("#g-addform").onsubmit=async p=>{p.preventDefault();let n=new FormData(p.target).getAll("bot");if(n.length===0){alert("Pick at least one bot.");return}try{let l=await(await fetch(`/api/groups/${encodeURIComponent(d)}/add-bots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:n})})).json();if(l.error==="no_proxy_bot")alert("No bot is currently in this chat \u2014 add one manually in Feishu first, then retry.");else if(l.result){let b=l.result.map(L=>`${L.id}: ${L.ok?"OK":`failed (${L.error??"unknown"})`}`).join(`
251
- `);alert(b),await N(),y()}else alert(`Unexpected response: ${JSON.stringify(l)}`)}catch(a){alert("Network error: "+a)}finally{c.close()}}}),r.addEventListener("click",async i=>{let k=i.target.closest("button.manage-chat");if(!k)return;let d=k.closest("tr[data-chat]").dataset.chat,u=A.chats.find(g=>g.chatId===d);u&&D(u)});function D(i){let k=i.memberBots.filter(d=>d.inChat),h=typeof i.ownerId=="string"?i.ownerId:"";c.innerHTML=`
260
+ </article>`,a.showModal(),a.querySelector("#g-cancel").onclick=()=>a.close(),a.querySelector("#g-addform").onsubmit=async b=>{b.preventDefault();let d=new FormData(b.target).getAll("bot");if(d.length===0){alert("Pick at least one bot.");return}try{let h=await(await fetch(`/api/groups/${encodeURIComponent(g)}/add-bots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:d})})).json();if(h.error==="no_proxy_bot")alert("No bot is currently in this chat \u2014 add one manually in Feishu first, then retry.");else if(h.result){let E=h.result.map(C=>`${C.id}: ${C.ok?"OK":`failed (${C.error??"unknown"})`}`).join(`
261
+ `);alert(E),await Y(),v()}else alert(`Unexpected response: ${JSON.stringify(h)}`)}catch(f){alert("Network error: "+f)}finally{a.close()}}}),o.addEventListener("click",async l=>{let I=l.target.closest("button.manage-chat");if(!I)return;let g=I.closest("tr[data-chat]").dataset.chat,u=B.chats.find($=>$.chatId===g);u&&T(u)});function T(l){let I=l.memberBots.filter(g=>g.inChat),L=typeof l.ownerId=="string"?l.ownerId:"";a.innerHTML=`
252
262
  <article>
253
- <header><h3>${e("groups.manageTitle",{name:i.name??i.chatId})}</h3></header>
254
- <p><b>chatId:</b> <code>${o(i.chatId)}</code></p>
255
- <p><b>${e("groups.owner")}:</b> <code>${o(i.ownerId??e("common.unknown"))}</code></p>
263
+ <header><h3>${t("groups.manageTitle",{name:l.name??l.chatId})}</h3></header>
264
+ <p><b>chatId:</b> <code>${w(l.chatId)}</code></p>
265
+ <p><b>${t("groups.owner")}:</b> <code>${w(l.ownerId??t("common.unknown"))}</code></p>
256
266
 
257
267
  <fieldset>
258
- <legend>${e("groups.oncall")}</legend>
259
- <p><small>${e("groups.oncallHelp")}</small></p>
260
- ${k.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':k.map(d=>{let u=!!d.oncallChat,g=d.oncallChat?.workingDir??"";return`
261
- <div class="oncall-row" data-bot="${o(d.larkAppId)}">
268
+ <legend>${t("groups.oncall")}</legend>
269
+ <p><small>${t("groups.oncallHelp")}</small></p>
270
+ ${I.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':I.map(g=>{let u=!!g.oncallChat,$=g.oncallChat?.workingDir??"";return`
271
+ <div class="oncall-row" data-bot="${w(g.larkAppId)}">
262
272
  <label class="checkbox-row">
263
273
  <input type="checkbox" data-action="toggle" ${u?"checked":""}>
264
- <strong>${o(d.botName??d.larkAppId)}</strong>
265
- <small>(${o(d.larkAppId)})</small>
274
+ <strong>${w(g.botName??g.larkAppId)}</strong>
275
+ <small>(${w(g.larkAppId)})</small>
266
276
  </label>
267
277
  <div class="oncall-row-body">
268
278
  <input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
269
- value="${o(g)}" ${u?"":"disabled"}>
270
- <button type="button" data-action="save">${e("groups.save")}</button>
279
+ value="${w($)}" ${u?"":"disabled"}>
280
+ <button type="button" data-action="save">${t("groups.save")}</button>
271
281
  <span class="oncall-status" data-status></span>
272
282
  </div>
273
283
  </div>
@@ -275,87 +285,372 @@
275
285
  </fieldset>
276
286
 
277
287
  <fieldset>
278
- <legend>${e("groups.leaveTitle")}</legend>
279
- ${k.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':k.map(d=>`
288
+ <legend>${t("groups.leaveTitle")}</legend>
289
+ ${I.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':I.map(g=>`
280
290
  <label class="checkbox-row">
281
- <input type="checkbox" name="leave-bot" value="${o(d.larkAppId)}">
282
- ${o(d.botName??d.larkAppId)}
283
- <small>${d.larkAppId===h?"\xB7 \u7FA4\u4E3B":""}</small>
291
+ <input type="checkbox" name="leave-bot" value="${w(g.larkAppId)}">
292
+ ${w(g.botName??g.larkAppId)}
293
+ <small>${g.larkAppId===L?"\xB7 \u7FA4\u4E3B":""}</small>
284
294
  </label>
285
295
  `).join("")}
286
296
  </fieldset>
287
297
 
288
298
  <div class="actions">
289
- <button id="g-leave-btn" type="button" ${k.length===0?"disabled":""}>${e("groups.leaveSelected")}</button>
290
- <button id="g-disband-btn" type="button" class="contrast" ${k.length===0?"disabled":""}>${e("groups.disband")}</button>
299
+ <button id="g-leave-btn" type="button" ${I.length===0?"disabled":""}>${t("groups.leaveSelected")}</button>
300
+ <button id="g-disband-btn" type="button" class="contrast" ${I.length===0?"disabled":""}>${t("groups.disband")}</button>
291
301
  </div>
292
- <p class="hint-warn"><small>${e("groups.dangerHint")}</small></p>
293
- <form method="dialog"><button>${e("sessions.dismiss")}</button></form>
294
- </article>`,c.showModal(),c.querySelectorAll(".oncall-row").forEach(d=>{let u=d.dataset.bot,g=d.querySelector("input[data-action=toggle]"),w=d.querySelector("input[data-input=workingDir]"),p=d.querySelector("button[data-action=save]"),$=d.querySelector("[data-status]");g.addEventListener("change",()=>{w.disabled=!g.checked,g.checked&&w.focus()}),p.addEventListener("click",async()=>{$.textContent="",$.className="oncall-status";let n=g.checked,a=w.value.trim();if(n&&!a){$.textContent=e("groups.needWorkingDir"),$.classList.add("hint-warn-inline");return}p.disabled=!0;try{let l=`/api/groups/${encodeURIComponent(i.chatId)}/oncall/${encodeURIComponent(u)}`,b=n?await fetch(l,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({workingDir:a})}):await fetch(l,{method:"DELETE"}),L=await b.json().catch(()=>({}));if(b.ok&&L.ok){$.textContent=n?`\u2713 \u5DF2\u7ED1\u5B9A \u2192 ${L.resolvedPath??a}`:"\u2713 \u5DF2\u89E3\u7ED1",$.classList.add("hint-ok");try{await N(),y()}catch{}}else $.textContent=`\u2717 ${L.error??b.status}`,$.classList.add("hint-warn-inline")}catch(l){$.textContent=`\u2717 ${l?.message??l}`,$.classList.add("hint-warn-inline")}finally{p.disabled=!1}})}),c.querySelector("#g-leave-btn").onclick=async()=>{let d=[...c.querySelectorAll("input[name=leave-bot]:checked")].map(u=>u.value);if(d.length===0){alert("\u81F3\u5C11\u9009\u4E00\u4E2A\u673A\u5668\u4EBA");return}if(confirm(`\u786E\u5B9A\u8BA9 ${d.length} \u4E2A\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A\uFF1F\u8BE5 bot \u5728\u6B64\u7FA4\u7684\u4F1A\u8BDD\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))try{let g=await(await fetch(`/api/groups/${encodeURIComponent(i.chatId)}/leave`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:d})})).json(),w=(g.result??[]).map(p=>{if(!p.ok)return`${p.larkAppId}: \u5931\u8D25 (${p.error??"unknown"})`;let $=p.closedSessions??[],n=$.filter(b=>!b.ok).length,a=$.length-n,l=$.length===0?"":n===0?`\uFF08\u5173\u95ED ${a} \u4E2A\u4F1A\u8BDD\uFF09`:`\uFF08\u5173\u95ED ${a} \u4E2A\uFF0C${n} \u4E2A\u5931\u8D25\uFF09`;return`${p.larkAppId}: OK${l}`}).join(`
295
- `);alert(w||`Unexpected: ${JSON.stringify(g)}`),await N(),y()}catch(u){alert("Network error: "+u)}finally{c.close()}},c.querySelector("#g-disband-btn").onclick=async()=>{if(k.length===0||!confirm(`\u786E\u5B9A\u89E3\u6563\u7FA4\u804A\u300C${i.name??i.chatId}\u300D\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\uFF0C\u672C\u7FA4\u6240\u6709\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E5F\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))return;let d=[...k].sort((g,w)=>(w.larkAppId===h?1:0)-(g.larkAppId===h?1:0)),u=[];for(let g of d)try{let w=await fetch(`/api/groups/${encodeURIComponent(i.chatId)}/disband`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppId:g.larkAppId})}),p=await w.json();if(p.ok){let $=p.closedSessions??[],n=$.filter(b=>!b.ok).length,a=$.length-n,l=$.length===0?"":n===0?`
296
- \u5173\u95ED\u4E86 ${a} \u4E2A\u4F1A\u8BDD\u3002`:`
297
- \u5173\u95ED\u4E86 ${a} \u4E2A\u4F1A\u8BDD\uFF0C${n} \u4E2A\u4F1A\u8BDD\u5173\u95ED\u5931\u8D25\u3002`;alert(`\u5DF2\u89E3\u6563\uFF08\u7531 ${g.botName??g.larkAppId} \u6267\u884C\uFF09${l}`),await N(),y(),c.close();return}u.push(`${g.botName??g.larkAppId}: ${p.error??w.status}`)}catch(w){u.push(`${g.botName??g.larkAppId}: ${w}`)}alert(`\u6240\u6709\u5728\u7FA4\u673A\u5668\u4EBA\u5747\u65E0\u6CD5\u89E3\u6563\uFF1A
302
+ <p class="hint-warn"><small>${t("groups.dangerHint")}</small></p>
303
+ <form method="dialog"><button>${t("sessions.dismiss")}</button></form>
304
+ </article>`,a.showModal(),a.querySelectorAll(".oncall-row").forEach(g=>{let u=g.dataset.bot,$=g.querySelector("input[data-action=toggle]"),k=g.querySelector("input[data-input=workingDir]"),b=g.querySelector("button[data-action=save]"),A=g.querySelector("[data-status]");$.addEventListener("change",()=>{k.disabled=!$.checked,$.checked&&k.focus()}),b.addEventListener("click",async()=>{A.textContent="",A.className="oncall-status";let d=$.checked,f=k.value.trim();if(d&&!f){A.textContent=t("groups.needWorkingDir"),A.classList.add("hint-warn-inline");return}b.disabled=!0;try{let h=`/api/groups/${encodeURIComponent(l.chatId)}/oncall/${encodeURIComponent(u)}`,E=d?await fetch(h,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({workingDir:f})}):await fetch(h,{method:"DELETE"}),C=await E.json().catch(()=>({}));if(E.ok&&C.ok){A.textContent=d?`\u2713 \u5DF2\u7ED1\u5B9A \u2192 ${C.resolvedPath??f}`:"\u2713 \u5DF2\u89E3\u7ED1",A.classList.add("hint-ok");try{await Y(),v()}catch{}}else A.textContent=`\u2717 ${C.error??E.status}`,A.classList.add("hint-warn-inline")}catch(h){A.textContent=`\u2717 ${h?.message??h}`,A.classList.add("hint-warn-inline")}finally{b.disabled=!1}})}),a.querySelector("#g-leave-btn").onclick=async()=>{let g=[...a.querySelectorAll("input[name=leave-bot]:checked")].map(u=>u.value);if(g.length===0){alert("\u81F3\u5C11\u9009\u4E00\u4E2A\u673A\u5668\u4EBA");return}if(confirm(`\u786E\u5B9A\u8BA9 ${g.length} \u4E2A\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A\uFF1F\u8BE5 bot \u5728\u6B64\u7FA4\u7684\u4F1A\u8BDD\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))try{let $=await(await fetch(`/api/groups/${encodeURIComponent(l.chatId)}/leave`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:g})})).json(),k=($.result??[]).map(b=>{if(!b.ok)return`${b.larkAppId}: \u5931\u8D25 (${b.error??"unknown"})`;let A=b.closedSessions??[],d=A.filter(E=>!E.ok).length,f=A.length-d,h=A.length===0?"":d===0?`\uFF08\u5173\u95ED ${f} \u4E2A\u4F1A\u8BDD\uFF09`:`\uFF08\u5173\u95ED ${f} \u4E2A\uFF0C${d} \u4E2A\u5931\u8D25\uFF09`;return`${b.larkAppId}: OK${h}`}).join(`
305
+ `);alert(k||`Unexpected: ${JSON.stringify($)}`),await Y(),v()}catch(u){alert("Network error: "+u)}finally{a.close()}},a.querySelector("#g-disband-btn").onclick=async()=>{if(I.length===0||!confirm(`\u786E\u5B9A\u89E3\u6563\u7FA4\u804A\u300C${l.name??l.chatId}\u300D\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\uFF0C\u672C\u7FA4\u6240\u6709\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E5F\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))return;let g=[...I].sort(($,k)=>(k.larkAppId===L?1:0)-($.larkAppId===L?1:0)),u=[];for(let $ of g)try{let k=await fetch(`/api/groups/${encodeURIComponent(l.chatId)}/disband`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppId:$.larkAppId})}),b=await k.json();if(b.ok){let A=b.closedSessions??[],d=A.filter(E=>!E.ok).length,f=A.length-d,h=A.length===0?"":d===0?`
306
+ \u5173\u95ED\u4E86 ${f} \u4E2A\u4F1A\u8BDD\u3002`:`
307
+ \u5173\u95ED\u4E86 ${f} \u4E2A\u4F1A\u8BDD\uFF0C${d} \u4E2A\u4F1A\u8BDD\u5173\u95ED\u5931\u8D25\u3002`;alert(`\u5DF2\u89E3\u6563\uFF08\u7531 ${$.botName??$.larkAppId} \u6267\u884C\uFF09${h}`),await Y(),v(),a.close();return}u.push(`${$.botName??$.larkAppId}: ${b.error??k.status}`)}catch(k){u.push(`${$.botName??$.larkAppId}: ${k}`)}alert(`\u6240\u6709\u5728\u7FA4\u673A\u5668\u4EBA\u5747\u65E0\u6CD5\u89E3\u6563\uFF1A
298
308
  ${u.join(`
299
309
  `)}
300
310
 
301
- \u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002`)}}m.addEventListener("input",y)}var R={bots:[]},P=null;function Be(){return`<section class="page">
311
+ \u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002`)}}r.addEventListener("input",v)}var X={bots:[]},Z=null;function Nt(){return`<section class="page">
302
312
  <div class="page-heading">
303
313
  <div>
304
- <p class="eyebrow">${e("nav.botDefaults")}</p>
305
- <h1>${e("botDefaults.title")}</h1>
306
- <p>${e("botDefaults.subtitle")}</p>
314
+ <p class="eyebrow">${t("nav.botDefaults")}</p>
315
+ <h1>${t("botDefaults.title")}</h1>
316
+ <p>${t("botDefaults.subtitle")}</p>
307
317
  </div>
308
318
  </div>
309
319
  <form id="bd-filters" class="filters">
310
- <input type="search" name="q" placeholder="${e("botDefaults.search")}" />
311
- <button type="button" id="bd-refresh">${e("botDefaults.refresh")}</button>
320
+ <input type="search" name="q" placeholder="${t("botDefaults.search")}" />
321
+ <button type="button" id="bd-refresh">${t("botDefaults.refresh")}</button>
312
322
  </form>
313
323
  <p class="hint-warn" style="max-width:760px">
314
- ${e("botDefaults.warning")}
324
+ ${t("botDefaults.warning")}
315
325
  </p>
316
326
  <div id="bd-list"></div>
317
- </section>`}async function pe(){try{let t=await fetch("/api/bots"),s=await t.json().catch(()=>({}));if(!t.ok){P=s?.error?`HTTP ${t.status}: ${s.error}${s.path?` (${s.path})`:""}`:`HTTP ${t.status}`,R={bots:[]};return}if(!s||!Array.isArray(s.bots)){P="unexpected response shape (no `bots` array)",R={bots:[]};return}P=null,R=s}catch(t){P=t?.message??String(t),R={bots:[]}}}function he(t){if(!t)return"\u2014";let s=new Date(t);return Number.isNaN(s.getTime())?"\u2014":s.toLocaleString()}async function ge(t){t.innerHTML=Be();let s=t.querySelector("#bd-list"),r=t.querySelector("#bd-filters"),m=t.querySelector("#bd-refresh");m.onclick=async()=>{m.disabled=!0;try{await pe(),v()}finally{m.disabled=!1}},await pe();function v(){let E=(new FormData(r).get("q")??"").toLowerCase(),S=R.bots.filter(y=>!E||(y.botName??"").toLowerCase().includes(E)||(y.larkAppId??"").toLowerCase().includes(E));if(P){s.innerHTML=`<p class="hint-warn">\u65E0\u6CD5\u52A0\u8F7D bot \u5217\u8868\uFF1A${o(P)}<br>\u5E38\u89C1\u539F\u56E0\uFF1Adashboard / daemon \u8FDB\u7A0B\u8FD8\u5728\u8DD1\u65E7\u4EE3\u7801\uFF0C\u6267\u884C <code>botmux restart</code> \u540E\u5237\u65B0\u3002</p>`;return}if(S.length===0){s.innerHTML=`<p class="empty">${e("botDefaults.empty")}</p>`;return}s.innerHTML=S.map(c).join(""),T()}function c(f){if(f.error)return`<article class="bd-card" data-appid="${o(f.larkAppId)}">
318
- <header><strong>${o(f.botName??f.larkAppId)}</strong>
319
- <small>${o(f.larkAppId)}</small></header>
320
- <p class="hint-warn-inline">\u67E5\u8BE2\u5931\u8D25\uFF1A${o(f.error)}</p>
321
- </article>`;let E=f.defaultOncall??{enabled:!1,workingDir:"",since:0},S=!!E.enabled;return`<article class="bd-card" data-appid="${o(f.larkAppId)}">
327
+ </section>`}async function Ke(){try{let e=await fetch("/api/bots"),n=await e.json().catch(()=>({}));if(!e.ok){Z=n?.error?`HTTP ${e.status}: ${n.error}${n.path?` (${n.path})`:""}`:`HTTP ${e.status}`,X={bots:[]};return}if(!n||!Array.isArray(n.bots)){Z="unexpected response shape (no `bots` array)",X={bots:[]};return}Z=null,X=n}catch(e){Z=e?.message??String(e),X={bots:[]}}}function Ve(e){if(!e)return"\u2014";let n=new Date(e);return Number.isNaN(n.getTime())?"\u2014":n.toLocaleString()}async function Qe(e){e.innerHTML=Nt();let n=e.querySelector("#bd-list"),o=e.querySelector("#bd-filters"),r=e.querySelector("#bd-refresh");r.onclick=async()=>{r.disabled=!0;try{await Ke(),s()}finally{r.disabled=!1}},await Ke();function s(){let y=(new FormData(o).get("q")??"").toLowerCase(),S=X.bots.filter(v=>!y||(v.botName??"").toLowerCase().includes(y)||(v.larkAppId??"").toLowerCase().includes(y));if(Z){n.innerHTML=`<p class="hint-warn">\u65E0\u6CD5\u52A0\u8F7D bot \u5217\u8868\uFF1A${w(Z)}<br>\u5E38\u89C1\u539F\u56E0\uFF1Adashboard / daemon \u8FDB\u7A0B\u8FD8\u5728\u8DD1\u65E7\u4EE3\u7801\uFF0C\u6267\u884C <code>botmux restart</code> \u540E\u5237\u65B0\u3002</p>`;return}if(S.length===0){n.innerHTML=`<p class="empty">${t("botDefaults.empty")}</p>`;return}n.innerHTML=S.map(a).join(""),c()}function a(p){if(p.error)return`<article class="bd-card" data-appid="${w(p.larkAppId)}">
328
+ <header><strong>${w(p.botName??p.larkAppId)}</strong>
329
+ <small>${w(p.larkAppId)}</small></header>
330
+ <p class="hint-warn-inline">\u67E5\u8BE2\u5931\u8D25\uFF1A${w(p.error)}</p>
331
+ </article>`;let y=p.defaultOncall??{enabled:!1,workingDir:"",since:0},S=!!y.enabled;return`<article class="bd-card" data-appid="${w(p.larkAppId)}">
322
332
  <header>
323
- <strong>${o(f.botName??f.larkAppId)}</strong>
324
- <small>${o(f.larkAppId)}</small>
333
+ <strong>${w(p.botName??p.larkAppId)}</strong>
334
+ <small>${w(p.larkAppId)}</small>
325
335
  </header>
326
336
  <div class="bd-body">
327
337
  <label class="checkbox-row">
328
338
  <input type="checkbox" data-action="toggle" ${S?"checked":""}>
329
- <strong>${e("botDefaults.defaultOncall")}</strong>
330
- <small>${e("botDefaults.defaultOncallHelp")}</small>
339
+ <strong>${t("botDefaults.defaultOncall")}</strong>
340
+ <small>${t("botDefaults.defaultOncallHelp")}</small>
331
341
  </label>
332
342
  <div class="bd-row">
333
343
  <label>
334
- <span>${e("botDefaults.workingDir")}</span>
344
+ <span>${t("botDefaults.workingDir")}</span>
335
345
  <input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
336
- value="${o(E.workingDir??"")}" ${S?"":"disabled"}>
346
+ value="${w(y.workingDir??"")}" ${S?"":"disabled"}>
337
347
  </label>
338
348
  </div>
339
349
  <div class="bd-meta">
340
- <small>${e("botDefaults.lastEnabled")}: ${o(he(E.since??0))}</small>
341
- <small>${e("botDefaults.autobound",{count:f.autoboundChatCount??0})}</small>
350
+ <small>${t("botDefaults.lastEnabled")}: ${w(Ve(y.since??0))}</small>
351
+ <small>${t("botDefaults.autobound",{count:p.autoboundChatCount??0})}</small>
342
352
  </div>
343
353
  <div class="actions">
344
- <button type="button" data-action="save">${e("botDefaults.save")}</button>
354
+ <button type="button" data-action="save">${t("botDefaults.save")}</button>
345
355
  <span class="oncall-status" data-status></span>
346
356
  </div>
347
357
  </div>
348
- </article>`}function T(){s.querySelectorAll(".bd-card").forEach(f=>{let E=f.dataset.appid,S=f.querySelector("input[data-action=toggle]"),y=f.querySelector("input[data-input=workingDir]"),D=f.querySelector("button[data-action=save]"),i=f.querySelector("[data-status]");!S||!y||!D||!i||(S.addEventListener("change",()=>{y.disabled=!S.checked,S.checked&&y.focus()}),D.addEventListener("click",async()=>{i.textContent="",i.className="oncall-status";let k=S.checked,h=y.value.trim();if(k&&!h){i.textContent=e("botDefaults.required"),i.classList.add("hint-warn-inline");return}D.disabled=!0;try{let d=await fetch(`/api/bots/${encodeURIComponent(E)}/default-oncall`,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({enabled:k,workingDir:h})}),u=await d.json().catch(()=>({}));if(d.ok&&u.ok){let g=u.resolvedPath?` \u2192 ${u.resolvedPath}`:"";i.textContent=k?`\u2713 \u5DF2\u5F00\u542F${g}\uFF08\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8 oncall\uFF09`:"\u2713 \u5DF2\u5173\u95ED\uFF08\u5DF2\u7ED1\u5B9A\u7684\u7FA4\u4E0D\u52A8\uFF09",i.classList.add("hint-ok");let w=R.bots.find($=>$.larkAppId===E);w&&u.defaultOncall&&(w.defaultOncall=u.defaultOncall);let p=f.querySelector(".bd-meta small:first-child");p&&u.defaultOncall?.since!=null&&(p.textContent=`\u4E0A\u6B21\u542F\u7528\u65F6\u95F4\uFF1A${he(u.defaultOncall.since)}`)}else i.textContent=`\u2717 ${u.error??d.status}`,i.classList.add("hint-warn-inline")}catch(d){i.textContent=`\u2717 ${d?.message??d}`,i.classList.add("hint-warn-inline")}finally{D.disabled=!1}}))})}v(),r.addEventListener("input",v)}var q=null,G=null;function W(){G!==null&&(window.clearInterval(G),G=null)}function me(){return q||(q=document.createElement("dialog"),q.className="onboarding-dialog",document.body.appendChild(q),q.addEventListener("close",W),q)}function qe(t){return t.status==="waiting_for_scan"?e("botOnboarding.waiting"):t.status==="verifying"?e("botOnboarding.verifying"):t.status==="completed"?e("botOnboarding.completed"):t.status==="failed"?`${e("botOnboarding.failed")}: ${t.message??t.error??"unknown"}`:e("botOnboarding.starting")}function _(t){let s=me(),r=t.qrDataUrl?`<div class="qr-card">
349
- <img class="qr-image" src="${t.qrDataUrl}" alt="${e("botOnboarding.qrAlt")}">
350
- ${t.qrUrl?`<a class="onboarding-link" href="${t.qrUrl}" target="_blank" rel="noopener">${e("botOnboarding.openLink")}</a>`:""}
351
- </div>`:"",m=t.appId?`<p><b>App ID:</b> <code>${t.appId}</code></p>`:"",v=t.status==="completed"?`<p class="hint-ok">${e("botOnboarding.restartHint")}</p>`:"";s.innerHTML=`<article>
358
+ </article>`}function c(){n.querySelectorAll(".bd-card").forEach(p=>{let y=p.dataset.appid,S=p.querySelector("input[data-action=toggle]"),v=p.querySelector("input[data-input=workingDir]"),T=p.querySelector("button[data-action=save]"),l=p.querySelector("[data-status]");!S||!v||!T||!l||(S.addEventListener("change",()=>{v.disabled=!S.checked,S.checked&&v.focus()}),T.addEventListener("click",async()=>{l.textContent="",l.className="oncall-status";let I=S.checked,L=v.value.trim();if(I&&!L){l.textContent=t("botDefaults.required"),l.classList.add("hint-warn-inline");return}T.disabled=!0;try{let g=await fetch(`/api/bots/${encodeURIComponent(y)}/default-oncall`,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({enabled:I,workingDir:L})}),u=await g.json().catch(()=>({}));if(g.ok&&u.ok){let $=u.resolvedPath?` \u2192 ${u.resolvedPath}`:"";l.textContent=I?`\u2713 \u5DF2\u5F00\u542F${$}\uFF08\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8 oncall\uFF09`:"\u2713 \u5DF2\u5173\u95ED\uFF08\u5DF2\u7ED1\u5B9A\u7684\u7FA4\u4E0D\u52A8\uFF09",l.classList.add("hint-ok");let k=X.bots.find(A=>A.larkAppId===y);k&&u.defaultOncall&&(k.defaultOncall=u.defaultOncall);let b=p.querySelector(".bd-meta small:first-child");b&&u.defaultOncall?.since!=null&&(b.textContent=`\u4E0A\u6B21\u542F\u7528\u65F6\u95F4\uFF1A${Ve(u.defaultOncall.since)}`)}else l.textContent=`\u2717 ${u.error??g.status}`,l.classList.add("hint-warn-inline")}catch(g){l.textContent=`\u2717 ${g?.message??g}`,l.classList.add("hint-warn-inline")}finally{T.disabled=!1}}))})}s(),o.addEventListener("input",s)}function Bt(){let e=[["",t("workflow.filter.nonTerminal")],["all",t("workflow.filter.all")],["pending",z("pending")],["running",z("running")],["waiting",z("waiting")],["succeeded",z("succeeded")],["failed",z("failed")],["cancelled",z("cancelled")]];return`
359
+ <nav class="wf-subnav">
360
+ <a href="#/workflows" class="active" data-i18n="workflow.subnav.runs">${i(t("workflow.subnav.runs"))}</a>
361
+ <a href="#/workflows/catalog" data-i18n="workflow.subnav.catalog">${i(t("workflow.subnav.catalog"))}</a>
362
+ </nav>
363
+ <form id="wf-filters" class="filters">
364
+ <input type="search" name="q" placeholder="${i(t("workflow.searchPlaceholder"))}" />
365
+ <select name="status">
366
+ ${e.map(([n,o])=>`<option value="${i(n)}">${i(o)}</option>`).join("")}
367
+ </select>
368
+ <span id="wf-last-load" class="muted"></span>
369
+ </form>
370
+ <table>
371
+ <thead><tr>
372
+ <th>${i(t("workflow.table.run"))}</th><th>${i(t("workflow.table.workflow"))}</th><th>${i(t("workflow.table.status"))}</th>
373
+ <th>${i(t("workflow.table.lastSeq"))}</th><th>${i(t("workflow.table.dangling"))}</th><th>${i(t("workflow.table.updated"))}</th>
374
+ <th>${i(t("workflow.table.chatApp"))}</th>
375
+ </tr></thead>
376
+ <tbody id="wf-tbody"></tbody>
377
+ </table>
378
+ `}var jt=5e3,Ut=2e3,ne=new Set(["succeeded","failed","cancelled"]);function i(e){return e.replace(/[&<>"']/g,n=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[n])}function Ft(e){let n=new Date(e),r=Date.now()-e;return r<6e4?t("time.secondsAgo",{value:Math.max(1,Math.floor(r/1e3))}):r<36e5?t("time.minutesAgo",{value:Math.floor(r/6e4)}):r<864e5?t("time.hoursAgo",{value:Math.floor(r/36e5)}):n.toISOString().slice(0,19).replace("T"," ")}function W(e){return`<span class="${ne.has(e)?"wf-status terminal":"wf-status live"} wf-status-${i(e)}">${i(z(e))}</span>`}function z(e){let n=`workflow.status.${e}`,o=t(n);return o===n?e:o}function et(e){let n=location.hash.match(/^#\/workflows\/([^?#]+)(?:\?([^#]*))?$/);if(n){let o=new URLSearchParams(n[2]??"");return _t(e,decodeURIComponent(n[1]),{focusAttemptId:o.get("attempt")??void 0})}return Wt(e)}function Wt(e){e.innerHTML=Bt();let n=e.querySelector("#wf-tbody"),o=e.querySelector("#wf-filters"),r=e.querySelector("#wf-last-load"),s=[],a=null,c=!1,p=null,y=!1;function S(g){let $=(new FormData(o).get("q")??"").trim().toLowerCase();return $?g.filter(k=>k.runId.toLowerCase().includes($)||k.workflowId.toLowerCase().includes($)||(k.chatId??"").toLowerCase().includes($)):g}function v(){let g=S(s);if(g.length===0){n.innerHTML=`<tr><td colspan="7" class="empty">${p?i(t("workflow.list.failedLoad",{error:p})):s.length===0?i(t("workflow.list.noRuns")):i(t("workflow.list.noFilterMatch"))}</td></tr>`;return}n.innerHTML=g.map(u=>{let $=`${u.dEf}/${u.dAct}/${u.dWait}`,k=u.dEf+u.dAct+u.dWait>0?"wf-dangling has":"wf-dangling none",b=[];u.chatId&&b.push(i(u.chatId)),u.larkAppId&&b.push(`<span class="muted">${i(u.larkAppId)}</span>`);let A=b.length>0?b.join("<br/>"):"\u2014",d=Gt(u);return`<tr data-runid="${i(u.runId)}">
379
+ <td><a href="#/workflows/${encodeURIComponent(u.runId)}"><code>${i(u.runId)}</code></a></td>
380
+ <td>${i(u.workflowId)}</td>
381
+ <td>${W(u.status)}${u.failedNodeId?` <span class="muted">(${i(u.failedNodeId)})</span>`:""}${d}</td>
382
+ <td>${u.lastSeq}</td>
383
+ <td class="${k}">${$}</td>
384
+ <td title="${i(new Date(u.updatedAt).toISOString())}">${Ft(u.updatedAt)}</td>
385
+ <td>${A}</td>
386
+ </tr>`}).join("")}function T(){p?(r.textContent=t("workflow.list.error",{error:p}),r.classList.add("error")):(r.textContent=t("workflow.list.loaded",{count:s.length,time:new Date().toLocaleTimeString()}),r.classList.remove("error"))}async function l(){if(!(y||c)&&!document.hidden){c=!0;try{let g=o.elements.namedItem("status")?.value??"",u=new URLSearchParams;g==="all"?u.set("all","1"):g&&u.set("status",g);let $="/api/workflows/runs"+(u.toString()?`?${u}`:""),k=await fetch($);k.ok?(s=(await k.json()).runs??[],p=null):(p=`HTTP ${k.status}`,s=[])}catch(g){p=g?.message??String(g),s=[]}finally{c=!1,y||(v(),T())}}}function I(){a!==null&&window.clearTimeout(a),a=window.setTimeout(async()=>{await l(),y||I()},jt)}function L(){document.hidden||l()}return o.addEventListener("input",()=>{v()}),o.addEventListener("change",g=>{g.target.getAttribute("name")==="status"&&l()}),document.addEventListener("visibilitychange",L),l().then(()=>{y||I()}),()=>{y=!0,a!==null&&window.clearTimeout(a),document.removeEventListener("visibilitychange",L)}}function _t(e,n,o={}){e.innerHTML=`
387
+ <div class="wf-detail-head">
388
+ <a class="btn-link" href="#/workflows">${i(t("workflow.detail.back"))}</a>
389
+ <div>
390
+ <h2><code>${i(n)}</code></h2>
391
+ <div id="wf-detail-subtitle" class="muted">${i(t("workflow.detail.loading"))}</div>
392
+ </div>
393
+ <button id="wf-cancel-run" type="button" class="contrast" hidden>${i(t("workflow.detail.cancel"))}</button>
394
+ <span id="wf-detail-refresh" class="muted"></span>
395
+ </div>
396
+ <section id="wf-detail-error" class="hint-warn" hidden></section>
397
+ <section id="wf-cancel-status" class="hint-ok" hidden></section>
398
+ <section id="wf-summary" class="wf-summary-grid"></section>
399
+ <section id="wf-dangling-panel"></section>
400
+ <section class="wf-panel">
401
+ <div class="wf-panel-title">
402
+ <h3>${i(t("workflow.detail.parallel"))}</h3>
403
+ <span id="wf-parallel-meta" class="muted"></span>
404
+ </div>
405
+ <div id="wf-parallel-view"></div>
406
+ </section>
407
+ <section class="wf-panel">
408
+ <div class="wf-panel-title">
409
+ <h3>${i(t("workflow.detail.nodes"))}</h3>
410
+ </div>
411
+ <div class="wf-table-scroll">
412
+ <table>
413
+ <thead><tr>
414
+ <th>${i(t("workflow.detail.node"))}</th><th>${i(t("workflow.detail.nodeStatus"))}</th><th>${i(t("workflow.detail.activity"))}</th><th>${i(t("workflow.detail.activityStatus"))}</th>
415
+ <th>${i(t("workflow.detail.attempts"))}</th><th>${i(t("workflow.detail.current"))}</th><th>${i(t("workflow.detail.detail"))}</th>
416
+ </tr></thead>
417
+ <tbody id="wf-node-tbody"></tbody>
418
+ </table>
419
+ </div>
420
+ </section>
421
+ <section class="wf-panel">
422
+ <div class="wf-panel-title">
423
+ <h3>${i(t("workflow.detail.nodeIO"))}</h3>
424
+ </div>
425
+ <div id="wf-io-list" class="wf-io-list"></div>
426
+ </section>
427
+ <section class="wf-panel">
428
+ <div class="wf-panel-title">
429
+ <h3>${i(t("workflow.detail.timeline"))}</h3>
430
+ <button id="wf-load-older" type="button" hidden>${i(t("workflow.detail.loadOlder"))}</button>
431
+ </div>
432
+ <div class="wf-table-scroll wf-timeline-scroll">
433
+ <table>
434
+ <thead><tr>
435
+ <th>${i(t("workflow.detail.seq"))}</th><th>${i(t("workflow.detail.event"))}</th><th>${i(t("workflow.detail.actor"))}</th><th>${i(t("workflow.detail.node"))}</th><th>${i(t("workflow.detail.activity"))}</th><th>${i(t("workflow.detail.error"))}</th><th>${i(t("workflow.detail.time"))}</th>
436
+ </tr></thead>
437
+ <tbody id="wf-event-tbody"></tbody>
438
+ </table>
439
+ </div>
440
+ <div id="wf-event-meta" class="muted"></div>
441
+ </section>
442
+ `;let r=e.querySelector("#wf-detail-subtitle"),s=e.querySelector("#wf-detail-refresh"),a=e.querySelector("#wf-detail-error"),c=e.querySelector("#wf-cancel-status"),p=e.querySelector("#wf-summary"),y=e.querySelector("#wf-dangling-panel"),S=e.querySelector("#wf-parallel-view"),v=e.querySelector("#wf-parallel-meta"),T=e.querySelector("#wf-node-tbody"),l=e.querySelector("#wf-io-list"),I=e.querySelector(".wf-timeline-scroll"),L=e.querySelector("#wf-event-tbody"),g=e.querySelector("#wf-event-meta"),u=e.querySelector("#wf-cancel-run"),$=e.querySelector("#wf-load-older"),k=null,b=[],A=new Set,d=null,f=null,h=!1,E=0,C=null,R=!1,q=!1,P=!1,x=new Set,be=new Map,Ae=new Map,re=new Map,ie=new Set,le=new Map,K=new Set,ee=new Map,gt=new Map,Ce=0,He=o.focusAttemptId;function j(m){if(!m){a.hidden=!0,a.textContent="";return}a.hidden=!1,a.textContent=m}function Re(m){if(!m){c.hidden=!0,c.textContent="";return}c.hidden=!1,c.textContent=m}async function Oe(){let m=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/snapshot`);if(m.status===404)throw new Error(t("workflow.detail.unknownRun"));if(!m.ok)throw new Error(t("workflow.detail.snapshotHttp",{status:m.status}));k=await m.json()}async function de(m){let N=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/events?${m}`);if(N.status===404)throw new Error(t("workflow.detail.unknownRun"));if(!N.ok)throw new Error(t("workflow.detail.eventsHttp",{status:N.status}));return await N.json()}function ce(m,N){let O=m.filter(H=>A.has(H.eventId)?!1:(A.add(H.eventId),!0));O.length!==0&&(b=N==="prepend"?[...O,...b]:[...b,...O],b.sort((H,_)=>oe(H.eventId)-oe(_.eventId)))}async function ht(){await Oe();let m=await de(new URLSearchParams({tail:"100"}));b=[],A=new Set,ce(m.events,"append"),d=m.oldestSeq,f=m.newestSeq,h=m.hasOlder,E=m.totalCount,U()}async function ue(){if(!(R||q||document.hidden)){q=!0;try{if(await Oe(),f!==null){let m=await de(new URLSearchParams({afterSeq:String(f),limit:"200"}));ce(m.events,"append"),m.newestSeq!==null&&(f=m.newestSeq),d===null&&m.oldestSeq!==null&&(d=m.oldestSeq),E=m.totalCount}else{let m=await de(new URLSearchParams({tail:"1"}));ce(m.events,"append"),d=m.oldestSeq,f=m.newestSeq,h=m.hasOlder,E=m.totalCount}j(null),U()}catch(m){j(m?.message??String(m))}finally{q=!1}}}async function bt(){if(!(d===null||!h)){$.disabled=!0;try{let m=await de(new URLSearchParams({beforeSeq:String(d),limit:"100"}));ce(m.events,"prepend"),m.oldestSeq!==null&&(d=m.oldestSeq),h=m.hasOlder,E=m.totalCount,j(null),U()}catch(m){j(m?.message??String(m))}finally{$.disabled=!1}}}async function vt(){if(!k||ne.has(k.run.status)||P)return;if(!k.chatBinding?.larkAppId){j(t("workflow.detail.cancelUnavailable",{runId:n}));return}let m=zt(k),N=t("workflow.detail.cancelConfirm",{runId:n,...m});if(window.confirm(N)){P=!0,u.disabled=!0;try{let O=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/cancel`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({reason:"cancelled via dashboard"})});if(O.status===401)throw new Error(t("workflow.detail.writeAccessCancel"));let H=await O.json().catch(()=>({}));if(!O.ok||!H.ok)throw new Error(H.hint??H.error??t("workflow.detail.cancelHttp",{status:O.status}));Re(H.pending?t("workflow.detail.cancelPending"):null),j(null),await ue()}catch(O){j(O?.message??String(O))}finally{P=!1,u.disabled=!1,U()}}}async function kt(m,N){if(!K.has(m)){K.add(m),ee.delete(m),U();try{let O=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/attempts/${encodeURIComponent(N)}/${encodeURIComponent(m)}/resume`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(O.status===401)throw new Error(t("workflow.detail.writeAccessResume"));let H=await O.json().catch(()=>({}));if(!O.ok||!H.ok||!H.resumeId||!H.url)throw new Error(H.hint??H.message??H.error??t("workflow.detail.resumeStartFailed",{status:O.status}));le.set(m,{resumeId:H.resumeId,url:H.url})}catch(O){let H=O?.message??String(O);ee.set(m,H)}finally{K.delete(m),U()}}}async function yt(m,N){if(!K.has(m)){K.add(m),ee.delete(m),U();try{let O=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/attempts/${encodeURIComponent(N)}/${encodeURIComponent(m)}/resume/end`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({reason:"ended_by_dashboard"})});if(O.status===401)throw new Error(t("workflow.detail.writeAccessResume"));let H=await O.json().catch(()=>({}));if(!O.ok||!H.ok)if(H.error==="resume_not_running")le.delete(m);else throw new Error(H.hint??H.message??H.error??t("workflow.detail.resumeEndFailed",{status:O.status}));else le.delete(m)}catch(O){let H=O?.message??String(O);ee.set(m,H)}finally{K.delete(m),U()}}}async function $t(m,N){if(!ie.has(m)){ie.add(m),re.delete(m),U();try{let O=Ae.get(m)?.trim()||void 0,H=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/${N}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({comment:O})});if(H.status===401)throw new Error(t("workflow.detail.writeAccessApproval"));let _=await H.json().catch(()=>({}));if(!H.ok||!_.ok)throw new Error(_.hint??_.message??_.error??t("workflow.detail.actionHttp",{action:N,status:H.status}));let ke=N==="approve"?t("workflow.detail.approved"):t("workflow.detail.rejected");re.set(m,{kind:"ok",text:_.alreadyTerminal?t("workflow.detail.alreadyTerminal",{label:ke}):_.pending?t("workflow.detail.workflowContinue",{label:ke}):t("workflow.detail.workflowRefreshing",{label:ke})}),j(null),await ue()}catch(O){let H=O?.message??String(O);re.set(m,{kind:"error",text:H}),j(H)}finally{ie.delete(m),U()}}}function U(){if(!k)return;Ce=I.scrollTop;let m=k.run;ne.has(m.status)&&Re(null),r.innerHTML=`${i(m.workflowId??"?")} \xB7 ${W(m.status)} \xB7 lastSeq ${k.lastSeq}`,s.textContent=t("workflow.detail.refreshed",{time:new Date().toLocaleTimeString()}),u.hidden=ne.has(m.status),u.disabled=P||!k.chatBinding?.larkAppId,u.textContent=k.chatBinding?.larkAppId?t("workflow.detail.cancel"):t("workflow.detail.cliCancelOnly"),u.title=k.chatBinding?.larkAppId?t("workflow.detail.cancelTitle"):t("workflow.detail.cliCancelTitle",{runId:n}),Jt(p,k),Kt(y,k),Vt(S,v,k,b),tn(T,k),nn(l,k,x,be,{comments:Ae,statuses:re,resolving:ie,onResolve:$t},{sessions:le,pending:K,errors:ee,onStart:kt,onEnd:yt},He,gt)&&(He=void 0),An(L,b),I.scrollTop=Ce,$.hidden=!h,g.textContent=t("workflow.detail.eventsLoaded",{loaded:b.length,total:E})}function ve(){if(C!==null&&window.clearTimeout(C),k&&ne.has(k.run.status)){C=null;return}C=window.setTimeout(async()=>{await ue(),R||ve()},Ut)}function xe(){document.hidden||ue().then(()=>{!R&&C===null&&ve()})}return $.addEventListener("click",()=>{bt()}),u.addEventListener("click",()=>{vt()}),document.addEventListener("visibilitychange",xe),ht().then(()=>{j(null),R||ve()}).catch(m=>{j(m?.message??String(m)),r.textContent=t("workflow.detail.loadFailed")}),()=>{R=!0,C!==null&&window.clearTimeout(C),document.removeEventListener("visibilitychange",xe)}}function Jt(e,n){let o=n.run,r=[[t("workflow.summary.workflow"),i(o.workflowId??"?")],[t("workflow.summary.status"),W(o.status)],[t("workflow.summary.lastSeq"),String(n.lastSeq)],[t("workflow.summary.updated"),i(new Date(n.updatedAt).toLocaleString())],[t("workflow.summary.revision"),i(me(o.revisionId))],[t("workflow.summary.initiator"),i(o.initiator??"-")]];o.failedNodeId&&r.push([t("workflow.summary.failedNode"),i(o.failedNodeId)]),o.cancelOriginEventId&&r.push([t("workflow.summary.cancelOrigin"),i(o.cancelOriginEventId)]),n.chatBinding&&(r.push([t("workflow.summary.chat"),`<code>${i(n.chatBinding.chatId)}</code>`]),r.push([t("workflow.summary.app"),`<code>${i(n.chatBinding.larkAppId)}</code>`])),e.innerHTML=r.map(([s,a])=>`<div class="wf-summary-item"><span>${s}</span><strong>${a}</strong></div>`).join("")}function Gt(e){if(!e.errorCode)return"";let n=e.errorMessage?` \u2014 ${On(e.errorMessage,96)}`:"";return`<div class="wf-run-error">
443
+ <span class="muted error">${i(e.errorCode)}</span>${i(n)}
444
+ </div>`}function zt(e){let n=e.dangling;return{total:new Set([...n.activities,...n.effectAttempted,...n.waits,...n.cancels]).size,effects:n.effectAttempted.length,activities:n.activities.length,waits:n.waits.length,cancels:n.cancels.length}}function Kt(e,n){let o=n.dangling,r=[[t("workflow.dangling.activities"),o.activities],[t("workflow.dangling.effects"),o.effectAttempted],[t("workflow.dangling.waits"),o.waits],[t("workflow.dangling.cancels"),o.cancels]],s=new Set(r.flatMap(([,a])=>a)).size;if(e.className=s>0?"wf-panel wf-dangling-panel has":"wf-panel wf-dangling-panel",s===0){e.innerHTML=`<div class="wf-panel-title"><h3>${i(t("workflow.detail.dangling"))}</h3></div><div class="muted">${i(t("workflow.detail.noDangling"))}</div>`;return}e.innerHTML=`<div class="wf-panel-title"><h3>${i(t("workflow.detail.dangling"))}</h3><span class="wf-dangling has">${s}</span></div>
445
+ <div class="wf-dangling-grid">
446
+ ${r.map(([a,c])=>`<div><strong>${a}</strong>${c.length===0?`<div class="muted">${i(t("workflow.detail.none"))}</div>`:`<ul>${c.map(p=>`<li><code>${i(p)}</code></li>`).join("")}</ul>`}</div>`).join("")}
447
+ </div>`}function Vt(e,n,o,r){let s=Qt(r,o);if(s.length===0){n.textContent="",e.innerHTML=`<div class="empty">${i(t("workflow.detail.noParallelData"))}</div>`;return}let a=Date.now(),c=Math.min(...s.map(l=>l.startedAt)),p=Math.max(...s.map(l=>l.endedAt??a),c+1e3),y=Math.max(1,p-c),S=Xt(s,a),v=s.filter(l=>!l.endedAt&&(l.status==="running"||l.status==="effectAttempting")).length;n.textContent=t("workflow.detail.parallelMeta",{count:s.length,max:S,running:v});let T=s.sort((l,I)=>l.startedAt-I.startedAt||l.activityId.localeCompare(I.activityId)).map(l=>Yt(l,c,y,a)).join("");e.innerHTML=`<div class="wf-parallel-axis">
448
+ <span title="${i(new Date(c).toISOString())}">${i(we(c))}</span>
449
+ <span title="${i(new Date(p).toISOString())}">${i(we(p))}</span>
450
+ </div>
451
+ <div class="wf-parallel-list">${T}</div>`}function Qt(e,n){let o=new Map,r=new Map(n.activities.map(s=>[s.activityId,s.ownerNodeId]));for(let s of[...e].sort((a,c)=>oe(a.eventId)-oe(c.eventId))){let a=Rn(s);if(!a)continue;let c=typeof a.activityId=="string"?a.activityId:void 0,p=typeof a.attemptId=="string"?a.attemptId:void 0;if(!c||!p)continue;let y=o.get(p);if(s.type==="attemptCreated"){let S=typeof a.attemptNumber=="number"?a.attemptNumber:void 0;y={nodeId:typeof a.nodeId=="string"?a.nodeId:r.get(c),activityId:c,attemptId:p,attemptNumber:S,status:"pending",startedAt:s.timestamp},o.set(p,y);continue}y||(y={nodeId:r.get(c),activityId:c,attemptId:p,status:"pending",startedAt:s.timestamp},o.set(p,y)),s.type==="activityRunning"?(y.status="running",y.runningAt=s.timestamp):s.type==="effectAttempted"?y.status="effectAttempting":s.type==="activityWaiting"||s.type==="waitCreated"?y.status="waiting":Zt(s.type)&&(y.status=en(s.type),y.endedAt=s.timestamp,y.endType=s.type)}return[...o.values()]}function Yt(e,n,o,r){let s=e.endedAt??r,a=Ze((e.startedAt-n)/o*100,0,100),c=Ze((Math.max(s,e.startedAt+1)-e.startedAt)/o*100,.7,100-a),p=e.nodeId??e.activityId,y=e.attemptNumber!==void 0?`#${e.attemptNumber}`:me(e.attemptId),S=[`${p} ${e.status}`,`${new Date(e.startedAt).toISOString()} \u2192 ${e.endedAt?new Date(e.endedAt).toISOString():t("workflow.detail.parallelNow")}`,e.endType?`end: ${e.endType}`:void 0].filter(Boolean).join(`
452
+ `);return`<div class="wf-parallel-row">
453
+ <div class="wf-parallel-label">
454
+ <code>${i(p)}</code>
455
+ <span class="muted">${i(e.activityId)} \xB7 ${i(y)}</span>
456
+ </div>
457
+ <div class="wf-parallel-track">
458
+ <div class="wf-parallel-bar wf-parallel-${i(e.status)}" style="left:${a.toFixed(3)}%;width:${c.toFixed(3)}%;" title="${i(S)}">
459
+ <span>${i(z(e.status))}</span>
460
+ </div>
461
+ </div>
462
+ </div>`}function Xt(e,n){let o=[];for(let a of e)o.push({time:a.startedAt,delta:1}),o.push({time:a.endedAt??n,delta:-1});o.sort((a,c)=>a.time-c.time||c.delta-a.delta);let r=0,s=0;for(let a of o)r+=a.delta,s=Math.max(s,r);return s}function Zt(e){return e==="activitySucceeded"||e==="activityFailed"||e==="activityTimedOut"||e==="activityCanceled"}function en(e){return e==="activitySucceeded"?"succeeded":e==="activityCanceled"?"cancelled":e==="activityTimedOut"?"timedOut":"failed"}function tn(e,n){let o=new Map(n.activities.map(a=>[a.activityId,a])),r=new Set,s=[];for(let a of n.nodes){let c=(a.activityId?o.get(a.activityId):void 0)??n.activities.find(p=>p.ownerNodeId===a.nodeId);c&&r.add(c.activityId),s.push(Ye(a,c))}for(let a of n.activities)r.has(a.activityId)||s.push(Ye(void 0,a));e.innerHTML=s.length>0?s.join(""):`<tr><td colspan="7" class="empty">${i(t("workflow.detail.noNodes"))}</td></tr>`}function Ye(e,n){let o=n?.attempts[n.attempts.length-1];return`<tr>
463
+ <td>${e?`<code>${i(e.nodeId)}</code>`:'<span class="muted">-</span>'}</td>
464
+ <td>${e?W(e.status):'<span class="muted">-</span>'}</td>
465
+ <td>${n?`<code>${i(n.activityId)}</code>`:'<span class="muted">-</span>'}</td>
466
+ <td>${n?W(n.status):'<span class="muted">-</span>'}</td>
467
+ <td>${n?.attempts.length??0}</td>
468
+ <td>${o?`<code>${i(o.attemptId)}</code>`:'<span class="muted">-</span>'}</td>
469
+ <td>${o?Mn(o):`<span class="muted">${i(t("workflow.detail.idle"))}</span>`}</td>
470
+ </tr>`}function nn(e,n,o,r,s,a,c,p){$n(e,o,r),bn(e,s.comments);let y=!!(c&&n.attemptIO?.[c]?.terminal);y&&c&&o.add(Le(c,t("workflow.detail.liveTerminal")));let S=on(n),v=new Set;if(p){for(let l of S){v.add(l.key);let I=p.get(l.key);I||(I=an(l.key),p.set(l.key,I),e.appendChild(I.article)),sn(I,l,o,s,a,c)}for(let[l,I]of Array.from(p))v.has(l)||(I.article.remove(),p.delete(l));if(S.length===0){if(!e.querySelector(".wf-io-empty-placeholder")){let l=document.createElement("div");l.className="empty wf-io-empty-placeholder",l.textContent=t("workflow.detail.noNodeIO"),e.appendChild(l)}}else e.querySelector(".wf-io-empty-placeholder")?.remove()}else{let l=[];for(let I of S)l.push(un(I,o,s,a,c));e.innerHTML=l.length>0?l.join(""):`<div class="empty">${i(t("workflow.detail.noNodeIO"))}</div>`}In(e,r);let T=yn(e,c);return Sn(e,o),Tn(e,r),kn(e,s),dt(e,a),T&&y}function on(e){let n=new Map(e.activities.map(s=>[s.activityId,s])),o=new Set,r=[];for(let s of e.nodes){let a=(s.activityId?n.get(s.activityId):void 0)??e.activities.find(c=>c.ownerNodeId===s.nodeId);if(!a){r.push({key:`node:${s.nodeId}`,node:s});continue}o.add(a.activityId),r.push({key:`activity:${a.activityId}`,node:s,activity:a,io:e.attemptIO?.[pe(a)?.attemptId??""]})}for(let s of e.activities)o.has(s.activityId)||r.push({key:`activity:${s.activityId}`,activity:s,io:e.attemptIO?.[pe(s)?.attemptId??""]});return r}function an(e){let n=document.createElement("article");n.className="wf-io-card",n.dataset.wfCardKey=e;let o=document.createElement("div");o.className="wf-io-card-head";let r=document.createElement("div");r.className="wf-io-terminal-slot";let s=document.createElement("div");return s.className="wf-io-grid",n.appendChild(o),n.appendChild(r),n.appendChild(s),{article:n,head:o,terminalSlot:r,grid:s,currentTerminalUrl:null}}function sn(e,n,o,r,s,a){let c=pe(n.activity),p=n.node?.nodeId??n.activity?.ownerNodeId??n.activity?.activityId??"unknown",y=!!(c&&c.attemptId===a);e.article.classList.toggle("is-focused",y),c?e.article.dataset.wfAttemptCard=c.attemptId:delete e.article.dataset.wfAttemptCard;let S=lt(c,r);e.head.innerHTML=`
471
+ <header>
472
+ <div>
473
+ <strong><code>${i(p)}</code></strong>
474
+ <span class="muted">${n.activity?i(n.activity.activityId):i(t("workflow.detail.notDispatched"))}</span>
475
+ </div>
476
+ <div>${n.node?W(n.node.status):""} ${n.activity?W(n.activity.status):""}</div>
477
+ </header>
478
+ <div class="wf-io-meta">
479
+ ${c?`${i(t("workflow.detail.attempt"))} <code>${i(c.attemptId)}</code>`:i(t("workflow.detail.noAttempt"))}
480
+ </div>
481
+ ${S}
482
+ `;let v=tt(c,n.activity,n.io?.terminal,s),T=v?.url??null;if(T!==e.currentTerminalUrl)v===null?e.terminalSlot.innerHTML="":e.terminalSlot.innerHTML=nt(n.key,c,n.activity,n.io?.terminal,v,o,s),e.currentTerminalUrl=T;else if(v!==null&&n.io?.terminal){let I=e.terminalSlot.querySelector("details.wf-terminal-block > summary");if(I){let L=ot(v.kind);I.innerHTML=`${i(L)} ${it(c,n.io.terminal)}`}c&&vn(e.terminalSlot,c,n.activity,n.io.terminal,v,s)}let l=c?.attemptId??n.activity?.activityId??n.node?.nodeId??"unknown";e.grid.innerHTML=`
483
+ ${F(l,t("workflow.detail.authoredInput"),n.io?.input,o)}
484
+ ${F(l,t("workflow.detail.resolvedInput"),n.io?.resolvedInput,o)}
485
+ ${F(l,t("workflow.detail.output"),n.io?.output,o)}
486
+ ${F(l,t("workflow.detail.executionLog"),n.io?.log,o)}
487
+ ${n.io?.waitPrompt?F(l,t("workflow.detail.waitPrompt"),n.io.waitPrompt,o):""}
488
+ `}function tt(e,n,o,r){if(!o||o.error)return null;if(fn(e,o))return{kind:"live",url:wn(o)};if(!e||!n||!pn(e,o))return null;let s=gn();if(!s)return null;let a=r?.sessions.get(e.attemptId);return a?{kind:"resume",url:a.url,resumeId:a.resumeId,downloadUrl:Xe(s,n.activityId,e.attemptId)}:{kind:"replay",url:mn(s,n.activityId,e.attemptId,!!o.hasPtyLog),downloadUrl:Xe(s,n.activityId,e.attemptId)}}function nt(e,n,o,r,s,a,c){if(!r)return"";let p=ot(s.kind),y=Le(e,p),S=it(n,r),v=rn(s.kind),T=s.kind==="replay"||s.kind==="resume"?`<a class="btn-link" href="${i(s.downloadUrl)}" download>${i(t("workflow.detail.downloadFullLog"))}</a>`:"",l=n?st(n,o,r,s,c):"",I=n?rt(n.attemptId,c):"";return`<details class="wf-io-block wf-terminal-block" data-io-key="${i(y)}"${a.has(y)?" open":""}>
489
+ <summary>${i(p)} ${S}</summary>
490
+ <div class="wf-terminal-actions">
491
+ <a class="btn-link" href="${i(s.url)}" target="_blank" rel="noreferrer">${i(v)}</a>
492
+ ${T}
493
+ ${l}
494
+ </div>
495
+ ${I}
496
+ <iframe class="wf-terminal-frame" src="${i(s.url)}" title="${i(p)}" loading="lazy"></iframe>
497
+ </details>`}function ot(e){return e==="live"?t("workflow.detail.liveTerminal"):e==="resume"?t("workflow.detail.terminalResume"):t("workflow.detail.terminalReplay")}function rn(e){return e==="live"?t("workflow.detail.openTerminalNewTab"):e==="resume"?t("workflow.detail.openResumeNewTab"):t("workflow.detail.openReplayNewTab")}var at=new Set(["antigravity","cursor"]),ln=new Set(["aiden","coco","claude-code","codex"]);function dn(e){return!!e&&(ln.has(e)||at.has(e))}function cn(e){return!!e&&at.has(e)}function st(e,n,o,r,s){if(!s||r.kind==="live"||!n)return"";let a=r.kind==="resume",c=s.pending.has(e.attemptId),p=`data-wf-resume-attempt="${i(e.attemptId)}" data-wf-resume-activity="${i(n.activityId)}"`;return a?`<button type="button" class="btn-link" data-wf-resume-button="1" data-wf-resume-action="end" ${p}${c?" disabled":""}>${i(c?t("workflow.detail.resumeEnding"):t("workflow.detail.endResumeSession"))}</button>`:dn(o.cliId)?cn(o.cliId)&&!o.cliSessionId?`<button type="button" class="btn-link" data-wf-resume-button="1" disabled title="${i(t("workflow.detail.resumeMissingCliSession"))}">${i(t("workflow.detail.resumeSession"))}</button>`:`<button type="button" class="btn-link" data-wf-resume-button="1" data-wf-resume-action="start" ${p}${c?" disabled":""}>${i(c?t("workflow.detail.resumeStarting"):t("workflow.detail.resumeSession"))}</button>`:`<button type="button" class="btn-link" data-wf-resume-button="1" disabled title="${i(t("workflow.detail.resumeUnsupportedCli",{cliId:o.cliId??"?"}))}">${i(t("workflow.detail.resumeSession"))}</button>`}function rt(e,n){if(!n)return"";let o=n.errors.get(e);return o?`<div class="hint-warn wf-resume-status" data-wf-resume-status="${i(e)}">${i(o)}</div>`:""}function un(e,n,o,r,s){let a=pe(e.activity),c=e.node?.nodeId??e.activity?.ownerNodeId??e.activity?.activityId??"unknown",p=a?.attemptId??e.activity?.activityId??e.node?.nodeId??"unknown",y=lt(a,o),S=a?.attemptId===s?" is-focused":"",v=a?` data-wf-attempt-card="${i(a.attemptId)}"`:"",T=tt(a,e.activity,e.io?.terminal,r),l=T?nt(p,a,e.activity,e.io?.terminal,T,n,r):"";return`<article class="wf-io-card${S}" data-wf-card-key="${i(e.key)}"${v}>
498
+ <div class="wf-io-card-head">
499
+ <header>
500
+ <div>
501
+ <strong><code>${i(c)}</code></strong>
502
+ <span class="muted">${e.activity?i(e.activity.activityId):i(t("workflow.detail.notDispatched"))}</span>
503
+ </div>
504
+ <div>${e.node?W(e.node.status):""} ${e.activity?W(e.activity.status):""}</div>
505
+ </header>
506
+ <div class="wf-io-meta">
507
+ ${a?`${i(t("workflow.detail.attempt"))} <code>${i(a.attemptId)}</code>`:i(t("workflow.detail.noAttempt"))}
508
+ </div>
509
+ ${y}
510
+ </div>
511
+ <div class="wf-io-terminal-slot">${l}</div>
512
+ <div class="wf-io-grid">
513
+ ${F(p,t("workflow.detail.authoredInput"),e.io?.input,n)}
514
+ ${F(p,t("workflow.detail.resolvedInput"),e.io?.resolvedInput,n)}
515
+ ${F(p,t("workflow.detail.output"),e.io?.output,n)}
516
+ ${F(p,t("workflow.detail.executionLog"),e.io?.log,n)}
517
+ ${e.io?.waitPrompt?F(p,t("workflow.detail.waitPrompt"),e.io.waitPrompt,n):""}
518
+ </div>
519
+ </article>`}function pe(e){return e?.attempts[e.attempts.length-1]}function it(e,n){let o=[];return n.error?o.push(t("workflow.detail.error")):o.push(n.status==="live"?t("workflow.detail.terminalLive"):t("workflow.detail.terminalClosedShort")),e?.status&&o.push(e.status),n.webPort>0&&o.push(`:${n.webPort}`),`<span class="muted">${i(o.join(" \xB7 "))}</span>`}function fn(e,n){return n.status==="live"&&n.webPort>0&&(e?.status==="pending"||e?.status==="running"||e?.status==="effectAttempting")}function pn(e,n){return e.status==="succeeded"||e.status==="failed"||e.status==="cancelled"||e.status==="timedOut"?!!(n.sessionId||n.startedAt):!1}function wn(e){return`http://${window.location.hostname||"127.0.0.1"}:${e.webPort}`}function mn(e,n,o,r){let s=new URLSearchParams({runId:e,activityId:n,attemptId:o});return r&&s.set("hasPtyLog","1"),`/assets/terminal-replay.html?${s.toString()}`}function Xe(e,n,o){return`/api/workflows/runs/${encodeURIComponent(e)}/attempts/${encodeURIComponent(n)}/${encodeURIComponent(o)}/terminal-log/raw?download=1`}function gn(){let e=window.location.hash.match(/^#\/workflows\/([^/?#]+)/);if(!e)return null;try{return decodeURIComponent(e[1])}catch{return null}}function lt(e,n){if(!hn(e))return"";let o=e.attemptId,r=n.comments.get(o)??"",s=n.resolving.has(o),a=n.statuses.get(o),c=a?.kind==="error"?"hint-warn":"hint-ok";return`<div class="wf-approval-box" data-wf-approval="${i(o)}">
520
+ <label>
521
+ <span>${i(t("workflow.detail.approvalComment"))}</span>
522
+ <textarea class="wf-approval-comment" data-wf-approval-comment="${i(o)}" rows="2" placeholder="${i(t("workflow.detail.optionalComment"))}"${s?" disabled":""}>${i(r)}</textarea>
523
+ </label>
524
+ <div class="wf-approval-actions">
525
+ <button type="button" class="primary" data-wf-approval-action="approve" data-wf-attempt-id="${i(o)}"${s?" disabled":""}>${i(t("workflow.detail.approve"))}</button>
526
+ <button type="button" data-wf-approval-action="reject" data-wf-attempt-id="${i(o)}"${s?" disabled":""}>${i(t("workflow.detail.reject"))}</button>
527
+ ${s?`<span class="muted">${i(t("workflow.detail.submitting"))}</span>`:""}
528
+ </div>
529
+ ${a?`<div class="${c} wf-approval-status">${i(a.text)}</div>`:""}
530
+ </div>`}function hn(e){return!!e&&e.status==="waiting"&&e.wait?.waitKind==="human-gate"&&!e.wait.resolution}function bn(e,n){e.querySelectorAll("textarea[data-wf-approval-comment]").forEach(o=>{let r=o.dataset.wfApprovalComment;r&&n.set(r,o.value)})}function dt(e,n){e.querySelectorAll("button[data-wf-resume-action][data-wf-resume-attempt][data-wf-resume-activity]").forEach(o=>{o.dataset.wfResumeBound!=="1"&&(o.dataset.wfResumeBound="1",o.addEventListener("click",()=>{let r=o.dataset.wfResumeAttempt,s=o.dataset.wfResumeActivity,a=o.dataset.wfResumeAction;!r||!s||(a==="start"?n.onStart(r,s):a==="end"&&n.onEnd(r,s))}))})}function vn(e,n,o,r,s,a){let c=e.querySelector(".wf-terminal-actions");if(!c)return;let p=c.querySelector('button[data-wf-resume-button="1"]'),y=st(n,o,r,s,a);p?p.outerHTML=y:y&&c.insertAdjacentHTML("beforeend",y);let S=e.querySelector("details.wf-terminal-block");if(S){let v=S.querySelector(".wf-resume-status"),T=rt(n.attemptId,a);v?v.outerHTML=T:T&&c.insertAdjacentHTML("afterend",T)}dt(e,a)}function kn(e,n){e.querySelectorAll("textarea[data-wf-approval-comment]").forEach(o=>{let r=o.dataset.wfApprovalComment;r&&o.addEventListener("input",()=>{n.comments.set(r,o.value)})}),e.querySelectorAll("button[data-wf-approval-action][data-wf-attempt-id]").forEach(o=>{o.addEventListener("click",()=>{let r=o.dataset.wfAttemptId,s=o.dataset.wfApprovalAction;!r||s!=="approve"&&s!=="reject"||n.onResolve(r,s)})})}function F(e,n,o,r){let s=Le(e,n);return`<details class="wf-io-block" data-io-key="${i(s)}"${r.has(s)?" open":""}>
531
+ <summary>${i(n)} ${Ln(o)}</summary>
532
+ ${En(o)}
533
+ </details>`}function Le(e,n){return`${e}:${n}`}function yn(e,n){if(!n)return!1;for(let o of e.querySelectorAll("[data-wf-attempt-card]"))if(o.dataset.wfAttemptCard===n)return o.scrollIntoView({block:"center"}),!0;return!1}function $n(e,n,o){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(r=>{let s=r.dataset.ioKey;if(!s)return;r.open?n.add(s):n.delete(s);let a=r.querySelector(".wf-io-pre");a&&o.set(s,a.scrollTop)})}function Sn(e,n){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(o=>{o.dataset.ioToggleBound!=="1"&&(o.dataset.ioToggleBound="1",o.addEventListener("toggle",()=>{let r=o.dataset.ioKey;r&&(o.open?n.add(r):n.delete(r))}))})}function In(e,n){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(o=>{let r=o.dataset.ioKey;if(!r)return;let s=n.get(r);if(s===void 0)return;let a=o.querySelector(".wf-io-pre");a&&(a.scrollTop=s)})}function Tn(e,n){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(o=>{let r=o.dataset.ioKey;if(!r)return;let s=o.querySelector(".wf-io-pre");s&&s.dataset.ioScrollBound!=="1"&&(s.dataset.ioScrollBound="1",s.addEventListener("scroll",()=>{n.set(r,s.scrollTop)}))})}function Ln(e){if(!e)return`<span class="muted">${i(t("workflow.detail.empty"))}</span>`;let n=[];return e.outputBytes!==void 0&&n.push(`${e.outputBytes}B`),e.truncated&&n.push(t("workflow.detail.truncated")),e.error&&n.push(t("workflow.detail.error")),e.outputHash&&n.push(me(e.outputHash)),n.length?`<span class="muted">${i(n.join(" \xB7 "))}</span>`:""}function En(e){if(!e)return`<div class="muted wf-io-empty">${i(t("workflow.detail.noData"))}</div>`;let n=e.value!==void 0?JSON.stringify(e.value,null,2):e.text??"",o=e.error?`<div class="muted error">${i(e.error)}</div>`:"";return n?`${o}<pre class="wf-io-pre">${i(n)}</pre>`:`${o}<div class="muted wf-io-empty">${i(t("workflow.detail.noPreview"))}</div>`}function Mn(e){let n=[];if(e.effectAttempted&&n.push(`${i(t("workflow.detail.effect"))} ${i(e.effectAttempted.provider)}`),e.wait){let o=e.wait.resolution?`${e.wait.resolution.kind}${e.wait.resolution.resolution?":"+e.wait.resolution.resolution:""}`:t("workflow.detail.open");n.push(`${i(t("workflow.detail.wait"))} ${i(e.wait.waitKind)} ${i(o)}`),e.wait.deadlineAt!==void 0&&n.push(`${i(t("workflow.detail.deadline"))} ${i(we(e.wait.deadlineAt))}`)}if(e.error){let o=`${e.error.errorCode}${e.error.errorClass?` \xB7 ${e.error.errorClass}`:""}`;n.push(`<span class="muted error">${i(o)}</span>`),e.error.errorMessage&&n.push(`<span class="error wf-error-msg">${i(e.error.errorMessage)}</span>`)}return e.output&&n.push(`${i(t("workflow.detail.output"))} ${i(me(e.output.outputHash))}`),e.runningMs!==void 0&&n.push(`${e.runningMs}ms`),n.length>0?n.join("<br/>"):'<span class="muted">-</span>'}function An(e,n){e.innerHTML=n.length>0?n.map(Cn).join(""):`<tr><td colspan="7" class="empty">${i(t("workflow.detail.noEvents"))}</td></tr>`}function Cn(e){let n=Hn(e.payload);return`<tr>
534
+ <td>${oe(e.eventId)}</td>
535
+ <td><code>${i(e.type)}</code></td>
536
+ <td>${i(e.actor)}</td>
537
+ <td>${n.nodeId?`<code>${i(n.nodeId)}</code>`:"-"}</td>
538
+ <td>${n.activityId?`<code>${i(n.activityId)}</code>`:"-"}</td>
539
+ <td>${n.errorCode?`<span class="muted error">${i(n.errorCode)}</span>`:"-"}</td>
540
+ <td title="${i(new Date(e.timestamp).toISOString())}">${i(we(e.timestamp))}</td>
541
+ </tr>`}function oe(e){let n=e.lastIndexOf("-");if(n<0)return 0;let o=Number(e.slice(n+1));return Number.isFinite(o)?o:0}function Hn(e){if(!e||typeof e!="object"||"ref"in e)return{};let n=e,o={};typeof n.nodeId=="string"&&(o.nodeId=n.nodeId),typeof n.activityId=="string"&&(o.activityId=n.activityId),typeof n.failedNodeId=="string"&&(o.nodeId=n.failedNodeId);let r=n.error;return r&&typeof r=="object"&&"errorCode"in r&&(o.errorCode=String(r.errorCode)),o}function Rn(e){return!e.payload||typeof e.payload!="object"||"ref"in e.payload?null:e.payload}function Ze(e,n,o){return Math.min(o,Math.max(n,e))}function me(e){return e?e.length>18?e.slice(0,10)+"..."+e.slice(-6):e:"-"}function On(e,n){return e.length>n?e.slice(0,n-1)+"\u2026":e}function we(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function M(e){return e.replace(/[&<>"']/g,n=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[n])}function ct(e){return e?e.length>18?e.slice(0,10)+"..."+e.slice(-6):e:"-"}function ut(e){let n=location.hash.match(/^#\/(?:workflows\/catalog|workflows-catalog)\/([^/?#]+)$/);return n?Dn(e,decodeURIComponent(n[1])):xn(e)}function xn(e){e.innerHTML=`
542
+ <nav class="wf-subnav">
543
+ <a href="#/workflows" data-i18n="workflow.subnav.runs">${M(t("workflow.subnav.runs"))}</a>
544
+ <a href="#/workflows/catalog" class="active" data-i18n="workflow.subnav.catalog">${M(t("workflow.subnav.catalog"))}</a>
545
+ </nav>
546
+ <section class="catalog-head">
547
+ <div>
548
+ <h2>${M(t("catalog.title"))}</h2>
549
+ <p class="muted">${M(t("catalog.subtitle"))}</p>
550
+ </div>
551
+ <button id="catalog-refresh" type="button">${M(t("catalog.refresh"))}</button>
552
+ </section>
553
+ <form id="catalog-filters" class="filters">
554
+ <input type="search" name="q" placeholder="${M(t("catalog.searchPlaceholder"))}" />
555
+ <span id="catalog-status" class="muted"></span>
556
+ </form>
557
+ <div class="wf-table-scroll">
558
+ <table>
559
+ <thead><tr>
560
+ <th>${M(t("catalog.table.workflow"))}</th>
561
+ <th>${M(t("catalog.table.version"))}</th>
562
+ <th>${M(t("catalog.table.params"))}</th>
563
+ <th>${M(t("catalog.table.nodes"))}</th>
564
+ <th>${M(t("catalog.table.revision"))}</th>
565
+ <th>${M(t("catalog.table.path"))}</th>
566
+ </tr></thead>
567
+ <tbody id="catalog-tbody"></tbody>
568
+ </table>
569
+ </div>
570
+ `;let n=e.querySelector("#catalog-tbody"),o=e.querySelector("#catalog-status"),r=e.querySelector("#catalog-filters"),s=e.querySelector("#catalog-refresh"),a=[],c=null,p=!1;function y(){let T=(new FormData(r).get("q")??"").trim().toLowerCase();return T?a.filter(l=>l.workflowId.toLowerCase().includes(T)||l.path.toLowerCase().includes(T)):a}function S(){c?(o.textContent=t("catalog.loadFailed",{error:c}),o.classList.add("error")):(o.textContent=`${a.length}`,o.classList.remove("error"));let T=y();if(T.length===0){n.innerHTML=`<tr><td colspan="6" class="empty">${a.length===0?M(t("catalog.noDefinitions")):M(t("catalog.noFilterMatch"))}</td></tr>`;return}n.innerHTML=T.map(l=>`
571
+ <tr>
572
+ <td><a href="#/workflows/catalog/${encodeURIComponent(l.workflowId)}"><code>${M(l.workflowId)}</code></a></td>
573
+ <td>${l.version}</td>
574
+ <td>${M(t("catalog.paramSummary",{required:l.requiredParamCount,total:l.paramCount}))}</td>
575
+ <td>${l.nodeCount}</td>
576
+ <td><code>${M(ct(l.revisionId))}</code></td>
577
+ <td><code>${M(l.path)}</code></td>
578
+ </tr>
579
+ `).join("")}async function v(){s.disabled=!0,o.textContent=t("catalog.loading");try{let T=await fetch("/api/workflows/definitions");if(!T.ok)throw new Error(`HTTP ${T.status}`);a=(await T.json()).definitions??[],c=null}catch(T){c=T?.message??String(T),a=[]}finally{s.disabled=!1,p||S()}}return r.addEventListener("input",S),s.addEventListener("click",()=>{v()}),v(),()=>{p=!0}}function Dn(e,n){e.innerHTML=`
580
+ <div class="catalog-detail-head">
581
+ <a class="btn-link" href="#/workflows/catalog">${M(t("catalog.back"))}</a>
582
+ <div>
583
+ <h2><code>${M(n)}</code></h2>
584
+ <div id="catalog-detail-subtitle" class="muted">${M(t("workflow.detail.loading"))}</div>
585
+ </div>
586
+ </div>
587
+ <section id="catalog-error" class="hint-warn" hidden></section>
588
+ <section id="catalog-run-status" class="hint-ok" hidden></section>
589
+ <div id="catalog-detail-body"></div>
590
+ `;let o=e.querySelector("#catalog-detail-subtitle"),r=e.querySelector("#catalog-error"),s=e.querySelector("#catalog-run-status"),a=e.querySelector("#catalog-detail-body"),c=null,p=!1,y=!1;function S(u){r.hidden=!u,r.textContent=u??""}function v(u){s.hidden=!u,s.textContent=u??""}function T(u){let $={};for(let[k,b]of Object.entries(u??{}))"default"in b&&($[k]=b.default);return $}function l(){if(!c)return;let u=c.definition;o.textContent=`${t("catalog.revision")} ${ct(c.revisionId)} \xB7 ${c.path}`;let $=JSON.stringify(T(u.params),null,2);a.innerHTML=`
591
+ <section class="wf-panel">
592
+ <div class="wf-panel-title"><h3>${M(t("catalog.summary"))}</h3></div>
593
+ <div class="wf-summary-grid">
594
+ <div class="wf-summary-item"><span>${M(t("catalog.table.workflow"))}</span><strong><code>${M(u.workflowId)}</code></strong></div>
595
+ <div class="wf-summary-item"><span>${M(t("catalog.table.version"))}</span><strong>${u.version}</strong></div>
596
+ <div class="wf-summary-item"><span>${M(t("catalog.nodeCount"))}</span><strong>${Object.keys(u.nodes).length}</strong></div>
597
+ <div class="wf-summary-item"><span>${M(t("catalog.path"))}</span><strong><code>${M(c.path)}</code></strong></div>
598
+ </div>
599
+ </section>
600
+
601
+ <section class="wf-panel">
602
+ <div class="wf-panel-title"><h3>${M(t("catalog.runPanel"))}</h3></div>
603
+ <form id="catalog-run-form" class="catalog-run-form">
604
+ <label>
605
+ <span>${M(t("catalog.paramsJson"))}</span>
606
+ <textarea id="catalog-params" rows="8" spellcheck="false" placeholder="${M(t("catalog.paramsPlaceholder"))}">${M($)}</textarea>
607
+ </label>
608
+ <div class="catalog-chat-grid">
609
+ <label>
610
+ <span>${M(t("catalog.chatId"))}</span>
611
+ <input id="catalog-chat-id" type="text" autocomplete="off" />
612
+ </label>
613
+ <label>
614
+ <span>${M(t("catalog.larkAppId"))}</span>
615
+ <input id="catalog-lark-app-id" type="text" autocomplete="off" />
616
+ </label>
617
+ </div>
618
+ <div class="muted">${M(t("catalog.chatBindingHint"))}</div>
619
+ <div id="catalog-param-errors" class="catalog-param-errors" hidden></div>
620
+ <button id="catalog-run-btn" type="submit" class="primary">${M(t("catalog.run"))}</button>
621
+ </form>
622
+ </section>
623
+
624
+ <section class="wf-panel">
625
+ <div class="wf-panel-title"><h3>${M(t("catalog.paramsSchema"))}</h3></div>
626
+ ${qn(u.params)}
627
+ </section>
628
+
629
+ <section class="wf-panel">
630
+ <div class="wf-panel-title"><h3>${M(t("catalog.definitionJson"))}</h3></div>
631
+ <pre class="wf-io-pre">${M(JSON.stringify(u,null,2))}</pre>
632
+ </section>
633
+ `,L()}async function I(){if(!c||y)return;let u=a.querySelector("#catalog-params"),$=a.querySelector("#catalog-chat-id"),k=a.querySelector("#catalog-lark-app-id"),b=a.querySelector("#catalog-run-btn"),A=a.querySelector("#catalog-param-errors"),d;try{if(d=JSON.parse(u.value||"{}"),!d||typeof d!="object"||Array.isArray(d))throw new Error(t("catalog.badParamsJson"))}catch(f){A.hidden=!1,A.innerHTML=`<div class="muted error">${M(f?.message??String(f))}</div>`;return}y=!0,b.disabled=!0,b.textContent=t("catalog.running"),A.hidden=!0,S(null),v(null);try{let f=await fetch(`/api/workflows/definitions/${encodeURIComponent(c.definition.workflowId)}/run`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({params:d,chatBinding:{chatId:$.value.trim(),larkAppId:k.value.trim()}})});if(f.status===401)throw new Error(t("catalog.writeAccess"));let h=await f.json().catch(()=>({}));if(!f.ok||!h.ok)throw h.issues?.length&&(A.hidden=!1,A.innerHTML=`<strong>${M(t("catalog.invalidParams"))}</strong><ul>${h.issues.map(E=>`<li>${M(t("catalog.issue",{path:E.path.length?E.path.join("."):"(root)",message:E.message}))}</li>`).join("")}</ul>`),new Error(h.hint??h.message??h.error??t("catalog.runHttp",{status:f.status}));v(t("catalog.runStarted")),h.runId&&(location.hash=`#/workflows/${encodeURIComponent(h.runId)}`)}catch(f){S(f?.message??String(f))}finally{y=!1,b.disabled=!1,b.textContent=t("catalog.run")}}function L(){a.querySelector("#catalog-run-form")?.addEventListener("submit",$=>{$.preventDefault(),I()})}async function g(){try{let u=await fetch(`/api/workflows/definitions/${encodeURIComponent(n)}`);if(u.status===404)throw new Error("unknown_workflow");if(!u.ok)throw new Error(`HTTP ${u.status}`);c=await u.json(),S(null),l()}catch(u){S(t("catalog.definitionLoadFailed",{error:u?.message??String(u)})),o.textContent=t("workflow.detail.loadFailed")}}return g().then(()=>{}),()=>{p=!0}}function qn(e){let n=Object.entries(e??{});return n.length===0?`<div class="muted">${M(t("catalog.noParams"))}</div>`:`<div class="catalog-param-list">${n.map(([o,r])=>`
634
+ <article class="catalog-param">
635
+ <header>
636
+ <code>${M(o)}</code>
637
+ <span class="wf-status">${M(r.required?t("catalog.required"):t("catalog.optional"))}</span>
638
+ <span class="muted">${M(r.type)}${r.format?` \xB7 ${M(r.format)}`:""}</span>
639
+ </header>
640
+ ${r.description?`<div class="muted">${M(t("catalog.description"))}: ${M(r.description)}</div>`:""}
641
+ ${"default"in r?`<pre class="wf-io-pre">${M(`${t("catalog.default")}: ${JSON.stringify(r.default,null,2)}`)}</pre>`:""}
642
+ </article>
643
+ `).join("")}</div>`}var V=null,ge=null;function he(){ge!==null&&(window.clearInterval(ge),ge=null)}function ft(){return V||(V=document.createElement("dialog"),V.className="onboarding-dialog",document.body.appendChild(V),V.addEventListener("close",he),V)}function Pn(e){return e.status==="waiting_for_scan"?t("botOnboarding.waiting"):e.status==="verifying"?t("botOnboarding.verifying"):e.status==="completed"?t("botOnboarding.completed"):e.status==="failed"?`${t("botOnboarding.failed")}: ${e.message??e.error??"unknown"}`:t("botOnboarding.starting")}function ae(e){let n=ft(),o=e.qrDataUrl?`<div class="qr-card">
644
+ <img class="qr-image" src="${e.qrDataUrl}" alt="${t("botOnboarding.qrAlt")}">
645
+ ${e.qrUrl?`<a class="onboarding-link" href="${e.qrUrl}" target="_blank" rel="noopener">${t("botOnboarding.openLink")}</a>`:""}
646
+ </div>`:"",r=e.appId?`<p><b>App ID:</b> <code>${e.appId}</code></p>`:"",s=e.status==="completed"?`<p class="hint-ok">${t("botOnboarding.restartHint")}</p>`:"";n.innerHTML=`<article>
352
647
  <header>
353
- <h3>${e("botOnboarding.title")}</h3>
354
- <p>${e("botOnboarding.intro")}</p>
648
+ <h3>${t("botOnboarding.title")}</h3>
649
+ <p>${t("botOnboarding.intro")}</p>
355
650
  </header>
356
- <p class="onboarding-status status-${t.status}">${qe(t)}</p>
651
+ <p class="onboarding-status status-${e.status}">${Pn(e)}</p>
652
+ ${o}
357
653
  ${r}
358
- ${m}
359
- ${v}
360
- <form method="dialog"><button>${e("botOnboarding.close")}</button></form>
361
- </article>`}async function Ne(t){let s=await fetch(`/api/bot-onboarding/${encodeURIComponent(t)}`),r=await s.json();if(!s.ok||!r?.job)throw new Error(r?.error??`http_${s.status}`);_(r.job),(r.job.status==="completed"||r.job.status==="failed")&&W()}async function Re(){W(),_({id:"",status:"starting"});let t=me();t.open||t.showModal();try{let s=await fetch("/api/bot-onboarding/start",{method:"POST"}),r=await s.json();if(!s.ok||!r?.job?.id)throw new Error(r?.error??`http_${s.status}`);_(r.job),G=window.setInterval(()=>{Ne(r.job.id).catch(m=>{W(),_({id:r.job.id,status:"failed",message:m instanceof Error?m.message:String(m)})})},1200)}catch(s){_({id:"",status:"failed",message:s instanceof Error?s.message:String(s)})}}function be(){let t=document.getElementById("add-bot-btn");t&&(t.onclick=()=>{Re()})}var F=document.getElementById("root");function V(){let t=location.hash||"#/";t.startsWith("#/groups")?ue(F):t.startsWith("#/bot-defaults")?ge(F):t.startsWith("#/schedules")?ce(F):t.startsWith("#/sessions")?ie(F):ae(F);for(let s of document.querySelectorAll(".sidebar-nav a")){let r=s.getAttribute("href")??"#/";s.classList.toggle("active",r===(t||"#/"))}}var X=document.getElementById("status");function ye(){X&&(X.textContent=C.online?e("status.live"):e("status.disconnected"),X.className="connection-status "+(C.online?"online":"offline"))}C.on(ye);function fe(){document.querySelectorAll("[data-i18n]").forEach(t=>{t.textContent=e(t.dataset.i18n??"")}),document.querySelectorAll("[data-locale]").forEach(t=>{t.classList.toggle("active",t.dataset.locale===O.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(t=>{t.classList.toggle("active",t.dataset.themeMode===O.themeMode)}),ye()}function Pe(){document.querySelectorAll("[data-locale]").forEach(t=>{t.onclick=()=>O.setLocale(t.dataset.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(t=>{t.onclick=()=>O.setThemeMode(t.dataset.themeMode)})}(async()=>{O.init(),Pe(),be(),O.on(()=>{fe(),V()}),fe();try{await Z()}catch(t){console.error("botmux dashboard bootstrap failed",t),C.setOnline(!1)}window.addEventListener("hashchange",V),V()})();})();
654
+ ${s}
655
+ <form method="dialog"><button>${t("botOnboarding.close")}</button></form>
656
+ </article>`}async function Nn(e){let n=await fetch(`/api/bot-onboarding/${encodeURIComponent(e)}`),o=await n.json();if(!n.ok||!o?.job)throw new Error(o?.error??`http_${n.status}`);ae(o.job),(o.job.status==="completed"||o.job.status==="failed")&&he()}async function Bn(){he(),ae({id:"",status:"starting"});let e=ft();e.open||e.showModal();try{let n=await fetch("/api/bot-onboarding/start",{method:"POST"}),o=await n.json();if(!n.ok||!o?.job?.id)throw new Error(o?.error??`http_${n.status}`);ae(o.job),ge=window.setInterval(()=>{Nn(o.job.id).catch(r=>{he(),ae({id:o.job.id,status:"failed",message:r instanceof Error?r.message:String(r)})})},1200)}catch(n){ae({id:"",status:"failed",message:n instanceof Error?n.message:String(n)})}}function pt(){let e=document.getElementById("add-bot-btn");e&&(e.onclick=()=>{Bn()})}var Q=document.getElementById("root"),se=null;function Ee(){se&&(se(),se=null);let e=location.hash||"#/";e.startsWith("#/workflows/catalog")||e.startsWith("#/workflows-catalog")?se=ut(Q):e.startsWith("#/workflows")?se=et(Q):e.startsWith("#/groups")?ze(Q):e.startsWith("#/bot-defaults")?Qe(Q):e.startsWith("#/schedules")?Je(Q):e.startsWith("#/sessions")?We(Q):Ue(Q);for(let n of document.querySelectorAll(".sidebar-nav a")){let o=n.getAttribute("href")??"#/";n.classList.toggle("active",o===(e||"#/"))}}var Me=document.getElementById("status");function mt(){Me&&(Me.textContent=D.online?t("status.live"):t("status.disconnected"),Me.className="connection-status "+(D.online?"online":"offline"))}D.on(mt);function wt(){document.querySelectorAll("[data-i18n]").forEach(e=>{e.textContent=t(e.dataset.i18n??"")}),document.querySelectorAll("[data-locale]").forEach(e=>{e.classList.toggle("active",e.dataset.locale===J.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(e=>{e.classList.toggle("active",e.dataset.themeMode===J.themeMode)}),mt()}function jn(){document.querySelectorAll("[data-locale]").forEach(e=>{e.onclick=()=>J.setLocale(e.dataset.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(e=>{e.onclick=()=>J.setThemeMode(e.dataset.themeMode)})}(async()=>{J.init(),jn(),pt(),J.on(()=>{wt(),Ee()}),wt();try{await De()}catch(e){console.error("botmux dashboard bootstrap failed",e),D.setOnline(!1)}window.addEventListener("hashchange",Ee),Ee()})();})();