aicodeman 1.33.2 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/dist/config/cli-registry/schema.d.ts +18 -0
  2. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  3. package/dist/config/cli-registry/schema.js +27 -0
  4. package/dist/config/cli-registry/schema.js.map +1 -1
  5. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  6. package/dist/config/cli-registry/stock.js +35 -0
  7. package/dist/config/cli-registry/stock.js.map +1 -1
  8. package/dist/config/cli-registry/types.d.ts +33 -0
  9. package/dist/config/cli-registry/types.d.ts.map +1 -1
  10. package/dist/hooks-config.d.ts.map +1 -1
  11. package/dist/hooks-config.js +5 -1
  12. package/dist/hooks-config.js.map +1 -1
  13. package/dist/mcp-sync.d.ts +105 -0
  14. package/dist/mcp-sync.d.ts.map +1 -0
  15. package/dist/mcp-sync.js +619 -0
  16. package/dist/mcp-sync.js.map +1 -0
  17. package/dist/mux-interface.d.ts +4 -0
  18. package/dist/mux-interface.d.ts.map +1 -1
  19. package/dist/session-cli-builder.d.ts +20 -1
  20. package/dist/session-cli-builder.d.ts.map +1 -1
  21. package/dist/session-cli-builder.js +33 -3
  22. package/dist/session-cli-builder.js.map +1 -1
  23. package/dist/session-cli-registry-bridge.d.ts +2 -0
  24. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  25. package/dist/session-cli-registry-bridge.js +15 -8
  26. package/dist/session-cli-registry-bridge.js.map +1 -1
  27. package/dist/session.d.ts +19 -0
  28. package/dist/session.d.ts.map +1 -1
  29. package/dist/session.js +29 -2
  30. package/dist/session.js.map +1 -1
  31. package/dist/tmux-manager.d.ts +2 -0
  32. package/dist/tmux-manager.d.ts.map +1 -1
  33. package/dist/tmux-manager.js +4 -2
  34. package/dist/tmux-manager.js.map +1 -1
  35. package/dist/types/index.d.ts +3 -1
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/index.js +3 -1
  38. package/dist/types/index.js.map +1 -1
  39. package/dist/types/mcp-sync.d.ts +40 -0
  40. package/dist/types/mcp-sync.d.ts.map +1 -0
  41. package/dist/types/mcp-sync.js +8 -0
  42. package/dist/types/mcp-sync.js.map +1 -0
  43. package/dist/types/push.d.ts +37 -2
  44. package/dist/types/push.d.ts.map +1 -1
  45. package/dist/types/push.js +10 -3
  46. package/dist/types/push.js.map +1 -1
  47. package/dist/types/session.d.ts +34 -1
  48. package/dist/types/session.d.ts.map +1 -1
  49. package/dist/types/session.js +25 -1
  50. package/dist/types/session.js.map +1 -1
  51. package/dist/web/public/admin-ui.js.gz +0 -0
  52. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  53. package/dist/web/public/app.cf0db8ce.js +43 -0
  54. package/dist/web/public/app.cf0db8ce.js.br +0 -0
  55. package/dist/web/public/app.cf0db8ce.js.gz +0 -0
  56. package/dist/web/public/approvals-ui.js.gz +0 -0
  57. package/dist/web/public/{constants.cc927eb8.js → constants.d95fade9.js} +19 -2
  58. package/dist/web/public/constants.d95fade9.js.br +0 -0
  59. package/dist/web/public/constants.d95fade9.js.gz +0 -0
  60. package/dist/web/public/cron-ui.js.gz +0 -0
  61. package/dist/web/public/entrance-animations.js.gz +0 -0
  62. package/dist/web/public/home-sessions.js.gz +0 -0
  63. package/dist/web/public/host-wake-ui.js.gz +0 -0
  64. package/dist/web/public/i18n.398b8331.js +1 -0
  65. package/dist/web/public/i18n.398b8331.js.br +0 -0
  66. package/dist/web/public/i18n.398b8331.js.gz +0 -0
  67. package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
  68. package/dist/web/public/index.html +125 -14
  69. package/dist/web/public/index.html.br +0 -0
  70. package/dist/web/public/index.html.gz +0 -0
  71. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  72. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
  73. package/dist/web/public/mobile-handlers.23a85db1.js.gz +0 -0
  74. package/dist/web/public/mobile-ime-preview.2dbaf262.js +1 -0
  75. package/dist/web/public/mobile-ime-preview.2dbaf262.js.br +0 -0
  76. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  77. package/dist/web/public/mobile-overview.js.gz +0 -0
  78. package/dist/web/public/mobile.1aa4952d.css.gz +0 -0
  79. package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
  80. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  81. package/dist/web/public/{panels-ui.67646701.js → panels-ui.6a5898d9.js} +69 -67
  82. package/dist/web/public/panels-ui.6a5898d9.js.br +0 -0
  83. package/dist/web/public/panels-ui.6a5898d9.js.gz +0 -0
  84. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  85. package/dist/web/public/{ralph-wizard.13a1831e.js → ralph-wizard.b6c0dd19.js} +2 -0
  86. package/dist/web/public/ralph-wizard.b6c0dd19.js.br +0 -0
  87. package/dist/web/public/{ralph-wizard.13a1831e.js.gz → ralph-wizard.b6c0dd19.js.gz} +0 -0
  88. package/dist/web/public/readmymind-ui.js.gz +0 -0
  89. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  90. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  91. package/dist/web/public/sanitize-html.e7147e76.js +1 -0
  92. package/dist/web/public/sanitize-html.e7147e76.js.br +0 -0
  93. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  94. package/dist/web/public/session-lineage.js +27 -11
  95. package/dist/web/public/session-lineage.js.br +0 -0
  96. package/dist/web/public/session-lineage.js.gz +0 -0
  97. package/dist/web/public/{session-ui.7575a231.js → session-ui.bf66c13b.js} +4 -4
  98. package/dist/web/public/session-ui.bf66c13b.js.br +0 -0
  99. package/dist/web/public/session-ui.bf66c13b.js.gz +0 -0
  100. package/dist/web/public/{settings-ui.c68e7f59.js → settings-ui.331d3b1d.js} +12 -10
  101. package/dist/web/public/settings-ui.331d3b1d.js.br +0 -0
  102. package/dist/web/public/settings-ui.331d3b1d.js.gz +0 -0
  103. package/dist/web/public/styles.8c485d26.css +1 -0
  104. package/dist/web/public/styles.8c485d26.css.br +0 -0
  105. package/dist/web/public/styles.8c485d26.css.gz +0 -0
  106. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  107. package/dist/web/public/sw.js +2 -2
  108. package/dist/web/public/sw.js.br +0 -0
  109. package/dist/web/public/sw.js.gz +0 -0
  110. package/dist/web/public/tab-layout-browser.4e0ed50c.js +1 -0
  111. package/dist/web/public/tab-layout-browser.4e0ed50c.js.br +0 -0
  112. package/dist/web/public/tab-layout-browser.4e0ed50c.js.gz +0 -0
  113. package/dist/web/public/tab-rail-resize.dcdc5772.js.gz +0 -0
  114. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
  115. package/dist/web/public/terminal-split.js +248 -15
  116. package/dist/web/public/terminal-split.js.br +0 -0
  117. package/dist/web/public/terminal-split.js.gz +0 -0
  118. package/dist/web/public/terminal-ui.a4535311.js +2 -0
  119. package/dist/web/public/terminal-ui.a4535311.js.br +0 -0
  120. package/dist/web/public/terminal-ui.a4535311.js.gz +0 -0
  121. package/dist/web/public/ultracode-panel.js.gz +0 -0
  122. package/dist/web/public/ultracode-windows.js.gz +0 -0
  123. package/dist/web/public/upload.html.gz +0 -0
  124. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  125. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  126. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  127. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  128. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  129. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  130. package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js → xterm-predictive-echo.ae5e3d5d.js} +1 -1
  131. package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js.br → xterm-predictive-echo.ae5e3d5d.js.br} +0 -0
  132. package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js.gz → xterm-predictive-echo.ae5e3d5d.js.gz} +0 -0
  133. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js +4 -0
  134. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.br +0 -0
  135. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.gz +0 -0
  136. package/dist/web/public/vendor/xterm.css.gz +0 -0
  137. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  138. package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
  139. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  140. package/dist/web/public/webview-tabs.js +36 -14
  141. package/dist/web/public/webview-tabs.js.br +0 -0
  142. package/dist/web/public/webview-tabs.js.gz +0 -0
  143. package/dist/web/reboot-restore-registry.d.ts +2 -1
  144. package/dist/web/reboot-restore-registry.d.ts.map +1 -1
  145. package/dist/web/reboot-restore-registry.js +2 -1
  146. package/dist/web/reboot-restore-registry.js.map +1 -1
  147. package/dist/web/routes/file-routes.d.ts.map +1 -1
  148. package/dist/web/routes/file-routes.js +2 -1
  149. package/dist/web/routes/file-routes.js.map +1 -1
  150. package/dist/web/routes/index.d.ts +2 -0
  151. package/dist/web/routes/index.d.ts.map +1 -1
  152. package/dist/web/routes/index.js +2 -0
  153. package/dist/web/routes/index.js.map +1 -1
  154. package/dist/web/routes/mcp-sync-routes.d.ts +29 -0
  155. package/dist/web/routes/mcp-sync-routes.d.ts.map +1 -0
  156. package/dist/web/routes/mcp-sync-routes.js +83 -0
  157. package/dist/web/routes/mcp-sync-routes.js.map +1 -0
  158. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  159. package/dist/web/routes/ralph-routes.js +2 -1
  160. package/dist/web/routes/ralph-routes.js.map +1 -1
  161. package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -1
  162. package/dist/web/routes/reboot-restore-routes.js +2 -0
  163. package/dist/web/routes/reboot-restore-routes.js.map +1 -1
  164. package/dist/web/routes/session-routes.d.ts.map +1 -1
  165. package/dist/web/routes/session-routes.js +40 -17
  166. package/dist/web/routes/session-routes.js.map +1 -1
  167. package/dist/web/routes/webhook-routes.d.ts +21 -0
  168. package/dist/web/routes/webhook-routes.d.ts.map +1 -0
  169. package/dist/web/routes/webhook-routes.js +87 -0
  170. package/dist/web/routes/webhook-routes.js.map +1 -0
  171. package/dist/web/schemas.d.ts +47 -2
  172. package/dist/web/schemas.d.ts.map +1 -1
  173. package/dist/web/schemas.js +64 -0
  174. package/dist/web/schemas.js.map +1 -1
  175. package/dist/web/server.d.ts +4 -0
  176. package/dist/web/server.d.ts.map +1 -1
  177. package/dist/web/server.js +55 -25
  178. package/dist/web/server.js.map +1 -1
  179. package/dist/webhook-notify.d.ts +71 -0
  180. package/dist/webhook-notify.d.ts.map +1 -0
  181. package/dist/webhook-notify.js +282 -0
  182. package/dist/webhook-notify.js.map +1 -0
  183. package/package.json +2 -1
  184. package/skills/codeman/SKILL.md +26 -8
  185. package/skills/codeman/preamble.sh +16 -4
  186. package/skills/codeman/reference/endpoints.md +11 -3
  187. package/skills/codeman/reference/recipes.md +1 -1
  188. package/dist/web/public/app.91131d2c.js +0 -43
  189. package/dist/web/public/app.91131d2c.js.br +0 -0
  190. package/dist/web/public/app.91131d2c.js.gz +0 -0
  191. package/dist/web/public/constants.cc927eb8.js.br +0 -0
  192. package/dist/web/public/constants.cc927eb8.js.gz +0 -0
  193. package/dist/web/public/i18n.f094d01f.js +0 -1
  194. package/dist/web/public/i18n.f094d01f.js.br +0 -0
  195. package/dist/web/public/i18n.f094d01f.js.gz +0 -0
  196. package/dist/web/public/panels-ui.67646701.js.br +0 -0
  197. package/dist/web/public/panels-ui.67646701.js.gz +0 -0
  198. package/dist/web/public/ralph-wizard.13a1831e.js.br +0 -0
  199. package/dist/web/public/sanitize-html.bc7078d6.js +0 -1
  200. package/dist/web/public/sanitize-html.bc7078d6.js.br +0 -0
  201. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  202. package/dist/web/public/session-ui.7575a231.js.br +0 -0
  203. package/dist/web/public/session-ui.7575a231.js.gz +0 -0
  204. package/dist/web/public/settings-ui.c68e7f59.js.br +0 -0
  205. package/dist/web/public/settings-ui.c68e7f59.js.gz +0 -0
  206. package/dist/web/public/styles.3fe3312d.css +0 -1
  207. package/dist/web/public/styles.3fe3312d.css.br +0 -0
  208. package/dist/web/public/styles.3fe3312d.css.gz +0 -0
  209. package/dist/web/public/terminal-ui.d5223c0e.js +0 -2
  210. package/dist/web/public/terminal-ui.d5223c0e.js.br +0 -0
  211. package/dist/web/public/terminal-ui.d5223c0e.js.gz +0 -0
  212. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js +0 -4
  213. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.br +0 -0
  214. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
