aicodeman 1.5.1 → 1.6.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 (122) hide show
  1. package/dist/remote-hosts.d.ts +61 -1
  2. package/dist/remote-hosts.d.ts.map +1 -1
  3. package/dist/remote-hosts.js +142 -0
  4. package/dist/remote-hosts.js.map +1 -1
  5. package/dist/remote-reconnect.d.ts +129 -0
  6. package/dist/remote-reconnect.d.ts.map +1 -0
  7. package/dist/remote-reconnect.js +119 -0
  8. package/dist/remote-reconnect.js.map +1 -0
  9. package/dist/services/unified-session-service.d.ts +13 -1
  10. package/dist/services/unified-session-service.d.ts.map +1 -1
  11. package/dist/services/unified-session-service.js +88 -3
  12. package/dist/services/unified-session-service.js.map +1 -1
  13. package/dist/session-order.d.ts +40 -0
  14. package/dist/session-order.d.ts.map +1 -0
  15. package/dist/session-order.js +67 -0
  16. package/dist/session-order.js.map +1 -0
  17. package/dist/session.d.ts +39 -0
  18. package/dist/session.d.ts.map +1 -1
  19. package/dist/session.js +87 -19
  20. package/dist/session.js.map +1 -1
  21. package/dist/state-store.d.ts +12 -0
  22. package/dist/state-store.d.ts.map +1 -1
  23. package/dist/state-store.js +33 -0
  24. package/dist/state-store.js.map +1 -1
  25. package/dist/tmux-manager.d.ts +66 -0
  26. package/dist/tmux-manager.d.ts.map +1 -1
  27. package/dist/tmux-manager.js +226 -2
  28. package/dist/tmux-manager.js.map +1 -1
  29. package/dist/types/app-state.d.ts +2 -0
  30. package/dist/types/app-state.d.ts.map +1 -1
  31. package/dist/types/app-state.js.map +1 -1
  32. package/dist/types/session.d.ts +42 -0
  33. package/dist/types/session.d.ts.map +1 -1
  34. package/dist/types/session.js.map +1 -1
  35. package/dist/web/public/admin-ui.js.gz +0 -0
  36. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  37. package/dist/web/public/app.b78b0465.js +38 -0
  38. package/dist/web/public/app.b78b0465.js.br +0 -0
  39. package/dist/web/public/app.b78b0465.js.gz +0 -0
  40. package/dist/web/public/{constants.ad3a8031.js → constants.21a0a83e.js} +9 -0
  41. package/dist/web/public/constants.21a0a83e.js.br +0 -0
  42. package/dist/web/public/{constants.ad3a8031.js.gz → constants.21a0a83e.js.gz} +0 -0
  43. package/dist/web/public/cron-ui.js.gz +0 -0
  44. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  45. package/dist/web/public/index.html +29 -10
  46. package/dist/web/public/index.html.br +0 -0
  47. package/dist/web/public/index.html.gz +0 -0
  48. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  49. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  50. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  51. package/dist/web/public/mobile.597136a6.css.gz +0 -0
  52. package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
  53. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  54. package/dist/web/public/{panels-ui.3c9b0465.js → panels-ui.58582d4c.js} +2 -2
  55. package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
  56. package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
  57. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  58. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  59. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  60. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  61. package/dist/web/public/{session-ui.e5d4f9b5.js → session-ui.0cf86dd4.js} +30 -22
  62. package/dist/web/public/session-ui.0cf86dd4.js.br +0 -0
  63. package/dist/web/public/session-ui.0cf86dd4.js.gz +0 -0
  64. package/dist/web/public/{settings-ui.29214cf1.js → settings-ui.bc5f4792.js} +2 -2
  65. package/dist/web/public/settings-ui.bc5f4792.js.br +0 -0
  66. package/dist/web/public/settings-ui.bc5f4792.js.gz +0 -0
  67. package/dist/web/public/styles.7295e6f4.css +1 -0
  68. package/dist/web/public/styles.7295e6f4.css.br +0 -0
  69. package/dist/web/public/styles.7295e6f4.css.gz +0 -0
  70. package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
  71. package/dist/web/public/sw.js.gz +0 -0
  72. package/dist/web/public/terminal-ui.9ea677e9.js +3 -0
  73. package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
  74. package/dist/web/public/terminal-ui.9ea677e9.js.gz +0 -0
  75. package/dist/web/public/ultracode-panel.js.gz +0 -0
  76. package/dist/web/public/ultracode-windows.js.gz +0 -0
  77. package/dist/web/public/upload.html.gz +0 -0
  78. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  79. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  80. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  81. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  82. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  83. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  84. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm.css.gz +0 -0
  86. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  87. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  88. package/dist/web/routes/case-routes.d.ts.map +1 -1
  89. package/dist/web/routes/case-routes.js +19 -1
  90. package/dist/web/routes/case-routes.js.map +1 -1
  91. package/dist/web/routes/session-routes.d.ts.map +1 -1
  92. package/dist/web/routes/session-routes.js +140 -3
  93. package/dist/web/routes/session-routes.js.map +1 -1
  94. package/dist/web/schemas.d.ts +13 -0
  95. package/dist/web/schemas.d.ts.map +1 -1
  96. package/dist/web/schemas.js +31 -0
  97. package/dist/web/schemas.js.map +1 -1
  98. package/dist/web/server.d.ts +9 -0
  99. package/dist/web/server.d.ts.map +1 -1
  100. package/dist/web/server.js +57 -1
  101. package/dist/web/server.js.map +1 -1
  102. package/dist/web/sse-events.d.ts +15 -0
  103. package/dist/web/sse-events.d.ts.map +1 -1
  104. package/dist/web/sse-events.js +18 -0
  105. package/dist/web/sse-events.js.map +1 -1
  106. package/package.json +1 -1
  107. package/dist/web/public/app.1ccca158.js +0 -38
  108. package/dist/web/public/app.1ccca158.js.br +0 -0
  109. package/dist/web/public/app.1ccca158.js.gz +0 -0
  110. package/dist/web/public/constants.ad3a8031.js.br +0 -0
  111. package/dist/web/public/panels-ui.3c9b0465.js.br +0 -0
  112. package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
  113. package/dist/web/public/session-ui.e5d4f9b5.js.br +0 -0
  114. package/dist/web/public/session-ui.e5d4f9b5.js.gz +0 -0
  115. package/dist/web/public/settings-ui.29214cf1.js.br +0 -0
  116. package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
  117. package/dist/web/public/styles.9e6cdb9d.css +0 -1
  118. package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
  119. package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
  120. package/dist/web/public/terminal-ui.efda6083.js +0 -3
  121. package/dist/web/public/terminal-ui.efda6083.js.br +0 -0
  122. package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
