aicodeman 1.48.0 → 1.49.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 (269) hide show
  1. package/README.md +12 -3
  2. package/dist/config/cli-registry/schema.d.ts +1 -0
  3. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  4. package/dist/config/cli-registry/schema.js +5 -1
  5. package/dist/config/cli-registry/schema.js.map +1 -1
  6. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  7. package/dist/config/cli-registry/stock.js +71 -5
  8. package/dist/config/cli-registry/stock.js.map +1 -1
  9. package/dist/config/cli-registry/types.d.ts +4 -4
  10. package/dist/config/cli-registry/types.d.ts.map +1 -1
  11. package/dist/config/foreign-tmux.d.ts +48 -0
  12. package/dist/config/foreign-tmux.d.ts.map +1 -0
  13. package/dist/config/foreign-tmux.js +48 -0
  14. package/dist/config/foreign-tmux.js.map +1 -0
  15. package/dist/custom-model-injection-apply.d.ts +4 -2
  16. package/dist/custom-model-injection-apply.d.ts.map +1 -1
  17. package/dist/custom-model-injection-apply.js +35 -21
  18. package/dist/custom-model-injection-apply.js.map +1 -1
  19. package/dist/docker-hosts.d.ts +8 -1
  20. package/dist/docker-hosts.d.ts.map +1 -1
  21. package/dist/docker-hosts.js +5 -5
  22. package/dist/docker-hosts.js.map +1 -1
  23. package/dist/foreign-tmux-discovery.d.ts +140 -0
  24. package/dist/foreign-tmux-discovery.d.ts.map +1 -0
  25. package/dist/foreign-tmux-discovery.js +366 -0
  26. package/dist/foreign-tmux-discovery.js.map +1 -0
  27. package/dist/foreign-tmux.d.ts +381 -0
  28. package/dist/foreign-tmux.d.ts.map +1 -0
  29. package/dist/foreign-tmux.js +775 -0
  30. package/dist/foreign-tmux.js.map +1 -0
  31. package/dist/git-fetch-scheduler.d.ts +2 -0
  32. package/dist/git-fetch-scheduler.d.ts.map +1 -1
  33. package/dist/git-fetch-scheduler.js +1 -1
  34. package/dist/git-fetch-scheduler.js.map +1 -1
  35. package/dist/git-fetch.d.ts +1 -1
  36. package/dist/git-fetch.d.ts.map +1 -1
  37. package/dist/git-workspace-status.d.ts +2 -2
  38. package/dist/git-workspace-status.d.ts.map +1 -1
  39. package/dist/hooks-config.d.ts +2 -1
  40. package/dist/hooks-config.d.ts.map +1 -1
  41. package/dist/hooks-config.js +2 -1
  42. package/dist/hooks-config.js.map +1 -1
  43. package/dist/live-worktree.d.ts +53 -0
  44. package/dist/live-worktree.d.ts.map +1 -0
  45. package/dist/live-worktree.js +97 -0
  46. package/dist/live-worktree.js.map +1 -0
  47. package/dist/mux-interface.d.ts +12 -1
  48. package/dist/mux-interface.d.ts.map +1 -1
  49. package/dist/reboot-restore.d.ts +1 -1
  50. package/dist/reboot-restore.d.ts.map +1 -1
  51. package/dist/reboot-restore.js +9 -0
  52. package/dist/reboot-restore.js.map +1 -1
  53. package/dist/remote-files.d.ts +150 -5
  54. package/dist/remote-files.d.ts.map +1 -1
  55. package/dist/remote-files.js +505 -7
  56. package/dist/remote-files.js.map +1 -1
  57. package/dist/remote-hosts.d.ts +15 -9
  58. package/dist/remote-hosts.d.ts.map +1 -1
  59. package/dist/remote-hosts.js +42 -20
  60. package/dist/remote-hosts.js.map +1 -1
  61. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  62. package/dist/session-cli-registry-bridge.js +19 -8
  63. package/dist/session-cli-registry-bridge.js.map +1 -1
  64. package/dist/session-env-clamp.d.ts +1 -1
  65. package/dist/session-env-clamp.d.ts.map +1 -1
  66. package/dist/session-env-clamp.js +4 -3
  67. package/dist/session-env-clamp.js.map +1 -1
  68. package/dist/session.d.ts +91 -21
  69. package/dist/session.d.ts.map +1 -1
  70. package/dist/session.js +190 -33
  71. package/dist/session.js.map +1 -1
  72. package/dist/tmux-manager.d.ts +74 -12
  73. package/dist/tmux-manager.d.ts.map +1 -1
  74. package/dist/tmux-manager.js +256 -41
  75. package/dist/tmux-manager.js.map +1 -1
  76. package/dist/types/foreign-tmux.d.ts +101 -0
  77. package/dist/types/foreign-tmux.d.ts.map +1 -0
  78. package/dist/types/foreign-tmux.js +27 -0
  79. package/dist/types/foreign-tmux.js.map +1 -0
  80. package/dist/types/index.d.ts +1 -0
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/types/index.js +1 -0
  83. package/dist/types/index.js.map +1 -1
  84. package/dist/types/session.d.ts +106 -2
  85. package/dist/types/session.d.ts.map +1 -1
  86. package/dist/types/session.js.map +1 -1
  87. package/dist/types/update.d.ts +4 -1
  88. package/dist/types/update.d.ts.map +1 -1
  89. package/dist/web/edit-history-sync.d.ts +27 -7
  90. package/dist/web/edit-history-sync.d.ts.map +1 -1
  91. package/dist/web/edit-history-sync.js +35 -19
  92. package/dist/web/edit-history-sync.js.map +1 -1
  93. package/dist/web/launch-defaults.d.ts +1 -1
  94. package/dist/web/launch-defaults.js +2 -2
  95. package/dist/web/launch-defaults.js.map +1 -1
  96. package/dist/web/linked-cases-writer.d.ts +43 -0
  97. package/dist/web/linked-cases-writer.d.ts.map +1 -0
  98. package/dist/web/linked-cases-writer.js +107 -0
  99. package/dist/web/linked-cases-writer.js.map +1 -0
  100. package/dist/web/paste-image-gc.d.ts +4 -1
  101. package/dist/web/paste-image-gc.d.ts.map +1 -1
  102. package/dist/web/paste-image-gc.js +4 -2
  103. package/dist/web/paste-image-gc.js.map +1 -1
  104. package/dist/web/public/admin-ui.js.gz +0 -0
  105. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  106. package/dist/web/public/{app.963b5ba8.js → app.9ea37070.js} +6 -6
  107. package/dist/web/public/app.9ea37070.js.br +0 -0
  108. package/dist/web/public/app.9ea37070.js.gz +0 -0
  109. package/dist/web/public/approvals-ui.js.gz +0 -0
  110. package/dist/web/public/constants.782170e1.js.gz +0 -0
  111. package/dist/web/public/cron-ui.js.gz +0 -0
  112. package/dist/web/public/edit-history-ui.js +27 -14
  113. package/dist/web/public/edit-history-ui.js.br +0 -0
  114. package/dist/web/public/edit-history-ui.js.gz +0 -0
  115. package/dist/web/public/entrance-animations.js.gz +0 -0
  116. package/dist/web/public/foreign-sessions.js +476 -0
  117. package/dist/web/public/foreign-sessions.js.br +0 -0
  118. package/dist/web/public/foreign-sessions.js.gz +0 -0
  119. package/dist/web/public/git-status-ui.js +5 -3
  120. package/dist/web/public/git-status-ui.js.br +0 -0
  121. package/dist/web/public/git-status-ui.js.gz +0 -0
  122. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  123. package/dist/web/public/home-sessions.js.gz +0 -0
  124. package/dist/web/public/host-wake-ui.js.gz +0 -0
  125. package/dist/web/public/i18n.a5e82c95.js +2 -0
  126. package/dist/web/public/i18n.a5e82c95.js.br +0 -0
  127. package/dist/web/public/i18n.a5e82c95.js.gz +0 -0
  128. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  129. package/dist/web/public/index.html +36 -17
  130. package/dist/web/public/index.html.br +0 -0
  131. package/dist/web/public/index.html.gz +0 -0
  132. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  133. package/dist/web/public/{keyboard-accessory.d4ec5957.js → keyboard-accessory.b941676c.js} +3 -1
  134. package/dist/web/public/keyboard-accessory.b941676c.js.br +0 -0
  135. package/dist/web/public/{keyboard-accessory.d4ec5957.js.gz → keyboard-accessory.b941676c.js.gz} +0 -0
  136. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  137. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  138. package/dist/web/public/mobile-overview.js +10 -0
  139. package/dist/web/public/mobile-overview.js.br +0 -0
  140. package/dist/web/public/mobile-overview.js.gz +0 -0
  141. package/dist/web/public/{mobile.5ef2ce85.css → mobile.67c8eb5b.css} +1 -1
  142. package/dist/web/public/mobile.67c8eb5b.css.br +0 -0
  143. package/dist/web/public/{mobile.5ef2ce85.css.gz → mobile.67c8eb5b.css.gz} +0 -0
  144. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  145. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  146. package/dist/web/public/{panels-ui.b100727d.js → panels-ui.8bcd4473.js} +4 -4
  147. package/dist/web/public/panels-ui.8bcd4473.js.br +0 -0
  148. package/dist/web/public/panels-ui.8bcd4473.js.gz +0 -0
  149. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  150. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  151. package/dist/web/public/readmymind-ui.js.gz +0 -0
  152. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  153. package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
  154. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  155. package/dist/web/public/session-lineage.js.gz +0 -0
  156. package/dist/web/public/{session-ui.5ac82f2c.js → session-ui.a7c8c095.js} +11 -11
  157. package/dist/web/public/session-ui.a7c8c095.js.br +0 -0
  158. package/dist/web/public/session-ui.a7c8c095.js.gz +0 -0
  159. package/dist/web/public/{settings-ui.d1d4fb99.js → settings-ui.18f92052.js} +3 -3
  160. package/dist/web/public/settings-ui.18f92052.js.br +0 -0
  161. package/dist/web/public/settings-ui.18f92052.js.gz +0 -0
  162. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  163. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  164. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  165. package/dist/web/public/{styles.77dc538f.css → styles.6a718331.css} +1 -1
  166. package/dist/web/public/styles.6a718331.css.br +0 -0
  167. package/dist/web/public/{styles.77dc538f.css.gz → styles.6a718331.css.gz} +0 -0
  168. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  169. package/dist/web/public/sw.js +2 -2
  170. package/dist/web/public/sw.js.br +0 -0
  171. package/dist/web/public/sw.js.gz +0 -0
  172. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  173. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  174. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  175. package/dist/web/public/terminal-split.js.gz +0 -0
  176. package/dist/web/public/terminal-tile.js +2 -2
  177. package/dist/web/public/terminal-tile.js.br +0 -0
  178. package/dist/web/public/terminal-tile.js.gz +0 -0
  179. package/dist/web/public/{terminal-ui.d7c75ce8.js → terminal-ui.a7883640.js} +2 -2
  180. package/dist/web/public/terminal-ui.a7883640.js.br +0 -0
  181. package/dist/web/public/terminal-ui.a7883640.js.gz +0 -0
  182. package/dist/web/public/tile-grid.js.gz +0 -0
  183. package/dist/web/public/ultracode-panel.js.gz +0 -0
  184. package/dist/web/public/ultracode-windows.js.gz +0 -0
  185. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  186. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  187. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  188. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  189. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  190. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  191. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  192. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  193. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  194. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  195. package/dist/web/public/vendor/xterm.css.gz +0 -0
  196. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  197. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  198. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  199. package/dist/web/public/webview-tabs.js.gz +0 -0
  200. package/dist/web/public/whats-new.json +9 -9
  201. package/dist/web/routes/case-routes.d.ts.map +1 -1
  202. package/dist/web/routes/case-routes.js +72 -53
  203. package/dist/web/routes/case-routes.js.map +1 -1
  204. package/dist/web/routes/discord-routes.js +1 -1
  205. package/dist/web/routes/discord-routes.js.map +1 -1
  206. package/dist/web/routes/edit-history-routes.d.ts +2 -2
  207. package/dist/web/routes/edit-history-routes.d.ts.map +1 -1
  208. package/dist/web/routes/edit-history-routes.js +4 -3
  209. package/dist/web/routes/edit-history-routes.js.map +1 -1
  210. package/dist/web/routes/file-routes.d.ts.map +1 -1
  211. package/dist/web/routes/file-routes.js +213 -102
  212. package/dist/web/routes/file-routes.js.map +1 -1
  213. package/dist/web/routes/git-status-routes.d.ts.map +1 -1
  214. package/dist/web/routes/git-status-routes.js +7 -2
  215. package/dist/web/routes/git-status-routes.js.map +1 -1
  216. package/dist/web/routes/mux-routes.d.ts +10 -3
  217. package/dist/web/routes/mux-routes.d.ts.map +1 -1
  218. package/dist/web/routes/mux-routes.js +59 -1
  219. package/dist/web/routes/mux-routes.js.map +1 -1
  220. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  221. package/dist/web/routes/ralph-routes.js +22 -0
  222. package/dist/web/routes/ralph-routes.js.map +1 -1
  223. package/dist/web/routes/readmymind-routes.d.ts.map +1 -1
  224. package/dist/web/routes/readmymind-routes.js +3 -2
  225. package/dist/web/routes/readmymind-routes.js.map +1 -1
  226. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  227. package/dist/web/routes/respawn-routes.js +24 -0
  228. package/dist/web/routes/respawn-routes.js.map +1 -1
  229. package/dist/web/routes/session-routes.d.ts +11 -8
  230. package/dist/web/routes/session-routes.d.ts.map +1 -1
  231. package/dist/web/routes/session-routes.js +262 -29
  232. package/dist/web/routes/session-routes.js.map +1 -1
  233. package/dist/web/routes/system-routes.d.ts.map +1 -1
  234. package/dist/web/routes/system-routes.js +3 -2
  235. package/dist/web/routes/system-routes.js.map +1 -1
  236. package/dist/web/schemas.d.ts +24 -1
  237. package/dist/web/schemas.d.ts.map +1 -1
  238. package/dist/web/schemas.js +34 -9
  239. package/dist/web/schemas.js.map +1 -1
  240. package/dist/web/self-update.d.ts +9 -0
  241. package/dist/web/self-update.d.ts.map +1 -1
  242. package/dist/web/self-update.js +19 -3
  243. package/dist/web/self-update.js.map +1 -1
  244. package/dist/web/server.d.ts +16 -3
  245. package/dist/web/server.d.ts.map +1 -1
  246. package/dist/web/server.js +84 -24
  247. package/dist/web/server.js.map +1 -1
  248. package/dist/web/session-wait-registry.d.ts +13 -0
  249. package/dist/web/session-wait-registry.d.ts.map +1 -1
  250. package/dist/web/session-wait-registry.js +16 -8
  251. package/dist/web/session-wait-registry.js.map +1 -1
  252. package/package.json +2 -2
  253. package/skills/codeman/SKILL.md +20 -4
  254. package/dist/web/public/app.963b5ba8.js.br +0 -0
  255. package/dist/web/public/app.963b5ba8.js.gz +0 -0
  256. package/dist/web/public/i18n.33f930d7.js +0 -2
  257. package/dist/web/public/i18n.33f930d7.js.br +0 -0
  258. package/dist/web/public/i18n.33f930d7.js.gz +0 -0
  259. package/dist/web/public/keyboard-accessory.d4ec5957.js.br +0 -0
  260. package/dist/web/public/mobile.5ef2ce85.css.br +0 -0
  261. package/dist/web/public/panels-ui.b100727d.js.br +0 -0
  262. package/dist/web/public/panels-ui.b100727d.js.gz +0 -0
  263. package/dist/web/public/session-ui.5ac82f2c.js.br +0 -0
  264. package/dist/web/public/session-ui.5ac82f2c.js.gz +0 -0
  265. package/dist/web/public/settings-ui.d1d4fb99.js.br +0 -0
  266. package/dist/web/public/settings-ui.d1d4fb99.js.gz +0 -0
  267. package/dist/web/public/styles.77dc538f.css.br +0 -0
  268. package/dist/web/public/terminal-ui.d7c75ce8.js.br +0 -0
  269. package/dist/web/public/terminal-ui.d7c75ce8.js.gz +0 -0
