aicodeman 1.18.4 → 1.19.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.
- package/README.md +1 -0
- package/dist/attachment-registry.d.ts +25 -0
- package/dist/attachment-registry.d.ts.map +1 -1
- package/dist/attachment-registry.js +45 -1
- package/dist/attachment-registry.js.map +1 -1
- package/dist/hooks-config.d.ts +22 -16
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +20 -14
- package/dist/hooks-config.js.map +1 -1
- package/dist/types/tools.d.ts +1 -1
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/web/ports/config-port.d.ts +2 -0
- package/dist/web/ports/config-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.a8b08065.js +34 -0
- package/dist/web/public/app.a8b08065.js.br +0 -0
- package/dist/web/public/app.a8b08065.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.04b7b284.js → constants.35c7f91c.js} +133 -0
- package/dist/web/public/constants.35c7f91c.js.br +0 -0
- package/dist/web/public/constants.35c7f91c.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js +79 -35
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.bb8949de.js +1 -0
- package/dist/web/public/i18n.bb8949de.js.br +0 -0
- package/dist/web/public/i18n.bb8949de.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +102 -18
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.bc45f976.js → mobile-handlers.05b89237.js} +16 -0
- package/dist/web/public/mobile-handlers.05b89237.js.br +0 -0
- package/dist/web/public/mobile-handlers.05b89237.js.gz +0 -0
- package/dist/web/public/mobile-overview.js +17 -15
- package/dist/web/public/mobile-overview.js.br +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.c40d28a6.css +1 -0
- package/dist/web/public/mobile.c40d28a6.css.br +0 -0
- package/dist/web/public/mobile.c40d28a6.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.1e4cb4b2.js → panels-ui.c5a82b6e.js} +48 -46
- package/dist/web/public/panels-ui.c5a82b6e.js.br +0 -0
- package/dist/web/public/panels-ui.c5a82b6e.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js +12 -1
- package/dist/web/public/session-lineage.js.br +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.c67565f2.js → session-ui.71a0ce9a.js} +3 -3
- package/dist/web/public/session-ui.71a0ce9a.js.br +0 -0
- package/dist/web/public/session-ui.71a0ce9a.js.gz +0 -0
- package/dist/web/public/{settings-ui.eacb4f92.js → settings-ui.6cb12d4b.js} +2 -2
- package/dist/web/public/settings-ui.6cb12d4b.js.br +0 -0
- package/dist/web/public/settings-ui.6cb12d4b.js.gz +0 -0
- package/dist/web/public/styles.325eb4f3.css +1 -0
- package/dist/web/public/styles.325eb4f3.css.br +0 -0
- package/dist/web/public/styles.325eb4f3.css.gz +0 -0
- package/dist/web/public/{subagent-windows.2a192c3e.js → subagent-windows.e6ca799f.js} +24 -12
- package/dist/web/public/subagent-windows.e6ca799f.js.br +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.02b89e2c.js +4 -0
- package/dist/web/public/terminal-ui.02b89e2c.js.br +0 -0
- package/dist/web/public/terminal-ui.02b89e2c.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js +15 -19
- package/dist/web/public/ultracode-windows.js.br +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +3 -0
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +48 -6
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +50 -12
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +5 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +13 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/sensitive-path.d.ts.map +1 -1
- package/dist/web/sensitive-path.js +17 -0
- package/dist/web/sensitive-path.js.map +1 -1
- package/dist/web/server.d.ts +20 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +51 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts.map +1 -1
- package/dist/web/session-listener-wiring.js +5 -0
- package/dist/web/session-listener-wiring.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +16 -15
- package/skills/codeman/preamble.sh +8 -8
- package/skills/codeman/reference/endpoints.md +28 -34
- package/skills/codeman/reference/messaging.md +8 -6
- package/skills/codeman/reference/recipes.md +3 -3
- package/skills/codeman/reference/verbs.md +39 -24
- package/dist/web/public/app.e2cb9065.js +0 -34
- package/dist/web/public/app.e2cb9065.js.br +0 -0
- package/dist/web/public/app.e2cb9065.js.gz +0 -0
- package/dist/web/public/constants.04b7b284.js.br +0 -0
- package/dist/web/public/constants.04b7b284.js.gz +0 -0
- package/dist/web/public/i18n.86757605.js +0 -1
- package/dist/web/public/i18n.86757605.js.br +0 -0
- package/dist/web/public/i18n.86757605.js.gz +0 -0
- package/dist/web/public/mobile-handlers.bc45f976.js.br +0 -0
- package/dist/web/public/mobile-handlers.bc45f976.js.gz +0 -0
- package/dist/web/public/mobile.1eef31fe.css +0 -1
- package/dist/web/public/mobile.1eef31fe.css.br +0 -0
- package/dist/web/public/mobile.1eef31fe.css.gz +0 -0
- package/dist/web/public/panels-ui.1e4cb4b2.js.br +0 -0
- package/dist/web/public/panels-ui.1e4cb4b2.js.gz +0 -0
- package/dist/web/public/session-ui.c67565f2.js.br +0 -0
- package/dist/web/public/session-ui.c67565f2.js.gz +0 -0
- package/dist/web/public/settings-ui.eacb4f92.js.br +0 -0
- package/dist/web/public/settings-ui.eacb4f92.js.gz +0 -0
- package/dist/web/public/styles.d2fa3a17.css +0 -1
- package/dist/web/public/styles.d2fa3a17.css.br +0 -0
- package/dist/web/public/styles.d2fa3a17.css.gz +0 -0
- package/dist/web/public/subagent-windows.2a192c3e.js.br +0 -0
- package/dist/web/public/subagent-windows.2a192c3e.js.gz +0 -0
- package/dist/web/public/terminal-ui.c182a68d.js +0 -4
- package/dist/web/public/terminal-ui.c182a68d.js.br +0 -0
- package/dist/web/public/terminal-ui.c182a68d.js.gz +0 -0
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
<span class="case-combobox-check">${l?"\u2713":""}</span>
|
|
11
11
|
<span class="case-combobox-option-label">${escapeHtml(o.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(),this._refreshMobileOverviewIfVisible?.()}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 _ensureCreatedSessionVisible(e,t){if(!e)return;let s=t;if(!s&&!this.sessions?.has(e)){const n=await(await fetch(`/api/sessions/${encodeURIComponent(e)}`)).json();if(!n.success)throw new Error(n.error||"Failed to load the new session");s=n.data?.session||n.data}s?.id&&this._onSessionCreated(s),this._renderSessionTabsImmediate?.()},async run(){if(this._runInFlight)return;const e=Date.now(),t=Number.isFinite(this._runMinLockMs)?this._runMinLockMs:500,s=document.getElementById("runBtn");this._runInFlight=!0,s&&(s.disabled=!0,s.setAttribute("aria-busy","true"));try{const a=this._runMode||"claude";return a==="opencode"?await this.runOpenCode():a==="codex"?await this.runCodex():a==="gemini"?await this.runGemini():a==="antigravity"?await this.runAntigravity():a==="pi"?await this.runPi():a==="shell"?await this.runShell():await this.runClaude()}finally{const a=t-(Date.now()-e);a>0&&await new Promise(n=>setTimeout(n,a)),this._runInFlight=!1,s&&(s.disabled=!1,s.removeAttribute("aria-busy"))}},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(),this._refreshRunModeAvailability(t);const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},_refreshRunModeAvailability(e){for(const t of["claude","opencode","codex","gemini","antigravity","pi"]){const s=e.querySelector(`.run-mode-option[data-mode="${t}"]`);s&&(s.style.display=this.isCliAvailable(t)?"flex":"none")}},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=this._shortenHomePath(s.workingDir),o=i.lastIndexOf("/"),r=o===-1?i:i.slice(o+1),c=(o===-1?"":i.slice(0,o)).replace(/\/\.claude\/worktrees$/,""),l=document.createElement("button");l.className="run-mode-option run-mode-hist-row",l.title=s.workingDir,l.dataset.sessionId=s.sessionId,l.dataset.workingDir=s.workingDir;const m=document.createElement("span");m.className="hist-name",m.textContent=r;const h=[m],u=this._worktreeLabel?this._worktreeLabel(s):"";if(u){const d=document.createElement("span");d.className="hist-wt",d.textContent=u,h.push(d)}if(c){const d=document.createElement("span");d.className="hist-dir",d.textContent=c,h.push(d)}const y=document.createElement("span");y.className="hist-meta",y.textContent=n,h.push(y),l.append(...h),l.addEventListener("click",d=>{d.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name)}),e.appendChild(l)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,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":e==="antigravity"?"Run AG":e==="pi"?"Run PI":e==="shell"?"Run SH":"Run")},sendEnterKey(){if(!this.activeSessionId)return;const e=this.terminal?._core?.coreService;if(e&&typeof e.triggerDataEvent=="function"){e.triggerDataEvent("\r",!0);return}this.sendInput("\r")},_initRunMode(){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},_beginSessionLaunchStatus(e,t="1;32"){const s=!this.activeSessionId;return s?(this.terminal.clear(),this.terminal.writeln(`\x1B[${t}m ${e}\x1B[0m`),this.terminal.writeln("")):this.showToast?.(e,"info"),s},_appendSessionLaunchStatus(e,t,s="90"){!e||this.activeSessionId||this.terminal.writeln(`\x1B[${s}m ${t}\x1B[0m`)},_reportSessionLaunchError(e,t){e&&!this.activeSessionId?this.terminal.writeln(`\x1B[1;31m Error: ${t}\x1B[0m`):this.showToast?.(t,"error")},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1)),s=this._beginSessionLaunchStatus(`Starting ${t} Claude session(s) in ${e}...`);this.terminal.focus();try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const S=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!S.success)throw new Error(S.error||"Failed to create case");n=S.data.case}const i=n.path;if(!i)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const g=this._nextCaseSessionStartNumber(e);let S;if(n.location==="docker"){const b=this.loadAppSettingsFromStorage(),E=this.getCaseSettings(e).opusContext1m||b.opusContext1mEnabled;S=b.claudeModel||(E?"opus[1m]":"")}const I=[];let T=!1;for(let b=0;b<t;b++){const B=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${g+b}-${e}`,...S!==void 0?{modelOverride:S}:{}}),E=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:B})).json();let w=await E();if(!w.success&&w.errorCode==="CONFLICT"&&n.location==="docker"&&!T&&(T=!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",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(),this._refreshMobileOverviewIfVisible?.()}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 _ensureCreatedSessionVisible(e,t){if(!e)return;let s=t;if(!s&&!this.sessions?.has(e)){const n=await(await fetch(`/api/sessions/${encodeURIComponent(e)}`)).json();if(!n.success)throw new Error(n.error||"Failed to load the new session");s=n.data?.session||n.data}s?.id&&this._onSessionCreated(s),this._renderSessionTabsImmediate?.()},async run(){if(this._runInFlight)return;const e=Date.now(),t=Number.isFinite(this._runMinLockMs)?this._runMinLockMs:500,s=document.getElementById("runBtn");this._runInFlight=!0,s&&(s.disabled=!0,s.setAttribute("aria-busy","true"));try{const a=this._runMode||"claude";return a==="opencode"?await this.runOpenCode():a==="codex"?await this.runCodex():a==="gemini"?await this.runGemini():a==="antigravity"?await this.runAntigravity():a==="pi"?await this.runPi():a==="shell"?await this.runShell():await this.runClaude()}finally{const a=t-(Date.now()-e);a>0&&await new Promise(n=>setTimeout(n,a)),this._runInFlight=!1,s&&(s.disabled=!1,s.removeAttribute("aria-busy"))}},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(),this._refreshRunModeAvailability(t);const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},_refreshRunModeAvailability(e){for(const t of["claude","opencode","codex","gemini","antigravity","pi"]){const s=e.querySelector(`.run-mode-option[data-mode="${t}"]`);s&&(s.style.display=this.isCliAvailable(t)?"flex":"none")}},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=this._shortenHomePath(s.workingDir),o=i.lastIndexOf("/"),r=o===-1?i:i.slice(o+1),c=(o===-1?"":i.slice(0,o)).replace(/\/\.claude\/worktrees$/,""),l=document.createElement("button");l.className="run-mode-option run-mode-hist-row",l.title=s.workingDir,l.dataset.sessionId=s.sessionId,l.dataset.workingDir=s.workingDir;const m=document.createElement("span");m.className="hist-name",m.textContent=r;const u=[m],h=this._worktreeLabel?this._worktreeLabel(s):"";if(h){const d=document.createElement("span");d.className="hist-wt",d.textContent=h,u.push(d)}if(c){const d=document.createElement("span");d.className="hist-dir",d.textContent=c,u.push(d)}const g=document.createElement("span");g.className="hist-meta",g.textContent=n,u.push(g),l.append(...u),l.addEventListener("click",d=>{d.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir,s.name)}),e.appendChild(l)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,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":e==="antigravity"?"Run AG":e==="pi"?"Run PI":e==="shell"?"Run SH":"Run")},sendEnterKey(){if(!this.activeSessionId)return;const e=this.terminal?._core?.coreService;if(e&&typeof e.triggerDataEvent=="function"){e.triggerDataEvent("\r",!0);return}this.sendInput("\r")},_initRunMode(){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},_beginSessionLaunchStatus(e,t="1;32"){const s=!this.activeSessionId;return s?(this.terminal.clear(),this.terminal.writeln(`\x1B[${t}m ${e}\x1B[0m`),this.terminal.writeln("")):this.showToast?.(e,"info"),s},_appendSessionLaunchStatus(e,t,s="90"){!e||this.activeSessionId||this.terminal.writeln(`\x1B[${s}m ${t}\x1B[0m`)},_reportSessionLaunchError(e,t){e&&!this.activeSessionId?this.terminal.writeln(`\x1B[1;31m Error: ${t}\x1B[0m`):this.showToast?.(t,"error")},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1)),s=this._beginSessionLaunchStatus(`Starting ${t} Claude session(s) in ${e}...`);this.terminal.focus();try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const S=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!S.success)throw new Error(S.error||"Failed to create case");n=S.data.case}const i=n.path;if(!i)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const f=this._nextCaseSessionStartNumber(e);let S;if(n.location==="docker"){const b=this.loadAppSettingsFromStorage(),E=this.getCaseSettings(e).opusContext1m||b.opusContext1mEnabled;S=b.claudeModel||(E?"opus[1m]":"")}const I=[];let T=!1;for(let b=0;b<t;b++){const B=JSON.stringify({caseName:e,mode:"claude",sessionName:`w${f+b}-${e}`,...S!==void 0?{modelOverride:S}:{}}),E=async()=>(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:B})).json();let w=await E();if(!w.success&&w.errorCode==="CONFLICT"&&n.location==="docker"&&!T&&(T=!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 x=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!x.success)throw new Error(x.error||"Failed to recreate container");w=await E()}if(!w.success)throw new Error(w.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(w.data.sessionId,w.data.session),I.push(w.data.sessionId)}this._appendSessionLaunchStatus(s,`All ${t} remote session(s) ready`),I[0]&&(await this.selectSession(I[0]),this.loadQuickStartCases()),this.terminal.focus();return}let o=null;const r=this._nextCaseSessionStartNumber(e),c=this.isRalphTrackerEnabledByDefault(),l=[];for(let g=0;g<t;g++)l.push(`w${r+g}-${e}`);const m=this.getCaseSettings(e),h=this.loadAppSettingsFromStorage(),u=this.buildEnvOverrides(m,h),y=Object.keys(u).length>0,d=this.getEffortSetting(h),p=m.opusContext1m||h.opusContext1mEnabled,f=h.claudeModel||(p?"opus[1m]":"");this._appendSessionLaunchStatus(s,`Creating ${t} session(s)...`);const C=l.map(g=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:i,name:g,...y?{envOverrides:u}:{},...d?{effort:d}:{},...f!==void 0?{modelOverride:f}:{},statusLineTelemetry:this.planUsageChipEnabled(h)})}).then(S=>S.json())),v=await Promise.all(C),k=[];for(const g of v){if(!g.success)throw new Error(g.error);await this._ensureCreatedSessionVisible(g.data.session.id,g.data.session),k.push(g.data.session.id)}o=k[0],await Promise.all(k.map(g=>fetch(`/api/sessions/${g}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:c,disableAutoEnable:!c})}))),this._appendSessionLaunchStatus(s,`Starting ${t} session(s) in parallel...`),await Promise.all(k.map(g=>fetch(`/api/sessions/${g}/interactive`,{method:"POST"}))),this._appendSessionLaunchStatus(s,`All ${t} sessions ready`),o&&(await this.selectSession(o),this.loadQuickStartCases()),this.terminal.focus()}catch(a){this._reportSessionLaunchError(s,a.message)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1)),s=this._beginSessionLaunchStatus(`Starting ${t} Shell session(s) in ${e}...`,"1;33");try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const p=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!p.success)throw new Error(p.error||"Failed to create case");n=p.data.case}const i=(this.cases||[]).find(d=>d.name===e),o=n.location==="remote"||n.location==="docker"||i?.location==="remote"||i?.location==="docker",r=n.path;if(!r)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const d=this._nextCaseSessionStartNumber(e,"s"),p=[];for(let f=0;f<t;f++){const v=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${d+f}-${e}`})})).json();if(!v.success)throw new Error(v.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(v.data.sessionId,v.data.session),p.push(v.data.sessionId)}p[0]&&await this.selectSession(p[0]),this.terminal.focus();return}const c=this._nextCaseSessionStartNumber(e,"s"),l=[];for(let d=0;d<t;d++)l.push(`s${c+d}-${e}`);const m=l.map(d=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...o?{caseName:e}:{workingDir:r},mode:"shell",name:d})}).then(p=>p.json())),h=await Promise.all(m),u=[];for(const d of h){if(!d.success)throw new Error(d.error);await this._ensureCreatedSessionVisible(d.data.session.id,d.data.session),u.push(d.data.session.id)}await Promise.all(u.map(d=>fetch(`/api/sessions/${d}/shell`,{method:"POST"})));const y=this.getTerminalDimensions();y&&await Promise.all(u.map(d=>fetch(`/api/sessions/${d}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(y)}))),u.length>0&&await this.selectSession(u[0]),this.terminal.focus()}catch(a){this._reportSessionLaunchError(s,a.message)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting OpenCode session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this._reportSessionLaunchError(a,"OpenCode CLI not found. Install with: curl -fsSL https://opencode.ai/install | bash");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=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(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start OpenCode");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Codex session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this._reportSessionLaunchError(a,"Codex CLI not found. Install with: npm install -g @openai/codex");return}const n=this.loadAppSettingsFromStorage(),i=this.buildEnvOverrides(this.getCaseSettings(e),n),r=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:n.codexDangerouslyBypassApprovals??!1,animations:n.codexAnimationsEnabled??!1,renderMode:"hybrid"},...Object.keys(i).length>0?{envOverrides:i}:{}}})})).json();if(!r.success)throw new Error(r.error||"Failed to start Codex");await this._ensureCreatedSessionVisible(r.data.sessionId,r.data.session),r.data.sessionId&&await this.selectSession(r.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Gemini session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this._reportSessionLaunchError(a,"Gemini CLI not found. Install with: npm install -g @google/gemini-cli");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=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(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Gemini");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runAntigravity(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Antigravity session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/antigravity/status")).json()).data.available){this._reportSessionLaunchError(a,"Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"antigravity",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{antigravityConfig:{dangerouslySkipPermissions:!0},...Object.keys(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Antigravity");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runPi(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Pi session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/pi/status")).json()).data.available){this._reportSessionLaunchError(a,"Pi CLI not found. Install with: npm install -g --ignore-scripts @earendil-works/pi-coding-agent");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"pi",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s||Object.keys(n).length===0?{}:{envOverrides:n}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Pi");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},_tabDetachOverrides(){if(this._tabDetachOverrideMap===void 0)try{this._tabDetachOverrideMap=JSON.parse(localStorage.getItem("codeman:tab-detach-overrides")||"{}")||{}}catch{this._tabDetachOverrideMap={}}return this._tabDetachOverrideMap},hasTabDetachOverride(e){return!!this._tabDetachOverrides()[e]},onSessionTabDetachToggle(e){const t=this.editingSessionId;if(!t)return;const s=this._tabDetachOverrides();e?s[t]=1:delete s[t];for(const n of Object.keys(s))n!==t&&this.sessions&&!this.sessions.has(n)&&delete s[n];try{localStorage.setItem("codeman:tab-detach-overrides",JSON.stringify(s))}catch{}const a=document.querySelector(`.session-tab[data-id="${CSS.escape(t)}"]`);a&&a.classList.toggle("tab-show-detach",!!e)},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=document.getElementById("sessionOptShowTabDetach");s&&(s.checked=this.hasTabDetachOverride(e));const a=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity"||t.mode==="pi";this.switchOptionsTab(a?"summary":"respawn");const n=document.getElementById("sessionRespawnStatus"),i=document.getElementById("modalEnableRespawnBtn"),o=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(n.classList.add("active"),n.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",i.style.display="none",o.style.display=""):(n.classList.remove("active"),n.querySelector(".respawn-status-text").textContent="Not active",i.style.display="",o.style.display="none");const r=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?r.style.display="":r.style.display="none";const c=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity"||t.mode==="pi";document.querySelectorAll("[data-claude-only]").forEach(C=>{C.style.display=c?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const m=parseSessionPrefix(t.name),h=document.getElementById("modalSessionPrefix");m?(h.textContent=m.prefix+": ",h.style.display="",document.getElementById("modalSessionName").value=m.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(h.style.display="none",h.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(C=>{C.classList.toggle("selected",C.dataset.color===u)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const d=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="ralph"]'),p=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="respawn"]');if(c?(d&&(d.style.display="none"),p&&(p.style.display="none"),this.switchOptionsTab("context")):(d&&(d.style.display=""),p&&(p.style.display="")),!c){const C=this.ralphStates.get(e);this.populateRalphForm({enabled:C?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:C?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:C?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:C?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:C?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const f=document.getElementById("sessionOptionsModal");f.dataset.chipsReady!=="1"&&(f.dataset.chipsReady="1",f.addEventListener("change",C=>{C.target?.closest?.(".set-chip")&&this._syncSettingsChips()})),this._syncSettingsChips(),f.classList.add("active"),this.activeFocusTrap=new FocusTrap(f),this.activeFocusTrap.activate()},_applyLocalSessionName(e,t){if(typeof t!="string")return;const s=this.sessions.get(e);s&&(s.name=t,this.sessions.set(e,s),this.updateSubagentParentNames?.(e))},async _putSessionName(e,t){const s=await this._apiPut(`/api/sessions/${e}/name`,{name:t});if(!s||!s.ok)return null;let a=null;try{a=await s.json()}catch{return null}if(a&&a.success===!1)return null;const n=a?.data?.name;return typeof n=="string"?n:t},async saveSessionName(){if(!this.editingSessionId)return;const e=this.editingSessionId,t=this.sessions.get(e),s=t?parseSessionPrefix(t.name):null,a=document.getElementById("modalSessionName").value.trim();let n;s?n=s.prefix+(a?": "+a:""):n=a;const i=await this._putSessionName(e,n);if(i===null){this.showToast("Failed to save session name","error");return}this._applyLocalSessionName(e,i),this.renderSessionTabs()},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const 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 .set-rail-item").forEach(s=>{s.classList.toggle("active",s.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary");const t=document.getElementById("sessionOptionsDoc");t&&(t.scrollTop=0),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;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 l=o.value.trim(),m=n?n.prefix+(l?": "+l:""):l;if(s.textContent=m||i,this.sessions.has(e)&&m!==t.name){const u=await this._putSessionName(e,m);u===null?(s.textContent=i,this.showToast("Failed to rename","error")):this._applyLocalSessionName(e,u)}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(a=>{const n=document.getElementById(a);n&&(n.value="")}),this._resetCloneForm();const t=document.getElementById("caseCloneTabBtn");t&&(t.style.display=this.isCliAvailable("git")?"":"none"),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const s=document.getElementById("createCaseModal");s.querySelectorAll(".set-rail-item").forEach(a=>{a.onclick=()=>this.switchCaseModalTab(a.dataset.tab)}),s.querySelectorAll('input[type="text"]').forEach(a=>{a._mobileScrollWired||(a._mobileScrollWired=!0,a.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>a.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),s.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".set-rail-item").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)}),t.querySelectorAll(".set-section").forEach(n=>{n.classList.toggle("hidden",n.id!==e)});const s=document.getElementById("createCaseDoc");s&&(s.scrollTop=0);const a=document.getElementById("caseModalSubmit");e==="case-manage"?(a.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(a.style.display="",a.textContent=e==="case-create"?"Create":e==="case-clone"?"Clone":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-clone"?document.getElementById("cloneRepoUrl").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":this.caseModalTab==="case-clone"?"Cloning...":"Linking...",e.disabled=!0;try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-clone"?await this.cloneCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.disabled=!1,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")}},_resetCloneForm(){const e=(o,r)=>{const c=document.getElementById(o);c&&(c.value=r)};e("cloneRepoUrl",""),e("cloneCaseName",""),e("cloneRepoRef","");const t=document.getElementById("cloneShallow");t&&(t.checked=!1);const s=document.getElementById("cloneStartSession");s&&(s.checked=!1);const a=document.getElementById("cloneRepoRefOptions");a&&a.replaceChildren();const n=document.getElementById("cloneRefHint");n&&(n.textContent="Leave blank for the repository's default branch."),this._cloneNameEdited=!1,this._clonePreflight=null,clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");const i=document.getElementById("cloneCaseBrain");if(i){for(const r of i.options){const c=r.dataset.cli;r.hidden=!!c&&!this.isCliAvailable(c)}const o=this.runMode||"claude";i.value=[...i.options].some(r=>r.value===o&&!r.hidden)?o:""}},_setCloneStatus(e,t){const s=document.getElementById("cloneRepoStatus");s&&(s.textContent=e,s.className=`form-hint clone-status${t?" clone-status-"+t:""}`)},_repoNameFromUrl(e){const t=(e||"").trim().replace(/\/+$/,"");return t?(t.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//,"").replace(/^[^@/]*@/,"").split(/[/:]/).filter(Boolean).pop()||"").replace(/\.git$/i,"").replace(/[^a-zA-Z0-9_-]+/g,"-").replace(/-{2,}/g,"-").replace(/^[-_]+|[-_]+$/g,"").slice(0,64):""},onCloneNameEdited(){this._cloneNameEdited=!!document.getElementById("cloneCaseName")?.value.trim()},onCloneUrlInput(){const e=document.getElementById("cloneRepoUrl")?.value.trim()||"",t=document.getElementById("cloneCaseName");if(t&&!this._cloneNameEdited&&(t.value=this._repoNameFromUrl(e)),clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,!e){this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");return}if(this.isCliAvailable("git")===!1){this._setCloneStatus("git is not installed on the Codeman host, so cloning is unavailable.","err");return}this._setCloneStatus("Checking the repository\u2026",""),this._clonePreflightTimer=setTimeout(()=>this._runClonePreflight(e),450)},async _runClonePreflight(e){const t=new AbortController;this._clonePreflightAbort=t;try{const a=await(await fetch("/api/cases/clone-preflight",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repository:e}),signal:t.signal})).json();if(document.getElementById("cloneRepoUrl")?.value.trim()!==e)return;if(!a.success){this._setCloneStatus(a.error||"Could not check that URL.","err");return}this._applyClonePreflight(a.data,e)}catch(s){if(s.name==="AbortError")return;this._setCloneStatus("Could not reach Codeman to check that URL.","err")}},_applyClonePreflight(e,t){this._clonePreflight=e;const s=e?.parse;if(!s?.cloneable){this._setCloneStatus(s?.message||"That URL cannot be cloned.","err");return}const a=document.getElementById("cloneCaseName");a&&!this._cloneNameEdited&&s.suggestedName&&(a.value=s.suggestedName);const n=s.owner?`${s.provider} ${s.owner}/${s.repo}`:`${s.provider} ${s.repo}`;if(e.gitAvailable===!1){this._setCloneStatus(`${n}: git is not installed on the Codeman host.`,"err");return}const i=e.remote;if(i&&!i.reachable){this._setCloneStatus(`${n}: ${i.failure?.message||"the remote could not be read."}`,"err");return}const o=document.getElementById("cloneRefHint"),r=document.getElementById("cloneRepoRefOptions");if(i&&r){r.replaceChildren();for(const l of[...i.branches||[],...i.tags||[]]){const m=document.createElement("option");m.value=l,r.appendChild(m)}if(o){const l=`${i.branches?.length||0} branches, ${i.tags?.length||0} tags`;o.textContent=i.defaultBranch?`Blank clones the default branch (${i.defaultBranch}). ${l} available.`:`Blank clones the default branch. ${l} available.`}}const c=s.warnings?.[0];this._setCloneStatus(c?`${n}: ${c}`:`${n}: ready to clone.`,c?"warn":"ok")},async cloneCase(){const e=document.getElementById("cloneRepoUrl").value.trim(),t=document.getElementById("cloneCaseName").value.trim(),s=document.getElementById("cloneRepoRef").value.trim(),a=!!document.getElementById("cloneShallow")?.checked,n=document.getElementById("cloneCaseBrain")?.value||"",i=!!document.getElementById("cloneStartSession")?.checked;if(!e){this.showToast("Please enter a repository URL","error");return}if(!t){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(t)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}this._setCloneStatus(`Cloning ${e}\u2026 this can take a while for a large repository.`,"");try{const r=await(await fetch("/api/cases/clone",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,repository:e,...s?{ref:s}:{},...a?{shallow:!0}:{}})})).json();if(!r.success){this._setCloneStatus(r.error||"Clone failed.","err"),this.showToast(r.error||"Failed to clone repository","error");return}n&&this.setRunMode(n),this.closeCreateCaseModal(),await this.loadQuickStartCases(t),await this.saveLastUsedCase(t),this.showToast(`Cloned into case "${t}"`,"success");for(const c of r.data?.warnings||[])this.showToast(c,"warning");i&&await this.run()}catch(o){console.error("Failed to clone repository:",o),this._setCloneStatus(`Lost the connection while cloning: ${o.message}. If git finishes, the case still appears in the list.`,"warn"),this.showToast("Clone request interrupted \u2014 watch the case list","error")}},openLinkCasePathPicker(){const e=document.getElementById("linkCasePath");PathPicker.open({title:"Select Existing Project Folder",initialPath:e.value.trim(),directoriesOnly:!0,onSelect:t=>{e.value=t;const s=document.getElementById("linkCaseName");if(!s.value.trim()){const a=t.split("/").filter(Boolean).pop()||"";/^[\p{L}\p{N}_-]+$/u.test(a)&&(s.value=a)}e.focus(),e.setSelectionRange(t.length,t.length)}})},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(),l=document.getElementById("remoteHostJumpHost").value.trim(),m=document.getElementById("remoteHostExtraSshOptions").value.split(`
|
|
16
|
-
`).map(
|
|
15
|
+
Recreate the container to apply the new config? Workspace files and the conversation survive (the conversation resumes on launch).`))){const x=await(await fetch(`/api/docker-cases/${encodeURIComponent(e)}/recreate`,{method:"POST"})).json();if(!x.success)throw new Error(x.error||"Failed to recreate container");w=await E()}if(!w.success)throw new Error(w.error||"Failed to start remote Claude session");await this._ensureCreatedSessionVisible(w.data.sessionId,w.data.session),I.push(w.data.sessionId)}this._appendSessionLaunchStatus(s,`All ${t} remote session(s) ready`),I[0]&&(await this.selectSession(I[0]),this.loadQuickStartCases()),this.terminal.focus();return}let o=null;const r=this._nextCaseSessionStartNumber(e),c=this.isRalphTrackerEnabledByDefault(),l=[];for(let f=0;f<t;f++)l.push(`w${r+f}-${e}`);const m=this.getCaseSettings(e),u=this.loadAppSettingsFromStorage(),h=this.buildEnvOverrides(m,u),g=Object.keys(h).length>0,d=this.getEffortSetting(u),p=m.opusContext1m||u.opusContext1mEnabled,y=u.claudeModel||(p?"opus[1m]":"");this._appendSessionLaunchStatus(s,`Creating ${t} session(s)...`);const C=l.map(f=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:i,name:f,...g?{envOverrides:h}:{},...d?{effort:d}:{},...y!==void 0?{modelOverride:y}:{},statusLineTelemetry:this.planUsageChipEnabled(u)})}).then(S=>S.json())),v=await Promise.all(C),k=[];for(const f of v){if(!f.success)throw new Error(f.error);await this._ensureCreatedSessionVisible(f.data.session.id,f.data.session),k.push(f.data.session.id)}o=k[0],await Promise.all(k.map(f=>fetch(`/api/sessions/${f}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:c,disableAutoEnable:!c})}))),this._appendSessionLaunchStatus(s,`Starting ${t} session(s) in parallel...`),await Promise.all(k.map(f=>fetch(`/api/sessions/${f}/interactive`,{method:"POST"}))),this._appendSessionLaunchStatus(s,`All ${t} sessions ready`),o&&(await this.selectSession(o),this.loadQuickStartCases()),this.terminal.focus()}catch(a){this._reportSessionLaunchError(s,a.message)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1)),s=this._beginSessionLaunchStatus(`Starting ${t} Shell session(s) in ${e}...`,"1;33");try{let n=(await(await fetch(`/api/cases/${e}`)).json())?.data??{};if(!n.path){const p=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!p.success)throw new Error(p.error||"Failed to create case");n=p.data.case}const i=(this.cases||[]).find(d=>d.name===e),o=n.location==="remote"||n.location==="docker"||i?.location==="remote"||i?.location==="docker",r=n.path;if(!r)throw new Error("Case path not found");if(n.location==="remote"||n.location==="docker"){const d=this._nextCaseSessionStartNumber(e,"s"),p=[];for(let y=0;y<t;y++){const v=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"shell",sessionName:`s${d+y}-${e}`})})).json();if(!v.success)throw new Error(v.error||"Failed to start remote shell session");await this._ensureCreatedSessionVisible(v.data.sessionId,v.data.session),p.push(v.data.sessionId)}p[0]&&await this.selectSession(p[0]),this.terminal.focus();return}const c=this._nextCaseSessionStartNumber(e,"s"),l=[];for(let d=0;d<t;d++)l.push(`s${c+d}-${e}`);const m=l.map(d=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...o?{caseName:e}:{workingDir:r},mode:"shell",name:d})}).then(p=>p.json())),u=await Promise.all(m),h=[];for(const d of u){if(!d.success)throw new Error(d.error);await this._ensureCreatedSessionVisible(d.data.session.id,d.data.session),h.push(d.data.session.id)}await Promise.all(h.map(d=>fetch(`/api/sessions/${d}/shell`,{method:"POST"})));const g=this.getTerminalDimensions();g&&await Promise.all(h.map(d=>fetch(`/api/sessions/${d}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)}))),h.length>0&&await this.selectSession(h[0]),this.terminal.focus()}catch(a){this._reportSessionLaunchError(s,a.message)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting OpenCode session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/opencode/status")).json()).data.available){this._reportSessionLaunchError(a,"OpenCode CLI not found. Install with: curl -fsSL https://opencode.ai/install | bash");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=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(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start OpenCode");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runCodex(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Codex session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/codex/status")).json()).data.available){this._reportSessionLaunchError(a,"Codex CLI not found. Install with: npm install -g @openai/codex");return}const n=this.loadAppSettingsFromStorage(),i=this.buildEnvOverrides(this.getCaseSettings(e),n),r=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:n.codexDangerouslyBypassApprovals??!1,animations:n.codexAnimationsEnabled??!1,renderMode:"hybrid"},...Object.keys(i).length>0?{envOverrides:i}:{}}})})).json();if(!r.success)throw new Error(r.error||"Failed to start Codex");await this._ensureCreatedSessionVisible(r.data.sessionId,r.data.session),r.data.sessionId&&await this.selectSession(r.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runGemini(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Gemini session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/gemini/status")).json()).data.available){this._reportSessionLaunchError(a,"Gemini CLI not found. Install with: npm install -g @google/gemini-cli");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=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(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Gemini");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runAntigravity(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Antigravity session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/antigravity/status")).json()).data.available){this._reportSessionLaunchError(a,"Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"antigravity",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s?{}:{antigravityConfig:{dangerouslySkipPermissions:!0},...Object.keys(n).length>0?{envOverrides:n}:{}}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Antigravity");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},async runPi(){const e=document.getElementById("quickStartCase").value||"testcase",t=(this.cases||[]).find(n=>n.name===e)?.location,s=t==="remote"||t==="docker",a=this._beginSessionLaunchStatus(`Starting Pi session in ${e}...`);this.terminal.focus();try{if(!s&&!(await(await fetch("/api/pi/status")).json()).data.available){this._reportSessionLaunchError(a,"Pi CLI not found. Install with: npm install -g --ignore-scripts @earendil-works/pi-coding-agent");return}const n=this.buildEnvOverrides(this.getCaseSettings(e),this.loadAppSettingsFromStorage()),o=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"pi",sessionName:`w${this._nextCaseSessionStartNumber(e)}-${e}`,...s||Object.keys(n).length===0?{}:{envOverrides:n}})})).json();if(!o.success)throw new Error(o.error||"Failed to start Pi");await this._ensureCreatedSessionVisible(o.data.sessionId,o.data.session),o.data.sessionId&&await this.selectSession(o.data.sessionId),this.terminal.focus()}catch(n){this._reportSessionLaunchError(a,n.message)}},_tabDetachOverrides(){if(this._tabDetachOverrideMap===void 0)try{this._tabDetachOverrideMap=JSON.parse(localStorage.getItem("codeman:tab-detach-overrides")||"{}")||{}}catch{this._tabDetachOverrideMap={}}return this._tabDetachOverrideMap},hasTabDetachOverride(e){return!!this._tabDetachOverrides()[e]},onSessionTabDetachToggle(e){const t=this.editingSessionId;if(!t)return;const s=this._tabDetachOverrides();e?s[t]=1:delete s[t];for(const n of Object.keys(s))n!==t&&this.sessions&&!this.sessions.has(n)&&delete s[n];try{localStorage.setItem("codeman:tab-detach-overrides",JSON.stringify(s))}catch{}const a=document.querySelector(`.session-tab[data-id="${CSS.escape(t)}"]`);a&&a.classList.toggle("tab-show-detach",!!e)},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e;const s=document.getElementById("sessionOptShowTabDetach");s&&(s.checked=this.hasTabDetachOverride(e));const a=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity"||t.mode==="pi";this.switchOptionsTab(a?"summary":"respawn");const n=document.getElementById("sessionRespawnStatus"),i=document.getElementById("modalEnableRespawnBtn"),o=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(n.classList.add("active"),n.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",i.style.display="none",o.style.display=""):(n.classList.remove("active"),n.querySelector(".respawn-status-text").textContent="Not active",i.style.display="",o.style.display="none");const r=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?r.style.display="":r.style.display="none";const c=t.mode==="opencode"||t.mode==="codex"||t.mode==="gemini"||t.mode==="antigravity"||t.mode==="pi";document.querySelectorAll("[data-claude-only]").forEach(C=>{C.style.display=c?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalAutoResumeEnabled").checked=t.autoResumeEnabled??!1,this.updateAutoResumeStatus(e),document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const m=parseSessionPrefix(t.name),u=document.getElementById("modalSessionPrefix");m?(u.textContent=m.prefix+": ",u.style.display="",document.getElementById("modalSessionName").value=m.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(u.style.display="none",u.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const h=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(C=>{C.classList.toggle("selected",C.dataset.color===h)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const d=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="ralph"]'),p=document.querySelector('#sessionOptionsModal .set-rail-item[data-tab="respawn"]');if(c?(d&&(d.style.display="none"),p&&(p.style.display="none"),this.switchOptionsTab("context")):(d&&(d.style.display=""),p&&(p.style.display="")),!c){const C=this.ralphStates.get(e);this.populateRalphForm({enabled:C?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:C?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:C?.loop?.maxIterations||t.ralphLoop?.maxIterations||0,maxTodos:C?.loop?.maxTodos||t.ralphLoop?.maxTodos,todoExpirationMinutes:C?.loop?.todoExpirationMinutes||t.ralphLoop?.todoExpirationMinutes})}const y=document.getElementById("sessionOptionsModal");y.dataset.chipsReady!=="1"&&(y.dataset.chipsReady="1",y.addEventListener("change",C=>{C.target?.closest?.(".set-chip")&&this._syncSettingsChips()})),this._syncSettingsChips(),y.classList.add("active"),this.activeFocusTrap=new FocusTrap(y),this.activeFocusTrap.activate()},_applyLocalSessionName(e,t){if(typeof t!="string")return;const s=this.sessions.get(e);s&&(s.name=t,this.sessions.set(e,s),this.updateSubagentParentNames?.(e))},async _putSessionName(e,t){const s=await this._apiPut(`/api/sessions/${e}/name`,{name:t});if(!s||!s.ok)return null;let a=null;try{a=await s.json()}catch{return null}if(a&&a.success===!1)return null;const n=a?.data?.name;return typeof n=="string"?n:t},async saveSessionName(){if(!this.editingSessionId)return;const e=this.editingSessionId,t=this.sessions.get(e),s=t?parseSessionPrefix(t.name):null,a=document.getElementById("modalSessionName").value.trim();let n;s?n=s.prefix+(a?": "+a:""):n=a;const i=await this._putSessionName(e,n);if(i===null){this.showToast("Failed to save session name","error");return}this._applyLocalSessionName(e,i),this.renderSessionTabs()},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async autoSaveAutoResume(){if(!this.editingSessionId)return;const e=document.getElementById("modalAutoResumeEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-resume`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.autoResumeEnabled=e,e||(t.autoResumeAt=void 0)),this.updateAutoResumeStatus(this.editingSessionId),this.showToast(`Auto-resume on usage limit ${e?"enabled":"disabled"}`,"success")}catch(t){this.showToast("Failed to toggle auto-resume: "+t.message,"error")}},updateAutoResumeStatus(e){const t=document.getElementById("autoResumeStatus");if(!t||this.editingSessionId!==e)return;const s=this.sessions.get(e);if(s?.autoResumeAt&&s.autoResumeAt>Date.now()){const 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 .set-rail-item").forEach(s=>{s.classList.toggle("active",s.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary");const t=document.getElementById("sessionOptionsDoc");t&&(t.scrollTop=0),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;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 l=document.createElement("span");l.textContent=n.prefix+": ",l.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(l)}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";const r=this.isSessionSidebarActive?.()?"100%":"80px";o.style.cssText=`width: ${r}; min-width: 0; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;`,s.appendChild(o),o.focus(),o.select();const c=async({commit:l})=>{if(!this._inlineRenameActive)return;if(this._inlineRenameActive=!1,this._activeRename=null,!l){this.renderSessionTabs();return}const m=o.value.trim(),u=n?n.prefix+(m?": "+m:""):m;if(s.textContent=u||i,this.sessions.has(e)&&u!==t.name){const g=await this._putSessionName(e,u);g===null?(s.textContent=i,this.showToast("Failed to rename","error")):this._applyLocalSessionName(e,g)}this.renderSessionTabs()};this._activeRename={sessionId:e,cancel:()=>c({commit:!1})},o.addEventListener("blur",()=>c({commit:!0})),o.addEventListener("keydown",l=>{l.isComposing||l.keyCode===229||(l.key==="Enter"?(l.preventDefault(),o.blur()):l.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(a=>{const n=document.getElementById(a);n&&(n.value="")}),this._resetCloneForm();const t=document.getElementById("caseCloneTabBtn");t&&(t.style.display=this.isCliAvailable("git")?"":"none"),this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const s=document.getElementById("createCaseModal");s.querySelectorAll(".set-rail-item").forEach(a=>{a.onclick=()=>this.switchCaseModalTab(a.dataset.tab)}),s.querySelectorAll('input[type="text"]').forEach(a=>{a._mobileScrollWired||(a._mobileScrollWired=!0,a.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>a.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),s.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".set-rail-item").forEach(n=>{n.classList.toggle("active",n.dataset.tab===e)}),t.querySelectorAll(".set-section").forEach(n=>{n.classList.toggle("hidden",n.id!==e)});const s=document.getElementById("createCaseDoc");s&&(s.scrollTop=0);const a=document.getElementById("caseModalSubmit");e==="case-manage"?(a.style.display="none",this.renderCaseManageList(),this.refreshDockerExports()):(a.style.display="",a.textContent=e==="case-create"?"Create":e==="case-clone"?"Clone":e==="case-remote"?"Link Remote":e==="case-docker"?"Link Docker":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-clone"?document.getElementById("cloneRepoUrl").focus():e==="case-link"?document.getElementById("linkCaseName").focus():e==="case-remote"?document.getElementById("remoteCaseName").focus():e==="case-docker"&&document.getElementById("dockerCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":this.caseModalTab==="case-clone"?"Cloning...":"Linking...",e.disabled=!0;try{this.caseModalTab==="case-create"?await this.createCase():this.caseModalTab==="case-clone"?await this.cloneCase():this.caseModalTab==="case-remote"?await this.linkRemoteCase():this.caseModalTab==="case-docker"?await this.linkDockerCase():await this.linkCase()}finally{e.classList.remove("loading"),e.disabled=!1,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")}},_resetCloneForm(){const e=(o,r)=>{const c=document.getElementById(o);c&&(c.value=r)};e("cloneRepoUrl",""),e("cloneCaseName",""),e("cloneRepoRef","");const t=document.getElementById("cloneShallow");t&&(t.checked=!1);const s=document.getElementById("cloneStartSession");s&&(s.checked=!1);const a=document.getElementById("cloneRepoRefOptions");a&&a.replaceChildren();const n=document.getElementById("cloneRefHint");n&&(n.textContent="Leave blank for the repository's default branch."),this._cloneNameEdited=!1,this._clonePreflight=null,clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");const i=document.getElementById("cloneCaseBrain");if(i){for(const r of i.options){const c=r.dataset.cli;r.hidden=!!c&&!this.isCliAvailable(c)}const o=this.runMode||"claude";i.value=[...i.options].some(r=>r.value===o&&!r.hidden)?o:""}},_setCloneStatus(e,t){const s=document.getElementById("cloneRepoStatus");s&&(s.textContent=e,s.className=`form-hint clone-status${t?" clone-status-"+t:""}`)},_repoNameFromUrl(e){const t=(e||"").trim().replace(/\/+$/,"");return t?(t.replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//,"").replace(/^[^@/]*@/,"").split(/[/:]/).filter(Boolean).pop()||"").replace(/\.git$/i,"").replace(/[^a-zA-Z0-9_-]+/g,"-").replace(/-{2,}/g,"-").replace(/^[-_]+|[-_]+$/g,"").slice(0,64):""},onCloneNameEdited(){this._cloneNameEdited=!!document.getElementById("cloneCaseName")?.value.trim()},onCloneUrlInput(){const e=document.getElementById("cloneRepoUrl")?.value.trim()||"",t=document.getElementById("cloneCaseName");if(t&&!this._cloneNameEdited&&(t.value=this._repoNameFromUrl(e)),clearTimeout(this._clonePreflightTimer),this._clonePreflightAbort?.abort(),this._clonePreflightAbort=null,!e){this._setCloneStatus("Public repositories only: Codeman clones with no credentials.","");return}if(this.isCliAvailable("git")===!1){this._setCloneStatus("git is not installed on the Codeman host, so cloning is unavailable.","err");return}this._setCloneStatus("Checking the repository\u2026",""),this._clonePreflightTimer=setTimeout(()=>this._runClonePreflight(e),450)},async _runClonePreflight(e){const t=new AbortController;this._clonePreflightAbort=t;try{const a=await(await fetch("/api/cases/clone-preflight",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repository:e}),signal:t.signal})).json();if(document.getElementById("cloneRepoUrl")?.value.trim()!==e)return;if(!a.success){this._setCloneStatus(a.error||"Could not check that URL.","err");return}this._applyClonePreflight(a.data,e)}catch(s){if(s.name==="AbortError")return;this._setCloneStatus("Could not reach Codeman to check that URL.","err")}},_applyClonePreflight(e,t){this._clonePreflight=e;const s=e?.parse;if(!s?.cloneable){this._setCloneStatus(s?.message||"That URL cannot be cloned.","err");return}const a=document.getElementById("cloneCaseName");a&&!this._cloneNameEdited&&s.suggestedName&&(a.value=s.suggestedName);const n=s.owner?`${s.provider} ${s.owner}/${s.repo}`:`${s.provider} ${s.repo}`;if(e.gitAvailable===!1){this._setCloneStatus(`${n}: git is not installed on the Codeman host.`,"err");return}const i=e.remote;if(i&&!i.reachable){this._setCloneStatus(`${n}: ${i.failure?.message||"the remote could not be read."}`,"err");return}const o=document.getElementById("cloneRefHint"),r=document.getElementById("cloneRepoRefOptions");if(i&&r){r.replaceChildren();for(const l of[...i.branches||[],...i.tags||[]]){const m=document.createElement("option");m.value=l,r.appendChild(m)}if(o){const l=`${i.branches?.length||0} branches, ${i.tags?.length||0} tags`;o.textContent=i.defaultBranch?`Blank clones the default branch (${i.defaultBranch}). ${l} available.`:`Blank clones the default branch. ${l} available.`}}const c=s.warnings?.[0];this._setCloneStatus(c?`${n}: ${c}`:`${n}: ready to clone.`,c?"warn":"ok")},async cloneCase(){const e=document.getElementById("cloneRepoUrl").value.trim(),t=document.getElementById("cloneCaseName").value.trim(),s=document.getElementById("cloneRepoRef").value.trim(),a=!!document.getElementById("cloneShallow")?.checked,n=document.getElementById("cloneCaseBrain")?.value||"",i=!!document.getElementById("cloneStartSession")?.checked;if(!e){this.showToast("Please enter a repository URL","error");return}if(!t){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(t)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}this._setCloneStatus(`Cloning ${e}\u2026 this can take a while for a large repository.`,"");try{const r=await(await fetch("/api/cases/clone",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,repository:e,...s?{ref:s}:{},...a?{shallow:!0}:{}})})).json();if(!r.success){this._setCloneStatus(r.error||"Clone failed.","err"),this.showToast(r.error||"Failed to clone repository","error");return}n&&this.setRunMode(n),this.closeCreateCaseModal(),await this.loadQuickStartCases(t),await this.saveLastUsedCase(t),this.showToast(`Cloned into case "${t}"`,"success");for(const c of r.data?.warnings||[])this.showToast(c,"warning");i&&await this.run()}catch(o){console.error("Failed to clone repository:",o),this._setCloneStatus(`Lost the connection while cloning: ${o.message}. If git finishes, the case still appears in the list.`,"warn"),this.showToast("Clone request interrupted \u2014 watch the case list","error")}},openLinkCasePathPicker(){const e=document.getElementById("linkCasePath");PathPicker.open({title:"Select Existing Project Folder",initialPath:e.value.trim(),directoriesOnly:!0,onSelect:t=>{e.value=t;const s=document.getElementById("linkCaseName");if(!s.value.trim()){const a=t.split("/").filter(Boolean).pop()||"";/^[\p{L}\p{N}_-]+$/u.test(a)&&(s.value=a)}e.focus(),e.setSelectionRange(t.length,t.length)}})},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(),l=document.getElementById("remoteHostJumpHost").value.trim(),m=document.getElementById("remoteHostExtraSshOptions").value.split(`
|
|
16
|
+
`).map(h=>h.trim()).filter(h=>h.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 h={id:s,label:s,host:a,username:n,...u?{port:u}:{},...r?{identityFile:r}:{},...c?{socksProxy:c}:{},...l?{jumpHost:l}:{},...m.length?{extraSshOptions:m}:{},...i?{commands:{codex:i}}:{}},d=await(await fetch("/api/remote-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)})).json();if(!d.success&&d.errorCode!=="ALREADY_EXISTS")throw new Error(d.error||"Failed to save remote host");const y=await(await fetch("/api/cases/remote-link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,hostId:s,remotePath:t})})).json();y.success?(this.closeCreateCaseModal(),this.showToast(`Remote case "${e}" linked`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(y.error||"Failed to link remote case","error")}catch(h){console.error("Failed to link remote case:",h),this.showToast("Failed to link remote case: "+h.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,l=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{l&&(l.textContent="Checking docker daemon + base image...");const m={};i&&(m.memory=i),o&&(m.cpus=o);const u={id:s,label:s,image:a,network:n,mountCredentials:r,resumeOnStart:c,...Object.keys(m).length?{resources:m}:{}};let h=await fetch("/api/docker-hosts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),g=await h.json();if(!g.success&&g.errorCode==="ALREADY_EXISTS"&&(h=await fetch(`/api/docker-hosts/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(u)}),g=await h.json()),!g.success)throw new Error(g.error||"Failed to save docker host");const p=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(p.success){this.closeCreateCaseModal();const y=p.data?.capsEnforced===!1?" (resource caps are advisory on this engine)":"";this.showToast(`Docker case "${e}" linked${y}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)}else l&&(l.textContent=p.error||"Failed to link docker case"),this.showToast(p.error||"Failed to link docker case","error")}catch(m){console.error("Failed to link docker case:",m),l&&(l.textContent=m.message),this.showToast("Failed to link docker case: "+m.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
17
|
<span style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${o}">${o} <span class="form-hint">(${i} MB)</span></span>
|
|
18
18
|
<span style="flex-shrink:0;">
|
|
19
19
|
<a class="btn-toolbar" href="/api/docker-exports/${encodeURIComponent(n.name)}" download>Download</a>
|
|
Binary file
|
|
Binary file
|