@@ -1038,6 +1038,7 @@ Object.assign(CodemanApp.prototype, {
1038
1038
  const ralphGlobalSettings = this.loadAppSettingsFromStorage();
1039
1039
  const envOverrides = this.buildEnvOverrides(this.getCaseSettings(config.caseName), ralphGlobalSettings);
1040
1040
  const effort = this.getEffortSetting(ralphGlobalSettings);
1041
+ const advisorModel = this.getAdvisorSetting(ralphGlobalSettings);
1041
1042
  const res = await fetch('/api/ralph-loop/start', {
1042
1043
  method: 'POST',
1043
1044
  headers: { 'Content-Type': 'application/json' },
@@ -1050,6 +1051,7 @@ Object.assign(CodemanApp.prototype, {
1050
1051
  planItems: enabledItems?.length ? enabledItems : undefined,
1051
1052
  ...(Object.keys(envOverrides).length > 0 ? { envOverrides } : {}),
1052
1053
  ...(effort ? { effort } : {}),
1054
+ ...(advisorModel ? { advisorModel } : {}),
1053
1055
  }),
1054
1056
  });
1055
1057
  const data = await res.json();
Binary file
@@ -0,0 +1 @@
1
+ "use strict";(function(e){"use strict";var n=["a","b","blockquote","br","caption","code","del","div","em","h1","h2","h3","h4","h5","h6","hr","i","img","ins","kbd","li","mark","ol","p","pre","q","s","samp","span","strong","sub","sup","table","tbody","td","tfoot","th","thead","tr","ul","var"],a=["href","src","alt","title","class","id","colspan","rowspan","align","width","height","lang","dir","start","reversed","type"];function i(t){if(!t||typeof t.sanitize!="function")throw new Error("createMarkdownSanitizer: a DOMPurify instance is required");var s={ALLOWED_TAGS:n,ALLOWED_ATTR:a,FORBID_TAGS:["style","svg","math","script","iframe","object","embed","form"],FORBID_ATTR:["style"],ALLOW_DATA_ATTR:!1,ADD_ATTR:[],RETURN_DOM:!1,RETURN_DOM_FRAGMENT:!1,KEEP_CONTENT:!0};return function(r){return t.sanitize(r==null?"":String(r),s)}}e&&(e.createMarkdownSanitizer=i,e.DOMPurify&&typeof e.DOMPurify.sanitize=="function"&&(e.sanitizeMarkdownHtml=i(e.DOMPurify))),typeof module<"u"&&module.exports&&(module.exports={createMarkdownSanitizer:i,ALLOWED_TAGS:n,ALLOWED_ATTR:a})})(typeof globalThis<"u"?globalThis:typeof window<"u"?window:void 0);
@@ -153,7 +153,6 @@ Object.assign(CodemanApp.prototype, {
153
153
 
154
154
  const edges = this._collectLineageEdges();
155
155
  if (edges.length === 0) return;
156
- this._lineageEdgeCount = edges.length;
157
156
  if (!rects) rects = new Map();
158
157
 
159
158
  // PHASE 1 — reads.
@@ -162,19 +161,35 @@ Object.assign(CodemanApp.prototype, {
162
161
  const stripRect = strip.getBoundingClientRect();
163
162
  const orientation =
164
163
  document.documentElement.getAttribute('data-tab-orientation') === 'vertical' ? 'vertical' : 'horizontal';
164
+ // A session hidden inside a collapsed group of the grouped rail has no row
165
+ // to anchor to, so its end of the arc moves to that group's header (a
166
+ // "proxied" endpoint, drawn quieter). Two endpoints proxied to the SAME
167
+ // header would be an arc from a row to itself: skipped.
168
+ const resolveEndpoint = (id) => {
169
+ const tab = strip.querySelector(`.session-tab[data-id="${CSS.escape(id)}"]`);
170
+ if (tab) return { key: 'tab:' + id, element: tab, proxied: false };
171
+ const groupId = this._hiddenTabGroupByRef?.get('session:' + id);
172
+ if (!groupId) return { key: 'tab:' + id, element: null, proxied: false };
173
+ const header = strip.querySelector(`[data-tab-group-header="${CSS.escape(groupId)}"]`);
174
+ return { key: 'group:' + groupId, element: header, proxied: !!header };
175
+ };
176
+ const resolvedEdges = [];
165
177
  for (const edge of edges) {
166
- for (const id of [edge.parentId, edge.childId]) {
167
- const key = 'tab:' + id;
168
- if (rects.has(key)) continue;
169
- const tab = strip.querySelector(`.session-tab[data-id="${CSS.escape(id)}"]`);
170
- rects.set(key, tab ? tab.getBoundingClientRect() : null);
178
+ const parentEndpoint = resolveEndpoint(edge.parentId);
179
+ const childEndpoint = resolveEndpoint(edge.childId);
180
+ if (parentEndpoint.key === childEndpoint.key) continue;
181
+ resolvedEdges.push({ edge, parentEndpoint, childEndpoint });
182
+ for (const endpoint of [parentEndpoint, childEndpoint]) {
183
+ if (rects.has(endpoint.key)) continue;
184
+ rects.set(endpoint.key, endpoint.element ? endpoint.element.getBoundingClientRect() : null);
171
185
  }
172
186
  }
187
+ this._lineageEdgeCount = resolvedEdges.length;
173
188
 
174
189
  // PHASE 2 — writes, from the cache only.
175
- for (const edge of edges) {
176
- const parentRect = rects.get('tab:' + edge.parentId);
177
- const childRect = rects.get('tab:' + edge.childId);
190
+ for (const { edge, parentEndpoint, childEndpoint } of resolvedEdges) {
191
+ const parentRect = rects.get(parentEndpoint.key);
192
+ const childRect = rects.get(childEndpoint.key);
178
193
  if (!parentRect || !childRect) continue;
179
194
 
180
195
  const geom = compute({
@@ -191,7 +206,8 @@ Object.assign(CodemanApp.prototype, {
191
206
  // The working class marches the dashes, so an active worker is visible along
192
207
  // the line itself. `status` is the CHILD's, which is the interesting end.
193
208
  const working = edge.status === 'working' ? ' lineage-line--working' : '';
194
- line.setAttribute('class', 'connection-line lineage-line' + working);
209
+ const proxied = parentEndpoint.proxied || childEndpoint.proxied;
210
+ line.setAttribute('class', 'connection-line lineage-line' + working + (proxied ? ' lineage-line--proxied' : ''));
195
211
  // The PARENT's colour rides a CSS custom property so the stylesheet keeps owning
196
212
  // opacity, glow and dash; an empty colour leaves the --session-blue fallback.
197
213
  // Every arc out of one tab shares it — see _lineageColorFor().
@@ -211,7 +227,7 @@ Object.assign(CodemanApp.prototype, {
211
227
  // Resting radius; `lineage-dot-pulse` breathes it 3.5 → 4.5 while the child
212
228
  // works, so the two have to be changed together.
213
229
  dot.setAttribute('r', '3.5');
214
- dot.setAttribute('class', 'lineage-line-dot' + working);
230
+ dot.setAttribute('class', 'lineage-line-dot' + working + (proxied ? ' lineage-line-dot--proxied' : ''));
215
231
  dot.setAttribute('data-child-tab', edge.childId);
216
232
  if (color) dot.style.setProperty('--lineage-color', color);
217
233
  svg.appendChild(dot);
@@ -1,4 +1,4 @@
1
- "use strict";const RUN_MODE_LAUNCH={opencode:{label:"OpenCode",installHint:"OpenCode CLI not found. Install with: curl -fsSL https://opencode.ai/install | bash",supportsCustomModel:!0,buildConfig:()=>({openCodeConfig:{autoAllowTools:!0}})},codex:{label:"Codex",installHint:"Codex CLI not found. Install with: npm install -g @openai/codex",supportsCustomModel:!0,buildConfig:e=>({codexConfig:{dangerouslyBypassApprovals:e.codexDangerouslyBypassApprovals??!1,animations:e.codexAnimationsEnabled??!1,renderMode:"hybrid"}})},gemini:{label:"Gemini",installHint:"Gemini CLI not found. Install with: npm install -g @google/gemini-cli",supportsCustomModel:!0,buildConfig:()=>({geminiConfig:{approvalMode:"yolo"}})},antigravity:{label:"Antigravity",installHint:"Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash",supportsCustomModel:!1,buildConfig:()=>({antigravityConfig:{dangerouslySkipPermissions:!0}})},pi:{label:"Pi",installHint:"Pi CLI not found. Install with: npm install -g --ignore-scripts @earendil-works/pi-coding-agent",supportsCustomModel:!0,buildConfig:()=>null},omp:{label:"OMP",installHint:"OMP CLI not found. Install with: curl -fsSL https://omp.sh/install | sh",supportsCustomModel:!0,buildConfig:()=>null},grok:{label:"Grok",installHint:"Grok CLI not found. Install with: curl -fsSL https://x.ai/cli/install.sh | bash",supportsCustomModel:!0,buildConfig:()=>({grokConfig:{alwaysApprove:!0}})},deepseek:{label:"DeepSeek",installHint:"DeepSeek Harness CLI (dsh) not found. Install with: npm install -g @deepseek-ai/dsh",unrunnableHint:"No interactive DeepSeek Harness profile is installed. DeepSeek ships only web and headless profiles, so the terminal agent comes from a plugin. Install one from the Run menu, or run: dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui",supportsCustomModel:!0,buildConfig:()=>({deepSeekConfig:{permissionMode:"danger-full-access"}})}},CASE_PICKER_REFRESH_MS=5e3,EXTERNAL_CLI_MODES=new Set(Object.keys(RUN_MODE_LAUNCH)),BUILT_IN_RUN_MODES=new Set(["claude","shell",...Object.keys(RUN_MODE_LAUNCH)]);function registryCliCatalog(){return typeof window<"u"&&Array.isArray(window.__codemanCliCatalog)?window.__codemanCliCatalog:[]}function registryCliById(e){return registryCliCatalog().find(t=>t.id===e)}function isExternalCliRunMode(e){return EXTERNAL_CLI_MODES.has(e)||registryCliById(e)?.kind==="agent"}Object.assign(CodemanApp.prototype,{buildEnvOverrides(e,t){const s={};return(e?.agentTeams||t?.agentTeamsEnabled)&&(s.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1"),s},getEffortSetting(e){const t=e?.thinkingEffort;return["low","medium","high","xhigh","max","ultracode"].includes(t)?t:void 0},formatCasePickerLabel(e){return e?.location==="remote"&&e.remote?.hostId?`${e.name} @ ${e.remote.hostId}`:e?.location==="docker"?`${e.name} (${this.dockerCaseTag(e.docker?.hostId)})`:e?.name||""},dockerCaseTag(e){return!e||e==="default"||e==="local"||/^q-/.test(e)?"docker":e},buildCasePickerOptions(e=[]){const t=[],s=new Set;for(const o of e)!o?.name||s.has(o.name)||(s.add(o.name),t.push(o));return s.has("testcase")||t.push({name:"testcase"}),t.map(o=>{const n=this.formatCasePickerLabel(o),a=[o.name,n,o.path,o.location,o.remote?.hostId,o.remote?.label,o.remote?.path,o.docker?.container,o.docker?.image,o.docker?.path].filter(Boolean).join(" ").toLowerCase();return{name:o.name,label:n,case:o,searchText:a}}).sort((o,n)=>o.label.localeCompare(n.label,void 0,{sensitivity:"base",numeric:!0}))},filterCasePickerOptions(e,t){const s=String(t||"").trim().toLowerCase().split(/\s+/).filter(Boolean);return s.length===0?e:e.filter(o=>s.every(n=>o.searchText.includes(n)))},getCasePickerOptions(){return this.buildCasePickerOptions(this.cases||[])},updateCasePickerInput(e){const t=document.getElementById("quickStartCaseSearch");if(!t)return;const s=this.getCasePickerOptions().find(o=>o.name===e);t.value=s?.label||e||"testcase",t.title=s?.label||t.value},renderQuickStartCaseSelectOptions(e,t){e&&(e.innerHTML=t.map(s=>`<option value="${escapeHtml(s.name)}">${escapeHtml(s.label)}</option>`).join(""))},openCasePicker(e=""){const t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList");if(!t||!s)return;const o=this._casePickerOpen===!0;this._casePickerOpen=!0,this._casePickerFilter=e,this._casePickerActiveIndex=0,t.setAttribute("aria-expanded","true"),this.renderCasePickerList(),o||this._startCasePickerRefresh()},_startCasePickerRefresh(){this.refreshCasePickerCases(),!this._casePickerRefreshTimer&&(this._casePickerRefreshTimer=setInterval(()=>{this.refreshCasePickerCases()},CASE_PICKER_REFRESH_MS))},_stopCasePickerRefresh(){this._casePickerRefreshTimer&&clearInterval(this._casePickerRefreshTimer),this._casePickerRefreshTimer=null},async refreshCasePickerCases(){if(!this._casePickerRefreshInFlight){this._casePickerRefreshInFlight=!0;try{const e=await fetch("/api/cases");if(!e.ok)return;const t=(await e.json()).data;if(!Array.isArray(t)||!this._casePickerOpen)return;const s=n=>JSON.stringify((n||[]).map(a=>[a.name,a.path,a.location]));if(s(t)===s(this.cases))return;this.cases=t;const o=document.getElementById("quickStartCase");if(o){const n=o.value;if(this.renderQuickStartCaseSelectOptions(o,this.getCasePickerOptions()),t.some(a=>a.name===n))o.value=n;else if(t.length>0){const a=t.find(i=>i.name==="testcase")||t[0];o.value=a.name,this.updateDirDisplayForCase(a.name),this.updateMobileCaseLabel(a.name),this.updateCasePickerInput(a.name)}}this.renderCasePickerList()}catch{}finally{this._casePickerRefreshInFlight=!1}}},closeCasePicker(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList");this._stopCasePickerRefresh(),this._casePickerOpen=!1,this._casePickerFilter="",e?.setAttribute("aria-expanded","false"),e?.removeAttribute("aria-activedescendant"),t?.classList.add("hidden")},renderCasePickerList(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList"),s=document.getElementById("quickStartCase");if(!e||!t||!s)return;const o=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||""),n=s.value||"testcase",a=Math.max(0,o.length-1);if(this._casePickerActiveIndex=Math.min(Math.max(this._casePickerActiveIndex||0,0),a),o.length===0){t.innerHTML='<div class="case-combobox-empty">No cases match</div>',t.classList.remove("hidden"),e.removeAttribute("aria-activedescendant");return}t.innerHTML=o.map((i,r)=>{const c=r===this._casePickerActiveIndex,l=i.name===n;return`
1
+ "use strict";const RUN_MODE_LAUNCH={opencode:{label:"OpenCode",installHint:"OpenCode CLI not found. Install with: curl -fsSL https://opencode.ai/install | bash",supportsCustomModel:!0,buildConfig:()=>({openCodeConfig:{autoAllowTools:!0}})},codex:{label:"Codex",installHint:"Codex CLI not found. Install with: npm install -g @openai/codex",supportsCustomModel:!0,buildConfig:e=>({codexConfig:{dangerouslyBypassApprovals:e.codexDangerouslyBypassApprovals??!1,animations:e.codexAnimationsEnabled??!1,renderMode:"hybrid"}})},gemini:{label:"Gemini",installHint:"Gemini CLI not found. Install with: npm install -g @google/gemini-cli",supportsCustomModel:!0,buildConfig:()=>({geminiConfig:{approvalMode:"yolo"}})},antigravity:{label:"Antigravity",installHint:"Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash",supportsCustomModel:!1,buildConfig:()=>({antigravityConfig:{dangerouslySkipPermissions:!0}})},pi:{label:"Pi",installHint:"Pi CLI not found. Install with: npm install -g --ignore-scripts @earendil-works/pi-coding-agent",supportsCustomModel:!0,buildConfig:()=>null},omp:{label:"OMP",installHint:"OMP CLI not found. Install with: curl -fsSL https://omp.sh/install | sh",supportsCustomModel:!0,buildConfig:()=>null},grok:{label:"Grok",installHint:"Grok CLI not found. Install with: curl -fsSL https://x.ai/cli/install.sh | bash",supportsCustomModel:!0,buildConfig:()=>({grokConfig:{alwaysApprove:!0}})},deepseek:{label:"DeepSeek",installHint:"DeepSeek Harness CLI (dsh) not found. Install with: npm install -g @deepseek-ai/dsh",unrunnableHint:"No interactive DeepSeek Harness profile is installed. DeepSeek ships only web and headless profiles, so the terminal agent comes from a plugin. Install one from the Run menu, or run: dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui",supportsCustomModel:!0,buildConfig:()=>({deepSeekConfig:{permissionMode:"danger-full-access"}})}},CASE_PICKER_REFRESH_MS=5e3,EXTERNAL_CLI_MODES=new Set(Object.keys(RUN_MODE_LAUNCH)),BUILT_IN_RUN_MODES=new Set(["claude","shell",...Object.keys(RUN_MODE_LAUNCH)]);function registryCliCatalog(){return typeof window<"u"&&Array.isArray(window.__codemanCliCatalog)?window.__codemanCliCatalog:[]}function registryCliById(e){return registryCliCatalog().find(t=>t.id===e)}function isExternalCliRunMode(e){return EXTERNAL_CLI_MODES.has(e)||registryCliById(e)?.kind==="agent"}Object.assign(CodemanApp.prototype,{buildEnvOverrides(e,t){const s={};return(e?.agentTeams||t?.agentTeamsEnabled)&&(s.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1"),s},getEffortSetting(e){const t=e?.thinkingEffort;return["low","medium","high","xhigh","max","ultracode"].includes(t)?t:void 0},getAdvisorSetting(e){const t=e?.claudeAdvisorModel;return["fable","opus","sonnet"].includes(t)?t:void 0},formatCasePickerLabel(e){return e?.location==="remote"&&e.remote?.hostId?`${e.name} @ ${e.remote.hostId}`:e?.location==="docker"?`${e.name} (${this.dockerCaseTag(e.docker?.hostId)})`:e?.name||""},dockerCaseTag(e){return!e||e==="default"||e==="local"||/^q-/.test(e)?"docker":e},buildCasePickerOptions(e=[]){const t=[],s=new Set;for(const o of e)!o?.name||s.has(o.name)||(s.add(o.name),t.push(o));return s.has("testcase")||t.push({name:"testcase"}),t.map(o=>{const n=this.formatCasePickerLabel(o),a=[o.name,n,o.path,o.location,o.remote?.hostId,o.remote?.label,o.remote?.path,o.docker?.container,o.docker?.image,o.docker?.path].filter(Boolean).join(" ").toLowerCase();return{name:o.name,label:n,case:o,searchText:a}}).sort((o,n)=>o.label.localeCompare(n.label,void 0,{sensitivity:"base",numeric:!0}))},filterCasePickerOptions(e,t){const s=String(t||"").trim().toLowerCase().split(/\s+/).filter(Boolean);return s.length===0?e:e.filter(o=>s.every(n=>o.searchText.includes(n)))},getCasePickerOptions(){return this.buildCasePickerOptions(this.cases||[])},updateCasePickerInput(e){const t=document.getElementById("quickStartCaseSearch");if(!t)return;const s=this.getCasePickerOptions().find(o=>o.name===e);t.value=s?.label||e||"testcase",t.title=s?.label||t.value},renderQuickStartCaseSelectOptions(e,t){e&&(e.innerHTML=t.map(s=>`<option value="${escapeHtml(s.name)}">${escapeHtml(s.label)}</option>`).join(""))},openCasePicker(e=""){const t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList");if(!t||!s)return;const o=this._casePickerOpen===!0;this._casePickerOpen=!0,this._casePickerFilter=e,this._casePickerActiveIndex=0,t.setAttribute("aria-expanded","true"),this.renderCasePickerList(),o||this._startCasePickerRefresh()},_startCasePickerRefresh(){this.refreshCasePickerCases(),!this._casePickerRefreshTimer&&(this._casePickerRefreshTimer=setInterval(()=>{this.refreshCasePickerCases()},CASE_PICKER_REFRESH_MS))},_stopCasePickerRefresh(){this._casePickerRefreshTimer&&clearInterval(this._casePickerRefreshTimer),this._casePickerRefreshTimer=null},async refreshCasePickerCases(){if(!this._casePickerRefreshInFlight){this._casePickerRefreshInFlight=!0;try{const e=await fetch("/api/cases");if(!e.ok)return;const t=(await e.json()).data;if(!Array.isArray(t)||!this._casePickerOpen)return;const s=n=>JSON.stringify((n||[]).map(a=>[a.name,a.path,a.location]));if(s(t)===s(this.cases))return;this.cases=t;const o=document.getElementById("quickStartCase");if(o){const n=o.value;if(this.renderQuickStartCaseSelectOptions(o,this.getCasePickerOptions()),t.some(a=>a.name===n))o.value=n;else if(t.length>0){const a=t.find(i=>i.name==="testcase")||t[0];o.value=a.name,this.updateDirDisplayForCase(a.name),this.updateMobileCaseLabel(a.name),this.updateCasePickerInput(a.name)}}this.renderCasePickerList()}catch{}finally{this._casePickerRefreshInFlight=!1}}},closeCasePicker(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList");this._stopCasePickerRefresh(),this._casePickerOpen=!1,this._casePickerFilter="",e?.setAttribute("aria-expanded","false"),e?.removeAttribute("aria-activedescendant"),t?.classList.add("hidden")},renderCasePickerList(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList"),s=document.getElementById("quickStartCase");if(!e||!t||!s)return;const o=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||""),n=s.value||"testcase",a=Math.max(0,o.length-1);if(this._casePickerActiveIndex=Math.min(Math.max(this._casePickerActiveIndex||0,0),a),o.length===0){t.innerHTML='<div class="case-combobox-empty">No cases match</div>',t.classList.remove("hidden"),e.removeAttribute("aria-activedescendant");return}t.innerHTML=o.map((i,r)=>{const c=r===this._casePickerActiveIndex,l=i.name===n;return`
2
2
  <button
3
3
  type="button"
4
4
  id="${`quickStartCaseOption-${r}`}"
@@ -21,10 +21,10 @@
21
21
  </button>`}).join(""),s.classList.add("active")},closeCustomModelPickModal(){document.getElementById("customModelPickModal")?.classList.remove("active"),this._pendingCustomModelPick=null},_confirmModelSwap(e){const t=document.getElementById("customModelSwapConfirmModal"),s=document.getElementById("customModelSwapConfirmMessage");return s&&(s.textContent=e),t?.classList.add("active"),new Promise(o=>{this._resolveModelSwapConfirmPromise=o})},_resolveModelSwapConfirm(e){document.getElementById("customModelSwapConfirmModal")?.classList.remove("active");const t=this._resolveModelSwapConfirmPromise;this._resolveModelSwapConfirmPromise=null,t?.(e)},_confirmContextWarning(e,t,s){const o=document.getElementById("customModelContextWarningModal"),n=document.getElementById("customModelContextWarningMessage");if(n){const a=typeof t=="number";n.textContent=`${e} is configured with `+(a?`only ${t.toLocaleString()} tokens of`:"an unknown (too small)")+` context, but this CLI needs roughly ${s.toLocaleString()}+ tokens just for its own system prompt and tools \u2014 before any conversation history. Its very first message will fail outright, no matter what context size Codeman tells it to expect.
22
22
 
23
23
  To fix this, reconfigure llama-swap to give this model (or a smaller one) an explicit larger context instead of relying on auto-fit (--fit-ctx), which optimizes for the biggest MODEL that fits, not the biggest CONTEXT \u2014 e.g. add "-c 65536" (or as large a --ctx-size as your hardware holds) to its llama-swap config entry. A smaller model at a much larger explicit context often fits in the same VRAM a bigger model's auto-fit context gets shrunk to make room for.`}return o?.classList.add("active"),new Promise(a=>{this._resolveContextWarningConfirmPromise=a})},_resolveContextWarningConfirm(e){document.getElementById("customModelContextWarningModal")?.classList.remove("active");const t=this._resolveContextWarningConfirmPromise;this._resolveContextWarningConfirmPromise=null,t?.(e)},chooseCustomModelAndRun(e){const t=this._pendingCustomModelPick;this.closeCustomModelPickModal(),t&&this.runCustomModelEntry(t.mode,t.endpointId,e)},async runCustomModelEntry(e,t,s){return e==="claude"?this._runCustomModelEntryViaRestart(e,t,s):this._runCustomModelEntryOneShot(e,t,s)},async _runCustomModelEntryOneShot(e,t,s){document.getElementById("runModeMenu")?.classList.remove("active");const o=this._runMode,n=document.getElementById("tabCount"),a=n?.value;this._runMode=e,this._pendingCustomModelForLaunch={endpointId:t,modelId:s},n&&(n.value="1");try{await this.run()}finally{this._runMode=o,this._pendingCustomModelForLaunch=void 0,n&&a!==void 0&&(n.value=a)}const i=this._lastCustomModelLaunchResult;this._lastCustomModelLaunchResult=void 0,i?.modelSwapInProgress&&this._watchLlamaSwapLoading(t,s,i.sessionId)},async _quickStartWithCustomModelConfirm(e){const t=async a=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();let s={},o=await t(e);if(o?.data?.requiresContextWarning){const{modelId:a,contextLength:i,minSafeContextTokens:r}=o.data;if(!await this._confirmContextWarning(a,i,r))return this._lastCustomModelLaunchResult=void 0,{success:!1,error:"Launch cancelled \u2014 context window too small"};s={...s,confirmedContext:!0},o=await t({...e,customModel:{...e.customModel,...s}})}if(o?.data?.requiresConfirmation){const{currentlyLoadedModel:a,affectedSessions:i}=o.data,r=i.map(u=>u.name||u.id).join(", "),c=r?`${r} ${i.length===1?"is":"are"} currently using`:"Another session on this endpoint is currently using",l=r&&i.length>1?"those sessions":"that session";if(!await this._confirmModelSwap(`${c} ${a} on this endpoint. Switching will unload it for ${l} too. Continue?`))return this._lastCustomModelLaunchResult=void 0,{success:!1,error:"Model switch cancelled"};s={...s,confirmedSwap:!0},o=await t({...e,customModel:{...e.customModel,...s}})}const n=o?.success!==!1;return this._lastCustomModelLaunchResult=n?o?.data:void 0,n&&e.customModel&&this._setCustomModelLastUsed(e.mode,e.customModel.endpointId,e.customModel.modelId),o},async _runCustomModelEntryViaRestart(e,t,s){document.getElementById("runModeMenu")?.classList.remove("active");const o=this._runMode,n=this.activeSessionId,a=document.getElementById("tabCount"),i=a?.value;this._runMode=e,a&&(a.value="1");try{await this.run()}finally{this._runMode=o,a&&i!==void 0&&(a.value=i)}const r=this.activeSessionId;if(!r||r===n)return;const c=this._showCenterStatus(`Claude started \u2014 switching to ${t}\u2026`);await this._apiJson(`/api/sessions/${r}/wait?until=idle&timeout=20000`);let{ok:l,data:d,res:u}=await this._applyCustomModelToSession(r,t,s),h=d?.success!==!1?d?.data:void 0,p={};if(l&&h?.requiresContextWarning){if(!await this._confirmContextWarning(h.modelId,h.contextLength,h.minSafeContextTokens)){c?.dismiss(),this.showToast("Kept the native backend \u2014 context window too small","info");return}p={...p,confirmedContext:!0},{ok:l,data:d,res:u}=await this._applyCustomModelToSession(r,t,s,p),h=d?.success!==!1?d?.data:void 0}if(l&&h?.requiresConfirmation){const m=h.affectedSessions.map(f=>f.name||f.id).join(", "),g=m?`${m} ${h.affectedSessions.length===1?"is":"are"} currently using`:"Another session on this endpoint is currently using",C=m&&h.affectedSessions.length>1?"those sessions":"that session";if(!await this._confirmModelSwap(`${g} ${h.currentlyLoadedModel} on this endpoint. Switching to ${s} will unload it for ${C} too. Continue?`)){c?.dismiss(),this.showToast("Kept the native backend \u2014 model switch cancelled","info");return}p={...p,confirmedSwap:!0},{ok:l,data:d,res:u}=await this._applyCustomModelToSession(r,t,s,p),h=d?.success!==!1?d?.data:void 0}if(!l||!d||d.success===!1){c?.dismiss();const m=d?.error?`: ${d.error}`:u?` (HTTP ${u.status})`:" (request failed)";this.showToast(`Session started on the native backend \u2014 could not apply the custom endpoint${m}`,"error",{duration:0});return}if(this._setCustomModelLastUsed(e,t,s),h?.modelSwapInProgress){c?.dismiss(),this._watchLlamaSwapLoading(t,s,r);return}c?.setMessage(`Pointed at ${t} \u2014 restarting the session...`),setTimeout(()=>c?.dismiss(),3e3)},async _applyCustomModelToSession(e,t,s,o){const n=await this._api(`/api/sessions/${e}/custom-model`,{method:"POST",body:{endpointId:t,modelId:s,...o||{}}}),a=n?await n.json().catch(()=>null):null;return{ok:!!n,data:a,res:n}},async _lookupModelSizeGB(e,t){const s=await this._apiJson("/api/model-endpoints").catch(()=>null);if(!Array.isArray(s))return;const n=s.find(a=>a.id===e)?.modelSizesGB?.[t];return typeof n=="number"&&Number.isFinite(n)&&n>0?n:void 0},_formatLlamaLogLine(e){return typeof e=="string"?e.replace(/^[\d.]+\s+[IWE]\s+\S+\s+/,""):e},async _watchLlamaSwapLoading(e,t,s,o=1e3){const n=this._watchLlamaSwapGeneration=(this._watchLlamaSwapGeneration||0)+1,a=()=>this._watchLlamaSwapGeneration===n,i=await this._lookupModelSizeGB(e,t);if(!a())return;const r=i?` (${i.toFixed(1)} GB)`:"",c=`Loading ${t}${r} on ${e} \u2014 this can take a while depending on your hardware and the model size.`,l=h=>{const p=this._formatLlamaLogLine(h);return c+(p?`
24
- llama.cpp: ${p}`:"")};let d=!1;const u=this._showCenterStatus(l(),{onCancel:()=>{d=!0}});for(;!d;){const h=await this._apiJson(`/api/model-endpoints/${encodeURIComponent(e)}/running-status`);if(!a())return;if(d)break;if(h)if(h.isLlamaSwap){if(h.running.some(p=>p.model===t&&p.state==="ready")){u?.dismiss(),this.showToast(`${t} is ready`,"success",{duration:2500});return}}else{u?.dismiss();return}if(!a()||d)break;u?.setMessage(l(h?.logLine)),await new Promise(p=>setTimeout(p,o))}if(a()&&(u?.dismiss(),this.showToast(`Cancelled loading ${t} on ${e}`+(s?" \u2014 the session has been closed.":"."),"info"),s))try{await this.closeSession(s)}catch{}},async runDeepSeekWeb(){document.getElementById("runModeMenu")?.classList.remove("active");const e=this._beginSessionLaunchStatus("Starting the DeepSeek web UI...");try{const s=await(await fetch("/api/deepseek/web",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({authority:location.host})})).json();if(!s.success)throw new Error(s.error||"Failed to start the DeepSeek web UI");const o=s.data.url;let n=[...this.webviews?.values()||[]].find(a=>a.managed==="deepseek-web");if(n){const i=await(await fetch(`/api/webviews/${n.id}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:o,trusted:!0})})).json();if(!i.success)throw new Error(i.error||"Failed to update the web tab");n=i.data.webview||i.data}else{const i=await(await fetch("/api/webviews",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:"DeepSeek Harness",url:o,icon:"\u{1F433}",managed:"deepseek-web",trusted:!0})})).json();if(!i.success)throw new Error(i.error||"Failed to save the web tab");n=i.data.webview||i.data}await this.refreshWebviews?.(),this._appendSessionLaunchStatus(e,`Serving on ${o} - opening it as a tab.`),n?.id&&await this.openWebview(n.id)}catch(t){this._reportSessionLaunchError(e,t.message)}},async installDeepSeekProfile(){const t=this._beginSessionLaunchStatus("Installing a DeepSeek terminal profile (this can take a minute)...");try{const o=await(await fetch("/api/deepseek/install-profile",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})})).json();if(!o.success)throw new Error(o.error||"Failed to install the profile");window.__codemanCliAvailable={...window.__codemanCliAvailable||{},deepseek:!!o.data.runnable},this._appendSessionLaunchStatus(t,`Installed ${o.data.package} into profile "${o.data.profile}".`),this.showToast?.(`DeepSeek profile "${o.data.profile}" installed`,"success");const n=document.getElementById("runModeMenu");n&&this._refreshRunModeAvailability(n)}catch(s){this._reportSessionLaunchError(t,s.message)}},_renderRunModeNotice(e,t){if(!e)return;let s=e.querySelector(".run-mode-notice");if(!t){s?.remove();return}s||(s=document.createElement("div"),s.className="run-mode-notice",e.prepend(s)),s.textContent=t},async _probeDockerCaseModes(e,t){const s=e?.name,o=e?.docker?.container,n=e?.docker?.hostId;if(!(!s||!o||!n)&&(this._dockerCaseModes=this._dockerCaseModes||{},!this._dockerModeProbeInFlight?.[s])){this._dockerModeProbeInFlight=this._dockerModeProbeInFlight||{},this._dockerModeProbeInFlight[s]=!0;try{const a=await this._apiJson("/api/docker-cases/adopt-preflight",{method:"POST",body:{hostId:n,container:o}});a?.ok&&Array.isArray(a.availableModes)?(this._dockerCaseModes[s]=a.availableModes,delete this._dockerCaseProbeError?.[s]):(e?.docker?.owned===!1&&(this._dockerCaseProbeError=this._dockerCaseProbeError||{},this._dockerCaseProbeError[s]=a?.error||`Could not read container "${o}".`),delete this._dockerCaseModes[s]),t?.classList.contains("active")&&this._refreshRunModeAvailability(t)}finally{delete this._dockerModeProbeInFlight[s]}}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const o=new Date(s.lastModified),n=o.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+o.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),a=this._shortenHomePath(s.workingDir),i=a.lastIndexOf("/"),r=i===-1?a:a.slice(i+1),c=(i===-1?"":a.slice(0,i)).replace(/\/\.claude\/worktrees$/,""),l=document.createElement("button");l.className="run-mode-option run-mode-hist-row",l.title=s.workingDir,l.dataset.sessionId=s.sessionId,l.dataset.workingDir=s.workingDir;const d=document.createElement("span");d.className="hist-name",d.textContent=r;const u=[d],h=this._worktreeLabel?this._worktreeLabel(s):"";if(h){const m=document.createElement("span");m.className="hist-wt",m.textContent=h,u.push(m)}if(c){const m=document.createElement("span");m.className="hist-dir",m.textContent=c,u.push(m)}const p=document.createElement("span");p.className="hist-meta",p.textContent=n,u.push(p),l.append(...u),l.addEventListener("click",m=>{m.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name,s.mode,s.resumeId)}),e.appendChild(l)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,o=document.getElementById("runBtnLabel");if(t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),o){const n=registryCliById(e);o.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":e==="gemini"?"Run GM":e==="antigravity"?"Run AG":e==="pi"?"Run PI":e==="grok"?"Run GK":e==="deepseek"?"Run DS":e==="omp"?"Run OMP":e==="shell"?"Run SH":n?`Run ${n.shortBadge}`:"Run"}},sendEnterKey(){if(!this.activeSessionId)return;const e=this.terminal?._core?.coreService;if(e&&typeof e.triggerDataEvent=="function"){e.triggerDataEvent("\r",!0);return}this.sendInput("\r")},_initRunMode(){this.renderRegistryRunOptions();let e="claude";try{e=localStorage.getItem("codeman_runMode")||"claude"}catch{}this.runMode=e,this._applyRunMode()},incrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},incrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},_nextCaseSessionStartNumber(e,t="w"){const s=new RegExp(`^${t}(\\d+)-([a-zA-Z0-9_-]+)`);let o=1;for(const[,n]of this.sessions||[]){const a=n.name&&n.name.match(s);if(a&&a[2]===e){const i=parseInt(a[1]);i>=o&&(o=i+1)}}return o},_beginSessionLaunchStatus(e,t="1;32"){const s=!this.activeSessionId;return s?(this.terminal.clear(),this.terminal.writeln(`\x1B[${t}m ${e}\x1B[0m`),this.terminal.writeln("")):this.showToast?.(e,"info"),s},_appendSessionLaunchStatus(e,t,s="90"){!e||this.activeSessionId||this.terminal.writeln(`\x1B[${s}m ${t}\x1B[0m`)},_reportSessionLaunchError(e,t){e&&!this.activeSessionId?this.terminal.writeln(`\x1B[1;31m Error: ${t}\x1B[0m`):this.showToast?.(t,"error")},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=this._readTabCount(),s=this._beginSessionLaunchStatus(`Starting ${t} Claude session(s) in ${e}...`);this.terminal.focus();try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const b=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!b.success)throw new Error(b.error||"Failed to create case");n=b.data.case}const a=n.path;if(!a)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const k=this._nextCaseSessionStartNumber(e);let b;if(n.location==="docker"){const E=this.loadAppSettingsFromStorage(),M=this.getCaseSettings(e).opusContext1m||E.opusContext1mEnabled;b=E.claudeModel||(M?"opus[1m]":"")}const v=[];let I=!1;for(let E=0;E<t;E++){const _=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${k+E}-${e}`,...b!==void 0?{modelOverride:b}:{}}),M=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:_})).json();let S=await M();if(!S.success&&S.errorCode==="CONFLICT"&&n.location==="docker"&&!I&&(I=!0,confirm(`Container config for "${e}" changed since its container was created.
24
+ llama.cpp: ${p}`:"")};let d=!1;const u=this._showCenterStatus(l(),{onCancel:()=>{d=!0}});for(;!d;){const h=await this._apiJson(`/api/model-endpoints/${encodeURIComponent(e)}/running-status`);if(!a())return;if(d)break;if(h)if(h.isLlamaSwap){if(h.running.some(p=>p.model===t&&p.state==="ready")){u?.dismiss(),this.showToast(`${t} is ready`,"success",{duration:2500});return}}else{u?.dismiss();return}if(!a()||d)break;u?.setMessage(l(h?.logLine)),await new Promise(p=>setTimeout(p,o))}if(a()&&(u?.dismiss(),this.showToast(`Cancelled loading ${t} on ${e}`+(s?" \u2014 the session has been closed.":"."),"info"),s))try{await this.closeSession(s)}catch{}},async runDeepSeekWeb(){document.getElementById("runModeMenu")?.classList.remove("active");const e=this._beginSessionLaunchStatus("Starting the DeepSeek web UI...");try{const s=await(await fetch("/api/deepseek/web",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({authority:location.host})})).json();if(!s.success)throw new Error(s.error||"Failed to start the DeepSeek web UI");const o=s.data.url;let n=[...this.webviews?.values()||[]].find(a=>a.managed==="deepseek-web");if(n){const i=await(await fetch(`/api/webviews/${n.id}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:o,trusted:!0})})).json();if(!i.success)throw new Error(i.error||"Failed to update the web tab");n=i.data.webview||i.data}else{const i=await(await fetch("/api/webviews",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:"DeepSeek Harness",url:o,icon:"\u{1F433}",managed:"deepseek-web",trusted:!0})})).json();if(!i.success)throw new Error(i.error||"Failed to save the web tab");n=i.data.webview||i.data}await this.refreshWebviews?.(),this._appendSessionLaunchStatus(e,`Serving on ${o} - opening it as a tab.`),n?.id&&await this.openWebview(n.id)}catch(t){this._reportSessionLaunchError(e,t.message)}},async installDeepSeekProfile(){const t=this._beginSessionLaunchStatus("Installing a DeepSeek terminal profile (this can take a minute)...");try{const o=await(await fetch("/api/deepseek/install-profile",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})})).json();if(!o.success)throw new Error(o.error||"Failed to install the profile");window.__codemanCliAvailable={...window.__codemanCliAvailable||{},deepseek:!!o.data.runnable},this._appendSessionLaunchStatus(t,`Installed ${o.data.package} into profile "${o.data.profile}".`),this.showToast?.(`DeepSeek profile "${o.data.profile}" installed`,"success");const n=document.getElementById("runModeMenu");n&&this._refreshRunModeAvailability(n)}catch(s){this._reportSessionLaunchError(t,s.message)}},_renderRunModeNotice(e,t){if(!e)return;let s=e.querySelector(".run-mode-notice");if(!t){s?.remove();return}s||(s=document.createElement("div"),s.className="run-mode-notice",e.prepend(s)),s.textContent=t},async _probeDockerCaseModes(e,t){const s=e?.name,o=e?.docker?.container,n=e?.docker?.hostId;if(!(!s||!o||!n)&&(this._dockerCaseModes=this._dockerCaseModes||{},!this._dockerModeProbeInFlight?.[s])){this._dockerModeProbeInFlight=this._dockerModeProbeInFlight||{},this._dockerModeProbeInFlight[s]=!0;try{const a=await this._apiJson("/api/docker-cases/adopt-preflight",{method:"POST",body:{hostId:n,container:o}});a?.ok&&Array.isArray(a.availableModes)?(this._dockerCaseModes[s]=a.availableModes,delete this._dockerCaseProbeError?.[s]):(e?.docker?.owned===!1&&(this._dockerCaseProbeError=this._dockerCaseProbeError||{},this._dockerCaseProbeError[s]=a?.error||`Could not read container "${o}".`),delete this._dockerCaseModes[s]),t?.classList.contains("active")&&this._refreshRunModeAvailability(t)}finally{delete this._dockerModeProbeInFlight[s]}}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const o=new Date(s.lastModified),n=o.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+o.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),a=this._shortenHomePath(s.workingDir),i=a.lastIndexOf("/"),r=i===-1?a:a.slice(i+1),c=(i===-1?"":a.slice(0,i)).replace(/\/\.claude\/worktrees$/,""),l=document.createElement("button");l.className="run-mode-option run-mode-hist-row",l.title=s.workingDir,l.dataset.sessionId=s.sessionId,l.dataset.workingDir=s.workingDir;const d=document.createElement("span");d.className="hist-name",d.textContent=r;const u=[d],h=this._worktreeLabel?this._worktreeLabel(s):"";if(h){const m=document.createElement("span");m.className="hist-wt",m.textContent=h,u.push(m)}if(c){const m=document.createElement("span");m.className="hist-dir",m.textContent=c,u.push(m)}const p=document.createElement("span");p.className="hist-meta",p.textContent=n,u.push(p),l.append(...u),l.addEventListener("click",m=>{m.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name,s.mode,s.resumeId)}),e.appendChild(l)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,o=document.getElementById("runBtnLabel");if(t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),o){const n=registryCliById(e);o.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":e==="gemini"?"Run GM":e==="antigravity"?"Run AG":e==="pi"?"Run PI":e==="grok"?"Run GK":e==="deepseek"?"Run DS":e==="omp"?"Run OMP":e==="shell"?"Run SH":n?`Run ${n.shortBadge}`:"Run"}},sendEnterKey(){if(!this.activeSessionId)return;const e=this.terminal?._core?.coreService;if(e&&typeof e.triggerDataEvent=="function"){e.triggerDataEvent("\r",!0);return}this.sendInput("\r")},_initRunMode(){this.renderRegistryRunOptions();let e="claude";try{e=localStorage.getItem("codeman_runMode")||"claude"}catch{}this.runMode=e,this._applyRunMode()},incrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},incrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},_nextCaseSessionStartNumber(e,t="w"){const s=new RegExp(`^${t}(\\d+)-([a-zA-Z0-9_-]+)`);let o=1;for(const[,n]of this.sessions||[]){const a=n.name&&n.name.match(s);if(a&&a[2]===e){const i=parseInt(a[1]);i>=o&&(o=i+1)}}return o},_beginSessionLaunchStatus(e,t="1;32"){const s=!this.activeSessionId;return s?(this.terminal.clear(),this.terminal.writeln(`\x1B[${t}m ${e}\x1B[0m`),this.terminal.writeln("")):this.showToast?.(e,"info"),s},_appendSessionLaunchStatus(e,t,s="90"){!e||this.activeSessionId||this.terminal.writeln(`\x1B[${s}m ${t}\x1B[0m`)},_reportSessionLaunchError(e,t){e&&!this.activeSessionId?this.terminal.writeln(`\x1B[1;31m Error: ${t}\x1B[0m`):this.showToast?.(t,"error")},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=this._readTabCount(),s=this._beginSessionLaunchStatus(`Starting ${t} Claude session(s) in ${e}...`);this.terminal.focus();try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const w=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!w.success)throw new Error(w.error||"Failed to create case");n=w.data.case}const a=n.path;if(!a)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const k=this._nextCaseSessionStartNumber(e);let w;if(n.location==="docker"){const E=this.loadAppSettingsFromStorage(),M=this.getCaseSettings(e).opusContext1m||E.opusContext1mEnabled;w=E.claudeModel||(M?"opus[1m]":"")}const I=[];let _=!1;for(let E=0;E<t;E++){const B=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${k+E}-${e}`,...w!==void 0?{modelOverride:w}:{}}),M=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:B})).json();let S=await M();if(!S.success&&S.errorCode==="CONFLICT"&&n.location==="docker"&&!_&&(_=!0,confirm(`Container config for "${e}" changed since its container was created.
25
25
 
26
- Recreate the container to apply the new config? Workspace files and the conversation survive (the conversation resumes on launch).`))){const B=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!B.success)throw new Error(B.error||"Failed to recreate container");S=await M()}if(!S.success)throw new Error(S.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(S.data.sessionId,S.data.session),v.push(S.data.sessionId)}this._appendSessionLaunchStatus(s,`All ${t} remote session(s) ready`),v[0]&&(await this.selectSession(v[0]),this.loadQuickStartCases()),this.terminal.focus();return}let i=null;const r=this._nextCaseSessionStartNumber(e),c=this.isRalphTrackerEnabledByDefault(),l=[];for(let k=0;k<t;k++)l.push(`w${r+k}-${e}`);const d=this.getCaseSettings(e),u=this.loadAppSettingsFromStorage(),h=this.buildEnvOverrides(d,u),p=Object.keys(h).length>0,m=this.getEffortSetting(u),g=d.opusContext1m||u.opusContext1mEnabled,C=u.claudeModel||(g?"opus[1m]":"");this._appendSessionLaunchStatus(s,`Creating ${t} session(s)...`);const y=l.map(k=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:a,name:k,...p?{envOverrides:h}:{},...m?{effort:m}:{},...C!==void 0?{modelOverride:C}:{}})}).then(b=>b.json())),f=await Promise.all(y),w=[];for(const k of f){if(!k.success)throw new Error(k.error);await this._ensureCreatedSessionVisible(k.data.session.id,k.data.session),w.push(k.data.session.id)}i=w[0],await Promise.all(w.map(k=>fetch(`/api/sessions/${k}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:c,disableAutoEnable:!c})}))),this._appendSessionLaunchStatus(s,`Starting ${t} session(s) in parallel...`),await Promise.all(w.map(k=>fetch(`/api/sessions/${k}/interactive`,{method:"POST"}))),this._appendSessionLaunchStatus(s,`All ${t} sessions ready`),i&&(await this.selectSession(i),this.loadQuickStartCases()),this.terminal.focus()}catch(o){this._reportSessionLaunchError(s,o.message)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1)),s=this._beginSessionLaunchStatus(`Starting ${t} Shell session(s) in ${e}...`,"1;33");try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const g=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!g.success)throw new Error(g.error||"Failed to create case");n=g.data.case}const a=(this.cases||[]).find(m=>m.name===e),i=n.location==="remote"||n.location==="docker"||a?.location==="remote"||a?.location==="docker",r=n.path;if(!r)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const m=this._nextCaseSessionStartNumber(e,"s"),g=[];for(let C=0;C<t;C++){const f=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${m+C}-${e}`})})).json();if(!f.success)throw new Error(f.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(f.data.sessionId,f.data.session),g.push(f.data.sessionId)}g[0]&&await this.selectSession(g[0]),this.terminal.focus();return}const c=this._nextCaseSessionStartNumber(e,"s"),l=[];for(let m=0;m<t;m++)l.push(`s${c+m}-${e}`);const d=l.map(m=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...i?{caseName:e}:{workingDir:r},mode:"shell",name:m})}).then(g=>g.json())),u=await Promise.all(d),h=[];for(const m of u){if(!m.success)throw new Error(m.error);await this._ensureCreatedSessionVisible(m.data.session.id,m.data.session),h.push(m.data.session.id)}await Promise.all(h.map(m=>fetch(`/api/sessions/${m}/shell`,{method:"POST"})));const p=this.getTerminalDimensions();p&&await Promise.all(h.map(m=>fetch(`/api/sessions/${m}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),h.length>0&&await this.selectSession(h[0]),this.terminal.focus()}catch(o){this._reportSessionLaunchError(s,o.message)}},_readTabCount(){return Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount")?.value)||1))},async _launchQuickStartInstances(e,t,s,o,n){const a=this._nextCaseSessionStartNumber(e);let i=null,r=!1;for(let c=0;c<t;c++){const l=`w${a+c}-${e}`,d=o(l),u=await this._quickStartWithCustomModelConfirm(r&&d.customModel?{...d,customModel:{...d.customModel,confirmedContext:!0,confirmedSwap:!0}}:d);if(!u.success)throw new Error(u.error||`Failed to start ${s}`);r=!0,await this._ensureCreatedSessionVisible(u.data.sessionId,u.data.session),i||(i=u.data.sessionId)}return t>1&&this._appendSessionLaunchStatus(n,`All ${t} ${s} session(s) ready`),i},async _runCliMode(e){const t=registryCliById(e),s=RUN_MODE_LAUNCH[e]||t&&{label:t.label,installHint:`${t.label} is not available on this host.`,supportsCustomModel:!1,buildConfig:()=>null};if(!s)throw new Error(`Unknown run mode: ${e}`);const o=document.getElementById("quickStartCase").value||"testcase",n=(this.cases||[]).find(c=>c.name===o)?.location,a=n==="remote"||n==="docker",i=this._readTabCount(),r=this._beginSessionLaunchStatus(`Starting ${i} ${s.label} session(s) in ${o}...`);this.terminal.focus();try{if(!a&&RUN_MODE_LAUNCH[e]){const h=(await(await fetch(`/api/${e}/status`)).json()).data;if(!h.available){this._reportSessionLaunchError(r,s.installHint);return}if(s.unrunnableHint&&!h.runnable){this._reportSessionLaunchError(r,s.unrunnableHint);return}}else if(!a&&!this.isCliAvailable(e)){this._reportSessionLaunchError(r,s.installHint);return}const c=this.loadAppSettingsFromStorage(),l=this.buildEnvOverrides(this.getCaseSettings(o),c),d=await this._launchQuickStartInstances(o,i,s.label,u=>({caseName:o,mode:e,sessionName:u,...a?{}:{...s.buildConfig(c)||{},...Object.keys(l).length>0?{envOverrides:l}:{},...s.supportsCustomModel&&this._pendingCustomModelForLaunch?{customModel:this._pendingCustomModelForLaunch}:{}}}),r);d&&await this.selectSession(d),this.terminal.focus()}catch(c){this._reportSessionLaunchError(r,c.message)}},async runOpenCode(){return this._runCliMode("opencode")},async runCodex(){return this._runCliMode("codex")},async runGemini(){return this._runCliMode("gemini")},async runAntigravity(){return this._runCliMode("antigravity")},async runPi(){return this._runCliMode("pi")},async runOmp(){return this._runCliMode("omp")},async runGrok(){return this._runCliMode("grok")},async runDeepSeek(){return this._runCliMode("deepseek")},_tabDetachOverrides(){if(this._tabDetachOverrideMap===void 0)try{this._tabDetachOverrideMap=JSON.parse(localStorage.getItem("codeman:tab-detach-overrides")||"{}")||{}}catch{this._tabDetachOverrideMap={}}return this._tabDetachOverrideMap},hasTabDetachOverride(e){return!!this._tabDetachOverrides()[e]},onSessionTabDetachToggle(e){const t=this.editingSessionId;if(!t)return;const s=this._tabDetachOverrides();e?s[t]=1:delete s[t];for(const n of Object.keys(s))n!==t&&this.sessions&&!this.sessions.has(n)&&delete s[n];try{localStorage.setItem("codeman:tab-detach-overrides",JSON.stringify(s))}catch{}const o=document.querySelector(`.session-tab[data-id="${CSS.escape(t)}"]`);o&&o.classList.toggle("tab-show-detach",!!e)},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=document.getElementById("sessionOptShowTabDetach");s&&(s.checked=this.hasTabDetachOverride(e));const o=isExternalCliRunMode(t.mode);this.switchOptionsTab(o?"summary":"respawn");const n=document.getElementById("sessionRespawnStatus"),a=document.getElementById("modalEnableRespawnBtn"),i=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(n.classList.add("active"),n.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",a.style.display="none",i.style.display=""):(n.classList.remove("active"),n.querySelector(".respawn-status-text").textContent="Not active",a.style.display="",i.style.display="none");const r=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?r.style.display="":r.style.display="none";const c=o;document.querySelectorAll("[data-claude-only]").forEach(y=>{y.style.display=c?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const d=parseSessionPrefix(t.name),u=document.getElementById("modalSessionPrefix");d?(u.textContent=d.prefix+": ",u.style.display="",document.getElementById("modalSessionName").value=d.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(u.style.display="none",u.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const h=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(y=>{y.classList.toggle("selected",y.dataset.color===h)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const m=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="ralph"]'),g=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="respawn"]');if(c?(m&&(m.style.display="none"),g&&(g.style.display="none"),this.switchOptionsTab("context")):(m&&(m.style.display=""),g&&(g.style.display="")),!c){const y=this.ralphStates.get(e);this.populateRalphForm({enabled:y?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:y?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:y?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:y?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:y?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const C=document.getElementById("sessionOptionsModal");C.dataset.chipsReady!=="1"&&(C.dataset.chipsReady="1",C.addEventListener("change",y=>{y.target?.closest?.(".set-chip")&&this._syncSettingsChips()})),this._syncSettingsChips(),C.classList.add("active"),this.activeFocusTrap=new FocusTrap(C),this.activeFocusTrap.activate()},_applyLocalSessionName(e,t){if(typeof t!="string")return;const s=this.sessions.get(e);s&&(s.name=t,this.sessions.set(e,s),this.updateSubagentParentNames?.(e))},async _putSessionName(e,t){const s=await this._apiPut(`/api/sessions/${e}/name`,{name:t});if(!s||!s.ok)return null;let o=null;try{o=await s.json()}catch{return null}if(o&&o.success===!1)return null;const n=o?.data?.name;return typeof n=="string"?n:t},async saveSessionName(){if(!this.editingSessionId)return;const e=this.editingSessionId,t=this.sessions.get(e),s=t?parseSessionPrefix(t.name):null,o=document.getElementById("modalSessionName").value.trim();let n;s?n=s.prefix+(o?": "+o:""):n=o;const a=await this._putSessionName(e,n);if(a===null){this.showToast("Failed to save session name","error");return}this._applyLocalSessionName(e,a),this.renderSessionTabs()},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const o=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${o}`,t.classList.add("active")}else t.textContent="",t.classList.remove("active")},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const o=s.data.config;document.getElementById("modalRespawnPrompt").value=o.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=o.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=o.sendInit??!0,document.getElementById("modalRespawnKickstart").value=o.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=o.autoAcceptPrompts??!0,o.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${o.durationMinutes}"]`)?this.selectDurationPreset(String(o.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=o.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),o=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),o.focus()):(s.classList.remove("visible"),o.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const o=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return o?parseInt(o):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .set-rail-item").forEach(s=>{s.classList.toggle("active",s.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary");const t=document.getElementById("sessionOptionsDoc");t&&(t.scrollTop=0),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;this._activeRename?.cancel();const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0,s.classList.add("tab-name-renaming");const o=this.getSessionName(t),n=parseSessionPrefix(t.name),a=s.textContent,i=[...s.childNodes].map(f=>f.cloneNode(!0)),r=()=>{s.replaceChildren(...i.map(f=>f.cloneNode(!0)))};for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(n){const f=document.createElement("span");f.className="tab-rename-prefix",f.textContent=n.prefix+": ",f.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(f)}const c=document.createElement("input");c.type="text",c.value=n?n.suffix:t.name||"",c.placeholder=n?"Add description...":o,c.className="tab-rename-input";const l=s.closest(".tab-rail")?"auto":this.isSessionSidebarActive?.()?"100%":"80px";c.style.cssText=`width: ${l}; min-width: 0; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;`,s.appendChild(c),c.focus(),c.select();let d=!1,u=!1,h=!1;const p=()=>{this._activeRename===y&&(this._inlineRenameActive=!1)},m=()=>{this._activeRename===y&&(h=!0,p(),this._activeRename=null,this.renderSessionTabs())},g=()=>{u||h||(u=!0,d=!0,s.classList.remove("tab-name-renaming"),r(),m())},C=async({commit:f})=>{if(d||u)return;if(d=!0,s.classList.remove("tab-name-renaming"),!f){g();return}if(this._activeRename!==y)return;p();const w=c.value.trim(),k=n?n.prefix+(w?": "+w:""):w;if(k===t.name?r():s.textContent=k||a,this.sessions.has(e)&&k!==t.name){const v=await this._putSessionName(e,k);if(u||this._activeRename!==y||!this.sessions.has(e))return;v===null?(r(),this.showToast("Failed to rename","error")):this._applyLocalSessionName(e,v)}m()},y={sessionId:e,cancel:g};this._activeRename=y,c.addEventListener("blur",()=>C({commit:!0})),c.addEventListener("keydown",f=>{f.isComposing||f.keyCode===229||(f.key==="Enter"?(f.preventDefault(),c.blur()):f.key==="Escape"&&(f.preventDefault(),g()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const o=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",o))};setTimeout(()=>document.addEventListener("click",o),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const o=document.getElementById("caseOpusContext1mMobile");o&&(o.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const o=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",o))};setTimeout(()=>document.addEventListener("click",o),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const o=document.getElementById("caseOpusContext1m");o&&(o.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",["remoteCaseName","remoteCasePath","remoteHostId","remoteHostAddress","remoteHostUsername","remoteHostPort","remoteHostCodexCommand","remoteHostIdentityFile","remoteHostSocksProxy","remoteHostJumpHost","remoteHostExtraSshOptions","remoteHostWakeMac","remoteHostWakeCommand"].forEach(r=>{const c=document.getElementById(r);c&&(c.value="")}),this._resetCloneForm();const t=document.getElementById("caseCloneTabBtn");t&&(t.style.display=this.isCliAvailable("git")?"":"none"),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const s=document.getElementById("createCaseModal");s.querySelectorAll(".set-rail-item").forEach(r=>{r.onclick=()=>this.switchCaseModalTab(r.dataset.tab)});const o=document.getElementById("dockerAdoptExisting");o&&(o.onchange=()=>this._syncDockerAdoptMode());const n=document.getElementById("dockerAdoptCheckBtn");n&&(n.onclick=()=>this._dockerAdoptPreflight());const a=document.getElementById("dockerAdoptJumpBtn");a&&(a.onclick=()=>this.jumpToDockerAdopt());const i=document.getElementById("dockerHostId");i&&(i.onchange=()=>{delete document.getElementById("dockerContainerList")?.dataset.loadedFor,document.getElementById("dockerAdoptExisting")?.checked&&this._loadDockerContainerOptions()}),delete document.getElementById("dockerContainerList")?.dataset.loadedFor,this._syncDockerAdoptMode(),s.querySelectorAll('input[type="text"]').forEach(r=>{r._mobileScrollWired||(r._mobileScrollWired=!0,r.addEventListener("focus",()=>{window.innerWidth<600&&setTimeout(()=>r.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),s.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".set-rail-item").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)}),t.querySelectorAll(".set-section").forEach(n=>{n.classList.toggle("hidden",n.id!==e)});const s=document.getElementById("createCaseDoc");s&&(s.scrollTop=0);const o=["caseModalSubmit","caseModalSubmitMobile"].map(n=>document.getElementById(n)).filter(Boolean);if(e==="case-manage")o.forEach(n=>{n.style.display="none"}),this.renderCaseManageList(),this.refreshDockerExports();else{const n=e==="case-create"?"Create":e==="case-clone"?"Clone":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link";o.forEach(a=>{a.style.display="",a.textContent=n})}e==="case-create"?document.getElementById("newCaseName").focus():e==="case-clone"?document.getElementById("cloneRepoUrl").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=["caseModalSubmit","caseModalSubmitMobile"].map(o=>document.getElementById(o)).filter(Boolean),t=e.map(o=>o.textContent),s=this.caseModalTab==="case-create"?"Creating...":this.caseModalTab==="case-clone"?"Cloning...":"Linking...";e.forEach(o=>{o.classList.add("loading"),o.textContent=s,o.disabled=!0});try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-clone"?await this.cloneCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.forEach((o,n)=>{o.classList.remove("loading"),o.disabled=!1,o.textContent=t[n]})}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}const s=document.getElementById("newCaseDocker")?.checked,o=s?"/api/cases/docker-quickcreate":"/api/cases",n=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const i=await(await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json();if(i.success)if(this.closeCreateCaseModal(),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e),s){const r=i.data?.capsEnforced===!1?" (resource caps advisory on this engine)":"";this.showToast(`Docker case "${e}" created${r} \u2014 starting session\u2026`,"success"),await this.runClaude()}else this.showToast(`Case "${e}" created`,"success");else this.showToast(i.error||"Failed to create case","error")}catch(a){console.error("Failed to create case:",a),this.showToast("Failed to create case: "+a.message,"error")}},applyDockerTemplate(){const e=document.getElementById("quickDockerTemplate")?.value,s={small:{m:"2g",c:"1",g:""},medium:{m:"",c:"",g:""},large:{m:"8g",c:"4",g:""},gpu:{m:"8g",c:"4",g:"all"}}[e];if(!s)return;const o=(n,a)=>{const i=document.getElementById(n);i&&(i.value=a)};o("quickDockerMemory",s.m),o("quickDockerCpus",s.c),o("quickDockerGpus",s.g)},_collectDockerQuickSettings(){const e=c=>(document.getElementById(c)?.value||"").trim(),t={},s=e("quickDockerMemory");s&&(t.memory=s);const o=e("quickDockerCpus");o&&(t.cpus=o);const n=e("quickDockerGpus");n&&n.toLowerCase()!=="none"&&(t.gpus=n);const a=document.getElementById("quickDockerNetwork")?.value;a&&a!=="bridge"&&(t.network=a);const i=e("quickDockerImage");i&&(t.image=i);const r=document.getElementById("quickDockerMountCreds");return r&&!r.checked&&(t.mountCredentials=!1),t},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const o=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();o.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(o.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},_resetCloneForm(){const e=(i,r)=>{const c=document.getElementById(i);c&&(c.value=r)};e("cloneRepoUrl",""),e("cloneCaseName",""),e("cloneRepoRef","");const t=document.getElementById("cloneShallow");t&&(t.checked=!1);const s=document.getElementById("cloneStartSession");s&&(s.checked=!1);const o=document.getElementById("cloneRepoRefOptions");o&&o.replaceChildren();const n=document.getElementById("cloneRefHint");n&&(n.textContent="Leave blank for the repository's default branch."),this._cloneNameEdited=!1,this._clonePreflight=null,clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");const a=document.getElementById("cloneCaseBrain");if(a){for(const r of a.options){const c=r.dataset.cli;r.hidden=!!c&&!this.isCliAvailable(c)}const i=this.runMode||"claude";a.value=[...a.options].some(r=>r.value===i&&!r.hidden)?i:""}},_setCloneStatus(e,t){const s=document.getElementById("cloneRepoStatus");s&&(s.textContent=e,s.className=`form-hint clone-status${t?" clone-status-"+t:""}`)},_repoNameFromUrl(e){const t=(e||"").trim().replace(/\/+$/,"");return t?(t.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//,"").replace(/^[^@/]*@/,"").split(/[/:]/).filter(Boolean).pop()||"").replace(/\.git$/i,"").replace(/[^a-zA-Z0-9_-]+/g,"-").replace(/-{2,}/g,"-").replace(/^[-_]+|[-_]+$/g,"").slice(0,64):""},onCloneNameEdited(){this._cloneNameEdited=!!document.getElementById("cloneCaseName")?.value.trim()},onCloneUrlInput(){const e=document.getElementById("cloneRepoUrl")?.value.trim()||"",t=document.getElementById("cloneCaseName");if(t&&!this._cloneNameEdited&&(t.value=this._repoNameFromUrl(e)),clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,!e){this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");return}if(this.isCliAvailable("git")===!1){this._setCloneStatus("git is not installed on the Codeman host, so cloning is unavailable.","err");return}this._setCloneStatus("Checking the repository\u2026",""),this._clonePreflightTimer=setTimeout(()=>this._runClonePreflight(e),450)},async _runClonePreflight(e){const t=new AbortController;this._clonePreflightAbort=t;try{const o=await(await fetch("/api/cases/clone-preflight",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repository:e}),signal:t.signal})).json();if(document.getElementById("cloneRepoUrl")?.value.trim()!==e)return;if(!o.success){this._setCloneStatus(o.error||"Could not check that URL.","err");return}this._applyClonePreflight(o.data,e)}catch(s){if(s.name==="AbortError")return;this._setCloneStatus("Could not reach Codeman to check that URL.","err")}},_applyClonePreflight(e,t){this._clonePreflight=e;const s=e?.parse;if(!s?.cloneable){this._setCloneStatus(s?.message||"That URL cannot be cloned.","err");return}const o=document.getElementById("cloneCaseName");o&&!this._cloneNameEdited&&s.suggestedName&&(o.value=s.suggestedName);const n=s.owner?`${s.provider} ${s.owner}/${s.repo}`:`${s.provider} ${s.repo}`;if(e.gitAvailable===!1){this._setCloneStatus(`${n}: git is not installed on the Codeman host.`,"err");return}const a=e.remote;if(a&&!a.reachable){this._setCloneStatus(`${n}: ${a.failure?.message||"the remote could not be read."}`,"err");return}const i=document.getElementById("cloneRefHint"),r=document.getElementById("cloneRepoRefOptions");if(a&&r){r.replaceChildren();for(const l of[...a.branches||[],...a.tags||[]]){const d=document.createElement("option");d.value=l,r.appendChild(d)}if(i){const l=`${a.branches?.length||0} branches, ${a.tags?.length||0} tags`;i.textContent=a.defaultBranch?`Blank clones the default branch (${a.defaultBranch}). ${l} available.`:`Blank clones the default branch. ${l} available.`}}const c=s.warnings?.[0];this._setCloneStatus(c?`${n}: ${c}`:`${n}: ready to clone.`,c?"warn":"ok")},async cloneCase(){const e=document.getElementById("cloneRepoUrl").value.trim(),t=document.getElementById("cloneCaseName").value.trim(),s=document.getElementById("cloneRepoRef").value.trim(),o=!!document.getElementById("cloneShallow")?.checked,n=document.getElementById("cloneCaseBrain")?.value||"",a=!!document.getElementById("cloneStartSession")?.checked;if(!e){this.showToast("Please enter a repository URL","error");return}if(!t){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(t)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}this._setCloneStatus(`Cloning ${e}\u2026 this can take a while for a large repository.`,"");try{const r=await(await fetch("/api/cases/clone",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,repository:e,...s?{ref:s}:{},...o?{shallow:!0}:{}})})).json();if(!r.success){this._setCloneStatus(r.error||"Clone failed.","err"),this.showToast(r.error||"Failed to clone repository","error");return}n&&this.setRunMode(n),this.closeCreateCaseModal(),await this.loadQuickStartCases(t),await this.saveLastUsedCase(t),this.showToast(`Cloned into case "${t}"`,"success");for(const c of r.data?.warnings||[])this.showToast(c,"warning");a&&await this.run()}catch(i){console.error("Failed to clone repository:",i),this._setCloneStatus(`Lost the connection while cloning: ${i.message}. If git finishes, the case still appears in the list.`,"warn"),this.showToast("Clone request interrupted \u2014 watch the case list","error")}},openLinkCasePathPicker(){const e=document.getElementById("linkCasePath");PathPicker.open({title:"Select Existing Project Folder",initialPath:e.value.trim(),directoriesOnly:!0,onSelect:t=>{e.value=t;const s=document.getElementById("linkCaseName");if(!s.value.trim()){const o=t.split("/").filter(Boolean).pop()||"";/^[\p{L}\p{N}_-]+$/u.test(o)&&(s.value=o)}e.focus(),e.setSelectionRange(t.length,t.length)}})},openDockerWorkspacePathPicker(){const e=document.getElementById("dockerWorkspacePath");PathPicker.open({title:"Select Host Workspace Folder",initialPath:e.value.trim(),directoriesOnly:!0,onSelect:t=>{e.value=t;const s=document.getElementById("dockerCaseName");if(s&&!s.value.trim()){const o=t.split("/").filter(Boolean).pop()||"";/^[a-zA-Z0-9_-]+$/.test(o)&&(s.value=o)}}})},openDockerWorkdirPicker(){const e=document.getElementById("dockerAdoptWorkdir"),t=document.getElementById("dockerContainerName")?.value.trim(),s=document.getElementById("dockerHostId")?.value.trim()||"local";if(!t){this.showToast("Enter the container name first","error");return}PathPicker.open({title:`Select Folder Inside ${t}`,initialPath:e.value.trim()||"/",directoriesOnly:!0,fetchListing:async o=>{const n=await this._apiJson("/api/docker-cases/browse",{method:"POST",body:{hostId:s,container:t,path:o||"/"}});return n?n.error?{success:!1,error:n.error}:{success:!0,data:{...n,root:"/",roots:[{label:t,path:"/"}],truncated:!1}}:{success:!1,error:`Could not read ${t}. Is it running?`}},onSelect:o=>{e.value=o}})},async linkRemoteCase(){const e=document.getElementById("remoteCaseName").value.trim(),t=document.getElementById("remoteCasePath").value.trim(),s=document.getElementById("remoteHostId").value.trim(),o=document.getElementById("remoteHostAddress").value.trim(),n=document.getElementById("remoteHostUsername").value.trim(),a=document.getElementById("remoteHostCodexCommand").value.trim(),i=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),l=document.getElementById("remoteHostJumpHost").value.trim(),d=document.getElementById("remoteHostWakeMac").value.trim(),u=document.getElementById("remoteHostWakeCommand").value.trim(),h=document.getElementById("remoteHostExtraSshOptions").value.split(`
27
- `).map(m=>m.trim()).filter(m=>m.length>0);if(!e||!t||!s||!o||!n){this.showToast("Please complete all required remote fields","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Remote path must be absolute","error");return}let p;if(i&&(p=Number(i),!Number.isInteger(p)||p<1||p>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const m={id:s,label:s,host:o,username:n,...p?{port:p}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...l?{jumpHost:l}:{},...h.length?{extraSshOptions:h}:{},...d?{wakeMac:d}:{},...u?{wakeCommand:u}:{},...a?{commands:{codex:a}}:{}},C=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)})).json();if(!C.success&&C.errorCode!=="ALREADY_EXISTS")throw new Error(C.error||"Failed to save remote host");const f=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();f.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(f.error||"Failed to link remote case","error")}catch(m){console.error("Failed to link remote case:",m),this.showToast("Failed to link remote case: "+m.message,"error")}},_syncDockerAdoptMode(){const e=document.getElementById("createCaseModal");if(!e)return;const t=document.getElementById("dockerAdoptExisting")?.checked;t?e.setAttribute("data-docker-adopt","1"):e.removeAttribute("data-docker-adopt"),t&&(this._loadDockerContainerOptions(),this._loadDockerCloneOptions())},async _loadDockerCloneOptions(){const e=document.getElementById("dockerAdoptCloneFrom"),t=document.getElementById("dockerAdoptCloneRow");if(!e||!t)return;let s=[];try{const a=await(await fetch("/api/cases")).json();s=(Array.isArray(a)?a:a?.data||[]).filter(i=>i?.docker&&i.docker.owned===!1)}catch{s=[]}e.textContent="";const o=document.createElement("option");o.value="",o.textContent="Start from scratch",e.appendChild(o);for(const n of s){const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name} \u2014 ${n.docker.container}:${n.docker.containerWorkdir||n.docker.path}`,a.dataset.container=n.docker.container,a.dataset.hostId=n.docker.hostId,a.dataset.path=n.docker.path,a.dataset.workdir=n.docker.containerWorkdir||n.docker.path,e.appendChild(a)}t.hidden=s.length===0},applyDockerCloneSource(){const e=document.getElementById("dockerAdoptCloneFrom"),t=e?.selectedOptions?.[0];if(!t||!t.value)return;const s=(n,a)=>{const i=document.getElementById(n);i&&(i.value=a||"")};s("dockerContainerName",t.dataset.container),s("dockerHostId",t.dataset.hostId),s("dockerWorkspacePath",t.dataset.path),s("dockerCaseName",t.value),s("dockerAdoptWorkdir",t.dataset.workdir),e.dataset.appliedName=t.value,e.dataset.appliedWorkdir=t.dataset.workdir||"";const o=document.getElementById("dockerAdoptWorkdir");o?.focus(),o&&o.setSelectionRange(o.value.length,o.value.length)},dockerCloneGuard(){const e=document.getElementById("dockerAdoptCloneFrom");if(!e||!e.value)return null;const t=document.getElementById("dockerCaseName"),s=document.getElementById("dockerAdoptWorkdir");return t&&t.value.trim()===(e.dataset.appliedName||"")?{el:t,message:`"${t.value.trim()}" is the case you copied from \u2014 give this one a new name.`}:s&&s.value.trim()===(e.dataset.appliedWorkdir||"")?{el:s,message:"Same container and same directory as the case you copied from \u2014 point this one at another directory."}:null},async _loadDockerContainerOptions(){const e=document.getElementById("dockerContainerList");if(!e)return;const t=document.getElementById("dockerHostId")?.value.trim()||"local";if(e.dataset.loadedFor===t)return;const o=(await this._apiJson(`/api/docker-hosts/${encodeURIComponent(t)}/containers`))?.containers||[];e.textContent="";for(const n of o){const a=document.createElement("option");a.value=n.name,a.textContent=n.running?`${n.image} \xB7 ${n.status}`:`${n.image} \xB7 ${n.status} (not running)`,e.appendChild(a)}e.dataset.loadedFor=t},jumpToDockerAdopt(){this.switchCaseModalTab("case-docker");const e=document.getElementById("dockerAdoptExisting");e&&(e.checked=!0),this._syncDockerAdoptMode(),document.getElementById("dockerContainerName")?.focus()},async _dockerAdoptPreflight(){const e=document.getElementById("dockerLinkStatus"),t=document.getElementById("dockerContainerName")?.value.trim(),s=document.getElementById("dockerAdoptWorkdir")?.value.trim(),o=document.getElementById("dockerHostId").value.trim()||"local";if(!t){e&&(e.textContent="Enter a container name first.");return}e&&(e.textContent="Inspecting container...");const n=await this._apiJson("/api/docker-cases/adopt-preflight",{method:"POST",body:{hostId:o,container:t,...s?{containerWorkdir:s}:{}}});if(!e)return;if(!n){e.textContent="Could not reach the docker host profile. Save a Host ID first.";return}if(!n.ok){e.textContent=n.error||"Container is not adoptable.";return}const a=(n.availableModes||[]).filter(i=>i!=="shell");e.textContent=a.length?`Running (${n.image||"unknown image"}). Available: ${a.join(", ")}.`:`Running (${n.image||"unknown image"}), but no agent CLI found inside \u2014 only Shell will work.`},async linkDockerCase(){const e=document.getElementById("dockerCaseName").value.trim(),t=document.getElementById("dockerWorkspacePath").value.trim(),s=document.getElementById("dockerHostId").value.trim()||"local",o=!!document.getElementById("dockerAdoptExisting")?.checked,n=document.getElementById("dockerContainerName")?.value.trim()||"",a=document.getElementById("dockerAdoptWorkdir")?.value.trim()||"",i=document.getElementById("dockerImage").value.trim()||"codeman/agent:base",r=document.getElementById("dockerNetwork").value,c=document.getElementById("dockerMemory").value.trim(),l=document.getElementById("dockerCpus").value.trim(),d=document.getElementById("dockerMountCredentials").checked,u=document.getElementById("dockerResumeOnStart").checked,h=document.getElementById("dockerLinkStatus");if(!e||!t){this.showToast("Please enter a case name and workspace path","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Workspace path must be absolute","error");return}if(o&&!n){this.showToast("Enter the name of the running container to attach to","error");return}const p=o?this.dockerCloneGuard():null;if(p){this.showToast(p.message,"error");const m=document.getElementById("dockerLinkStatus");m&&(m.textContent=p.message),p.el.focus(),p.el.select?.();return}try{h&&(h.textContent=o?"Inspecting the existing container...":"Checking docker daemon + base image...");const m={};c&&(m.memory=c),l&&(m.cpus=l);const g={id:s,label:s,image:i,network:r,mountCredentials:d,resumeOnStart:u,...Object.keys(m).length?{resources:m}:{}};let C=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)}),y=await C.json();if(!y.success&&y.errorCode==="ALREADY_EXISTS"&&(C=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)}),y=await C.json()),!y.success)throw new Error(y.error||"Failed to save docker host");const w=await(await fetch(o?"/api/cases/docker-adopt":"/api/cases/docker-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o?{name:e,hostId:s,hostWorkspacePath:t,container:n,...a?{containerWorkdir:a}:{}}:{name:e,hostId:s,hostWorkspacePath:t})})).json();if(w.success){this.closeCreateCaseModal();const k=w.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"",b=(w.data?.availableModes||[]).filter(I=>I!=="shell"),v=o&&b.length?` \u2014 found ${b.join(", ")}`:"";this.showToast(`Docker case "${e}" ${o?"attached":"linked"}${k}${v}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else h&&(h.textContent=w.error||"Failed to link docker case"),this.showToast(w.error||"Failed to link docker case","error")}catch(m){console.error("Failed to link docker case:",m),h&&(h.textContent=m.message),this.showToast("Failed to link docker case: "+m.message,"error")}},async refreshDockerExports(){const e=document.getElementById("dockerExportsList");if(e)try{const o=(await(await fetch("/api/docker-exports")).json())?.data?.exports||[];if(o.length===0){e.innerHTML='<span class="form-hint">No exports yet. Export a docker case from its tab.</span>';return}e.innerHTML=o.map(n=>{const a=(n.sizeBytes/1e6).toFixed(1),i=escapeHtml(n.name);return`<div class="case-manage-item" style="display:flex; align-items:center; gap:8px; justify-content:space-between;">
26
+ Recreate the container to apply the new config? Workspace files and the conversation survive (the conversation resumes on launch).`))){const T=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!T.success)throw new Error(T.error||"Failed to recreate container");S=await M()}if(!S.success)throw new Error(S.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(S.data.sessionId,S.data.session),I.push(S.data.sessionId)}this._appendSessionLaunchStatus(s,`All ${t} remote session(s) ready`),I[0]&&(await this.selectSession(I[0]),this.loadQuickStartCases()),this.terminal.focus();return}let i=null;const r=this._nextCaseSessionStartNumber(e),c=this.isRalphTrackerEnabledByDefault(),l=[];for(let k=0;k<t;k++)l.push(`w${r+k}-${e}`);const d=this.getCaseSettings(e),u=this.loadAppSettingsFromStorage(),h=this.buildEnvOverrides(d,u),p=Object.keys(h).length>0,m=this.getEffortSetting(u),g=this.getAdvisorSetting(u),C=d.opusContext1m||u.opusContext1mEnabled,y=u.claudeModel||(C?"opus[1m]":"");this._appendSessionLaunchStatus(s,`Creating ${t} session(s)...`);const f=l.map(k=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:a,name:k,...p?{envOverrides:h}:{},...m?{effort:m}:{},...g?{advisorModel:g}:{},...y!==void 0?{modelOverride:y}:{}})}).then(w=>w.json())),b=await Promise.all(f),v=[];for(const k of b){if(!k.success)throw new Error(k.error);await this._ensureCreatedSessionVisible(k.data.session.id,k.data.session),v.push(k.data.session.id)}i=v[0],await Promise.all(v.map(k=>fetch(`/api/sessions/${k}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:c,disableAutoEnable:!c})}))),this._appendSessionLaunchStatus(s,`Starting ${t} session(s) in parallel...`),await Promise.all(v.map(k=>fetch(`/api/sessions/${k}/interactive`,{method:"POST"}))),this._appendSessionLaunchStatus(s,`All ${t} sessions ready`),i&&(await this.selectSession(i),this.loadQuickStartCases()),this.terminal.focus()}catch(o){this._reportSessionLaunchError(s,o.message)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1)),s=this._beginSessionLaunchStatus(`Starting ${t} Shell session(s) in ${e}...`,"1;33");try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const g=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!g.success)throw new Error(g.error||"Failed to create case");n=g.data.case}const a=(this.cases||[]).find(m=>m.name===e),i=n.location==="remote"||n.location==="docker"||a?.location==="remote"||a?.location==="docker",r=n.path;if(!r)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const m=this._nextCaseSessionStartNumber(e,"s"),g=[];for(let C=0;C<t;C++){const f=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${m+C}-${e}`})})).json();if(!f.success)throw new Error(f.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(f.data.sessionId,f.data.session),g.push(f.data.sessionId)}g[0]&&await this.selectSession(g[0]),this.terminal.focus();return}const c=this._nextCaseSessionStartNumber(e,"s"),l=[];for(let m=0;m<t;m++)l.push(`s${c+m}-${e}`);const d=l.map(m=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...i?{caseName:e}:{workingDir:r},mode:"shell",name:m})}).then(g=>g.json())),u=await Promise.all(d),h=[];for(const m of u){if(!m.success)throw new Error(m.error);await this._ensureCreatedSessionVisible(m.data.session.id,m.data.session),h.push(m.data.session.id)}await Promise.all(h.map(m=>fetch(`/api/sessions/${m}/shell`,{method:"POST"})));const p=this.getTerminalDimensions();p&&await Promise.all(h.map(m=>fetch(`/api/sessions/${m}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),h.length>0&&await this.selectSession(h[0]),this.terminal.focus()}catch(o){this._reportSessionLaunchError(s,o.message)}},_readTabCount(){return Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount")?.value)||1))},async _launchQuickStartInstances(e,t,s,o,n){const a=this._nextCaseSessionStartNumber(e);let i=null,r=!1;for(let c=0;c<t;c++){const l=`w${a+c}-${e}`,d=o(l),u=await this._quickStartWithCustomModelConfirm(r&&d.customModel?{...d,customModel:{...d.customModel,confirmedContext:!0,confirmedSwap:!0}}:d);if(!u.success)throw new Error(u.error||`Failed to start ${s}`);r=!0,await this._ensureCreatedSessionVisible(u.data.sessionId,u.data.session),i||(i=u.data.sessionId)}return t>1&&this._appendSessionLaunchStatus(n,`All ${t} ${s} session(s) ready`),i},async _runCliMode(e){const t=registryCliById(e),s=RUN_MODE_LAUNCH[e]||t&&{label:t.label,installHint:`${t.label} is not available on this host.`,supportsCustomModel:!1,buildConfig:()=>null};if(!s)throw new Error(`Unknown run mode: ${e}`);const o=document.getElementById("quickStartCase").value||"testcase",n=(this.cases||[]).find(c=>c.name===o)?.location,a=n==="remote"||n==="docker",i=this._readTabCount(),r=this._beginSessionLaunchStatus(`Starting ${i} ${s.label} session(s) in ${o}...`);this.terminal.focus();try{if(!a&&RUN_MODE_LAUNCH[e]){const h=(await(await fetch(`/api/${e}/status`)).json()).data;if(!h.available){this._reportSessionLaunchError(r,s.installHint);return}if(s.unrunnableHint&&!h.runnable){this._reportSessionLaunchError(r,s.unrunnableHint);return}}else if(!a&&!this.isCliAvailable(e)){this._reportSessionLaunchError(r,s.installHint);return}const c=this.loadAppSettingsFromStorage(),l=this.buildEnvOverrides(this.getCaseSettings(o),c),d=await this._launchQuickStartInstances(o,i,s.label,u=>({caseName:o,mode:e,sessionName:u,...a?{}:{...s.buildConfig(c)||{},...Object.keys(l).length>0?{envOverrides:l}:{},...s.supportsCustomModel&&this._pendingCustomModelForLaunch?{customModel:this._pendingCustomModelForLaunch}:{}}}),r);d&&await this.selectSession(d),this.terminal.focus()}catch(c){this._reportSessionLaunchError(r,c.message)}},async runOpenCode(){return this._runCliMode("opencode")},async runCodex(){return this._runCliMode("codex")},async runGemini(){return this._runCliMode("gemini")},async runAntigravity(){return this._runCliMode("antigravity")},async runPi(){return this._runCliMode("pi")},async runOmp(){return this._runCliMode("omp")},async runGrok(){return this._runCliMode("grok")},async runDeepSeek(){return this._runCliMode("deepseek")},_tabDetachOverrides(){if(this._tabDetachOverrideMap===void 0)try{this._tabDetachOverrideMap=JSON.parse(localStorage.getItem("codeman:tab-detach-overrides")||"{}")||{}}catch{this._tabDetachOverrideMap={}}return this._tabDetachOverrideMap},hasTabDetachOverride(e){return!!this._tabDetachOverrides()[e]},onSessionTabDetachToggle(e){const t=this.editingSessionId;if(!t)return;const s=this._tabDetachOverrides();e?s[t]=1:delete s[t];for(const n of Object.keys(s))n!==t&&this.sessions&&!this.sessions.has(n)&&delete s[n];try{localStorage.setItem("codeman:tab-detach-overrides",JSON.stringify(s))}catch{}const o=document.querySelector(`.session-tab[data-id="${CSS.escape(t)}"]`);o&&o.classList.toggle("tab-show-detach",!!e)},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=document.getElementById("sessionOptShowTabDetach");s&&(s.checked=this.hasTabDetachOverride(e));const o=isExternalCliRunMode(t.mode);this.switchOptionsTab(o?"summary":"respawn");const n=document.getElementById("sessionRespawnStatus"),a=document.getElementById("modalEnableRespawnBtn"),i=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(n.classList.add("active"),n.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",a.style.display="none",i.style.display=""):(n.classList.remove("active"),n.querySelector(".respawn-status-text").textContent="Not active",a.style.display="",i.style.display="none");const r=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?r.style.display="":r.style.display="none";const c=o;document.querySelectorAll("[data-claude-only]").forEach(y=>{y.style.display=c?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const d=parseSessionPrefix(t.name),u=document.getElementById("modalSessionPrefix");d?(u.textContent=d.prefix+": ",u.style.display="",document.getElementById("modalSessionName").value=d.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(u.style.display="none",u.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const h=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(y=>{y.classList.toggle("selected",y.dataset.color===h)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const m=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="ralph"]'),g=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="respawn"]');if(c?(m&&(m.style.display="none"),g&&(g.style.display="none"),this.switchOptionsTab("context")):(m&&(m.style.display=""),g&&(g.style.display="")),!c){const y=this.ralphStates.get(e);this.populateRalphForm({enabled:y?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:y?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:y?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:y?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:y?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const C=document.getElementById("sessionOptionsModal");C.dataset.chipsReady!=="1"&&(C.dataset.chipsReady="1",C.addEventListener("change",y=>{y.target?.closest?.(".set-chip")&&this._syncSettingsChips()})),this._syncSettingsChips(),C.classList.add("active"),this.activeFocusTrap=new FocusTrap(C),this.activeFocusTrap.activate()},_applyLocalSessionName(e,t){if(typeof t!="string")return;const s=this.sessions.get(e);s&&(s.name=t,this.sessions.set(e,s),this.updateSubagentParentNames?.(e))},async _putSessionName(e,t){const s=await this._apiPut(`/api/sessions/${e}/name`,{name:t});if(!s||!s.ok)return null;let o=null;try{o=await s.json()}catch{return null}if(o&&o.success===!1)return null;const n=o?.data?.name;return typeof n=="string"?n:t},async saveSessionName(){if(!this.editingSessionId)return;const e=this.editingSessionId,t=this.sessions.get(e),s=t?parseSessionPrefix(t.name):null,o=document.getElementById("modalSessionName").value.trim();let n;s?n=s.prefix+(o?": "+o:""):n=o;const a=await this._putSessionName(e,n);if(a===null){this.showToast("Failed to save session name","error");return}this._applyLocalSessionName(e,a),this.renderSessionTabs()},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const o=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${o}`,t.classList.add("active")}else t.textContent="",t.classList.remove("active")},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.data&&s.data.config){const o=s.data.config;document.getElementById("modalRespawnPrompt").value=o.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=o.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=o.sendInit??!0,document.getElementById("modalRespawnKickstart").value=o.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=o.autoAcceptPrompts??!0,o.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${o.durationMinutes}"]`)?this.selectDurationPreset(String(o.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=o.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),o=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),o.focus()):(s.classList.remove("visible"),o.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const o=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return o?parseInt(o):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .set-rail-item").forEach(s=>{s.classList.toggle("active",s.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary");const t=document.getElementById("sessionOptionsDoc");t&&(t.scrollTop=0),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;this._activeRename?.cancel();const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0,s.classList.add("tab-name-renaming");const o=this.getSessionName(t),n=parseSessionPrefix(t.name),a=s.textContent,i=[...s.childNodes].map(f=>f.cloneNode(!0)),r=()=>{s.replaceChildren(...i.map(f=>f.cloneNode(!0)))};for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(n){const f=document.createElement("span");f.className="tab-rename-prefix",f.textContent=n.prefix+": ",f.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(f)}const c=document.createElement("input");c.type="text",c.value=n?n.suffix:t.name||"",c.placeholder=n?"Add description...":o,c.className="tab-rename-input";const l=s.closest(".tab-rail")?"auto":this.isSessionSidebarActive?.()?"100%":"80px";c.style.cssText=`width: ${l}; min-width: 0; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;`,s.appendChild(c),c.focus(),c.select();let d=!1,u=!1,h=!1;const p=()=>{this._activeRename===y&&(this._inlineRenameActive=!1)},m=()=>{this._activeRename===y&&(h=!0,p(),this._activeRename=null,this.renderSessionTabs())},g=()=>{u||h||(u=!0,d=!0,s.classList.remove("tab-name-renaming"),r(),m())},C=async({commit:f})=>{if(d||u)return;if(d=!0,s.classList.remove("tab-name-renaming"),!f){g();return}if(this._activeRename!==y)return;p();const b=c.value.trim(),v=n?n.prefix+(b?": "+b:""):b;if(v===t.name?r():s.textContent=v||a,this.sessions.has(e)&&v!==t.name){const w=await this._putSessionName(e,v);if(u||this._activeRename!==y||!this.sessions.has(e))return;w===null?(r(),this.showToast("Failed to rename","error")):this._applyLocalSessionName(e,w)}m()},y={sessionId:e,cancel:g};this._activeRename=y,c.addEventListener("blur",()=>C({commit:!0})),c.addEventListener("keydown",f=>{f.isComposing||f.keyCode===229||(f.key==="Enter"?(f.preventDefault(),c.blur()):f.key==="Escape"&&(f.preventDefault(),g()))})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const o=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",o))};setTimeout(()=>document.addEventListener("click",o),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const o=document.getElementById("caseOpusContext1mMobile");o&&(o.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const o=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",o))};setTimeout(()=>document.addEventListener("click",o),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const o=document.getElementById("caseOpusContext1m");o&&(o.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",["remoteCaseName","remoteCasePath","remoteHostId","remoteHostAddress","remoteHostUsername","remoteHostPort","remoteHostCodexCommand","remoteHostIdentityFile","remoteHostSocksProxy","remoteHostJumpHost","remoteHostExtraSshOptions","remoteHostWakeMac","remoteHostWakeCommand"].forEach(r=>{const c=document.getElementById(r);c&&(c.value="")}),this._resetCloneForm();const t=document.getElementById("caseCloneTabBtn");t&&(t.style.display=this.isCliAvailable("git")?"":"none"),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const s=document.getElementById("createCaseModal");s.querySelectorAll(".set-rail-item").forEach(r=>{r.onclick=()=>this.switchCaseModalTab(r.dataset.tab)});const o=document.getElementById("dockerAdoptExisting");o&&(o.onchange=()=>this._syncDockerAdoptMode());const n=document.getElementById("dockerAdoptCheckBtn");n&&(n.onclick=()=>this._dockerAdoptPreflight());const a=document.getElementById("dockerAdoptJumpBtn");a&&(a.onclick=()=>this.jumpToDockerAdopt());const i=document.getElementById("dockerHostId");i&&(i.onchange=()=>{delete document.getElementById("dockerContainerList")?.dataset.loadedFor,document.getElementById("dockerAdoptExisting")?.checked&&this._loadDockerContainerOptions()}),delete document.getElementById("dockerContainerList")?.dataset.loadedFor,this._syncDockerAdoptMode(),s.querySelectorAll('input[type="text"]').forEach(r=>{r._mobileScrollWired||(r._mobileScrollWired=!0,r.addEventListener("focus",()=>{window.innerWidth<600&&setTimeout(()=>r.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),s.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".set-rail-item").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)}),t.querySelectorAll(".set-section").forEach(n=>{n.classList.toggle("hidden",n.id!==e)});const s=document.getElementById("createCaseDoc");s&&(s.scrollTop=0);const o=["caseModalSubmit","caseModalSubmitMobile"].map(n=>document.getElementById(n)).filter(Boolean);if(e==="case-manage")o.forEach(n=>{n.style.display="none"}),this.renderCaseManageList(),this.refreshDockerExports();else{const n=e==="case-create"?"Create":e==="case-clone"?"Clone":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link";o.forEach(a=>{a.style.display="",a.textContent=n})}e==="case-create"?document.getElementById("newCaseName").focus():e==="case-clone"?document.getElementById("cloneRepoUrl").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=["caseModalSubmit","caseModalSubmitMobile"].map(o=>document.getElementById(o)).filter(Boolean),t=e.map(o=>o.textContent),s=this.caseModalTab==="case-create"?"Creating...":this.caseModalTab==="case-clone"?"Cloning...":"Linking...";e.forEach(o=>{o.classList.add("loading"),o.textContent=s,o.disabled=!0});try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-clone"?await this.cloneCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.forEach((o,n)=>{o.classList.remove("loading"),o.disabled=!1,o.textContent=t[n]})}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}const s=document.getElementById("newCaseDocker")?.checked,o=s?"/api/cases/docker-quickcreate":"/api/cases",n=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const i=await(await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json();if(i.success)if(this.closeCreateCaseModal(),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e),s){const r=i.data?.capsEnforced===!1?" (resource caps advisory on this engine)":"";this.showToast(`Docker case "${e}" created${r} \u2014 starting session\u2026`,"success"),await this.runClaude()}else this.showToast(`Case "${e}" created`,"success");else this.showToast(i.error||"Failed to create case","error")}catch(a){console.error("Failed to create case:",a),this.showToast("Failed to create case: "+a.message,"error")}},applyDockerTemplate(){const e=document.getElementById("quickDockerTemplate")?.value,s={small:{m:"2g",c:"1",g:""},medium:{m:"",c:"",g:""},large:{m:"8g",c:"4",g:""},gpu:{m:"8g",c:"4",g:"all"}}[e];if(!s)return;const o=(n,a)=>{const i=document.getElementById(n);i&&(i.value=a)};o("quickDockerMemory",s.m),o("quickDockerCpus",s.c),o("quickDockerGpus",s.g)},_collectDockerQuickSettings(){const e=c=>(document.getElementById(c)?.value||"").trim(),t={},s=e("quickDockerMemory");s&&(t.memory=s);const o=e("quickDockerCpus");o&&(t.cpus=o);const n=e("quickDockerGpus");n&&n.toLowerCase()!=="none"&&(t.gpus=n);const a=document.getElementById("quickDockerNetwork")?.value;a&&a!=="bridge"&&(t.network=a);const i=e("quickDockerImage");i&&(t.image=i);const r=document.getElementById("quickDockerMountCreds");return r&&!r.checked&&(t.mountCredentials=!1),t},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const o=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();o.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(o.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},_resetCloneForm(){const e=(i,r)=>{const c=document.getElementById(i);c&&(c.value=r)};e("cloneRepoUrl",""),e("cloneCaseName",""),e("cloneRepoRef","");const t=document.getElementById("cloneShallow");t&&(t.checked=!1);const s=document.getElementById("cloneStartSession");s&&(s.checked=!1);const o=document.getElementById("cloneRepoRefOptions");o&&o.replaceChildren();const n=document.getElementById("cloneRefHint");n&&(n.textContent="Leave blank for the repository's default branch."),this._cloneNameEdited=!1,this._clonePreflight=null,clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");const a=document.getElementById("cloneCaseBrain");if(a){for(const r of a.options){const c=r.dataset.cli;r.hidden=!!c&&!this.isCliAvailable(c)}const i=this.runMode||"claude";a.value=[...a.options].some(r=>r.value===i&&!r.hidden)?i:""}},_setCloneStatus(e,t){const s=document.getElementById("cloneRepoStatus");s&&(s.textContent=e,s.className=`form-hint clone-status${t?" clone-status-"+t:""}`)},_repoNameFromUrl(e){const t=(e||"").trim().replace(/\/+$/,"");return t?(t.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//,"").replace(/^[^@/]*@/,"").split(/[/:]/).filter(Boolean).pop()||"").replace(/\.git$/i,"").replace(/[^a-zA-Z0-9_-]+/g,"-").replace(/-{2,}/g,"-").replace(/^[-_]+|[-_]+$/g,"").slice(0,64):""},onCloneNameEdited(){this._cloneNameEdited=!!document.getElementById("cloneCaseName")?.value.trim()},onCloneUrlInput(){const e=document.getElementById("cloneRepoUrl")?.value.trim()||"",t=document.getElementById("cloneCaseName");if(t&&!this._cloneNameEdited&&(t.value=this._repoNameFromUrl(e)),clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,!e){this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");return}if(this.isCliAvailable("git")===!1){this._setCloneStatus("git is not installed on the Codeman host, so cloning is unavailable.","err");return}this._setCloneStatus("Checking the repository\u2026",""),this._clonePreflightTimer=setTimeout(()=>this._runClonePreflight(e),450)},async _runClonePreflight(e){const t=new AbortController;this._clonePreflightAbort=t;try{const o=await(await fetch("/api/cases/clone-preflight",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repository:e}),signal:t.signal})).json();if(document.getElementById("cloneRepoUrl")?.value.trim()!==e)return;if(!o.success){this._setCloneStatus(o.error||"Could not check that URL.","err");return}this._applyClonePreflight(o.data,e)}catch(s){if(s.name==="AbortError")return;this._setCloneStatus("Could not reach Codeman to check that URL.","err")}},_applyClonePreflight(e,t){this._clonePreflight=e;const s=e?.parse;if(!s?.cloneable){this._setCloneStatus(s?.message||"That URL cannot be cloned.","err");return}const o=document.getElementById("cloneCaseName");o&&!this._cloneNameEdited&&s.suggestedName&&(o.value=s.suggestedName);const n=s.owner?`${s.provider} ${s.owner}/${s.repo}`:`${s.provider} ${s.repo}`;if(e.gitAvailable===!1){this._setCloneStatus(`${n}: git is not installed on the Codeman host.`,"err");return}const a=e.remote;if(a&&!a.reachable){this._setCloneStatus(`${n}: ${a.failure?.message||"the remote could not be read."}`,"err");return}const i=document.getElementById("cloneRefHint"),r=document.getElementById("cloneRepoRefOptions");if(a&&r){r.replaceChildren();for(const l of[...a.branches||[],...a.tags||[]]){const d=document.createElement("option");d.value=l,r.appendChild(d)}if(i){const l=`${a.branches?.length||0} branches, ${a.tags?.length||0} tags`;i.textContent=a.defaultBranch?`Blank clones the default branch (${a.defaultBranch}). ${l} available.`:`Blank clones the default branch. ${l} available.`}}const c=s.warnings?.[0];this._setCloneStatus(c?`${n}: ${c}`:`${n}: ready to clone.`,c?"warn":"ok")},async cloneCase(){const e=document.getElementById("cloneRepoUrl").value.trim(),t=document.getElementById("cloneCaseName").value.trim(),s=document.getElementById("cloneRepoRef").value.trim(),o=!!document.getElementById("cloneShallow")?.checked,n=document.getElementById("cloneCaseBrain")?.value||"",a=!!document.getElementById("cloneStartSession")?.checked;if(!e){this.showToast("Please enter a repository URL","error");return}if(!t){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(t)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}this._setCloneStatus(`Cloning ${e}\u2026 this can take a while for a large repository.`,"");try{const r=await(await fetch("/api/cases/clone",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,repository:e,...s?{ref:s}:{},...o?{shallow:!0}:{}})})).json();if(!r.success){this._setCloneStatus(r.error||"Clone failed.","err"),this.showToast(r.error||"Failed to clone repository","error");return}n&&this.setRunMode(n),this.closeCreateCaseModal(),await this.loadQuickStartCases(t),await this.saveLastUsedCase(t),this.showToast(`Cloned into case "${t}"`,"success");for(const c of r.data?.warnings||[])this.showToast(c,"warning");a&&await this.run()}catch(i){console.error("Failed to clone repository:",i),this._setCloneStatus(`Lost the connection while cloning: ${i.message}. If git finishes, the case still appears in the list.`,"warn"),this.showToast("Clone request interrupted \u2014 watch the case list","error")}},openLinkCasePathPicker(){const e=document.getElementById("linkCasePath");PathPicker.open({title:"Select Existing Project Folder",initialPath:e.value.trim(),directoriesOnly:!0,onSelect:t=>{e.value=t;const s=document.getElementById("linkCaseName");if(!s.value.trim()){const o=t.split("/").filter(Boolean).pop()||"";/^[\p{L}\p{N}_-]+$/u.test(o)&&(s.value=o)}e.focus(),e.setSelectionRange(t.length,t.length)}})},openDockerWorkspacePathPicker(){const e=document.getElementById("dockerWorkspacePath");PathPicker.open({title:"Select Host Workspace Folder",initialPath:e.value.trim(),directoriesOnly:!0,onSelect:t=>{e.value=t;const s=document.getElementById("dockerCaseName");if(s&&!s.value.trim()){const o=t.split("/").filter(Boolean).pop()||"";/^[a-zA-Z0-9_-]+$/.test(o)&&(s.value=o)}}})},openDockerWorkdirPicker(){const e=document.getElementById("dockerAdoptWorkdir"),t=document.getElementById("dockerContainerName")?.value.trim(),s=document.getElementById("dockerHostId")?.value.trim()||"local";if(!t){this.showToast("Enter the container name first","error");return}PathPicker.open({title:`Select Folder Inside ${t}`,initialPath:e.value.trim()||"/",directoriesOnly:!0,fetchListing:async o=>{const n=await this._apiJson("/api/docker-cases/browse",{method:"POST",body:{hostId:s,container:t,path:o||"/"}});return n?n.error?{success:!1,error:n.error}:{success:!0,data:{...n,root:"/",roots:[{label:t,path:"/"}],truncated:!1}}:{success:!1,error:`Could not read ${t}. Is it running?`}},onSelect:o=>{e.value=o}})},async linkRemoteCase(){const e=document.getElementById("remoteCaseName").value.trim(),t=document.getElementById("remoteCasePath").value.trim(),s=document.getElementById("remoteHostId").value.trim(),o=document.getElementById("remoteHostAddress").value.trim(),n=document.getElementById("remoteHostUsername").value.trim(),a=document.getElementById("remoteHostCodexCommand").value.trim(),i=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),l=document.getElementById("remoteHostJumpHost").value.trim(),d=document.getElementById("remoteHostWakeMac").value.trim(),u=document.getElementById("remoteHostWakeCommand").value.trim(),h=document.getElementById("remoteHostExtraSshOptions").value.split(`
27
+ `).map(m=>m.trim()).filter(m=>m.length>0);if(!e||!t||!s||!o||!n){this.showToast("Please complete all required remote fields","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Remote path must be absolute","error");return}let p;if(i&&(p=Number(i),!Number.isInteger(p)||p<1||p>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const m={id:s,label:s,host:o,username:n,...p?{port:p}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...l?{jumpHost:l}:{},...h.length?{extraSshOptions:h}:{},...d?{wakeMac:d}:{},...u?{wakeCommand:u}:{},...a?{commands:{codex:a}}:{}},C=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)})).json();if(!C.success&&C.errorCode!=="ALREADY_EXISTS")throw new Error(C.error||"Failed to save remote host");const f=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();f.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(f.error||"Failed to link remote case","error")}catch(m){console.error("Failed to link remote case:",m),this.showToast("Failed to link remote case: "+m.message,"error")}},_syncDockerAdoptMode(){const e=document.getElementById("createCaseModal");if(!e)return;const t=document.getElementById("dockerAdoptExisting")?.checked;t?e.setAttribute("data-docker-adopt","1"):e.removeAttribute("data-docker-adopt"),t&&(this._loadDockerContainerOptions(),this._loadDockerCloneOptions())},async _loadDockerCloneOptions(){const e=document.getElementById("dockerAdoptCloneFrom"),t=document.getElementById("dockerAdoptCloneRow");if(!e||!t)return;let s=[];try{const a=await(await fetch("/api/cases")).json();s=(Array.isArray(a)?a:a?.data||[]).filter(i=>i?.docker&&i.docker.owned===!1)}catch{s=[]}e.textContent="";const o=document.createElement("option");o.value="",o.textContent="Start from scratch",e.appendChild(o);for(const n of s){const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name} \u2014 ${n.docker.container}:${n.docker.containerWorkdir||n.docker.path}`,a.dataset.container=n.docker.container,a.dataset.hostId=n.docker.hostId,a.dataset.path=n.docker.path,a.dataset.workdir=n.docker.containerWorkdir||n.docker.path,e.appendChild(a)}t.hidden=s.length===0},applyDockerCloneSource(){const e=document.getElementById("dockerAdoptCloneFrom"),t=e?.selectedOptions?.[0];if(!t||!t.value)return;const s=(n,a)=>{const i=document.getElementById(n);i&&(i.value=a||"")};s("dockerContainerName",t.dataset.container),s("dockerHostId",t.dataset.hostId),s("dockerWorkspacePath",t.dataset.path),s("dockerCaseName",t.value),s("dockerAdoptWorkdir",t.dataset.workdir),e.dataset.appliedName=t.value,e.dataset.appliedWorkdir=t.dataset.workdir||"";const o=document.getElementById("dockerAdoptWorkdir");o?.focus(),o&&o.setSelectionRange(o.value.length,o.value.length)},dockerCloneGuard(){const e=document.getElementById("dockerAdoptCloneFrom");if(!e||!e.value)return null;const t=document.getElementById("dockerCaseName"),s=document.getElementById("dockerAdoptWorkdir");return t&&t.value.trim()===(e.dataset.appliedName||"")?{el:t,message:`"${t.value.trim()}" is the case you copied from \u2014 give this one a new name.`}:s&&s.value.trim()===(e.dataset.appliedWorkdir||"")?{el:s,message:"Same container and same directory as the case you copied from \u2014 point this one at another directory."}:null},async _loadDockerContainerOptions(){const e=document.getElementById("dockerContainerList");if(!e)return;const t=document.getElementById("dockerHostId")?.value.trim()||"local";if(e.dataset.loadedFor===t)return;const o=(await this._apiJson(`/api/docker-hosts/${encodeURIComponent(t)}/containers`))?.containers||[];e.textContent="";for(const n of o){const a=document.createElement("option");a.value=n.name,a.textContent=n.running?`${n.image} \xB7 ${n.status}`:`${n.image} \xB7 ${n.status} (not running)`,e.appendChild(a)}e.dataset.loadedFor=t},jumpToDockerAdopt(){this.switchCaseModalTab("case-docker");const e=document.getElementById("dockerAdoptExisting");e&&(e.checked=!0),this._syncDockerAdoptMode(),document.getElementById("dockerContainerName")?.focus()},async _dockerAdoptPreflight(){const e=document.getElementById("dockerLinkStatus"),t=document.getElementById("dockerContainerName")?.value.trim(),s=document.getElementById("dockerAdoptWorkdir")?.value.trim(),o=document.getElementById("dockerHostId").value.trim()||"local";if(!t){e&&(e.textContent="Enter a container name first.");return}e&&(e.textContent="Inspecting container...");const n=await this._apiJson("/api/docker-cases/adopt-preflight",{method:"POST",body:{hostId:o,container:t,...s?{containerWorkdir:s}:{}}});if(!e)return;if(!n){e.textContent="Could not reach the docker host profile. Save a Host ID first.";return}if(!n.ok){e.textContent=n.error||"Container is not adoptable.";return}const a=(n.availableModes||[]).filter(i=>i!=="shell");e.textContent=a.length?`Running (${n.image||"unknown image"}). Available: ${a.join(", ")}.`:`Running (${n.image||"unknown image"}), but no agent CLI found inside \u2014 only Shell will work.`},async linkDockerCase(){const e=document.getElementById("dockerCaseName").value.trim(),t=document.getElementById("dockerWorkspacePath").value.trim(),s=document.getElementById("dockerHostId").value.trim()||"local",o=!!document.getElementById("dockerAdoptExisting")?.checked,n=document.getElementById("dockerContainerName")?.value.trim()||"",a=document.getElementById("dockerAdoptWorkdir")?.value.trim()||"",i=document.getElementById("dockerImage").value.trim()||"codeman/agent:base",r=document.getElementById("dockerNetwork").value,c=document.getElementById("dockerMemory").value.trim(),l=document.getElementById("dockerCpus").value.trim(),d=document.getElementById("dockerMountCredentials").checked,u=document.getElementById("dockerResumeOnStart").checked,h=document.getElementById("dockerLinkStatus");if(!e||!t){this.showToast("Please enter a case name and workspace path","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)||!/^[a-zA-Z0-9_-]+$/.test(s)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t.startsWith("/")){this.showToast("Workspace path must be absolute","error");return}if(o&&!n){this.showToast("Enter the name of the running container to attach to","error");return}const p=o?this.dockerCloneGuard():null;if(p){this.showToast(p.message,"error");const m=document.getElementById("dockerLinkStatus");m&&(m.textContent=p.message),p.el.focus(),p.el.select?.();return}try{h&&(h.textContent=o?"Inspecting the existing container...":"Checking docker daemon + base image...");const m={};c&&(m.memory=c),l&&(m.cpus=l);const g={id:s,label:s,image:i,network:r,mountCredentials:d,resumeOnStart:u,...Object.keys(m).length?{resources:m}:{}};let C=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)}),y=await C.json();if(!y.success&&y.errorCode==="ALREADY_EXISTS"&&(C=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)}),y=await C.json()),!y.success)throw new Error(y.error||"Failed to save docker host");const b=await(await fetch(o?"/api/cases/docker-adopt":"/api/cases/docker-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o?{name:e,hostId:s,hostWorkspacePath:t,container:n,...a?{containerWorkdir:a}:{}}:{name:e,hostId:s,hostWorkspacePath:t})})).json();if(b.success){this.closeCreateCaseModal();const v=b.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"",k=(b.data?.availableModes||[]).filter(I=>I!=="shell"),w=o&&k.length?` \u2014 found ${k.join(", ")}`:"";this.showToast(`Docker case "${e}" ${o?"attached":"linked"}${v}${w}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else h&&(h.textContent=b.error||"Failed to link docker case"),this.showToast(b.error||"Failed to link docker case","error")}catch(m){console.error("Failed to link docker case:",m),h&&(h.textContent=m.message),this.showToast("Failed to link docker case: "+m.message,"error")}},async refreshDockerExports(){const e=document.getElementById("dockerExportsList");if(e)try{const o=(await(await fetch("/api/docker-exports")).json())?.data?.exports||[];if(o.length===0){e.innerHTML='<span class="form-hint">No exports yet. Export a docker case from its tab.</span>';return}e.innerHTML=o.map(n=>{const a=(n.sizeBytes/1e6).toFixed(1),i=escapeHtml(n.name);return`<div class="case-manage-item" style="display:flex; align-items:center; gap:8px; justify-content:space-between;">
28
28
  <span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${i}">${i} <span class="form-hint">(${a} MB)</span></span>
29
29
  <span style="flex-shrink:0;">
30
30
  <a class="btn-toolbar" href="${CodemanBase.url(`/api/docker-exports/${encodeURIComponent(n.name)}`)}" download>Download</a>