@@ -1,4 +1,4 @@
1
- "use strict";Object.assign(CodemanApp.prototype,{_onHookIdlePrompt(e){e.acknowledgedReason||(e.sessionId&&this.setPendingHook(e.sessionId,"idle_prompt"),this._notifySession(e.sessionId,"warning","hook-idle","Waiting for Input",e.message||"Claude is idle and waiting for a prompt"))},_onHookPermissionPrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"permission_prompt");const t=e.tool?`${e.tool}${e.command?": "+e.command:e.file?": "+e.file:""}`:"",n=e.source==="screen"&&e.message?e.message:"";this._notifySession(e.sessionId,"critical","hook-permission","Permission Required",t||n||"Claude needs tool approval to continue")},_onHookElicitationDialog(e){e.sessionId&&this.setPendingHook(e.sessionId,"elicitation_dialog"),this._notifySession(e.sessionId,"critical","hook-elicitation","Question Asked",e.question||"Claude is asking a question and waiting for your answer")},_onHookElicitationComplete(e){e.sessionId&&(this.clearPendingHooks(e.sessionId,"elicitation_dialog"),this.clearPendingHooks(e.sessionId,"permission_prompt"))},_onHookElicitationResponse(e){this._onHookElicitationComplete(e)},_onHookStop(e){e.acknowledgedReason||(e.sessionId&&this.clearPendingHooks(e.sessionId),this._notifySession(e.sessionId,"info","hook-stop","Response Complete",e.reason||"Claude has finished responding"))},_onHookAgentWorking(e){e.sessionId&&(this.clearPendingHooks(e.sessionId,"elicitation_dialog"),this.clearPendingHooks(e.sessionId,"permission_prompt"),this.clearPendingHooks(e.sessionId,"idle_prompt"))},_onHookTeammateIdle(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"warning","hook-teammate-idle","Teammate Idle",`A teammate is idle in ${t?.name||e.sessionId}`)},_onHookTaskCompleted(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"info","hook-task-completed","Task Completed",`A team task completed in ${t?.name||e.sessionId}`)},_onTunnelStarted(e){console.log("[Tunnel] Started:",e.url),this._tunnelUrl=e.url,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(e.url),this._updateTunnelIndicator(!0),document.getElementById("welcomeOverlay")?.classList.contains("visible")?(this._updateWelcomeTunnelBtn(!0,e.url,!0),this.showToast("Tunnel active","success")):(this._updateWelcomeTunnelBtn(!0,e.url),this.showToast(`Tunnel active: ${e.url}`,"success"),this.showTunnelQR())},_onTunnelStopped(){console.log("[Tunnel] Stopped"),this._tunnelUrl=null,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1),this.closeTunnelPanel(),this.closeTunnelQR()},_onTunnelProgress(e){console.log("[Tunnel] Progress:",e.message);const t=document.getElementById("tunnelConnectingToast");t&&(t.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`);const n=document.getElementById("welcomeTunnelBtn");n?.classList.contains("connecting")&&(n.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`)},_onTunnelError(e){console.warn("[Tunnel] Error:",e.message),this._dismissTunnelConnecting(),this.showToast(`Tunnel error: ${e.message}`,"error");const t=document.getElementById("welcomeTunnelBtn");t&&(t.disabled=!1,t.classList.remove("connecting"))},_onTunnelQrRotated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrRegenerated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrAuthUsed(e){const n=(e.ua||"Unknown device").match(/Chrome|Firefox|Safari|Edge|Mobile/)?.[0]||"Browser";this.showToast(`Device authenticated via QR (${n}, ${e.ip}). Not you?`,"warning",{duration:1e4,action:{label:"Revoke All",onClick:()=>{fetch("/api/auth/revoke",{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}).then(()=>this.showToast("All sessions revoked","success")).catch(()=>this.showToast("Failed to revoke sessions","error"))}}})},registerServiceWorker(){if(!("serviceWorker"in navigator))return;const e=window.CodemanBase?.base||"";navigator.serviceWorker.register(e+"/sw.js",{scope:e+"/"}).then(t=>{this._swRegistration=t,navigator.serviceWorker.addEventListener("message",n=>{if(n.data?.type==="notification-click"){const{sessionId:s,action:i,approvalId:o}=n.data;i?this.handleNotificationAction?.(i,o,s):s&&this.sessions.has(s)&&this.selectSession(s),window.focus()}}),t.pushManager.getSubscription().then(n=>{n&&(this._pushSubscription=n,this._updatePushUI(!0))})}).catch(()=>{})},async subscribeToPush(){if(!this._swRegistration){this.showToast("Service worker not available. HTTPS or localhost required.","error");return}try{const e=await this._apiJson("/api/push/vapid-key");if(!e)throw new Error("Failed to get VAPID key");const t=urlBase64ToUint8Array(e.publicKey),n=await this._swRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t}),s=n.toJSON(),i=await this._apiJson("/api/push/subscribe",{method:"POST",body:{endpoint:s.endpoint,keys:s.keys,userAgent:navigator.userAgent,pushPreferences:this._buildPushPreferences()}});if(!i)throw new Error("Failed to register subscription");this._pushSubscription=n,this._pushSubscriptionId=i.id,localStorage.setItem("codeman-push-subscription-id",i.id),this._updatePushUI(!0),this.showToast("Push notifications enabled","success")}catch(e){this.showToast("Push subscription failed: "+(e.message||e),"error")}},async unsubscribeFromPush(){try{this._pushSubscription&&await this._pushSubscription.unsubscribe();const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");e&&await fetch(`/api/push/subscribe/${e}`,{method:"DELETE"}).catch(()=>{}),this._pushSubscription=null,this._pushSubscriptionId=null,localStorage.removeItem("codeman-push-subscription-id"),this._updatePushUI(!1),this.showToast("Push notifications disabled","success")}catch(e){this.showToast("Failed to unsubscribe: "+(e.message||e),"error")}},async togglePushSubscription(){this._pushSubscription?await this.unsubscribeFromPush():await this.subscribeToPush()},async _syncPushPreferences(){const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");if(e)try{await fetch(`/api/push/subscribe/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({pushPreferences:this._buildPushPreferences()})})}catch{}},_buildPushPreferences(){const e={},t={"hook:permission_prompt":"eventPermissionPush","hook:elicitation_dialog":"eventQuestionPush","hook:idle_prompt":"eventIdlePush","hook:stop":"eventStopPush","respawn:blocked":"eventRespawnPush","session:ralphCompletionDetected":"eventRalphPush"};for(const[n,s]of Object.entries(t)){const i=document.getElementById(s);e[n]=i?i.checked:!0}return e["session:error"]=!0,e},_updatePushUI(e){const t=document.getElementById("pushSubscribeBtn"),n=document.getElementById("pushSubscriptionStatus");t&&(t.textContent=e?"Unsubscribe":"Subscribe"),n&&(n.textContent=e?"active":"off",n.classList.remove("granted","denied"),e&&n.classList.add("granted"))},populateTerminalFontWeight(e,t){if(!e)return;const n=t==null?"":String(t).trim();if(n&&!Array.from(e.options).some(s=>s.value===n)){const s=document.createElement("option");s.value=n,s.textContent=`${n} (custom)`,e.appendChild(s)}e.value=n},readTerminalFontWeight(e){return e?.value.trim()||""},async checkWhatsNew(e){if(this._whatsNewChecked)return;this._whatsNewChecked=!0;const t="codeman-whats-new-seen";let n=null;try{n=localStorage.getItem(t)}catch{return}const s=()=>{try{localStorage.setItem(t,e)}catch{}},[i,o]=await Promise.all([n&&n!==e?this._fetchWhatsNewNotes():null,this._loadSecurityNotices()]);if(this.isSoloWindow&&o.length)return;const a=window.CodemanSecurityNotices.plan({seenVersion:n,runningVersion:e,notes:i,notices:o});if(a.recordVersion&&s(),a.update){const l=i.highlights.slice(0,3).join(" \xB7 "),c=i.highlights.length>3?` (+${i.highlights.length-3} more)`:"",p=`Codeman updated to v${e}: ${l}${c}`,d=`https://github.com/Ark0N/Codeman/releases/tag/codeman%40${e}`;a.updateNotices.length?this.showUpdateNotice({text:p,releaseUrl:d,notices:a.updateNotices,onNoticeSeen:r=>this._rememberSecurityNotice(r)}):this.showToast(p,"info",{duration:2e4,action:{label:"See what's new",onClick:()=>window.open(d,"_blank","noopener")}})}for(const l of a.cards)this.showSecurityNotice(l,{onClose:()=>this._rememberSecurityNotice(l.id)})},async _fetchWhatsNewNotes(){try{const e=await fetch("/whats-new.json",{cache:"no-cache"});return e.ok?await e.json():null}catch{return null}},async _loadSecurityNotices(){let e;try{e=this._readSecurityNoticesSeen()}catch{return[]}try{const t=await fetch(CodemanBase.url("/security-notices.json"),{cache:"no-cache"});if(!t.ok)return[];const n=await t.json(),s=Array.isArray(n?.notices)?n.notices:[],i=this.isSoloWindow?Object.fromEntries(s.filter(o=>typeof o?.cli=="string").map(o=>[o.cli,!0])):window.__codemanCliAvailable;return window.CodemanSecurityNotices.select(n?.notices,{available:i,seen:e,now:Date.now(),lang:window.CodemanI18n?.language||window.__codemanLanguage})}catch{return[]}},_readSecurityNoticesSeen(){const e=localStorage.getItem("codeman-security-notices-seen");let t=[];try{t=e?JSON.parse(e):[]}catch{}return Array.isArray(t)?t.filter(n=>typeof n=="string"):[]},_rememberSecurityNotice(e){try{const t=this._readSecurityNoticesSeen();t.includes(e)||t.push(e),localStorage.setItem("codeman-security-notices-seen",JSON.stringify(t))}catch{}},openAppSettings(){const e=this.loadAppSettingsFromStorage();document.getElementById("appSettingsDisplayName").value=e.displayName||"Codeman",document.getElementById("appSettingsLanguage").value=e.language==="zh-CN"?"zh-CN":"en",document.getElementById("appSettingsClaudeMdPath").value=e.defaultClaudeMdPath||"",document.getElementById("appSettingsDefaultDir").value=e.defaultWorkingDir||"";const t=this.getDefaultSettings();document.getElementById("appSettingsRalphEnabled").checked=e.ralphTrackerEnabled??t.ralphTrackerEnabled??!1,document.getElementById("appSettingsShowFontControls").checked=e.showFontControls??t.showFontControls??!1,document.getElementById("appSettingsShowSystemStats").checked=e.showSystemStats??t.showSystemStats??!0,document.getElementById("appSettingsShowGpuStats").checked=e.showGpuStats??t.showGpuStats??!1,document.getElementById("appSettingsShowCpuMhz").checked=e.showCpuMhz??t.showCpuMhz??!1,document.getElementById("appSettingsHeaderStatsStyle").value=this.resolveHeaderStatsStyle(e),document.getElementById("appSettingsShowLifecycleLog").checked=e.showLifecycleLog??t.showLifecycleLog??!1,document.getElementById("appSettingsShowResponseViewer").checked=e.showResponseViewer??t.showResponseViewer??!1,document.getElementById("appSettingsShowFileViewerButton").checked=e.showFileViewerButton??t.showFileViewerButton??!0,document.getElementById("appSettingsShowAttachmentsButton").checked=e.showAttachmentsButton??t.showAttachmentsButton??!1,document.getElementById("appSettingsSkin").value=e.skin??t.skin??"daylight-blue",this._syncEntranceAnimSetting?.(),document.getElementById("appSettingsWebglRenderer").checked=e.webglRendererEnabled??t.webglRendererEnabled??!0;const n=document.getElementById("appSettingsWebglRendererItem");n&&(n.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsShowMonitor").checked=e.showMonitor??t.showMonitor??!1,document.getElementById("appSettingsShowProjectInsights").checked=e.showProjectInsights??t.showProjectInsights??!1,document.getElementById("appSettingsShowFileBrowser").checked=e.showFileBrowser??t.showFileBrowser??!1,document.getElementById("appSettingsShowSubagents").checked=e.showSubagents??t.showSubagents??!1,document.getElementById("appSettingsShowUltracodeAgents").checked=e.showUltracodeAgents??t.showUltracodeAgents??!1,document.getElementById("appSettingsApprovalsInbox").checked=e.approvalsInboxEnabled===!0,document.getElementById("appSettingsAgentInboxPersist").checked=e.agentInboxPersistEnabled===!0,document.getElementById("appSettingsAgentCoordination").checked=e.agentCoordinationEnabled===!0,document.getElementById("appSettingsCustomModelEndpoints").checked=e.customModelEndpointsEnabled===!0,this.applyCustomModelEndpointsVisibility(),document.getElementById("appSettingsCliManagement").checked=e.cliManagementEnabled===!0,this.applyCliManagementVisibility(),this._mcpSyncSavedOn=e.mcpSyncEnabled===!0,document.getElementById("appSettingsMcpSync").checked=this._mcpSyncSavedOn,this.applyMcpSyncVisibility(),document.getElementById("appSettingsConfigBackup").checked=e.configBackupEnabled!==!1,document.getElementById("appSettingsConfigBackupDir").value=e.configBackupDir||"",document.getElementById("appSettingsConfigBackupKeepCount").value=e.configBackupKeepCount??20,document.getElementById("appSettingsConfigBackupKeepDays").value=e.configBackupKeepDays??30,this.loadConfigBackups(),this._applyDoctorAdminGate(),this.loadWebhook(),this.loadDiscord(),document.getElementById("appSettingsReadMyMind").checked=e.readMyMindEnabled===!0,document.getElementById("appSettingsUltracodeFloatingWindows").checked=e.ultracodeFloatingWindows??t.ultracodeFloatingWindows??!1,document.getElementById("appSettingsShowMultiMonitorButton").checked=e.showMultiMonitorButton??t.showMultiMonitorButton??!1,document.getElementById("appSettingsShowSplitButton").checked=e.showSplitButton??t.showSplitButton??!1,document.getElementById("appSettingsShowTileGridButton").checked=e.showTileGridButton??t.showTileGridButton??!0,document.getElementById("appSettingsShowPlanUsageLimits").checked=this.planUsageChipEnabled(e),document.getElementById("appSettingsShowRedrawButton").checked=e.showRedrawButton??t.showRedrawButton??!1,document.getElementById("appSettingsLineageLines").checked=e.sessionLineageLines??t.sessionLineageLines??!0,document.getElementById("appSettingsAutoNameSessions").checked=e.autoNameSessions===!0,document.getElementById("appSettingsSpawnedTabsFollowParent").checked=e.spawnedTabsFollowParent===!0;const s=document.getElementById("appSettingsLineageLinesItem");s&&(s.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsMobileOverview").checked=e.mobileOverviewEnabled??t.mobileOverviewEnabled??!1;const i=document.getElementById("appSettingsMobileOverviewItem");i&&(i.style.display=MobileDetection.getDeviceType()==="mobile"?"":"none"),document.getElementById("appSettingsShowSessionButton").checked=e.showSessionButton??t.showSessionButton??!1,document.getElementById("appSettingsShowAwayDigestButton").checked=e.showAwayDigestButton??t.showAwayDigestButton??!1,document.getElementById("appSettingsShowCronButton").checked=e.showCronButton??t.showCronButton??!1,document.getElementById("appSettingsShowGitStatus").checked=e.showGitStatus??t.showGitStatus??!1,document.getElementById("appSettingsGitStatusTree").checked=e.gitStatusTree??t.gitStatusTree??!0,document.getElementById("appSettingsGitStatusMaxRepos").value=e.gitStatusMaxRepos??t.gitStatusMaxRepos??12,document.getElementById("appSettingsGitStatusTimeout").value=e.gitStatusTimeoutSeconds??t.gitStatusTimeoutSeconds??30,document.getElementById("appSettingsGitFetchEnabled").checked=e.gitFetchEnabled===!0,document.getElementById("appSettingsGitFetchIntervalSeconds").value=e.gitFetchIntervalSeconds??300,document.getElementById("appSettingsShowEditHistory").checked=e.showEditHistory??t.showEditHistory??!1;const o=document.getElementById("appSettingsGestureControlItem");o&&(o.style.display=window.__codemanGestureAvailable?"":"none"),document.getElementById("appSettingsGestureControl").checked=e.gestureControlEnabled??t.gestureControlEnabled??!1,document.getElementById("appSettingsSubagentTracking").checked=e.subagentTrackingEnabled??t.subagentTrackingEnabled??!0,document.getElementById("appSettingsSubagentActiveTabOnly").checked=e.subagentActiveTabOnly??t.subagentActiveTabOnly??!0,document.getElementById("appSettingsImageWatcherEnabled").checked=e.imageWatcherEnabled??t.imageWatcherEnabled??!1,document.getElementById("appSettingsPromptCacheKeepWarm").checked=e.promptCacheKeepWarm===!0,document.getElementById("appSettingsPromptCacheKeepWarmMaxHours").value=e.promptCacheKeepWarmMaxHours??2,document.getElementById("appSettingsTunnelEnabled").checked=e.tunnelEnabled??!1,this.loadTunnelStatus(),document.getElementById("appSettingsLocalEcho").checked=e.localEchoEnabled??MobileDetection.isTouchDevice(),document.getElementById("appSettingsKeyboardHoldsRows").checked=e.keyboardHoldsRows!==!1,document.getElementById("appSettingsAutoCopySelection").checked=e.autoCopySelection===!0,document.getElementById("appSettingsCopyStripMargin").checked=e.copyStripMargin!==!1,document.getElementById("appSettingsTerminalFont").value=e.terminalFontFamily||"",this.populateTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeight"),e.terminalFontWeight),this.populateTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeightBold"),e.terminalFontWeightBold),document.getElementById("appSettingsTerminalWheelLocal").checked=e.terminalWheelLocalScrollback??t.terminalWheelLocalScrollback??!1,document.getElementById("appSettingsCjkInput").checked=e.cjkInputEnabled??t.cjkInputEnabled??!1,document.getElementById("appSettingsExtendedKeyboardBar").checked=e.extendedKeyboardBar??!1,document.getElementById("appSettingsTabTwoRows").checked=e.tabTwoRows??t.tabTwoRows??!1,document.getElementById("appSettingsShowTabCliLogos").checked=this.tabCliLogosEnabled(e),document.getElementById("appSettingsTabOrientation").value=e.tabOrientation??t.tabOrientation??"horizontal";const a=window.CodemanTabRail?.resolveWidth({width:e.tabRailWidth??this._defaultTabRailWidth?.()??t.tabRailWidth??256})??256;this.syncTabRailWidthSetting?.(a),document.getElementById("appSettingsTabRailDetail").value=e.tabRailDetail??t.tabRailDetail??"rich",document.getElementById("appSettingsTabRailSort").value=e.tabRailSort??t.tabRailSort??"activity",document.getElementById("appSettingsTabArrangement").value=this.resolveTabArrangement(e),document.getElementById("appSettingsTabStateOrder").value=this.resolveTabStateOrder(e),document.getElementById("appSettingsShowTabDetachButton").checked=this.tabDetachButtonEnabled?.(e,t)??e.showTabDetachButton??t.showTabDetachButton??!1,document.getElementById("appSettingsShowTabNewButton").checked=e.showTabNewButton===!0,document.getElementById("appSettingsTileDetach").checked=e.tileDetachEnabled===!0;const l=document.getElementById("appSettingsTileDetachItem");l&&(l.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsSessionListLayout").value=e.sessionListLayout??t.sessionListLayout??"header";const c=this.resolveSessionSidebarFontSize(e.sessionSidebarFontSize??t.sessionSidebarFontSize);document.getElementById("appSettingsSessionSidebarFontSize").value=String(c),document.getElementById("appSettingsSessionSidebarFontSizeValue").textContent=`${c} px`;const p=document.getElementById("appSettingsClaudeMode"),d=document.getElementById("allowedToolsRow");p.value=e.claudeMode||"dangerously-skip-permissions",document.getElementById("appSettingsAllowedTools").value=e.allowedTools||"",d.style.display=p.value==="allowedTools"?"":"none",p.onchange=()=>{d.style.display=p.value==="allowedTools"?"":"none"},document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked=e.codexDangerouslyBypassApprovals??!1,document.getElementById("appSettingsCodexAnimations").checked=e.codexAnimationsEnabled??!1,document.getElementById("appSettingsCodexModel").value=e.codexModel??"",document.getElementById("appSettingsCodexReasoningEffort").value=e.codexReasoningEffort??"",this._applyCodexSettingsVisibility(),document.getElementById("appSettingsAgentTeams").checked=e.agentTeamsEnabled??!1,document.getElementById("appSettingsAgentSkill").checked=e.agentSkillEnabled??!1,document.getElementById("appSettingsWorkspaceHooks").checked=e.workspaceHooksEnabled!==!1,document.getElementById("appSettingsPinCliSessionName").checked=e.pinCliSessionName===!0,document.getElementById("appSettingsClaudeModel").value=e.claudeModel??"",document.getElementById("appSettingsOpusContext1m").checked=e.opusContext1mEnabled??!1,document.getElementById("appSettingsRemoteAutoReconnect").checked=e.remoteAutoReconnect??!0,document.getElementById("appSettingsThinkingEffort").value=e.thinkingEffort??"",document.getElementById("appSettingsClaudeAdvisor").value=e.claudeAdvisorModel??"";const r=e.nice||{};document.getElementById("appSettingsNiceEnabled").checked=r.enabled??!1,document.getElementById("appSettingsNiceValue").value=r.niceValue??10,this.loadModelConfigForSettings();const u=this.notificationManager?.preferences||{};document.getElementById("appSettingsNotifEnabled").checked=u.enabled??!0,document.getElementById("appSettingsNotifBrowser").checked=u.browserNotifications??!1,document.getElementById("appSettingsNotifAudio").checked=u.audioAlerts??!1,document.getElementById("appSettingsNotifStuckMins").value=Math.round((u.stuckThresholdMs||6e5)/6e4),document.getElementById("appSettingsNotifToastSecs").value=Math.round((this.notificationManager?.getToastDurationMs?.()??DEFAULT_TOAST_DURATION_MS)/1e3),document.getElementById("appSettingsNotifBrowserSecs").value=Math.round((u.browserAutoCloseMs??AUTO_CLOSE_NOTIFICATION_MS)/1e3),document.getElementById("appSettingsNotifCritical").checked=!u.muteCritical,document.getElementById("appSettingsNotifWarning").checked=!u.muteWarning,document.getElementById("appSettingsNotifInfo").checked=!u.muteInfo,document.getElementById("appSettingsPushEnabled").checked=!!this._pushSubscription,this._updatePushUI(!!this._pushSubscription);const h=u.eventTypes||{},f=h.permission_prompt||{};document.getElementById("eventPermissionEnabled").checked=f.enabled??!0,document.getElementById("eventPermissionBrowser").checked=f.browser??!0,document.getElementById("eventPermissionPush").checked=f.push??!1,document.getElementById("eventPermissionAudio").checked=f.audio??!0;const m=h.elicitation_dialog||{};document.getElementById("eventQuestionEnabled").checked=m.enabled??!0,document.getElementById("eventQuestionBrowser").checked=m.browser??!0,document.getElementById("eventQuestionPush").checked=m.push??!1,document.getElementById("eventQuestionAudio").checked=m.audio??!0;const g=h.idle_prompt||{};document.getElementById("eventIdleEnabled").checked=g.enabled??!0,document.getElementById("eventIdleBrowser").checked=g.browser??!0,document.getElementById("eventIdlePush").checked=g.push??!1,document.getElementById("eventIdleAudio").checked=g.audio??!1;const y=h.stop||{};document.getElementById("eventStopEnabled").checked=y.enabled??!1,document.getElementById("eventStopBrowser").checked=y.browser??!1,document.getElementById("eventStopPush").checked=y.push??!1,document.getElementById("eventStopAudio").checked=y.audio??!1;const S=h.respawn_cycle||{};document.getElementById("eventRespawnEnabled").checked=S.enabled??!0,document.getElementById("eventRespawnBrowser").checked=S.browser??!1,document.getElementById("eventRespawnPush").checked=S.push??!1,document.getElementById("eventRespawnAudio").checked=S.audio??!1;const b=h.ralph_complete||{};document.getElementById("eventRalphEnabled").checked=b.enabled??!0,document.getElementById("eventRalphBrowser").checked=b.browser??!0,document.getElementById("eventRalphPush").checked=b.push??!1,document.getElementById("eventRalphAudio").checked=b.audio??!0;const E=h.subagent_spawn||{};document.getElementById("eventSubagentEnabled").checked=E.enabled??!1,document.getElementById("eventSubagentBrowser").checked=E.browser??!1,document.getElementById("eventSubagentPush").checked=E.push??!1,document.getElementById("eventSubagentAudio").checked=E.audio??!1;const w=document.getElementById("notifPermissionStatus");if(w&&typeof Notification<"u"){const I=Notification.permission;w.textContent=I==="granted"?"\u2713":I==="denied"?"\u2717":"?",w.classList.remove("granted","denied"),I==="granted"?w.classList.add("granted"):I==="denied"&&w.classList.add("denied")}const v=VoiceInput._getDeepgramConfig();document.getElementById("voiceDeepgramKey").value=v.apiKey||"",document.getElementById("voiceLanguage").value=v.language||"en-US",document.getElementById("voiceKeyterms").value=v.keyterms||DEFAULT_VOICE_KEYTERMS,document.getElementById("voiceInsertMode").value=v.insertMode||"direct",document.getElementById("voiceProvider").value=v.provider||"auto",document.getElementById("appSettingsClaudeVoice").checked=e.claudeVoiceEnabled??!1;const B=document.getElementById("voiceDeepgramKey");B.type="password",document.getElementById("voiceKeyToggleBtn").textContent="Show",this._renderVoiceProviderStatus(),VoiceInput.refreshClaudeStatus().then(()=>this._renderVoiceProviderStatus()),this._initUpdatesSection(),this._initSettingsNav(),this._syncSettingsChips(),this._syncModelCards(),this._syncEffortSegment(),this._syncAdvisorSegment(),this.switchSettingsTab("settings-updates");const k=document.getElementById("appSettingsModal");k.classList.add("active"),this.activeFocusTrap=new FocusTrap(k),this.activeFocusTrap.activate(),this._captureSettingsFormBaseline()},_applyCodexSettingsVisibility(){const e=document.getElementById("appSettingsCodexGroup");e&&(e.style.display=window.__codemanCliAvailable?.codex===!0?"":"none")},switchSettingsTab(e){e==="settings-shortcuts"&&this.renderShortcutSettingsList?.();const t=document.getElementById("appSettingsDoc"),n=document.getElementById(e);if(t&&n&&typeof n.offsetTop=="number"){const s=document.getElementById("appSettingsJump"),i=s&&s.offsetParent?s.offsetHeight+16:6;t.scrollTop=Math.max(0,n.offsetTop-i)}this._setActiveSettingsSection(e)},_setActiveSettingsSection(e){const t=document.getElementById("appSettingsModal");if(!t||typeof t.querySelectorAll!="function")return;let n=null;t.querySelectorAll(".set-rail-item").forEach(a=>{const l=a.dataset.section===e;a.classList.toggle("active",l),l&&(n=a)}),t.querySelectorAll(".set-jump-row").forEach(a=>{a.classList.toggle("active",a.dataset.section===e)});const s=document.getElementById("appSettingsJump")?.querySelector(".set-jump-label");s&&n&&(s.textContent=n.textContent.trim());const i=document.getElementById("appSettingsJump")?.querySelector(".set-jump-ico"),o=n?.querySelector("svg");i&&o&&(i.innerHTML=o.innerHTML)},_initSettingsNav(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsDoc");if(!e||!t||typeof e.querySelectorAll!="function"||(this._buildModelCards(),this._buildEffortSegment(),this._buildAdvisorSegment(),this._buildSettingsJumpMenu(),e.dataset.navReady==="1"))return;e.dataset.navReady="1",e.querySelector(".set-rail-items")?.addEventListener("click",o=>{const a=o.target.closest?.(".set-rail-item");a?.dataset.section&&this.switchSettingsTab(a.dataset.section)}),document.getElementById("appSettingsJumpMenu")?.addEventListener("click",o=>{const a=o.target.closest?.(".set-jump-row");a?.dataset.section&&(this._toggleSettingsJump(!1),this.switchSettingsTab(a.dataset.section))}),document.getElementById("appSettingsJump")?.addEventListener("click",()=>this._toggleSettingsJump()),document.getElementById("appSettingsJumpVeil")?.addEventListener("click",()=>this._toggleSettingsJump(!1)),t.addEventListener("scroll",()=>{this._settingsSpyQueued||(this._settingsSpyQueued=!0,requestAnimationFrame(()=>{this._settingsSpyQueued=!1;const o=[...t.querySelectorAll(".set-section")].filter(l=>l.offsetParent!==null);if(!o.length)return;let a=o[0].id;for(const l of o)l.offsetTop-t.scrollTop<=140&&(a=l.id);this._setActiveSettingsSection(a)}))});const n=document.getElementById("appSettingsSearch");n?.addEventListener("input",()=>this._filterSettings(n.value)),e.querySelectorAll(".set-chip input").forEach(o=>{o.addEventListener("change",()=>this._syncSettingsChips())});const s=e.querySelector(".set-group-head-toggle"),i=s?.closest(".set-group-advanced");if(s&&i){const o=()=>{const a=i.classList.toggle("open");s.setAttribute("aria-expanded",a?"true":"false")};s.addEventListener("click",o),s.addEventListener("keydown",a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),o())})}document.getElementById("appSettingsOpusContext1m")?.addEventListener("change",()=>this._applyModelSelection())},_buildSettingsJumpMenu(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsJumpMenu");!e||!t||(t.innerHTML="",e.querySelectorAll(".set-rail-item").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-jump-row",s.dataset.section=n.dataset.section,s.innerHTML=n.innerHTML;const i=document.getElementById(n.dataset.section),o=i?i.querySelectorAll("input, select").length:0;if(o){const a=document.createElement("span");a.className="set-jump-count",a.textContent=String(o),s.appendChild(a)}t.appendChild(s)}))},_toggleSettingsJump(e){const t=document.getElementById("appSettingsModal");if(!t)return;const n=e===void 0?!t.classList.contains("jump-open"):e;t.classList.toggle("jump-open",n),document.getElementById("appSettingsJump")?.setAttribute("aria-expanded",n?"true":"false")},_syncSettingsChips(){document.querySelectorAll("#appSettingsModal .set-chip, #sessionOptionsModal .set-chip").forEach(e=>{e.classList.toggle("is-on",!!e.querySelector("input")?.checked)}),this._syncLayoutPreview()},_syncLayoutPreview(){const e=document.getElementById("appSettingsModal");if(!e||typeof e.querySelectorAll!="function")return;const t={header:document.getElementById("appSettingsPreviewHeader"),panel:document.getElementById("appSettingsPreviewPanels"),toolbar:document.getElementById("appSettingsPreviewToolbar"),float:document.getElementById("appSettingsPreviewFloats")};if(!t.header)return;Object.values(t).forEach(o=>{o&&(o.innerHTML="")});const n=[...e.querySelectorAll(".set-chip[data-preview]")].filter(o=>o.querySelector("input")?.checked).sort((o,a)=>(Number(o.dataset.previewOrder)||0)-(Number(a.dataset.previewOrder)||0));let s=0;for(const o of n){const a=o.dataset.preview,l=t[a];if(!l)continue;const c=o.querySelectorAll("span"),p=(c[c.length-1]?.textContent||"").trim(),d=document.createElement("span");if(d.title=p,a==="header"){const r=o.dataset.previewText;d.className=r?"set-preview-chip":"set-preview-btn",r?d.textContent=r:this._appendPreviewIcon(d,o)}else{d.className=`set-preview-${a}`,this._appendPreviewIcon(d,o);const r=document.createElement("span");r.textContent=p,d.appendChild(r)}l.appendChild(d),s++}const i=document.getElementById("appSettingsPreviewEmpty");i&&(i.hidden=s>0)},_appendPreviewIcon(e,t){const n=t.querySelector(".set-chip-ico");if(!n)return;const s=n.cloneNode(!0);s.classList.remove("set-chip-ico"),s.classList.add("set-preview-ico"),e.appendChild(s)},_buildModelCards(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].filter(n=>n.dataset.variant!=="1m").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-modelcard",s.setAttribute("role","radio"),s.dataset.value=n.value,n.dataset.ctx==="1"&&(s.dataset.ctx="1");const i=document.createElement("span");i.className="set-mc-top";const o=document.createElement("span");o.className="set-mc-name",o.textContent=n.textContent,i.appendChild(o);const a=document.createElement("span");a.className="set-mc-dot",i.appendChild(a),s.appendChild(i);const l=document.createElement("span");if(l.className="set-mc-meta",l.textContent=n.dataset.meta||"",s.appendChild(l),n.dataset.ctx==="1"){const c=document.createElement("span");c.className="set-mc-ctx",c.textContent="1M capable",s.appendChild(c)}s.addEventListener("click",()=>{this._settingsModelBase=n.value,this._applyModelSelection()}),t.appendChild(s)}),t.dataset.built="1")},_syncModelCards(){const e=document.getElementById("appSettingsClaudeModel");if(!e)return;const t=e.value||"";if(this._settingsModelBase=t.endsWith("[1m]")?t.slice(0,-4):t,t.endsWith("[1m]")){const n=document.getElementById("appSettingsOpusContext1m");n&&(n.checked=!0)}this._applyModelSelection()},_applyModelSelection(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");if(!e||!t)return;const n=this._settingsModelBase||"";let s=!1;t.querySelectorAll(".set-modelcard").forEach(c=>{const p=c.dataset.value===n;c.classList.toggle("selected",p),c.setAttribute("aria-checked",p?"true":"false"),p&&(s=c.dataset.ctx==="1")});const i=!!document.getElementById("appSettingsOpusContext1m")?.checked;e.value=n&&s&&i?`${n}[1m]`:n;const o=document.getElementById("appSettingsContextRow"),a=document.getElementById("appSettingsContextDesc"),l=!!n&&!s;o?.classList.toggle("set-row-disabled",l),a&&(a.textContent=l?"The selected model has no 1M variant.":n?"Available for Fable 5.1, Fable 5, Opus 5.5, Opus and Opus 4.6.":"With no model pinned, this starts new sessions on Opus with a 1M window.")},_buildEffortSegment(){this._buildSelectSegment("appSettingsThinkingEffort","appSettingsEffortSegment")},_syncEffortSegment(){this._syncSelectSegment("appSettingsThinkingEffort","appSettingsEffortSegment")},_buildAdvisorSegment(){this._buildSelectSegment("appSettingsClaudeAdvisor","appSettingsAdvisorSegment")},_syncAdvisorSegment(){this._syncSelectSegment("appSettingsClaudeAdvisor","appSettingsAdvisorSegment")},_buildSelectSegment(e,t){const n=document.getElementById(e),s=document.getElementById(t);!n||!s||s.dataset.built==="1"||!n.options||(s.innerHTML="",[...n.options].forEach(i=>{const o=document.createElement("button");o.type="button",o.setAttribute("role","radio"),o.dataset.value=i.value,o.textContent=i.textContent,o.addEventListener("click",()=>{n.value=i.value,this._syncSelectSegment(e,t)}),s.appendChild(o)}),s.dataset.built="1")},_syncSelectSegment(e,t){const n=document.getElementById(e),s=document.getElementById(t);!n||!s||s.querySelectorAll("button").forEach(i=>{const o=i.dataset.value===(n.value||"");i.classList.toggle("selected",o),i.setAttribute("aria-checked",o?"true":"false")})},_filterSettings(e){const t=document.getElementById("appSettingsDoc");if(!t)return;const n=(e||"").trim().toLowerCase(),s=".set-row, .set-chip, .set-modelgrid, .set-minigrid, .event-type-grid, #appSettingsShortcutsList",i=[...t.querySelectorAll(s)];let o=!1;i.forEach(l=>{if(!n){l.classList.remove("set-hit-hidden");return}const p=`${l.dataset?.search||""} ${l.textContent||""}`.toLowerCase().includes(n);l.classList.toggle("set-hit-hidden",!p),p&&(o=!0)}),t.querySelectorAll(".set-chips").forEach(l=>{const c=[...l.querySelectorAll(".set-chip")].some(p=>!p.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-group").forEach(l=>{const c=[...l.querySelectorAll(s)].some(p=>!p.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c),n&&c&&l.classList.add("open")}),t.querySelectorAll(".set-section").forEach(l=>{const c=[...l.querySelectorAll(".set-group")].some(p=>!p.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-preview").forEach(l=>l.classList.toggle("set-hit-hidden",!!n));const a=document.getElementById("appSettingsSearchEmpty");a&&(a.hidden=!n||o),n||t.querySelectorAll(".set-group-advanced").forEach(l=>l.classList.remove("open"))},closeAppSettings(){this._toggleSettingsJump(!1),document.getElementById("appSettingsModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},_updatePhaseText(e){return{queued:"Queued\u2026",preparing:"Preparing\u2026",stashing:"Stashing local changes\u2026",fetching:"Fetching release\u2026",checkout:"Checking out release\u2026",installing:"Installing dependencies\u2026",building:"Building\u2026",restarting:"Restarting Codeman\u2026"}[e]||e},_initUpdatesSection(){const e=this.$("updateCurrentVersion");e&&(e.textContent=(this.$("versionDisplay")?.textContent||"").trim()||"\u2014");for(const t of["updateResult","updateActionRow","updateNotes","updateProgress"]){const n=this.$(t);n&&(n.style.display="none")}this._updateCheck=null},keyTesterEvent(e){const t=document.getElementById("keyTesterLog");if(!t)return;const n=["ctrlKey","shiftKey","altKey","metaKey"].filter(o=>e[o]).map(o=>o.replace("Key","")),s=`${e.type.padEnd(8)} key=${JSON.stringify(e.key)} code=${e.code||"-"} mods=${n.join("+")||"none"}`+(e.type==="keypress"?` charCode=${e.charCode}`:"")+(e.repeat?" (repeat)":""),i=(t.textContent?t.textContent.split(`
1
+ "use strict";Object.assign(CodemanApp.prototype,{_onHookIdlePrompt(e){e.acknowledgedReason||(e.sessionId&&this.setPendingHook(e.sessionId,"idle_prompt"),this._notifySession(e.sessionId,"warning","hook-idle","Waiting for Input",e.message||"Claude is idle and waiting for a prompt"))},_onHookPermissionPrompt(e){e.sessionId&&this.setPendingHook(e.sessionId,"permission_prompt");const t=e.tool?`${e.tool}${e.command?": "+e.command:e.file?": "+e.file:""}`:"",n=e.source==="screen"&&e.message?e.message:"";this._notifySession(e.sessionId,"critical","hook-permission","Permission Required",t||n||"Claude needs tool approval to continue")},_onHookElicitationDialog(e){e.sessionId&&this.setPendingHook(e.sessionId,"elicitation_dialog"),this._notifySession(e.sessionId,"critical","hook-elicitation","Question Asked",e.question||"Claude is asking a question and waiting for your answer")},_onHookElicitationComplete(e){e.sessionId&&(this.clearPendingHooks(e.sessionId,"elicitation_dialog"),this.clearPendingHooks(e.sessionId,"permission_prompt"))},_onHookElicitationResponse(e){this._onHookElicitationComplete(e)},_onHookStop(e){e.acknowledgedReason||(e.sessionId&&this.clearPendingHooks(e.sessionId),this._notifySession(e.sessionId,"info","hook-stop","Response Complete",e.reason||"Claude has finished responding"))},_onHookAgentWorking(e){e.sessionId&&(this.clearPendingHooks(e.sessionId,"elicitation_dialog"),this.clearPendingHooks(e.sessionId,"permission_prompt"),this.clearPendingHooks(e.sessionId,"idle_prompt"))},_onHookTeammateIdle(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"warning","hook-teammate-idle","Teammate Idle",`A teammate is idle in ${t?.name||e.sessionId}`)},_onHookTaskCompleted(e){const t=this.sessions.get(e.sessionId);this._notifySession(e.sessionId,"info","hook-task-completed","Task Completed",`A team task completed in ${t?.name||e.sessionId}`)},_onTunnelStarted(e){console.log("[Tunnel] Started:",e.url),this._tunnelUrl=e.url,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(e.url),this._updateTunnelIndicator(!0),document.getElementById("welcomeOverlay")?.classList.contains("visible")?(this._updateWelcomeTunnelBtn(!0,e.url,!0),this.showToast("Tunnel active","success")):(this._updateWelcomeTunnelBtn(!0,e.url),this.showToast(`Tunnel active: ${e.url}`,"success"),this.showTunnelQR())},_onTunnelStopped(){console.log("[Tunnel] Stopped"),this._tunnelUrl=null,this._dismissTunnelConnecting(),this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1),this.closeTunnelPanel(),this.closeTunnelQR()},_onTunnelProgress(e){console.log("[Tunnel] Progress:",e.message);const t=document.getElementById("tunnelConnectingToast");t&&(t.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`);const n=document.getElementById("welcomeTunnelBtn");n?.classList.contains("connecting")&&(n.innerHTML=`<span class="tunnel-spinner"></span> ${e.message}`)},_onTunnelError(e){console.warn("[Tunnel] Error:",e.message),this._dismissTunnelConnecting(),this.showToast(`Tunnel error: ${e.message}`,"error");const t=document.getElementById("welcomeTunnelBtn");t&&(t.disabled=!1,t.classList.remove("connecting"))},_onTunnelQrRotated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrRegenerated(e){if(e.svg){const t=document.getElementById("tunnelQrContainer");t&&(t.innerHTML=e.svg);const n=document.getElementById("welcomeQrInner");n&&(n.innerHTML=e.svg)}else this._refreshTunnelQrFromApi();this._resetQrCountdown()},_onTunnelQrAuthUsed(e){const n=(e.ua||"Unknown device").match(/Chrome|Firefox|Safari|Edge|Mobile/)?.[0]||"Browser";this.showToast(`Device authenticated via QR (${n}, ${e.ip}). Not you?`,"warning",{duration:1e4,action:{label:"Revoke All",onClick:()=>{fetch("/api/auth/revoke",{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}).then(()=>this.showToast("All sessions revoked","success")).catch(()=>this.showToast("Failed to revoke sessions","error"))}}})},registerServiceWorker(){if(!("serviceWorker"in navigator))return;const e=window.CodemanBase?.base||"";navigator.serviceWorker.register(e+"/sw.js",{scope:e+"/"}).then(t=>{this._swRegistration=t,navigator.serviceWorker.addEventListener("message",n=>{if(n.data?.type==="notification-click"){const{sessionId:s,action:i,approvalId:o}=n.data;i?this.handleNotificationAction?.(i,o,s):s&&this.sessions.has(s)&&this.selectSession(s),window.focus()}}),t.pushManager.getSubscription().then(n=>{n&&(this._pushSubscription=n,this._updatePushUI(!0))})}).catch(()=>{})},async subscribeToPush(){if(!this._swRegistration){this.showToast("Service worker not available. HTTPS or localhost required.","error");return}try{const e=await this._apiJson("/api/push/vapid-key");if(!e)throw new Error("Failed to get VAPID key");const t=urlBase64ToUint8Array(e.publicKey),n=await this._swRegistration.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:t}),s=n.toJSON(),i=await this._apiJson("/api/push/subscribe",{method:"POST",body:{endpoint:s.endpoint,keys:s.keys,userAgent:navigator.userAgent,pushPreferences:this._buildPushPreferences()}});if(!i)throw new Error("Failed to register subscription");this._pushSubscription=n,this._pushSubscriptionId=i.id,localStorage.setItem("codeman-push-subscription-id",i.id),this._updatePushUI(!0),this.showToast("Push notifications enabled","success")}catch(e){this.showToast("Push subscription failed: "+(e.message||e),"error")}},async unsubscribeFromPush(){try{this._pushSubscription&&await this._pushSubscription.unsubscribe();const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");e&&await fetch(`/api/push/subscribe/${e}`,{method:"DELETE"}).catch(()=>{}),this._pushSubscription=null,this._pushSubscriptionId=null,localStorage.removeItem("codeman-push-subscription-id"),this._updatePushUI(!1),this.showToast("Push notifications disabled","success")}catch(e){this.showToast("Failed to unsubscribe: "+(e.message||e),"error")}},async togglePushSubscription(){this._pushSubscription?await this.unsubscribeFromPush():await this.subscribeToPush()},async _syncPushPreferences(){const e=this._pushSubscriptionId||localStorage.getItem("codeman-push-subscription-id");if(e)try{await fetch(`/api/push/subscribe/${e}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({pushPreferences:this._buildPushPreferences()})})}catch{}},_buildPushPreferences(){const e={},t={"hook:permission_prompt":"eventPermissionPush","hook:elicitation_dialog":"eventQuestionPush","hook:idle_prompt":"eventIdlePush","hook:stop":"eventStopPush","respawn:blocked":"eventRespawnPush","session:ralphCompletionDetected":"eventRalphPush"};for(const[n,s]of Object.entries(t)){const i=document.getElementById(s);e[n]=i?i.checked:!0}return e["session:error"]=!0,e},_updatePushUI(e){const t=document.getElementById("pushSubscribeBtn"),n=document.getElementById("pushSubscriptionStatus");t&&(t.textContent=e?"Unsubscribe":"Subscribe"),n&&(n.textContent=e?"active":"off",n.classList.remove("granted","denied"),e&&n.classList.add("granted"))},populateTerminalFontWeight(e,t){if(!e)return;const n=t==null?"":String(t).trim();if(n&&!Array.from(e.options).some(s=>s.value===n)){const s=document.createElement("option");s.value=n,s.textContent=`${n} (custom)`,e.appendChild(s)}e.value=n},readTerminalFontWeight(e){return e?.value.trim()||""},async checkWhatsNew(e){if(this._whatsNewChecked)return;this._whatsNewChecked=!0;const t="codeman-whats-new-seen";let n=null;try{n=localStorage.getItem(t)}catch{return}const s=()=>{try{localStorage.setItem(t,e)}catch{}},[i,o]=await Promise.all([n&&n!==e?this._fetchWhatsNewNotes():null,this._loadSecurityNotices()]);if(this.isSoloWindow&&o.length)return;const a=window.CodemanSecurityNotices.plan({seenVersion:n,runningVersion:e,notes:i,notices:o});if(a.recordVersion&&s(),a.update){const l=i.highlights.slice(0,3).join(" \xB7 "),c=i.highlights.length>3?` (+${i.highlights.length-3} more)`:"",p=`Codeman updated to v${e}: ${l}${c}`,d=`https://github.com/Ark0N/Codeman/releases/tag/codeman%40${e}`;a.updateNotices.length?this.showUpdateNotice({text:p,releaseUrl:d,notices:a.updateNotices,onNoticeSeen:r=>this._rememberSecurityNotice(r)}):this.showToast(p,"info",{duration:2e4,action:{label:"See what's new",onClick:()=>window.open(d,"_blank","noopener")}})}for(const l of a.cards)this.showSecurityNotice(l,{onClose:()=>this._rememberSecurityNotice(l.id)})},async _fetchWhatsNewNotes(){try{const e=await fetch("/whats-new.json",{cache:"no-cache"});return e.ok?await e.json():null}catch{return null}},async _loadSecurityNotices(){let e;try{e=this._readSecurityNoticesSeen()}catch{return[]}try{const t=await fetch(CodemanBase.url("/security-notices.json"),{cache:"no-cache"});if(!t.ok)return[];const n=await t.json(),s=Array.isArray(n?.notices)?n.notices:[],i=this.isSoloWindow?Object.fromEntries(s.filter(o=>typeof o?.cli=="string").map(o=>[o.cli,!0])):window.__codemanCliAvailable;return window.CodemanSecurityNotices.select(n?.notices,{available:i,seen:e,now:Date.now(),lang:window.CodemanI18n?.language||window.__codemanLanguage})}catch{return[]}},_readSecurityNoticesSeen(){const e=localStorage.getItem("codeman-security-notices-seen");let t=[];try{t=e?JSON.parse(e):[]}catch{}return Array.isArray(t)?t.filter(n=>typeof n=="string"):[]},_rememberSecurityNotice(e){try{const t=this._readSecurityNoticesSeen();t.includes(e)||t.push(e),localStorage.setItem("codeman-security-notices-seen",JSON.stringify(t))}catch{}},openAppSettings(){const e=this.loadAppSettingsFromStorage();document.getElementById("appSettingsDisplayName").value=e.displayName||"Codeman",document.getElementById("appSettingsLanguage").value=e.language==="zh-CN"?"zh-CN":"en",document.getElementById("appSettingsClaudeMdPath").value=e.defaultClaudeMdPath||"",document.getElementById("appSettingsDefaultDir").value=e.defaultWorkingDir||"";const t=this.getDefaultSettings();document.getElementById("appSettingsRalphEnabled").checked=e.ralphTrackerEnabled??t.ralphTrackerEnabled??!1,document.getElementById("appSettingsShowFontControls").checked=e.showFontControls??t.showFontControls??!1,document.getElementById("appSettingsShowSystemStats").checked=e.showSystemStats??t.showSystemStats??!0,document.getElementById("appSettingsShowGpuStats").checked=e.showGpuStats??t.showGpuStats??!1,document.getElementById("appSettingsShowCpuMhz").checked=e.showCpuMhz??t.showCpuMhz??!1,document.getElementById("appSettingsHeaderStatsStyle").value=this.resolveHeaderStatsStyle(e),document.getElementById("appSettingsShowLifecycleLog").checked=e.showLifecycleLog??t.showLifecycleLog??!1,document.getElementById("appSettingsShowResponseViewer").checked=e.showResponseViewer??t.showResponseViewer??!1,document.getElementById("appSettingsShowFileViewerButton").checked=e.showFileViewerButton??t.showFileViewerButton??!0,document.getElementById("appSettingsShowAttachmentsButton").checked=e.showAttachmentsButton??t.showAttachmentsButton??!1,document.getElementById("appSettingsSkin").value=e.skin??t.skin??"daylight-blue",this._syncEntranceAnimSetting?.(),document.getElementById("appSettingsWebglRenderer").checked=e.webglRendererEnabled??t.webglRendererEnabled??!0;const n=document.getElementById("appSettingsWebglRendererItem");n&&(n.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsShowMonitor").checked=e.showMonitor??t.showMonitor??!1,document.getElementById("appSettingsShowProjectInsights").checked=e.showProjectInsights??t.showProjectInsights??!1,document.getElementById("appSettingsShowFileBrowser").checked=e.showFileBrowser??t.showFileBrowser??!1,document.getElementById("appSettingsShowSubagents").checked=e.showSubagents??t.showSubagents??!1,document.getElementById("appSettingsShowUltracodeAgents").checked=e.showUltracodeAgents??t.showUltracodeAgents??!1,document.getElementById("appSettingsApprovalsInbox").checked=e.approvalsInboxEnabled===!0,document.getElementById("appSettingsAgentInboxPersist").checked=e.agentInboxPersistEnabled===!0,document.getElementById("appSettingsAgentCoordination").checked=e.agentCoordinationEnabled===!0,document.getElementById("appSettingsCustomModelEndpoints").checked=e.customModelEndpointsEnabled===!0,this.applyCustomModelEndpointsVisibility(),document.getElementById("appSettingsCliManagement").checked=e.cliManagementEnabled===!0,this.applyCliManagementVisibility(),this._mcpSyncSavedOn=e.mcpSyncEnabled===!0,document.getElementById("appSettingsMcpSync").checked=this._mcpSyncSavedOn,this.applyMcpSyncVisibility(),document.getElementById("appSettingsConfigBackup").checked=e.configBackupEnabled!==!1,document.getElementById("appSettingsConfigBackupDir").value=e.configBackupDir||"",document.getElementById("appSettingsConfigBackupKeepCount").value=e.configBackupKeepCount??20,document.getElementById("appSettingsConfigBackupKeepDays").value=e.configBackupKeepDays??30,this.loadConfigBackups(),this._applyDoctorAdminGate(),this.loadWebhook(),this.loadDiscord(),document.getElementById("appSettingsReadMyMind").checked=e.readMyMindEnabled===!0,document.getElementById("appSettingsUltracodeFloatingWindows").checked=e.ultracodeFloatingWindows??t.ultracodeFloatingWindows??!1,document.getElementById("appSettingsShowMultiMonitorButton").checked=e.showMultiMonitorButton??t.showMultiMonitorButton??!1,document.getElementById("appSettingsShowSplitButton").checked=e.showSplitButton??t.showSplitButton??!1,document.getElementById("appSettingsShowTileGridButton").checked=e.showTileGridButton??t.showTileGridButton??!0,document.getElementById("appSettingsShowPlanUsageLimits").checked=this.planUsageChipEnabled(e),document.getElementById("appSettingsShowRedrawButton").checked=e.showRedrawButton??t.showRedrawButton??!1,document.getElementById("appSettingsLineageLines").checked=e.sessionLineageLines??t.sessionLineageLines??!0,document.getElementById("appSettingsAutoNameSessions").checked=e.autoNameSessions===!0,document.getElementById("appSettingsSpawnedTabsFollowParent").checked=e.spawnedTabsFollowParent===!0;const s=document.getElementById("appSettingsLineageLinesItem");s&&(s.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsMobileOverview").checked=e.mobileOverviewEnabled??t.mobileOverviewEnabled??!1;const i=document.getElementById("appSettingsMobileOverviewItem");i&&(i.style.display=MobileDetection.getDeviceType()==="mobile"?"":"none"),document.getElementById("appSettingsShowSessionButton").checked=e.showSessionButton??t.showSessionButton??!1,document.getElementById("appSettingsShowAwayDigestButton").checked=e.showAwayDigestButton??t.showAwayDigestButton??!1,document.getElementById("appSettingsShowCronButton").checked=e.showCronButton??t.showCronButton??!1,document.getElementById("appSettingsShowGitStatus").checked=e.showGitStatus??t.showGitStatus??!1,document.getElementById("appSettingsGitStatusTree").checked=e.gitStatusTree??t.gitStatusTree??!0,document.getElementById("appSettingsGitStatusMaxRepos").value=e.gitStatusMaxRepos??t.gitStatusMaxRepos??12,document.getElementById("appSettingsGitStatusTimeout").value=e.gitStatusTimeoutSeconds??t.gitStatusTimeoutSeconds??30,document.getElementById("appSettingsGitFetchEnabled").checked=e.gitFetchEnabled===!0,document.getElementById("appSettingsGitFetchIntervalSeconds").value=e.gitFetchIntervalSeconds??300,document.getElementById("appSettingsShowEditHistory").checked=e.showEditHistory??t.showEditHistory??!1;const o=document.getElementById("appSettingsGestureControlItem");o&&(o.style.display=window.__codemanGestureAvailable?"":"none"),document.getElementById("appSettingsGestureControl").checked=e.gestureControlEnabled??t.gestureControlEnabled??!1,document.getElementById("appSettingsSubagentTracking").checked=e.subagentTrackingEnabled??t.subagentTrackingEnabled??!0,document.getElementById("appSettingsSubagentActiveTabOnly").checked=e.subagentActiveTabOnly??t.subagentActiveTabOnly??!0,document.getElementById("appSettingsImageWatcherEnabled").checked=e.imageWatcherEnabled??t.imageWatcherEnabled??!1,document.getElementById("appSettingsPromptCacheKeepWarm").checked=e.promptCacheKeepWarm===!0,document.getElementById("appSettingsPromptCacheKeepWarmMaxHours").value=e.promptCacheKeepWarmMaxHours??2,document.getElementById("appSettingsTunnelEnabled").checked=e.tunnelEnabled??!1,this.loadTunnelStatus(),document.getElementById("appSettingsLocalEcho").checked=e.localEchoEnabled??MobileDetection.isTouchDevice(),document.getElementById("appSettingsKeyboardHoldsRows").checked=e.keyboardHoldsRows!==!1,document.getElementById("appSettingsAutoCopySelection").checked=e.autoCopySelection===!0,document.getElementById("appSettingsCopyStripMargin").checked=e.copyStripMargin!==!1,document.getElementById("appSettingsTerminalFont").value=e.terminalFontFamily||"",this.populateTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeight"),e.terminalFontWeight),this.populateTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeightBold"),e.terminalFontWeightBold),document.getElementById("appSettingsTerminalWheelLocal").checked=e.terminalWheelLocalScrollback??t.terminalWheelLocalScrollback??!1,document.getElementById("appSettingsCjkInput").checked=e.cjkInputEnabled??t.cjkInputEnabled??!1,document.getElementById("appSettingsExtendedKeyboardBar").checked=e.extendedKeyboardBar??!1,document.getElementById("appSettingsTabTwoRows").checked=e.tabTwoRows??t.tabTwoRows??!1,document.getElementById("appSettingsShowTabCliLogos").checked=this.tabCliLogosEnabled(e),document.getElementById("appSettingsTabOrientation").value=e.tabOrientation??t.tabOrientation??"horizontal";const a=window.CodemanTabRail?.resolveWidth({width:e.tabRailWidth??this._defaultTabRailWidth?.()??t.tabRailWidth??256})??256;this.syncTabRailWidthSetting?.(a),document.getElementById("appSettingsTabRailDetail").value=e.tabRailDetail??t.tabRailDetail??"rich",document.getElementById("appSettingsTabRailSort").value=e.tabRailSort??t.tabRailSort??"activity",document.getElementById("appSettingsTabArrangement").value=this.resolveTabArrangement(e),document.getElementById("appSettingsTabStateOrder").value=this.resolveTabStateOrder(e),document.getElementById("appSettingsShowTabDetachButton").checked=this.tabDetachButtonEnabled?.(e,t)??e.showTabDetachButton??t.showTabDetachButton??!1,document.getElementById("appSettingsShowTabNewButton").checked=e.showTabNewButton===!0,document.getElementById("appSettingsTileDetach").checked=e.tileDetachEnabled===!0;const l=document.getElementById("appSettingsTileDetachItem");l&&(l.style.display=MobileDetection.getDeviceType()==="desktop"?"":"none"),document.getElementById("appSettingsSessionListLayout").value=e.sessionListLayout??t.sessionListLayout??"header";const c=this.resolveSessionSidebarFontSize(e.sessionSidebarFontSize??t.sessionSidebarFontSize);document.getElementById("appSettingsSessionSidebarFontSize").value=String(c),document.getElementById("appSettingsSessionSidebarFontSizeValue").textContent=`${c} px`;const p=document.getElementById("appSettingsClaudeMode"),d=document.getElementById("allowedToolsRow");p.value=e.claudeMode||"dangerously-skip-permissions",document.getElementById("appSettingsAllowedTools").value=e.allowedTools||"",d.style.display=p.value==="allowedTools"?"":"none",p.onchange=()=>{d.style.display=p.value==="allowedTools"?"":"none"},document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked=e.codexDangerouslyBypassApprovals??!1,document.getElementById("appSettingsCodexNetworkAccess").checked=e.codexNetworkAccess??!1,document.getElementById("appSettingsCodexAnimations").checked=e.codexAnimationsEnabled??!1,document.getElementById("appSettingsCodexModel").value=e.codexModel??"",document.getElementById("appSettingsCodexReasoningEffort").value=e.codexReasoningEffort??"",this._applyCodexSettingsVisibility(),document.getElementById("appSettingsAgentTeams").checked=e.agentTeamsEnabled??!1,document.getElementById("appSettingsAgentSkill").checked=e.agentSkillEnabled??!1,document.getElementById("appSettingsWorkspaceHooks").checked=e.workspaceHooksEnabled!==!1,document.getElementById("appSettingsPinCliSessionName").checked=e.pinCliSessionName===!0,document.getElementById("appSettingsClaudeModel").value=e.claudeModel??"",document.getElementById("appSettingsOpusContext1m").checked=e.opusContext1mEnabled??!1,document.getElementById("appSettingsRemoteAutoReconnect").checked=e.remoteAutoReconnect??!0,document.getElementById("appSettingsThinkingEffort").value=e.thinkingEffort??"",document.getElementById("appSettingsClaudeAdvisor").value=e.claudeAdvisorModel??"";const r=e.nice||{};document.getElementById("appSettingsNiceEnabled").checked=r.enabled??!1,document.getElementById("appSettingsNiceValue").value=r.niceValue??10,this.loadModelConfigForSettings();const u=this.notificationManager?.preferences||{};document.getElementById("appSettingsNotifEnabled").checked=u.enabled??!0,document.getElementById("appSettingsNotifBrowser").checked=u.browserNotifications??!1,document.getElementById("appSettingsNotifAudio").checked=u.audioAlerts??!1,document.getElementById("appSettingsNotifStuckMins").value=Math.round((u.stuckThresholdMs||6e5)/6e4),document.getElementById("appSettingsNotifToastSecs").value=Math.round((this.notificationManager?.getToastDurationMs?.()??DEFAULT_TOAST_DURATION_MS)/1e3),document.getElementById("appSettingsNotifBrowserSecs").value=Math.round((u.browserAutoCloseMs??AUTO_CLOSE_NOTIFICATION_MS)/1e3),document.getElementById("appSettingsNotifCritical").checked=!u.muteCritical,document.getElementById("appSettingsNotifWarning").checked=!u.muteWarning,document.getElementById("appSettingsNotifInfo").checked=!u.muteInfo,document.getElementById("appSettingsPushEnabled").checked=!!this._pushSubscription,this._updatePushUI(!!this._pushSubscription);const h=u.eventTypes||{},f=h.permission_prompt||{};document.getElementById("eventPermissionEnabled").checked=f.enabled??!0,document.getElementById("eventPermissionBrowser").checked=f.browser??!0,document.getElementById("eventPermissionPush").checked=f.push??!1,document.getElementById("eventPermissionAudio").checked=f.audio??!0;const m=h.elicitation_dialog||{};document.getElementById("eventQuestionEnabled").checked=m.enabled??!0,document.getElementById("eventQuestionBrowser").checked=m.browser??!0,document.getElementById("eventQuestionPush").checked=m.push??!1,document.getElementById("eventQuestionAudio").checked=m.audio??!0;const g=h.idle_prompt||{};document.getElementById("eventIdleEnabled").checked=g.enabled??!0,document.getElementById("eventIdleBrowser").checked=g.browser??!0,document.getElementById("eventIdlePush").checked=g.push??!1,document.getElementById("eventIdleAudio").checked=g.audio??!1;const y=h.stop||{};document.getElementById("eventStopEnabled").checked=y.enabled??!1,document.getElementById("eventStopBrowser").checked=y.browser??!1,document.getElementById("eventStopPush").checked=y.push??!1,document.getElementById("eventStopAudio").checked=y.audio??!1;const S=h.respawn_cycle||{};document.getElementById("eventRespawnEnabled").checked=S.enabled??!0,document.getElementById("eventRespawnBrowser").checked=S.browser??!1,document.getElementById("eventRespawnPush").checked=S.push??!1,document.getElementById("eventRespawnAudio").checked=S.audio??!1;const b=h.ralph_complete||{};document.getElementById("eventRalphEnabled").checked=b.enabled??!0,document.getElementById("eventRalphBrowser").checked=b.browser??!0,document.getElementById("eventRalphPush").checked=b.push??!1,document.getElementById("eventRalphAudio").checked=b.audio??!0;const E=h.subagent_spawn||{};document.getElementById("eventSubagentEnabled").checked=E.enabled??!1,document.getElementById("eventSubagentBrowser").checked=E.browser??!1,document.getElementById("eventSubagentPush").checked=E.push??!1,document.getElementById("eventSubagentAudio").checked=E.audio??!1;const w=document.getElementById("notifPermissionStatus");if(w&&typeof Notification<"u"){const I=Notification.permission;w.textContent=I==="granted"?"\u2713":I==="denied"?"\u2717":"?",w.classList.remove("granted","denied"),I==="granted"?w.classList.add("granted"):I==="denied"&&w.classList.add("denied")}const v=VoiceInput._getDeepgramConfig();document.getElementById("voiceDeepgramKey").value=v.apiKey||"",document.getElementById("voiceLanguage").value=v.language||"en-US",document.getElementById("voiceKeyterms").value=v.keyterms||DEFAULT_VOICE_KEYTERMS,document.getElementById("voiceInsertMode").value=v.insertMode||"direct",document.getElementById("voiceProvider").value=v.provider||"auto",document.getElementById("appSettingsClaudeVoice").checked=e.claudeVoiceEnabled??!1;const B=document.getElementById("voiceDeepgramKey");B.type="password",document.getElementById("voiceKeyToggleBtn").textContent="Show",this._renderVoiceProviderStatus(),VoiceInput.refreshClaudeStatus().then(()=>this._renderVoiceProviderStatus()),this._initUpdatesSection(),this._initSettingsNav(),this._syncSettingsChips(),this._syncModelCards(),this._syncEffortSegment(),this._syncAdvisorSegment(),this.switchSettingsTab("settings-updates");const k=document.getElementById("appSettingsModal");k.classList.add("active"),this.activeFocusTrap=new FocusTrap(k),this.activeFocusTrap.activate(),this._captureSettingsFormBaseline()},_applyCodexSettingsVisibility(){const e=document.getElementById("appSettingsCodexGroup");e&&(e.style.display=window.__codemanCliAvailable?.codex===!0?"":"none")},switchSettingsTab(e){e==="settings-shortcuts"&&this.renderShortcutSettingsList?.();const t=document.getElementById("appSettingsDoc"),n=document.getElementById(e);if(t&&n&&typeof n.offsetTop=="number"){const s=document.getElementById("appSettingsJump"),i=s&&s.offsetParent?s.offsetHeight+16:6;t.scrollTop=Math.max(0,n.offsetTop-i)}this._setActiveSettingsSection(e)},_setActiveSettingsSection(e){const t=document.getElementById("appSettingsModal");if(!t||typeof t.querySelectorAll!="function")return;let n=null;t.querySelectorAll(".set-rail-item").forEach(a=>{const l=a.dataset.section===e;a.classList.toggle("active",l),l&&(n=a)}),t.querySelectorAll(".set-jump-row").forEach(a=>{a.classList.toggle("active",a.dataset.section===e)});const s=document.getElementById("appSettingsJump")?.querySelector(".set-jump-label");s&&n&&(s.textContent=n.textContent.trim());const i=document.getElementById("appSettingsJump")?.querySelector(".set-jump-ico"),o=n?.querySelector("svg");i&&o&&(i.innerHTML=o.innerHTML)},_initSettingsNav(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsDoc");if(!e||!t||typeof e.querySelectorAll!="function"||(this._buildModelCards(),this._buildEffortSegment(),this._buildAdvisorSegment(),this._buildSettingsJumpMenu(),e.dataset.navReady==="1"))return;e.dataset.navReady="1",e.querySelector(".set-rail-items")?.addEventListener("click",o=>{const a=o.target.closest?.(".set-rail-item");a?.dataset.section&&this.switchSettingsTab(a.dataset.section)}),document.getElementById("appSettingsJumpMenu")?.addEventListener("click",o=>{const a=o.target.closest?.(".set-jump-row");a?.dataset.section&&(this._toggleSettingsJump(!1),this.switchSettingsTab(a.dataset.section))}),document.getElementById("appSettingsJump")?.addEventListener("click",()=>this._toggleSettingsJump()),document.getElementById("appSettingsJumpVeil")?.addEventListener("click",()=>this._toggleSettingsJump(!1)),t.addEventListener("scroll",()=>{this._settingsSpyQueued||(this._settingsSpyQueued=!0,requestAnimationFrame(()=>{this._settingsSpyQueued=!1;const o=[...t.querySelectorAll(".set-section")].filter(l=>l.offsetParent!==null);if(!o.length)return;let a=o[0].id;for(const l of o)l.offsetTop-t.scrollTop<=140&&(a=l.id);this._setActiveSettingsSection(a)}))});const n=document.getElementById("appSettingsSearch");n?.addEventListener("input",()=>this._filterSettings(n.value)),e.querySelectorAll(".set-chip input").forEach(o=>{o.addEventListener("change",()=>this._syncSettingsChips())});const s=e.querySelector(".set-group-head-toggle"),i=s?.closest(".set-group-advanced");if(s&&i){const o=()=>{const a=i.classList.toggle("open");s.setAttribute("aria-expanded",a?"true":"false")};s.addEventListener("click",o),s.addEventListener("keydown",a=>{(a.key==="Enter"||a.key===" ")&&(a.preventDefault(),o())})}document.getElementById("appSettingsOpusContext1m")?.addEventListener("change",()=>this._applyModelSelection())},_buildSettingsJumpMenu(){const e=document.getElementById("appSettingsModal"),t=document.getElementById("appSettingsJumpMenu");!e||!t||(t.innerHTML="",e.querySelectorAll(".set-rail-item").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-jump-row",s.dataset.section=n.dataset.section,s.innerHTML=n.innerHTML;const i=document.getElementById(n.dataset.section),o=i?i.querySelectorAll("input, select").length:0;if(o){const a=document.createElement("span");a.className="set-jump-count",a.textContent=String(o),s.appendChild(a)}t.appendChild(s)}))},_toggleSettingsJump(e){const t=document.getElementById("appSettingsModal");if(!t)return;const n=e===void 0?!t.classList.contains("jump-open"):e;t.classList.toggle("jump-open",n),document.getElementById("appSettingsJump")?.setAttribute("aria-expanded",n?"true":"false")},_syncSettingsChips(){document.querySelectorAll("#appSettingsModal .set-chip, #sessionOptionsModal .set-chip").forEach(e=>{e.classList.toggle("is-on",!!e.querySelector("input")?.checked)}),this._syncLayoutPreview()},_syncLayoutPreview(){const e=document.getElementById("appSettingsModal");if(!e||typeof e.querySelectorAll!="function")return;const t={header:document.getElementById("appSettingsPreviewHeader"),panel:document.getElementById("appSettingsPreviewPanels"),toolbar:document.getElementById("appSettingsPreviewToolbar"),float:document.getElementById("appSettingsPreviewFloats")};if(!t.header)return;Object.values(t).forEach(o=>{o&&(o.innerHTML="")});const n=[...e.querySelectorAll(".set-chip[data-preview]")].filter(o=>o.querySelector("input")?.checked).sort((o,a)=>(Number(o.dataset.previewOrder)||0)-(Number(a.dataset.previewOrder)||0));let s=0;for(const o of n){const a=o.dataset.preview,l=t[a];if(!l)continue;const c=o.querySelectorAll("span"),p=(c[c.length-1]?.textContent||"").trim(),d=document.createElement("span");if(d.title=p,a==="header"){const r=o.dataset.previewText;d.className=r?"set-preview-chip":"set-preview-btn",r?d.textContent=r:this._appendPreviewIcon(d,o)}else{d.className=`set-preview-${a}`,this._appendPreviewIcon(d,o);const r=document.createElement("span");r.textContent=p,d.appendChild(r)}l.appendChild(d),s++}const i=document.getElementById("appSettingsPreviewEmpty");i&&(i.hidden=s>0)},_appendPreviewIcon(e,t){const n=t.querySelector(".set-chip-ico");if(!n)return;const s=n.cloneNode(!0);s.classList.remove("set-chip-ico"),s.classList.add("set-preview-ico"),e.appendChild(s)},_buildModelCards(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");!e||!t||t.dataset.built==="1"||!e.options||(t.innerHTML="",[...e.options].filter(n=>n.dataset.variant!=="1m").forEach(n=>{const s=document.createElement("button");s.type="button",s.className="set-modelcard",s.setAttribute("role","radio"),s.dataset.value=n.value,n.dataset.ctx==="1"&&(s.dataset.ctx="1");const i=document.createElement("span");i.className="set-mc-top";const o=document.createElement("span");o.className="set-mc-name",o.textContent=n.textContent,i.appendChild(o);const a=document.createElement("span");a.className="set-mc-dot",i.appendChild(a),s.appendChild(i);const l=document.createElement("span");if(l.className="set-mc-meta",l.textContent=n.dataset.meta||"",s.appendChild(l),n.dataset.ctx==="1"){const c=document.createElement("span");c.className="set-mc-ctx",c.textContent="1M capable",s.appendChild(c)}s.addEventListener("click",()=>{this._settingsModelBase=n.value,this._applyModelSelection()}),t.appendChild(s)}),t.dataset.built="1")},_syncModelCards(){const e=document.getElementById("appSettingsClaudeModel");if(!e)return;const t=e.value||"";if(this._settingsModelBase=t.endsWith("[1m]")?t.slice(0,-4):t,t.endsWith("[1m]")){const n=document.getElementById("appSettingsOpusContext1m");n&&(n.checked=!0)}this._applyModelSelection()},_applyModelSelection(){const e=document.getElementById("appSettingsClaudeModel"),t=document.getElementById("appSettingsModelCards");if(!e||!t)return;const n=this._settingsModelBase||"";let s=!1;t.querySelectorAll(".set-modelcard").forEach(c=>{const p=c.dataset.value===n;c.classList.toggle("selected",p),c.setAttribute("aria-checked",p?"true":"false"),p&&(s=c.dataset.ctx==="1")});const i=!!document.getElementById("appSettingsOpusContext1m")?.checked;e.value=n&&s&&i?`${n}[1m]`:n;const o=document.getElementById("appSettingsContextRow"),a=document.getElementById("appSettingsContextDesc"),l=!!n&&!s;o?.classList.toggle("set-row-disabled",l),a&&(a.textContent=l?"The selected model has no 1M variant.":n?"Available for Fable 5.1, Fable 5, Opus 5.5, Opus and Opus 4.6.":"With no model pinned, this starts new sessions on Opus with a 1M window.")},_buildEffortSegment(){this._buildSelectSegment("appSettingsThinkingEffort","appSettingsEffortSegment")},_syncEffortSegment(){this._syncSelectSegment("appSettingsThinkingEffort","appSettingsEffortSegment")},_buildAdvisorSegment(){this._buildSelectSegment("appSettingsClaudeAdvisor","appSettingsAdvisorSegment")},_syncAdvisorSegment(){this._syncSelectSegment("appSettingsClaudeAdvisor","appSettingsAdvisorSegment")},_buildSelectSegment(e,t){const n=document.getElementById(e),s=document.getElementById(t);!n||!s||s.dataset.built==="1"||!n.options||(s.innerHTML="",[...n.options].forEach(i=>{const o=document.createElement("button");o.type="button",o.setAttribute("role","radio"),o.dataset.value=i.value,o.textContent=i.textContent,o.addEventListener("click",()=>{n.value=i.value,this._syncSelectSegment(e,t)}),s.appendChild(o)}),s.dataset.built="1")},_syncSelectSegment(e,t){const n=document.getElementById(e),s=document.getElementById(t);!n||!s||s.querySelectorAll("button").forEach(i=>{const o=i.dataset.value===(n.value||"");i.classList.toggle("selected",o),i.setAttribute("aria-checked",o?"true":"false")})},_filterSettings(e){const t=document.getElementById("appSettingsDoc");if(!t)return;const n=(e||"").trim().toLowerCase(),s=".set-row, .set-chip, .set-modelgrid, .set-minigrid, .event-type-grid, #appSettingsShortcutsList",i=[...t.querySelectorAll(s)];let o=!1;i.forEach(l=>{if(!n){l.classList.remove("set-hit-hidden");return}const p=`${l.dataset?.search||""} ${l.textContent||""}`.toLowerCase().includes(n);l.classList.toggle("set-hit-hidden",!p),p&&(o=!0)}),t.querySelectorAll(".set-chips").forEach(l=>{const c=[...l.querySelectorAll(".set-chip")].some(p=>!p.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-group").forEach(l=>{const c=[...l.querySelectorAll(s)].some(p=>!p.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c),n&&c&&l.classList.add("open")}),t.querySelectorAll(".set-section").forEach(l=>{const c=[...l.querySelectorAll(".set-group")].some(p=>!p.classList.contains("set-hit-hidden"));l.classList.toggle("set-hit-hidden",!!n&&!c)}),t.querySelectorAll(".set-preview").forEach(l=>l.classList.toggle("set-hit-hidden",!!n));const a=document.getElementById("appSettingsSearchEmpty");a&&(a.hidden=!n||o),n||t.querySelectorAll(".set-group-advanced").forEach(l=>l.classList.remove("open"))},closeAppSettings(){this._toggleSettingsJump(!1),document.getElementById("appSettingsModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},_updatePhaseText(e){return{queued:"Queued\u2026",preparing:"Preparing\u2026",stashing:"Stashing local changes\u2026",fetching:"Fetching release\u2026",checkout:"Checking out release\u2026",installing:"Installing dependencies\u2026",building:"Building\u2026",restarting:"Restarting Codeman\u2026"}[e]||e},_initUpdatesSection(){const e=this.$("updateCurrentVersion");e&&(e.textContent=(this.$("versionDisplay")?.textContent||"").trim()||"\u2014");for(const t of["updateResult","updateActionRow","updateNotes","updateProgress"]){const n=this.$(t);n&&(n.style.display="none")}this._updateCheck=null},keyTesterEvent(e){const t=document.getElementById("keyTesterLog");if(!t)return;const n=["ctrlKey","shiftKey","altKey","metaKey"].filter(o=>e[o]).map(o=>o.replace("Key","")),s=`${e.type.padEnd(8)} key=${JSON.stringify(e.key)} code=${e.code||"-"} mods=${n.join("+")||"none"}`+(e.type==="keypress"?` charCode=${e.charCode}`:"")+(e.repeat?" (repeat)":""),i=(t.textContent?t.textContent.split(`
2
2
  `):[]).concat(s);t.textContent=i.slice(-14).join(`
3
3
  `),t.style.display="block"},applyMcpSyncVisibility(){const e=document.getElementById("appSettingsMcpSync")?.checked??!1,t=document.getElementById("mcpSyncActionRow");t&&(t.style.display=e?"":"none");const n=this.$("mcpSyncResult");!e&&n&&(n.style.display="none",n.innerHTML=""),this._applyMcpSyncAdminGate()},_applyMcpSyncAdminGate(){const e=document.getElementById("mcpSyncGroup");if(!e)return;const t=window.__codemanUser||{};e.style.display=t.multiUser&&t.role!=="admin"?"none":""},_applyDoctorAdminGate(){const e=window.__codemanUser||{};for(const t of["doctorGroup","configBackupGroup"]){const n=document.getElementById(t);n&&(n.style.display=e.multiUser&&e.role!=="admin"?"none":"")}},_configBackupRetentionPayload(e){const t=document.getElementById("appSettingsConfigBackupKeepCount"),n=document.getElementById("appSettingsConfigBackupKeepDays"),s={};if((e.configBackupKeepCount!==void 0||t.dataset.touched)&&(s.configBackupKeepCount=Math.min(500,Math.max(2,parseInt(t.value,10)||20))),e.configBackupKeepDays!==void 0||n.dataset.touched){const i=parseInt(n.value,10);s.configBackupKeepDays=Math.min(3650,Math.max(0,Number.isNaN(i)?30:i))}return s},async loadConfigBackups(){const e=this.$("configBackupList");if(!e)return;const t=await this._api("/api/config-backups");let n=null;try{n=t?await t.json():null}catch{}if(!t||!t.ok||!n||n.success===!1){e.textContent=n?.error||"Backups are not available here (admin only in multi-user mode).";return}const s=n.data,i=this.$("appSettingsConfigBackupDir")||document.getElementById("appSettingsConfigBackupDir");i&&s.defaultDir&&(i.placeholder=`default: ${s.defaultDir}`);const o=this.loadAppSettingsFromStorage(),a=document.getElementById("appSettingsConfigBackupKeepCount"),l=document.getElementById("appSettingsConfigBackupKeepDays");if(a&&o.configBackupKeepCount===void 0&&s.settings&&(a.value=s.settings.keepCount),l&&o.configBackupKeepDays===void 0&&s.settings&&(l.value=s.settings.keepDays),!s.backups.length){e.innerHTML=`No backups yet in <code>${escapeHtml(s.dir)}</code>.`;return}const c=u=>u<1024?`${u} B`:`${(u/1024).toFixed(1)} KB`;this._configBackups=s.backups;const p=s.backups.map((u,h)=>{const f=new Date(u.createdAt).toLocaleString(),m=u.files.map(b=>escapeHtml(b.name)).join(", "),g=escapeHtml(u.id),y=s.backups[h+1],S=y?`<button class="btn-toolbar btn-sm" onclick="app.showConfigDiff('${escapeHtml(y.id)}','${g}')">Changes in this backup</button>`:"";return`<li><b>${escapeHtml(f)}</b> <span class="set-row-desc">(${escapeHtml(u.reason)}, ${c(u.bytes)})</span>
4
4
  <button class="btn-toolbar btn-sm" onclick="app.restoreConfigBackup('${g}')">Restore</button>
@@ -9,7 +9,7 @@
9
9
 
10
10
  Files replaced: ${t.join(", ")}`:"",i=n.length?`
11
11
 
12
- This rolls back credentials and accounts (${n.join(", ")}): passwords, disabled accounts and keys return to how they were then.`:"";if(!confirm(`Restore this backup? Its files replace the current ones (a copy of the current files is made first). Some files only take effect after a restart.${s}${i}`))return;const o=await this._apiPost(`/api/config-backups/${encodeURIComponent(e)}/restore`,{});let a=null;try{a=o?await o.json():null}catch{}const l=o&&o.ok&&a&&a.success!==!1,c=a?.data;if(!l&&!Array.isArray(c?.restored)){this.showToast(a?.error||"Restore failed","error");return}const p=c.restartRequired?.length?` Restart Codeman to apply: ${c.restartRequired.join(", ")}.`:"";c.restored.includes("settings.json")&&(await this.loadAppSettingsFromServer(),this.openAppSettings()),l?this.showToast(`Restored ${c.restored.length} file${c.restored.length===1?"":"s"}.${p}`,"success"):this.showToast(`${a.error||"Restore failed partway"}.${p}`,"error"),this.loadConfigBackups()},async mcpSync(e){const t=this.$("mcpSyncResult"),n=(d,r="")=>{t&&(t.style.display="block",t.innerHTML=d,t.dataset.hint=r)};if(!this._mcpSyncSavedOn){n("Apply or Save settings to turn MCP sync on first, then preview or sync.","save-first");return}if(e&&!confirm("Add missing MCP servers to every installed, enabled CLI's config file (and GitHub Copilot CLI's, when it is installed)? Env values and headers on those servers are copied too."))return;n("Working\u2026");const s=e?await this._apiPost("/api/mcp-sync",{}):await this._api("/api/mcp-sync");let i=null;try{i=s?await s.json():null}catch{}if(!s||!s.ok||!i||i.success===!1){n(escapeHtml(i?.error||"MCP sync failed."));return}const o=i.data,a=o.targets.map(d=>{if(d.status==="absent")return`<li><b>${escapeHtml(d.label)}</b>: not installed, skipped</li>`;if(d.status==="skipped")return`<li><b>${escapeHtml(d.label)}</b>: not touched (${escapeHtml(d.error||"config location unknown")})</li>`;if(d.status==="unreadable")return`<li><b>${escapeHtml(d.label)}</b>: not touched, file can't be read safely (${escapeHtml(d.error||"unreadable")})</li>`;if(d.status==="failed")return`<li><b>${escapeHtml(d.label)}</b>: failed (${escapeHtml(d.error||"error")}); the file may be unchanged</li>`;const r=o.applied?"added":"would add",u=[d.added.length?`${r} ${d.added.map(escapeHtml).join(", ")}`:"up to date"];d.skipped.length&&u.push(`can't express ${d.skipped.map(escapeHtml).join(", ")}`);const h=`${d.servers.length} server${d.servers.length===1?"":"s"}`;return`<li><b>${escapeHtml(d.label)}</b> (${h}): ${u.join("; ")}</li>`}),l=o.conflicts.length?`<p>Defined differently across CLIs (each existing definition is kept; the first CLI's is copied where the name is missing): ${o.conflicts.map(escapeHtml).join(", ")}</p>`:"",c=o.disabled?.length?`<p>Switched off in their own CLI, so not copied: ${o.disabled.map(escapeHtml).join(", ")}</p>`:"",p=o.unsupported?.length?`<p>No MCP config support for: ${o.unsupported.map(escapeHtml).join(", ")}</p>`:"";n(`<ul>${a.join("")}</ul>${l}${c}${p}`)},_webhookSay(e,t=!1){const n=document.getElementById("webhookResult");n&&(n.textContent=e,n.style.display=e?"block":"none",n.style.color=t?"var(--danger, #e5534b)":"")},async loadWebhook(){const e=document.getElementById("webhookGroup");if(!e)return;const t=await this._api("/api/webhook");if(!t||!t.ok){this._webhookLoaded=null,e.style.display="none";return}let n=null;try{n=await t.json()}catch{}if(!n||n.success===!1){this._webhookLoaded=null,e.style.display="none";return}const s=n.data;e.style.display="",document.getElementById("webhookEnabled").checked=s.enabled===!0,document.getElementById("webhookKind").value=s.kind,document.getElementById("webhookScope").value=s.scope;const i=document.getElementById("webhookUrl");i.value="",i.placeholder=s.hasUrl?"Saved. Paste a new URL to replace it":"https://ntfy.sh/your-topic",document.getElementById("webhookUrlHint").textContent=s.hasUrl?`Saved: ${s.urlMasked}`:"Nothing saved yet.";const o=document.getElementById("webhookClearBtn");if(o&&(o.style.display=s.hasUrl?"":"none"),this._webhookLoaded={enabled:document.getElementById("webhookEnabled").checked,kind:document.getElementById("webhookKind").value,scope:document.getElementById("webhookScope").value},s.lastResult){const a=new Date(s.lastResult.at).toLocaleString();this._webhookSay(s.lastResult.ok?`Last delivery succeeded (${a}).`:`Last delivery failed (${a}): ${s.lastResult.error}`,!s.lastResult.ok)}else this._webhookSay("")},_webhookPending(){const e=document.getElementById("webhookGroup"),t=this._webhookLoaded;return!e||e.style.display==="none"||!t?!1:document.getElementById("webhookUrl").value.trim()!==""||document.getElementById("webhookEnabled").checked!==t.enabled||document.getElementById("webhookKind").value!==t.kind||document.getElementById("webhookScope").value!==t.scope},async saveWebhook(){const e={enabled:document.getElementById("webhookEnabled").checked,kind:document.getElementById("webhookKind").value,scope:document.getElementById("webhookScope").value},t=document.getElementById("webhookUrl").value.trim();t&&(e.url=t);const n=await this._api("/api/webhook",{method:"PUT",body:e});let s=null;try{s=n?await n.json():null}catch{}if(!n||!n.ok||!s||s.success===!1){const i=s?.error||"Could not save the webhook.";return this._webhookSay(i,!0),i}return await this.loadWebhook(),this._webhookSay("Saved."),""},async clearWebhook(){if(!confirm("Remove the saved webhook URL from the server? Webhook alerts stop until you save a new one."))return;const e=await this._api("/api/webhook",{method:"PUT",body:{url:"",enabled:!1}});let t=null;try{t=e?await e.json():null}catch{}if(!e||!e.ok||!t||t.success===!1){this._webhookSay(t?.error||"Could not remove the webhook URL.",!0);return}await this.loadWebhook(),this._webhookSay("Webhook URL removed.")},async testWebhook(){const e=document.getElementById("webhookTestBtn");e&&(e.disabled=!0);try{if(this._webhookPending()&&await this.saveWebhook())return;this._webhookSay("Sending\u2026");const t=await this._apiPost("/api/webhook/test",{});let n=null;try{n=t?await t.json():null}catch{}if(!t||!t.ok||!n||n.success===!1){this._webhookSay(n?.error||"Could not send the test.",!0);return}const s=n.data;this._webhookSay(s.ok?"Test sent. Check your phone or channel.":`Delivery failed: ${s.error}`,!s.ok)}finally{e&&(e.disabled=!1)}},_discordSay(e,t=!1){const n=document.getElementById("discordResult");n&&(n.textContent=e,n.style.display=e?"block":"none",n.style.color=t?"var(--danger, #e5534b)":"")},async loadDiscord(){const e=document.getElementById("discordGroup");if(!e)return;const t=await this._api("/api/discord");let n=null;try{n=t&&t.ok?await t.json():null}catch{}if(!n||n.success===!1){e.style.display="none";return}const s=n.data;e.style.display="",document.getElementById("discordEnabled").checked=s.enabled===!0;const i=document.getElementById("discordToken");i.value="",i.placeholder=s.hasToken?"Saved. Paste a new token to replace it":"Bot token from the Developer Portal",document.getElementById("discordTokenHint").textContent=s.hasToken?`Saved: ${s.tokenMasked}`:"Nothing saved yet.",document.getElementById("discordGuildId").value=s.guildId||"",document.getElementById("discordParentChannelId").value=s.parentChannelId||"",document.getElementById("discordAllowedUserIds").value=(s.allowedUserIds||[]).join(", "),document.getElementById("discordThreadCreation").value=s.threadCreation,document.getElementById("discordOnSessionDelete").value=s.onSessionDelete,document.getElementById("discordStaleThreads").value=s.staleThreads||"keep",document.getElementById("discordPingMode").value=s.pingMode,document.getElementById("discordScope").value=s.scope,document.getElementById("discordStartupHint").checked=s.startupHint!==!1;const o=document.getElementById("discordClearBtn");o&&(o.style.display=s.hasToken?"":"none");let l={off:"Not connected (disabled).",connecting:"Connecting to Discord\u2026",connected:"Connected: replies are being read.",error:`Not connected: ${s.gatewayError||"error"}`}[s.gateway]||"";if(s.lastResult){const c=new Date(s.lastResult.at).toLocaleString();l+=s.lastResult.ok?` Last post succeeded (${c}).`:` Last post failed (${c}): ${s.lastResult.error}`}this._discordSay(l.trim(),s.gateway==="error"||s.lastResult&&!s.lastResult.ok)},async saveDiscord(){const e=document.getElementById("discordAllowedUserIds").value.split(/[\s,]+/).filter(Boolean),t={enabled:document.getElementById("discordEnabled").checked,guildId:document.getElementById("discordGuildId").value.trim(),parentChannelId:document.getElementById("discordParentChannelId").value.trim(),allowedUserIds:e,threadCreation:document.getElementById("discordThreadCreation").value,onSessionDelete:document.getElementById("discordOnSessionDelete").value,staleThreads:document.getElementById("discordStaleThreads").value,pingMode:document.getElementById("discordPingMode").value,scope:document.getElementById("discordScope").value,startupHint:document.getElementById("discordStartupHint").checked},n=document.getElementById("discordToken").value.trim();n&&(t.token=n);const s=await this._api("/api/discord",{method:"PUT",body:t});let i=null;try{i=s?await s.json():null}catch{}if(!s||!s.ok||!i||i.success===!1){const o=i?.error||"Could not save the Discord settings.";return this._discordSay(o,!0),o}return await this.loadDiscord(),""},async clearDiscord(){if(!confirm("Remove the saved bot token from the server? The Discord bridge stops until you save a new one."))return;const e=await this._api("/api/discord",{method:"PUT",body:{token:"",enabled:!1}});let t=null;try{t=e?await e.json():null}catch{}if(!e||!e.ok||!t||t.success===!1){this._discordSay(t?.error||"Could not remove the token.",!0);return}await this.loadDiscord()},async testDiscord(){const e=document.getElementById("discordTestBtn");e&&(e.disabled=!0);try{if(await this.saveDiscord())return;this._discordSay("Sending\u2026");const t=await this._apiPost("/api/discord/test",{});let n=null;try{n=t?await t.json():null}catch{}if(!t||!t.ok||!n||n.success===!1){this._discordSay(n?.error||"Could not send the test.",!0);return}const s=n.data;this._discordSay(s.ok?"Test sent. Check the channel.":`Delivery failed: ${s.error}`,!s.ok)}finally{e&&(e.disabled=!1)}},async runDoctor(){const e=document.getElementById("doctorResult"),t=document.getElementById("doctorRunBtn");if(!e)return;const n=s=>{e.replaceChildren(document.createTextNode(s)),e.style.display="block"};t&&(t.disabled=!0),n("Checking\u2026");try{const s=await this._api("/api/doctor");let i=null;try{i=s?await s.json():null}catch{}if(!s||!s.ok||!i||i.success===!1){n(i?.error||"The check failed.");return}const{tools:o,summary:a,platform:l}=i.data,c={ok:"\u2713",missing:"\u2717",outdated:"!",error:"!",skipped:"\u2013"},p=document.createElement("ul");p.style.margin="0",p.style.paddingLeft="1.2em";for(const r of o){const u=document.createElement("li"),h=document.createElement("b"),f=r.status==="missing"&&!r.required?"\u25CB":c[r.status]||"?";h.textContent=`${f} ${r.label}`,u.append(h);const m=[r.status];if(r.version&&m.push(r.version),r.status!=="ok"&&r.status!=="skipped"&&m.push(r.required?"required":"optional"),r.reason&&m.push(r.reason),u.append(document.createTextNode(` ${m.join(" \xB7 ")}`)),r.path){const g=document.createElement("div");g.className="mono",g.textContent=r.path,u.append(g)}if(r.status==="missing"&&r.installHint){const g=document.createElement("div");g.textContent=`Install: ${r.installHint}`,u.append(g)}p.append(u)}const d=document.createElement("p");d.textContent=`${a.ok} ok \xB7 ${a.requiredMissing} required missing \xB7 ${a.optionalMissing} optional missing (${l.environment})`,e.replaceChildren(d,p),e.style.display="block"}finally{t&&(t.disabled=!1)}},_setUpdateResult(e){const t=this.$("updateResult");t&&(t.style.display="block",t.innerHTML=e)},_setUpdateProgress(e){const t=this.$("updateProgress");t&&(t.style.display="block",t.innerHTML=e)},async checkForUpdate(){const e=this.$("updateCheckBtn");e&&(e.disabled=!0,e.textContent="Checking\u2026");const t=await this._apiJson("/api/system/update/check");e&&(e.disabled=!1,e.textContent="Check now");const n=this.$("updateActionRow"),s=this.$("updateNotes");if(n&&(n.style.display="none"),s&&(s.style.display="none"),!t){this._setUpdateResult("Could not check for updates. Try again later.");return}this._updateCheck=t;const i=this.$("updateCurrentVersion");if(i&&t.currentVersion&&(i.textContent=`v${t.currentVersion}`),t.installKind&&t.installKind!=="git"&&t.installKind!=="docker-compose"){const a=t.supervisor==="docker-compose"?"Update from the Docker host with <code>docker/Start-Codeman.sh</code>.":"Update with <code>npm i -g aicodeman@latest</code>.";this._setUpdateResult(`This install can't update itself (${escapeHtml(t.installKind)}). ${a}`);return}if(t.selfUpdateEnabled===!1){this._setUpdateResult("In-app updates are disabled on this server (CODEMAN_DISABLE_SELF_UPDATE=1).");return}if(t.error&&!t.updateAvailable){this._setUpdateResult(escapeHtml(t.error));return}const o=t.environment?.blockers||[];if(t.updateAvailable&&o.length>0){const a=o.map(l=>{const c=l.details?.length?`<br><code>${escapeHtml(l.details.join(" "))}</code>`:"";return`<li>${escapeHtml(l.message)}${c}</li>`}).join("");this._setUpdateResult(`<strong>v${escapeHtml(t.latestVersion||"")}</strong> needs a rebuild on the Docker host (current v${escapeHtml(t.currentVersion||"")}):<ul>${a}</ul>Run <code>${escapeHtml(t.environment?.hostCommand||"docker/Start-Codeman.sh")}</code> there to apply it.`),s&&t.notes&&(s.style.display="block",s.textContent=t.notes);return}if(t.updateAvailable&&t.latestVersion){this._setUpdateResult(`Update available: <strong>v${escapeHtml(t.latestVersion)}</strong> &nbsp;(current v${escapeHtml(t.currentVersion||"")})`);const a=this.$("updateActionLabel");a&&(a.textContent=`Update to v${t.latestVersion}`),n&&(n.style.display="flex");const l=this.$("updateNowBtn");l&&(l.disabled=!1,l.textContent="Update now"),s&&t.notes&&(s.style.display="block",s.textContent=t.notes)}else this._setUpdateResult(`You're up to date (v${escapeHtml(t.currentVersion||"")}).`)},async startSelfUpdate(){const e=this._updateCheck?.latestVersion?`v${this._updateCheck.latestVersion}`:"the latest release";if(!confirm(`Update Codeman to ${e}? The server will restart and this page will reload.`))return;const t=this.$("updateNowBtn");t&&(t.disabled=!0,t.textContent="Starting\u2026");const n=await this._apiPost("/api/system/update",{});if(!n||!n.ok){let o="Failed to start the update.";try{const a=await n.json();typeof a?.error=="string"&&a.error&&(o=a.error)}catch{}this._setUpdateProgress(`<span style="color:var(--danger,#e5534b)">${escapeHtml(o)}</span>`),t&&(t.disabled=!1,t.textContent="Update now");return}const s=this.$("updateActionRow");s&&(s.style.display="none");const i=this.$("updateNotes");i&&(i.style.display="none"),this._setUpdateProgress("Starting update\u2026"),this._pollUpdateStatus()},_stopUpdatePolling(){this._updatePollTimer&&(clearInterval(this._updatePollTimer),this._updatePollTimer=null)},_pollUpdateStatus(){this._stopUpdatePolling();const e=new Set(["completed","completed-needs-manual-restart","failed","idle"]),t=async()=>{let n=null;try{const s=await fetch("/api/system/update/status");if(s.ok){const i=await s.json();n=i&&i.success===!0?i.data:i}}catch{}if(!n){this._setUpdateProgress("\u21BB Restarting Codeman\u2026");return}if(!e.has(n.phase)){const s=n.message&&n.message.trim()?n.message.trim():this._updatePhaseText(n.phase);let i="";n.startedAt&&(i=` <span style="color:var(--text-secondary)">\xB7 ${Math.max(0,Math.round((Date.now()-n.startedAt)/1e3))}s</span>`),this._setUpdateProgress(`<span class="tunnel-spinner"></span> ${escapeHtml(s)}${i}`);return}if(this._stopUpdatePolling(),n.phase==="completed"){let s=`<span style="color:var(--success,#3fb950)">\u2713 Updated to v${escapeHtml(n.toVersion||"")}. Reloading\u2026</span>`;n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code> \u2014 run <code>git stash pop</code> to restore.</span>`),this._setUpdateProgress(s),setTimeout(()=>location.reload(),2500)}else if(n.phase==="completed-needs-manual-restart")this._setUpdateProgress(`Update staged. Restart Codeman to apply:<br><code>${escapeHtml(n.manualRestartCommand||"restart codeman web")}</code>`);else if(n.phase==="failed"){let s=`<span style="color:var(--danger,#e5534b)">\u2717 ${escapeHtml(n.message||"Update failed")}.</span>`;n.error&&(s+=`<br><span style="color:var(--text-secondary)">${escapeHtml(n.error)}</span>`),s+='<br><span style="color:var(--text-secondary)">The previous version is still running.</span>',n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code>.</span>`),this._setUpdateProgress(s);const i=this.$("updateNowBtn"),o=this.$("updateActionRow");i&&(i.disabled=!1,i.textContent="Try again"),o&&(o.style.display="flex")}};t(),this._updatePollTimer=setInterval(t,1500)},isCliAvailable(e){const t=window.__codemanCliAvailable;return!t||typeof t!="object"?!0:t[e]!==!1},renderWelcomeCliActions(){const e=document.getElementById("welcomeCliActions");if(!e)return;const n=(Array.isArray(window.__codemanCliCatalog)?window.__codemanCliCatalog:[]).filter(u=>u.enabled&&this.isCliAvailable(u.id)),s=n.find(u=>u.kind!=="shell")||n[0],i=u=>`Run ${u.kind==="shell"?"Shell":u.label}`,o=u=>{const h=document.createElement("span");return h.className=`run-mode-dot ${u.id}`,h.setAttribute("aria-hidden","true"),h},a=u=>()=>{this.setRunMode(u.id),this.run()};if(e.replaceChildren(),!s)return;const l=document.createElement("button");l.type="button",l.className="welcome-primary",l.dataset.mode=s.id;const c=document.createElement("span");c.className="welcome-primary-logo",c.appendChild(o(s)),l.appendChild(c);const p=document.createElement("span");p.className="welcome-label",p.textContent=i(s),l.appendChild(p),l.onclick=a(s),e.appendChild(l);const d=n.filter(u=>u!==s);if(!d.length)return;const r=document.createElement("div");r.className="welcome-chips",r.setAttribute("role","group"),r.setAttribute("aria-label","More tools");for(const u of d){const h=document.createElement("button");h.type="button",h.className="welcome-chip",h.dataset.mode=u.id,h.title=i(u),h.setAttribute("aria-label",i(u)),h.appendChild(o(u));const f=document.createElement("span");f.className="welcome-label",f.textContent=u.kind==="shell"?"Shell":u.label,h.appendChild(f),h.onclick=a(u),r.appendChild(h)}e.appendChild(r)},applyWelcomeCliVisibility(){this.renderWelcomeCliActions();const e=document.getElementById("welcomeTunnelBtn");e&&(e.style.display=this.isCliAvailable("cloudflared")?"flex":"none")},async loadTunnelStatus(){try{const t=await(await fetch("/api/tunnel/status")).json(),n=t?.success===!0?t.data:t,s=n.running&&n.url;this._tunnelUrl=s?n.url:null,this._updateTunnelUrlDisplay(this._tunnelUrl),this._updateWelcomeTunnelBtn(!!s,this._tunnelUrl),this._updateTunnelIndicator(!!s)}catch{this._tunnelUrl=null,this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1)}},_updateTunnelUrlDisplay(e){const t=document.getElementById("tunnelUrlRow"),n=document.getElementById("tunnelUrlDisplay");!t||!n||(e?(t.style.display="",n.textContent=e,n.onclick=()=>{navigator.clipboard.writeText(e).then(()=>{this.showToast("Tunnel URL copied","success")})}):(t.style.display="none",n.textContent="",n.onclick=null))},showTunnelQR(){this.closeTunnelQR();const e=document.createElement("div");e.id="tunnelQrOverlay",e.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:5000;display:flex;align-items:center;justify-content:center;cursor:pointer",e.onclick=n=>{n.target===e&&this.closeTunnelQR()};const t=document.createElement("div");t.style.cssText="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px;text-align:center;max-width:340px;width:90vw;box-shadow:var(--shadow-lg);cursor:default",t.innerHTML=`
12
+ This rolls back credentials and accounts (${n.join(", ")}): passwords, disabled accounts and keys return to how they were then.`:"";if(!confirm(`Restore this backup? Its files replace the current ones (a copy of the current files is made first). Some files only take effect after a restart.${s}${i}`))return;const o=await this._apiPost(`/api/config-backups/${encodeURIComponent(e)}/restore`,{});let a=null;try{a=o?await o.json():null}catch{}const l=o&&o.ok&&a&&a.success!==!1,c=a?.data;if(!l&&!Array.isArray(c?.restored)){this.showToast(a?.error||"Restore failed","error");return}const p=c.restartRequired?.length?` Restart Codeman to apply: ${c.restartRequired.join(", ")}.`:"";c.restored.includes("settings.json")&&(await this.loadAppSettingsFromServer(),this.openAppSettings()),l?this.showToast(`Restored ${c.restored.length} file${c.restored.length===1?"":"s"}.${p}`,"success"):this.showToast(`${a.error||"Restore failed partway"}.${p}`,"error"),this.loadConfigBackups()},async mcpSync(e){const t=this.$("mcpSyncResult"),n=(d,r="")=>{t&&(t.style.display="block",t.innerHTML=d,t.dataset.hint=r)};if(!this._mcpSyncSavedOn){n("Apply or Save settings to turn MCP sync on first, then preview or sync.","save-first");return}if(e&&!confirm("Add missing MCP servers to every installed, enabled CLI's config file (and GitHub Copilot CLI's, when it is installed)? Env values and headers on those servers are copied too."))return;n("Working\u2026");const s=e?await this._apiPost("/api/mcp-sync",{}):await this._api("/api/mcp-sync");let i=null;try{i=s?await s.json():null}catch{}if(!s||!s.ok||!i||i.success===!1){n(escapeHtml(i?.error||"MCP sync failed."));return}const o=i.data,a=o.targets.map(d=>{if(d.status==="absent")return`<li><b>${escapeHtml(d.label)}</b>: not installed, skipped</li>`;if(d.status==="skipped")return`<li><b>${escapeHtml(d.label)}</b>: not touched (${escapeHtml(d.error||"config location unknown")})</li>`;if(d.status==="unreadable")return`<li><b>${escapeHtml(d.label)}</b>: not touched, file can't be read safely (${escapeHtml(d.error||"unreadable")})</li>`;if(d.status==="failed")return`<li><b>${escapeHtml(d.label)}</b>: failed (${escapeHtml(d.error||"error")}); the file may be unchanged</li>`;const r=o.applied?"added":"would add",u=[d.added.length?`${r} ${d.added.map(escapeHtml).join(", ")}`:"up to date"];d.skipped.length&&u.push(`can't express ${d.skipped.map(escapeHtml).join(", ")}`);const h=`${d.servers.length} server${d.servers.length===1?"":"s"}`;return`<li><b>${escapeHtml(d.label)}</b> (${h}): ${u.join("; ")}</li>`}),l=o.conflicts.length?`<p>Defined differently across CLIs (each existing definition is kept; the first CLI's is copied where the name is missing): ${o.conflicts.map(escapeHtml).join(", ")}</p>`:"",c=o.disabled?.length?`<p>Switched off in their own CLI, so not copied: ${o.disabled.map(escapeHtml).join(", ")}</p>`:"",p=o.unsupported?.length?`<p>No MCP config support for: ${o.unsupported.map(escapeHtml).join(", ")}</p>`:"";n(`<ul>${a.join("")}</ul>${l}${c}${p}`)},_webhookSay(e,t=!1){const n=document.getElementById("webhookResult");n&&(n.textContent=e,n.style.display=e?"block":"none",n.style.color=t?"var(--danger, #e5534b)":"")},async loadWebhook(){const e=document.getElementById("webhookGroup");if(!e)return;const t=await this._api("/api/webhook");if(!t||!t.ok){this._webhookLoaded=null,e.style.display="none";return}let n=null;try{n=await t.json()}catch{}if(!n||n.success===!1){this._webhookLoaded=null,e.style.display="none";return}const s=n.data;e.style.display="",document.getElementById("webhookEnabled").checked=s.enabled===!0,document.getElementById("webhookKind").value=s.kind,document.getElementById("webhookScope").value=s.scope;const i=document.getElementById("webhookUrl");i.value="",i.placeholder=s.hasUrl?"Saved. Paste a new URL to replace it":"https://ntfy.sh/your-topic",document.getElementById("webhookUrlHint").textContent=s.hasUrl?`Saved: ${s.urlMasked}`:"Nothing saved yet.";const o=document.getElementById("webhookClearBtn");if(o&&(o.style.display=s.hasUrl?"":"none"),this._webhookLoaded={enabled:document.getElementById("webhookEnabled").checked,kind:document.getElementById("webhookKind").value,scope:document.getElementById("webhookScope").value},s.lastResult){const a=new Date(s.lastResult.at).toLocaleString();this._webhookSay(s.lastResult.ok?`Last delivery succeeded (${a}).`:`Last delivery failed (${a}): ${s.lastResult.error}`,!s.lastResult.ok)}else this._webhookSay("")},_webhookPending(){const e=document.getElementById("webhookGroup"),t=this._webhookLoaded;return!e||e.style.display==="none"||!t?!1:document.getElementById("webhookUrl").value.trim()!==""||document.getElementById("webhookEnabled").checked!==t.enabled||document.getElementById("webhookKind").value!==t.kind||document.getElementById("webhookScope").value!==t.scope},async saveWebhook(){const e={enabled:document.getElementById("webhookEnabled").checked,kind:document.getElementById("webhookKind").value,scope:document.getElementById("webhookScope").value},t=document.getElementById("webhookUrl").value.trim();t&&(e.url=t);const n=await this._api("/api/webhook",{method:"PUT",body:e});let s=null;try{s=n?await n.json():null}catch{}if(!n||!n.ok||!s||s.success===!1){const i=s?.error||"Could not save the webhook.";return this._webhookSay(i,!0),i}return await this.loadWebhook(),this._webhookSay("Saved."),""},async clearWebhook(){if(!confirm("Remove the saved webhook URL from the server? Webhook alerts stop until you save a new one."))return;const e=await this._api("/api/webhook",{method:"PUT",body:{url:"",enabled:!1}});let t=null;try{t=e?await e.json():null}catch{}if(!e||!e.ok||!t||t.success===!1){this._webhookSay(t?.error||"Could not remove the webhook URL.",!0);return}await this.loadWebhook(),this._webhookSay("Webhook URL removed.")},async testWebhook(){const e=document.getElementById("webhookTestBtn");e&&(e.disabled=!0);try{if(this._webhookPending()&&await this.saveWebhook())return;this._webhookSay("Sending\u2026");const t=await this._apiPost("/api/webhook/test",{});let n=null;try{n=t?await t.json():null}catch{}if(!t||!t.ok||!n||n.success===!1){this._webhookSay(n?.error||"Could not send the test.",!0);return}const s=n.data;this._webhookSay(s.ok?"Test sent. Check your phone or channel.":`Delivery failed: ${s.error}`,!s.ok)}finally{e&&(e.disabled=!1)}},_discordSay(e,t=!1){const n=document.getElementById("discordResult");n&&(n.textContent=e,n.style.display=e?"block":"none",n.style.color=t?"var(--danger, #e5534b)":"")},async loadDiscord(){const e=document.getElementById("discordGroup");if(!e)return;const t=await this._api("/api/discord");let n=null;try{n=t&&t.ok?await t.json():null}catch{}if(!n||n.success===!1){e.style.display="none";return}const s=n.data;e.style.display="",document.getElementById("discordEnabled").checked=s.enabled===!0;const i=document.getElementById("discordToken");i.value="",i.placeholder=s.hasToken?"Saved. Paste a new token to replace it":"Bot token from the Developer Portal",document.getElementById("discordTokenHint").textContent=s.hasToken?`Saved: ${s.tokenMasked}`:"Nothing saved yet.",document.getElementById("discordGuildId").value=s.guildId||"",document.getElementById("discordParentChannelId").value=s.parentChannelId||"",document.getElementById("discordAllowedUserIds").value=(s.allowedUserIds||[]).join(", "),document.getElementById("discordThreadCreation").value=s.threadCreation,document.getElementById("discordOnSessionDelete").value=s.onSessionDelete,document.getElementById("discordStaleThreads").value=s.staleThreads||"keep",document.getElementById("discordPingMode").value=s.pingMode,document.getElementById("discordScope").value=s.scope,document.getElementById("discordStartupHint").checked=s.startupHint!==!1;const o=document.getElementById("discordClearBtn");o&&(o.style.display=s.hasToken?"":"none");let l={off:"Not connected (disabled).",connecting:"Connecting to Discord\u2026",connected:"Connected: replies are being read.",error:`Not connected: ${s.gatewayError||"error"}`}[s.gateway]||"";if(s.lastResult){const c=new Date(s.lastResult.at).toLocaleString();l+=s.lastResult.ok?` Last post succeeded (${c}).`:` Last post failed (${c}): ${s.lastResult.error}`}this._discordSay(l.trim(),s.gateway==="error"||s.lastResult&&!s.lastResult.ok)},async saveDiscord(){const e=document.getElementById("discordAllowedUserIds").value.split(/[\s,]+/).filter(Boolean),t={enabled:document.getElementById("discordEnabled").checked,guildId:document.getElementById("discordGuildId").value.trim(),parentChannelId:document.getElementById("discordParentChannelId").value.trim(),allowedUserIds:e,threadCreation:document.getElementById("discordThreadCreation").value,onSessionDelete:document.getElementById("discordOnSessionDelete").value,staleThreads:document.getElementById("discordStaleThreads").value,pingMode:document.getElementById("discordPingMode").value,scope:document.getElementById("discordScope").value,startupHint:document.getElementById("discordStartupHint").checked},n=document.getElementById("discordToken").value.trim();n&&(t.token=n);const s=await this._api("/api/discord",{method:"PUT",body:t});let i=null;try{i=s?await s.json():null}catch{}if(!s||!s.ok||!i||i.success===!1){const o=i?.error||"Could not save the Discord settings.";return this._discordSay(o,!0),o}return await this.loadDiscord(),""},async clearDiscord(){if(!confirm("Remove the saved bot token from the server? The Discord bridge stops until you save a new one."))return;const e=await this._api("/api/discord",{method:"PUT",body:{token:"",enabled:!1}});let t=null;try{t=e?await e.json():null}catch{}if(!e||!e.ok||!t||t.success===!1){this._discordSay(t?.error||"Could not remove the token.",!0);return}await this.loadDiscord()},async testDiscord(){const e=document.getElementById("discordTestBtn");e&&(e.disabled=!0);try{if(await this.saveDiscord())return;this._discordSay("Sending\u2026");const t=await this._apiPost("/api/discord/test",{});let n=null;try{n=t?await t.json():null}catch{}if(!t||!t.ok||!n||n.success===!1){this._discordSay(n?.error||"Could not send the test.",!0);return}const s=n.data;this._discordSay(s.ok?"Test sent. Check the channel.":`Delivery failed: ${s.error}`,!s.ok)}finally{e&&(e.disabled=!1)}},async runDoctor(){const e=document.getElementById("doctorResult"),t=document.getElementById("doctorRunBtn");if(!e)return;const n=s=>{e.replaceChildren(document.createTextNode(s)),e.style.display="block"};t&&(t.disabled=!0),n("Checking\u2026");try{const s=await this._api("/api/doctor");let i=null;try{i=s?await s.json():null}catch{}if(!s||!s.ok||!i||i.success===!1){n(i?.error||"The check failed.");return}const{tools:o,summary:a,platform:l}=i.data,c={ok:"\u2713",missing:"\u2717",outdated:"!",error:"!",skipped:"\u2013"},p=document.createElement("ul");p.style.margin="0",p.style.paddingLeft="1.2em";for(const r of o){const u=document.createElement("li"),h=document.createElement("b"),f=r.status==="missing"&&!r.required?"\u25CB":c[r.status]||"?";h.textContent=`${f} ${r.label}`,u.append(h);const m=[r.status];if(r.version&&m.push(r.version),r.status!=="ok"&&r.status!=="skipped"&&m.push(r.required?"required":"optional"),r.reason&&m.push(r.reason),u.append(document.createTextNode(` ${m.join(" \xB7 ")}`)),r.path){const g=document.createElement("div");g.className="mono",g.textContent=r.path,u.append(g)}if(r.status==="missing"&&r.installHint){const g=document.createElement("div");g.textContent=`Install: ${r.installHint}`,u.append(g)}p.append(u)}const d=document.createElement("p");d.textContent=`${a.ok} ok \xB7 ${a.requiredMissing} required missing \xB7 ${a.optionalMissing} optional missing (${l.environment})`,e.replaceChildren(d,p),e.style.display="block"}finally{t&&(t.disabled=!1)}},_setUpdateResult(e){const t=this.$("updateResult");t&&(t.style.display="block",t.innerHTML=e)},_setUpdateProgress(e){const t=this.$("updateProgress");t&&(t.style.display="block",t.innerHTML=e)},async checkForUpdate(){const e=this.$("updateCheckBtn");e&&(e.disabled=!0,e.textContent="Checking\u2026");const t=await this._apiJson("/api/system/update/check");e&&(e.disabled=!1,e.textContent="Check now");const n=this.$("updateActionRow"),s=this.$("updateNotes");if(n&&(n.style.display="none"),s&&(s.style.display="none"),!t){this._setUpdateResult("Could not check for updates. Try again later.");return}this._updateCheck=t;const i=this.$("updateCurrentVersion");if(i&&t.currentVersion&&(i.textContent=`v${t.currentVersion}`),t.installKind&&t.installKind!=="git"&&t.installKind!=="docker-compose"){const a=t.supervisor==="docker-compose"?"Update from the Docker host with <code>docker/Start-Codeman.sh</code>.":t.installKind==="archive"?"It was installed without git. Re-run the installer to update: <code>curl -fsSL https://getcodeman.com/install | bash</code>":"Update with <code>npm i -g aicodeman@latest</code>.";this._setUpdateResult(`This install can't update itself (${escapeHtml(t.installKind)}). ${a}`);return}if(t.selfUpdateEnabled===!1){this._setUpdateResult("In-app updates are disabled on this server (CODEMAN_DISABLE_SELF_UPDATE=1).");return}if(t.error&&!t.updateAvailable){this._setUpdateResult(escapeHtml(t.error));return}const o=t.environment?.blockers||[];if(t.updateAvailable&&o.length>0){const a=o.map(l=>{const c=l.details?.length?`<br><code>${escapeHtml(l.details.join(" "))}</code>`:"";return`<li>${escapeHtml(l.message)}${c}</li>`}).join("");this._setUpdateResult(`<strong>v${escapeHtml(t.latestVersion||"")}</strong> needs a rebuild on the Docker host (current v${escapeHtml(t.currentVersion||"")}):<ul>${a}</ul>Run <code>${escapeHtml(t.environment?.hostCommand||"docker/Start-Codeman.sh")}</code> there to apply it.`),s&&t.notes&&(s.style.display="block",s.textContent=t.notes);return}if(t.updateAvailable&&t.latestVersion){this._setUpdateResult(`Update available: <strong>v${escapeHtml(t.latestVersion)}</strong> &nbsp;(current v${escapeHtml(t.currentVersion||"")})`);const a=this.$("updateActionLabel");a&&(a.textContent=`Update to v${t.latestVersion}`),n&&(n.style.display="flex");const l=this.$("updateNowBtn");l&&(l.disabled=!1,l.textContent="Update now"),s&&t.notes&&(s.style.display="block",s.textContent=t.notes)}else this._setUpdateResult(`You're up to date (v${escapeHtml(t.currentVersion||"")}).`)},async startSelfUpdate(){const e=this._updateCheck?.latestVersion?`v${this._updateCheck.latestVersion}`:"the latest release";if(!confirm(`Update Codeman to ${e}? The server will restart and this page will reload.`))return;const t=this.$("updateNowBtn");t&&(t.disabled=!0,t.textContent="Starting\u2026");const n=await this._apiPost("/api/system/update",{});if(!n||!n.ok){let o="Failed to start the update.";try{const a=await n.json();typeof a?.error=="string"&&a.error&&(o=a.error)}catch{}this._setUpdateProgress(`<span style="color:var(--danger,#e5534b)">${escapeHtml(o)}</span>`),t&&(t.disabled=!1,t.textContent="Update now");return}const s=this.$("updateActionRow");s&&(s.style.display="none");const i=this.$("updateNotes");i&&(i.style.display="none"),this._setUpdateProgress("Starting update\u2026"),this._pollUpdateStatus()},_stopUpdatePolling(){this._updatePollTimer&&(clearInterval(this._updatePollTimer),this._updatePollTimer=null)},_pollUpdateStatus(){this._stopUpdatePolling();const e=new Set(["completed","completed-needs-manual-restart","failed","idle"]),t=async()=>{let n=null;try{const s=await fetch("/api/system/update/status");if(s.ok){const i=await s.json();n=i&&i.success===!0?i.data:i}}catch{}if(!n){this._setUpdateProgress("\u21BB Restarting Codeman\u2026");return}if(!e.has(n.phase)){const s=n.message&&n.message.trim()?n.message.trim():this._updatePhaseText(n.phase);let i="";n.startedAt&&(i=` <span style="color:var(--text-secondary)">\xB7 ${Math.max(0,Math.round((Date.now()-n.startedAt)/1e3))}s</span>`),this._setUpdateProgress(`<span class="tunnel-spinner"></span> ${escapeHtml(s)}${i}`);return}if(this._stopUpdatePolling(),n.phase==="completed"){let s=`<span style="color:var(--success,#3fb950)">\u2713 Updated to v${escapeHtml(n.toVersion||"")}. Reloading\u2026</span>`;n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code> \u2014 run <code>git stash pop</code> to restore.</span>`),this._setUpdateProgress(s),setTimeout(()=>location.reload(),2500)}else if(n.phase==="completed-needs-manual-restart")this._setUpdateProgress(`Update staged. Restart Codeman to apply:<br><code>${escapeHtml(n.manualRestartCommand||"restart codeman web")}</code>`);else if(n.phase==="failed"){let s=`<span style="color:var(--danger,#e5534b)">\u2717 ${escapeHtml(n.message||"Update failed")}.</span>`;n.error&&(s+=`<br><span style="color:var(--text-secondary)">${escapeHtml(n.error)}</span>`),s+='<br><span style="color:var(--text-secondary)">The previous version is still running.</span>',n.stashRef&&(s+=`<br><span style="color:var(--text-secondary)">Local changes stashed as <code>${escapeHtml(n.stashRef)}</code>.</span>`),this._setUpdateProgress(s);const i=this.$("updateNowBtn"),o=this.$("updateActionRow");i&&(i.disabled=!1,i.textContent="Try again"),o&&(o.style.display="flex")}};t(),this._updatePollTimer=setInterval(t,1500)},isCliAvailable(e){const t=window.__codemanCliAvailable;return!t||typeof t!="object"?!0:t[e]!==!1},renderWelcomeCliActions(){const e=document.getElementById("welcomeCliActions");if(!e)return;const n=(Array.isArray(window.__codemanCliCatalog)?window.__codemanCliCatalog:[]).filter(u=>u.enabled&&this.isCliAvailable(u.id)),s=n.find(u=>u.kind!=="shell")||n[0],i=u=>`Run ${u.kind==="shell"?"Shell":u.label}`,o=u=>{const h=document.createElement("span");return h.className=`run-mode-dot ${u.id}`,h.setAttribute("aria-hidden","true"),h},a=u=>()=>{this.setRunMode(u.id),this.run()};if(e.replaceChildren(),!s)return;const l=document.createElement("button");l.type="button",l.className="welcome-primary",l.dataset.mode=s.id;const c=document.createElement("span");c.className="welcome-primary-logo",c.appendChild(o(s)),l.appendChild(c);const p=document.createElement("span");p.className="welcome-label",p.textContent=i(s),l.appendChild(p),l.onclick=a(s),e.appendChild(l);const d=n.filter(u=>u!==s);if(!d.length)return;const r=document.createElement("div");r.className="welcome-chips",r.setAttribute("role","group"),r.setAttribute("aria-label","More tools");for(const u of d){const h=document.createElement("button");h.type="button",h.className="welcome-chip",h.dataset.mode=u.id,h.title=i(u),h.setAttribute("aria-label",i(u)),h.appendChild(o(u));const f=document.createElement("span");f.className="welcome-label",f.textContent=u.kind==="shell"?"Shell":u.label,h.appendChild(f),h.onclick=a(u),r.appendChild(h)}e.appendChild(r)},applyWelcomeCliVisibility(){this.renderWelcomeCliActions();const e=document.getElementById("welcomeTunnelBtn");e&&(e.style.display=this.isCliAvailable("cloudflared")?"flex":"none")},async loadTunnelStatus(){try{const t=await(await fetch("/api/tunnel/status")).json(),n=t?.success===!0?t.data:t,s=n.running&&n.url;this._tunnelUrl=s?n.url:null,this._updateTunnelUrlDisplay(this._tunnelUrl),this._updateWelcomeTunnelBtn(!!s,this._tunnelUrl),this._updateTunnelIndicator(!!s)}catch{this._tunnelUrl=null,this._updateTunnelUrlDisplay(null),this._updateWelcomeTunnelBtn(!1),this._updateTunnelIndicator(!1)}},_updateTunnelUrlDisplay(e){const t=document.getElementById("tunnelUrlRow"),n=document.getElementById("tunnelUrlDisplay");!t||!n||(e?(t.style.display="",n.textContent=e,n.onclick=()=>{navigator.clipboard.writeText(e).then(()=>{this.showToast("Tunnel URL copied","success")})}):(t.style.display="none",n.textContent="",n.onclick=null))},showTunnelQR(){this.closeTunnelQR();const e=document.createElement("div");e.id="tunnelQrOverlay",e.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:5000;display:flex;align-items:center;justify-content:center;cursor:pointer",e.onclick=n=>{n.target===e&&this.closeTunnelQR()};const t=document.createElement("div");t.style.cssText="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px;text-align:center;max-width:340px;width:90vw;box-shadow:var(--shadow-lg);cursor:default",t.innerHTML=`
13
13
  <div style="font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:16px">Scan to connect</div>
14
14
  <div id="tunnelQrContainer" style="background:#fff;border-radius:8px;padding:16px;display:inline-block">
15
15
  <div style="color:#666;font-size:12px">Loading...</div>
@@ -69,7 +69,7 @@ Enable the unauthenticated public tunnel anyway?`)},async _confirmTunnelEnable()
69
69
  <td style="padding:3px 8px;color:#e0e0e0" title="${p.sessionId}">${u}</td>
70
70
  <td style="padding:3px 8px;color:#aaa">${p.reason||""}</td>
71
71
  <td style="padding:3px 8px;color:#666">${h.join(", ")}</td>
72
- </tr>`}).join("")}catch(s){console.error("Failed to load lifecycle log:",s)}},_renderVoiceProviderStatus(){const e=document.getElementById("voiceProviderStatus");if(e){const i=VoiceInput.getActiveProviderName();e.textContent=i;const o=i.startsWith("Deepgram Nova")||i.startsWith("Claude (this");e.className="voice-provider-status"+(o?" active":"")}const t=document.getElementById("voiceClaudeStatus");if(!t)return;const n=VoiceInput._claudeStatus,s=n?n.available?`Ready${n.subscriptionType?` (${n.subscriptionType})`:""}`:n.reason==="expired"?"Login expired - run a Claude session to refresh":n.reason==="no-credentials"?"No Claude Code login on the server":n.reason==="malformed"?"Claude credentials unreadable":"Off - enable it above":"Checking...";t.textContent=s,t.className="voice-provider-status"+(n?.available?" active":"")},async applyAppSettings(){if(!this._applyInFlight){this._applyInFlight=!0,this._keepSettingsOpenOnce=!0;try{await this.saveAppSettings()}finally{this._applyInFlight=!1,this._keepSettingsOpenOnce=!1}}},async saveAppSettings(){const e=this._keepSettingsOpenOnce===!0;this._keepSettingsOpenOnce=!1;const t=this.loadAppSettingsFromStorage(),n=this._unsavedSettingsBaseline??t,s=(n.gestureControlEnabled??!1)===!0,i=(t.webglRendererEnabled??!0)===!0,o={displayName:window.CodemanI18n?.normalizeDisplayName(document.getElementById("appSettingsDisplayName").value)||"Codeman",language:window.CodemanI18n?.normalizeLanguage(document.getElementById("appSettingsLanguage").value)||"en",defaultClaudeMdPath:document.getElementById("appSettingsClaudeMdPath").value.trim(),defaultWorkingDir:document.getElementById("appSettingsDefaultDir").value.trim(),ralphTrackerEnabled:document.getElementById("appSettingsRalphEnabled").checked,showFontControls:document.getElementById("appSettingsShowFontControls").checked,showSystemStats:document.getElementById("appSettingsShowSystemStats").checked,showGpuStats:document.getElementById("appSettingsShowGpuStats").checked,showCpuMhz:document.getElementById("appSettingsShowCpuMhz").checked,headerStatsStyle:document.getElementById("appSettingsHeaderStatsStyle").value,showLifecycleLog:document.getElementById("appSettingsShowLifecycleLog").checked,showResponseViewer:document.getElementById("appSettingsShowResponseViewer").checked,showFileViewerButton:document.getElementById("appSettingsShowFileViewerButton").checked,showAttachmentsButton:document.getElementById("appSettingsShowAttachmentsButton").checked,showMonitor:document.getElementById("appSettingsShowMonitor").checked,showProjectInsights:document.getElementById("appSettingsShowProjectInsights").checked,showFileBrowser:document.getElementById("appSettingsShowFileBrowser").checked,showSubagents:document.getElementById("appSettingsShowSubagents").checked,showUltracodeAgents:document.getElementById("appSettingsShowUltracodeAgents").checked,approvalsInboxEnabled:document.getElementById("appSettingsApprovalsInbox").checked,customModelEndpointsEnabled:document.getElementById("appSettingsCustomModelEndpoints").checked,cliManagementEnabled:document.getElementById("appSettingsCliManagement").checked,mcpSyncEnabled:document.getElementById("appSettingsMcpSync").checked,configBackupEnabled:document.getElementById("appSettingsConfigBackup").checked,configBackupDir:document.getElementById("appSettingsConfigBackupDir").value.trim(),...this._configBackupRetentionPayload(t),agentInboxPersistEnabled:document.getElementById("appSettingsAgentInboxPersist").checked,agentCoordinationEnabled:document.getElementById("appSettingsAgentCoordination").checked,readMyMindEnabled:document.getElementById("appSettingsReadMyMind").checked,ultracodeFloatingWindows:document.getElementById("appSettingsUltracodeFloatingWindows").checked,showMultiMonitorButton:document.getElementById("appSettingsShowMultiMonitorButton").checked,showSplitButton:document.getElementById("appSettingsShowSplitButton").checked,showTileGridButton:document.getElementById("appSettingsShowTileGridButton").checked,showPlanUsageLimits:document.getElementById("appSettingsShowPlanUsageLimits").checked,showRedrawButton:document.getElementById("appSettingsShowRedrawButton").checked,mobileOverviewEnabled:document.getElementById("appSettingsMobileOverview").checked,sessionLineageLines:document.getElementById("appSettingsLineageLines").checked,autoNameSessions:document.getElementById("appSettingsAutoNameSessions").checked,spawnedTabsFollowParent:document.getElementById("appSettingsSpawnedTabsFollowParent").checked,showSessionButton:document.getElementById("appSettingsShowSessionButton").checked,showAwayDigestButton:document.getElementById("appSettingsShowAwayDigestButton").checked,showCronButton:document.getElementById("appSettingsShowCronButton").checked,showGitStatus:document.getElementById("appSettingsShowGitStatus").checked,gitStatusTree:document.getElementById("appSettingsGitStatusTree").checked,gitStatusMaxRepos:Math.min(50,Math.max(1,parseInt(document.getElementById("appSettingsGitStatusMaxRepos").value,10)||12)),gitStatusTimeoutSeconds:Math.min(120,Math.max(5,parseInt(document.getElementById("appSettingsGitStatusTimeout").value,10)||30)),gitFetchEnabled:document.getElementById("appSettingsGitFetchEnabled").checked,gitFetchIntervalSeconds:Math.min(3600,Math.max(30,parseInt(document.getElementById("appSettingsGitFetchIntervalSeconds").value,10)||300)),showEditHistory:document.getElementById("appSettingsShowEditHistory").checked,gestureControlEnabled:document.getElementById("appSettingsGestureControl").checked,subagentTrackingEnabled:document.getElementById("appSettingsSubagentTracking").checked,subagentActiveTabOnly:document.getElementById("appSettingsSubagentActiveTabOnly").checked,imageWatcherEnabled:document.getElementById("appSettingsImageWatcherEnabled").checked,promptCacheKeepWarm:document.getElementById("appSettingsPromptCacheKeepWarm").checked,promptCacheKeepWarmMaxHours:Math.min(24,Math.max(.5,parseFloat(document.getElementById("appSettingsPromptCacheKeepWarmMaxHours").value)||2)),tunnelEnabled:document.getElementById("appSettingsTunnelEnabled").checked,localEchoEnabled:document.getElementById("appSettingsLocalEcho").checked,keyboardHoldsRows:document.getElementById("appSettingsKeyboardHoldsRows").checked,autoCopySelection:document.getElementById("appSettingsAutoCopySelection").checked,copyStripMargin:document.getElementById("appSettingsCopyStripMargin").checked,terminalFontFamily:document.getElementById("appSettingsTerminalFont").value.trim(),terminalFontWeight:this.readTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeight")),terminalFontWeightBold:this.readTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeightBold")),terminalWheelLocalScrollback:document.getElementById("appSettingsTerminalWheelLocal").checked,cjkInputEnabled:document.getElementById("appSettingsCjkInput").checked,webglRendererEnabled:document.getElementById("appSettingsWebglRenderer").checked,extendedKeyboardBar:document.getElementById("appSettingsExtendedKeyboardBar").checked,tabTwoRows:document.getElementById("appSettingsTabTwoRows").checked,showTabCliLogos:document.getElementById("appSettingsShowTabCliLogos").checked,tabOrientation:document.getElementById("appSettingsTabOrientation").value,tabRailWidth:this.readTabRailWidthSetting?.()??256,tabRailDetail:document.getElementById("appSettingsTabRailDetail").value,tabRailSort:document.getElementById("appSettingsTabRailSort").value,tabArrangement:document.getElementById("appSettingsTabArrangement").value,tabStateOrder:document.getElementById("appSettingsTabStateOrder").value,showTabDetachButton:document.getElementById("appSettingsShowTabDetachButton").checked,showTabNewButton:document.getElementById("appSettingsShowTabNewButton").checked,tileDetachEnabled:document.getElementById("appSettingsTileDetach").checked,sessionListLayout:document.getElementById("appSettingsSessionListLayout").value,sessionSidebarFontSize:this.resolveSessionSidebarFontSize(document.getElementById("appSettingsSessionSidebarFontSize").value),skin:document.getElementById("appSettingsSkin").value,claudeMode:document.getElementById("appSettingsClaudeMode").value,allowedTools:document.getElementById("appSettingsAllowedTools").value.trim(),codexModel:document.getElementById("appSettingsCodexModel").value.trim(),codexReasoningEffort:document.getElementById("appSettingsCodexReasoningEffort").value,codexDangerouslyBypassApprovals:document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked,codexAnimationsEnabled:document.getElementById("appSettingsCodexAnimations").checked,agentTeamsEnabled:document.getElementById("appSettingsAgentTeams").checked,agentSkillEnabled:document.getElementById("appSettingsAgentSkill").checked,workspaceHooksEnabled:document.getElementById("appSettingsWorkspaceHooks").checked,pinCliSessionName:document.getElementById("appSettingsPinCliSessionName").checked,claudeVoiceEnabled:document.getElementById("appSettingsClaudeVoice").checked,claudeModel:document.getElementById("appSettingsClaudeModel").value,opusContext1mEnabled:document.getElementById("appSettingsOpusContext1m").checked,remoteAutoReconnect:document.getElementById("appSettingsRemoteAutoReconnect").checked,thinkingEffort:document.getElementById("appSettingsThinkingEffort").value,claudeAdvisorModel:document.getElementById("appSettingsClaudeAdvisor").value,nice:{enabled:document.getElementById("appSettingsNiceEnabled").checked,niceValue:parseInt(document.getElementById("appSettingsNiceValue").value)||10}};if(this._readSettingsFormOnce===!0)return this._readSettingsFormOnce=!1,o;if(!/^[A-Za-z0-9._\/-]*$/.test(o.codexModel)){this.showToast('Default Codex model may only contain letters, digits, ".", "_", "-" and "/"',"error"),document.getElementById("appSettingsCodexModel")?.focus();return}t.showTokenCount!==void 0&&(o.showTokenCount=t.showTokenCount),t.showCost!==void 0&&(o.showCost=t.showCost),t.shortcutOverrides!==void 0&&(o.shortcutOverrides=t.shortcutOverrides);const a=this._settingsReadOnly(),l=a&&this._keepServerSyncedSettings(o,t);if(this.saveAppSettingsToStorage(o),this._updateLocalEchoState(),this._reconcileKeyboardHold?.(),this.applyTerminalFontFamily?.(o.terminalFontFamily),this.applyTerminalFontWeights?.(o),!i&&o.webglRendererEnabled)try{localStorage.removeItem("codeman-webgl-disabled")}catch{}const c={provider:document.getElementById("voiceProvider").value,apiKey:document.getElementById("voiceDeepgramKey").value.trim(),language:document.getElementById("voiceLanguage").value,keyterms:document.getElementById("voiceKeyterms").value.trim(),insertMode:document.getElementById("voiceInsertMode").value};VoiceInput._saveDeepgramConfig(c);const p={enabled:document.getElementById("appSettingsNotifEnabled").checked,browserNotifications:document.getElementById("appSettingsNotifBrowser").checked,audioAlerts:document.getElementById("appSettingsNotifAudio").checked,stuckThresholdMs:(parseInt(document.getElementById("appSettingsNotifStuckMins").value)||10)*6e4,toastDurationMs:(parseInt(document.getElementById("appSettingsNotifToastSecs").value)||3)*1e3,browserAutoCloseMs:(parseInt(document.getElementById("appSettingsNotifBrowserSecs").value)||8)*1e3,muteCritical:!document.getElementById("appSettingsNotifCritical").checked,muteWarning:!document.getElementById("appSettingsNotifWarning").checked,muteInfo:!document.getElementById("appSettingsNotifInfo").checked,eventTypes:{permission_prompt:{enabled:document.getElementById("eventPermissionEnabled").checked,browser:document.getElementById("eventPermissionBrowser").checked,push:document.getElementById("eventPermissionPush").checked,audio:document.getElementById("eventPermissionAudio").checked},elicitation_dialog:{enabled:document.getElementById("eventQuestionEnabled").checked,browser:document.getElementById("eventQuestionBrowser").checked,push:document.getElementById("eventQuestionPush").checked,audio:document.getElementById("eventQuestionAudio").checked},idle_prompt:{enabled:document.getElementById("eventIdleEnabled").checked,browser:document.getElementById("eventIdleBrowser").checked,push:document.getElementById("eventIdlePush").checked,audio:document.getElementById("eventIdleAudio").checked},stop:{enabled:document.getElementById("eventStopEnabled").checked,browser:document.getElementById("eventStopBrowser").checked,push:document.getElementById("eventStopPush").checked,audio:document.getElementById("eventStopAudio").checked},session_error:{enabled:!0,browser:this.notificationManager?.preferences?.eventTypes?.session_error?.browser??!0,push:this.notificationManager?.preferences?.eventTypes?.session_error?.push??!1,audio:!1},respawn_cycle:{enabled:document.getElementById("eventRespawnEnabled").checked,browser:document.getElementById("eventRespawnBrowser").checked,push:document.getElementById("eventRespawnPush").checked,audio:document.getElementById("eventRespawnAudio").checked},token_milestone:{enabled:!0,browser:!1,push:!1,audio:!1},ralph_complete:{enabled:document.getElementById("eventRalphEnabled").checked,browser:document.getElementById("eventRalphBrowser").checked,push:document.getElementById("eventRalphPush").checked,audio:document.getElementById("eventRalphAudio").checked},subagent_spawn:{enabled:document.getElementById("eventSubagentEnabled").checked,browser:document.getElementById("eventSubagentBrowser").checked,push:document.getElementById("eventSubagentPush").checked,audio:document.getElementById("eventSubagentAudio").checked},subagent_complete:{enabled:document.getElementById("eventSubagentEnabled").checked,browser:document.getElementById("eventSubagentBrowser").checked,push:document.getElementById("eventSubagentPush").checked,audio:document.getElementById("eventSubagentAudio").checked}},_version:5};this.notificationManager&&(this.notificationManager.preferences=this.notificationManager.normalizePreferences(p),this.notificationManager.savePreferences()),this._syncPushPreferences(),this.applyHeaderVisibilitySettings(),this.applySkin(),this.applyLocalization(),this.applySessionListLayout(),this.applyTabOrientation({settleRailWidth:!0}),this.applyLineageLineSettings?.(),this._updateTokensImmediate(),this.applyMonitorVisibility(),this.renderApprovals?.(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this._updateCjkInputState(),this.activeSessionId||this.showWelcome(),KeyboardAccessoryBar.setMode(o.extendedKeyboardBar?"extended":"simple");const d=this.planUsageCollectionFlip(n,o.showPlanUsageLimits),{localEchoEnabled:r,keyboardHoldsRows:u,cjkInputEnabled:h,extendedKeyboardBar:f,skin:m,language:g,showPlanUsageLimits:y,showAttachmentsButton:S,showFileViewerButton:b,showSplitButton:E,showTileGridButton:w,webglRendererEnabled:v,terminalWheelLocalScrollback:B,autoCopySelection:k,copyStripMargin:I,terminalFontFamily:L,terminalFontWeight:U,terminalFontWeightBold:A,showSessionButton:O,showAwayDigestButton:P,showCronButton:N,showGitStatus:R,showGpuStats:W,showCpuMhz:x,gitStatusTree:$,gitStatusMaxRepos:V,gitStatusTimeoutSeconds:F,showEditHistory:G,showTabDetachButton:z,showTabNewButton:J,tileDetachEnabled:Y,mobileOverviewEnabled:X,sessionLineageLines:Z,shortcutOverrides:ee,...q}=o;let M="",C="",j=!1;try{const H=o.tunnelEnabled&&!a?await this._confirmTunnelEnable():{proceed:!0,acknowledge:!1};if(!H.proceed){this._unsavedSettingsBaseline=n,this._abandonTunnelEnable(o,e);return}const K={...q,...d!==void 0?{showPlanUsageLimits:d}:{},...H.acknowledge?{acknowledgeUnauthTunnel:!0}:{},notificationPreferences:p,voiceSettings:c},T=a?null:await this._apiPut("/api/settings",K),_=!a&&await this._noteSettingsWriteRefusal(T);_&&(this._keepServerSyncedSettings(o,t),this.saveAppSettingsToStorage(o));const D=!_&&o.tunnelEnabled&&T?.status===403;if(D&&await this._handleTunnelEnableRefusal(T,K)){this._unsavedSettingsBaseline=n,this._abandonTunnelEnable(o,e);return}j=!!T?.ok||D,C=a||_||D?"":await this._settingsWriteError(T),this._unsavedSettingsBaseline=C?n:null;const Q=await this.saveModelConfigFromSettings()===!0;M=this._webhookPending()?await this.saveWebhook():"",C?this.showToast(`Saved on this device, not on the server: ${C}`,"warning",{duration:0}):M?this.showToast(`Settings saved, but not the webhook: ${M}`,"warning"):l||Q||_?this.showToast("Saved on this device. Server-wide settings can only be changed by an admin.","warning"):this.showToast(e?"Settings applied":"Settings saved","success"),o.tunnelEnabled&&!a&&!_&&!C&&this.showToast("Tunnel starting \u2014 QR code will appear when ready...","info")}catch{this.showToast("Settings saved locally","warning")}e&&j&&this._refreshSettingsAfterApply(o),M?document.getElementById("webhookGroup")?.scrollIntoView({block:"center"}):!e&&!C&&this.closeAppSettings(),VoiceInput.refreshClaudeStatus(),!C&&(o.gestureControlEnabled??!1)!==s&&(this.showToast(o.gestureControlEnabled?"Enabling gesture control \u2014 reloading\u2026":"Disabling gesture control \u2014 reloading\u2026","info"),setTimeout(()=>location.reload(),400))},_settingsReadOnly(){if(this._settingsWriteForbidden===!0)return!0;const e=typeof window<"u"&&window.__codemanUser||{};return!!(e.multiUser&&e.role!=="admin")},async _noteSettingsWriteRefusal(e){if(!e||e.status!==403)return!1;let t=null;try{t=await(typeof e.clone=="function"?e.clone():e).json()}catch{return!1}return t?.errorCode!=="FORBIDDEN"?!1:(this._settingsWriteForbidden=!0,!0)},async _settingsWriteError(e){if(!e)return"the server could not be reached";if(e.ok)return"";let t=null;try{t=await e.clone().json()}catch{}return t?.error||`HTTP ${e.status}`},async _noteSettingsWriteResult(e,t){if(await this._noteSettingsWriteRefusal(e))return;const n=await this._settingsWriteError(e);n&&this.showToast(`${t} saved on this device, not on the server: ${n}`,"warning",{duration:0})},_ownSettingsKey(){return`codeman:own-settings:${(typeof window<"u"&&window.__codemanUser||{}).username||""}`},_readOwnSettings(){try{const e=JSON.parse(localStorage.getItem(this._ownSettingsKey())||"{}");return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}catch{return{}}},_rememberOwnSetting(e,t){try{const n=this._readOwnSettings();n[e]=t,localStorage.setItem(this._ownSettingsKey(),JSON.stringify(n))}catch{}},_applyOwnSettings(){if(!this._settingsReadOnly())return;const e=this._readOwnSettings();if(Array.isArray(e.respawnPresets)){this._serverRespawnPresets=e.respawnPresets;try{localStorage.setItem("codeman-respawn-presets",JSON.stringify(e.respawnPresets))}catch{}}if(typeof e.runMode=="string"&&e.runMode){this.runMode=e.runMode;try{localStorage.setItem("codeman_runMode",e.runMode)}catch{}this._applyRunMode?.()}},_captureSettingsFormBaseline(){if(this._settingsFormBaseline=null,!this._settingsReadOnly())return;this._readSettingsFormOnce=!0;let e;try{e=this.saveAppSettings()}finally{this._readSettingsFormOnce=!1}Promise.resolve(e).then(t=>{this._settingsFormBaseline=t&&typeof t=="object"?t:null}).catch(()=>{})},_keepServerSyncedSettings(e,t){const n=this._displaySettingKeySet(),s=this._settingsFormBaseline||t||{};let i=!1;for(const o of Object.keys(e))n.has(o)||o==="shortcutOverrides"||(o in s&&JSON.stringify(e[o])!==JSON.stringify(s[o])&&(i=!0),t&&o in t?e[o]=t[o]:delete e[o]);return i},_refreshSettingsAfterApply(e){this._mcpSyncSavedOn=e.mcpSyncEnabled===!0;const t=this.$("mcpSyncResult");this._mcpSyncSavedOn&&t&&t.dataset?.hint==="save-first"&&(t.style.display="none",t.innerHTML=""),this.applyMcpSyncVisibility(),this.applyCustomModelEndpointsVisibility(),this.applyCliManagementVisibility(),this._applyDoctorAdminGate()},async loadModelConfigForSettings(){try{const t=await(await fetch("/api/execution/model-config")).json();if(t.success&&t.data){const n=t.data,s=document.getElementById("appSettingsDefaultModel");s&&(s.value=n.defaultModel||"");const i=document.getElementById("appSettingsShowModelRecommendations");i&&(i.checked=n.showRecommendations??!0);const o=n.agentTypeOverrides||{},a=document.getElementById("appSettingsModelExplore"),l=document.getElementById("appSettingsModelImplement"),c=document.getElementById("appSettingsModelTest"),p=document.getElementById("appSettingsModelReview");a&&(a.value=o.explore||""),l&&(l.value=o.implement||""),c&&(c.value=o.test||""),p&&(p.value=o.review||"")}}catch(e){console.warn("Failed to load model config:",e)}this._modelConfigBaseline=JSON.stringify(this._modelConfigFromSettingsForm())},_modelConfigFromSettingsForm(){const e=document.getElementById("appSettingsDefaultModel"),t=document.getElementById("appSettingsShowModelRecommendations"),n=document.getElementById("appSettingsModelExplore"),s=document.getElementById("appSettingsModelImplement"),i=document.getElementById("appSettingsModelTest"),o=document.getElementById("appSettingsModelReview"),a={};return n?.value&&(a.explore=n.value),s?.value&&(a.implement=s.value),i?.value&&(a.test=i.value),o?.value&&(a.review=o.value),{defaultModel:e?.value||"",showRecommendations:t?.checked??!0,agentTypeOverrides:a}},async saveModelConfigFromSettings(){const e=this._modelConfigFromSettingsForm();if(this._settingsReadOnly()){const t=this._modelConfigBaseline;return typeof t=="string"&&JSON.stringify(e)!==t}try{await fetch("/api/execution/model-config",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch(t){console.warn("Failed to save model config:",t)}},applyCustomModelEndpointsVisibility(){const e=document.getElementById("appSettingsCustomModelEndpoints").checked,t=document.getElementById("customModelEndpointsBody");t&&(t.style.display=e?"":"none"),e?this.loadCustomModelEndpointsForSettings():this.closeCustomModelHostEditor(),this._applyCustomModelAdminGate()},_applyCustomModelAdminGate(){const e=document.getElementById("customModelHostAddBtn");if(!e)return;const t=window.__codemanUser||{},n=t.multiUser&&t.role!=="admin";e.style.display=n?"none":""},async loadCustomModelEndpointsForSettings(){const e=await this._apiJson("/api/model-endpoints");this._customModelHosts=Array.isArray(e)?e:[],this.renderCustomModelHostsList()},renderCustomModelHostsList(){const e=document.getElementById("customModelHostsList");if(!e)return;const t=this._customModelHosts||[];if(t.length===0){e.innerHTML='<p class="set-group-hint">No endpoints yet. Add one below to point a harness at a local or cloud OpenAI-compatible server.</p>';return}e.innerHTML=t.map(n=>{const s=(n.models||[]).length,i=s===0?"No models discovered yet":`${s} model${s===1?"":"s"}${n.defaultModelId?` \xB7 default: ${escapeHtml(n.defaultModelId)}`:" \xB7 no default set"}`,o=escapeHtml(JSON.stringify(n.id));return`
72
+ </tr>`}).join("")}catch(s){console.error("Failed to load lifecycle log:",s)}},_renderVoiceProviderStatus(){const e=document.getElementById("voiceProviderStatus");if(e){const i=VoiceInput.getActiveProviderName();e.textContent=i;const o=i.startsWith("Deepgram Nova")||i.startsWith("Claude (this");e.className="voice-provider-status"+(o?" active":"")}const t=document.getElementById("voiceClaudeStatus");if(!t)return;const n=VoiceInput._claudeStatus,s=n?n.available?`Ready${n.subscriptionType?` (${n.subscriptionType})`:""}`:n.reason==="expired"?"Login expired - run a Claude session to refresh":n.reason==="no-credentials"?"No Claude Code login on the server":n.reason==="malformed"?"Claude credentials unreadable":"Off - enable it above":"Checking...";t.textContent=s,t.className="voice-provider-status"+(n?.available?" active":"")},async applyAppSettings(){if(!this._applyInFlight){this._applyInFlight=!0,this._keepSettingsOpenOnce=!0;try{await this.saveAppSettings()}finally{this._applyInFlight=!1,this._keepSettingsOpenOnce=!1}}},async saveAppSettings(){const e=this._keepSettingsOpenOnce===!0;this._keepSettingsOpenOnce=!1;const t=this.loadAppSettingsFromStorage(),n=this._unsavedSettingsBaseline??t,s=(n.gestureControlEnabled??!1)===!0,i=(t.webglRendererEnabled??!0)===!0,o={displayName:window.CodemanI18n?.normalizeDisplayName(document.getElementById("appSettingsDisplayName").value)||"Codeman",language:window.CodemanI18n?.normalizeLanguage(document.getElementById("appSettingsLanguage").value)||"en",defaultClaudeMdPath:document.getElementById("appSettingsClaudeMdPath").value.trim(),defaultWorkingDir:document.getElementById("appSettingsDefaultDir").value.trim(),ralphTrackerEnabled:document.getElementById("appSettingsRalphEnabled").checked,showFontControls:document.getElementById("appSettingsShowFontControls").checked,showSystemStats:document.getElementById("appSettingsShowSystemStats").checked,showGpuStats:document.getElementById("appSettingsShowGpuStats").checked,showCpuMhz:document.getElementById("appSettingsShowCpuMhz").checked,headerStatsStyle:document.getElementById("appSettingsHeaderStatsStyle").value,showLifecycleLog:document.getElementById("appSettingsShowLifecycleLog").checked,showResponseViewer:document.getElementById("appSettingsShowResponseViewer").checked,showFileViewerButton:document.getElementById("appSettingsShowFileViewerButton").checked,showAttachmentsButton:document.getElementById("appSettingsShowAttachmentsButton").checked,showMonitor:document.getElementById("appSettingsShowMonitor").checked,showProjectInsights:document.getElementById("appSettingsShowProjectInsights").checked,showFileBrowser:document.getElementById("appSettingsShowFileBrowser").checked,showSubagents:document.getElementById("appSettingsShowSubagents").checked,showUltracodeAgents:document.getElementById("appSettingsShowUltracodeAgents").checked,approvalsInboxEnabled:document.getElementById("appSettingsApprovalsInbox").checked,customModelEndpointsEnabled:document.getElementById("appSettingsCustomModelEndpoints").checked,cliManagementEnabled:document.getElementById("appSettingsCliManagement").checked,mcpSyncEnabled:document.getElementById("appSettingsMcpSync").checked,configBackupEnabled:document.getElementById("appSettingsConfigBackup").checked,configBackupDir:document.getElementById("appSettingsConfigBackupDir").value.trim(),...this._configBackupRetentionPayload(t),agentInboxPersistEnabled:document.getElementById("appSettingsAgentInboxPersist").checked,agentCoordinationEnabled:document.getElementById("appSettingsAgentCoordination").checked,readMyMindEnabled:document.getElementById("appSettingsReadMyMind").checked,ultracodeFloatingWindows:document.getElementById("appSettingsUltracodeFloatingWindows").checked,showMultiMonitorButton:document.getElementById("appSettingsShowMultiMonitorButton").checked,showSplitButton:document.getElementById("appSettingsShowSplitButton").checked,showTileGridButton:document.getElementById("appSettingsShowTileGridButton").checked,showPlanUsageLimits:document.getElementById("appSettingsShowPlanUsageLimits").checked,showRedrawButton:document.getElementById("appSettingsShowRedrawButton").checked,mobileOverviewEnabled:document.getElementById("appSettingsMobileOverview").checked,sessionLineageLines:document.getElementById("appSettingsLineageLines").checked,autoNameSessions:document.getElementById("appSettingsAutoNameSessions").checked,spawnedTabsFollowParent:document.getElementById("appSettingsSpawnedTabsFollowParent").checked,showSessionButton:document.getElementById("appSettingsShowSessionButton").checked,showAwayDigestButton:document.getElementById("appSettingsShowAwayDigestButton").checked,showCronButton:document.getElementById("appSettingsShowCronButton").checked,showGitStatus:document.getElementById("appSettingsShowGitStatus").checked,gitStatusTree:document.getElementById("appSettingsGitStatusTree").checked,gitStatusMaxRepos:Math.min(50,Math.max(1,parseInt(document.getElementById("appSettingsGitStatusMaxRepos").value,10)||12)),gitStatusTimeoutSeconds:Math.min(120,Math.max(5,parseInt(document.getElementById("appSettingsGitStatusTimeout").value,10)||30)),gitFetchEnabled:document.getElementById("appSettingsGitFetchEnabled").checked,gitFetchIntervalSeconds:Math.min(3600,Math.max(30,parseInt(document.getElementById("appSettingsGitFetchIntervalSeconds").value,10)||300)),showEditHistory:document.getElementById("appSettingsShowEditHistory").checked,gestureControlEnabled:document.getElementById("appSettingsGestureControl").checked,subagentTrackingEnabled:document.getElementById("appSettingsSubagentTracking").checked,subagentActiveTabOnly:document.getElementById("appSettingsSubagentActiveTabOnly").checked,imageWatcherEnabled:document.getElementById("appSettingsImageWatcherEnabled").checked,promptCacheKeepWarm:document.getElementById("appSettingsPromptCacheKeepWarm").checked,promptCacheKeepWarmMaxHours:Math.min(24,Math.max(.5,parseFloat(document.getElementById("appSettingsPromptCacheKeepWarmMaxHours").value)||2)),tunnelEnabled:document.getElementById("appSettingsTunnelEnabled").checked,localEchoEnabled:document.getElementById("appSettingsLocalEcho").checked,keyboardHoldsRows:document.getElementById("appSettingsKeyboardHoldsRows").checked,autoCopySelection:document.getElementById("appSettingsAutoCopySelection").checked,copyStripMargin:document.getElementById("appSettingsCopyStripMargin").checked,terminalFontFamily:document.getElementById("appSettingsTerminalFont").value.trim(),terminalFontWeight:this.readTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeight")),terminalFontWeightBold:this.readTerminalFontWeight(document.getElementById("appSettingsTerminalFontWeightBold")),terminalWheelLocalScrollback:document.getElementById("appSettingsTerminalWheelLocal").checked,cjkInputEnabled:document.getElementById("appSettingsCjkInput").checked,webglRendererEnabled:document.getElementById("appSettingsWebglRenderer").checked,extendedKeyboardBar:document.getElementById("appSettingsExtendedKeyboardBar").checked,tabTwoRows:document.getElementById("appSettingsTabTwoRows").checked,showTabCliLogos:document.getElementById("appSettingsShowTabCliLogos").checked,tabOrientation:document.getElementById("appSettingsTabOrientation").value,tabRailWidth:this.readTabRailWidthSetting?.()??256,tabRailDetail:document.getElementById("appSettingsTabRailDetail").value,tabRailSort:document.getElementById("appSettingsTabRailSort").value,tabArrangement:document.getElementById("appSettingsTabArrangement").value,tabStateOrder:document.getElementById("appSettingsTabStateOrder").value,showTabDetachButton:document.getElementById("appSettingsShowTabDetachButton").checked,showTabNewButton:document.getElementById("appSettingsShowTabNewButton").checked,tileDetachEnabled:document.getElementById("appSettingsTileDetach").checked,sessionListLayout:document.getElementById("appSettingsSessionListLayout").value,sessionSidebarFontSize:this.resolveSessionSidebarFontSize(document.getElementById("appSettingsSessionSidebarFontSize").value),skin:document.getElementById("appSettingsSkin").value,claudeMode:document.getElementById("appSettingsClaudeMode").value,allowedTools:document.getElementById("appSettingsAllowedTools").value.trim(),codexModel:document.getElementById("appSettingsCodexModel").value.trim(),codexReasoningEffort:document.getElementById("appSettingsCodexReasoningEffort").value,codexDangerouslyBypassApprovals:document.getElementById("appSettingsCodexDangerouslyBypassApprovals").checked,codexNetworkAccess:document.getElementById("appSettingsCodexNetworkAccess").checked,codexAnimationsEnabled:document.getElementById("appSettingsCodexAnimations").checked,agentTeamsEnabled:document.getElementById("appSettingsAgentTeams").checked,agentSkillEnabled:document.getElementById("appSettingsAgentSkill").checked,workspaceHooksEnabled:document.getElementById("appSettingsWorkspaceHooks").checked,pinCliSessionName:document.getElementById("appSettingsPinCliSessionName").checked,claudeVoiceEnabled:document.getElementById("appSettingsClaudeVoice").checked,claudeModel:document.getElementById("appSettingsClaudeModel").value,opusContext1mEnabled:document.getElementById("appSettingsOpusContext1m").checked,remoteAutoReconnect:document.getElementById("appSettingsRemoteAutoReconnect").checked,thinkingEffort:document.getElementById("appSettingsThinkingEffort").value,claudeAdvisorModel:document.getElementById("appSettingsClaudeAdvisor").value,nice:{enabled:document.getElementById("appSettingsNiceEnabled").checked,niceValue:parseInt(document.getElementById("appSettingsNiceValue").value)||10}};if(this._readSettingsFormOnce===!0)return this._readSettingsFormOnce=!1,o;if(!/^[A-Za-z0-9._\/-]*$/.test(o.codexModel)){this.showToast('Default Codex model may only contain letters, digits, ".", "_", "-" and "/"',"error"),document.getElementById("appSettingsCodexModel")?.focus();return}t.showTokenCount!==void 0&&(o.showTokenCount=t.showTokenCount),t.showCost!==void 0&&(o.showCost=t.showCost),t.shortcutOverrides!==void 0&&(o.shortcutOverrides=t.shortcutOverrides);const a=this._settingsReadOnly(),l=a&&this._keepServerSyncedSettings(o,t);if(this.saveAppSettingsToStorage(o),this._updateLocalEchoState(),this._reconcileKeyboardHold?.(),this.applyTerminalFontFamily?.(o.terminalFontFamily),this.applyTerminalFontWeights?.(o),!i&&o.webglRendererEnabled)try{localStorage.removeItem("codeman-webgl-disabled")}catch{}const c={provider:document.getElementById("voiceProvider").value,apiKey:document.getElementById("voiceDeepgramKey").value.trim(),language:document.getElementById("voiceLanguage").value,keyterms:document.getElementById("voiceKeyterms").value.trim(),insertMode:document.getElementById("voiceInsertMode").value};VoiceInput._saveDeepgramConfig(c);const p={enabled:document.getElementById("appSettingsNotifEnabled").checked,browserNotifications:document.getElementById("appSettingsNotifBrowser").checked,audioAlerts:document.getElementById("appSettingsNotifAudio").checked,stuckThresholdMs:(parseInt(document.getElementById("appSettingsNotifStuckMins").value)||10)*6e4,toastDurationMs:(parseInt(document.getElementById("appSettingsNotifToastSecs").value)||3)*1e3,browserAutoCloseMs:(parseInt(document.getElementById("appSettingsNotifBrowserSecs").value)||8)*1e3,muteCritical:!document.getElementById("appSettingsNotifCritical").checked,muteWarning:!document.getElementById("appSettingsNotifWarning").checked,muteInfo:!document.getElementById("appSettingsNotifInfo").checked,eventTypes:{permission_prompt:{enabled:document.getElementById("eventPermissionEnabled").checked,browser:document.getElementById("eventPermissionBrowser").checked,push:document.getElementById("eventPermissionPush").checked,audio:document.getElementById("eventPermissionAudio").checked},elicitation_dialog:{enabled:document.getElementById("eventQuestionEnabled").checked,browser:document.getElementById("eventQuestionBrowser").checked,push:document.getElementById("eventQuestionPush").checked,audio:document.getElementById("eventQuestionAudio").checked},idle_prompt:{enabled:document.getElementById("eventIdleEnabled").checked,browser:document.getElementById("eventIdleBrowser").checked,push:document.getElementById("eventIdlePush").checked,audio:document.getElementById("eventIdleAudio").checked},stop:{enabled:document.getElementById("eventStopEnabled").checked,browser:document.getElementById("eventStopBrowser").checked,push:document.getElementById("eventStopPush").checked,audio:document.getElementById("eventStopAudio").checked},session_error:{enabled:!0,browser:this.notificationManager?.preferences?.eventTypes?.session_error?.browser??!0,push:this.notificationManager?.preferences?.eventTypes?.session_error?.push??!1,audio:!1},respawn_cycle:{enabled:document.getElementById("eventRespawnEnabled").checked,browser:document.getElementById("eventRespawnBrowser").checked,push:document.getElementById("eventRespawnPush").checked,audio:document.getElementById("eventRespawnAudio").checked},token_milestone:{enabled:!0,browser:!1,push:!1,audio:!1},ralph_complete:{enabled:document.getElementById("eventRalphEnabled").checked,browser:document.getElementById("eventRalphBrowser").checked,push:document.getElementById("eventRalphPush").checked,audio:document.getElementById("eventRalphAudio").checked},subagent_spawn:{enabled:document.getElementById("eventSubagentEnabled").checked,browser:document.getElementById("eventSubagentBrowser").checked,push:document.getElementById("eventSubagentPush").checked,audio:document.getElementById("eventSubagentAudio").checked},subagent_complete:{enabled:document.getElementById("eventSubagentEnabled").checked,browser:document.getElementById("eventSubagentBrowser").checked,push:document.getElementById("eventSubagentPush").checked,audio:document.getElementById("eventSubagentAudio").checked}},_version:5};this.notificationManager&&(this.notificationManager.preferences=this.notificationManager.normalizePreferences(p),this.notificationManager.savePreferences()),this._syncPushPreferences(),this.applyHeaderVisibilitySettings(),this.applySkin(),this.applyLocalization(),this.applySessionListLayout(),this.applyTabOrientation({settleRailWidth:!0}),this.applyLineageLineSettings?.(),this._updateTokensImmediate(),this.applyMonitorVisibility(),this.renderApprovals?.(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this._updateCjkInputState(),this.activeSessionId||this.showWelcome(),KeyboardAccessoryBar.setMode(o.extendedKeyboardBar?"extended":"simple");const d=this.planUsageCollectionFlip(n,o.showPlanUsageLimits),{localEchoEnabled:r,keyboardHoldsRows:u,cjkInputEnabled:h,extendedKeyboardBar:f,skin:m,language:g,showPlanUsageLimits:y,showAttachmentsButton:S,showFileViewerButton:b,showSplitButton:E,showTileGridButton:w,webglRendererEnabled:v,terminalWheelLocalScrollback:B,autoCopySelection:k,copyStripMargin:I,terminalFontFamily:L,terminalFontWeight:U,terminalFontWeightBold:A,showSessionButton:O,showAwayDigestButton:P,showCronButton:N,showGitStatus:R,showGpuStats:W,showCpuMhz:x,gitStatusTree:$,gitStatusMaxRepos:V,gitStatusTimeoutSeconds:F,showEditHistory:G,showTabDetachButton:z,showTabNewButton:J,tileDetachEnabled:Y,mobileOverviewEnabled:X,sessionLineageLines:Z,shortcutOverrides:ee,...q}=o;let M="",C="",j=!1;try{const H=o.tunnelEnabled&&!a?await this._confirmTunnelEnable():{proceed:!0,acknowledge:!1};if(!H.proceed){this._unsavedSettingsBaseline=n,this._abandonTunnelEnable(o,e);return}const K={...q,...d!==void 0?{showPlanUsageLimits:d}:{},...H.acknowledge?{acknowledgeUnauthTunnel:!0}:{},notificationPreferences:p,voiceSettings:c},T=a?null:await this._apiPut("/api/settings",K),_=!a&&await this._noteSettingsWriteRefusal(T);_&&(this._keepServerSyncedSettings(o,t),this.saveAppSettingsToStorage(o));const D=!_&&o.tunnelEnabled&&T?.status===403;if(D&&await this._handleTunnelEnableRefusal(T,K)){this._unsavedSettingsBaseline=n,this._abandonTunnelEnable(o,e);return}j=!!T?.ok||D,C=a||_||D?"":await this._settingsWriteError(T),this._unsavedSettingsBaseline=C?n:null;const Q=await this.saveModelConfigFromSettings()===!0;M=this._webhookPending()?await this.saveWebhook():"",C?this.showToast(`Saved on this device, not on the server: ${C}`,"warning",{duration:0}):M?this.showToast(`Settings saved, but not the webhook: ${M}`,"warning"):l||Q||_?this.showToast("Saved on this device. Server-wide settings can only be changed by an admin.","warning"):this.showToast(e?"Settings applied":"Settings saved","success"),o.tunnelEnabled&&!a&&!_&&!C&&this.showToast("Tunnel starting \u2014 QR code will appear when ready...","info")}catch{this.showToast("Settings saved locally","warning")}e&&j&&this._refreshSettingsAfterApply(o),M?document.getElementById("webhookGroup")?.scrollIntoView({block:"center"}):!e&&!C&&this.closeAppSettings(),VoiceInput.refreshClaudeStatus(),!C&&(o.gestureControlEnabled??!1)!==s&&(this.showToast(o.gestureControlEnabled?"Enabling gesture control \u2014 reloading\u2026":"Disabling gesture control \u2014 reloading\u2026","info"),setTimeout(()=>location.reload(),400))},_settingsReadOnly(){if(this._settingsWriteForbidden===!0)return!0;const e=typeof window<"u"&&window.__codemanUser||{};return!!(e.multiUser&&e.role!=="admin")},async _noteSettingsWriteRefusal(e){if(!e||e.status!==403)return!1;let t=null;try{t=await(typeof e.clone=="function"?e.clone():e).json()}catch{return!1}return t?.errorCode!=="FORBIDDEN"?!1:(this._settingsWriteForbidden=!0,!0)},async _settingsWriteError(e){if(!e)return"the server could not be reached";if(e.ok)return"";let t=null;try{t=await e.clone().json()}catch{}return t?.error||`HTTP ${e.status}`},async _noteSettingsWriteResult(e,t){if(await this._noteSettingsWriteRefusal(e))return;const n=await this._settingsWriteError(e);n&&this.showToast(`${t} saved on this device, not on the server: ${n}`,"warning",{duration:0})},_ownSettingsKey(){return`codeman:own-settings:${(typeof window<"u"&&window.__codemanUser||{}).username||""}`},_readOwnSettings(){try{const e=JSON.parse(localStorage.getItem(this._ownSettingsKey())||"{}");return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}catch{return{}}},_rememberOwnSetting(e,t){try{const n=this._readOwnSettings();n[e]=t,localStorage.setItem(this._ownSettingsKey(),JSON.stringify(n))}catch{}},_applyOwnSettings(){if(!this._settingsReadOnly())return;const e=this._readOwnSettings();if(Array.isArray(e.respawnPresets)){this._serverRespawnPresets=e.respawnPresets;try{localStorage.setItem("codeman-respawn-presets",JSON.stringify(e.respawnPresets))}catch{}}if(typeof e.runMode=="string"&&e.runMode){this.runMode=e.runMode;try{localStorage.setItem("codeman_runMode",e.runMode)}catch{}this._applyRunMode?.()}},_captureSettingsFormBaseline(){if(this._settingsFormBaseline=null,!this._settingsReadOnly())return;this._readSettingsFormOnce=!0;let e;try{e=this.saveAppSettings()}finally{this._readSettingsFormOnce=!1}Promise.resolve(e).then(t=>{this._settingsFormBaseline=t&&typeof t=="object"?t:null}).catch(()=>{})},_keepServerSyncedSettings(e,t){const n=this._displaySettingKeySet(),s=this._settingsFormBaseline||t||{};let i=!1;for(const o of Object.keys(e))n.has(o)||o==="shortcutOverrides"||(o in s&&JSON.stringify(e[o])!==JSON.stringify(s[o])&&(i=!0),t&&o in t?e[o]=t[o]:delete e[o]);return i},_refreshSettingsAfterApply(e){this._mcpSyncSavedOn=e.mcpSyncEnabled===!0;const t=this.$("mcpSyncResult");this._mcpSyncSavedOn&&t&&t.dataset?.hint==="save-first"&&(t.style.display="none",t.innerHTML=""),this.applyMcpSyncVisibility(),this.applyCustomModelEndpointsVisibility(),this.applyCliManagementVisibility(),this._applyDoctorAdminGate()},async loadModelConfigForSettings(){try{const t=await(await fetch("/api/execution/model-config")).json();if(t.success&&t.data){const n=t.data,s=document.getElementById("appSettingsDefaultModel");s&&(s.value=n.defaultModel||"");const i=document.getElementById("appSettingsShowModelRecommendations");i&&(i.checked=n.showRecommendations??!0);const o=n.agentTypeOverrides||{},a=document.getElementById("appSettingsModelExplore"),l=document.getElementById("appSettingsModelImplement"),c=document.getElementById("appSettingsModelTest"),p=document.getElementById("appSettingsModelReview");a&&(a.value=o.explore||""),l&&(l.value=o.implement||""),c&&(c.value=o.test||""),p&&(p.value=o.review||"")}}catch(e){console.warn("Failed to load model config:",e)}this._modelConfigBaseline=JSON.stringify(this._modelConfigFromSettingsForm())},_modelConfigFromSettingsForm(){const e=document.getElementById("appSettingsDefaultModel"),t=document.getElementById("appSettingsShowModelRecommendations"),n=document.getElementById("appSettingsModelExplore"),s=document.getElementById("appSettingsModelImplement"),i=document.getElementById("appSettingsModelTest"),o=document.getElementById("appSettingsModelReview"),a={};return n?.value&&(a.explore=n.value),s?.value&&(a.implement=s.value),i?.value&&(a.test=i.value),o?.value&&(a.review=o.value),{defaultModel:e?.value||"",showRecommendations:t?.checked??!0,agentTypeOverrides:a}},async saveModelConfigFromSettings(){const e=this._modelConfigFromSettingsForm();if(this._settingsReadOnly()){const t=this._modelConfigBaseline;return typeof t=="string"&&JSON.stringify(e)!==t}try{await fetch("/api/execution/model-config",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch(t){console.warn("Failed to save model config:",t)}},applyCustomModelEndpointsVisibility(){const e=document.getElementById("appSettingsCustomModelEndpoints").checked,t=document.getElementById("customModelEndpointsBody");t&&(t.style.display=e?"":"none"),e?this.loadCustomModelEndpointsForSettings():this.closeCustomModelHostEditor(),this._applyCustomModelAdminGate()},_applyCustomModelAdminGate(){const e=document.getElementById("customModelHostAddBtn");if(!e)return;const t=window.__codemanUser||{},n=t.multiUser&&t.role!=="admin";e.style.display=n?"none":""},async loadCustomModelEndpointsForSettings(){const e=await this._apiJson("/api/model-endpoints");this._customModelHosts=Array.isArray(e)?e:[],this.renderCustomModelHostsList()},renderCustomModelHostsList(){const e=document.getElementById("customModelHostsList");if(!e)return;const t=this._customModelHosts||[];if(t.length===0){e.innerHTML='<p class="set-group-hint">No endpoints yet. Add one below to point a harness at a local or cloud OpenAI-compatible server.</p>';return}e.innerHTML=t.map(n=>{const s=(n.models||[]).length,i=s===0?"No models discovered yet":`${s} model${s===1?"":"s"}${n.defaultModelId?` \xB7 default: ${escapeHtml(n.defaultModelId)}`:" \xB7 no default set"}`,o=escapeHtml(JSON.stringify(n.id));return`
73
73
  <div class="set-row" data-endpoint-id="${escapeHtml(n.id)}">
74
74
  <div class="set-row-text">
75
75
  <span class="set-row-label">${escapeHtml(n.label)}</span>