@@ -1,26 +1,34 @@
1
- "use strict";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 a of e)!a?.name||s.has(a.name)||(s.add(a.name),t.push(a));return s.has("testcase")||t.push({name:"testcase"}),t.map(a=>{const n=this.formatCasePickerLabel(a),i=[a.name,n,a.path,a.location,a.remote?.hostId,a.remote?.label,a.remote?.path,a.docker?.container,a.docker?.image,a.docker?.path].filter(Boolean).join(" ").toLowerCase();return{name:a.name,label:n,case:a,searchText:i}}).sort((a,n)=>a.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(a=>s.every(n=>a.searchText.includes(n)))},getCasePickerOptions(){return this.buildCasePickerOptions(this.cases||[])},updateCasePickerInput(e){const t=document.getElementById("quickStartCaseSearch");if(!t)return;const s=this.getCasePickerOptions().find(a=>a.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");!t||!s||(this._casePickerOpen=!0,this._casePickerFilter=e,this._casePickerActiveIndex=0,t.setAttribute("aria-expanded","true"),this.renderCasePickerList())},closeCasePicker(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList");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 a=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||""),n=s.value||"testcase",i=Math.max(0,a.length-1);if(this._casePickerActiveIndex=Math.min(Math.max(this._casePickerActiveIndex||0,0),i),a.length===0){t.innerHTML='<div class="case-combobox-empty">No cases match</div>',t.classList.remove("hidden"),e.removeAttribute("aria-activedescendant");return}t.innerHTML=a.map((o,r)=>{const c=r===this._casePickerActiveIndex,m=o.name===n;return`
1
+ "use strict";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 a of e)!a?.name||s.has(a.name)||(s.add(a.name),t.push(a));return s.has("testcase")||t.push({name:"testcase"}),t.map(a=>{const o=this.formatCasePickerLabel(a),n=[a.name,o,a.path,a.location,a.remote?.hostId,a.remote?.label,a.remote?.path,a.docker?.container,a.docker?.image,a.docker?.path].filter(Boolean).join(" ").toLowerCase();return{name:a.name,label:o,case:a,searchText:n}}).sort((a,o)=>a.label.localeCompare(o.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(a=>s.every(o=>a.searchText.includes(o)))},getCasePickerOptions(){return this.buildCasePickerOptions(this.cases||[])},updateCasePickerInput(e){const t=document.getElementById("quickStartCaseSearch");if(!t)return;const s=this.getCasePickerOptions().find(a=>a.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");!t||!s||(this._casePickerOpen=!0,this._casePickerFilter=e,this._casePickerActiveIndex=0,t.setAttribute("aria-expanded","true"),this.renderCasePickerList())},closeCasePicker(){const e=document.getElementById("quickStartCaseSearch"),t=document.getElementById("quickStartCaseList");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 a=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||""),o=s.value||"testcase",n=Math.max(0,a.length-1);if(this._casePickerActiveIndex=Math.min(Math.max(this._casePickerActiveIndex||0,0),n),a.length===0){t.innerHTML='<div class="case-combobox-empty">No cases match</div>',t.classList.remove("hidden"),e.removeAttribute("aria-activedescendant");return}t.innerHTML=a.map((i,r)=>{const c=r===this._casePickerActiveIndex,d=i.name===o;return`
2
2
  <button
3
3
  type="button"
4
4
  id="${`quickStartCaseOption-${r}`}"
5
- class="case-combobox-option ${c?"active":""} ${m?"selected":""}"
5
+ class="case-combobox-option ${c?"active":""} ${d?"selected":""}"
6
6
  role="option"
7
- aria-selected="${m?"true":"false"}"
8
- data-case="${escapeHtml(o.name)}"
9
- title="${escapeHtml(o.label)}">
10
- <span class="case-combobox-check">${m?"\u2713":""}</span>
11
- <span class="case-combobox-option-label">${escapeHtml(o.label)}</span>
7
+ aria-selected="${d?"true":"false"}"
8
+ data-case="${escapeHtml(i.name)}"
9
+ title="${escapeHtml(i.label)}">
10
+ <span class="case-combobox-check">${d?"\u2713":""}</span>
11
+ <span class="case-combobox-option-label">${escapeHtml(i.label)}</span>
12
12
  </button>
13
- `}).join(""),t.classList.remove("hidden"),e.setAttribute("aria-activedescendant",`quickStartCaseOption-${this._casePickerActiveIndex}`)},selectQuickStartCase(e,{save:t=!0}={}){const s=document.getElementById("quickStartCase");s&&(s.value=e||"testcase",this.updateCasePickerInput(s.value),this.closeCasePicker(),this.updateDirDisplayForCase(s.value),this.updateMobileCaseLabel(s.value),t&&this.saveLastUsedCase(s.value))},setupQuickStartCasePicker(){const e=document.getElementById("quickStartCase"),t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList"),a=document.getElementById("quickStartCasePicker");!e||!t||!s||!a||t.dataset.listenerAdded||(t.addEventListener("focus",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("click",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("input",()=>{this.openCasePicker(t.value)}),t.addEventListener("keydown",n=>{const i=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||t.value);if(n.key==="ArrowDown")n.preventDefault(),this._casePickerActiveIndex=Math.min((this._casePickerActiveIndex||0)+1,Math.max(0,i.length-1)),this._casePickerOpen?this.renderCasePickerList():this.openCasePicker(t.value);else if(n.key==="ArrowUp")n.preventDefault(),this._casePickerActiveIndex=Math.max((this._casePickerActiveIndex||0)-1,0),this._casePickerOpen?this.renderCasePickerList():this.openCasePicker(t.value);else if(n.key==="Enter"){const o=i[this._casePickerActiveIndex||0];o&&(n.preventDefault(),this.selectQuickStartCase(o.name),this.run?.())}else n.key==="Escape"?(n.preventDefault(),this.updateCasePickerInput(e.value),this.closeCasePicker()):n.key==="Tab"&&(this.updateCasePickerInput(e.value),this.closeCasePicker())}),s.addEventListener("mousedown",n=>n.preventDefault()),s.addEventListener("click",n=>{const i=n.target.closest?.(".case-combobox-option");i?.dataset?.case&&this.selectQuickStartCase(i.dataset.case)}),document.addEventListener&&!this._casePickerDocumentListenerAdded&&(document.addEventListener("pointerdown",n=>{a.contains(n.target)||(this.updateCasePickerInput(e.value),this.closeCasePicker())}),this._casePickerDocumentListenerAdded=!0),t.dataset.listenerAdded="true")},async loadQuickStartCases(e=null,t=null){try{let s=null;try{const r=t?await t:await fetch("/api/settings").then(c=>c.ok?c.json():null).then(c=>c?.data??null);r&&(s=r.lastUsedCase||null)}catch{}const n=(await(await fetch("/api/cases")).json()).data;this.cases=n,console.log("[loadQuickStartCases] Loaded cases:",n.map(r=>r.name),"lastUsedCase:",s);const i=document.getElementById("quickStartCase"),o=this.getCasePickerOptions();if(this.renderQuickStartCaseSelectOptions(i,o),console.log("[loadQuickStartCases] Set options:",i.innerHTML.substring(0,200)),e)i.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&n.some(r=>r.name===s))i.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(n.length>0){const r=n.find(c=>c.name==="testcase")||n[0];i.value=r.name,this.updateDirDisplayForCase(r.name),this.updateMobileCaseLabel(r.name)}else i.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");this.updateCasePickerInput(i.value),this.renderCasePickerList(),this.closeCasePicker(),i.dataset.listenerAdded||(i.addEventListener("change",()=>{this.updateDirDisplayForCase(i.value),this.saveLastUsedCase(i.value),this.updateMobileCaseLabel(i.value),this.updateCasePickerInput(i.value)}),i.dataset.listenerAdded="true"),this.setupQuickStartCasePicker()}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=(await(await fetch(`/api/cases/${e}`)).json()).data;s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async run(){const e=this._runMode||"claude";return e==="opencode"?this.runOpenCode():e==="codex"?this.runCodex():e==="gemini"?this.runGemini():this.runClaude()},setRunMode(e){this._runMode=e;try{localStorage.setItem("codeman_runMode",e)}catch{}this._applyRunMode(),this._apiPut("/api/settings",{runMode:e}).catch(()=>{}),document.getElementById("runModeMenu")?.classList.remove("active")},toggleRunModeMenu(e){e?.stopPropagation();const t=document.getElementById("runModeMenu");if(t&&(t.classList.toggle("active"),t.querySelectorAll(".run-mode-option").forEach(s=>{s.classList.toggle("selected",s.dataset.mode===this.runMode)}),t.classList.contains("active"))){this._loadRunModeHistory();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},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 a=new Date(s.lastModified),n=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),i=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),o=document.createElement("button");o.className="run-mode-option",o.title=s.workingDir,o.dataset.sessionId=s.sessionId,o.dataset.workingDir=s.workingDir;const r=document.createElement("span");r.className="hist-dir",r.textContent=i;const c=document.createElement("span");c.className="hist-meta",c.textContent=n,o.append(r,c),o.addEventListener("click",m=>{m.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir)}),e.appendChild(o)}}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,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":e==="gemini"?"Run GM":"Run")},_initRunMode(){try{this._runMode=localStorage.getItem("codeman_runMode")||"claude"}catch{this._runMode="claude"}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 a=1;for(const[,n]of this.sessions||[]){const i=n.name&&n.name.match(s);if(i&&i[2]===e){const o=parseInt(i[1]);o>=a&&(a=o+1)}}return a},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const C=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!C.success)throw new Error(C.error||"Failed to create case");a=C.data.case}const n=a.path;if(!n)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const y=this._nextCaseSessionStartNumber(e);let C;if(a.location==="docker"){const k=this.loadAppSettingsFromStorage(),S=this.getCaseSettings(e).opusContext1m||k.opusContext1mEnabled;C=k.claudeModel||(S?"opus[1m]":"")}const E=[];let I=!1;for(let k=0;k<t;k++){const x=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${y+k}-${e}`,...C!==void 0?{modelOverride:C}:{}}),S=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:x})).json();let b=await S();if(!b.success&&b.errorCode==="CONFLICT"&&a.location==="docker"&&!I&&(I=!0,confirm(`Container config for "${e}" changed since its container was created.
13
+ `}).join(""),t.classList.remove("hidden"),e.setAttribute("aria-activedescendant",`quickStartCaseOption-${this._casePickerActiveIndex}`)},selectQuickStartCase(e,{save:t=!0}={}){const s=document.getElementById("quickStartCase");s&&(s.value=e||"testcase",this.updateCasePickerInput(s.value),this.closeCasePicker(),this.updateDirDisplayForCase(s.value),this.updateMobileCaseLabel(s.value),t&&this.saveLastUsedCase(s.value))},setupQuickStartCasePicker(){const e=document.getElementById("quickStartCase"),t=document.getElementById("quickStartCaseSearch"),s=document.getElementById("quickStartCaseList"),a=document.getElementById("quickStartCasePicker");!e||!t||!s||!a||t.dataset.listenerAdded||(t.addEventListener("focus",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("click",()=>{t.select?.(),this.openCasePicker("")}),t.addEventListener("input",()=>{this.openCasePicker(t.value)}),t.addEventListener("keydown",o=>{const n=this.filterCasePickerOptions(this.getCasePickerOptions(),this._casePickerFilter||t.value);if(o.key==="ArrowDown")o.preventDefault(),this._casePickerActiveIndex=Math.min((this._casePickerActiveIndex||0)+1,Math.max(0,n.length-1)),this._casePickerOpen?this.renderCasePickerList():this.openCasePicker(t.value);else if(o.key==="ArrowUp")o.preventDefault(),this._casePickerActiveIndex=Math.max((this._casePickerActiveIndex||0)-1,0),this._casePickerOpen?this.renderCasePickerList():this.openCasePicker(t.value);else if(o.key==="Enter"){const i=n[this._casePickerActiveIndex||0];i&&(o.preventDefault(),this.selectQuickStartCase(i.name),this.run?.())}else o.key==="Escape"?(o.preventDefault(),this.updateCasePickerInput(e.value),this.closeCasePicker()):o.key==="Tab"&&(this.updateCasePickerInput(e.value),this.closeCasePicker())}),s.addEventListener("mousedown",o=>o.preventDefault()),s.addEventListener("click",o=>{const n=o.target.closest?.(".case-combobox-option");n?.dataset?.case&&this.selectQuickStartCase(n.dataset.case)}),document.addEventListener&&!this._casePickerDocumentListenerAdded&&(document.addEventListener("pointerdown",o=>{a.contains(o.target)||(this.updateCasePickerInput(e.value),this.closeCasePicker())}),this._casePickerDocumentListenerAdded=!0),t.dataset.listenerAdded="true")},async loadQuickStartCases(e=null,t=null){try{let s=null;try{const r=t?await t:await fetch("/api/settings").then(c=>c.ok?c.json():null).then(c=>c?.data??null);r&&(s=r.lastUsedCase||null)}catch{}const o=(await(await fetch("/api/cases")).json()).data;this.cases=o,console.log("[loadQuickStartCases] Loaded cases:",o.map(r=>r.name),"lastUsedCase:",s);const n=document.getElementById("quickStartCase"),i=this.getCasePickerOptions();if(this.renderQuickStartCaseSelectOptions(n,i),console.log("[loadQuickStartCases] Set options:",n.innerHTML.substring(0,200)),e)n.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&o.some(r=>r.name===s))n.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(o.length>0){const r=o.find(c=>c.name==="testcase")||o[0];n.value=r.name,this.updateDirDisplayForCase(r.name),this.updateMobileCaseLabel(r.name)}else n.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");this.updateCasePickerInput(n.value),this.renderCasePickerList(),this.closeCasePicker(),n.dataset.listenerAdded||(n.addEventListener("change",()=>{this.updateDirDisplayForCase(n.value),this.saveLastUsedCase(n.value),this.updateMobileCaseLabel(n.value),this.updateCasePickerInput(n.value)}),n.dataset.listenerAdded="true"),this.setupQuickStartCasePicker()}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=(await(await fetch(`/api/cases/${e}`)).json()).data;s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async run(){const e=this._runMode||"claude";return e==="opencode"?this.runOpenCode():e==="codex"?this.runCodex():e==="gemini"?this.runGemini():this.runClaude()},setRunMode(e){this._runMode=e;try{localStorage.setItem("codeman_runMode",e)}catch{}this._applyRunMode(),this._apiPut("/api/settings",{runMode:e}).catch(()=>{}),document.getElementById("runModeMenu")?.classList.remove("active")},toggleRunModeMenu(e){e?.stopPropagation();const t=document.getElementById("runModeMenu");if(t&&(t.classList.toggle("active"),t.querySelectorAll(".run-mode-option").forEach(s=>{s.classList.toggle("selected",s.dataset.mode===this.runMode)}),t.classList.contains("active"))){this._loadRunModeHistory();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},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 a=new Date(s.lastModified),o=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),n=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),i=document.createElement("button");i.className="run-mode-option",i.title=s.workingDir,i.dataset.sessionId=s.sessionId,i.dataset.workingDir=s.workingDir;const r=document.createElement("span");r.className="hist-dir",r.textContent=n;const c=document.createElement("span");c.className="hist-meta",c.textContent=o,i.append(r,c),i.addEventListener("click",d=>{d.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name)}),e.appendChild(i)}}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,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":e==="codex"?"Run CX":e==="gemini"?"Run GM":"Run")},_initRunMode(){try{this._runMode=localStorage.getItem("codeman_runMode")||"claude"}catch{this._runMode="claude"}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 a=1;for(const[,o]of this.sessions||[]){const n=o.name&&o.name.match(s);if(n&&n[2]===e){const i=parseInt(n[1]);i>=a&&(a=i+1)}}return a},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const C=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!C.success)throw new Error(C.error||"Failed to create case");a=C.data.case}const o=a.path;if(!o)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const f=this._nextCaseSessionStartNumber(e);let C;if(a.location==="docker"){const k=this.loadAppSettingsFromStorage(),S=this.getCaseSettings(e).opusContext1m||k.opusContext1mEnabled;C=k.claudeModel||(S?"opus[1m]":"")}const E=[];let I=!1;for(let k=0;k<t;k++){const x=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${f+k}-${e}`,...C!==void 0?{modelOverride:C}:{}}),S=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:x})).json();let b=await S();if(!b.success&&b.errorCode==="CONFLICT"&&a.location==="docker"&&!I&&(I=!0,confirm(`Container config for "${e}" changed since its container was created.
14
14
 
15
- 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");b=await S()}if(!b.success)throw new Error(b.error||"Failed to start remote Claude session");E.push(b.data.sessionId)}this.terminal.writeln(`\x1B[90m All ${t} remote session(s) ready\x1B[0m`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let i=null;const o=this._nextCaseSessionStartNumber(e),r=this.isRalphTrackerEnabledByDefault(),c=[];for(let y=0;y<t;y++)c.push(`w${o+y}-${e}`);const m=this.getCaseSettings(e),d=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(m,d),p=Object.keys(u).length>0,l=this.getEffortSetting(d),h=m.opusContext1m||d.opusContext1mEnabled,f=d.claudeModel||(h?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const g=c.map(y=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,name:y,...p?{envOverrides:u}:{},...l?{effort:l}:{},...f!==void 0?{modelOverride:f}:{},statusLineTelemetry:d.showPlanUsageLimits===!0})}).then(C=>C.json())),w=await Promise.all(g),v=[];for(const y of w){if(!y.success)throw new Error(y.error);v.push(y.data.session.id)}i=v[0],await Promise.all(v.map(y=>fetch(`/api/sessions/${y}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:r,disableAutoEnable:!r})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(v.map(y=>fetch(`/api/sessions/${y}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),i&&(await this.selectSession(i),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},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));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const h=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!h.success)throw new Error(h.error||"Failed to create case");a=h.data.case}const n=(this.cases||[]).find(l=>l.name===e),i=a.location==="remote"||a.location==="docker"||n?.location==="remote"||n?.location==="docker",o=a.path;if(!o)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const l=this._nextCaseSessionStartNumber(e,"s"),h=[];for(let f=0;f<t;f++){const w=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${l+f}-${e}`})})).json();if(!w.success)throw new Error(w.error||"Failed to start remote shell session");h.push(w.data.sessionId)}h[0]&&await this.selectSession(h[0]),this.terminal.focus();return}const r=this._nextCaseSessionStartNumber(e,"s"),c=[];for(let l=0;l<t;l++)c.push(`s${r+l}-${e}`);const m=c.map(l=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...i?{caseName:e}:{workingDir:o},mode:"shell",name:l})}).then(h=>h.json())),d=await Promise.all(m),u=[];for(const l of d){if(!l.success)throw new Error(l.error);u.push(l.data.session.id)}await Promise.all(u.map(l=>fetch(`/api/sessions/${l}/shell`,{method:"POST"})));const p=this.getTerminalDimensions();p&&await Promise.all(u.map(l=>fetch(`/api/sessions/${l}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),u.length>0&&await this.selectSession(u[0]),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{openCodeConfig:{autoAllowTools:!0},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!i.success)throw new Error(i.error||"Failed to start OpenCode");i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),n=this.buildEnvOverrides(this.getCaseSettings(e),a),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Codex");o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Gemini session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Gemini CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @google/gemini-cli\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"gemini",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{geminiConfig:{approvalMode:"yolo"},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!i.success)throw new Error(i.error||"Failed to start Gemini");i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),n=document.getElementById("modalEnableRespawnBtn"),i=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(a.classList.add("active"),a.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",n.style.display="none",i.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",n.style.display="",i.style.display="none");const o=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?o.style.display="":o.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";document.querySelectorAll("[data-claude-only]").forEach(g=>{g.style.display=r?"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 m=parseSessionPrefix(t.name),d=document.getElementById("modalSessionPrefix");m?(d.textContent=m.prefix+": ",d.style.display="",document.getElementById("modalSessionName").value=m.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(d.style.display="none",d.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const u=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(g=>{g.classList.toggle("selected",g.dataset.color===u)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const l=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),h=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(r?(l&&(l.style.display="none"),h&&(h.style.display="none"),this.switchOptionsTab("context")):(l&&(l.style.display=""),h&&(h.style.display="")),!r){const g=this.ralphStates.get(e);this.populateRalphForm({enabled:g?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:g?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:g?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:g?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:g?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const f=document.getElementById("sessionOptionsModal");f.classList.add("active"),this.activeFocusTrap=new FocusTrap(f),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(n){this.showToast("Failed to save session name: "+n.message,"error")}},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 a=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${a}`,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 a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.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"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.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 a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.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"),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;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),n=parseSessionPrefix(t.name),i=s.textContent;for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(n){const c=document.createElement("span");c.textContent=n.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const o=document.createElement("input");o.type="text",o.value=n?n.suffix:t.name||"",o.placeholder=n?"Add description...":a,o.className="tab-rename-input",o.style.cssText="width: 80px; 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(o),o.focus(),o.select();const r=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const m=o.value.trim(),d=n?n.prefix+(m?": "+m:""):m;if(s.textContent=d||i,this.sessions.has(e)&&d!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:d})})}catch{s.textContent=i,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>r({commit:!1})},o.addEventListener("blur",()=>r({commit:!0})),o.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),o.blur()):c.key==="Escape"&&(o.value="",o.blur()))})},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 a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),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 a=document.getElementById("caseOpusContext1mMobile");a&&(a.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 a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),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 a=document.getElementById("caseOpusContext1m");a&&(a.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"].forEach(s=>{const a=document.getElementById(s);a&&(a.value="")}),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(s=>{s.onclick=()=>this.switchCaseModalTab(s.dataset.tab)}),t.querySelectorAll('input[type="text"]').forEach(s=>{s._mobileScrollWired||(s._mobileScrollWired=!0,s.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>s.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),t.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(s.style.display="",s.textContent=e==="case-create"?"Create":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").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=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},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,a=s?"/api/cases/docker-quickcreate":"/api/cases",n=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const o=await(await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json();if(o.success)if(this.closeCreateCaseModal(),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e),s){const r=o.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(o.error||"Failed to create case","error")}catch(i){console.error("Failed to create case:",i),this.showToast("Failed to create case: "+i.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 a=(n,i)=>{const o=document.getElementById(n);o&&(o.value=i)};a("quickDockerMemory",s.m),a("quickDockerCpus",s.c),a("quickDockerGpus",s.g)},_collectDockerQuickSettings(){const e=c=>(document.getElementById(c)?.value||"").trim(),t={},s=e("quickDockerMemory");s&&(t.memory=s);const a=e("quickDockerCpus");a&&(t.cpus=a);const n=e("quickDockerGpus");n&&n.toLowerCase()!=="none"&&(t.gpus=n);const i=document.getElementById("quickDockerNetwork")?.value;i&&i!=="bridge"&&(t.network=i);const o=e("quickDockerImage");o&&(t.image=o);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 a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},async linkRemoteCase(){const e=document.getElementById("remoteCaseName").value.trim(),t=document.getElementById("remoteCasePath").value.trim(),s=document.getElementById("remoteHostId").value.trim(),a=document.getElementById("remoteHostAddress").value.trim(),n=document.getElementById("remoteHostUsername").value.trim(),i=document.getElementById("remoteHostCodexCommand").value.trim(),o=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),m=document.getElementById("remoteHostJumpHost").value.trim(),d=document.getElementById("remoteHostExtraSshOptions").value.split(`
16
- `).map(p=>p.trim()).filter(p=>p.length>0);if(!e||!t||!s||!a||!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 u;if(o&&(u=Number(o),!Number.isInteger(u)||u<1||u>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const p={id:s,label:s,host:a,username:n,...u?{port:u}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...m?{jumpHost:m}:{},...d.length?{extraSshOptions:d}:{},...i?{commands:{codex:i}}:{}},h=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)})).json();if(!h.success&&h.errorCode!=="ALREADY_EXISTS")throw new Error(h.error||"Failed to save remote host");const g=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();g.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(g.error||"Failed to link remote case","error")}catch(p){console.error("Failed to link remote case:",p),this.showToast("Failed to link remote case: "+p.message,"error")}},async linkDockerCase(){const e=document.getElementById("dockerCaseName").value.trim(),t=document.getElementById("dockerWorkspacePath").value.trim(),s=document.getElementById("dockerHostId").value.trim()||"local",a=document.getElementById("dockerImage").value.trim()||"codeman/agent:base",n=document.getElementById("dockerNetwork").value,i=document.getElementById("dockerMemory").value.trim(),o=document.getElementById("dockerCpus").value.trim(),r=document.getElementById("dockerMountCredentials").checked,c=document.getElementById("dockerResumeOnStart").checked,m=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}try{m&&(m.textContent="Checking docker daemon + base image...");const d={};i&&(d.memory=i),o&&(d.cpus=o);const u={id:s,label:s,image:a,network:n,mountCredentials:r,resumeOnStart:c,...Object.keys(d).length?{resources:d}:{}};let p=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),l=await p.json();if(!l.success&&l.errorCode==="ALREADY_EXISTS"&&(p=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),l=await p.json()),!l.success)throw new Error(l.error||"Failed to save docker host");const f=await(await fetch("/api/cases/docker-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,hostWorkspacePath:t})})).json();if(f.success){this.closeCreateCaseModal();const g=f.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"";this.showToast(`Docker case "${e}" linked${g}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else m&&(m.textContent=f.error||"Failed to link docker case"),this.showToast(f.error||"Failed to link docker case","error")}catch(d){console.error("Failed to link docker case:",d),m&&(m.textContent=d.message),this.showToast("Failed to link docker case: "+d.message,"error")}},async refreshDockerExports(){const e=document.getElementById("dockerExportsList");if(e)try{const a=(await(await fetch("/api/docker-exports")).json())?.data?.exports||[];if(a.length===0){e.innerHTML='<span class="form-hint">No exports yet. Export a docker case from its tab.</span>';return}e.innerHTML=a.map(n=>{const i=(n.sizeBytes/1e6).toFixed(1),o=escapeHtml(n.name);return`<div class="case-manage-item" style="display:flex; align-items:center; gap:8px; justify-content:space-between;">
17
- <span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${o}">${o} <span class="form-hint">(${i} MB)</span></span>
15
+ 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");b=await S()}if(!b.success)throw new Error(b.error||"Failed to start remote Claude session");E.push(b.data.sessionId)}this.terminal.writeln(`\x1B[90m All ${t} remote session(s) ready\x1B[0m`),E[0]&&(await this.selectSession(E[0]),this.loadQuickStartCases()),this.terminal.focus();return}let n=null;const i=this._nextCaseSessionStartNumber(e),r=this.isRalphTrackerEnabledByDefault(),c=[];for(let f=0;f<t;f++)c.push(`w${i+f}-${e}`);const d=this.getCaseSettings(e),l=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(d,l),p=Object.keys(u).length>0,m=this.getEffortSetting(l),h=d.opusContext1m||l.opusContext1mEnabled,y=l.claudeModel||(h?"opus[1m]":"");this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const g=c.map(f=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:o,name:f,...p?{envOverrides:u}:{},...m?{effort:m}:{},...y!==void 0?{modelOverride:y}:{},statusLineTelemetry:l.showPlanUsageLimits===!0})}).then(C=>C.json())),w=await Promise.all(g),v=[];for(const f of w){if(!f.success)throw new Error(f.error);v.push(f.data.session.id)}n=v[0],await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:r,disableAutoEnable:!r})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(v.map(f=>fetch(`/api/sessions/${f}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),n&&(await this.selectSession(n),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},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));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!a.path){const h=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!h.success)throw new Error(h.error||"Failed to create case");a=h.data.case}const o=(this.cases||[]).find(m=>m.name===e),n=a.location==="remote"||a.location==="docker"||o?.location==="remote"||o?.location==="docker",i=a.path;if(!i)throw new Error("Case path not found");if(a.location==="remote"||a.location==="docker"){const m=this._nextCaseSessionStartNumber(e,"s"),h=[];for(let y=0;y<t;y++){const w=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${m+y}-${e}`})})).json();if(!w.success)throw new Error(w.error||"Failed to start remote shell session");h.push(w.data.sessionId)}h[0]&&await this.selectSession(h[0]),this.terminal.focus();return}const r=this._nextCaseSessionStartNumber(e,"s"),c=[];for(let m=0;m<t;m++)c.push(`s${r+m}-${e}`);const d=c.map(m=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...n?{caseName:e}:{workingDir:i},mode:"shell",name:m})}).then(h=>h.json())),l=await Promise.all(d),u=[];for(const m of l){if(!m.success)throw new Error(m.error);u.push(m.data.session.id)}await Promise.all(u.map(m=>fetch(`/api/sessions/${m}/shell`,{method:"POST"})));const p=this.getTerminalDimensions();p&&await Promise.all(u.map(m=>fetch(`/api/sessions/${m}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)}))),u.length>0&&await this.selectSession(u[0]),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{openCodeConfig:{autoAllowTools:!0},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!n.success)throw new Error(n.error||"Failed to start OpenCode");n.data.sessionId&&await this.selectSession(n.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Codex session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Codex CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @openai/codex\x1B[0m");return}const a=this.loadAppSettingsFromStorage(),o=this.buildEnvOverrides(this.getCaseSettings(e),a),i=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"codex",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{codexConfig:{dangerouslyBypassApprovals:a.codexDangerouslyBypassApprovals??!1,renderMode:"hybrid"},...Object.keys(o).length>0?{envOverrides:o}:{}}})})).json();if(!i.success)throw new Error(i.error||"Failed to start Codex");i.data.sessionId&&await this.selectSession(i.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(a=>a.name===e)?.location,s=t==="remote"||t==="docker";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting Gemini session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this.terminal.writeln("\x1B[1;31m Gemini CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: npm install -g @google/gemini-cli\x1B[0m");return}const a=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"gemini",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{geminiConfig:{approvalMode:"yolo"},...Object.keys(a).length>0?{envOverrides:a}:{}}})})).json();if(!n.success)throw new Error(n.error||"Failed to start Gemini");n.data.sessionId&&await this.selectSession(n.data.sessionId),this.terminal.focus()}catch(a){this.terminal.writeln(`\x1B[1;31m Error: ${a.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";this.switchOptionsTab(s?"summary":"respawn");const a=document.getElementById("sessionRespawnStatus"),o=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(a.classList.add("active"),a.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",o.style.display="none",n.style.display=""):(a.classList.remove("active"),a.querySelector(".respawn-status-text").textContent="Not active",o.style.display="",n.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const r=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini";document.querySelectorAll("[data-claude-only]").forEach(g=>{g.style.display=r?"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),l=document.getElementById("modalSessionPrefix");d?(l.textContent=d.prefix+": ",l.style.display="",document.getElementById("modalSessionName").value=d.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(l.style.display="none",l.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const u=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(g=>{g.classList.toggle("selected",g.dataset.color===u)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const m=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),h=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(r?(m&&(m.style.display="none"),h&&(h.style.display="none"),this.switchOptionsTab("context")):(m&&(m.style.display=""),h&&(h.style.display="")),!r){const g=this.ralphStates.get(e);this.populateRalphForm({enabled:g?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:g?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:g?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:g?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:g?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const y=document.getElementById("sessionOptionsModal");y.classList.add("active"),this.activeFocusTrap=new FocusTrap(y),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(o){this.showToast("Failed to save session name: "+o.message,"error")}},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 a=new Date(s.autoResumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.textContent=`Usage limit pause active \u2014 resumes at ${a}`,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 a=s.data.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(o=>o.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"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.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 a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.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"),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;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;this._inlineRenameActive=!0;const a=this.getSessionName(t),o=parseSessionPrefix(t.name),n=s.textContent;for(s.textContent="";s.firstChild;)s.removeChild(s.firstChild);if(o){const c=document.createElement("span");c.textContent=o.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const i=document.createElement("input");i.type="text",i.value=o?o.suffix:t.name||"",i.placeholder=o?"Add description...":a,i.className="tab-rename-input",i.style.cssText="width: 80px; 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(i),i.focus(),i.select();const r=async({commit:c})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!c){this.renderSessionTabs();return}const d=i.value.trim(),l=o?o.prefix+(d?": "+d:""):d;if(s.textContent=l||n,this.sessions.has(e)&&l!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:l})})}catch{s.textContent=n,this.showToast("Failed to rename","error")}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>r({commit:!1})},i.addEventListener("blur",()=>r({commit:!0})),i.addEventListener("keydown",c=>{c.isComposing||c.keyCode===229||(c.key==="Enter"?(c.preventDefault(),i.blur()):c.key==="Escape"&&(i.value="",i.blur()))})},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 a=o=>{!e.contains(o.target)&&!o.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),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 a=document.getElementById("caseOpusContext1mMobile");a&&(a.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 a=o=>{!e.contains(o.target)&&!o.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),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 a=document.getElementById("caseOpusContext1m");a&&(a.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"].forEach(s=>{const a=document.getElementById(s);a&&(a.value="")}),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(s=>{s.onclick=()=>this.switchCaseModalTab(s.dataset.tab)}),t.querySelectorAll('input[type="text"]').forEach(s=>{s._mobileScrollWired||(s._mobileScrollWired=!0,s.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>s.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),t.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(s.style.display="",s.textContent=e==="case-create"?"Create":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").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=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},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,a=s?"/api/cases/docker-quickcreate":"/api/cases",o=s?{name:e,description:t,...this._collectDockerQuickSettings()}:{name:e,description:t};try{const i=await(await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).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(n){console.error("Failed to create case:",n),this.showToast("Failed to create case: "+n.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 a=(o,n)=>{const i=document.getElementById(o);i&&(i.value=n)};a("quickDockerMemory",s.m),a("quickDockerCpus",s.c),a("quickDockerGpus",s.g)},_collectDockerQuickSettings(){const e=c=>(document.getElementById(c)?.value||"").trim(),t={},s=e("quickDockerMemory");s&&(t.memory=s);const a=e("quickDockerCpus");a&&(t.cpus=a);const o=e("quickDockerGpus");o&&o.toLowerCase()!=="none"&&(t.gpus=o);const n=document.getElementById("quickDockerNetwork")?.value;n&&n!=="bridge"&&(t.network=n);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 a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},async linkRemoteCase(){const e=document.getElementById("remoteCaseName").value.trim(),t=document.getElementById("remoteCasePath").value.trim(),s=document.getElementById("remoteHostId").value.trim(),a=document.getElementById("remoteHostAddress").value.trim(),o=document.getElementById("remoteHostUsername").value.trim(),n=document.getElementById("remoteHostCodexCommand").value.trim(),i=document.getElementById("remoteHostPort").value.trim(),r=document.getElementById("remoteHostIdentityFile").value.trim(),c=document.getElementById("remoteHostSocksProxy").value.trim(),d=document.getElementById("remoteHostJumpHost").value.trim(),l=document.getElementById("remoteHostExtraSshOptions").value.split(`
16
+ `).map(p=>p.trim()).filter(p=>p.length>0);if(!e||!t||!s||!a||!o){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 u;if(i&&(u=Number(i),!Number.isInteger(u)||u<1||u>65535)){this.showToast("SSH port must be a number between 1 and 65535","error");return}try{const p={id:s,label:s,host:a,username:o,...u?{port:u}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...d?{jumpHost:d}:{},...l.length?{extraSshOptions:l}:{},...n?{commands:{codex:n}}:{}},h=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)})).json();if(!h.success&&h.errorCode!=="ALREADY_EXISTS")throw new Error(h.error||"Failed to save remote host");const g=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();g.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(g.error||"Failed to link remote case","error")}catch(p){console.error("Failed to link remote case:",p),this.showToast("Failed to link remote case: "+p.message,"error")}},async linkDockerCase(){const e=document.getElementById("dockerCaseName").value.trim(),t=document.getElementById("dockerWorkspacePath").value.trim(),s=document.getElementById("dockerHostId").value.trim()||"local",a=document.getElementById("dockerImage").value.trim()||"codeman/agent:base",o=document.getElementById("dockerNetwork").value,n=document.getElementById("dockerMemory").value.trim(),i=document.getElementById("dockerCpus").value.trim(),r=document.getElementById("dockerMountCredentials").checked,c=document.getElementById("dockerResumeOnStart").checked,d=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}try{d&&(d.textContent="Checking docker daemon + base image...");const l={};n&&(l.memory=n),i&&(l.cpus=i);const u={id:s,label:s,image:a,network:o,mountCredentials:r,resumeOnStart:c,...Object.keys(l).length?{resources:l}:{}};let p=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),m=await p.json();if(!m.success&&m.errorCode==="ALREADY_EXISTS"&&(p=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),m=await p.json()),!m.success)throw new Error(m.error||"Failed to save docker host");const y=await(await fetch("/api/cases/docker-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,hostWorkspacePath:t})})).json();if(y.success){this.closeCreateCaseModal();const g=y.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"";this.showToast(`Docker case "${e}" linked${g}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else d&&(d.textContent=y.error||"Failed to link docker case"),this.showToast(y.error||"Failed to link docker case","error")}catch(l){console.error("Failed to link docker case:",l),d&&(d.textContent=l.message),this.showToast("Failed to link docker case: "+l.message,"error")}},async refreshDockerExports(){const e=document.getElementById("dockerExportsList");if(e)try{const a=(await(await fetch("/api/docker-exports")).json())?.data?.exports||[];if(a.length===0){e.innerHTML='<span class="form-hint">No exports yet. Export a docker case from its tab.</span>';return}e.innerHTML=a.map(o=>{const n=(o.sizeBytes/1e6).toFixed(1),i=escapeHtml(o.name);return`<div class="case-manage-item" style="display:flex; align-items:center; gap:8px; justify-content:space-between;">
17
+ <span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${i}">${i} <span class="form-hint">(${n} MB)</span></span>
18
18
  <span style="flex-shrink:0;">
19
- <a class="btn-toolbar" href="/api/docker-exports/${encodeURIComponent(n.name)}" download>Download</a>
20
- <button class="btn-toolbar" onclick="app.importDockerBundle('${o.replace(/'/g,"\\'")}')">Import</button>
21
- <button class="btn-toolbar" onclick="app.deleteDockerExport('${o.replace(/'/g,"\\'")}')">Delete</button>
19
+ <a class="btn-toolbar" href="/api/docker-exports/${encodeURIComponent(o.name)}" download>Download</a>
20
+ <button class="btn-toolbar" onclick="app.importDockerBundle('${i.replace(/'/g,"\\'")}')">Import</button>
21
+ <button class="btn-toolbar" onclick="app.deleteDockerExport('${i.replace(/'/g,"\\'")}')">Delete</button>
22
22
  </span>
23
- </div>`}).join("")}catch(t){e.innerHTML=`<span class="form-hint">Failed to load exports: ${t.message}</span>`}},async exportDockerCaseBundle(e,t="full"){try{const a=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/export`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:t})})).json();a.success?this.showToast(`Exporting "${e}" (${t})... you'll be notified when the bundle is ready`,"info"):this.showToast(a.error||"Export failed","error")}catch(s){this.showToast("Export failed: "+s.message,"error")}},async importDockerBundle(e){const t=prompt("New case name for the imported bundle:",e.split("-")[0]+"-imported");if(!t)return;const s=prompt("Absolute host directory to restore the workspace into:","");if(s)try{const n=await(await fetch("/api/docker-cases/import",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({bundle:e,newCaseName:t,destWorkspacePath:s})})).json();n.success?(this.showToast(`Imported as "${t}"`,"success"),await this.loadQuickStartCases(t)):this.showToast(n.error||"Import failed","error")}catch(a){this.showToast("Import failed: "+a.message,"error")}},async deleteDockerExport(e){if(confirm(`Delete export bundle "${e}"?`))try{const s=await(await fetch(`/api/docker-exports/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast("Export deleted","success"),this.refreshDockerExports()):this.showToast(s.error||"Delete failed","error")}catch(t){this.showToast("Delete failed: "+t.message,"error")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,n)=>{const i=n===0,o=n===t.length-1,r=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
23
+ </div>`}).join("")}catch(t){e.innerHTML=`<span class="form-hint">Failed to load exports: ${t.message}</span>`}},async exportDockerCaseBundle(e,t="full"){try{const a=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/export`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:t})})).json();a.success?this.showToast(`Exporting "${e}" (${t})... you'll be notified when the bundle is ready`,"info"):this.showToast(a.error||"Export failed","error")}catch(s){this.showToast("Export failed: "+s.message,"error")}},async importDockerBundle(e){const t=prompt("New case name for the imported bundle:",e.split("-")[0]+"-imported");if(!t)return;const s=prompt("Absolute host directory to restore the workspace into:","");if(s)try{const o=await(await fetch("/api/docker-cases/import",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({bundle:e,newCaseName:t,destWorkspacePath:s})})).json();o.success?(this.showToast(`Imported as "${t}"`,"success"),await this.loadQuickStartCases(t)):this.showToast(o.error||"Import failed","error")}catch(a){this.showToast("Import failed: "+a.message,"error")}},async deleteDockerExport(e){if(confirm(`Delete export bundle "${e}"?`))try{const s=await(await fetch(`/api/docker-exports/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast("Export deleted","success"),this.refreshDockerExports()):this.showToast(s.error||"Delete failed","error")}catch(t){this.showToast("Delete failed: "+t.message,"error")}},_readRemoteHostFromForm(){const e=document.getElementById("remoteHostId").value.trim(),t=document.getElementById("remoteHostAddress").value.trim(),s=document.getElementById("remoteHostUsername").value.trim(),a=document.getElementById("remoteHostPort").value.trim(),o=document.getElementById("remoteHostIdentityFile").value.trim(),n=document.getElementById("remoteHostSocksProxy").value.trim(),i=document.getElementById("remoteHostJumpHost").value.trim(),r=document.getElementById("remoteHostCodexCommand").value.trim(),c=document.getElementById("remoteHostExtraSshOptions").value.split(`
24
+ `).map(l=>l.trim()).filter(l=>l.length>0);let d;if(a){const l=Number(a);Number.isInteger(l)&&l>=1&&l<=65535&&(d=l)}return{id:e,label:e,host:t,username:s,...d?{port:d}:{},...o?{identityFile:o}:{},...n?{socksProxy:n}:{},...i?{jumpHost:i}:{},...c.length?{extraSshOptions:c}:{},...r?{commands:{codex:r}}:{}}},async discoverRemoteSessions(){const e=document.getElementById("remoteDiscoverResults"),t=document.getElementById("remoteDiscoverBtn"),s=this._readRemoteHostFromForm();if(!s.id||!s.host||!s.username){this.showToast("Fill in Host ID, address, and username first","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(s.id)){this.showToast("Invalid Host ID. Use letters, numbers, hyphens, underscores.","error");return}t&&(t.disabled=!0),e&&(e.innerHTML='<div class="form-hint">Discovering\u2026</div>');try{const o=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})).json();if(!o.success&&o.errorCode!=="ALREADY_EXISTS")throw new Error(o.error||"Failed to save remote host");const i=await(await fetch(`/api/remote-hosts/${encodeURIComponent(s.id)}/sessions`)).json();if(!i.success)throw new Error(i.error||"Discovery failed");this._renderDiscoveredSessions(s.id,i.data.sessions||[])}catch(a){console.error("Discover remote sessions failed:",a),e&&(e.innerHTML=`<div class="form-hint" style="color: var(--error, #e06c75);">${escapeHtml(a.message)}</div>`)}finally{t&&(t.disabled=!1)}},_renderDiscoveredSessions(e,t){const s=document.getElementById("remoteDiscoverResults");if(!s)return;if(!t.length){s.innerHTML='<div class="form-hint">No <code>codeman-*</code> sessions running on this host (or it is unreachable).</div>';return}const a=Math.floor(Date.now()/1e3),o=t.map(n=>{const i=Math.max(0,a-(n.created||0)),r=i<3600?`${Math.floor(i/60)}m`:i<86400?`${Math.floor(i/3600)}h`:`${Math.floor(i/86400)}d`,c=n.attachedClients!=null?n.attachedClients:n.attached?1:0,d=c>1?`<span class="case-location-badge" style="background: var(--warning, #e5c07b); color: #000;">shared \xB7 ${c} clients</span>`:c===1?'<span class="case-location-badge" style="background: var(--accent, #61afef);">attached</span>':"";return`
25
+ <div class="remote-discover-item">
26
+ <div class="remote-discover-info">
27
+ <span class="remote-discover-name">${escapeHtml(n.name)} ${d}</span>
28
+ <span class="form-hint">age ${r} \xB7 ${n.windows||1} window(s)</span>
29
+ </div>
30
+ <button type="button" class="btn-toolbar" onclick="app.attachDiscoveredSession('${escapeHtml(e)}', '${escapeHtml(n.name)}')">Attach</button>
31
+ </div>`}).join("");s.innerHTML=o},async attachDiscoveredSession(e,t){try{const a=await(await fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mode:"shell",name:t,attachRemoteSession:{hostId:e,remoteSessionName:t}})})).json();if(!a.success)throw new Error(a.error||"Failed to create session");const o=a.data.session.id;await fetch(`/api/sessions/${o}/shell`,{method:"POST"});const n=this.getTerminalDimensions();n&&await fetch(`/api/sessions/${o}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)}),this.closeCreateCaseModal(),this.showToast(`Attached to ${t} (detach on close)`,"success"),this.activeSessionId=o,await this.selectSession(o),this.terminal&&typeof this.terminal.focus=="function"&&this.terminal.focus()}catch(s){console.error("Attach discovered session failed:",s),this.showToast("Failed to attach: "+s.message,"error")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,o)=>{const n=o===0,i=o===t.length-1,r=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
24
32
  <div class="case-manage-item" data-case="${escapeHtml(a.name)}">
25
33
  <div class="case-manage-info">
26
34
  <span class="case-manage-name">${escapeHtml(a.name)}</span>
@@ -30,23 +38,23 @@ Recreate the container to apply the new config? Workspace files and the conversa
30
38
  ${a.location==="docker"?`<button class="case-manage-btn" onclick="app.exportDockerCaseBundle(${escapeHtml(JSON.stringify(a.name))}, 'full')"
31
39
  title="Export container (full image + workspace) to move to another machine">&#x1F4E6;</button>`:""}
32
40
  <button class="case-manage-btn" onclick="app.moveCaseUp(${escapeHtml(JSON.stringify(a.name))})"
33
- title="Move up" ${i?"disabled":""}>&#x25B2;</button>
41
+ title="Move up" ${n?"disabled":""}>&#x25B2;</button>
34
42
  <button class="case-manage-btn" onclick="app.moveCaseDown(${escapeHtml(JSON.stringify(a.name))})"
35
- title="Move down" ${o?"disabled":""}>&#x25BC;</button>
43
+ title="Move down" ${i?"disabled":""}>&#x25BC;</button>
36
44
  <button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase(${escapeHtml(JSON.stringify(a.name))})"
37
45
  title="Delete case">&#x2715;</button>
38
46
  </div>
39
47
  </div>
40
- `}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(i=>i.name!==e),this.renderCaseManageList();const a=document.getElementById("quickStartCase"),n=a.value;n===e&&a.blur?.(),await this.loadQuickStartCases(n===e?null:n),n===e&&await this.saveLastUsedCase(document.getElementById("quickStartCase")?.value||"testcase")}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let n="";const i=this.getCasePickerOptions();for(const o of i){const r=o.name===a;n+=`
48
+ `}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(o=>o.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(o=>o.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(o=>o.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(o=>o.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(n=>n.name!==e),this.renderCaseManageList();const a=document.getElementById("quickStartCase"),o=a.value;o===e&&a.blur?.(),await this.loadQuickStartCases(o===e?null:o),o===e&&await this.saveLastUsedCase(document.getElementById("quickStartCase")?.value||"testcase")}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let o="";const n=this.getCasePickerOptions();for(const i of n){const r=i.name===a;o+=`
41
49
  <button class="mobile-case-item ${r?"selected":""}"
42
- onclick="app.selectMobileCase(${escapeHtml(JSON.stringify(o.name))})">
50
+ onclick="app.selectMobileCase(${escapeHtml(JSON.stringify(i.name))})">
43
51
  <span class="mobile-case-item-icon">
44
52
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
45
53
  <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
46
54
  </svg>
47
55
  </span>
48
- <span class="mobile-case-item-name">${escapeHtml(o.label)}</span>
49
- <span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile(${escapeHtml(JSON.stringify(o.name))})" title="Delete">
56
+ <span class="mobile-case-item-name">${escapeHtml(i.label)}</span>
57
+ <span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile(${escapeHtml(JSON.stringify(i.name))})" title="Delete">
50
58
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
51
59
  <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
52
60
  </svg>
@@ -57,4 +65,4 @@ Recreate the container to apply the new config? Workspace files and the conversa
57
65
  </svg>
58
66
  </span>
59
67
  </button>
60
- `}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="gemini"||e==="claude"?e:"claude"}});
68
+ `}t.innerHTML=o,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}}),Object.defineProperty(CodemanApp.prototype,"runMode",{configurable:!0,enumerable:!0,get(){return this._runMode||"claude"},set(e){this._runMode=e==="opencode"||e==="codex"||e==="gemini"||e==="claude"?e:"claude"}});